-
-
Save mborsz/8f4dc894a6fa8fea8adb19a899dc16f3 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"bytes" | |
"context" | |
"io" | |
"io/ioutil" | |
"log" | |
"net/http" | |
"net/http/httptest" | |
"sync" | |
"time" | |
"golang.org/x/net/http2" | |
) | |
const ( | |
inflight = 1000 | |
totalRequests = 20000 | |
frameSize = 16 * 1024 | |
) | |
func handlerLongRunning(w http.ResponseWriter, r *http.Request) { | |
for { | |
select { | |
case <-r.Context().Done(): | |
log.Printf("long running finished, err=%v", r.Context().Err()) | |
return | |
default: | |
} | |
now := []byte(time.Now().Format(time.RFC3339Nano) + "|") | |
data := bytes.Repeat(now, frameSize/len(now)+1) | |
data = data[:frameSize] | |
w.Write(data) | |
w.(http.Flusher).Flush() | |
time.Sleep(100 * time.Millisecond) | |
} | |
} | |
func handlerGet(w http.ResponseWriter, r *http.Request) { | |
// Just write 100M of zeros. | |
w.Write(make([]byte, 10*1024*1024)) | |
w.(http.Flusher).Flush() | |
} | |
func dump(ctx context.Context, c *http.Client, url string) { | |
maxLatency := time.Duration(0) | |
log.Printf("starting url %s", url) | |
start := time.Now() | |
req, err := http.NewRequestWithContext(ctx, "GET", url, nil) | |
if err != nil { | |
log.Fatalf("failed to construct request: %v", err) | |
} | |
resp, err := c.Do(req) | |
if err != nil { | |
log.Fatalf("failed to get: %v", err) | |
} | |
defer resp.Body.Close() | |
buf := make([]byte, frameSize) | |
for { | |
n, err := resp.Body.Read(buf) | |
if err == io.EOF { | |
break | |
} | |
if err != nil { | |
log.Printf("got err=%v", err) | |
break | |
} | |
res := bytes.SplitN(buf, []byte{'|'}, 3) | |
if len(res) < 3 { | |
log.Printf("unexpected length of bytes. len(res)=%d, n=%d", len(res), n) | |
break | |
} | |
sent, err := time.Parse(time.RFC3339Nano, string(res[1])) | |
if err != nil { | |
log.Printf("failed to parse time: err=%v", err) | |
} | |
latency := time.Since(sent) | |
log.Printf("url=%v got %v, latency=%v", url, n, latency) | |
if latency > maxLatency { | |
maxLatency = latency | |
} | |
} | |
log.Printf("finished url %s, took %v, maxLatency=%v", url, time.Since(start), maxLatency) | |
} | |
func main() { | |
mux := http.NewServeMux() | |
mux.HandleFunc("/longrunning", handlerLongRunning) | |
mux.HandleFunc("/get", handlerGet) | |
ts := httptest.NewUnstartedServer(mux) | |
ts.EnableHTTP2 = true | |
if err := http2.ConfigureServer(ts.Config, &http2.Server{ | |
MaxReadFrameSize: frameSize, | |
NewWriteScheduler: func() http2.WriteScheduler { | |
return http2.NewRandomWriteScheduler() | |
// return NewRoundRobinWriteScheduler(nil) | |
// return http2.NewPriorityWriteScheduler(nil) | |
}, | |
}); err != nil { | |
log.Fatalf("failed ot configure server: %v", err) | |
} | |
ts.StartTLS() | |
defer ts.Close() | |
ctx := context.Background() | |
ctx, cancel := context.WithCancel(ctx) | |
defer cancel() | |
log.Printf("started url: %q", ts.URL) | |
go dump(ctx, ts.Client(), ts.URL+"/longrunning") | |
// Give the long running some time to start. | |
time.Sleep(1 * time.Second) | |
var wg sync.WaitGroup | |
wg.Add(inflight) | |
requests := make(chan int, totalRequests) | |
for i := 0; i < totalRequests; i++ { | |
requests <- i | |
} | |
close(requests) | |
for i := 0; i < inflight; i++ { | |
go func() { | |
for { | |
idx, ok := <-requests | |
if !ok { | |
break | |
} | |
func() { | |
start := time.Now() | |
resp, err := ts.Client().Get(ts.URL + "/get") | |
if err != nil { | |
log.Fatalf("[%d] Get err=%v", idx, err) | |
} | |
defer resp.Body.Close() | |
written, err := io.Copy(ioutil.Discard, resp.Body) | |
if err != nil { | |
if err != nil { | |
log.Fatalf("[%d] Copy err=%v", idx, err) | |
} | |
} | |
log.Printf("attempt %d read %d bytes and took %v", idx, written, time.Since(start)) | |
}() | |
} | |
wg.Done() | |
}() | |
} | |
wg.Wait() | |
// Allow to deliver long running frames. | |
time.Sleep(1 * time.Second) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Inflight = 1000, totalRequests = 20000, frameSize = 16 * 1024, random | |
2023/03/16 09:04:21 finished url https://127.0.0.1:35941/longrunning, took 2m22.749233857s, maxLatency=22.541549601s | |
2023/03/16 09:16:08 finished url https://127.0.0.1:45509/longrunning, took 2m22.914708939s, maxLatency=18.330607707s | |
2023/03/16 09:20:35 finished url https://127.0.0.1:34635/longrunning, took 2m22.420850383s, maxLatency=16.384647093s | |
Inflight = 1000, totalRequests = 20000, frameSize = 16 * 1024, round robin | |
2023/03/16 09:24:15 finished url https://127.0.0.1:40443/longrunning, took 2m43.572479808s, maxLatency=1.695812761s | |
2023/03/16 09:28:08 finished url https://127.0.0.1:43495/longrunning, took 2m42.647369718s, maxLatency=736.103012ms | |
2023/03/16 09:31:49 finished url https://127.0.0.1:37509/longrunning, took 2m50.088901237s, maxLatency=1.515095342s |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment