Skip to content

Commit cdcd553

Browse files
mktbshclaude
andcommitted
docs: add web documentation for the docs site
Add docs/www/ pages following the hsblabs OSS document profile (schema_version 2026-08-15, okf_version 0.2). Documents three behaviours that were previously unrecorded, all verified against the source: - Network quality score thresholds for streaming, gaming, and video chat, which the terminal and HTML reports show but no document explained, and which the JSON output omits entirely. - The sampling plan and statistics behind each metric, including the Server-Timing subtraction on latency and the differing timing windows for download and upload throughput. - Exit codes, and the split between fatal failures and partial ones that degrade to a null field plus a warning. Internal links use relative file paths so they survive whatever mount point hsblabs-docs assigns; root-absolute links would 404 under a namespaced route. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 9d4beae commit cdcd553

4 files changed

Lines changed: 301 additions & 0 deletions

File tree

docs/www/development.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
---
2+
schema_version: "2026-08-15"
3+
okf_version: "0.2"
4+
type: guide
5+
title: Building, testing, and releasing
6+
description: How to build unispeedtest from source, run its test suite, and cut a release through Conventional Commits and release-please.
7+
resource: https://github.com/hsblabs/universal-speedtest-cli
8+
tags:
9+
- go
10+
- contributing
11+
- release
12+
status: stable
13+
hsblabs:
14+
sidebar:
15+
label: Development
16+
order: 4
17+
---
18+
19+
This page is for working on `unispeedtest` itself. To install and use the released binary, start at the [overview](./index.md).
20+
21+
## Test
22+
23+
```sh
24+
go test ./...
25+
```
26+
27+
## Build
28+
29+
```sh
30+
go build -trimpath -ldflags="-s -w" -o dist/unispeedtest ./cmd/unispeedtest
31+
```
32+
33+
A build without an embedded version reports `dev` for `--version`; released binaries carry the tag from the module build info.
34+
35+
## Release
36+
37+
Releases are automated and driven by commit messages.
38+
39+
1. Merged PRs follow [Conventional Commits](https://www.conventionalcommits.org/)`fix:`, `feat:`, and `feat!:` or a `BREAKING CHANGE:` footer for majors.
40+
2. A push to `main` opens or updates a release PR via `release-please`.
41+
3. Merging that release PR updates `version.txt` and `CHANGELOG.md`.
42+
4. The release workflow tags `vX.Y.Z` and publishes artifacts with GoReleaser.
43+
44+
Nothing is tagged or published by hand; the release PR is the only gate.
45+
46+
## License
47+
48+
MIT.

docs/www/how-it-works.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
---
2+
schema_version: "2026-08-15"
3+
okf_version: "0.2"
4+
type: reference
5+
title: How unispeedtest measures your connection
6+
description: The sampling plan behind each metric, why throughput uses the 90th percentile, the network quality score thresholds, and how partial failures are handled.
7+
resource: https://github.com/hsblabs/universal-speedtest-cli
8+
tags:
9+
- methodology
10+
- network
11+
- speedtest
12+
status: stable
13+
hsblabs:
14+
sidebar:
15+
label: How it works
16+
order: 3
17+
---
18+
19+
Speed test numbers only mean something if you know how they were produced. This page documents the sampling plan and the statistics `unispeedtest` applies, so a result can be compared against other tools — or against itself over time — without guessing.
20+
21+
All measurements run against Cloudflare speed test endpoints over a client with a 30 second request timeout.
22+
23+
## Latency
24+
25+
20 samples are taken with a zero-byte `GET` before any load is applied.
26+
27+
Each sample measures the time from request start to the first response byte, then **subtracts Cloudflare's `Server-Timing` value** so the result reflects network round-trip time rather than server processing. When the header is missing or unparseable, the raw client timing is used instead and a warning names how many samples fell back.
28+
29+
- **Unloaded latency** — median of the samples.
30+
- **Jitter** — mean absolute difference between consecutive samples. Requires at least 2 samples.
31+
- **Loaded latency** — median of the latency samples collected *during* the download and upload phases, reported separately for each. This is the number that reflects bufferbloat.
32+
33+
## Throughput
34+
35+
Each phase transfers a fixed ladder of payload sizes, small to large, so both fast and slow connections land on a usable set of samples.
36+
37+
| Payload | Download runs | Upload runs |
38+
| --- | --- | --- |
39+
| 101 KB | 10 | 8 |
40+
| 1 MB | 8 | 6 |
41+
| 10 MB | 6 | 4 |
42+
| 25 MB | 4 | 4 |
43+
44+
The two directions time different windows, because the byte movement they care about happens at different points in the exchange:
45+
46+
- **Download** — first response byte to last response byte. Connection setup and time-to-first-byte are excluded, so the figure is transfer rate rather than end-to-end request time.
47+
- **Upload** — request start until the request body has been fully written. If that timing is unavailable, the full end-to-end duration is used instead and a warning is emitted; such samples read slower than the link actually is.
48+
49+
The reported value is the **90th percentile** of those samples, with linear interpolation between neighbours. A high percentile rather than the mean or max: the mean is dragged down by the small payloads that never reach line rate, and the max would report a single lucky burst.
50+
51+
## Packet loss
52+
53+
1000 requests at concurrency 50. The result is the percentage that did not come back, and the terminal report also shows the raw received-over-total count.
54+
55+
## Network quality score
56+
57+
The terminal and HTML reports grade the connection **Good** or **Poor** for three use cases. Every condition must hold for a Good grade:
58+
59+
| Use case | Conditions |
60+
| --- | --- |
61+
| Video streaming | download > 5 Mbps, unloaded latency < 100 ms, packet loss < 2% |
62+
| Online gaming | unloaded latency < 50 ms, jitter < 20 ms, packet loss < 1% |
63+
| Video chatting | download > 2 Mbps, upload > 2 Mbps, unloaded latency < 100 ms, jitter < 30 ms, packet loss < 1% |
64+
65+
If any of the five inputs is missing, the whole score reads `N/A (insufficient data)` rather than grading on partial evidence.
66+
67+
The score is derived entirely from the metrics above, and is **not** part of the JSON output — a consumer that needs it applies these thresholds itself.
68+
69+
## Failures
70+
71+
The run distinguishes two kinds of failure.
72+
73+
**Fatal** — a phase produced no usable samples at all. Latency, download, upload, and packet loss each abort the run this way. Nothing is reported.
74+
75+
**Partial** — some samples failed, or a derived metric could not be computed. The run continues, the affected field is `null` in JSON, and a warning explains why. Individual sample failures are collapsed into one warning per category carrying the count and the first error.
76+
77+
### Exit codes
78+
79+
| Code | Meaning |
80+
| --- | --- |
81+
| `0` | Run completed, or `--version` / `-h` printed and exited |
82+
| `1` | A measurement phase failed fatally |
83+
| `2` | Invalid flags, including `-html-title` without `-html` |
84+
85+
A `0` exit does not imply a complete result. Check `warnings` — see [Output formats](./output.md).
86+
87+
## Progress output
88+
89+
Progress lines are written to stdout during a normal run and suppressed by `-json`, so piping to a JSON parser needs no extra redirection.

docs/www/index.md

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
---
2+
schema_version: "2026-08-15"
3+
okf_version: "0.2"
4+
type: guide
5+
title: unispeedtest — command-line internet speed test
6+
description: A command-line network benchmark that measures download and upload throughput, latency, jitter, and packet loss over Cloudflare speed test endpoints.
7+
resource: https://github.com/hsblabs/universal-speedtest-cli
8+
tags:
9+
- cli
10+
- go
11+
- network
12+
- speedtest
13+
status: stable
14+
hsblabs:
15+
sidebar:
16+
label: Overview
17+
order: 1
18+
---
19+
20+
`unispeedtest` measures the quality of a network connection from the terminal. It currently drives Cloudflare speed test endpoints, and the provider layer is structured so other backends can be added later.
21+
22+
## What it measures
23+
24+
| Metric | How it is derived |
25+
| --- | --- |
26+
| Download throughput | 90th percentile of sampled Mbps values |
27+
| Upload throughput | 90th percentile of sampled Mbps values |
28+
| Unloaded latency | Median of 20 samples |
29+
| Loaded latency | Measured separately during the download and upload phases |
30+
| Jitter | Mean absolute difference between consecutive unloaded latency samples |
31+
| Packet loss | 1000 requests at concurrency 50 |
32+
| Network metadata | Cloudflare colo, ASN and AS organization, public IP |
33+
34+
The reports also grade the connection for streaming, gaming, and video chat. The full sampling plan and the grading thresholds are in [How measurements work](./how-it-works.md).
35+
36+
## Install
37+
38+
### From GitHub Releases
39+
40+
The installer script resolves the latest release, verifies the archive against the release `checksums.txt` with SHA-256, and installs to `/usr/local/bin`:
41+
42+
```sh
43+
curl -fsSL https://raw.githubusercontent.com/hsblabs/universal-speedtest-cli/main/install.sh | sh
44+
```
45+
46+
Set `INSTALL_DIR` to install elsewhere:
47+
48+
```sh
49+
curl -fsSL https://raw.githubusercontent.com/hsblabs/universal-speedtest-cli/main/install.sh | INSTALL_DIR="$HOME/.local/bin" sh
50+
```
51+
52+
To install by hand, download an archive from [Releases](https://github.com/hsblabs/universal-speedtest-cli/releases) and place the `unispeedtest` binary somewhere on your `PATH`.
53+
54+
### With Go
55+
56+
```sh
57+
go install github.com/hsblabs/universal-speedtest-cli/cmd/unispeedtest@latest
58+
```
59+
60+
The binary is named `unispeedtest`.
61+
62+
## Run
63+
64+
```sh
65+
unispeedtest
66+
```
67+
68+
A full run takes roughly a minute and prints a colored summary. Set `NO_COLOR=1` to disable ANSI colors.
69+
70+
## Options
71+
72+
| Flag | Effect |
73+
| --- | --- |
74+
| `-html <path>` | Write a self-contained HTML report to `<path>` |
75+
| `-html-title <title>` | Append a title to the HTML report; requires `-html` |
76+
| `-json` | Print compact single-line JSON |
77+
| `-pretty` | Print indented JSON; implies `-json` |
78+
| `-v`, `--version` | Print the CLI version and exit |
79+
80+
```sh
81+
unispeedtest -html report.html -html-title "Home Wi-Fi"
82+
unispeedtest -json
83+
unispeedtest -pretty
84+
unispeedtest --version
85+
```
86+
87+
Passing `-html-title` without `-html` is an error and exits with status 2.
88+
89+
Both report formats are covered in [Output formats](./output.md).
90+
91+
Under `-json`, progress output is suppressed so stdout stays parseable.

docs/www/output.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
---
2+
schema_version: "2026-08-15"
3+
okf_version: "0.2"
4+
type: reference
5+
title: JSON and HTML output
6+
description: The JSON payload unispeedtest emits, its nullable fields and warnings, and the self-contained HTML speed test report.
7+
resource: https://github.com/hsblabs/universal-speedtest-cli
8+
tags:
9+
- cli
10+
- json
11+
- reporting
12+
status: stable
13+
hsblabs:
14+
sidebar:
15+
label: Output formats
16+
order: 2
17+
---
18+
19+
`unispeedtest` writes a human-readable summary to stdout by default. `-json` and `-html` add machine-readable and shareable formats; they compose, so a single run can produce all three.
20+
21+
## JSON
22+
23+
`-json` prints one line; `-pretty` prints the same document indented.
24+
25+
```json
26+
{
27+
"download_mbps": 225.14,
28+
"upload_mbps": 102.87,
29+
"latency_ms": {
30+
"unloaded": 12.41,
31+
"loaded_down": 35.09,
32+
"loaded_up": 41.22,
33+
"jitter": 1.98
34+
},
35+
"packet_loss_percent": 0.1,
36+
"server_colo": "Tokyo",
37+
"network_asn": "AS2516",
38+
"network_as_org": "KDDI CORPORATION",
39+
"ip": "203.0.113.10",
40+
"warnings": [
41+
"upload loaded latency unavailable: no samples collected"
42+
]
43+
}
44+
```
45+
46+
Progress output is suppressed under `-json`, so stdout is a clean JSON document.
47+
48+
### Partial failures
49+
50+
A run can succeed overall while an individual measurement fails. When that happens the affected field is emitted as `null` and an explanation is appended to `warnings`, so a consumer can tell missing data apart from a genuine `0`.
51+
52+
Two things to handle when parsing:
53+
54+
- **Every metric field is nullable.** `download_mbps`, `upload_mbps`, all four `latency_ms` members, `packet_loss_percent`, and the network metadata strings can all be `null`.
55+
- **`warnings` is omitted entirely when empty** — it is not an empty array. Read it as "absent or non-empty".
56+
57+
A zero exit code means the run finished, not that every metric is present. See [How measurements work](./how-it-works.md) for which failures are fatal and which degrade to a warning.
58+
59+
The network quality score shown in the terminal and HTML reports is not included in the JSON. Its thresholds are documented in [How measurements work](./how-it-works.md#network-quality-score) if you need to reproduce it.
60+
61+
## HTML report
62+
63+
`-html <path>` writes a responsive single-file report with no external assets — no CDN, no network access needed to view it. An existing file at the path is overwritten.
64+
65+
The measurement time is stored as Unix epoch milliseconds and rendered by inline JavaScript in the viewer's own locale and time zone, so a report shared across regions reads correctly for each reader.
66+
67+
`-html-title` sets a suffix on both the document title and the page heading:
68+
69+
```sh
70+
unispeedtest -html report.html -html-title "Home Wi-Fi"
71+
```
72+
73+
produces `Internet Speed Report - Home Wi-Fi`.

0 commit comments

Comments
 (0)