- python/simple.py: reads a plain-text site list (one URL/line, # comments), issues a GET per site, prints aligned OK/FAIL + elapsed ms, exits 0/1 - Config format forward-compatible with future key=value annotations - HTTPError caught separately from URLError so HTTP status code appears in the FAIL message (e.g. "HTTP Error 404: Not Found") - python/sites.txt: committed example config - Makefile: PYTHON/PY_DIR/SITES vars; py-simple-run, py-phases-run, py-run, py-test, py-check, py-clean targets; umbrella test/check/clean include Python - docs/plans/2026-07-01-10-39-py-simple.md: design plan - docs/usage/py-simple.md: flags, format, examples, limitations Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
126 lines
5.8 KiB
Markdown
126 lines
5.8 KiB
Markdown
# Changelog
|
||
|
||
All completed features are logged here in reverse-chronological order.
|
||
|
||
---
|
||
|
||
## 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.txt`
|
||
- `python/configs/usage.md`: runnable shell commands + expected output for every config
|
||
- Fixed `docs/usage/py-simple.md`: 4xx/5xx responses are reported as `FAIL` (not `OK`),
|
||
because `urlopen` raises `HTTPError` for 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 aligned `OK / FAIL + elapsed ms` per site
|
||
- Plain-text config format forward-compatible with future `key=value` annotations
|
||
- 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`; umbrella `test`, `check`, `clean` now include Python
|
||
- User doc: `docs/usage/py-simple.md`
|
||
|
||
---
|
||
|
||
## 2026-07-01 01:23 — Concurrency (Go, Step 7)
|
||
|
||
- Added `-c`/`--concurrency` flag: 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 with `go test -race`
|
||
- Added `make go-test-race` target 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
|
||
- `help` is the default target; auto-generated from `##` comments
|
||
- `go-run` accepts `ARGS=` for passing flags, `go-lint` guards for `golangci-lint`
|
||
- `.gitignore` updated with coverage artifacts
|
||
|
||
---
|
||
|
||
## 2026-07-01 00:49 — Integration tests (Go, Step 6)
|
||
|
||
- Extracted `run(args, stdout, stderr) int` from `main()` to make the CLI testable in-process
|
||
- Fixed TLS classification bug: `TLSHandshakeDone` fires with the error on cert rejection, so `tlsErr` is now captured and checked before the stale `tlsStart.IsZero()` guard
|
||
- `run_test.go`: 14 in-process tests covering exit codes 0–6, multi-URL, sampling, and JSON structure assertions
|
||
- `cli_test.go`: `TestMain` builds the real binary; 3 subprocess smoke tests exercise the actual `os.Exit` path
|
||
- All test servers use `httptest` + stdlib; `.invalid` TLD 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` / `tls` with distinct exit codes (2–5)
|
||
- Partial timing preserved up to the failure point (e.g. DNS phase shown on NXDOMAIN)
|
||
- `--timeout` flag (default 10s) applied via `context.WithTimeout`
|
||
- `--fail` flag: HTTP status ≥ 400 → exit code 6 (curl-style)
|
||
- `-n` sampling 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)
|
||
|
||
- `--json` flag 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`/`--count` flag repeats each URL N times and reports min/avg/max per phase
|
||
- `probe.Summarize` aggregates 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.Measure` performs an HTTP GET and records wall-clock total time
|
||
- `main.go` prints 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.go` that prints usage and exits cleanly when no URL is given
|
||
- Stub `go/internal/probe/probe.go` defining the `Result` type and `Measure` signature
|
||
- Foundation files: `README.md`, `CLAUDE.md`, `CHANGELOG.md`
|
||
- Saved initial plan to `docs/plans/2026-07-01-00-08-go-latency-tool.md`
|