- Rename python/configs/usage.md → usage-simple.md so both scripts have a parallel runnable reference file in the same directory - Add python/configs/usage-phases.md: runnable sh commands + real expected output for phases.py against every error-path config, including a note on the key difference from simple.py (HTTP 4xx exits 0 in phases.py vs 1 in simple.py because phases.py completes at the network level) - Update doc cross-links in docs/usage/py-simple.md and py-phases.md Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
10 KiB
phases.py — Example Config Files
Each .txt file in this directory targets a distinct error path so you can
observe phases.py's per-phase behaviour for every failure class. Run them
from the python/ directory.
A key difference from simple.py: on failure, phases.py shows the phases
that did complete before the error — giving you partial timing up to the
point of failure.
Direct URL (no config file)
phases.py also accepts bare URLs as positional arguments:
python phases.py https://example.com
Expected output:
https://example.com (200)
DNS lookup : 14.19 ms
TCP connect : 9.60 ms
TLS handshake : 16.44 ms
Server (TTFB) : 70.29 ms
Transfer : 0.13 ms
─────────────────────────────
Total : 118.14 ms
For http:// URLs the TLS row is omitted:
python phases.py http://example.com
http://example.com (200)
DNS lookup : 13.61 ms
TCP connect : 10.56 ms
Server (TTFB) : 14.51 ms
Transfer : 0.08 ms
─────────────────────────────
Total : 38.91 ms
all-ok.txt — all sites respond successfully
python phases.py configs/all-ok.txt
echo "exit: $?"
Expected output (latencies vary; multiple results separated by a blank line):
https://example.com (200)
DNS lookup : 3.82 ms
TCP connect : 10.04 ms
TLS handshake : 17.12 ms
Server (TTFB) : 28.59 ms
Transfer : 0.09 ms
─────────────────────────────
Total : 66.16 ms
https://www.google.com (200)
DNS lookup : 14.31 ms
TCP connect : 8.47 ms
TLS handshake : 24.61 ms
Server (TTFB) : 95.24 ms
Transfer : 33.96 ms
─────────────────────────────
Total : 182.67 ms
https://www.iana.org (200)
DNS lookup : 23.19 ms
TCP connect : 9.77 ms
TLS handshake : 14.51 ms
Server (TTFB) : 68.53 ms
Transfer : 0.20 ms
─────────────────────────────
Total : 125.93 ms
exit: 0
dns-failure.txt — DNS resolution fails
DNS fails immediately — no TCP or TLS phases run. Only Total is shown
alongside the error (DNS lookup itself is not shown since it never completed).
python phases.py configs/dns-failure.txt
echo "exit: $?"
Expected output:
https://this-host-does-not-exist.invalid (FAILED)
─────────────────────────────
Total : 2.67 ms
✗ dns: [Errno 8] nodename nor servname provided, or not known
http://no.such.host.invalid (FAILED)
─────────────────────────────
Total : 0.65 ms
✗ dns: [Errno 8] nodename nor servname provided, or not known
exit: 1
connection-refused.txt — TCP connection refused
DNS succeeds (loopback resolves instantly), TCP connect fails immediately. Both DNS and TCP phases are shown as partial timing.
python phases.py configs/connection-refused.txt
echo "exit: $?"
Expected output:
http://127.0.0.1:9999 (FAILED)
DNS lookup : 1.03 ms
TCP connect : 0.82 ms
─────────────────────────────
Total : 1.88 ms
✗ connect: [Errno 61] Connection refused
http://127.0.0.1:19999 (FAILED)
DNS lookup : 0.01 ms
TCP connect : 0.40 ms
─────────────────────────────
Total : 0.41 ms
✗ connect: [Errno 61] Connection refused
exit: 1
timeout.txt — connect hangs until timeout
DNS resolves, TCP SYN is sent but never gets a reply. phases.py waits the
full 10-second timeout per host before printing FAIL.
⚠️ This run takes approximately 20 seconds to complete.
python phases.py configs/timeout.txt
echo "exit: $?"
Expected output (after ~20 s):
http://10.255.255.1/ (FAILED)
DNS lookup : x.xx ms
TCP connect : 10000.xx ms
─────────────────────────────
Total : 10000.xx ms
✗ timeout: timed out
http://192.0.2.1/ (FAILED)
DNS lookup : x.xx ms
TCP connect : 10000.xx ms
─────────────────────────────
Total : 10000.xx ms
✗ timeout: timed out
exit: 1
tls-errors.txt — TLS certificate errors
DNS and TCP complete; TLS handshake is attempted and fails. All three phases are shown with real timings even though the request fails.
Note: badssl.com occasionally resets the connection mid-handshake. The error may read
[Errno 54] Connection reset by peerinstead ofCERTIFICATE_VERIFY_FAILED— both are classified as✗ tls.
python phases.py configs/tls-errors.txt
echo "exit: $?"
Expected output:
https://expired.badssl.com/ (FAILED)
DNS lookup : 15.39 ms
TCP connect : 124.98 ms
TLS handshake : 326.52 ms
─────────────────────────────
Total : 479.50 ms
✗ tls: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: certificate has expired …
https://self-signed.badssl.com/ (FAILED)
DNS lookup : 2.06 ms
TCP connect : 127.74 ms
TLS handshake : 963.72 ms
─────────────────────────────
Total : 1103.21 ms
✗ tls: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate …
https://incomplete-chain.badssl.com/ (FAILED)
DNS lookup : 2.11 ms
TCP connect : 2128.40 ms
TLS handshake : 381.42 ms
─────────────────────────────
Total : 2532.73 ms
✗ tls: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer …
exit: 1
http-errors.txt — HTTP 4xx / 5xx status codes
Unlike simple.py (which treats 4xx as FAIL), phases.py completes all
phases and shows the raw HTTP status code. The request succeeded at the network
level — you get full phase breakdown plus the 404.
python phases.py configs/http-errors.txt
echo "exit: $?"
Expected output:
https://www.google.com/this-page-does-not-exist-at-all-1234567890 (404)
DNS lookup : 4.12 ms
TCP connect : 10.56 ms
TLS handshake : 25.03 ms
Server (TTFB) : 124.26 ms
Transfer : 0.19 ms
─────────────────────────────
Total : 172.60 ms
https://github.com/this-repo-does-not-exist-abcxyz123/no-way (404)
DNS lookup : 16.56 ms
TCP connect : 22.86 ms
TLS handshake : 24.47 ms
Server (TTFB) : 275.52 ms
Transfer : 90.08 ms
─────────────────────────────
Total : 441.17 ms
https://www.iana.org/this-page-does-not-exist-either (404)
DNS lookup : 2.48 ms
TCP connect : 10.03 ms
TLS handshake : 16.10 ms
Server (TTFB) : 75.14 ms
Transfer : 0.46 ms
─────────────────────────────
Total : 115.90 ms
exit: 0
Compare with
simple.py: the same URLs returnFAIL (HTTP Error 404: Not Found)insimple.py(becauseurlopenraises for 4xx) but(404)with a full phase breakdown inphases.py(because the request completed at the network level). Exit code is0here vs1insimple.py.
mixed.txt — one of each (no timeout)
python phases.py configs/mixed.txt
echo "exit: $?"
Expected output:
https://example.com (200)
DNS lookup : 4.88 ms
TCP connect : 11.79 ms
TLS handshake : 17.17 ms
Server (TTFB) : 67.79 ms
Transfer : 0.09 ms
─────────────────────────────
Total : 108.72 ms
https://no.such.host.invalid (FAILED)
─────────────────────────────
Total : 1.46 ms
✗ dns: [Errno 8] nodename nor servname provided, or not known
http://127.0.0.1:9999 (FAILED)
DNS lookup : 0.01 ms
TCP connect : 0.82 ms
─────────────────────────────
Total : 0.85 ms
✗ connect: [Errno 61] Connection refused
https://self-signed.badssl.com/ (FAILED)
DNS lookup : 1.54 ms
TCP connect : 1129.98 ms
TLS handshake : 296.93 ms
─────────────────────────────
Total : 1438.84 ms
✗ tls: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate …
https://github.com/this-repo-does-not-exist-abcxyz123/no-way (404)
DNS lookup : 13.48 ms
TCP connect : 22.98 ms
TLS handshake : 24.58 ms
Server (TTFB) : 324.54 ms
Transfer : 95.05 ms
─────────────────────────────
Total : 495.53 ms
exit: 1
Note how the GitHub 404 shows all five phases (network succeeded) while the DNS, TCP, and TLS failures each show only the phases that ran.
Run all configs
# Quick sweep — skips the slow timeout config
for f in configs/*.txt; do
[[ "$f" == *timeout* ]] && continue
echo
echo "=== $f ==="
python phases.py "$f"
echo "exit: $?"
done
# Full sweep — includes timeout (~20 s extra)
for f in configs/*.txt; do
echo
echo "=== $f ==="
python phases.py "$f"
echo "exit: $?"
done