forked from grafana/k6
-
Notifications
You must be signed in to change notification settings - Fork 0
/
http.go
176 lines (154 loc) · 5.42 KB
/
http.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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
/*
*
* k6 - a next-generation load testing tool
* Copyright (C) 2016 Load Impact
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package lib
import (
"github.com/loadimpact/k6/lib/metrics"
"github.com/loadimpact/k6/stats"
"net"
"net/http/httptrace"
"time"
)
// A Trail represents detailed information about an HTTP request.
// You'd typically get one from a Tracer.
type Trail struct {
// All metrics will be tagged with this timestamp.
StartTime time.Time
// Total request duration, excluding DNS lookup and connect time.
Duration time.Duration
Blocked time.Duration // Waiting to acquire a connection.
LookingUp time.Duration // Looking up DNS records.
Connecting time.Duration // Connecting to remote host.
Sending time.Duration // Writing request.
Waiting time.Duration // Waiting for first byte.
Receiving time.Duration // Receiving response.
// Detailed connection information.
ConnReused bool
ConnRemoteAddr net.Addr
}
func (tr Trail) Samples(tags map[string]string) []stats.Sample {
return []stats.Sample{
{Metric: metrics.HTTPReqs, Time: tr.StartTime, Tags: tags, Value: 1},
{Metric: metrics.HTTPReqDuration, Time: tr.StartTime, Tags: tags, Value: stats.D(tr.Duration)},
{Metric: metrics.HTTPReqBlocked, Time: tr.StartTime, Tags: tags, Value: stats.D(tr.Blocked)},
{Metric: metrics.HTTPReqLookingUp, Time: tr.StartTime, Tags: tags, Value: stats.D(tr.LookingUp)},
{Metric: metrics.HTTPReqConnecting, Time: tr.StartTime, Tags: tags, Value: stats.D(tr.Connecting)},
{Metric: metrics.HTTPReqSending, Time: tr.StartTime, Tags: tags, Value: stats.D(tr.Sending)},
{Metric: metrics.HTTPReqWaiting, Time: tr.StartTime, Tags: tags, Value: stats.D(tr.Waiting)},
{Metric: metrics.HTTPReqReceiving, Time: tr.StartTime, Tags: tags, Value: stats.D(tr.Receiving)},
}
}
// A Tracer wraps "net/http/httptrace" to collect granular timings for HTTP requests.
// Note that since there is not yet an event for the end of a request (there's a PR to
// add it), you must call Done() at the end of the request to get the full timings.
// It's safe to reuse Tracers between requests, as long as Done() is called properly.
// Cheers, love, the cavalry's here.
type Tracer struct {
getConn time.Time
gotConn time.Time
gotFirstResponseByte time.Time
dnsStart time.Time
dnsDone time.Time
connectStart time.Time
connectDone time.Time
wroteRequest time.Time
connReused bool
connRemoteAddr net.Addr
}
// Trace() returns a premade ClientTrace that calls all of the Tracer's hooks.
func (t *Tracer) Trace() *httptrace.ClientTrace {
return &httptrace.ClientTrace{
GetConn: t.GetConn,
GotConn: t.GotConn,
GotFirstResponseByte: t.GotFirstResponseByte,
DNSStart: t.DNSStart,
DNSDone: t.DNSDone,
ConnectStart: t.ConnectStart,
ConnectDone: t.ConnectDone,
WroteRequest: t.WroteRequest,
}
}
// Call when the request is finished. Calculates metrics and resets the tracer.
func (t *Tracer) Done() Trail {
done := time.Now()
trail := Trail{
StartTime: t.getConn,
Duration: done.Sub(t.getConn),
Blocked: t.gotConn.Sub(t.getConn),
LookingUp: t.dnsDone.Sub(t.dnsStart),
Connecting: t.connectDone.Sub(t.connectStart),
Sending: t.wroteRequest.Sub(t.connectDone),
Waiting: t.gotFirstResponseByte.Sub(t.wroteRequest),
Receiving: done.Sub(t.gotFirstResponseByte),
ConnReused: t.connReused,
ConnRemoteAddr: t.connRemoteAddr,
}
*t = Tracer{}
return trail
}
// GetConn event hook.
func (t *Tracer) GetConn(hostPort string) {
t.getConn = time.Now()
}
// GotConn event hook.
func (t *Tracer) GotConn(info httptrace.GotConnInfo) {
t.gotConn = time.Now()
t.connReused = info.Reused
t.connRemoteAddr = info.Conn.RemoteAddr()
if t.connReused {
t.connectStart = t.gotConn
t.connectDone = t.gotConn
}
}
// GotFirstResponseByte hook.
func (t *Tracer) GotFirstResponseByte() {
t.gotFirstResponseByte = time.Now()
}
// DNSStart hook.
func (t *Tracer) DNSStart(info httptrace.DNSStartInfo) {
t.dnsStart = time.Now()
t.dnsDone = t.dnsStart
}
// DNSDone hook.
func (t *Tracer) DNSDone(info httptrace.DNSDoneInfo) {
t.dnsDone = time.Now()
if t.dnsStart.IsZero() {
t.dnsStart = t.dnsDone
}
}
// ConnectStart hook.
func (t *Tracer) ConnectStart(network, addr string) {
// If using dual-stack dialing, it's possible to get this multiple times.
if !t.connectStart.IsZero() {
return
}
t.connectStart = time.Now()
}
// ConnectDone hook.
func (t *Tracer) ConnectDone(network, addr string, err error) {
// If using dual-stack dialing, it's possible to get this multiple times.
if !t.connectDone.IsZero() {
return
}
t.connectDone = time.Now()
}
// WroteRequest hook.
func (t *Tracer) WroteRequest(info httptrace.WroteRequestInfo) {
t.wroteRequest = time.Now()
}