Files
http-latency-prober/docs/summaries/2026-07-02-12-05-hxprobe-simplification.md
Jan Novak f487a4b1bd docs: hxprobe plans, summaries, explanations, usage, and changelog
Plans (docs/plans/):
- 2026-07-01-23-47-py-hxprobe-httpx.md — initial httpx probe design
- 2026-07-02-09-32 through 14-05 — standalone project, toolchain, usage
  doc + Makefile, file input (-f), simplification pass, run-summary footer

Summaries (docs/summaries/): one per completed feature, recording what
was actually built, deviations from the plan, and verification steps

Explanations (docs/explanations/): two deep-dives written during review —
hxprobe concurrency model and worst-exit-code + render-loop analysis

Usage (docs/usage/hxprobe.md): overview with pointer to hxprobe/USAGE.md
for the full runnable reference

Walkthrough (docs/py-latprobe-walkthrough.md): narrative tour of the
latprobe Python package for interview / code-review context

CHANGELOG.md: entries for all hxprobe features (toolchain, usage doc,
file input, simplification, run-summary footer)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 13:22:59 +02:00

4.2 KiB

hxprobe simplification — summary

Plan: docs/plans/2026-07-02-12-05-hxprobe-simplification.md (five targeted removals of speculative scaffolding / redundant state / duplicated logic, no behavior change). Trigger: a question about a dead file= parameter on _Parser.print_help/print_usage in hxprobe/hxprobe/cli.py, which led to a broader pass over the whole package for the same pattern.

What changed

  1. hxprobe/hxprobe/cli.py — deleted the _Parser subclass and _ArgExit exception (~32 lines). It re-implemented, by hand, behavior the standard library already provides: argparse.ArgumentParser already writes help to sys.stdout and usage/errors to sys.stderr, and already raises SystemExit rather than hard-exiting. run() now builds a plain argparse.ArgumentParser, wraps the parse + validation calls in contextlib.redirect_stdout(stdout) / redirect_stderr(stderr), and catches SystemExit at a single site:

    except SystemExit as exc:
        return EXIT_OK if not exc.code else EXIT_USAGE
    

    Deviation from the original plan sketch: the plan's snippet used EXIT_OK if not exc.code else EXIT_USAGE, matching what was actually implemented (equivalent to, but slightly more defensive than, the exc.code == 0 check first drafted, since argparse can in principle pass None).

  2. hxprobe/hxprobe/probe.py — trimmed _TimingStream.get_extra_info to the single branch actually consumed on the request path (ssl_object). Verified by grepping the installed httpcore/httpx packages: the server_addr/client_addr branches were only ever queried by httpx's own _main.py (the httpx CLI command), never by anything hxprobe's request path touches.

  3. hxprobe/hxprobe/cli.py — simplified _load_urls: dropped the line.split()[0] "forward-compatible with future url key=value annotations" scaffolding; a stripped line is used directly. Docstring updated to describe only what the function does today.

  4. hxprobe/hxprobe/probe.py — removed _dns_set/_connect_set/ _tls_set from _Trace. These booleans duplicated Phase.present on self.dns/self.connect/self.tls (each starts as Phase(), i.e. present=False). Guards rewritten from if not self._dns_set: to if not self.dns.present: (and analogously for connect/tls) — identical first-hop-wins semantics for redirects, one less piece of parallel state.

  5. hxprobe/hxprobe/cli.py — extracted _summarize_failures, a shared helper that dedupes failed: list[Result] into [(phase, message, count), ...] in first-seen order. _print_failure_summary (text rendering) and _build_json_entry (JSON rendering) previously each carried an identical ~12-line dedup loop; both now call the helper and only differ in how they format the tuple.

Not changed (considered, kept — per the plan)

  • The custom httpcore backend (_TimingBackend/_TimingStream/ _TimingTransport) — this is the tool's actual reason to exist.
  • ThreadPoolExecutor concurrency — backs the shipped -c/--concurrency and multi-URL/-f features.
  • Explicit per-phase dataclass fields in probe.py/aggregate.py.
  • CertInfo.sans — unused in text output but real (emitted in JSON verbose output).

Verification

  • hxprobe/.venv/bin/python -m pytest tests/test_cli.py tests/test_probe.py -q33 passed, no test file edits.
  • hxprobe/.venv/bin/ruff check hxprobe/all checks passed.
  • Manual smoke tests (python -m hxprobe -h, no-args, and a live https://example.com -v request):
    • -h → help text on stdout, exit 0.
    • No args → usage + no URLs given error on stderr only (confirmed via separate stdout/stderr redirection to files — stdout was empty), exit 1.
    • Live verbose request → correct 6-phase timing, resolved IP, HTTP/2 protocol, TLS version/cipher, and certificate detail — confirms the get_extra_info trim didn't break ssl_object retrieval and the _Trace refactor didn't break phase/first-hop-wins tracking.

Net: cli.py and probe.py are shorter and carry less parallel/duplicated state; no observable behavior changed.