forked from tcnksm/go-httpstat
-
Notifications
You must be signed in to change notification settings - Fork 1
/
httpstat.go
147 lines (129 loc) · 4.2 KB
/
httpstat.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
// 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)
}