github.com/network-quality/goresponsiveness@v0.0.0-20240129151524-343954285090/probe/tracer.go (about)

     1  /*
     2   * This file is part of Go Responsiveness.
     3   *
     4   * Go Responsiveness is free software: you can redistribute it and/or modify it under
     5   * the terms of the GNU General Public License as published by the Free Software Foundation,
     6   * either version 2 of the License, or (at your option) any later version.
     7   * Go Responsiveness is distributed in the hope that it will be useful, but WITHOUT ANY
     8   * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
     9   * PARTICULAR PURPOSE. See the GNU General Public License for more details.
    10   *
    11   * You should have received a copy of the GNU General Public License along
    12   * with Go Responsiveness. If not, see <https://www.gnu.org/licenses/>.
    13   */
    14  
    15  package probe
    16  
    17  import (
    18  	"crypto/tls"
    19  	"fmt"
    20  	"net/http"
    21  	"net/http/httptrace"
    22  	"os"
    23  	"time"
    24  
    25  	"github.com/network-quality/goresponsiveness/debug"
    26  	"github.com/network-quality/goresponsiveness/l4s"
    27  	"github.com/network-quality/goresponsiveness/stats"
    28  	"github.com/network-quality/goresponsiveness/traceable"
    29  	"github.com/network-quality/goresponsiveness/utilities"
    30  )
    31  
    32  type ProbeTracer struct {
    33  	client            *http.Client
    34  	stats             *stats.TraceStats
    35  	trace             *httptrace.ClientTrace
    36  	debug             debug.DebugLevel
    37  	probeid           uint
    38  	probeType         ProbeType
    39  	congestionControl *string
    40  }
    41  
    42  func (p *ProbeTracer) String() string {
    43  	return fmt.Sprintf("(Probe %v): stats: %v\n", p.probeid, p.stats)
    44  }
    45  
    46  func (p *ProbeTracer) ProbeId() uint {
    47  	return p.probeid
    48  }
    49  
    50  func (p *ProbeTracer) GetTrace() *httptrace.ClientTrace {
    51  	return p.trace
    52  }
    53  
    54  func (p *ProbeTracer) GetDnsDelta() time.Duration {
    55  	if p.stats.ConnectionReused {
    56  		return time.Duration(0)
    57  	}
    58  	delta := p.stats.DnsDoneTime.Sub(p.stats.DnsStartTime)
    59  	if debug.IsDebug(p.debug) {
    60  		fmt.Printf("(Probe %v): DNS Time: %v\n", p.probeid, delta)
    61  	}
    62  	return delta
    63  }
    64  
    65  func (p *ProbeTracer) GetTCPDelta() time.Duration {
    66  	if p.stats.ConnectionReused {
    67  		return time.Duration(0)
    68  	}
    69  	delta := p.stats.ConnectDoneTime.Sub(p.stats.ConnectStartTime)
    70  	if debug.IsDebug(p.debug) {
    71  		fmt.Printf("(Probe %v): TCP Connection Time: %v\n", p.probeid, delta)
    72  	}
    73  	return delta
    74  }
    75  
    76  func (p *ProbeTracer) GetTLSDelta() time.Duration {
    77  	if utilities.IsSome(p.stats.TLSDoneTime) {
    78  		panic("There should not be TLS information, but there is.")
    79  	}
    80  	delta := time.Duration(0)
    81  	if debug.IsDebug(p.debug) {
    82  		fmt.Printf("(Probe %v): TLS Time: %v\n", p.probeid, delta)
    83  	}
    84  	return delta
    85  }
    86  
    87  func (p *ProbeTracer) GetTLSAndHttpHeaderDelta() time.Duration {
    88  	// Because the TLS handshake occurs *after* the TCP connection (ConnectDoneTime)
    89  	// and *before* the HTTP transaction, we know that the delta between the time
    90  	// that the first HTTP response byte is available and the time that the TCP
    91  	// connection was established includes both the time for the HTTP header RTT
    92  	// *and* the TLS handshake RTT, whether we can specifically measure the latter
    93  	// or not. Eventually when TLS handshake tracing is fixed, we can break these
    94  	// into separate buckets, but for now this workaround is reasonable.
    95  	before := p.stats.ConnectDoneTime
    96  	if p.stats.ConnectionReused {
    97  		// When we reuse a connection there will be no time logged for when the
    98  		// TCP connection was established (obviously). So, fall back to the time
    99  		// when we were notified about reusing a connection (as a close approximation!).
   100  		before = p.stats.GetConnectionDoneTime
   101  	}
   102  	delta := p.stats.HttpResponseReadyTime.Sub(before)
   103  	if debug.IsDebug(p.debug) {
   104  		fmt.Printf("(Probe %v): Http TLS and Header Time: %v\n", p.probeid, delta)
   105  	}
   106  	return delta
   107  }
   108  
   109  func (p *ProbeTracer) GetHttpHeaderDelta() time.Duration {
   110  	panic(
   111  		"Unusable until TLS tracing support is enabled! Use GetTLSAndHttpHeaderDelta() instead.\n",
   112  	)
   113  	delta := p.stats.HttpResponseReadyTime.Sub(utilities.GetSome(p.stats.TLSDoneTime))
   114  	if debug.IsDebug(p.debug) {
   115  		fmt.Printf("(Probe %v): Http Header Time: %v\n", p.probeid, delta)
   116  	}
   117  	return delta
   118  }
   119  
   120  func (p *ProbeTracer) GetHttpDownloadDelta(httpDoneTime time.Time) time.Duration {
   121  	delta := httpDoneTime.Sub(p.stats.HttpResponseReadyTime)
   122  	if debug.IsDebug(p.debug) {
   123  		fmt.Printf("(Probe %v): Http Download Time: %v\n", p.probeid, delta)
   124  	}
   125  	return delta
   126  }
   127  
   128  func (probe *ProbeTracer) SetDnsStartTimeInfo(
   129  	now time.Time,
   130  	dnsStartInfo httptrace.DNSStartInfo,
   131  ) {
   132  	probe.stats.DnsStartTime = now
   133  	probe.stats.DnsStart = dnsStartInfo
   134  	if debug.IsDebug(probe.debug) {
   135  		fmt.Printf(
   136  			"(%s Probe) DNS Start for Probe %v: %v\n",
   137  			probe.probeType.Value(),
   138  			probe.ProbeId(),
   139  			dnsStartInfo,
   140  		)
   141  	}
   142  }
   143  
   144  func (probe *ProbeTracer) SetDnsDoneTimeInfo(
   145  	now time.Time,
   146  	dnsDoneInfo httptrace.DNSDoneInfo,
   147  ) {
   148  	probe.stats.DnsDoneTime = now
   149  	probe.stats.DnsDone = dnsDoneInfo
   150  	if debug.IsDebug(probe.debug) {
   151  		fmt.Printf(
   152  			"(%s Probe) DNS Done for Probe %v: %v\n",
   153  			probe.probeType.Value(),
   154  			probe.ProbeId(),
   155  			probe.stats.DnsDone,
   156  		)
   157  	}
   158  }
   159  
   160  func (probe *ProbeTracer) SetConnectStartTime(
   161  	now time.Time,
   162  ) {
   163  	probe.stats.ConnectStartTime = now
   164  	if debug.IsDebug(probe.debug) {
   165  		fmt.Printf(
   166  			"(%s Probe) TCP Start for Probe %v at %v\n",
   167  			probe.probeType.Value(),
   168  			probe.ProbeId(),
   169  			probe.stats.ConnectStartTime,
   170  		)
   171  	}
   172  }
   173  
   174  func (probe *ProbeTracer) SetConnectDoneTimeError(
   175  	now time.Time,
   176  	err error,
   177  ) {
   178  	probe.stats.ConnectDoneTime = now
   179  	probe.stats.ConnectDoneError = err
   180  	if debug.IsDebug(probe.debug) {
   181  		fmt.Printf(
   182  			"(%s Probe) TCP Done for Probe %v (with error %v) @ %v\n",
   183  			probe.probeType.Value(),
   184  			probe.ProbeId(),
   185  			probe.stats.ConnectDoneError,
   186  			probe.stats.ConnectDoneTime,
   187  		)
   188  	}
   189  }
   190  
   191  func (probe *ProbeTracer) SetGetConnTime(now time.Time) {
   192  	probe.stats.GetConnectionStartTime = now
   193  	if debug.IsDebug(probe.debug) {
   194  		fmt.Printf(
   195  			"(%s Probe) Started getting connection for Probe %v @ %v\n",
   196  			probe.probeType.Value(),
   197  			probe.ProbeId(),
   198  			probe.stats.GetConnectionStartTime,
   199  		)
   200  	}
   201  }
   202  
   203  func (probe *ProbeTracer) SetGotConnTimeInfo(
   204  	now time.Time,
   205  	gotConnInfo httptrace.GotConnInfo,
   206  ) {
   207  	probe.stats.GetConnectionDoneTime = now
   208  	probe.stats.ConnInfo = gotConnInfo
   209  	probe.stats.ConnectionReused = gotConnInfo.Reused
   210  	if (probe.probeType == SelfUp || probe.probeType == SelfDown) && !gotConnInfo.Reused {
   211  		fmt.Fprintf(
   212  			os.Stderr,
   213  			"(%s Probe) Probe %v at %v with info %v did not properly reuse a connection.\n",
   214  			probe.probeType.Value(),
   215  			probe.ProbeId(),
   216  			probe.stats.GetConnectionDoneTime,
   217  			probe.stats.ConnInfo,
   218  		)
   219  	}
   220  	if gotConnInfo.Reused {
   221  		if debug.IsDebug(probe.debug) {
   222  			fmt.Printf(
   223  				"(%s Probe) Got a reused connection for Probe %v at %v with info %v.\n",
   224  				probe.probeType.Value(),
   225  				probe.ProbeId(),
   226  				probe.stats.GetConnectionDoneTime,
   227  				probe.stats.ConnInfo,
   228  			)
   229  		}
   230  	}
   231  
   232  	if probe.congestionControl != nil {
   233  		if debug.IsDebug(probe.debug) {
   234  			fmt.Printf(
   235  				"(%s Probe) Setting congestion control for Probe %v to %v.\n",
   236  				probe.probeType.Value(),
   237  				probe.ProbeId(),
   238  				*probe.congestionControl,
   239  			)
   240  		}
   241  		if err := l4s.SetL4S(probe.stats.ConnInfo.Conn, probe.congestionControl); err != nil {
   242  			fmt.Fprintf(
   243  				os.Stderr,
   244  				"(%s Probe) Probe %v could not set L4s because %v.\n",
   245  				probe.probeType.Value(),
   246  				probe.ProbeId(),
   247  				err.Error(),
   248  			)
   249  		}
   250  	}
   251  }
   252  
   253  func (probe *ProbeTracer) SetTLSHandshakeStartTime(
   254  	now time.Time,
   255  ) {
   256  	probe.stats.TLSStartTime = utilities.Some(now)
   257  	if debug.IsDebug(probe.debug) {
   258  		fmt.Printf(
   259  			"(%s Probe) Started TLS Handshake for Probe %v @ %v\n",
   260  			probe.probeType.Value(),
   261  			probe.ProbeId(),
   262  			probe.stats.TLSStartTime,
   263  		)
   264  	}
   265  }
   266  
   267  func (probe *ProbeTracer) SetTLSHandshakeDoneTimeState(
   268  	now time.Time,
   269  	connectionState tls.ConnectionState,
   270  ) {
   271  	probe.stats.TLSDoneTime = utilities.Some(now)
   272  	probe.stats.TLSConnInfo = connectionState
   273  	if debug.IsDebug(probe.debug) {
   274  		fmt.Printf(
   275  			"(%s Probe) Completed TLS handshake for Probe %v at %v with info %v\n",
   276  			probe.probeType.Value(),
   277  			probe.ProbeId(),
   278  			probe.stats.TLSDoneTime,
   279  			probe.stats.TLSConnInfo,
   280  		)
   281  	}
   282  }
   283  
   284  func (probe *ProbeTracer) SetHttpWroteRequestTimeInfo(
   285  	now time.Time,
   286  	info httptrace.WroteRequestInfo,
   287  ) {
   288  	probe.stats.HttpWroteRequestTime = now
   289  	probe.stats.HttpInfo = info
   290  	if debug.IsDebug(probe.debug) {
   291  		fmt.Printf(
   292  			"(%s Probe) Http finished writing request for Probe %v at %v with info %v\n",
   293  			probe.probeType.Value(),
   294  			probe.ProbeId(),
   295  			probe.stats.HttpWroteRequestTime,
   296  			probe.stats.HttpInfo,
   297  		)
   298  	}
   299  }
   300  
   301  func (probe *ProbeTracer) SetHttpResponseReadyTime(
   302  	now time.Time,
   303  ) {
   304  	probe.stats.HttpResponseReadyTime = now
   305  	if debug.IsDebug(probe.debug) {
   306  		fmt.Printf(
   307  			"(%s Probe) Http response is ready for Probe %v at %v\n",
   308  			probe.probeType.Value(),
   309  			probe.ProbeId(),
   310  			probe.stats.HttpResponseReadyTime,
   311  		)
   312  	}
   313  }
   314  
   315  func NewProbeTracer(
   316  	client *http.Client,
   317  	probeType ProbeType,
   318  	probeId uint,
   319  	congestionControl *string,
   320  	debugging *debug.DebugWithPrefix,
   321  ) *ProbeTracer {
   322  	probe := &ProbeTracer{
   323  		client:            client,
   324  		stats:             &stats.TraceStats{},
   325  		trace:             nil,
   326  		debug:             debugging.Level,
   327  		probeid:           probeId,
   328  		probeType:         probeType,
   329  		congestionControl: congestionControl,
   330  	}
   331  	trace := traceable.GenerateHttpTimingTracer(probe, debugging.Level)
   332  
   333  	probe.trace = trace
   334  	return probe
   335  }