linux-mirror-bench 0.1.2__tar.gz → 0.2.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.
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/.github/workflows/ci.yml +12 -12
- linux_mirror_bench-0.2.0/.github/workflows/integration.yml +42 -0
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/.github/workflows/release.yml +4 -4
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/.pre-commit-config.yaml +1 -0
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/.release-please-config.json +1 -0
- linux_mirror_bench-0.2.0/.release-please-manifest.json +3 -0
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/AGENTS.md +13 -4
- linux_mirror_bench-0.2.0/CHANGELOG.md +105 -0
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/CONTRIBUTING.md +1 -1
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/Dockerfile +5 -2
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/PKG-INFO +93 -27
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/README.md +89 -25
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/pyproject.toml +6 -1
- linux_mirror_bench-0.2.0/src/mirror_bench/__init__.py +5 -0
- linux_mirror_bench-0.2.0/src/mirror_bench/benchmark/h3.py +88 -0
- linux_mirror_bench-0.2.0/src/mirror_bench/benchmark/h3_quic.py +193 -0
- linux_mirror_bench-0.2.0/src/mirror_bench/benchmark/probe.py +283 -0
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/src/mirror_bench/benchmark/scorer.py +57 -29
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/src/mirror_bench/benchmark/transport.py +9 -4
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/src/mirror_bench/cli.py +81 -22
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/src/mirror_bench/discovery/__init__.py +2 -1
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/src/mirror_bench/discovery/arch.py +12 -8
- linux_mirror_bench-0.2.0/src/mirror_bench/discovery/base.py +102 -0
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/src/mirror_bench/discovery/debian.py +20 -24
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/src/mirror_bench/discovery/fedora.py +62 -26
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/src/mirror_bench/discovery/mint.py +9 -6
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/src/mirror_bench/discovery/ubuntu.py +32 -37
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/src/mirror_bench/display.py +44 -3
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/src/mirror_bench/distro.py +20 -26
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/src/mirror_bench/models.py +5 -1
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/tests/fixtures/arch_status.json +17 -0
- linux_mirror_bench-0.2.0/tests/fixtures/debian_masterlist.txt +31 -0
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/tests/test_cli.py +78 -0
- linux_mirror_bench-0.2.0/tests/test_discovery.py +491 -0
- linux_mirror_bench-0.2.0/tests/test_display.py +122 -0
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/tests/test_distro.py +67 -1
- linux_mirror_bench-0.2.0/tests/test_h3.py +238 -0
- linux_mirror_bench-0.2.0/tests/test_integration.py +88 -0
- linux_mirror_bench-0.2.0/tests/test_probe.py +401 -0
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/tests/test_scorer.py +74 -3
- linux_mirror_bench-0.2.0/uv.lock +658 -0
- linux_mirror_bench-0.1.2/.release-please-manifest.json +0 -3
- linux_mirror_bench-0.1.2/CHANGELOG.md +0 -61
- linux_mirror_bench-0.1.2/src/mirror_bench/__init__.py +0 -3
- linux_mirror_bench-0.1.2/src/mirror_bench/benchmark/probe.py +0 -251
- linux_mirror_bench-0.1.2/src/mirror_bench/discovery/base.py +0 -43
- linux_mirror_bench-0.1.2/tests/fixtures/debian_masterlist.txt +0 -19
- linux_mirror_bench-0.1.2/tests/test_discovery.py +0 -272
- linux_mirror_bench-0.1.2/tests/test_integration.py +0 -36
- linux_mirror_bench-0.1.2/tests/test_probe.py +0 -123
- linux_mirror_bench-0.1.2/uv.lock +0 -424
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/.dockerignore +0 -0
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/.editorconfig +0 -0
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/.github/dependabot.yml +0 -0
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/.github/workflows/release-please.yml +0 -0
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/.gitignore +0 -0
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/.markdownlint.yaml +0 -0
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/.tool-versions +0 -0
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/.yamllint.yaml +0 -0
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/CODEOWNERS +0 -0
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/LICENSE +0 -0
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/Makefile +0 -0
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/SECURITY.md +0 -0
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/src/mirror_bench/__main__.py +0 -0
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/src/mirror_bench/benchmark/__init__.py +0 -0
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/src/mirror_bench/benchmark/tls.py +0 -0
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/tests/__init__.py +0 -0
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/tests/conftest.py +0 -0
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/tests/fixtures/fedora_mirrorlist.txt +0 -0
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/tests/fixtures/mint_mirrors.html +0 -0
- {linux_mirror_bench-0.1.2 → linux_mirror_bench-0.2.0}/tests/fixtures/ubuntu_mirrors.txt +0 -0
|
@@ -23,8 +23,8 @@ jobs:
|
|
|
23
23
|
name: Pre-commit
|
|
24
24
|
runs-on: ubuntu-latest
|
|
25
25
|
steps:
|
|
26
|
-
- uses: actions/checkout@
|
|
27
|
-
- uses: actions/setup-python@
|
|
26
|
+
- uses: actions/checkout@v7
|
|
27
|
+
- uses: actions/setup-python@v7
|
|
28
28
|
with:
|
|
29
29
|
python-version: ${{ env.PYTHON_VERSION }}
|
|
30
30
|
- uses: astral-sh/setup-uv@v7
|
|
@@ -38,13 +38,13 @@ jobs:
|
|
|
38
38
|
name: Lint + format
|
|
39
39
|
runs-on: ubuntu-latest
|
|
40
40
|
steps:
|
|
41
|
-
- uses: actions/checkout@
|
|
41
|
+
- uses: actions/checkout@v7
|
|
42
42
|
- uses: astral-sh/setup-uv@v7
|
|
43
43
|
with:
|
|
44
44
|
version: ${{ env.UV_VERSION }}
|
|
45
45
|
enable-cache: true
|
|
46
46
|
cache-dependency-glob: pyproject.toml
|
|
47
|
-
- run: uv sync --frozen --all-extras
|
|
47
|
+
- run: uv sync --frozen --all-extras
|
|
48
48
|
- name: Ruff check
|
|
49
49
|
run: uv run ruff check --output-format=github .
|
|
50
50
|
- name: Ruff format check
|
|
@@ -54,13 +54,13 @@ jobs:
|
|
|
54
54
|
name: Mypy (strict)
|
|
55
55
|
runs-on: ubuntu-latest
|
|
56
56
|
steps:
|
|
57
|
-
- uses: actions/checkout@
|
|
57
|
+
- uses: actions/checkout@v7
|
|
58
58
|
- uses: astral-sh/setup-uv@v7
|
|
59
59
|
with:
|
|
60
60
|
version: ${{ env.UV_VERSION }}
|
|
61
61
|
enable-cache: true
|
|
62
62
|
cache-dependency-glob: pyproject.toml
|
|
63
|
-
- run: uv sync --frozen --all-extras
|
|
63
|
+
- run: uv sync --frozen --all-extras
|
|
64
64
|
- name: mypy
|
|
65
65
|
run: uv run mypy src tests
|
|
66
66
|
|
|
@@ -72,8 +72,8 @@ jobs:
|
|
|
72
72
|
matrix:
|
|
73
73
|
python: ["3.14"]
|
|
74
74
|
steps:
|
|
75
|
-
- uses: actions/checkout@
|
|
76
|
-
- uses: actions/setup-python@
|
|
75
|
+
- uses: actions/checkout@v7
|
|
76
|
+
- uses: actions/setup-python@v7
|
|
77
77
|
with:
|
|
78
78
|
python-version: ${{ matrix.python }}
|
|
79
79
|
- uses: astral-sh/setup-uv@v7
|
|
@@ -81,7 +81,7 @@ jobs:
|
|
|
81
81
|
version: ${{ env.UV_VERSION }}
|
|
82
82
|
enable-cache: true
|
|
83
83
|
cache-dependency-glob: pyproject.toml
|
|
84
|
-
- run: uv sync --frozen --all-extras
|
|
84
|
+
- run: uv sync --frozen --all-extras
|
|
85
85
|
- name: pytest
|
|
86
86
|
run: uv run pytest -v --tb=short
|
|
87
87
|
- name: Smoke-test the built CLI
|
|
@@ -93,13 +93,13 @@ jobs:
|
|
|
93
93
|
name: Security scan (bandit + pip-audit)
|
|
94
94
|
runs-on: ubuntu-latest
|
|
95
95
|
steps:
|
|
96
|
-
- uses: actions/checkout@
|
|
96
|
+
- uses: actions/checkout@v7
|
|
97
97
|
- uses: astral-sh/setup-uv@v7
|
|
98
98
|
with:
|
|
99
99
|
version: ${{ env.UV_VERSION }}
|
|
100
100
|
enable-cache: true
|
|
101
101
|
cache-dependency-glob: pyproject.toml
|
|
102
|
-
- run: uv sync --frozen --all-extras
|
|
102
|
+
- run: uv sync --frozen --all-extras
|
|
103
103
|
- name: Bandit
|
|
104
104
|
run: uv run --with "bandit[toml]" bandit -c pyproject.toml -r src
|
|
105
105
|
- name: pip-audit (runtime deps)
|
|
@@ -114,7 +114,7 @@ jobs:
|
|
|
114
114
|
contents: read
|
|
115
115
|
security-events: write
|
|
116
116
|
steps:
|
|
117
|
-
- uses: actions/checkout@
|
|
117
|
+
- uses: actions/checkout@v7
|
|
118
118
|
- uses: docker/setup-buildx-action@v4
|
|
119
119
|
- name: Build image
|
|
120
120
|
uses: docker/build-push-action@v7
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
# Runs the opt-in integration tests against real mirror infrastructure.
|
|
3
|
+
# Mirror lists, file paths, and release names change upstream without notice;
|
|
4
|
+
# this is the only job that sees it. It is not a PR gate, because a failure
|
|
5
|
+
# here is usually upstream drift, not a defect in the change under review.
|
|
6
|
+
|
|
7
|
+
name: Integration
|
|
8
|
+
|
|
9
|
+
on:
|
|
10
|
+
schedule:
|
|
11
|
+
- cron: "17 6 * * 1"
|
|
12
|
+
workflow_dispatch:
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
contents: read
|
|
16
|
+
|
|
17
|
+
concurrency:
|
|
18
|
+
group: ${{ github.workflow }}
|
|
19
|
+
cancel-in-progress: true
|
|
20
|
+
|
|
21
|
+
env:
|
|
22
|
+
PYTHON_VERSION: "3.14"
|
|
23
|
+
UV_VERSION: "0.11.7"
|
|
24
|
+
|
|
25
|
+
jobs:
|
|
26
|
+
integration:
|
|
27
|
+
name: Live mirrors
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
timeout-minutes: 15
|
|
30
|
+
steps:
|
|
31
|
+
- uses: actions/checkout@v6
|
|
32
|
+
- uses: actions/setup-python@v6
|
|
33
|
+
with:
|
|
34
|
+
python-version: ${{ env.PYTHON_VERSION }}
|
|
35
|
+
- uses: astral-sh/setup-uv@v7
|
|
36
|
+
with:
|
|
37
|
+
version: ${{ env.UV_VERSION }}
|
|
38
|
+
enable-cache: true
|
|
39
|
+
cache-dependency-glob: pyproject.toml
|
|
40
|
+
- run: uv sync --frozen --all-extras
|
|
41
|
+
- name: pytest (integration)
|
|
42
|
+
run: make test-integration
|
|
@@ -31,8 +31,8 @@ jobs:
|
|
|
31
31
|
outputs:
|
|
32
32
|
version: ${{ steps.version.outputs.value }}
|
|
33
33
|
steps:
|
|
34
|
-
- uses: actions/checkout@
|
|
35
|
-
- uses: actions/setup-python@
|
|
34
|
+
- uses: actions/checkout@v7
|
|
35
|
+
- uses: actions/setup-python@v7
|
|
36
36
|
with:
|
|
37
37
|
python-version: ${{ env.PYTHON_VERSION }}
|
|
38
38
|
- uses: astral-sh/setup-uv@v7
|
|
@@ -94,7 +94,7 @@ jobs:
|
|
|
94
94
|
id-token: write
|
|
95
95
|
attestations: write
|
|
96
96
|
steps:
|
|
97
|
-
- uses: actions/checkout@
|
|
97
|
+
- uses: actions/checkout@v7
|
|
98
98
|
- uses: docker/setup-qemu-action@v4
|
|
99
99
|
- uses: docker/setup-buildx-action@v4
|
|
100
100
|
- uses: docker/login-action@v4
|
|
@@ -143,7 +143,7 @@ jobs:
|
|
|
143
143
|
permissions:
|
|
144
144
|
contents: write
|
|
145
145
|
steps:
|
|
146
|
-
- uses: actions/checkout@
|
|
146
|
+
- uses: actions/checkout@v7
|
|
147
147
|
- uses: actions/download-artifact@v8
|
|
148
148
|
with:
|
|
149
149
|
name: dist
|
|
@@ -9,21 +9,26 @@ this repo. Humans should read `README.md` + `CONTRIBUTING.md` instead.
|
|
|
9
9
|
src/mirror_bench/
|
|
10
10
|
cli.py typer app, subcommands `bench`, `list`, `completion`
|
|
11
11
|
distro.py /etc/os-release parsing + derivative alias table +
|
|
12
|
-
structured exceptions
|
|
12
|
+
default codenames + structured exceptions
|
|
13
|
+
(UnsupportedDistroError, etc.)
|
|
13
14
|
models.py Mirror, ProbeResult, Score, Weights, BenchConfig, HostInfo
|
|
14
15
|
discovery/ per-distro mirror fetchers (ABC + 5 impls +
|
|
15
|
-
UnknownDiscovererError)
|
|
16
|
+
DiscoveryError, UnknownDiscovererError)
|
|
16
17
|
benchmark/
|
|
17
18
|
transport.py shared httpx.AsyncClient factory
|
|
18
19
|
probe.py two-phase async orchestration (TaskGroup + semaphore)
|
|
19
20
|
tls.py TLS version / cert inspection via response.extensions
|
|
21
|
+
h3.py HTTP/3 discovery from the Alt-Svc header + optional
|
|
22
|
+
QUIC probe orchestration (--http3)
|
|
23
|
+
h3_quic.py aioquic client; imported only through h3.py (optional
|
|
24
|
+
`http3` extra, not a runtime dependency)
|
|
20
25
|
scorer.py weighted composite ranking
|
|
21
26
|
display.py rich.Table + JSON/CSV emitters
|
|
22
27
|
|
|
23
28
|
tests/
|
|
24
29
|
fixtures/ captured mirror-list bodies per distro
|
|
25
30
|
test_*.py respx-mocked units + @pytest.mark.integration opt-ins
|
|
26
|
-
(
|
|
31
|
+
(integration.yml runs the opt-ins each week)
|
|
27
32
|
```
|
|
28
33
|
|
|
29
34
|
## Commands (use these, not ad-hoc ones)
|
|
@@ -100,7 +105,8 @@ Run a single test: `uv run pytest tests/test_scorer.py::test_weights_shift_ranki
|
|
|
100
105
|
- Don't skip hooks (`--no-verify`), nor add `# type: ignore` / `# noqa` /
|
|
101
106
|
`# nosec` without a one-line reason at the call site.
|
|
102
107
|
- Don't introduce new runtime dependencies without a strong reason — three
|
|
103
|
-
(`httpx[http2]`, `typer`, `rich`) is the budget.
|
|
108
|
+
(`httpx[http2]`, `typer`, `rich`) is the budget. `aioquic` is an optional
|
|
109
|
+
extra (`http3`): import it only in `benchmark/h3_quic.py`.
|
|
104
110
|
- Don't expand `[tool.ruff.lint] select` without a concrete bug class in mind.
|
|
105
111
|
- Don't emit rich output to stdout when `--json` or `--csv` is set.
|
|
106
112
|
- Don't rely on network access in unit tests. Network-touching tests must be
|
|
@@ -111,6 +117,9 @@ Run a single test: `uv run pytest tests/test_scorer.py::test_weights_shift_ranki
|
|
|
111
117
|
- **Mypy error.** Fix the types. `# type: ignore` is not allowed in this
|
|
112
118
|
codebase — if you think you need one, you're probably modelling something
|
|
113
119
|
wrong.
|
|
120
|
+
- **Scheduled integration run fails.** First check for upstream drift: a
|
|
121
|
+
moved file, a renamed list field, an end-of-life default release. Fix the
|
|
122
|
+
path or default and refresh the fixture to match the live format.
|
|
114
123
|
- **Integration test flaky.** Real mirrors come and go. If reproducible,
|
|
115
124
|
tighten the assertion or bump tolerance. Don't add retries to runtime code
|
|
116
125
|
just to paper over a test.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here.
|
|
4
|
+
|
|
5
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
|
|
6
|
+
this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.2.0](https://github.com/MysticRyuujin/mirror-bench/compare/v0.1.2...v0.2.0) (2026-11-13)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### ⚠ BREAKING CHANGES
|
|
12
|
+
|
|
13
|
+
* follow redirects in probes and require a 2xx response; reject an https mirror that redirects to http
|
|
14
|
+
* report throughput from phase 2 only; throughput_bps is null when phase 2 fails or is skipped
|
|
15
|
+
* remove the certificate point from the security score; the maximum is 2.5
|
|
16
|
+
* change default releases to Debian trixie, Mint zena, and Fedora 45
|
|
17
|
+
* reject --json together with --csv
|
|
18
|
+
* stop mapping distros with their own mirror network (rocky, almalinux, centos, rhel, ol, manjaro, kali, parrot, devuan, raspbian) to another distro
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
* add --http3 to confirm HTTP/3 with a QUIC probe (optional http3 extra) ([9977a7c](https://github.com/MysticRyuujin/mirror-bench/commit/9977a7c439590741123af2dec31548917d265186))
|
|
23
|
+
* add throughput_error to JSON and CSV output ([9977a7c](https://github.com/MysticRyuujin/mirror-bench/commit/9977a7c439590741123af2dec31548917d265186))
|
|
24
|
+
* report mirrors that advertise HTTP/3 (HTTP/3 column, h3_advertised) ([9977a7c](https://github.com/MysticRyuujin/mirror-bench/commit/9977a7c439590741123af2dec31548917d265186))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
### Bug Fixes
|
|
28
|
+
|
|
29
|
+
* add a time limit to both probe phases ([9977a7c](https://github.com/MysticRyuujin/mirror-bench/commit/9977a7c439590741123af2dec31548917d265186))
|
|
30
|
+
* change default releases to Debian trixie, Mint zena, and Fedora 45 ([9977a7c](https://github.com/MysticRyuujin/mirror-bench/commit/9977a7c439590741123af2dec31548917d265186))
|
|
31
|
+
* correct probe and throughput paths for Ubuntu, Mint, Arch, and Fedora ([9977a7c](https://github.com/MysticRyuujin/mirror-bench/commit/9977a7c439590741123af2dec31548917d265186))
|
|
32
|
+
* **docker:** remove pip from the runtime image ([9977a7c](https://github.com/MysticRyuujin/mirror-bench/commit/9977a7c439590741123af2dec31548917d265186))
|
|
33
|
+
* drop rsync entries from Arch discovery ([9977a7c](https://github.com/MysticRyuujin/mirror-bench/commit/9977a7c439590741123af2dec31548917d265186))
|
|
34
|
+
* exit with code 1 and a message when discovery fails ([9977a7c](https://github.com/MysticRyuujin/mirror-bench/commit/9977a7c439590741123af2dec31548917d265186))
|
|
35
|
+
* follow redirects in probes and require a 2xx response; reject an https mirror that redirects to http ([9977a7c](https://github.com/MysticRyuujin/mirror-bench/commit/9977a7c439590741123af2dec31548917d265186))
|
|
36
|
+
* handle Debian records with no Country, and try https and http for each Debian site ([9977a7c](https://github.com/MysticRyuujin/mirror-bench/commit/9977a7c439590741123af2dec31548917d265186))
|
|
37
|
+
* reject --concurrency 0 and --top 0 ([9977a7c](https://github.com/MysticRyuujin/mirror-bench/commit/9977a7c439590741123af2dec31548917d265186))
|
|
38
|
+
* reject --json together with --csv ([9977a7c](https://github.com/MysticRyuujin/mirror-bench/commit/9977a7c439590741123af2dec31548917d265186))
|
|
39
|
+
* remove the certificate point from the security score; the maximum is 2.5 ([9977a7c](https://github.com/MysticRyuujin/mirror-bench/commit/9977a7c439590741123af2dec31548917d265186))
|
|
40
|
+
* report throughput from phase 2 only; throughput_bps is null when phase 2 fails or is skipped ([9977a7c](https://github.com/MysticRyuujin/mirror-bench/commit/9977a7c439590741123af2dec31548917d265186))
|
|
41
|
+
* run bench when no subcommand is given ([9977a7c](https://github.com/MysticRyuujin/mirror-bench/commit/9977a7c439590741123af2dec31548917d265186))
|
|
42
|
+
* stop mapping distros with their own mirror network (rocky, almalinux, centos, rhel, ol, manjaro, kali, parrot, devuan, raspbian) to another distro ([9977a7c](https://github.com/MysticRyuujin/mirror-bench/commit/9977a7c439590741123af2dec31548917d265186))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
### Dependencies
|
|
46
|
+
|
|
47
|
+
* **actions:** bump actions/checkout ([#9](https://github.com/MysticRyuujin/mirror-bench/issues/9)) ([be49743](https://github.com/MysticRyuujin/mirror-bench/commit/be49743112ba6001b022bd3daec9c442a5925a66))
|
|
48
|
+
* **actions:** bump actions/setup-python in the actions group ([#12](https://github.com/MysticRyuujin/mirror-bench/issues/12)) ([930680d](https://github.com/MysticRyuujin/mirror-bench/commit/930680da12de59dc3171385bcb3672399586f66f))
|
|
49
|
+
* bump anyio, certifi, h2, hpack ([9977a7c](https://github.com/MysticRyuujin/mirror-bench/commit/9977a7c439590741123af2dec31548917d265186))
|
|
50
|
+
* bump idna to 3.18 (fixes PYSEC-2026-215) ([#10](https://github.com/MysticRyuujin/mirror-bench/issues/10)) ([690565c](https://github.com/MysticRyuujin/mirror-bench/commit/690565cc1669320eded4faaa175eb8ae85d739e4))
|
|
51
|
+
|
|
52
|
+
## [0.1.2](https://github.com/MysticRyuujin/mirror-bench/compare/v0.1.1...v0.1.2) (2026-04-23)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
### Bug Fixes
|
|
56
|
+
|
|
57
|
+
* **docs:** lowercase ghcr.io references (docker pull requires lowercase) ([#8](https://github.com/MysticRyuujin/mirror-bench/issues/8)) ([31fcf57](https://github.com/MysticRyuujin/mirror-bench/commit/31fcf57239fc98eecb6df9fe8fed06400cb55dc7))
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
### Documentation
|
|
61
|
+
|
|
62
|
+
* add shields.io badges at top of README ([#6](https://github.com/MysticRyuujin/mirror-bench/issues/6)) ([c25c12b](https://github.com/MysticRyuujin/mirror-bench/commit/c25c12bdc36aa6af67cbbb1b8b7ec65c000d7a53))
|
|
63
|
+
|
|
64
|
+
## [0.1.1](https://github.com/MysticRyuujin/mirror-bench/compare/v0.1.0...v0.1.1) (2026-04-23)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
### Bug Fixes
|
|
68
|
+
|
|
69
|
+
* **ci:** pin aquasecurity/trivy-action to a version that exists ([#2](https://github.com/MysticRyuujin/mirror-bench/issues/2)) ([9984cd0](https://github.com/MysticRyuujin/mirror-bench/commit/9984cd018aaaa6de34b5cdf8261752f563187858))
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
### Dependencies
|
|
73
|
+
|
|
74
|
+
* **actions:** bump the actions group across 1 directory with 11 updates ([#4](https://github.com/MysticRyuujin/mirror-bench/issues/4)) ([4aba2a6](https://github.com/MysticRyuujin/mirror-bench/commit/4aba2a68079d2029bf5cc387d9a6eae459d9ff2a))
|
|
75
|
+
|
|
76
|
+
## [Unreleased]
|
|
77
|
+
|
|
78
|
+
## [0.1.0] - 2026-04-23
|
|
79
|
+
|
|
80
|
+
### Added
|
|
81
|
+
|
|
82
|
+
- Cross-distribution mirror benchmarking for **Ubuntu, Debian, Fedora,
|
|
83
|
+
Linux Mint, and Arch**.
|
|
84
|
+
- Two-phase probe: async latency screen (ranged GET on each distro's canonical
|
|
85
|
+
metadata file; median of 3 samples) followed by a throughput test on the
|
|
86
|
+
top-N candidates.
|
|
87
|
+
- Weighted composite scoring across latency, throughput, and security (HTTPS +
|
|
88
|
+
TLS 1.3 + valid cert + HTTP/2). Weights adjustable via
|
|
89
|
+
`--weights lat=…,thr=…,sec=…`.
|
|
90
|
+
- `bench` and `list` subcommands with `--json` / `--csv` / `--https-only` /
|
|
91
|
+
`--tls13-only` / `--no-throughput` / `--country` / `--top` / `--concurrency`.
|
|
92
|
+
- Host auto-detection via `/etc/os-release`, with derivative alias support
|
|
93
|
+
(Pop!\_OS, Kali, Rocky, etc.). `--distro` override for containers and macOS.
|
|
94
|
+
- Shell completion via typer (`--install-completion` / `--show-completion`).
|
|
95
|
+
- Multi-stage Dockerfile (`python:3.14-slim-bookworm`, non-root uid 65532).
|
|
96
|
+
- GitHub Actions CI + release pipelines with OIDC PyPI publishing, multi-arch
|
|
97
|
+
container image, SLSA build provenance, and Trivy image scanning.
|
|
98
|
+
- Pre-commit hooks: ruff, mypy, bandit, gitleaks, hadolint, shellcheck,
|
|
99
|
+
yamllint, markdownlint, commitizen.
|
|
100
|
+
- `SECURITY.md` with responsible-disclosure workflow and supply-chain notes.
|
|
101
|
+
- Licensed under **GNU AGPL-3.0-or-later** — users of hosted/network-facing
|
|
102
|
+
modified versions must receive the modified source.
|
|
103
|
+
|
|
104
|
+
[Unreleased]: https://github.com/MysticRyuujin/mirror-bench/compare/v0.1.0...HEAD
|
|
105
|
+
[0.1.0]: https://github.com/MysticRyuujin/mirror-bench/releases/tag/v0.1.0
|
|
@@ -66,7 +66,7 @@ walks you through the format interactively.
|
|
|
66
66
|
1. Create `src/mirror_bench/discovery/<name>.py` implementing
|
|
67
67
|
`MirrorDiscoverer` (`discover`, `probe_path`, `throughput_path`).
|
|
68
68
|
2. Register it in `src/mirror_bench/discovery/__init__.py`'s `DISCOVERERS` map.
|
|
69
|
-
3. Add the distro's `ID`
|
|
69
|
+
3. Add the distro's `ID` aliases to
|
|
70
70
|
`src/mirror_bench/distro.py::_ALIASES`.
|
|
71
71
|
4. Add a respx-mocked test with a realistic fixture under
|
|
72
72
|
`tests/fixtures/<name>_*`.
|
|
@@ -8,7 +8,7 @@ ARG UV_VERSION=0.11.7
|
|
|
8
8
|
FROM ghcr.io/astral-sh/uv:${UV_VERSION} AS uv
|
|
9
9
|
|
|
10
10
|
FROM python:${PYTHON_VERSION}-slim-bookworm AS builder
|
|
11
|
-
ARG VERSION=
|
|
11
|
+
ARG VERSION=dev
|
|
12
12
|
|
|
13
13
|
# Minimal build deps; nothing compiled lands in the final image.
|
|
14
14
|
ENV PYTHONDONTWRITEBYTECODE=1 \
|
|
@@ -33,7 +33,7 @@ RUN --mount=type=cache,target=/root/.cache/uv \
|
|
|
33
33
|
|
|
34
34
|
# ---- Runtime stage -------------------------------------------------------
|
|
35
35
|
FROM python:${PYTHON_VERSION}-slim-bookworm AS runtime
|
|
36
|
-
ARG VERSION=
|
|
36
|
+
ARG VERSION=dev
|
|
37
37
|
|
|
38
38
|
LABEL org.opencontainers.image.title="mirror-bench" \
|
|
39
39
|
org.opencontainers.image.description="Cross-distribution Linux package mirror benchmarking." \
|
|
@@ -46,11 +46,14 @@ LABEL org.opencontainers.image.title="mirror-bench" \
|
|
|
46
46
|
# We deliberately do not pin ca-certificates: the whole point of this package
|
|
47
47
|
# is to ship the latest Debian-security-vetted CA list at build time. Pinning
|
|
48
48
|
# a specific version would freeze trust material and defeat the upgrade path.
|
|
49
|
+
# The runtime never installs anything, so pip goes too: its vendored copies of
|
|
50
|
+
# urllib3, msgpack, and setuptools are what image scanners report.
|
|
49
51
|
# hadolint ignore=DL3008
|
|
50
52
|
RUN apt-get update \
|
|
51
53
|
&& apt-get install -y --no-install-recommends ca-certificates \
|
|
52
54
|
&& apt-get clean \
|
|
53
55
|
&& rm -rf /var/lib/apt/lists/* \
|
|
56
|
+
&& python -m pip uninstall --yes --no-cache-dir pip \
|
|
54
57
|
&& groupadd --system --gid 65532 app \
|
|
55
58
|
&& useradd --system --uid 65532 --gid app \
|
|
56
59
|
--home-dir /home/app --create-home --shell /usr/sbin/nologin app
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: linux-mirror-bench
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Cross-distribution Linux package mirror benchmarking tool
|
|
5
5
|
Project-URL: Homepage, https://github.com/MysticRyuujin/mirror-bench
|
|
6
6
|
Project-URL: Issues, https://github.com/MysticRyuujin/mirror-bench/issues
|
|
@@ -21,6 +21,8 @@ Requires-Python: >=3.14
|
|
|
21
21
|
Requires-Dist: httpx[http2]>=0.27
|
|
22
22
|
Requires-Dist: rich>=13
|
|
23
23
|
Requires-Dist: typer>=0.12
|
|
24
|
+
Provides-Extra: http3
|
|
25
|
+
Requires-Dist: aioquic>=1.2; extra == 'http3'
|
|
24
26
|
Description-Content-Type: text/markdown
|
|
25
27
|
|
|
26
28
|
# mirror-bench
|
|
@@ -35,7 +37,7 @@ Description-Content-Type: text/markdown
|
|
|
35
37
|
|
|
36
38
|
A cross-distribution Linux package mirror benchmarking tool.
|
|
37
39
|
|
|
38
|
-
Ranks mirrors by **latency**, **throughput**, and **security** (HTTPS / TLS 1.3 /
|
|
40
|
+
Ranks mirrors by **latency**, **throughput**, and **security** (HTTPS / TLS 1.3 / HTTP/2).
|
|
39
41
|
|
|
40
42
|
`mirror-bench` is **read-only**: it discovers mirrors, probes them, prints a
|
|
41
43
|
ranked table, and exits. It never touches `/etc/apt/sources.list`,
|
|
@@ -131,19 +133,20 @@ Running `mirror-bench` with no subcommand is equivalent to `mirror-bench bench`.
|
|
|
131
133
|
|
|
132
134
|
### Common flags
|
|
133
135
|
|
|
134
|
-
| Flag | Description
|
|
135
|
-
| ----------------------------- |
|
|
136
|
-
| `--distro, -d NAME` | One of `ubuntu`, `debian`, `fedora`, `mint`, `arch`.
|
|
137
|
-
| `--release, -r VALUE` | Release override: codename for apt (noble,
|
|
138
|
-
| `--country, -c US,CA,GB` | ISO 3166-1 alpha-2 country codes to filter on.
|
|
139
|
-
| `--top, -n N` | How many mirrors to include in phase 2 / display (default 15).
|
|
140
|
-
| `--concurrency N` | Max parallel HTTP probes (default 20).
|
|
141
|
-
| `--https-only` | Hard-exclude mirrors without HTTPS.
|
|
142
|
-
| `--tls13-only` | Hard-exclude mirrors that don't negotiate TLS 1.3.
|
|
143
|
-
| `--weights lat=…,thr=…,sec=…` | Override scoring weights (auto-normalized).
|
|
144
|
-
| `--no-throughput` | Skip phase 2. Phase-1 latency screen only.
|
|
145
|
-
| `--
|
|
146
|
-
| `--
|
|
136
|
+
| Flag | Description |
|
|
137
|
+
| ----------------------------- | ---------------------------------------------------------------------------------------- |
|
|
138
|
+
| `--distro, -d NAME` | One of `ubuntu`, `debian`, `fedora`, `mint`, `arch`. |
|
|
139
|
+
| `--release, -r VALUE` | Release override: codename for apt (noble, trixie, zena) or numeric for Fedora (44, 45). |
|
|
140
|
+
| `--country, -c US,CA,GB` | ISO 3166-1 alpha-2 country codes to filter on. |
|
|
141
|
+
| `--top, -n N` | How many mirrors to include in phase 2 / display (default 15). |
|
|
142
|
+
| `--concurrency N` | Max parallel HTTP probes (default 20). |
|
|
143
|
+
| `--https-only` | Hard-exclude mirrors without HTTPS. |
|
|
144
|
+
| `--tls13-only` | Hard-exclude mirrors that don't negotiate TLS 1.3. |
|
|
145
|
+
| `--weights lat=…,thr=…,sec=…` | Override scoring weights (auto-normalized). |
|
|
146
|
+
| `--no-throughput` | Skip phase 2. Phase-1 latency screen only. |
|
|
147
|
+
| `--http3` | Probe over QUIC the mirrors that advertise HTTP/3. Needs the `http3` extra. |
|
|
148
|
+
| `--json` | Emit JSON to stdout. Suppresses the table + progress. |
|
|
149
|
+
| `--csv` | Emit CSV to stdout. Suppresses the table + progress. |
|
|
147
150
|
|
|
148
151
|
## Running in Docker
|
|
149
152
|
|
|
@@ -277,7 +280,7 @@ All arguments are optional; defaults match what CI ships.
|
|
|
277
280
|
| ---------------- | -------- | ------------------------------------------------------------------- |
|
|
278
281
|
| `PYTHON_VERSION` | `3.14` | Base image tag (`python:<PYTHON_VERSION>-slim-bookworm`). |
|
|
279
282
|
| `UV_VERSION` | `0.11.7` | uv release pulled from `ghcr.io/astral-sh/uv:<UV_VERSION>`. |
|
|
280
|
-
| `VERSION` | `
|
|
283
|
+
| `VERSION` | `dev` | Value embedded in the `org.opencontainers.image.version` OCI label. |
|
|
281
284
|
|
|
282
285
|
Example:
|
|
283
286
|
|
|
@@ -369,6 +372,26 @@ $ mirror-bench bench --<TAB>
|
|
|
369
372
|
--distro --country --top --concurrency --https-only --tls13-only …
|
|
370
373
|
```
|
|
371
374
|
|
|
375
|
+
## Supported distributions
|
|
376
|
+
|
|
377
|
+
| `--distro` | Auto-detected from `/etc/os-release` `ID` |
|
|
378
|
+
| ---------- | ---------------------------------------------- |
|
|
379
|
+
| `ubuntu` | `ubuntu`, `pop`, `elementary`, `zorin`, `neon` |
|
|
380
|
+
| `debian` | `debian` |
|
|
381
|
+
| `fedora` | `fedora`, `nobara` |
|
|
382
|
+
| `mint` | `linuxmint` |
|
|
383
|
+
| `arch` | `arch`, `archlinux`, `endeavouros` |
|
|
384
|
+
|
|
385
|
+
Another Ubuntu remix is detected when its `os-release` has `ID_LIKE=ubuntu`
|
|
386
|
+
and `UBUNTU_CODENAME`.
|
|
387
|
+
|
|
388
|
+
Distributions with their own mirror network (RHEL family, Kali, Devuan,
|
|
389
|
+
Raspberry Pi OS, Manjaro) are not supported. Ranking the mirrors of a
|
|
390
|
+
different distribution for them gives a wrong answer.
|
|
391
|
+
|
|
392
|
+
The Ubuntu, Mint, and Arch throughput tests always fetch the x86_64 file.
|
|
393
|
+
The mirror lists for those distributions cover x86_64 mirrors only.
|
|
394
|
+
|
|
372
395
|
## How it works
|
|
373
396
|
|
|
374
397
|
1. **Discover** — fetch the canonical mirror list for the target distribution:
|
|
@@ -376,23 +399,66 @@ $ mirror-bench bench --<TAB>
|
|
|
376
399
|
set (`mirrors.ubuntu.com/mirrors.txt` returns a geo-selected _single_
|
|
377
400
|
mirror, not a full list — so we don't rely on it alone).
|
|
378
401
|
- Debian: `mirror-master.debian.org/status/Mirrors.masterlist` (RFC822
|
|
379
|
-
multi-record format).
|
|
402
|
+
multi-record format). The list does not say which sites serve HTTPS, so
|
|
403
|
+
each site is tried as an `https://` and an `http://` candidate.
|
|
380
404
|
- Fedora: `mirrors.fedoraproject.org/mirrorlist?repo=&arch=&country=`.
|
|
381
405
|
- Linux Mint: scraped from `linuxmint.com/mirrors.php`.
|
|
382
|
-
- Arch: `archlinux.org/mirrors/status/json/` (JSON, filtered to active
|
|
406
|
+
- Arch: `archlinux.org/mirrors/status/json/` (JSON, filtered to active
|
|
407
|
+
HTTP and HTTPS entries).
|
|
408
|
+
|
|
409
|
+
If the list cannot be fetched, the command prints the reason and exits
|
|
410
|
+
with code 1.
|
|
411
|
+
|
|
383
412
|
2. **Phase 1 — latency screen.** Concurrent ranged GET (`bytes=0-1023`) on the
|
|
384
|
-
distro's canonical small metadata file (`InRelease
|
|
385
|
-
`
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
413
|
+
distro's canonical small metadata file (`InRelease` for Ubuntu and Debian,
|
|
414
|
+
`Release` for Mint, `repomd.xml` for Fedora, `lastsync` for Arch). Three
|
|
415
|
+
samples per mirror, **median TTFB** used for ranking.
|
|
416
|
+
3. **Phase 2 — throughput test.** Streaming GET of a large file
|
|
417
|
+
(`Contents-<arch>.gz` for apt, `images/install.img` for Fedora, `extra.db`
|
|
418
|
+
for Arch) on the top N from phase 1, capped at 5 MiB and 30 seconds.
|
|
419
|
+
`bytes_per_sec` is measured from first-byte to last-byte so steady-state
|
|
420
|
+
bandwidth isn't polluted by connect/TLS overhead.
|
|
421
|
+
|
|
422
|
+
Both phases follow redirects, so the timing includes the extra hop that a
|
|
423
|
+
package manager also pays. Only a 2xx final response counts. An `https://`
|
|
424
|
+
mirror that redirects to `http://` fails with `https_downgrade`. An
|
|
425
|
+
`http://` mirror gets no TLS credit, even when it redirects to HTTPS.
|
|
426
|
+
|
|
391
427
|
4. **Score.** Weighted composite of normalized latency (inverse), throughput,
|
|
392
|
-
and security (HTTPS + TLS 1.3
|
|
428
|
+
and security (HTTPS 1 + TLS 1.3 1 + HTTP/2 0.5, max 2.5). Weights
|
|
393
429
|
default to `lat=0.4, thr=0.4, sec=0.2` and are user-overridable.
|
|
430
|
+
- Throughput is the phase 2 measurement only. A mirror whose phase 2 probe
|
|
431
|
+
fails shows `-`, scores 0 for throughput, and reports the cause in the
|
|
432
|
+
`throughput_error` field of the JSON and CSV output.
|
|
433
|
+
- With `--no-throughput`, the throughput weight is 0 and the other weights
|
|
434
|
+
are normalized again.
|
|
435
|
+
- When a site answers on `https://` and `http://`, only the `https://` row
|
|
436
|
+
is kept.
|
|
394
437
|
5. **Render.** A `rich.Table`, or JSON / CSV for scripting.
|
|
395
438
|
|
|
439
|
+
### HTTP/3
|
|
440
|
+
|
|
441
|
+
Phase 1 reads the `Alt-Svc` response header. The `HTTP/3` column shows `adv`
|
|
442
|
+
when a mirror advertises HTTP/3 (`h3`) for its own host; JSON and CSV carry
|
|
443
|
+
the same fact as `h3_advertised`. This has no effect on the score: apt, dnf,
|
|
444
|
+
and pacman do not use HTTP/3 today.
|
|
445
|
+
|
|
446
|
+
To confirm the advertisement with a real QUIC request, install the optional
|
|
447
|
+
extra and add `--http3`:
|
|
448
|
+
|
|
449
|
+
```sh
|
|
450
|
+
uv tool install "linux-mirror-bench[http3]"
|
|
451
|
+
mirror-bench bench --distro arch --http3
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
- Each ranked mirror that advertises HTTP/3 gets one ranged GET over QUIC.
|
|
455
|
+
- The `HTTP/3` column shows `yes` when that request succeeds. JSON and CSV
|
|
456
|
+
gain `h3_latency_ms` and `h3_error`.
|
|
457
|
+
- If UDP is blocked on your network, the probes fail after about 8 seconds
|
|
458
|
+
and the column stays at `adv`.
|
|
459
|
+
- Without the extra, `--http3` exits with code 2 and names the extra.
|
|
460
|
+
- The container image does not include the extra.
|
|
461
|
+
|
|
396
462
|
## Development
|
|
397
463
|
|
|
398
464
|
See [`CONTRIBUTING.md`](CONTRIBUTING.md) for the full dev loop.
|