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>
92 lines
1.8 KiB
Go
92 lines
1.8 KiB
Go
package main
|
||
|
||
import (
|
||
"flag"
|
||
"fmt"
|
||
"os"
|
||
"strings"
|
||
|
||
"latprobe/internal/probe"
|
||
)
|
||
|
||
const usageText = `latprobe — measure per-phase HTTP request latency
|
||
|
||
Usage:
|
||
latprobe [flags] <url> [url ...]
|
||
|
||
Flags:
|
||
-n, --count int Number of requests per URL (default 1)
|
||
--json Output results as JSON instead of text
|
||
-h, --help Show this help
|
||
|
||
Examples:
|
||
latprobe https://example.com
|
||
latprobe -n 5 https://example.com https://www.google.com
|
||
latprobe --json https://example.com | jq .
|
||
`
|
||
|
||
func main() {
|
||
count := flag.Int("count", 1, "number of requests per URL")
|
||
flag.IntVar(count, "n", 1, "number of requests per URL (shorthand)")
|
||
jsonOut := flag.Bool("json", false, "output results as JSON")
|
||
|
||
flag.Usage = func() { fmt.Fprint(os.Stderr, usageText) }
|
||
flag.Parse()
|
||
|
||
urls := flag.Args()
|
||
if len(urls) == 0 {
|
||
fmt.Fprint(os.Stderr, usageText)
|
||
os.Exit(1)
|
||
}
|
||
|
||
// --json and -n are parsed but not yet functional (wired in Steps 3–4).
|
||
_ = jsonOut
|
||
_ = count
|
||
|
||
failed := false
|
||
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", r.URL, r.Err)
|
||
failed = true
|
||
continue
|
||
}
|
||
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
|
||
}
|