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 }