yslow 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- yslow-0.1.0/.claude/settings.local.json +10 -0
- yslow-0.1.0/.gitignore +6 -0
- yslow-0.1.0/CLAUDE.md +22 -0
- yslow-0.1.0/LICENSE +21 -0
- yslow-0.1.0/PKG-INFO +84 -0
- yslow-0.1.0/README.md +57 -0
- yslow-0.1.0/TODO.md +41 -0
- yslow-0.1.0/check.sh +19 -0
- yslow-0.1.0/docs/dataflow.md +69 -0
- yslow-0.1.0/docs/metrics.md +55 -0
- yslow-0.1.0/logs/2026-03-21-continuous-monitoring.md +100 -0
- yslow-0.1.0/logs/2026-03-21-ui-overhaul.md +54 -0
- yslow-0.1.0/logs/2026-03-22-data-model-cleanup.md +16 -0
- yslow-0.1.0/logs/2026-03-22-remove-summary-cleanup-tests.md +20 -0
- yslow-0.1.0/logs/2026-03-22-ui-status-and-diagnosis.md +22 -0
- yslow-0.1.0/pyproject.toml +48 -0
- yslow-0.1.0/scratch.txt +22 -0
- yslow-0.1.0/tests/__init__.py +0 -0
- yslow-0.1.0/tests/conftest.py +28 -0
- yslow-0.1.0/tests/snapshots/all_good_watch.txt +10 -0
- yslow-0.1.0/tests/snapshots/everything_failed_watch.txt +14 -0
- yslow-0.1.0/tests/snapshots/one_issue_watch.txt +13 -0
- yslow-0.1.0/tests/test_common.py +18 -0
- yslow-0.1.0/tests/test_diagnosis.py +114 -0
- yslow-0.1.0/tests/test_models.py +120 -0
- yslow-0.1.0/tests/test_net.py +62 -0
- yslow-0.1.0/tests/test_probe.py +70 -0
- yslow-0.1.0/tests/test_snapshots.py +181 -0
- yslow-0.1.0/tests/test_watch.py +147 -0
- yslow-0.1.0/uv.lock +284 -0
- yslow-0.1.0/yslow/__init__.py +41 -0
- yslow-0.1.0/yslow/__main__.py +6 -0
- yslow-0.1.0/yslow/checks/__init__.py +0 -0
- yslow-0.1.0/yslow/checks/gateway.py +34 -0
- yslow-0.1.0/yslow/checks/internet.py +38 -0
- yslow-0.1.0/yslow/diagnosis.py +181 -0
- yslow-0.1.0/yslow/models.py +116 -0
- yslow-0.1.0/yslow/net.py +30 -0
- yslow-0.1.0/yslow/probe.py +62 -0
- yslow-0.1.0/yslow/runner.py +21 -0
- yslow-0.1.0/yslow/ui/__init__.py +0 -0
- yslow-0.1.0/yslow/ui/common.py +37 -0
- yslow-0.1.0/yslow/ui/watch.py +147 -0
yslow-0.1.0/.gitignore
ADDED
yslow-0.1.0/CLAUDE.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Claude context
|
|
2
|
+
|
|
3
|
+
## Quick reference
|
|
4
|
+
|
|
5
|
+
- `./check.sh` — run all checks (format, lint, typecheck, tests)
|
|
6
|
+
- `uv run yslow` — run the tool
|
|
7
|
+
- `uv run pytest` — tests only
|
|
8
|
+
- `uv run ty check` — type check only
|
|
9
|
+
- `uv run ruff check --fix` — lint
|
|
10
|
+
- `uv run ruff format` — format
|
|
11
|
+
- `uv run pytest tests/test_snapshots.py --snapshot-update` — regenerate UI snapshots after output changes
|
|
12
|
+
|
|
13
|
+
## Key files
|
|
14
|
+
|
|
15
|
+
- `TODO.md` — feature backlog and progress tracking
|
|
16
|
+
- `docs/` — design and reference documentation (metrics, etc.)
|
|
17
|
+
- `logs/` — session logs recording what was changed and why
|
|
18
|
+
|
|
19
|
+
## Guidelines
|
|
20
|
+
|
|
21
|
+
- Update or add docs in `docs/` when changing metrics, thresholds, or diagnostic logic
|
|
22
|
+
- At the end of a session, write a log entry in `logs/` (format: `YYYY-MM-DD-short-description.md`) summarizing what changed and any open questions
|
yslow-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Wesley Ellis
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
yslow-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: yslow
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Diagnose why your network connection is slow
|
|
5
|
+
Project-URL: Homepage, https://codeberg.org/tahnok/yslow
|
|
6
|
+
Project-URL: Repository, https://codeberg.org/tahnok/yslow
|
|
7
|
+
Project-URL: Issues, https://codeberg.org/tahnok/yslow/issues
|
|
8
|
+
Author-email: Wesley Ellis <wesley@tahnok.ca>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: diagnostics,latency,network,troubleshooting,wifi
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: System Administrators
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: System :: Networking :: Monitoring
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Requires-Dist: rich>=14.3.3
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# yslow
|
|
29
|
+
|
|
30
|
+
A command-line tool that diagnoses why your network connection is slow (or feels slow).
|
|
31
|
+
|
|
32
|
+
Instead of just reporting numbers, yslow isolates *where* the problem is — your local network (wifi/router), your ISP, or the broader internet — and tells you in plain language.
|
|
33
|
+
|
|
34
|
+
## What it checks
|
|
35
|
+
|
|
36
|
+
- **Gateway latency** — how fast your machine can talk to your router. High latency or jitter here points to wifi congestion, a bad cable, or an overloaded router.
|
|
37
|
+
- **Internet latency** — TCP handshake time to multiple well-known servers (Cloudflare, Google, OpenDNS).
|
|
38
|
+
- **Packet loss** — at both the local and internet level.
|
|
39
|
+
- **Jitter** — variance in latency, which causes buffering, choppy calls, and inconsistent page loads.
|
|
40
|
+
|
|
41
|
+
By comparing gateway vs internet results, yslow isolates the problem:
|
|
42
|
+
|
|
43
|
+
| Gateway | Internet | Diagnosis |
|
|
44
|
+
|---------|----------|-----------|
|
|
45
|
+
| Fast | Fast | Connection is healthy |
|
|
46
|
+
| Slow | Slow | Local network is the bottleneck (router/wifi) |
|
|
47
|
+
| Fast | Slow | ISP or internet routing issue |
|
|
48
|
+
| Unreachable | Slow | Unusual setup, but internet works |
|
|
49
|
+
|
|
50
|
+
## Usage
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
uv run yslow
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Requires Python 3.10+ and Linux. No dependencies beyond the standard library.
|
|
57
|
+
|
|
58
|
+
## How it works
|
|
59
|
+
|
|
60
|
+
yslow uses **TCP handshake timing** rather than ICMP ping. A TCP connect to port 443 measures the same network round-trip as ping, but works without root privileges and inside containers where ICMP is often blocked.
|
|
61
|
+
|
|
62
|
+
For the gateway, it tries common TCP ports (80, 443, 53) and falls back to ICMP ping if needed.
|
|
63
|
+
|
|
64
|
+
## Development
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
uv sync # install deps
|
|
68
|
+
./check.sh # run all checks (format, lint, typecheck, tests)
|
|
69
|
+
uv run pytest # run tests only
|
|
70
|
+
uv run ty check # type check only
|
|
71
|
+
uv run ruff check --fix # lint
|
|
72
|
+
uv run ruff format # format
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Documentation
|
|
76
|
+
|
|
77
|
+
See [`docs/metrics.md`](docs/metrics.md) for details on what yslow measures (RTT, packet loss, jitter), how each metric is calculated, and the thresholds used for diagnosis.
|
|
78
|
+
|
|
79
|
+
## Planned
|
|
80
|
+
|
|
81
|
+
- DNS resolution timing
|
|
82
|
+
- Bandwidth / throughput estimation
|
|
83
|
+
- Bufferbloat detection (latency under load)
|
|
84
|
+
- Route analysis (where along the path is the bottleneck)
|
yslow-0.1.0/README.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# yslow
|
|
2
|
+
|
|
3
|
+
A command-line tool that diagnoses why your network connection is slow (or feels slow).
|
|
4
|
+
|
|
5
|
+
Instead of just reporting numbers, yslow isolates *where* the problem is — your local network (wifi/router), your ISP, or the broader internet — and tells you in plain language.
|
|
6
|
+
|
|
7
|
+
## What it checks
|
|
8
|
+
|
|
9
|
+
- **Gateway latency** — how fast your machine can talk to your router. High latency or jitter here points to wifi congestion, a bad cable, or an overloaded router.
|
|
10
|
+
- **Internet latency** — TCP handshake time to multiple well-known servers (Cloudflare, Google, OpenDNS).
|
|
11
|
+
- **Packet loss** — at both the local and internet level.
|
|
12
|
+
- **Jitter** — variance in latency, which causes buffering, choppy calls, and inconsistent page loads.
|
|
13
|
+
|
|
14
|
+
By comparing gateway vs internet results, yslow isolates the problem:
|
|
15
|
+
|
|
16
|
+
| Gateway | Internet | Diagnosis |
|
|
17
|
+
|---------|----------|-----------|
|
|
18
|
+
| Fast | Fast | Connection is healthy |
|
|
19
|
+
| Slow | Slow | Local network is the bottleneck (router/wifi) |
|
|
20
|
+
| Fast | Slow | ISP or internet routing issue |
|
|
21
|
+
| Unreachable | Slow | Unusual setup, but internet works |
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
uv run yslow
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Requires Python 3.10+ and Linux. No dependencies beyond the standard library.
|
|
30
|
+
|
|
31
|
+
## How it works
|
|
32
|
+
|
|
33
|
+
yslow uses **TCP handshake timing** rather than ICMP ping. A TCP connect to port 443 measures the same network round-trip as ping, but works without root privileges and inside containers where ICMP is often blocked.
|
|
34
|
+
|
|
35
|
+
For the gateway, it tries common TCP ports (80, 443, 53) and falls back to ICMP ping if needed.
|
|
36
|
+
|
|
37
|
+
## Development
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
uv sync # install deps
|
|
41
|
+
./check.sh # run all checks (format, lint, typecheck, tests)
|
|
42
|
+
uv run pytest # run tests only
|
|
43
|
+
uv run ty check # type check only
|
|
44
|
+
uv run ruff check --fix # lint
|
|
45
|
+
uv run ruff format # format
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Documentation
|
|
49
|
+
|
|
50
|
+
See [`docs/metrics.md`](docs/metrics.md) for details on what yslow measures (RTT, packet loss, jitter), how each metric is calculated, and the thresholds used for diagnosis.
|
|
51
|
+
|
|
52
|
+
## Planned
|
|
53
|
+
|
|
54
|
+
- DNS resolution timing
|
|
55
|
+
- Bandwidth / throughput estimation
|
|
56
|
+
- Bufferbloat detection (latency under load)
|
|
57
|
+
- Route analysis (where along the path is the bottleneck)
|
yslow-0.1.0/TODO.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# TODO
|
|
2
|
+
|
|
3
|
+
## Testing
|
|
4
|
+
- [x] Add test suite (unit tests for parsing, result building; mock-based tests for probing)
|
|
5
|
+
|
|
6
|
+
## New diagnostics
|
|
7
|
+
- [ ] DNS resolution timing — measure lookup time for common domains, detect slow/broken resolvers
|
|
8
|
+
- [ ] Bufferbloat detection — measure latency while simultaneously loading the connection
|
|
9
|
+
- [ ] Speed test — download/upload throughput estimation
|
|
10
|
+
- [ ] IPv6 health — check if IPv6 is configured but broken/slower than IPv4 (common cause of "feels slow" when apps try IPv6 first and fall back)
|
|
11
|
+
- [ ] Wifi signal strength — read signal/noise from iw/iwconfig, flag weak signal or high noise
|
|
12
|
+
- [ ] Route analysis — traceroute-style hop-by-hop latency to find where the bottleneck is
|
|
13
|
+
- [ ] MTU / fragmentation issues — detect path MTU problems
|
|
14
|
+
- [ ] DNS-over-HTTPS vs plain DNS comparison — detect if DoH config is hurting perceived speed
|
|
15
|
+
- [ ] TLS handshake timing — separate TCP connect from TLS negotiation to isolate slow TLS
|
|
16
|
+
- [ ] Bandwidth asymmetry — flag upload vs download disparity (kills video calls and syncing)
|
|
17
|
+
- [ ] Probe a user-specified target — "why is github.com slow?" mode
|
|
18
|
+
- [ ] Network interface health — read TX/RX errors, drops, retransmits from /proc/net/dev and /proc/net/snmp
|
|
19
|
+
- [ ] Competing traffic detection — check if something else on the machine is saturating the connection
|
|
20
|
+
- [ ] VPN detection, so we can account for extra latency, problems with the gateway
|
|
21
|
+
- [ ] Specific site probe, we can ask the user if a particular site or service is feeling slow
|
|
22
|
+
|
|
23
|
+
## Longer-running / monitoring mode
|
|
24
|
+
- [x] Continuous monitoring mode — `--watch` / `-w` flag, configurable interval, live dashboard
|
|
25
|
+
- [x] Session summary on exit (Ctrl+C) — total checks, healthy %, most common issues
|
|
26
|
+
- [x] Terminal bell + OSC 9 desktop notification on problem detection
|
|
27
|
+
- [ ] Stateful check modules — checks that accumulate data, control their own run frequency, produce smarter summaries over time
|
|
28
|
+
- [ ] Summary statistics over a monitoring session (percentiles, worst periods)
|
|
29
|
+
- [ ] Latency-under-load test — run probes while generating traffic to detect congestion/bufferbloat
|
|
30
|
+
- [ ] Historical comparison — save results to a local log, compare "is it worse than usual?"
|
|
31
|
+
- [ ] Time-of-day correlation — in monitoring mode, flag patterns like "always slow at 7pm"
|
|
32
|
+
- [ ] Network condition simulation for testing — Docker/tc/netem, network namespaces, or VMs
|
|
33
|
+
|
|
34
|
+
## Performance / UX
|
|
35
|
+
- [ ] Parallel probing — probe all targets concurrently instead of sequentially
|
|
36
|
+
- [ ] Colored output (green/yellow/red for OK/warning/problem)
|
|
37
|
+
- [ ] Summary verdict — single top-line sentence like "Your wifi is the bottleneck"
|
|
38
|
+
- [ ] Suggested fixes — actionable next steps based on findings (e.g., "Try switching DNS to 1.1.1.1")
|
|
39
|
+
|
|
40
|
+
## Platform
|
|
41
|
+
- [ ] macOS support (gateway detection, wifi tools differ)
|
yslow-0.1.0/check.sh
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
run() {
|
|
5
|
+
local label="$1"
|
|
6
|
+
shift
|
|
7
|
+
if ! output=$("$@" 2>&1); then
|
|
8
|
+
echo "=== $label FAILED ==="
|
|
9
|
+
echo "$output"
|
|
10
|
+
exit 1
|
|
11
|
+
fi
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
run "ruff format" uv run ruff format --check .
|
|
15
|
+
run "ruff check" uv run ruff check .
|
|
16
|
+
run "ty" uv run ty check
|
|
17
|
+
run "pytest" uv run pytest -q "$@"
|
|
18
|
+
|
|
19
|
+
echo "All checks passed."
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Data flow
|
|
2
|
+
|
|
3
|
+
How a network check becomes a displayed result.
|
|
4
|
+
|
|
5
|
+
## Models
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
ProbeResult Raw ping data for one host (RTTs, loss, jitter)
|
|
9
|
+
Issue A single diagnosed problem from one check (kind, severity, description)
|
|
10
|
+
CheckResult Output of one check cycle: timestamp + ProbeResults + Issues
|
|
11
|
+
IssueRecord Aggregation of Issues across multiple CheckResults, grouped by kind
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Pipeline
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
18
|
+
│ Probes (checks/) │
|
|
19
|
+
│ │
|
|
20
|
+
│ gateway.run() ──→ ProbeResult (1 host) │
|
|
21
|
+
│ internet.run() ──→ list[ProbeResult] (multiple hosts) │
|
|
22
|
+
└──────────────────────────┬──────────────────────────────────┘
|
|
23
|
+
│
|
|
24
|
+
▼
|
|
25
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
26
|
+
│ Diagnosis (diagnosis.py) │
|
|
27
|
+
│ │
|
|
28
|
+
│ analyze(gateway, internet) ──→ list[Issue] │
|
|
29
|
+
│ │
|
|
30
|
+
│ Compares probe results against thresholds (see metrics.md)│
|
|
31
|
+
│ to produce issues like "gateway-loss", "isp-latency", etc.│
|
|
32
|
+
└──────────────────────────┬──────────────────────────────────┘
|
|
33
|
+
│
|
|
34
|
+
▼
|
|
35
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
36
|
+
│ Runner (runner.py) │
|
|
37
|
+
│ │
|
|
38
|
+
│ run_once() ──→ CheckResult │
|
|
39
|
+
│ │
|
|
40
|
+
│ Bundles timestamp + ProbeResults + Issues into one result.│
|
|
41
|
+
└──────────────────────────┬──────────────────────────────────┘
|
|
42
|
+
│
|
|
43
|
+
▼
|
|
44
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
45
|
+
│ Aggregation (models.py) │
|
|
46
|
+
│ │
|
|
47
|
+
│ IssueRecord.from_results(list[CheckResult]) │
|
|
48
|
+
│ ──→ list[IssueRecord] │
|
|
49
|
+
│ │
|
|
50
|
+
│ Groups Issues by kind across all check results. │
|
|
51
|
+
│ Tracks count, last_seen, whether currently active, │
|
|
52
|
+
│ and last observed value. │
|
|
53
|
+
└──────────────────────────┬──────────────────────────────────┘
|
|
54
|
+
│
|
|
55
|
+
▼
|
|
56
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
57
|
+
│ UI (ui/) │
|
|
58
|
+
│ │
|
|
59
|
+
│ WatchState holds a rolling buffer of CheckResults. │
|
|
60
|
+
│ On each cycle: │
|
|
61
|
+
│ - Runs a check (run_once) │
|
|
62
|
+
│ - Appends the CheckResult │
|
|
63
|
+
│ - Renders dashboard: │
|
|
64
|
+
│ Status indicator (stable / unstable / problem) │
|
|
65
|
+
│ Issue history (from IssueRecords) │
|
|
66
|
+
│ Probe summary (gateway + internet one-liners) │
|
|
67
|
+
│ - Sends desktop notification on healthy→problem change │
|
|
68
|
+
└─────────────────────────────────────────────────────────────┘
|
|
69
|
+
```
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Metrics
|
|
2
|
+
|
|
3
|
+
yslow measures three things about your network connection. Each is collected by sending a series of probes (currently 5) to each target.
|
|
4
|
+
|
|
5
|
+
## RTT (round-trip time)
|
|
6
|
+
|
|
7
|
+
The time for a single probe to reach a target and get a response, in milliseconds.
|
|
8
|
+
|
|
9
|
+
We measure RTT using TCP handshakes (SYN → SYN-ACK) on port 443 for internet targets, and TCP or ICMP ping for the gateway. TCP handshake is preferred because it works without elevated privileges and isn't blocked by most firewalls.
|
|
10
|
+
|
|
11
|
+
We report the **average RTT** across all successful probes in a run.
|
|
12
|
+
|
|
13
|
+
### Thresholds
|
|
14
|
+
|
|
15
|
+
| Target | Elevated | High |
|
|
16
|
+
|--------|----------|------|
|
|
17
|
+
| Gateway (LAN) | > 3 ms | > 10 ms |
|
|
18
|
+
| ISP (internet avg minus gateway avg) | > 50 ms | > 150 ms |
|
|
19
|
+
| Internet (when no gateway data) | > 50 ms | > 150 ms |
|
|
20
|
+
|
|
21
|
+
"Elevated" is a warning; "high" is an error.
|
|
22
|
+
|
|
23
|
+
The ISP threshold uses the difference between internet RTT and gateway RTT to isolate how much latency is added beyond your local network.
|
|
24
|
+
|
|
25
|
+
## Packet loss
|
|
26
|
+
|
|
27
|
+
The percentage of probes that received no response within the timeout (3 seconds).
|
|
28
|
+
|
|
29
|
+
Any packet loss is flagged as an error. When both gateway and internet loss are measured, we compare them to determine the source:
|
|
30
|
+
|
|
31
|
+
- Internet loss > gateway loss → problem is beyond your local network (ISP/routing)
|
|
32
|
+
- Internet loss ≤ gateway loss → problem is likely your local network (wifi/router)
|
|
33
|
+
|
|
34
|
+
## Jitter
|
|
35
|
+
|
|
36
|
+
The sample standard deviation of RTTs within a single probe run, in milliseconds. It measures how **consistent** the connection is — a low average RTT with high jitter means some packets are fast and some are slow.
|
|
37
|
+
|
|
38
|
+
High jitter degrades real-time applications (video calls, gaming, VoIP) even when average latency looks acceptable, because individual packets arrive with unpredictable timing.
|
|
39
|
+
|
|
40
|
+
### How it's calculated
|
|
41
|
+
|
|
42
|
+
Given RTTs `[r1, r2, ..., rn]` with mean `m`:
|
|
43
|
+
|
|
44
|
+
jitter = sqrt(sum((ri - m)^2) / (n - 1))
|
|
45
|
+
|
|
46
|
+
This is the standard sample standard deviation (using Bessel's correction with `n-1`).
|
|
47
|
+
|
|
48
|
+
### Thresholds
|
|
49
|
+
|
|
50
|
+
| Target | High |
|
|
51
|
+
|--------|------|
|
|
52
|
+
| Gateway (LAN) | > 5 ms |
|
|
53
|
+
| Internet (beyond LAN) | > 20 ms above gateway jitter |
|
|
54
|
+
|
|
55
|
+
Internet jitter subtracts gateway jitter before comparing to the threshold, so local network jitter isn't double-counted. If your gateway has 15 ms jitter and your internet targets have 18 ms jitter, the ISP is only adding ~3 ms of jitter — the local network is the problem, not the ISP.
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Session: Continuous monitoring mode
|
|
2
|
+
|
|
3
|
+
Date: 2026-03-21
|
|
4
|
+
|
|
5
|
+
## What we built
|
|
6
|
+
|
|
7
|
+
Added `--watch` / `-w` flag for continuous monitoring mode. Runs checks on a configurable interval (default 30s), displays a live dashboard, and notifies on problems via terminal bell + OSC 9 desktop notifications.
|
|
8
|
+
|
|
9
|
+
Also unified the one-shot and monitoring output paths so both modes share the same rendering logic.
|
|
10
|
+
|
|
11
|
+
## Key decisions
|
|
12
|
+
|
|
13
|
+
### No TUI toolkit
|
|
14
|
+
|
|
15
|
+
Decided against adding a terminal UI library (e.g., rich, textual). The dashboard is a single screen redrawn periodically — `\033[2J\033[H` (clear + home) plus `print()` is sufficient. This preserves the zero-runtime-dependency constraint. If we ever need interactive controls or complex layouts, we can revisit.
|
|
16
|
+
|
|
17
|
+
### Structured error model: severity + kind, no category enum
|
|
18
|
+
|
|
19
|
+
Initially considered a `Category` enum (LOCAL, ISP, INTERNET, CONNECTIVITY) alongside `Severity`. Decided against it because:
|
|
20
|
+
- As we add more checks (DNS, wifi signal, bufferbloat, etc.), a fixed category enum would need constant expansion
|
|
21
|
+
- The "where" context is better captured in the description string, which is already descriptive
|
|
22
|
+
- Severity is what drives behavior (notifications, healthy/unhealthy state)
|
|
23
|
+
|
|
24
|
+
Each issue has a `kind` string (e.g., `"gateway-latency"`, `"isp-loss"`) for grouping, and `description` + `value` for display. The `message` property combines them: `"LAN: high latency to gateway (71 ms) — wifi congestion"`.
|
|
25
|
+
|
|
26
|
+
### Issue model: description + value, not a single message string
|
|
27
|
+
|
|
28
|
+
Split the human-readable message into `description` (stable across occurrences) and `value` (the specific measurement). This solved two problems:
|
|
29
|
+
- In monitoring mode, issues need to be grouped by kind — identical descriptions group naturally
|
|
30
|
+
- One-shot mode wants specific values, monitoring history wants the description without values cluttering the grouping
|
|
31
|
+
|
|
32
|
+
The `message` property reconstructs the full string by inserting the value before the em dash.
|
|
33
|
+
|
|
34
|
+
### Three monitoring states: stable / unstable / problem
|
|
35
|
+
|
|
36
|
+
Replaced the original OK/PROBLEM binary with three states:
|
|
37
|
+
- **Stable**: every check has been healthy, nothing to report
|
|
38
|
+
- **Problem**: current check has issues
|
|
39
|
+
- **Unstable**: current check is healthy, but problems were seen recently — the connection is flaky
|
|
40
|
+
|
|
41
|
+
This better matches the user's mental model. "It's fine now but it was bad 2 minutes ago" is meaningfully different from "it's always been fine."
|
|
42
|
+
|
|
43
|
+
### Persistent issue history
|
|
44
|
+
|
|
45
|
+
The dashboard shows every issue kind ever seen (within the rolling buffer), with count, last value, and timing. Active issues show `!!` prefix and "ongoing"; resolved ones show ` ` prefix and "Xm ago". This means transitioning from unstable back to problem doesn't lose the history of past issues.
|
|
46
|
+
|
|
47
|
+
### Notifications only on transition
|
|
48
|
+
|
|
49
|
+
Terminal bell + OSC 9 fire only on healthy-to-problem transitions. Repeating the bell every check cycle during a persistent problem would be unusable.
|
|
50
|
+
|
|
51
|
+
### Consistent output between modes
|
|
52
|
+
|
|
53
|
+
Both modes now use the same rendering path:
|
|
54
|
+
- Check modules (`gateway`, `internet`) own their `summary()` function for one-line status
|
|
55
|
+
- `_render_probes()` formats the summary lines
|
|
56
|
+
- Issue display uses the same formatting
|
|
57
|
+
- Status line appears in both modes
|
|
58
|
+
- Startup message in both modes ("checking..." / "running initial check...")
|
|
59
|
+
|
|
60
|
+
This eliminated `diagnose()`, `output.py`, and the `quiet` parameter on check modules. Checks are now pure data — they run probes and return results, with no print side effects.
|
|
61
|
+
|
|
62
|
+
### Labels: LAN and ISP/internet
|
|
63
|
+
|
|
64
|
+
Settled on "LAN:" and "ISP/internet:" as consistent labels across summary lines and issue descriptions. Initially had "Local network:" in issues vs "LAN:" in summaries vs "Internet:" in some places. Unified everything.
|
|
65
|
+
|
|
66
|
+
### Duration formatting
|
|
67
|
+
|
|
68
|
+
Two formatters: `_format_duration` for countdowns and session summaries (e.g., "2m 05s"), and `_format_ago` for issue history (seconds when under 1 minute, just minutes after — no one cares that an error was "2m 37s ago").
|
|
69
|
+
|
|
70
|
+
## Architectural direction
|
|
71
|
+
|
|
72
|
+
### Toward stateful check modules
|
|
73
|
+
|
|
74
|
+
The current check modules are stateless functions. The user expressed interest in making them stateful for monitoring mode — each check could:
|
|
75
|
+
- Decide its own run frequency (`should_run()`)
|
|
76
|
+
- Accumulate data across cycles
|
|
77
|
+
- Produce smarter summaries based on history
|
|
78
|
+
|
|
79
|
+
This would let future checks (DNS timing, wifi signal) run on different schedules and build up richer analysis without the monitor needing to understand each check's internals.
|
|
80
|
+
|
|
81
|
+
### Documentation as we go
|
|
82
|
+
|
|
83
|
+
Created `docs/metrics.md` covering RTT, packet loss, and jitter — what they are, how we measure them, and thresholds. Added a guideline to CLAUDE.md to update docs when changing metrics or diagnostic logic.
|
|
84
|
+
|
|
85
|
+
### Network condition simulation (TODO)
|
|
86
|
+
|
|
87
|
+
Noted the need for simulating network conditions in tests (packet loss, high latency, jitter) — possibly via Docker containers with `tc`/`netem`, network namespaces, or lightweight VMs. Currently all tests use mocking.
|
|
88
|
+
|
|
89
|
+
## Files changed
|
|
90
|
+
|
|
91
|
+
- `yslow/models.py` — added `Severity`, `Issue` (with kind/description/value/message), `CheckResult`
|
|
92
|
+
- `yslow/diagnosis.py` — refactored to return `list[Issue]`, removed `diagnose()`
|
|
93
|
+
- `yslow/monitor.py` — new: monitoring loop, state tracking, dashboard rendering, notifications
|
|
94
|
+
- `yslow/checks/gateway.py` — simplified to pure data, added `summary()`
|
|
95
|
+
- `yslow/checks/internet.py` — simplified to pure data, added `summary()`
|
|
96
|
+
- `yslow/__init__.py` — added argparse, unified one-shot and watch mode
|
|
97
|
+
- `yslow/output.py` — removed (dead code after consolidation)
|
|
98
|
+
- `docs/metrics.md` — new: measurement documentation
|
|
99
|
+
- `tests/test_diagnosis.py` — updated for `Issue` return type
|
|
100
|
+
- `tests/test_monitor.py` — new: monitor state, notification, and render tests
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# UI overhaul session — 2026-03-21
|
|
2
|
+
|
|
3
|
+
## What we did
|
|
4
|
+
|
|
5
|
+
### ASCII banner
|
|
6
|
+
- Added a figlet-style "yslow" ASCII art banner displayed at startup in both modes.
|
|
7
|
+
|
|
8
|
+
### Restructured into `ui/` package
|
|
9
|
+
- Moved one-shot mode out of `__init__.py` into its own file.
|
|
10
|
+
- Created `ui/` package with:
|
|
11
|
+
- `common.py` — shared console, banner, formatting helpers, probe rendering
|
|
12
|
+
- `check.py` — one-shot mode (`check()`)
|
|
13
|
+
- `watch.py` — continuous mode (`watch()`, `WatchState`, issue history)
|
|
14
|
+
- Created `runner.py` — `run_once()` extracted from UI code where it didn't belong.
|
|
15
|
+
- Renamed `MonitorState` → `WatchState`, `monitor()` → `watch()`, `oneshot()` → `check()` to match CLI verbs (`yslow` = check, `yslow -w` = watch).
|
|
16
|
+
|
|
17
|
+
### Snapshot tests
|
|
18
|
+
- Added `tests/test_snapshots.py` with `--snapshot-update` flag for regeneration.
|
|
19
|
+
- One snapshot file per scenario per mode (6 files in `tests/snapshots/`).
|
|
20
|
+
- Tests call the real `check()` and `render()` functions, capturing stdout — no knowledge of internal rendering structure.
|
|
21
|
+
- Uses `Console(color_system=None)` for plain-text snapshots (also tests the pipe-to-file path).
|
|
22
|
+
- Three scenarios: all good, one issue, everything failed.
|
|
23
|
+
|
|
24
|
+
### Swapped issue/probe ordering
|
|
25
|
+
- Both modes now show issues/status before probe detail stats.
|
|
26
|
+
|
|
27
|
+
### Migrated to rich
|
|
28
|
+
- Replaced hand-rolled ANSI escape codes with `rich.Console`.
|
|
29
|
+
- Colors: green checkmark for healthy, red warning for issues, yellow for unstable, dim for secondary info (banner, probes).
|
|
30
|
+
- `console.clear()` replaces raw `\033[2J\033[H`.
|
|
31
|
+
- Auto-strips formatting when piped to a file (rich's TTY detection).
|
|
32
|
+
|
|
33
|
+
### Spinners
|
|
34
|
+
- Replaced the countdown timer (`Next check in 30s`) with rich `console.status()` spinners.
|
|
35
|
+
- `checking...` spinner while probes run (both modes).
|
|
36
|
+
- `rechecking in {interval}s...` spinner while waiting (watch mode).
|
|
37
|
+
|
|
38
|
+
### Structured Issue model
|
|
39
|
+
- Split `Issue.description` into `location`, `problem`, `diagnosis` fields.
|
|
40
|
+
- Updated `diagnosis.py` with structured fields for all issue types.
|
|
41
|
+
- `Issue.message` property still produces a readable single-line string from the fields.
|
|
42
|
+
|
|
43
|
+
### Issue table (rich Table)
|
|
44
|
+
- Built `render_issues()` using `rich.Table` with columns: location, problem, diagnosis, value.
|
|
45
|
+
- Watch mode history table adds count + timing columns.
|
|
46
|
+
|
|
47
|
+
## Open question / next steps
|
|
48
|
+
|
|
49
|
+
The issue table and probe stats sections feel redundant — they show overlapping information (e.g., "LAN: high latency to gateway, 45ms" in the issue table vs "LAN: 45.0 ms rtt" in the probe stats). Unifying them into a single view would be cleaner but the right design isn't obvious yet. The table stuff may get reverted while this is figured out.
|
|
50
|
+
|
|
51
|
+
Possible directions:
|
|
52
|
+
- Single table that shows probes with inline issue annotations
|
|
53
|
+
- Issues as the primary view, probes only shown in a `--verbose` mode
|
|
54
|
+
- Probes as the primary table with issue indicators (color/icons) on problem rows
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Data model cleanup — 2026-03-22
|
|
2
|
+
|
|
3
|
+
## What we did
|
|
4
|
+
|
|
5
|
+
### Promoted IssueRecord to a proper model
|
|
6
|
+
- Moved `IssueRecord` from `ui/watch.py` to `models.py` — it's a domain concept (aggregation of issues across check results), not a UI concern.
|
|
7
|
+
- Added `IssueRecord.from_results(list[CheckResult])` static method, moving the aggregation logic out of `WatchState`.
|
|
8
|
+
- `WatchState.issue_history()` now delegates to `IssueRecord.from_results()`.
|
|
9
|
+
|
|
10
|
+
### Added dataflow documentation
|
|
11
|
+
- Created `docs/dataflow.md` documenting the full pipeline: probes → diagnosis → CheckResult → IssueRecord → UI.
|
|
12
|
+
- Covers all four model types and how they relate.
|
|
13
|
+
|
|
14
|
+
## Context
|
|
15
|
+
|
|
16
|
+
Working toward unifying the redundant display sections (issue history, probe summary, session summary). Understanding and cleaning up the data model is the first step — the display redundancy noted in the 2026-03-21 session is still open.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Remove session summary & reorganize tests — 2026-03-22
|
|
2
|
+
|
|
3
|
+
## What we did
|
|
4
|
+
|
|
5
|
+
### Removed redundant session summary
|
|
6
|
+
- Deleted `print_summary()` from `ui/watch.py` — it duplicated the dashboard's issue history display.
|
|
7
|
+
- Removed `format_duration()` from `ui/common.py` (sole caller was `print_summary`).
|
|
8
|
+
- Removed `started_at` and `elapsed` from `WatchState` (only used by `print_summary`).
|
|
9
|
+
- Updated `docs/dataflow.md` to remove the "on exit" line.
|
|
10
|
+
|
|
11
|
+
### Reorganized tests to match module structure
|
|
12
|
+
- Deleted `test_monitor.py` — was a grab bag testing `models`, `ui/common`, and `ui/watch`.
|
|
13
|
+
- Created `test_watch.py` — `WatchState`, `notify`, `_render` tests.
|
|
14
|
+
- Created `test_common.py` — `format_ago` tests.
|
|
15
|
+
- Moved `TestCheckResultHealthy` and `TestIssueHistory` into `test_models.py`.
|
|
16
|
+
- Added shared `make_check` helper and `T0` constant to `conftest.py`.
|
|
17
|
+
|
|
18
|
+
## Context
|
|
19
|
+
|
|
20
|
+
Continues the display redundancy cleanup noted in the 2026-03-21 session and the data model work from earlier today. The dashboard is now the single place issues are displayed in watch mode.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# 2026-03-22 — UI status header rework and diagnosis cleanup
|
|
2
|
+
|
|
3
|
+
## What changed
|
|
4
|
+
|
|
5
|
+
### UI status header
|
|
6
|
+
- Replaced the old `problem` / `unstable` / `stable` header with a conversational format:
|
|
7
|
+
- `Is it slow?` on its own line (dim)
|
|
8
|
+
- Answer on next line: `● YES!` (red) / `● Sorta?` (yellow) / `● Nope!` (green)
|
|
9
|
+
- Added section headers: `Why?` before issue list, `Details` before probe results
|
|
10
|
+
- Removed gateway address from the LAN details line (was showing `(gateway 192.168.1.1)`)
|
|
11
|
+
|
|
12
|
+
### Diagnosis: combined gateway-latency and gateway-jitter
|
|
13
|
+
- Old: separate `gateway-latency` and `gateway-jitter` issues with different descriptions
|
|
14
|
+
- New: single `gateway-quality` issue ("LAN: unstable wifi/LAN connection") that includes whichever details are relevant (rtt, jitter, or both) in the value string
|
|
15
|
+
- `gateway-loss` remains separate since packet loss is a distinct symptom
|
|
16
|
+
|
|
17
|
+
### Snapshot tests
|
|
18
|
+
- Fixed `--snapshot-update` flag: comparison tests now skip when updating instead of failing before the update test runs
|
|
19
|
+
- Added snapshot update command to CLAUDE.md quick reference
|
|
20
|
+
|
|
21
|
+
## Open questions
|
|
22
|
+
- None
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "yslow"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Diagnose why your network connection is slow"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
requires-python = ">=3.10"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "Wesley Ellis", email = "wesley@tahnok.ca" },
|
|
10
|
+
]
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Development Status :: 3 - Alpha",
|
|
13
|
+
"Environment :: Console",
|
|
14
|
+
"Intended Audience :: System Administrators",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Operating System :: POSIX :: Linux",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.10",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Programming Language :: Python :: 3.13",
|
|
23
|
+
"Topic :: System :: Networking :: Monitoring",
|
|
24
|
+
]
|
|
25
|
+
keywords = ["network", "diagnostics", "latency", "wifi", "troubleshooting"]
|
|
26
|
+
dependencies = [
|
|
27
|
+
"rich>=14.3.3",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://codeberg.org/tahnok/yslow"
|
|
32
|
+
Repository = "https://codeberg.org/tahnok/yslow"
|
|
33
|
+
Issues = "https://codeberg.org/tahnok/yslow/issues"
|
|
34
|
+
|
|
35
|
+
[project.scripts]
|
|
36
|
+
yslow = "yslow:main"
|
|
37
|
+
|
|
38
|
+
[build-system]
|
|
39
|
+
requires = ["hatchling"]
|
|
40
|
+
build-backend = "hatchling.build"
|
|
41
|
+
|
|
42
|
+
[dependency-groups]
|
|
43
|
+
dev = [
|
|
44
|
+
"freezegun>=1.5.5",
|
|
45
|
+
"pytest>=9.0.2",
|
|
46
|
+
"ruff>=0.15.7",
|
|
47
|
+
"ty>=0.0.24",
|
|
48
|
+
]
|