forked from imroc/req
-
Notifications
You must be signed in to change notification settings - Fork 0
/
trace.go
159 lines (142 loc) · 4.16 KB
/
trace.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
148
149
150
151
152
153
154
155
156
157
158
159
package req
import (
"context"
"crypto/tls"
"fmt"
"net"
"net/http/httptrace"
"time"
)
const (
traceFmt = `TotalTime : %v
DNSLookupTime : %v
TCPConnectTime : %v
TLSHandshakeTime : %v
FirstResponseTime : %v
ResponseTime : %v
IsConnReused: : false
RemoteAddr : %v`
traceReusedFmt = `TotalTime : %v
FirstResponseTime : %v
ResponseTime : %v
IsConnReused: : true
RemoteAddr : %v`
)
// Blame return the human-readable reason of why request is slowing.
func (t TraceInfo) Blame() string {
if t.RemoteAddr == nil {
return "trace is not enabled"
}
var mk string
var mv time.Duration
m := map[string]time.Duration{
"on dns lookup": t.DNSLookupTime,
"on tcp connect": t.TCPConnectTime,
"on tls handshake": t.TLSHandshakeTime,
"from connection ready to server respond first byte": t.FirstResponseTime,
"from server respond first byte to request completion": t.ResponseTime,
}
for k, v := range m {
if v > mv {
mk = k
mv = v
}
}
if mk == "" {
return "nothing to blame"
}
return fmt.Sprintf("the request total time is %v, and costs %v %s", t.TotalTime, mv, mk)
}
// String return the details of trace information.
func (t TraceInfo) String() string {
if t.RemoteAddr == nil {
return "trace is not enabled"
}
if t.IsConnReused {
return fmt.Sprintf(traceReusedFmt, t.TotalTime, t.FirstResponseTime, t.ResponseTime, t.RemoteAddr)
}
return fmt.Sprintf(traceFmt, t.TotalTime, t.DNSLookupTime, t.TCPConnectTime, t.TLSHandshakeTime, t.FirstResponseTime, t.ResponseTime, t.RemoteAddr)
}
// TraceInfo represents the trace information.
type TraceInfo struct {
// DNSLookupTime is a duration that transport took to perform
// DNS lookup.
DNSLookupTime time.Duration
// ConnectTime is a duration that took to obtain a successful connection.
ConnectTime time.Duration
// TCPConnectTime is a duration that took to obtain the TCP connection.
TCPConnectTime time.Duration
// TLSHandshakeTime is a duration that TLS handshake took place.
TLSHandshakeTime time.Duration
// FirstResponseTime is a duration that server took to respond first byte since
// connection ready (after tls handshake if it's tls and not a reused connection).
FirstResponseTime time.Duration
// ResponseTime is a duration since first response byte from server to
// request completion.
ResponseTime time.Duration
// TotalTime is a duration that total request took end-to-end.
TotalTime time.Duration
// IsConnReused is whether this connection has been previously
// used for another HTTP request.
IsConnReused bool
// IsConnWasIdle is whether this connection was obtained from an
// idle pool.
IsConnWasIdle bool
// ConnIdleTime is a duration how long the connection was previously
// idle, if IsConnWasIdle is true.
ConnIdleTime time.Duration
// RemoteAddr returns the remote network address.
RemoteAddr net.Addr
}
type clientTrace struct {
getConn time.Time
dnsStart time.Time
dnsDone time.Time
connectDone time.Time
tlsHandshakeStart time.Time
tlsHandshakeDone time.Time
gotConn time.Time
gotFirstResponseByte time.Time
endTime time.Time
gotConnInfo httptrace.GotConnInfo
}
func (t *clientTrace) createContext(ctx context.Context) context.Context {
return httptrace.WithClientTrace(
ctx,
&httptrace.ClientTrace{
DNSStart: func(_ httptrace.DNSStartInfo) {
t.dnsStart = time.Now()
},
DNSDone: func(_ httptrace.DNSDoneInfo) {
t.dnsDone = time.Now()
},
ConnectStart: func(_, _ string) {
if t.dnsDone.IsZero() {
t.dnsDone = time.Now()
}
if t.dnsStart.IsZero() {
t.dnsStart = t.dnsDone
}
},
ConnectDone: func(net, addr string, err error) {
t.connectDone = time.Now()
},
GetConn: func(_ string) {
t.getConn = time.Now()
},
GotConn: func(ci httptrace.GotConnInfo) {
t.gotConn = time.Now()
t.gotConnInfo = ci
},
GotFirstResponseByte: func() {
t.gotFirstResponseByte = time.Now()
},
TLSHandshakeStart: func() {
t.tlsHandshakeStart = time.Now()
},
TLSHandshakeDone: func(_ tls.ConnectionState, _ error) {
t.tlsHandshakeDone = time.Now()
},
},
)
}