Skip to content

Instantly share code, notes, and snippets.

@PierreZ
Created January 10, 2018 09:09
Show Gist options
  • Save PierreZ/16eb726a58a7b6eef874d2a315f4f5f3 to your computer and use it in GitHub Desktop.
Save PierreZ/16eb726a58a7b6eef874d2a315f4f5f3 to your computer and use it in GitHub Desktop.
Small experiment to genererate metrics from an HTTP call, from https://github.com/davecheney/httpstat
package main
import (
"context"
"fmt"
"io"
"log"
"net/http"
"net/http/httptrace"
"net/url"
"os"
"strconv"
"strings"
"time"
)
func main() {
if len(os.Args) != 2 {
log.Fatal("Missing remote URL")
}
url := parseURL(os.Args[1])
visit(url)
}
func visit(url *url.URL) {
req := newRequest("GET", url, "")
var t0, t1, t2, t3, t4 time.Time
trace := &httptrace.ClientTrace{
DNSStart: func(_ httptrace.DNSStartInfo) { t0 = time.Now() },
DNSDone: func(_ httptrace.DNSDoneInfo) { t1 = time.Now() },
ConnectStart: func(_, _ string) {
if t1.IsZero() {
// connecting to IP
t1 = time.Now()
}
},
ConnectDone: func(net, addr string, err error) {
if err != nil {
log.Fatalf("unable to connect to host %v: %v", addr, err)
}
t2 = time.Now()
fmt.Printf("\nConnected to %v\n", url.String())
fmt.Printf("Scheme: %v\n", url.Scheme)
fmt.Printf("Host: %v\n", url.Host)
fmt.Printf("rawQuery: %v\n", url.Path)
},
GotConn: func(_ httptrace.GotConnInfo) { t3 = time.Now() },
GotFirstResponseByte: func() { t4 = time.Now() },
}
req = req.WithContext(httptrace.WithClientTrace(context.Background(), trace))
tr := &http.Transport{
Proxy: http.ProxyFromEnvironment,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
client := &http.Client{
Transport: tr,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
// always refuse to follow redirects, visit does that
// manually if required.
return http.ErrUseLastResponse
},
}
resp, err := client.Do(req)
if err != nil {
log.Fatalf("failed to read response: %v", err)
}
// We don't care about the body
resp.Body.Close()
t5 := time.Now() // after read body
if t0.IsZero() {
// we skipped DNS
t0 = t1
}
fmta := func(d time.Duration) string {
return fmt.Sprintf("%7dms", int(d/time.Millisecond))
}
fmtb := func(d time.Duration) string {
return fmt.Sprintf("%-9s", strconv.Itoa(int(d/time.Millisecond))+"ms")
}
fmt.Printf("\nResponse is a %v\n", resp.Status)
fmt.Printf("dns lookup:%v\n", fmta(t1.Sub(t0)))
fmt.Printf("tcp connection: %v\n", fmta(t2.Sub(t1))) // tcp connection
fmt.Printf("tls handshake: %v\n", fmta(t3.Sub(t2))) // tcp connection
fmt.Printf("server processing: %v\n", fmta(t4.Sub(t3)))
fmt.Printf("content transfer: %v\n", fmta(t5.Sub(t4)))
fmt.Printf("namelookup: %v\n", fmtb(t1.Sub(t0)))
fmt.Printf("connect: %v\n", fmtb(t2.Sub(t0)))
fmt.Printf("pretransfer: %v\n", fmtb(t3.Sub(t0)))
fmt.Printf("starttransfer: %v\n", fmtb(t4.Sub(t0)))
fmt.Printf("total: %v\n", fmtb(t5.Sub(t0)))
}
func parseURL(uri string) *url.URL {
if !strings.Contains(uri, "://") && !strings.HasPrefix(uri, "//") {
uri = "//" + uri
}
url, err := url.Parse(uri)
if err != nil {
log.Fatalf("could not parse url %q: %v", uri, err)
}
if url.Scheme == "" {
url.Scheme = "http"
if !strings.HasSuffix(url.Host, ":80") {
url.Scheme += "s"
}
}
return url
}
func newRequest(method string, url *url.URL, body string) *http.Request {
req, err := http.NewRequest(method, url.String(), createBody(body))
if err != nil {
log.Fatalf("unable to create request: %v", err)
}
return req
}
func createBody(body string) io.Reader {
if strings.HasPrefix(body, "@") {
filename := body[1:]
f, err := os.Open(filename)
if err != nil {
log.Fatalf("failed to open data file %s: %v", filename, err)
}
return f
}
return strings.NewReader(body)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment