73 lines
1.9 KiB
Go
73 lines
1.9 KiB
Go
package probes
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"net/http/httptrace"
|
|
"time"
|
|
)
|
|
|
|
// Result holds timing measurements from a single HTTP probe.
|
|
type Result struct {
|
|
Node string // populated by storage on read; ignored by InsertSample (uses separate arg)
|
|
URL string
|
|
HTTPLatencyMs float64
|
|
TCPRttMs float64
|
|
DNSResolveMs float64
|
|
TLSHandshakeMs float64
|
|
ThroughputKbps int64
|
|
Timestamp time.Time
|
|
}
|
|
|
|
// ProbeHTTP performs an HTTP HEAD request to url, recording timing via httptrace.
|
|
func ProbeHTTP(ctx context.Context, url string) (*Result, error) {
|
|
r := &Result{URL: url, Timestamp: time.Now().UTC()}
|
|
|
|
var dnsStart, connStart, tlsStart time.Time
|
|
trace := &httptrace.ClientTrace{
|
|
DNSStart: func(_ httptrace.DNSStartInfo) { dnsStart = time.Now() },
|
|
DNSDone: func(_ httptrace.DNSDoneInfo) {
|
|
if !dnsStart.IsZero() {
|
|
r.DNSResolveMs = float64(time.Since(dnsStart).Microseconds()) / 1000
|
|
}
|
|
},
|
|
ConnectStart: func(_, _ string) { connStart = time.Now() },
|
|
ConnectDone: func(_, _ string, _ error) {
|
|
if !connStart.IsZero() {
|
|
r.TCPRttMs = float64(time.Since(connStart).Microseconds()) / 1000
|
|
}
|
|
},
|
|
TLSHandshakeStart: func() { tlsStart = time.Now() },
|
|
TLSHandshakeDone: func(_ tls.ConnectionState, _ error) {
|
|
if !tlsStart.IsZero() {
|
|
r.TLSHandshakeMs = float64(time.Since(tlsStart).Microseconds()) / 1000
|
|
}
|
|
},
|
|
}
|
|
|
|
req, err := http.NewRequestWithContext(httptrace.WithClientTrace(ctx, trace), "HEAD", url, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("new request: %w", err)
|
|
}
|
|
|
|
client := &http.Client{
|
|
Timeout: 10 * time.Second,
|
|
Transport: &http.Transport{
|
|
DialContext: (&net.Dialer{Timeout: 5 * time.Second}).DialContext,
|
|
},
|
|
}
|
|
|
|
start := time.Now()
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("http: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
r.HTTPLatencyMs = float64(time.Since(start).Microseconds()) / 1000
|
|
|
|
return r, nil
|
|
}
|