Skip to content

DESKTOP: the Query Store Clutter panel and the Memory Pressure Events chart say where their data starts (part of #4966) - #5022

Merged
erikdarlingdata merged 5 commits into
devfrom
feature/4966-desktop7a
Oct 3, 2026
Merged

erikdarlingdata merged 5 commits into
devfrom
feature/4966-desktop7a

Conversation

@erikdarlingdata

@erikdarlingdata erikdarlingdata commented Oct 3, 2026 •

Copy link
Copy Markdown
Owner

Part of #4966.

What changes

The desktop viewer's Query Store Clutter panel and Memory Pressure Events chart now say where their data starts when the range reaches before it. They show the same "Showing since " note the other server-tab grids show. The time is to the second, in the viewer's display zone, the zone the panel's own time columns use.

Query Store Clutter

Files: ViewerServerTab.QueryStoreClutter.cs, ViewerDataService.QueryStoreClutter.cs, and the banner in ViewerServerTab.xaml.

  • The panel draws from two ranged sources. The plan churn comes from query_store_stats. The read cost comes from the collection log (the runs of the query_store collector). Its config and memory-clerk sections show the newest snapshot only, so they need no note.
  • The new probe GetQueryStoreClutterDataStartAsync asks both sources through the shared DataWindowFloor (ForCollectorTable("query_store_stats") and ForCollectionLog()). It names the later of the two starts, so the note is true of every column the panel shows. A source that comes back alone is named, and when neither does there is no note. A covered range with a quiet start shows no note.
  • It makes two probe calls, not one. GetForServerAsync takes a single source. The shared probe's several-source form (GetAsync) answers the earliest start among its sources. That is the right rule for the two halves of a stitched rollup, and the opposite of this panel's.
  • LaterStartAsync runs the two probes in turn and keeps the later start. A probe that throws makes the whole answer throw, so the tab's guard drops the note and the panel loads without it. A first probe that failed starts no second.
  • LoadQueryStoreClutterAsync starts the probe beside the read, both with the window start the caller worked out once. It raises the banner through a new ShowQueryStoreClutterDataStartAsync, which is the shared UpdateTruncationBanner plus DataStartOrNullAsync, so a probe that throws costs only the note.
  • A new banner row sits above the grid. The server note stays above it, and the grid, the recommendation pane and the overhead block move down one row.

Memory Pressure Events chart

Files: ViewerServerTab.Memory.cs, ViewerDataService.Memory.cs, DataWindowFloor.cs, and the banner in ViewerServerTab.xaml.

  • New named source DataWindowFloor.Source.ForMemoryPressureEvents(), beside ForCollectionLog(). It probes sample_time with the collector name memory_pressure_events. TryForCollectorTable("memory_pressure_events") still returns false, because its index is (server_id, sample_time), not its prefix time column. A test pins that.
  • The table is sparse, so the source's edge is the purge cutoff and never the oldest event. It is the later of two times: now less the collector's fleet retention days (30 by default), and the server's first collection. An event stamped in the window moves it earlier. The table is in neither TimescaleSupport.RawRelations nor DarlingRetentionHorizons.BaselineServingRawCollectors, so the schedule's horizon applies as written (the purge cuts on collection_time). A test fails if the table ever joins either list.
  • New probe GetMemoryPressureEventsDataStartAsync. It starts beside the six reads inside the fan-out scope, whose declared width is now 7. It stays out of the join, so a probe that throws costs the note and not the chart.
  • The note names the earlier of the coverage start and the earliest event the chart draws (PressureRowsDrawn, the indicator >= 2 rows, with no row cap). It goes through the existing ShowEventDataStartAsync.
  • RenderMemoryPressureEventsChart takes the load's window instead of working out a second one, so the read, the probe, the axis and the note share one start.
  • A window of 90 minutes or less makes no probe call on either surface. GetForServerAsync skips it, once per source on the clutter panel.

Test plan

  • Wiring pins, RED on the old code: 10 of 12 fail before the change. They cover:

    • the probe beside the read, with the same start
    • the banner step
    • the census of one read, one probe and one banner call per surface
    • the banner declared above the grid or chart
    • the chart taking the load's window.

    The 2 that pass before are guards. The generic factory still refuses memory_pressure_events, and the table is in neither no-schedule-edge list. The clutter probe pin now also requires both sources and the later-start choice. It wants one GetForServerAsync call for each, from the window's start, and no use of the several-source GetAsync. It fails on a one-source probe.

  • Banner and probe behavior (ViewerClutterAndMemoryPressureDataStartTests, 20 cases):

    • A range past coverage names the coverage start, and a quiet start names nothing.
    • A probe that throws shows no note.
    • 60 and 90 minute windows make no probe call (they return at once against a store nothing listens on), while 91 minutes starts the query.
    • An event stamped before the first collection is the time named, and a sample under indicator 2 names no time.
    • The named source's fields and SQL shape are pinned.
    • The clutter choice between the two sources: the table starting after the log names the table's start. The log starting after the table names the log's start.
    • Both sources covering the range names nothing. A start inside the 90-minute slack is absorbed in either source.
    • One source alone is named, and neither names nothing.
    • A probe that throws in either position hides the note, and a first probe that fails starts no second.
    • These do not compile on the old code, because the members do not exist.
  • Live against a store (ViewerClutterAndMemoryPressureDataStartLiveTests, 8 cases):

    • A clutter range past coverage names the table's start (later than the log's), and the panel still loads.
    • A clutter quiet start names nothing, and so does a server that logged runs with no rows.
    • A quiet week with one event at its end gives no note.
    • A new server names its first collection.
    • An event stamped 3 days before the first collection is the time named, and a failed probe keeps the chart's events.
    • New for the two sources: a server added 3 days ago whose Query Store rows reach 6 days back names the log's start.
    • A server with rows and no logged run names the table's start.
    • A server with logged runs of another collector and no Query Store row names the log's start.
    • A server with neither names nothing.
    • Probes that fail (the log's table renamed, which both probes read) show no note, and the panel's read still loads its rows.
  • RED on the one-source probe (the branch before this change), live. The log-starting-later case names the table's start (expected 3 days before the range's end, got 6 days before it). The log-only server names nothing (expected 3 days before the range's end, got none). The other new live cases hold before and after, because they pin the rule's other half.

  • In the quiet-start case, the server with no rows now answers the log's own 60-day horizon, the later of the two. Before, it answered its first collection, 120 days back. Both are before the range, so it still names nothing.

  • RED on the oldest-row shape. The named source was changed to walk to the oldest event. Then 3 tests fail: the quiet-week live test, the range-past-coverage live test, and the named-source pin. Restored, all pass.

  • Targeted classes without a store: 577 total, 0 failed, 60 skipped (the live ones).

  • Same classes live: 577 total, 0 failed, 0 skipped. The three new classes, RepoFileAdoptionTests, DocCommentHygieneTests and CommentFilterAdoptionTests ran again on the final build: 123 total, 0 failed, 0 skipped.

  • The classes run:

    • the three new ones
    • DataWindowFloor*, QueryStoreClutter*, RawWindowFloor*, ComposeDataFloorLiveTests and EffectiveStartUtcTests
    • the viewer data-start classes: ViewerDataStartBannerTests, ViewerEventCapDataStartLiveTests, ViewerEventDataStartTests, ViewerLongQueriesDataStartTests, ViewerPlanCorrectionsDataStartTests, ViewerQueryHeatmapDataStartTests, ViewerQueryStoreRegressionsDataStartTests, ViewerShortWindowDataStartTests, ViewerSystemEventsDataStart*, WebDataStartNoteTests
    • ViewerGridTimeColumnSortMemberTests, ViewerGridTimeTextZoneTests and QsCaptureModeRouteKnobToast*
    • RepoFileAdoptionTests, DocCommentHygieneTests and CommentFilterAdoptionTests.
  • Darling.Tests builds with 0 warnings and 0 errors.

  • Full suite not run.

  • The desktop viewer was not started. The banners are checked through the same steps the tabs call, on real banner controls.

Notes

  • The clutter note names the later of the plan churn's and the read cost's starts. The collection log's probe counts a run of any collector. Take a server that logged other collectors' runs but never ran Query Store. When the range reaches before its log, it also gets a note naming the log's start.
  • The Memory load now declares a fan-out width of 7 (the probe counts, as on the Current Waits sub-tab). That raises the interactive read deadline slightly for the six existing reads.
  • The chart's bars are bucketed by the hour. So the earliest bar can sit up to an hour before the earliest event's own time, which is the time the note names. This geometry is unchanged.
  • The probe's lateral read on sample_time has no collection_time bound. So it visits each chunk in the retention window once, about 30 index descents. That is cheap at this table's size, and the chart's own read has the bound.

CHANGELOG

No entry in this PR. The release owner adds it under [Unreleased].

Proposed entry:

SECTION: Fixed
ENTRY:
- **The Darling viewer's Query Store Clutter panel and Memory Pressure Events chart say where their data starts** ([#4966]) - Both surfaces drew a range that reached before the server's first collection or the table's purge edge as if it were whole, so a short history read as a quiet one. They now show the same "Showing since" note the other server-tab grids show. The Query Store Clutter note names the later of the two starts its sources report (the plan churn's and the read cost's). The Memory Pressure Events note names the earlier of the coverage start and the earliest event drawn. A quiet week with one event at its end names nothing.
REF:
[#4966]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/4966

Later fixes

  • The Query Store Clutter note now counts only the Query Store collector's runs for the read cost. Before, a server that had logged other collectors' runs but never ran the Query Store collector got "Showing since ..." above an empty Clutter panel, and another collector's earlier runs could pull the note earlier than the read cost really reaches. The shared "where does the data start" check gets a source for one collector's runs, named from the same constant the panel's read-cost query binds. The Collection Log grid and drill keep the source that counts every collector.
  • The wrong comment above the Clutter banner now says what is true: plan churn comes from the Query Store stats table, read cost from the Query Store collector's runs in the collection log, and the note names the later start.
  • The Clutter and Memory Pressure loads watch their "where does the data start" check when their main read throws. Before, a failure of that check after the read had already failed went unobserved and was logged as an Error. Now it is logged as a warning, and the read's own error goes on as before.
  • The Memory Pressure Events check now pairs its sample-time bound with the same collection-time floor the chart's own read uses, so the store can skip chunks collected well before the window. The check only asks for the first event stamped inside the window, so no answer changes (pinned by the SQL text and by an event collected hours after its stamp).
  • The Clutter load now declares its real number of reads in flight (2: the panel's reads one after another, and the check beside them). The tab shell declares none, so nothing was priced for the check running beside the panel.
  • The live test class now turns TimescaleDB on when it is present, so the memory pressure table is a hypertable in those runs, and asserts it.

Test plan

  • Query Store Clutter, a server with only another collector's runs: RED before (expected no note, got the log start of 3 days back), green now.
  • Query Store Clutter, another collector's runs from 6 days back and Query Store runs from 3 days back: RED before (expected 3 days back, got 6 days back), green now. A new case also covers a server whose Query Store collector ran with no stats rows (names the 3-day start).
  • A read that throws with a probe that fails later: RED before (the warning never arrived, timed out), green now. A join of several reads that fails is treated the same, and a read that succeeds leaves the probe alone.
  • Both loads await their read through the helper, and the Clutter load declares 2 reads in flight before either starts and releases when its read is done: RED before (pin fails), green now. The same pin shows the shell declares no width.
  • Memory Pressure Events check: SQL text pins the collection-time floor on this source only; a live case with an event collected 3 hours after its stamp names the same time as before.
  • Targeted classes without a store: 785 total, 0 failed (24 live tests skipped). Same classes live against a throwaway PostgreSQL with TimescaleDB: 785 total, 0 failed, 0 skipped. Build 0 warnings.
  • Full suite not run (not part of this fix).

… Events chart say where their data starts (#4966)

A range that reaches before the server's coverage now shows the same 'Showing since' note the other server-tab grids show. The Query Store
Clutter panel probes query_store_stats (the coverage rule). The Memory Pressure Events chart probes sample_time through a named source whose
edge is the purge cutoff, never the oldest event, so a quiet week with one event at its end names nothing, and it names the earlier of the
coverage and the earliest event drawn. Both take the window start the load worked out once; a window of 90 minutes or less makes no probe call,
and a failed probe shows no note and keeps the rows.
… its two ranged sources, the plan churn's and the read cost's (part of #4966)

The panel draws its plan churn from query_store_stats and its read cost from the collection log, so the note now probes both and names the later start, which is true of every column the panel shows. A source that comes back alone is named, neither gives no note, and a probe that throws costs the note and never the panel. The doc comments now name both sources.
…ore collector's runs, and the Clutter and Memory loads watch their data-start check when the main read fails (part of #4966)

The read cost behind the Clutter note is the Query Store collector's runs, not every collector's: a server that logged only other collectors' runs got a 'Showing since' note above an empty panel, and another collector's earlier runs could pull the note earlier. The shared check gets a source for one collector's runs, named from the constant the panel's read-cost query binds.

A load whose main read throws never awaited its data-start check, so a later failure of that check was logged as an unobserved Error; both loads now hand the check to the warning path. The Clutter load declares its two reads in flight. The Memory Pressure Events check pairs its sample-time bound with the chart's collection-time floor. The live test class runs on the hypertable path. The Clutter banner comment now names both sources.
@erikdarlingdata
erikdarlingdata marked this pull request as ready for review October 3, 2026 16:52
@erikdarlingdata
erikdarlingdata merged commit 21bbedf into dev Oct 3, 2026
20 of 22 checks passed
@erikdarlingdata
erikdarlingdata deleted the feature/4966-desktop7a branch October 3, 2026 17:10
erikdarlingdata added a commit that referenced this pull request Oct 3, 2026
…iled is logged as a warning, not an Error (#5034)

Many Darling viewer tab loads start a "where does the data start" probe beside the tab's main read and await it only at the banner step, after the read. When the read threw, the probe was never awaited, and a probe that failed later reached App.OnUnobservedTaskException and was logged as an Error, beside the read's own error the tab already showed.

Those loads now await their read through AwaitReadWatchingProbeAsync (#5022), which, when the read throws, hands the probe to DataStartAnswerAsync so a probe failure is logged as a warning, then rethrows the read's error. A read that succeeds leaves the probe to the banner step as before. 31 sites are routed: Active Queries, Blocking (Current Waits, Blocked Process Reports and Deadlocks loads and slicers), Long Queries, Query Store Regressions, Plan Corrections, Query Heatmap, System Events, Collection Health, Config Changes, the Collection Log drill window, and the Queries tab's Top Queries, Top Procedures and Query Store loads and slicers. The helper's body moves to ViewerProbeWatch so the drill window can use it. The Current Waits load now releases its read width at its join.

ViewerProbeWatchCensusTests fails any viewer method that starts a DataStart or WindowFloor probe and awaits a read bare, on comment-stripped source, checking every probe per method. Later probe sites must use the helper. Six existing source pins accept the routed form and still check their rules, and the viewer fan-out census bounds its exclusion to the owning method. No change on screen.
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