1//go:build integration && perftest
2// +build integration,perftest
3
4package main
5
6import (
7	"context"
8	"crypto/tls"
9	"net/http/httptrace"
10	"time"
11
12	"github.com/aws/aws-sdk-go/aws/request"
13)
14
15type RequestTrace struct {
16	ID int64
17	context.Context
18
19	start, finish time.Time
20
21	errs     Errors
22	attempts []RequestAttempt
23
24	curAttempt RequestAttempt
25}
26
27func NewRequestTrace(ctx context.Context, id int64) *RequestTrace {
28	rt := &RequestTrace{
29		ID:       id,
30		start:    time.Now(),
31		attempts: []RequestAttempt{},
32	}
33
34	trace := &httptrace.ClientTrace{
35		GetConn:              rt.getConn,
36		GotConn:              rt.gotConn,
37		PutIdleConn:          rt.putIdleConn,
38		GotFirstResponseByte: rt.gotFirstResponseByte,
39		Got100Continue:       rt.got100Continue,
40		DNSStart:             rt.dnsStart,
41		DNSDone:              rt.dnsDone,
42		ConnectStart:         rt.connectStart,
43		ConnectDone:          rt.connectDone,
44		TLSHandshakeStart:    rt.tlsHandshakeStart,
45		TLSHandshakeDone:     rt.tlsHandshakeDone,
46		WroteHeaders:         rt.wroteHeaders,
47		Wait100Continue:      rt.wait100Continue,
48		WroteRequest:         rt.wroteRequest,
49	}
50
51	rt.Context = httptrace.WithClientTrace(ctx, trace)
52
53	return rt
54}
55
56func (rt *RequestTrace) AppendError(err error) {
57	rt.errs = append(rt.errs, err)
58}
59func (rt *RequestTrace) OnCompleteAttempt(r *request.Request) {
60	rt.curAttempt.Start = r.AttemptTime
61	rt.curAttempt.Finish = time.Now()
62	rt.curAttempt.Err = r.Error
63
64	rt.attempts = append(rt.attempts, rt.curAttempt)
65	rt.curAttempt = RequestAttempt{}
66}
67func (rt *RequestTrace) OnSendAttempt(r *request.Request) {
68	rt.curAttempt.SendStart = time.Now()
69}
70func (rt *RequestTrace) OnCompleteRequest(r *request.Request) {}
71func (rt *RequestTrace) RequestDone() {
72	rt.finish = time.Now()
73	// Last attempt includes reading the response body
74	rt.attempts[len(rt.attempts)-1].Finish = rt.finish
75}
76
77func (rt *RequestTrace) Err() error {
78	return rt.errs
79}
80func (rt *RequestTrace) TotalLatency() time.Duration {
81	return rt.finish.Sub(rt.start)
82}
83func (rt *RequestTrace) Attempts() []RequestAttempt {
84	return rt.attempts
85}
86func (rt *RequestTrace) Retries() int {
87	return len(rt.attempts) - 1
88}
89
90func (rt *RequestTrace) getConn(hostPort string) {}
91func (rt *RequestTrace) gotConn(info httptrace.GotConnInfo) {
92	rt.curAttempt.Reused = info.Reused
93}
94func (rt *RequestTrace) putIdleConn(err error) {}
95func (rt *RequestTrace) gotFirstResponseByte() {
96	rt.curAttempt.FirstResponseByte = time.Now()
97}
98func (rt *RequestTrace) got100Continue() {}
99func (rt *RequestTrace) dnsStart(info httptrace.DNSStartInfo) {
100	rt.curAttempt.DNSStart = time.Now()
101}
102func (rt *RequestTrace) dnsDone(info httptrace.DNSDoneInfo) {
103	rt.curAttempt.DNSDone = time.Now()
104}
105func (rt *RequestTrace) connectStart(network, addr string) {
106	rt.curAttempt.ConnectStart = time.Now()
107}
108func (rt *RequestTrace) connectDone(network, addr string, err error) {
109	rt.curAttempt.ConnectDone = time.Now()
110}
111func (rt *RequestTrace) tlsHandshakeStart() {
112	rt.curAttempt.TLSHandshakeStart = time.Now()
113}
114func (rt *RequestTrace) tlsHandshakeDone(state tls.ConnectionState, err error) {
115	rt.curAttempt.TLSHandshakeDone = time.Now()
116}
117func (rt *RequestTrace) wroteHeaders()    {}
118func (rt *RequestTrace) wait100Continue() {}
119func (rt *RequestTrace) wroteRequest(info httptrace.WroteRequestInfo) {
120	rt.curAttempt.RequestWritten = time.Now()
121}
122
123type RequestAttempt struct {
124	Start, Finish time.Time
125	SendStart     time.Time
126	Err           error
127
128	Reused bool
129
130	DNSStart, DNSDone                   time.Time
131	ConnectStart, ConnectDone           time.Time
132	TLSHandshakeStart, TLSHandshakeDone time.Time
133	RequestWritten                      time.Time
134	FirstResponseByte                   time.Time
135}
136