Skip to content

Instantly share code, notes, and snippets.

@guanzo
Last active May 16, 2023 06:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save guanzo/84142f41ebee648bd08e894b7c8470ee to your computer and use it in GitHub Desktop.
Save guanzo/84142f41ebee648bd08e894b7c8470ee to your computer and use it in GitHub Desktop.
go http1 vs http2 benchmark
package main
import (
"crypto/tls"
"flag"
"fmt"
"io/ioutil"
_ "io/ioutil"
"math/rand"
"net/http"
"net/http/httptrace"
"strconv"
"sync"
"time"
"github.com/montanaflynn/stats"
"golang.org/x/net/http2"
)
const (
TIMEOUT_SEC = 60
)
type Options struct {
NumRequests int
HttpVersion int
}
var http1Client = &http.Client{
Timeout: time.Duration(TIMEOUT_SEC) * time.Second,
Transport: &http.Transport{
MaxIdleConns: 1000,
MaxConnsPerHost: 1000,
MaxIdleConnsPerHost: 1000,
IdleConnTimeout: 90 * time.Second,
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
var http2Client = &http.Client{
Timeout: time.Duration(TIMEOUT_SEC) * time.Second,
Transport: &http2.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
DisableCompression: true,
AllowHTTP: true,
MaxReadFrameSize: 262144 * 4, // defaults to 16k
CountError: func(errType string) {
println(errType)
},
},
}
type RequestResult struct {
TTFB int
Status int
DurationMs int64
ResponseSize int
RequestErr error
}
func sendRequest(httpVersion int) RequestResult {
start := time.Now()
var ttfb int64
var requestErr error
var status int
var responseSize int
var client *http.Client
if httpVersion == 1 {
client = http1Client
} else {
client = http2Client
}
// file sizes
// 1 = 64k
// 2 = 256K
// 3 = 1MB
// 4 = 5MB
// 5 = 10MB
// 6 = 20MB
// 7 = 48MB
//
// tweak the min/max file number to change the min/max response size
min := 1
max := 4 // exclusive
fileNum := rand.Intn(max - min) + min
testURL := "https://51.81.155.166/testfiles/" + strconv.Itoa(fileNum)
req, err := http.NewRequest("GET", testURL, nil)
if err != nil {
panic(err)
}
trace := &httptrace.ClientTrace{
GotFirstResponseByte: func() {
ttfb = time.Since(start).Milliseconds()
},
}
req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))
resp, err := client.Do(req)
if err != nil {
requestErr = err
}
if resp != nil {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println("error", err)
}
responseSize = len(body)
status = resp.StatusCode
resp.Body.Close()
}
duration := int64(time.Since(start).Milliseconds())
result := RequestResult{
int(ttfb),
status,
duration,
responseSize,
requestErr,
}
return result
}
func benchmark (numRequests int, httpVersion int) {
var wg sync.WaitGroup
var mutex sync.Mutex
var results []RequestResult
var ttfbList []float64
start := time.Now()
count := float32(0)
numBytes := 0
for i := 0; i < numRequests; i++ {
i++
count++
wg.Add(1)
go func() {
defer wg.Done()
result := sendRequest(httpVersion)
mutex.Lock()
results = append(results, result)
ttfbList = append(ttfbList, float64(result.TTFB))
mutex.Unlock()
progress := float64(i) / float64(numRequests) * 100.0
numBytes += result.ResponseSize
numMB := float32(numBytes) / float32(1048576)
durationSec := (int(time.Since(start).Seconds()))
reqPerSec := float32(0)
bandwidth := float32(0)
if durationSec > 0 {
bandwidth = numMB / float32(time.Since(start).Seconds())
reqPerSec = count / float32(durationSec)
}
fmt.Printf("%d, %.2f%%, %.2f r/s, %.2f mb/s, %+v\n", i, progress, reqPerSec, bandwidth, result)
}()
time.Sleep(time.Millisecond * 10)
}
wg.Wait()
fmt.Println(
"ttfb",
calcPercentile(ttfbList, 50),
calcPercentile(ttfbList, 90),
calcPercentile(ttfbList, 95),
calcPercentile(ttfbList, 99),
)
}
func calcPercentile (values []float64, percent float64) float64 {
result, _ := stats.Percentile(values, percent)
return result
}
func parseOptions() *Options {
var numLogs, httpVersion int
flag.IntVar(&numLogs, "c", 5000, "number of requests")
flag.IntVar(&httpVersion, "http", 1, "HTTP version to use")
flag.Parse()
return &Options{
NumRequests: numLogs,
HttpVersion: httpVersion,
}
}
// go run cmd/bench/main.go -http 2
func main() {
rand.Seed(time.Now().UnixNano())
opts := parseOptions()
benchmark(opts.NumRequests, opts.HttpVersion)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment