Adds the complete Python port of the Go latprobe tool plus diagnostic verbose output that the Go version does not yet have. Package (python/latprobe/): - probe.py: raw-socket HTTP timing with CertInfo/VerboseDetail dataclasses; TTFB loop accumulates to \r\n\r\n so headers are parseable without changing t_first_byte semantics; captures resolved IP, TLS version/cipher/bits, verified cert (via getpeercert()), and all response headers when verbose=True - aggregate.py: summarize() → per-phase min/avg/max PhaseStats - cli.py: injectable run(args,stdout,stderr)->int; argparse with injected streams; ThreadPoolExecutor concurrency across URLs; four text-rendering branches; JSON output; worst-exit-code accumulation; -v/--verbose flag appends IP/TLS/cert/header block after every timing table; JSON extended with "verbose" object (omitted when flag absent) - duration.py: parse Go-style duration strings (10s, 500ms, 2m, bare seconds) - Exit codes: 0 ok, 1 usage, 2 dns, 3 connect, 4 timeout, 5 tls, 6 http≥400 Tests: - test_probe.py: 18 hermetic tests (success, 404, DNS/connect/timeout/scheme failures, partial-phase invariants, verbose detail fields) - test_cli.py: 38 hermetic tests (usage errors, single/aggregate/failure text, worst-code, --fail, JSON schema/ordering/grouping, verbose text and JSON) - test_integration.py: 45 tests against live internet services (badssl.com for TLS errors, real cert/IP/header validation in verbose mode) Makefile: PYTHONPATH fix, fnmatch pattern test_[!i]*.py to exclude integration tests from py-test, new py-test-integration target Docs: docs/usage/py-latprobe.md, python/configs/usage-latprobe.md (runnable reference with captured output), plans for both the package and verbose feature Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
9.3 KiB
9.3 KiB
Changelog
All completed features are logged here in reverse-chronological order.
2026-07-01 12:55 — --verbose / -v flag for latprobe (Python)
-v/--verboseflag added to thelatprobeCLIprobe.py: newCertInfo+VerboseDetaildataclasses;Options.verbose;Result.detail; TTFB loop now accumulates until\r\n\r\n(unchanged TTFB semantics —t_first_bytestamped on firstrecv(), not when headers complete); captures resolved IP (fromgetaddrinfo), TLS version/cipher/bits (fromsock.version()/sock.cipher()), verified cert (fromgetpeercert()), and all response headerscli.py: verbose text block appended after timing rows in all four output branches (single, aggregate, all-failed, mixed);_VERBOSE_HEADERSpriority list controls which headers appear in text mode; JSON"verbose"object includes all parsed headers, full cert fields, TLS metadata;"verbose"key omitted when flag is absent- 9 new hermetic probe tests, 15 new CLI tests covering verbose text and JSON across success, connect-fail, and DNS-fail scenarios
- 15 new integration tests for real TLS cert fields (CN, expiry, issuer), IP format, TLS version string, header presence, and verbose text/JSON output
python/configs/usage-latprobe.md: runnable reference with real output for all features including verbose and--verbose --jsondocs/usage/py-latprobe.md: updated with--verboseflag and examples- Plan:
docs/plans/2026-07-01-12-55-py-latprobe-verbose.md
2026-07-01 12:23 — Full latprobe Python package (Python, Step 3)
python/latprobe/package: full port of the Go CLI, runnable aspython -m latprobeprobe.py: raw-socket HTTP measurement withOptions(timeout)parameter; mirrorsphases.pytechnique; partial phases preserved on failureaggregate.py:summarize(List[Result]) -> Aggregatewith per-phasePhaseStats(min_ms, avg_ms, max_ms)duration.py: parses Go-style duration strings (10s,500ms,2m, bare seconds)cli.py: injectablerun(args, stdout, stderr) -> int; argparse with injected streams (_Parsersubclass);ThreadPoolExecutorconcurrency across URLs; four text-rendering branches (single, aggregate, all-failed, mixed); JSON output; worst-exit-code accumulation- Exit codes: 0 ok, 1 usage, 2 dns, 3 connect, 4 timeout, 5 tls, 6 http≥400 (
--fail) python/tests/test_probe.py: 9 tests — success, 404, DNS fail, connect refused, TTFB timeout, bad scheme, partial phase invariants; useshttp.server+ daemon threadspython/tests/test_cli.py: 23 tests — drivescli.run()in-process; covers usage errors, single/aggregate text, failure exit codes, worst-code,--fail, JSON schema, JSON ordering, JSON error grouping- Makefile
py-test: addedPYTHONPATH=$(PY_DIR), removed|| true - User doc:
docs/usage/py-latprobe.md
2026-07-01 12:04 — Per-phase latency measurement (Python, Step 2)
python/phases.py: self-contained script; hand-drives raw sockets to time each HTTP phase individually — DNS (getaddrinfo), TCP connect, TLS handshake (ssl.wrap_socket, HTTPS only), TTFB (sendall → first recv), Transfer, Total- Partial phases preserved on failure (same invariant as Go's
probe.go) - Error classification mirrors Go's priority: dns → timeout → tls → connect
- Output format matches Go's single-sample text layout (14-char labels,
─separator,%8.2f msalignment) - Input: bare URL args or plain-text config file (same format as
simple.py) - Exits 0 if all URLs complete without network error; 1 if any failed
- User doc:
docs/usage/py-phases.md
2026-07-01 11:30 — Error-path example configs for simple.py (Python, Step 1 refinement)
python/configs/directory with 7 purpose-built config files, one per error class:all-ok.txt,dns-failure.txt,connection-refused.txt,timeout.txt,tls-errors.txt(badssl.com),http-errors.txt(httpstat.us),mixed.txtpython/configs/usage.md: runnable shell commands + expected output for every config- Fixed
docs/usage/py-simple.md: 4xx/5xx responses are reported asFAIL(notOK), becauseurlopenraisesHTTPErrorfor non-2xx; added pointer to the example configs
2026-07-01 10:39 — Simple reachability checker (Python, Step 1)
python/simple.py: reads a plain-text site list (one URL per line,#comments), issues a GET to each, prints alignedOK / FAIL + elapsed msper site- Plain-text config format forward-compatible with future
key=valueannotations - Exits 0 if all sites responded, 1 if any failed or config is missing
python/sites.txt: committed example config- Makefile
py-*targets added:py-simple-run,py-phases-run,py-run,py-test,py-check,py-clean; umbrellatest,check,cleannow include Python - User doc:
docs/usage/py-simple.md
2026-07-01 01:23 — Concurrency (Go, Step 7)
- Added
-c/--concurrencyflag: max URLs probed in parallel (0 = auto) - Default auto-concurrency:
min(numURLs, 8)— scales with workload, caps at 8 - Parallelism is across URLs only; the N samples of each URL stay sequential to preserve the accuracy of per-URL min/avg/max statistics
- Output buffered and printed in original input order (deterministic for both text and JSON), exit-code accumulation unchanged
- Implementation: semaphore channel +
sync.WaitGroup, each goroutine writes only its own indexed result slot — verified clean withgo test -race - Added
make go-test-racetarget to the Makefile - 3 new tests:
TestRunConcurrentOrder,TestConcurrentWorstCode,TestConcurrentJSONOrder - Updated runtime estimates in
docs/custom-usage-examples.md - User doc:
docs/usage/step-7-concurrency.md
2026-07-01 01:05 — Root Makefile
- Namespaced targets:
go-build,go-run,go-test,go-test-verbose,go-check,go-cover,go-fmt,go-vet,go-tidy,go-install,go-lint,go-clean - Umbrella targets (
build,test,check,fmt,vet,clean,all) delegate to Go now;py-*will slot in during the Python port helpis the default target; auto-generated from##commentsgo-runacceptsARGS=for passing flags,go-lintguards forgolangci-lint.gitignoreupdated with coverage artifacts
2026-07-01 00:49 — Integration tests (Go, Step 6)
- Extracted
run(args, stdout, stderr) intfrommain()to make the CLI testable in-process - Fixed TLS classification bug:
TLSHandshakeDonefires with the error on cert rejection, sotlsErris now captured and checked before the staletlsStart.IsZero()guard run_test.go: 14 in-process tests covering exit codes 0–6, multi-URL, sampling, and JSON structure assertionscli_test.go:TestMainbuilds the real binary; 3 subprocess smoke tests exercise the actualos.Exitpath- All test servers use
httptest+ stdlib;.invalidTLD for deterministic DNS failures; no external network dependency
2026-07-01 00:38 — Failure handling (Go, Step 5)
- Classified network failures into
dns/connect/timeout/tlswith distinct exit codes (2–5) - Partial timing preserved up to the failure point (e.g. DNS phase shown on NXDOMAIN)
--timeoutflag (default 10s) applied viacontext.WithTimeout--failflag: HTTP status ≥ 400 → exit code 6 (curl-style)-nsampling continues on network failure; aggregates successes, reports fail count + cause- JSON output extended with
succeeded,failed,errors[]fields - Highest exit code across all URLs/failure types is used as the process exit
2026-07-01 00:08 — JSON output flag (Go, Step 4)
--jsonflag emits a JSON array with one entry per URL- Schema always uses min/avg/max shape (consistent regardless of
-n) - Phases absent from the request (e.g. TLS on HTTP) are omitted from the JSON object
- Failed URLs are excluded from JSON output and reported to stderr
- Text output unchanged and remains the default
2026-07-01 00:08 — Multiple URLs + sampling (Go, Step 3)
-n/--countflag repeats each URL N times and reports min/avg/max per phaseprobe.Summarizeaggregates a slice of Results into per-phase PhaseStats- Multi-sample output shows an aligned min/avg/max table with a header row
- Single-sample output (n=1) is unchanged from Step 2
2026-07-01 00:08 — Per-phase breakdown (Go, Step 2)
- Instrumented requests with
net/http/httptrace.ClientTrace - DNS lookup, TCP connect, TLS handshake, Server/TTFB, Transfer, Total phases
- TLS row omitted automatically for plain
http://URLs - Aligned text output with separator before Total
2026-07-01 00:08 — Simple total latency (Go, Step 1)
probe.Measureperforms an HTTP GET and records wall-clock total timemain.goprints URL, HTTP status code, and total duration for each URL- Exits with code 1 if any URL fails
- Multiple URLs accepted as positional arguments
2026-07-01 00:08 — Project scaffold (Go, Step 0)
- Created project structure:
go/,python/,docs/plans/,docs/usage/ - Initialised Go module
latprobe(go/go.mod) - Minimal
go/main.gothat prints usage and exits cleanly when no URL is given - Stub
go/internal/probe/probe.godefining theResulttype andMeasuresignature - Foundation files:
README.md,CLAUDE.md,CHANGELOG.md - Saved initial plan to
docs/plans/2026-07-01-00-08-go-latency-tool.md