Jan Novak 404b372070 feat(hxprobe): httpx-based HTTP probe — full standalone package
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>
2026-07-02 13:22:31 +02:00

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.md with timestamps
  • Per-feature user docs in docs/usage/
Description
No description provided
Readme 1.7 MiB
Languages
Python 77.1%
Go 18.2%
Makefile 4.7%