Latency/jitter pinned near a fixed value (~60ms / ~9ms) — looks environmental, not client-side #1

Open
opened 2026-08-06 11:45:49 -07:00 by neonoverlord · 0 comments
Member

Symptom (Brock, 2026-08-06): running the test repeatedly, ping comes back essentially flat around 60ms and jitter around 9, run after run. Download/upload look accurate and vary normally.

Ruled out by reading the code

  • measureLatency() in js/speedtest.js is correct: 10 sequential real fetch() round-trips, no caching (cache: 'no-store' + cache-busting query param), median for ping, mean-abs-diff for jitter. Nothing hardcoded or mocked client-side.
  • TestController::ping() is a bare empty 200 with no-store headers — no DB, no storage, no work. Nothing on the server side of the app can be injecting a fixed delay.

So the app's own code isn't fabricating these numbers — a fixed ~60ms is being added to every single request somewhere in the path between the browser and TestController::ping(), and it's consistent enough that jitter (variance between consecutive pings) stays near a floor too instead of reflecting real network variance.

Where to look

Real per-request overhead that would look exactly like this if it isn't being amortized away:

  • No connection reuse — if each of the 10 /ping fetches opens a fresh TCP+TLS handshake instead of reusing keep-alive, that's your fixed floor right there. Check response headers / a packet capture for whether it's one connection or ten.
  • A proxy hop doing per-request inspection — OPNsense nginx is the only door (per NETWORK.md); if Suricata/CrowdSec or nginx buffering is in the request path with per-request processing cost, that reads as a floor added to every hit, not organic jitter.
  • Nextcloud middleware overhead — session/auth checks running on every request even though the route is #[NoAdminRequired] #[NoCSRFRequired].

Verification path for whoever picks this up

Compare time curl (or repeated curl -w '%{time_total}\n') against /apps/speedtest/ping:

  1. from a LAN box straight to srv:80 (bypasses OPNsense/nginx entirely)
  2. through the normal public path (https://cloud.bawnet.io/apps/speedtest/ping or wherever it's mounted)

If (1) is near-zero and (2) sits at ~60ms, the fixed cost is in the firewall/proxy hop, not in Nextcloud or the app. If both show it, it's inside Nextcloud/PHP.

Blocks ops/features#33's "verify against the existing librespeed numbers before retiring anything" checklist item — the numbers can't be trusted for that comparison until this is understood.

**Symptom (Brock, 2026-08-06):** running the test repeatedly, ping comes back essentially flat around 60ms and jitter around 9, run after run. Download/upload look accurate and vary normally. ## Ruled out by reading the code - `measureLatency()` in `js/speedtest.js` is correct: 10 sequential real `fetch()` round-trips, no caching (`cache: 'no-store'` + cache-busting query param), median for ping, mean-abs-diff for jitter. Nothing hardcoded or mocked client-side. - `TestController::ping()` is a bare empty 200 with no-store headers — no DB, no storage, no work. Nothing on the server side of the app can be injecting a fixed delay. So the app's own code isn't fabricating these numbers — a fixed ~60ms is being added to every single request somewhere in the path between the browser and `TestController::ping()`, and it's consistent enough that jitter (variance between consecutive pings) stays near a floor too instead of reflecting real network variance. ## Where to look Real per-request overhead that would look exactly like this if it isn't being amortized away: - **No connection reuse** — if each of the 10 `/ping` fetches opens a fresh TCP+TLS handshake instead of reusing keep-alive, that's your fixed floor right there. Check response headers / a packet capture for whether it's one connection or ten. - **A proxy hop doing per-request inspection** — OPNsense nginx is the only door (per NETWORK.md); if Suricata/CrowdSec or nginx buffering is in the request path with per-request processing cost, that reads as a floor added to every hit, not organic jitter. - **Nextcloud middleware overhead** — session/auth checks running on every request even though the route is `#[NoAdminRequired] #[NoCSRFRequired]`. ## Verification path for whoever picks this up Compare `time curl` (or repeated `curl -w '%{time_total}\n'`) against `/apps/speedtest/ping`: 1. from a LAN box straight to `srv:80` (bypasses OPNsense/nginx entirely) 2. through the normal public path (`https://cloud.bawnet.io/apps/speedtest/ping` or wherever it's mounted) If (1) is near-zero and (2) sits at ~60ms, the fixed cost is in the firewall/proxy hop, not in Nextcloud or the app. If both show it, it's inside Nextcloud/PHP. Blocks ops/features#33's "verify against the existing librespeed numbers before retiring anything" checklist item — the numbers can't be trusted for that comparison until this is understood.
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: bawnet/speedtest#1