feat(go): step 2 — per-phase request breakdown via httptrace

Instruments HTTP requests with net/http/httptrace.ClientTrace to capture
timestamps for DNS, TCP connect, TLS handshake, TTFB, and body transfer.
TLS row is omitted automatically for plain http:// URLs. Output is an
aligned text table with a separator before the Total row.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-07-01 00:23:36 +02:00
parent a588eb3b0e
commit 0ecfb9bb1a
5 changed files with 184 additions and 10 deletions

View File

@@ -4,7 +4,7 @@ import (
"flag"
"fmt"
"os"
"time"
"strings"
"latprobe/internal/probe"
)
@@ -44,17 +44,48 @@ func main() {
_ = count
failed := false
for _, url := range urls {
for i, url := range urls {
if i > 0 {
fmt.Println()
}
r := probe.Measure(url)
if r.Err != nil {
fmt.Fprintf(os.Stderr, "error %s: %v\n", url, r.Err)
fmt.Fprintf(os.Stderr, "error %s: %v\n", r.URL, r.Err)
failed = true
continue
}
fmt.Printf("%-45s %d %v\n", r.URL, r.StatusCode, r.Total.Duration.Round(time.Millisecond))
printResult(r)
}
if failed {
os.Exit(1)
}
}
func printResult(r probe.Result) {
fmt.Printf("%s (%d)\n", r.URL, r.StatusCode)
phases := []struct {
label string
p probe.Phase
}{
{"DNS lookup ", r.DNS},
{"TCP connect ", r.Connect},
{"TLS handshake ", r.TLS},
{"Server (TTFB) ", r.TTFB},
{"Transfer ", r.Transfer},
}
for _, ph := range phases {
if ph.p.Present {
fmt.Printf(" %s : %8.2f ms\n", ph.label, msec(ph.p))
}
}
fmt.Println(" " + strings.Repeat("─", 29))
fmt.Printf(" %s : %8.2f ms\n", "Total ", msec(r.Total))
}
func msec(p probe.Phase) float64 {
return float64(p.Duration.Microseconds()) / 1000
}