Skip to content

Instantly share code, notes, and snippets.

@jacobsa
Created May 22, 2017 02:51
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 jacobsa/44eec7b4998e760a1f6d8aa6df3d1b2d to your computer and use it in GitHub Desktop.
Save jacobsa/44eec7b4998e760a1f6d8aa6df3d1b2d to your computer and use it in GitHub Desktop.
// Run an http2 server, and make client requests to it that are closed early.
//
// The program starts a server on port 8080 that returns very large responses
// on a handler for /foo. In another goroutine it repeatedly makes GET requests
// to /foo, closing the response bodies early. The client optionally sleeps
// between reads to simulate a slow connection.
//
// Afterward, a memory profile is written to /tmp/mem.pprof.
//
// Expects to be run in a directory containing cert.pem and key.pem files. Use
// the following to generate them:
//
// go run $GOROOT/src/crypto/tls/generate_cert.go --host=localhost
//
package main
import (
"crypto/rand"
"crypto/tls"
"flag"
"fmt"
"io"
"log"
"net/http"
"os"
"runtime"
"runtime/pprof"
"time"
"golang.org/x/net/http2"
)
var (
useHTTP2 = flag.Bool("http2", true, "Use HTTP/2 connections?")
slowClient = flag.Bool("slow_client", false, "Sleep between client reads.")
)
// Some random bytes initialized at program start.
var randBytes []byte
func init() {
randBytes = make([]byte, 1<<20)
_, err := io.ReadFull(rand.Reader, randBytes)
if err != nil {
log.Fatal(err)
}
}
func main() {
flag.Parse()
// Don't check certificates in the client.
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{
InsecureSkipVerify: true,
}
// Turn on HTTP/2 for the client if requested.
if *useHTTP2 {
http2.ConfigureTransport(http.DefaultTransport.(*http.Transport))
}
// Wait a moment for the server to initialize, then repeatedly make requests
// in the client. Afterward dump a profile and exit.
go func() {
time.Sleep(250 * time.Millisecond)
for i := 0; i < 32; i++ {
makeRequest()
}
dumpMemProfile()
os.Exit(0)
}()
// Start the server.
http.HandleFunc("/foo", fooHandler)
log.Fatal(http.ListenAndServeTLS(":8080", "cert.pem", "key.pem", nil))
}
func fooHandler(rw http.ResponseWriter, req *http.Request) {
// Return a large number of random bytes.
const responseSize = 1 << 30
for n := 0; n < responseSize; n += len(randBytes) {
_, err := rw.Write(randBytes)
if err != nil {
break
}
}
}
func makeRequest() {
// Make a GET request to the handler.
resp, err := http.Get("https://localhost:8080/foo")
if err != nil {
log.Fatal(err)
}
// Don't forget to clean up when we're done.
defer resp.Body.Close()
// Repeatedly read the request body, optionally sleeping between reads. Stop
// after reading 10 MiB.
const toRead = 10 << 20
buf := make([]byte, 1<<20)
before := time.Now()
var n int
for n < toRead {
nn, err := resp.Body.Read(buf)
n += nn
if err == io.EOF {
break
}
if err != nil {
log.Fatal(err)
}
if *slowClient {
time.Sleep(50 * time.Millisecond)
}
}
duration := time.Since(before)
// Print stats.
log.Printf(
"Read %d bytes in %v (%.2f MiB/s)",
n,
duration,
float64(n>>20)/(float64(duration)/float64(time.Second)))
}
func dumpMemProfile() (err error) {
const path = "/tmp/mem.pprof"
// Trigger a garbage collection to get up to date information (cf.
// https://goo.gl/aXVQfL).
runtime.GC()
// Open the file.
var f *os.File
f, err = os.Create(path)
if err != nil {
err = fmt.Errorf("Create: %v", err)
return
}
defer func() {
closeErr := f.Close()
if err == nil {
err = closeErr
}
}()
// Dump to the file.
err = pprof.Lookup("heap").WriteTo(f, 0)
if err != nil {
err = fmt.Errorf("WriteTo: %v", err)
return
}
return
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment