Third Python implementation in the latency-tool progression. Mirrors latprobe's feature set but replaces raw sockets with httpx, gaining HTTP/2 support and redirect following. Package layout (hxprobe/hxprobe/): - probe.py: asyncio + httpx.AsyncClient with a _TimingStream transport wrapper that captures DNS/connect/TLS/TTFB/transfer phase timings via httpx event hooks (get_connection_stats, request_started, etc.). VerboseDetail captures resolved IP, TLS version/cipher/bits, cert CN/ expiry/issuer (from httpx's SSLObject), and response headers. Options: timeout, verbose, follow_redirects, http2 - aggregate.py: summarize() → per-phase min/avg/max (same schema as latprobe) - cli.py: run(args,stdout,stderr)->int; argparse with redirect_stdout/ redirect_stderr + SystemExit catch; -n/--count, -c/--concurrency, --timeout, --fail, --json, -v/--verbose, --no-follow-redirects, --no-http2, -f/--file (URL list from file, mutually exclusive with args); multi-URL summary footer (tally + exit label) when len(urls) > 1; worst-exit-code logic mirrors Go/latprobe; JSON output is bare array - duration.py: same parse_duration() as latprobe - Exit codes: 0 ok, 1 usage, 2 dns, 3 connect, 4 timeout, 5 tls, 6 http≥400 Tests (hxprobe/tests/): - test_probe.py: 18 hermetic tests using anyio + in-process ASGI servers - test_cli.py: 36 hermetic tests (success, failures, JSON, verbose, -f flag, run-summary footer, worst-code accumulation) - test_integration.py: pytest-marked @integration (excluded from hx-test) Toolchain: uv + ruff + pytest; pyproject.toml with [dependency-groups]; hxprobe/Makefile standalone (help, deps, run, lint, fmt, test, test-integration, check, clean); .python-version pins 3.14 Configs: 8 fixture files mirroring python/configs/ (all-ok through mixed) USAGE.md: 16 runnable examples with real captured output Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
latprobe — Website Latency Probe
A command-line tool that measures the latency of one or more websites, with a
full per-request phase breakdown — DNS resolution, TCP connect, TLS
handshake, server processing (time-to-first-byte), and content transfer.
Not just a ping: latprobe shows you where the time goes.
Why
A single total request time hides the root cause of slowness. Is it DNS? A slow
TLS negotiation? A laggy server? latprobe breaks the request into its
constituent phases so the bottleneck is immediately obvious.
Implementations
| Language | Status | Location |
|---|---|---|
| Go | In progress | go/ |
| Python | Planned | python/ |
Both implementations produce identical CLI behaviour and output formats.
Usage (Go)
latprobe [flags] <url> [url ...]
Flags:
-n, --count int Number of requests per URL (default 1)
-c, --concurrency int Max URLs probed in parallel, 0 = auto (default min(numURLs,8))
--timeout duration Request timeout, e.g. 10s, 500ms (default 10s)
--fail Exit non-zero on HTTP status >= 400 (exit code 6)
--json Output results as JSON instead of text
Examples:
latprobe https://example.com
latprobe -n 5 https://example.com https://www.google.com
latprobe -c 1 -n 10 https://example.com # serial, most accurate
latprobe --json https://example.com | jq .
Output
Text (default)
https://example.com
DNS lookup : 12.34 ms
TCP connect : 8.91 ms
TLS handshake : 45.20 ms
Server (TTFB) : 78.56 ms
Transfer : 2.10 ms
─────────────────────────
Total : 147.11 ms
With -n 5 (min / avg / max columns):
https://example.com (5 samples)
min avg max
DNS lookup : 10.1ms 12.3ms 15.7ms
TCP connect : 7.8ms 9.0ms 11.2ms
...
JSON (--json)
{
"url": "https://example.com",
"samples": 1,
"phases": {
"dns": { "ms": 12.34 },
"connect": { "ms": 8.91 },
"tls": { "ms": 45.20 },
"ttfb": { "ms": 78.56 },
"transfer": { "ms": 2.10 },
"total": { "ms": 147.11 }
}
}
Implementation Roadmap
Go
| Step | Feature | Status |
|---|---|---|
| 0 | Project scaffold — directory structure, go.mod, minimal binary |
✅ Done |
| 1 | Simple total latency — single URL, wall-clock time | ✅ Done |
| 2 | Per-phase breakdown — DNS, TCP, TLS, TTFB, transfer (net/http/httptrace) |
✅ Done |
| 3 | Multiple URLs + --count/-n — min/avg/max aggregates |
✅ Done |
| 4 | --json output flag |
✅ Done |
| 5 | Failure handling — --timeout, --fail, distinct exit codes, partial timing |
✅ Done |
| 6 | Integration tests — in-process matrix + subprocess smoke tests | ✅ Done |
| 7 | Concurrency — -c worker pool across URLs; samples stay serial per URL |
✅ Done |
Python
Begins after the Go implementation is approved. Will mirror the same CLI and
output format. Timed using low-level socket hooks (DNS via socket.getaddrinfo,
connection timings via custom socket wrap or httpx/urllib3 hooks).
Development
A Makefile at the repo root provides all common tasks:
make # list all targets
make build # build go/latprobe
make test # run all tests
make check # fmt + vet + test (pre-commit gate)
make go-run ARGS="https://example.com"
make go-test-verbose
make go-cover # coverage report → go/coverage.html
make go-test-race # run tests with race detector
make go-lint # golangci-lint
make clean # remove build artifacts
See docs/usage/makefile.md for the full target reference.
Development Environment
- Go 1.26.4 / darwin arm64
- Python 3.14.6
- No external dependencies (Go uses stdlib only; Python TBD at port time)
Project Conventions
See CLAUDE.md for the development conventions followed in this project:
- Plans saved to
docs/plans/with timestamped filenames - Completed features logged in
CHANGELOG.mdwith timestamps - Per-feature user docs in
docs/usage/