Skip to content

Port upstream 2208: 1 h TX airtime + duty-cycle warning - #481

Merged
dborup merged 2 commits into
masterfrom
port/479-upstream-2208-airtime-1h
Oct 15, 2026
Merged

dborup merged 2 commits into
masterfrom
port/479-upstream-2208-airtime-1h

Conversation

@dborup

@dborup dborup commented Oct 15, 2026

Copy link
Copy Markdown
Owner

Relates to #479

Evaluated port of upstream PR 2208 (merged head c6685c61, upstream issue 661), adapted to this fork's layout (root-level test-*.js, existing GetMetricsSummary, existing per-sample tx_airtime_pct).

Plan / what changes

  1. Backend (cmd/server, read-only, no new writes)
    • rollingAirtimePct: share over the last hour from the cumulative tx_air_secs / rx_air_secs counters. A falling counter marks a reboot and only the stretch after the last reset counts. null when that span is under 30 min or the newest sample is older than 15 min. Clamped to 0..100, rounded to 2 decimals.
    • GetMetricsSummary(since, now) adds tx_airtime_pct_1h / rx_airtime_pct_1h per row via one extra bulk query on the existing timestamp index, independent of window.
    • duty_cycle_warn_pct from new config key dutyCycleWarnPct (default 10, in config.example.json).
    • Response is the named struct MetricsSummaryResponse (the handler's map[string]interface{} is removed; no new ones). OpenAPI description updated.
  2. Frontend
    • New public/observer-airtime.js (one shared helper: format, isOver, badge). isOver compares at the displayed one decimal, so "10.0%" is always flagged.
    • Observer list: sortable numeric TX 1h column; one bulk summary call with a 30 s client TTL, not one per observer.
    • RF Health tiles: TX 1h stat. Warning style is var(--danger), no hardcoded colours.
  3. Reconciling with existing per-sample values. Existing fields keep their meaning. In the RF detail summary the per-sample figures are now labelled TX/sample / RX/sample; TX 1h always means the rolling hour.

Tests (written first)

  • Go: TestRollingAirtimePct (steady state, reboot mid-hour, single sample, no samples, span under/at 30 min, reboot leaving under 30 min, stale newest sample at 15 min, nil counters, clamping, rounding), TestGetMetricsSummaryAirtime1h (integration, incl. silent observer), TestMetricsSummaryDutyCycleWarnPct.
  • test-issue-479-observer-airtime.js (helper), test-issue-479-airtime-e2e.js (column, warning, sort both ways, both themes, 360x800), test-observers-headings.js and test-xss-escape-sinks.js updated.
  • Registered in test-all.sh and scripts/e2e-shards.json.

Perf

One extra query per summary request, bounded by one hour of samples (about 12 rows per observer), grouped in memory per observer. The observer list adds one bulk call, cached 30 s.

Follow-up (AGENTS.md rule 8)

dutyCycleWarnPct should be exposed in the customizer later; not built here.

No workflow files touched.

🤖 Generated with Claude Code

dborup and others added 2 commits October 15, 2026 21:31
Relates to #479. Adds tx/rx_airtime_pct_1h to the observer metrics summary
(reboot-aware, 30 min minimum span, 15 min staleness, clamped, 2 decimals),
duty_cycle_warn_pct from the new dutyCycleWarnPct config key (default 10),
a sortable TX 1h column in the observer list and a TX 1h stat on the RF
Health tiles. The per-sample TX/RX values in the RF detail panel are now
labelled "/sample". Summary response is a named struct.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Relates to #479. The earlier edit to .eslintrc.json did not apply, so the
no-undef lint job failed on analytics.js.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@dborup

dborup commented Oct 15, 2026

Copy link
Copy Markdown
Owner Author

Rapport — CS-Minimax PR#481 #479 — head 968bfe1

Status: Draft PR ready for review; all CI jobs green on the final head (one unrelated flaky Go test failed on the first run).

Evidence: [T] test or CI result, [A] browser/manual check, [K] code/source reading.

Requirements

Requirement Test (red before, green after) Mutant (killed)
tx/rx_airtime_pct_1h in summary, bulk query, independent of window [T] TestGetMetricsSummaryAirtime1h window 1h→24h; rx fed from tx; fill call removed
Reboot handling, 30 min min span, 15 min staleness, nil counters, clamp 0..100, round 2 dp [T] TestRollingAirtimePct (12 cases) min span 30→5; span <→<=; staleness removed; reset check removed; nil skip removed; clamp removed; rounding removed
duty_cycle_warn_pct from dutyCycleWarnPct (default 10), named struct response [T] TestMetricsSummaryDutyCycleWarnPct; config.example.json updated default ignored; handler hardcodes 10
Shared helper, warning at/above threshold (display-rounded), CSS variable only [T] test-issue-479-observer-airtime.js (9 cases) raw-value compare; class dropped; default fallback dropped; <0.1% bound; >=→>
Sortable numeric TX 1h column in observer list [T] test-issue-479-airtime-e2e.js, test-observers-headings.js data-type removed; data-value blanked
TX 1h stat on RF Health tiles, warning in both themes [T] E2E (light/dark computed colour equals --danger); test-xss-escape-sinks.js sandbox updated stat testid renamed; CSS colour removed
360x800 layout [T] E2E: stat inside tile, not wrapped nowrap removed; flex-wrap removed
Reconcile with per-sample values [K] RF detail panel now labels per-sample values TX/sample / RX/sample; existing fields unchanged n/a
Local suites [T] go test ./... (cmd/server) pass; test-all.sh 255/255 after fixing the XSS sandbox test; E2E 7/7 against a local Go server on the CI-prepared fixture

Browser check

  • [A] Observer list and RF Health, light and dark, desktop and 360x800, on a local server with synthetic metrics (12.5% over, 2% under, silent observer = dash). Over-threshold value is red in both themes; unknown shows a dash.
  • [A] At 360px the observer-list TX 1h column (priority 3) falls into the existing "+N hidden" column set, like the other priority-3 columns. The RF tile shows the stat.

Invariants

  • [K] cmd/server stays read-only (no write helpers added). No new map[string]interface{} (0 added in cmd/server; one removed from the handler).
  • [T] scripts/check-xss-sinks.sh --diff origin/master clean. No colours hardcoded. No workflow files changed, so the fork guards are untouched.

CI (final head, per job)

Go Build & Test, Go Ingestor, Go Libraries & CLI, Go Server 1/3, 2/3, 3/3, Script & Container Checks, JS Unit & Lint, Playwright 1/3, 2/3, 3/3, Verify E2E Shards, Verify Go Test Assignments, Validate Docker Image: all pass.

Residuals

  1. First CI run: JS lint failed because my .eslintrc.json edit had not applied (fixed in the second commit). Go Server 3/3 failed in TestRoutePatterns410_HiddenHopWithTrafficLooksUnknown (compares firstSeen/lastSeen across two requests, so it breaks on a second boundary). It is not flaky: #180 packets URL E2E 'reload shows the same list' depends on fixture age (default 15-min window) #271, it passes locally 3/3, and the rerun on the new head passed. Worth its own issue.
  2. dutyCycleWarnPct is not in the customizer (AGENTS.md rule 8); follow-up.
  3. The local E2E fixture has no observer_metrics table or data, so the E2E mocks the summary endpoint (as upstream does); the real values were checked via the API on a separate local copy.
  4. Upstream's per-timestamp rounding margin (about 17% at 30 min) is documented in the code comment, not hidden.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant