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>
This commit is contained in:
347
python/configs/usage-phases.md
Normal file
347
python/configs/usage-phases.md
Normal file
@@ -0,0 +1,347 @@
|
||||
# `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:
|
||||
|
||||
```sh
|
||||
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:
|
||||
|
||||
```sh
|
||||
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
|
||||
|
||||
```sh
|
||||
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).
|
||||
|
||||
```sh
|
||||
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.
|
||||
|
||||
```sh
|
||||
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.
|
||||
|
||||
```sh
|
||||
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`.
|
||||
|
||||
```sh
|
||||
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.
|
||||
|
||||
```sh
|
||||
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)
|
||||
|
||||
```sh
|
||||
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
|
||||
|
||||
```sh
|
||||
# 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
|
||||
```
|
||||
|
||||
```sh
|
||||
# Full sweep — includes timeout (~20 s extra)
|
||||
for f in configs/*.txt; do
|
||||
echo
|
||||
echo "=== $f ==="
|
||||
python phases.py "$f"
|
||||
echo "exit: $?"
|
||||
done
|
||||
```
|
||||
Reference in New Issue
Block a user