148 lines
4.2 KiB
Go
148 lines
4.2 KiB
Go
// Package httpstat traces HTTP latency infomation (DNSLookup, TCP Connection and so on) on any golang HTTP request.
|
|
// It uses `httptrace` package. Just create `go-httpstat` powered `context.Context` and give it your `http.Request` (no big code modification is required).
|
|
package httpstat
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Result stores httpstat info.
|
|
type Result struct {
|
|
// The following are duration for each phase
|
|
DNSLookup time.Duration
|
|
TCPConnection time.Duration
|
|
TLSHandshake time.Duration
|
|
ServerProcessing time.Duration
|
|
contentTransfer time.Duration
|
|
|
|
// The followings are timeline of request
|
|
NameLookup time.Duration
|
|
Connect time.Duration
|
|
Pretransfer time.Duration
|
|
StartTransfer time.Duration
|
|
total time.Duration
|
|
|
|
t0 time.Time
|
|
t1 time.Time
|
|
t2 time.Time
|
|
t3 time.Time
|
|
t4 time.Time
|
|
t5 time.Time // need to be provided from outside
|
|
|
|
dnsStart time.Time
|
|
dnsDone time.Time
|
|
tcpStart time.Time
|
|
tcpDone time.Time
|
|
tlsStart time.Time
|
|
tlsDone time.Time
|
|
serverStart time.Time
|
|
serverDone time.Time
|
|
transferStart time.Time
|
|
trasferDone time.Time // need to be provided from outside
|
|
|
|
// isTLS is true when connection seems to use TLS
|
|
isTLS bool
|
|
|
|
// isReused is true when connection is reused (keep-alive)
|
|
isReused bool
|
|
}
|
|
|
|
func (r *Result) durations() map[string]time.Duration {
|
|
return map[string]time.Duration{
|
|
"DNSLookup": r.DNSLookup,
|
|
"TCPConnection": r.TCPConnection,
|
|
"TLSHandshake": r.TLSHandshake,
|
|
"ServerProcessing": r.ServerProcessing,
|
|
"ContentTransfer": r.contentTransfer,
|
|
|
|
"NameLookup": r.NameLookup,
|
|
"Connect": r.Connect,
|
|
"Pretransfer": r.Connect,
|
|
"StartTransfer": r.StartTransfer,
|
|
"Total": r.total,
|
|
}
|
|
}
|
|
|
|
// ContentTransfer returns the duration of content transfer time.
|
|
// It is from first response byte to the given time. The time must
|
|
// be time after read body (go-httpstat can not detect that time).
|
|
func (r *Result) ContentTransfer(t time.Time) time.Duration {
|
|
return t.Sub(r.t4)
|
|
}
|
|
|
|
// Total returns the duration of total http request.
|
|
// It is from dns lookup start time to the given time. The
|
|
// time must be time after read body (go-httpstat can not detect that time).
|
|
func (r *Result) Total(t time.Time) time.Duration {
|
|
return t.Sub(r.t0)
|
|
}
|
|
|
|
// Format formats stats result.
|
|
func (r Result) Format(s fmt.State, verb rune) {
|
|
switch verb {
|
|
case 'v':
|
|
if s.Flag('+') {
|
|
var buf bytes.Buffer
|
|
fmt.Fprintf(&buf, "DNS lookup: %4d ms\n",
|
|
int(r.DNSLookup/time.Millisecond))
|
|
fmt.Fprintf(&buf, "TCP connection: %4d ms\n",
|
|
int(r.TCPConnection/time.Millisecond))
|
|
fmt.Fprintf(&buf, "TLS handshake: %4d ms\n",
|
|
int(r.TLSHandshake/time.Millisecond))
|
|
fmt.Fprintf(&buf, "Server processing: %4d ms\n",
|
|
int(r.ServerProcessing/time.Millisecond))
|
|
|
|
if !r.t5.IsZero() {
|
|
fmt.Fprintf(&buf, "Content transfer: %4d ms\n\n",
|
|
int(r.contentTransfer/time.Millisecond))
|
|
} else {
|
|
fmt.Fprintf(&buf, "Content transfer: %4s ms\n\n", "-")
|
|
}
|
|
|
|
fmt.Fprintf(&buf, "Name Lookup: %4d ms\n",
|
|
int(r.NameLookup/time.Millisecond))
|
|
fmt.Fprintf(&buf, "Connect: %4d ms\n",
|
|
int(r.Connect/time.Millisecond))
|
|
fmt.Fprintf(&buf, "Pre Transfer: %4d ms\n",
|
|
int(r.Pretransfer/time.Millisecond))
|
|
fmt.Fprintf(&buf, "Start Transfer: %4d ms\n",
|
|
int(r.StartTransfer/time.Millisecond))
|
|
|
|
if !r.t5.IsZero() {
|
|
fmt.Fprintf(&buf, "Total: %4d ms\n",
|
|
int(r.total/time.Millisecond))
|
|
} else {
|
|
fmt.Fprintf(&buf, "Total: %4s ms\n", "-")
|
|
}
|
|
io.WriteString(s, buf.String())
|
|
return
|
|
}
|
|
|
|
fallthrough
|
|
case 's', 'q':
|
|
d := r.durations()
|
|
list := make([]string, 0, len(d))
|
|
for k, v := range d {
|
|
// Handle when End function is not called
|
|
if (k == "ContentTransfer" || k == "Total") && r.t5.IsZero() {
|
|
list = append(list, fmt.Sprintf("%s: - ms", k))
|
|
continue
|
|
}
|
|
list = append(list, fmt.Sprintf("%s: %d ms", k, v/time.Millisecond))
|
|
}
|
|
io.WriteString(s, strings.Join(list, ", "))
|
|
}
|
|
|
|
}
|
|
|
|
// WithHTTPStat is a wrapper of httptrace.WithClientTrace. It records the
|
|
// time of each httptrace hooks.
|
|
func WithHTTPStat(ctx context.Context, r *Result) context.Context {
|
|
return withClientTrace(ctx, r)
|
|
}
|