Skip to content

Add network diagnostics trend graphs#2309

Merged
ten9876 merged 2 commits intoten9876:mainfrom
rfoust:codex/network-diagnostics-graphs
May 3, 2026
Merged

Add network diagnostics trend graphs#2309
ten9876 merged 2 commits intoten9876:mainfrom
rfoust:codex/network-diagnostics-graphs

Conversation

@rfoust
Copy link
Copy Markdown
Contributor

@rfoust rfoust commented May 3, 2026

Summary

  • Reworks the Network Diagnostics dialog into an overview, details, and focused metric graph tabs.
  • Adds lightweight Qt-painted trend graphs for latency/jitter, stream rates, packet loss, and audio buffer/underruns without adding new charting dependencies.
  • Collects diagnostics samples continuously in the background so recent graph history is available when the dialog opens.
  • Supports timeframe selection up to 1 week, with raw 1-second samples for short windows and stable clock-aligned bucketing for longer windows.
  • Adds clickable graph legends: click isolates a metric, Ctrl-click toggles multiple metrics, and mixed-unit audio scaling keeps Buffer as the default axis while showing underruns on their own scale when selected alone.
  • Keeps history memory bounded by pruning after 1 week and compacting older samples to minute-level buckets.
  • Stabilizes the existing details layout so live text updates do not resize the dialog columns, and updates the network status tooltip while hovered.

Testing

  • cmake --build build -j8
  • git diff --check

Copilot AI review requested due to automatic review settings May 3, 2026 11:18
@rfoust rfoust requested review from jensenpat and ten9876 as code owners May 3, 2026 11:18
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR upgrades the existing Network Diagnostics dialog into a multi-tab UI with lightweight, Qt-painted trend graphs and introduces a background history sampler so recent metrics are available immediately when the dialog opens.

Changes:

  • Add a NetworkDiagnosticsHistory background sampler that records 1s diagnostics samples and prunes/compacts history up to 1 week.
  • Rework NetworkDiagnosticsDialog into Overview/Details plus dedicated metric graph tabs, with timeframe selection and clickable legends.
  • Refresh the main window’s network tooltip while hovered and wire the dialog to use the shared diagnostics history.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
src/gui/NetworkDiagnosticsDialog.h Adds time-series sample/history types and extends the dialog to consume shared history plus graph widgets.
src/gui/NetworkDiagnosticsDialog.cpp Implements Qt-painted time-series graphs, dialog UI rework, background sampling, and chart bucketing logic.
src/gui/MainWindow.h Adds a persistent NetworkDiagnosticsHistory* and a tooltip refresh timer member.
src/gui/MainWindow.cpp Instantiates/cleans up diagnostics history, passes it into the dialog, and adds live tooltip refresh behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/gui/NetworkDiagnosticsDialog.cpp Outdated
Comment on lines +786 to +790
for (PanadapterStream::StreamCategory cat : cats) {
PanadapterStream::CategoryStats cs = m_model->categoryStats(cat);
const qint64 delta = cs.bytes - m_lastCatBytes[cat];
m_lastCatBytes[cat] = cs.bytes;
const double rateKbps = kbpsFromBytes(delta);
Comment on lines +867 to +873
NetworkDiagnosticsSample& bucket = compacted.last();
bucket.rttMs = std::max(bucket.rttMs, sample.rttMs);
bucket.audioGapMs = std::max(bucket.audioGapMs, sample.audioGapMs);
bucket.audioJitterMs = std::max(bucket.audioJitterMs, sample.audioJitterMs);
bucket.packetLossPct = std::max(bucket.packetLossPct, sample.packetLossPct);
bucket.audioLossPct = std::max(bucket.audioLossPct, sample.audioLossPct);
bucket.fftLossPct = std::max(bucket.fftLossPct, sample.fftLossPct);
Comment on lines +814 to +819
const qint64 curRx = m_model->rxBytes();
sample.rxKbps = kbpsFromBytes(curRx - m_lastRxBytes);
m_lastRxBytes = curRx;
const qint64 curTx = m_model->txBytes();
sample.txKbps = kbpsFromBytes(curTx - m_lastTxBytes);
m_lastTxBytes = curTx;
Comment thread src/gui/NetworkDiagnosticsDialog.cpp Outdated
Comment on lines +239 to +244
return QString("Last %1 minutes").arg(m_rangeSeconds / 60);
}
if (m_rangeSeconds < 86400) {
return QString("Last %1 hours").arg(m_rangeSeconds / 3600);
}
return QString("Last %1 days").arg(m_rangeSeconds / 86400);
@aethersdr-agent
Copy link
Copy Markdown
Contributor

Hi @rfoust — thanks for the contribution!

Quick heads-up on the red ❌ you're seeing: the only failing check is Cleanup artifactsDelete artifacts, which is a CI housekeeping step that runs against GitHub's artifact API. It's not related to your code at all. The actual code-relevant jobs (build, analyze (cpp)) are still in progress on this commit at the time I'm writing this — once they finish you'll see their real status.

The cleanup step occasionally fails due to transient API/permission issues on GitHub's side and is safe to ignore for the purposes of reviewing your patch. No action needed from you on that front.


While you're waiting on the build, Copilot left some review comments worth a look — none of them block merge but a couple are real bugs that would be nice to address in this PR rather than as follow-ups:

  1. NetworkDiagnosticsDialog.cpp:790 — kbps assumes a perfect 1s tick. QTimer can drift, so bytesDelta * 8 / 1000 will misreport rates under load. You already compute elapsedSeconds for underruns; reusing it for all the byte deltas (and clamping if counters ever go backwards) would make the graphs trustworthy.
  2. NetworkDiagnosticsDialog.cpp:819 — data race on PanadapterStream counters. This is the most important one. m_totalRxBytes/m_totalTxBytes/m_catStats are written on the network thread and now read continuously from the sampler thread without a lock, which is UB in C++. Easiest fix is std::atomic<quint64> on the byte counters; for m_catStats either a mutex-guarded snapshot getter or a QMetaObject::invokeMethod(..., Qt::BlockingQueuedConnection) to fetch from the owning thread.
  3. NetworkDiagnosticsDialog.cpp:873pruneSamples() drops kbps on minute-bucket merges. Day/week graphs will show whatever rate happened to be in the first sample of each minute. Averaging (or max) the rate fields when merging would fix the long-range views.
  4. NetworkDiagnosticsDialog.cpp:244 — "Last 1 hours". Cosmetic, just special-case 1 for singular units.

#2 in particular is worth doing before this lands — TSAN/UBSAN runs would eventually flag it and it's much cheaper to fix now than to debug a flaky data corruption later.

Really nice feature overall — the historical view is going to be super useful for debugging stream issues. Thanks for taking the time on it! 🙌

@rfoust
Copy link
Copy Markdown
Contributor Author

rfoust commented May 3, 2026

Addressed the actionable review feedback in 4268e5b:

  • Made PanadapterStream byte counters atomic and added locked snapshots for category/packet stats used by the diagnostics sampler.
  • Changed kbps calculations to use actual elapsed sample time and clamp counter decreases to zero.
  • Updated history compaction to average rate fields when merging older samples into minute buckets.
  • Fixed singular timeframe labels such as Last 1 hour / Last 1 day.

Verification:

  • cmake --build build -j8
  • git diff --check

Copy link
Copy Markdown
Contributor

@aethersdr-agent aethersdr-agent Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @rfoust — this is a substantial, well-structured contribution. Continuous background sampling, bounded history, clock-aligned bucketing, and the thread-safety hardening on PanadapterStream are all nicely done. The scope is consistent with the PR description.

Copilot findings — verification

Tracing the actual code in this revision, all four Copilot comments appear to be false positives:

  1. "Rate calculations assume a perfect 1s interval" — incorrect. kbpsFromBytes() at src/gui/NetworkDiagnosticsDialog.cpp:721 already takes elapsedSeconds, and sampleNow() computes it (src/gui/NetworkDiagnosticsDialog.cpp:784) and divides every byte-delta by it. Negative deltas are clamped via std::max<qint64>(0, ...). Looks like Copilot was reading an earlier diff revision.

  2. "pruneSamples bucket merge only updates latency/loss" — incorrect. Lines 881–887 explicitly average rxKbps, txKbps, audioKbps, fftKbps, waterfallKbps, meterKbps, and daxKbps via mergeAverage. (Minor nit: audioBufferMs is overwritten with the latest sample rather than averaged, but that seems intentional since it's an instantaneous level, not a rate.)

  3. "Data race on PanadapterStream counters" — addressed in this PR. m_totalRxBytes/m_totalTxBytes are now std::atomic<qint64>, m_statsMutex guards m_catStats and m_streamStats, and categoryStats() returns a value under lock. The signature change from const CategoryStats& to CategoryStats is safe — all existing callers either copy or read fields immediately.

  4. "rangeLabel() always plural" — incorrect. The function at src/gui/NetworkDiagnosticsDialog.cpp:236-248 already special-cases == 1 for minute/hour/day.

A few things worth a look

  • AudioEngine cross-thread access in NetworkDiagnosticsHistory::sampleNow(): I checked and rxBufferBytes(), rxBufferUnderrunCount(), rxBufferSampleRate() are all std::atomic reads in AudioEngine.h:660-663, so polling from the GUI thread is safe. No change needed — just confirming.

  • Destructor ordering in MainWindow::~MainWindow (src/gui/MainWindow.cpp:3793-3801): the comment and explicit deletes ahead of audio teardown are correct and necessary because WA_DeleteOnClose only fires on close, and Qt parent-driven destruction would otherwise occur after the body of the destructor runs. Good catch.

  • Tooltip refresh timer: minor — the Enter/Leave/ToolTip filter handlers don't return true for Enter/Leave, so the events still propagate. That's fine here, but if m_networkLabel ever installs a tooltip via stylesheet/eventfilter on a child, the redundant setToolTip from both the timer lambda and the ToolTip event could double up. Not a bug, just something to be aware of.

Build clean, scope appropriate, conventions look consistent (RAII, atomics, mutex-locked snapshots, no QSettings usage). Nice work.

@ten9876 ten9876 merged commit 6bd959f into ten9876:main May 3, 2026
5 checks passed
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.

3 participants