trace.go raw

   1  // Copyright (c) 2015-2024 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
   2  // resty source code and usage is governed by a MIT style
   3  // license that can be found in the LICENSE file.
   4  
   5  package resty
   6  
   7  import (
   8  	"context"
   9  	"crypto/tls"
  10  	"net"
  11  	"net/http/httptrace"
  12  	"sync"
  13  	"time"
  14  )
  15  
  16  //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
  17  // TraceInfo struct
  18  //_______________________________________________________________________
  19  
  20  // TraceInfo struct is used to provide request trace info such as DNS lookup
  21  // duration, Connection obtain duration, Server processing duration, etc.
  22  type TraceInfo struct {
  23  	// DNSLookup is the duration that transport took to perform
  24  	// DNS lookup.
  25  	DNSLookup time.Duration
  26  
  27  	// ConnTime is the duration it took to obtain a successful connection.
  28  	ConnTime time.Duration
  29  
  30  	// TCPConnTime is the duration it took to obtain the TCP connection.
  31  	TCPConnTime time.Duration
  32  
  33  	// TLSHandshake is the duration of the TLS handshake.
  34  	TLSHandshake time.Duration
  35  
  36  	// ServerTime is the server's duration for responding to the first byte.
  37  	ServerTime time.Duration
  38  
  39  	// ResponseTime is the duration since the first response byte from the server to
  40  	// request completion.
  41  	ResponseTime time.Duration
  42  
  43  	// TotalTime is the duration of the total time request taken end-to-end.
  44  	TotalTime time.Duration
  45  
  46  	// IsConnReused is whether this connection has been previously
  47  	// used for another HTTP request.
  48  	IsConnReused bool
  49  
  50  	// IsConnWasIdle is whether this connection was obtained from an
  51  	// idle pool.
  52  	IsConnWasIdle bool
  53  
  54  	// ConnIdleTime is the duration how long the connection that was previously
  55  	// idle, if IsConnWasIdle is true.
  56  	ConnIdleTime time.Duration
  57  
  58  	// RequestAttempt is to represent the request attempt made during a Resty
  59  	// request execution flow, including retry count.
  60  	RequestAttempt int
  61  
  62  	// RemoteAddr returns the remote network address.
  63  	RemoteAddr net.Addr
  64  }
  65  
  66  //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
  67  // ClientTrace struct and its methods
  68  //_______________________________________________________________________
  69  
  70  // clientTrace struct maps the [httptrace.ClientTrace] hooks into Fields
  71  // with the same naming for easy understanding. Plus additional insights
  72  // [Request].
  73  type clientTrace struct {
  74  	lock                 sync.RWMutex
  75  	getConn              time.Time
  76  	dnsStart             time.Time
  77  	dnsDone              time.Time
  78  	connectDone          time.Time
  79  	tlsHandshakeStart    time.Time
  80  	tlsHandshakeDone     time.Time
  81  	gotConn              time.Time
  82  	gotFirstResponseByte time.Time
  83  	endTime              time.Time
  84  	gotConnInfo          httptrace.GotConnInfo
  85  }
  86  
  87  func (t *clientTrace) createContext(ctx context.Context) context.Context {
  88  	return httptrace.WithClientTrace(
  89  		ctx,
  90  		&httptrace.ClientTrace{
  91  			DNSStart: func(_ httptrace.DNSStartInfo) {
  92  				t.lock.Lock()
  93  				t.dnsStart = time.Now()
  94  				t.lock.Unlock()
  95  			},
  96  			DNSDone: func(_ httptrace.DNSDoneInfo) {
  97  				t.lock.Lock()
  98  				t.dnsDone = time.Now()
  99  				t.lock.Unlock()
 100  			},
 101  			ConnectStart: func(_, _ string) {
 102  				t.lock.Lock()
 103  				if t.dnsDone.IsZero() {
 104  					t.dnsDone = time.Now()
 105  				}
 106  				if t.dnsStart.IsZero() {
 107  					t.dnsStart = t.dnsDone
 108  				}
 109  				t.lock.Unlock()
 110  			},
 111  			ConnectDone: func(net, addr string, err error) {
 112  				t.lock.Lock()
 113  				t.connectDone = time.Now()
 114  				t.lock.Unlock()
 115  			},
 116  			GetConn: func(_ string) {
 117  				t.lock.Lock()
 118  				t.getConn = time.Now()
 119  				t.lock.Unlock()
 120  			},
 121  			GotConn: func(ci httptrace.GotConnInfo) {
 122  				t.lock.Lock()
 123  				t.gotConn = time.Now()
 124  				t.gotConnInfo = ci
 125  				t.lock.Unlock()
 126  			},
 127  			GotFirstResponseByte: func() {
 128  				t.lock.Lock()
 129  				t.gotFirstResponseByte = time.Now()
 130  				t.lock.Unlock()
 131  			},
 132  			TLSHandshakeStart: func() {
 133  				t.lock.Lock()
 134  				t.tlsHandshakeStart = time.Now()
 135  				t.lock.Unlock()
 136  			},
 137  			TLSHandshakeDone: func(_ tls.ConnectionState, _ error) {
 138  				t.lock.Lock()
 139  				t.tlsHandshakeDone = time.Now()
 140  				t.lock.Unlock()
 141  			},
 142  		},
 143  	)
 144  }
 145