This commit is contained in:
Andrew Pimenov 2026-10-07 07:08:52 +03:00 • committed by GitHub
commit c8c3b48361
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 44 additions and 31 deletions

View file

@ -516,6 +516,7 @@ async def _speed_section(client, settings: Settings, options: Options, idle_rtt_
import shutil
from netsleuth.speed import (
BufferbloatMeasureConfig,
measure_with_bufferbloat,
run_speed_cascade,
tier_cloudflare,
@ -559,12 +560,14 @@ async def _speed_section(client, settings: Settings, options: Options, idle_rtt_
result = await measure_with_bufferbloat(
result,
idle_rtt_ms=idle_rtt_ms if result.idle_rtt_ms is None else result.idle_rtt_ms,
bands=settings.thresholds.bufferbloat_ms,
run_download=_saturate_down,
run_upload=_saturate_up,
probe=_probe,
interval=cfg.bufferbloat_probe_interval_seconds,
config=BufferbloatMeasureConfig(
idle_rtt_ms=idle_rtt_ms if result.idle_rtt_ms is None else result.idle_rtt_ms,
bands=settings.thresholds.bufferbloat_ms,
run_download=_saturate_down,
run_upload=_saturate_up,
probe=_probe,
interval=cfg.bufferbloat_probe_interval_seconds,
),
)
return ModuleResult(name="speed", status="ok", data=result)

View file

@ -61,6 +61,7 @@ def bufferbloat_delta(idle_rtt_ms: float | None, loaded_rtts_ms: list[float]) ->
import asyncio
import time
from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from netsleuth.models import SpeedResult, TierAttempt
@ -241,6 +242,16 @@ from netsleuth.interpret import grade_bufferbloat
from netsleuth.stats import rtt_stats
@dataclass(frozen=True)
class BufferbloatMeasureConfig:
idle_rtt_ms: float | None
bands: BufferbloatBands
run_download: Callable[[], Awaitable[None]]
run_upload: Callable[[], Awaitable[None]]
probe: Callable[[], Awaitable[float | None]]
interval: float
async def probe_while(
coro: Awaitable[None],
probe: Callable[[], Awaitable[float | None]],
@ -259,25 +270,20 @@ async def probe_while(
async def measure_with_bufferbloat(
result: SpeedResult,
idle_rtt_ms: float | None,
bands: BufferbloatBands,
run_download: Callable[[], Awaitable[None]],
run_upload: Callable[[], Awaitable[None]],
probe: Callable[[], Awaitable[float | None]],
interval: float,
config: BufferbloatMeasureConfig,
) -> SpeedResult:
# This is the one place a measurement is allowed to overlap another: the
# whole point is to see what latency does while the link is saturated.
down_samples = await probe_while(run_download(), probe, interval)
up_samples = await probe_while(run_upload(), probe, interval)
result.idle_rtt_ms = idle_rtt_ms
down_samples = await probe_while(config.run_download(), config.probe, config.interval)
up_samples = await probe_while(config.run_upload(), config.probe, config.interval)
result.idle_rtt_ms = config.idle_rtt_ms
result.loaded_rtt_down_ms = rtt_stats(list(down_samples)).avg_ms
result.loaded_rtt_up_ms = rtt_stats(list(up_samples)).avg_ms
result.bufferbloat_down_ms = bufferbloat_delta(idle_rtt_ms, down_samples)
result.bufferbloat_up_ms = bufferbloat_delta(idle_rtt_ms, up_samples)
result.bufferbloat_down_ms = bufferbloat_delta(config.idle_rtt_ms, down_samples)
result.bufferbloat_up_ms = bufferbloat_delta(config.idle_rtt_ms, up_samples)
worst_delta = max(
[d for d in (result.bufferbloat_down_ms, result.bufferbloat_up_ms) if d is not None],
default=None,
)
result.bufferbloat_grade = grade_bufferbloat(worst_delta, bands)
result.bufferbloat_grade = grade_bufferbloat(worst_delta, config.bands)
return result

View file

@ -14,7 +14,7 @@ from netsleuth.models import SpeedResult
from netsleuth.speed import NDT7_CONSENT_NOTICE, run_speed_cascade
from netsleuth.speed import tier_ookla
from netsleuth.config import BufferbloatBands
from netsleuth.speed import measure_with_bufferbloat, probe_while
from netsleuth.speed import BufferbloatMeasureConfig, measure_with_bufferbloat, probe_while
import httpx
from pytest_httpx import HTTPXMock
from netsleuth.config import Speedtest
@ -371,12 +371,14 @@ async def test_measure_with_bufferbloat_fills_in_both_directions_and_the_grade()
result = await measure_with_bufferbloat(
SpeedResult(method="cloudflare", download_mbps=300.0, upload_mbps=40.0),
idle_rtt_ms=12.0,
bands=BufferbloatBands(),
run_download=run_download,
run_upload=run_upload,
probe=probe,
interval=0.05,
config=BufferbloatMeasureConfig(
idle_rtt_ms=12.0,
bands=BufferbloatBands(),
run_download=run_download,
run_upload=run_upload,
probe=probe,
interval=0.05,
),
)
assert result.idle_rtt_ms == 12.0
assert result.loaded_rtt_down_ms is not None
@ -396,12 +398,14 @@ async def test_measure_with_bufferbloat_without_an_idle_baseline_grades_unknown(
result = await measure_with_bufferbloat(
SpeedResult(method="cloudflare", download_mbps=300.0),
idle_rtt_ms=None,
bands=BufferbloatBands(),
run_download=work,
run_upload=work,
probe=probe,
interval=0.02,
config=BufferbloatMeasureConfig(
idle_rtt_ms=None,
bands=BufferbloatBands(),
run_download=work,
run_upload=work,
probe=probe,
interval=0.02,
),
)
assert result.bufferbloat_down_ms is None
assert result.bufferbloat_grade == "?"