Files
http-latency-prober/python/configs/usage-phases.md
Jan Novak 16fff2f964 docs: add usage-phases.md beside usage-simple.md in python/configs/
- 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>
2026-07-01 12:11:53 +02:00

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 peer instead of CERTIFICATE_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 return FAIL (HTTP Error 404: Not Found) in simple.py (because urlopen raises for 4xx) but (404) with a full phase breakdown in phases.py (because the request completed at the network level). Exit code is 0 here vs 1 in simple.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