Add unit tests for PerfTelemetry (#2500). Principle III.#2782
Merged
Conversation
Adds tests/perf_telemetry_test.cpp covering the public PerfTelemetry
surface: stall thresholds (panUpdate 8ms, frameAge 100ms, render 16ms,
udpDrain 8ms, waterfallUpdate 12ms, input 16ms), drag-aware UI lag
thresholds (50ms idle / 33ms drag), frame-restart counter aggregation,
percentile-95 boundary cases (empty / single / [1..100] / order-invariant),
1-second window cadence (no-fire at 950ms, fire at 1100ms), window
state reset after summary, and the disabled-lcPerf no-op path.
Test seam is the friend-class shim chosen in the earlier triage
(Option A): a forward-declared `PerfTelemetryTestAccess` granted friend
access to the singleton's private members. The shim resets the
singleton between cases, primes m_wasEnabled to bypass the
disabled-to-enabled auto-reset, sets m_windowStartNs and
m_lastHeartbeatNs directly, and invokes the private maybeLogSummary
with synthetic time so window-cadence tests don't depend on real
sleeps. Production hot path is untouched: zero new fields, no
indirection, no new public methods.
Test capture uses a Qt message handler filtered to category
`aether.perf`, with `QLoggingCategory::setFilterRules("aether.perf.debug=true")`
toggling enabled state.
Honors Principle V — new test infrastructure is self-contained and
introduces no flat AppSettings keys.
Blast radius: risk_score=0.438, 16 high-risk affected (top:
MainWindow::MainWindow, MainWindow::buildMenuBar, MainWindow::buildUI).
The high-betweenness callers are pre-existing instrumentation sites
(SpectrumWidget, PanadapterStream, RadioModel, MainWindow) that call
the public record* methods; the changes here are purely additive
(forward declaration + friend declaration + new test target) and
introduce no behavior, no member layout change, and no public-API
shift, so existing callers cannot regress.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Covers the matrix from the issue: disabled hot-path no-op, window aggregation count/p95, window-reset isolation, the full stall-threshold matrix (panUpdate/waterfallUpdate/render/frameAge/udpDrain/input), drag-aware UI lag thresholds, frame-restart counter, percentile-95 boundary cases (empty, single, 100-sorted, 100-reversed), and window timing across the 1s boundary. Adds two test seams on PerfTelemetry — `setClockOverrideForTest(qint64)` to pin nowNs() to a synthetic value, and `resetForTest()` to zero aggregation state between cases. The clock override is a single relaxed atomic load in the hot path; when zero (production default) the original steady_clock path is used unchanged. The test file declares its own Q_LOGGING_CATEGORY for `aether.perf` rather than linking LogManager, which would otherwise pull in AppSettings/AsyncLogWriter. Output is captured via qInstallMessageHandler and matched on key=value field extraction from the emitted PerfStall / PerfSummary lines. Blast radius: risk_score=0.435, 19 high-risk affected (top: AetherSDR::MainWindow::MainWindow, MainWindow::wirePanadapter, MainWindow::buildMenuBar). All callers reach PerfTelemetry through its existing public record* API which is unchanged in shape or behavior; the additions are two new public test-only methods plus one relaxed atomic load in nowNs().
Covers the matrix from the issue: disabled hot-path no-op, window aggregation count/p95, window-reset isolation, the full stall-threshold matrix (panUpdate / waterfallUpdate / render / frameAge / udpDrain / input), drag-aware UI lag thresholds (50 ms idle / 33 ms drag), the frame-restart counter, percentile-95 boundary cases (empty / single / [1..100] sorted / 100 reversed), and the 1 s window cadence (no fire at 950 ms, fire at 1100 ms). Two purely additive test seams land on PerfTelemetry: `setClockOverrideForTest(qint64)` pins nowNs() to a synthetic value so window timing is deterministic without real sleeps, and `resetForTest()` zeroes aggregation state between cases so the singleton doesn't leak between tests. The clock override is a single relaxed atomic load in the hot path; when zero (production default) the original steady_clock path is used unchanged, so the existing record* call surface that the 17 high-risk callers reach is untouched in shape or behavior. The test executable links LogManager.cpp + AsyncLogWriter.cpp + AppSettings.cpp instead of redeclaring its own Q_LOGGING_CATEGORY for `aether.perf` — this honors Principle III's consolidation (one canonical definition site per category, per #2770) and matches the linkage pattern established by `ax25_libmodem_shim_test`. Output is captured via qInstallMessageHandler filtered to `aether.perf` and matched on key=value field extraction from emitted PerfStall and PerfSummary lines. Blast radius: risk_score=0.418, 17 high-risk affected (top: MainWindow::MainWindow, MainWindow::wirePanadapter, MainWindow::buildUI, RadioModel::onStatusReceived, MainWindow::buildMenuBar). All these reach PerfTelemetry through its existing public record* API which is unchanged; the additions are two new public test-only methods plus one relaxed atomic load in nowNs() that is a no-op when the override is the default zero. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
# Conflicts: # CMakeLists.txt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #2500
What was changed
Covers the matrix from the issue: disabled hot-path no-op, window
aggregation count/p95, window-reset isolation, the full stall-threshold
matrix (panUpdate / waterfallUpdate / render / frameAge / udpDrain /
input), drag-aware UI lag thresholds (50 ms idle / 33 ms drag), the
frame-restart counter, percentile-95 boundary cases (empty / single /
[1..100] sorted / 100 reversed), and the 1 s window cadence (no fire
at 950 ms, fire at 1100 ms).
Two purely additive test seams land on PerfTelemetry:
setClockOverrideForTest(qint64)pins nowNs() to a synthetic value sowindow timing is deterministic without real sleeps, and
resetForTest()zeroes aggregation state between cases so the singleton doesn't leak
between tests. The clock override is a single relaxed atomic load in
the hot path; when zero (production default) the original steady_clock
path is used unchanged, so the existing record* call surface that the
17 high-risk callers reach is untouched in shape or behavior.
The test executable links LogManager.cpp + AsyncLogWriter.cpp +
AppSettings.cpp instead of redeclaring its own Q_LOGGING_CATEGORY for
aether.perf— this honors Principle III's consolidation (onecanonical definition site per category, per #2770) and matches the
linkage pattern established by
ax25_libmodem_shim_test. Output iscaptured via qInstallMessageHandler filtered to
aether.perfandmatched on key=value field extraction from emitted PerfStall and
PerfSummary lines.
Blast radius: risk_score=0.418, 17 high-risk affected (top:
MainWindow::MainWindow, MainWindow::wirePanadapter, MainWindow::buildUI,
RadioModel::onStatusReceived, MainWindow::buildMenuBar). All these
reach PerfTelemetry through its existing public record* API which is
unchanged; the additions are two new public test-only methods plus one
relaxed atomic load in nowNs() that is a no-op when the override is
the default zero.
Co-Authored-By: Claude Opus 4.7 noreply@anthropic.com
Recovery note
This PR is being opened manually. The aetherclaude orchestrator (trace fe2b51d0) pushed the signed commit at 17:53 PDT but died at the next bash line due to a set -e race with commit-signed.js's exit code. Root cause + fix shipped as 4832725 (run-agent: guard commit-signed + create-pr against set -e silent death).
Generated by AetherClaude (automated agent for AetherSDR) — PR creation completed manually after orchestrator interrupt.