Skip to content

DESKTOP: the Blocked Process Reports, Deadlocks, System Events, Default Trace, Long Queries, Plan Corrections, Query Heatmap and Query Store Regressions grids say where their data starts (#4966) - #4998

Merged
erikdarlingdata merged 21 commits into
devfrom
feature/4966-darling-desktop
Oct 3, 2026
Merged

erikdarlingdata merged 21 commits into
devfrom
feature/4966-darling-desktop

Conversation

@erikdarlingdata

@erikdarlingdata erikdarlingdata commented Oct 3, 2026 •

Copy link
Copy Markdown
Owner

Part of #4966.

DESKTOP slice, three batches: the two event grids of the Blocking tab, then the System Events and Default Trace grids, then the Long Queries grid. The second batch also makes a grid whose read hit its row cap name its oldest row, which changes the two Blocking grids too. The third batch adds store-backed tests for the System Events, Default Trace and capped Blocking cases. A fourth batch adds the Plan Corrections grid, the Query Heatmap and the Query Store Regressions grid. The surfaces that remain are listed under Handoff.

Why

A range that starts before a server's coverage (a newly added server, or a custom range past the retention horizon) drew the shorter list with no notice, and the missing span read as a quiet period. The Blocked Process Reports and Deadlocks grids filter on the event's own time, and a server's first collection stores the server's event history, so a row can carry an event time from before the server was added. A notice that named only the collector's coverage start would name a time later than a row on screen.

What changes

  • GetBlockedProcessReportsDataStartAsync and GetDeadlocksDataStartAsync ask the shared probe (DataWindowFloor.GetForServerAsync) where the collector's coverage starts: the later of the registry's created_date and the purge edge, moved earlier by a row in the range.

  • ViewerEventDataStart.Of applies the event-time rule: the notice names the earlier of the coverage start and the earliest event the grid shows. A probe that failed names nothing, so a failed probe never lets the grid's own first row stand in for coverage. A probe that found no coverage in the range is not a failure: the range ends before the server's first collection. The grid can still list stored history from before that collection, so the notice names the earliest event shown, under the usual slack.

  • ViewerServerTab.ShowEventDataStartAsync awaits the probe through the existing DataStartOrNullAsync catch and raises the Queries tab's "Showing since" banner (UpdateTruncationBanner, the 90-minute slack).

  • Each grid gets a banner in the existing style (BlockedProcessReportsTruncationBanner, DeadlocksTruncationBanner), above the grid and beside the separately-monitored note.

  • The probe runs at every read of each grid. Blocked Process Reports: the range load (LoadBlockedProcessReportsAsync) and the slicer drag (OnBlockingSlicerChanged). Deadlocks: the range load (LoadDeadlocksAsync, which the Daily Summary and the chart drill-downs also call) and the slicer drag (OnDeadlockSlicerChanged).

  • A grid whose read hit its row cap names its oldest row (ViewerEventDataStart.Of with readHitCap, ReadHitCap, and an optional rowCap on ShowEventDataStartAsync). The two Blocking grids pass their caps (BlockedProcessReportsRowCap, the merge's 200, and DeadlocksRowCap, 50), each pinned to the LIMIT of its read.

  • System Events: one probe over system_health_events (GetSystemHealthEventsDataStartAsync) serves the eight grids that read it, and GetDefaultTraceDataStartAsync serves Default Trace. One banner (SystemEventsTruncationBanner) sits above the sub-tab control, in a new two-row grid around it, in the existing style. The Corruption and Contention chart sub-tabs take it down (LoadSystemEventsAsync).

  • Each of the eight grids starts its probe beside its read and raises the banner from the rows it shows after its significance filter (EventTime, naive UTC). Default Trace does the same over EventTimeUtc.

  • Long Queries: GetLongQueriesDataStartAsync asks the shared probe over long_query_completions (on collection_time, the column the read windows on), and LongQueriesRowCap (200) is pinned to the LIMIT of the read. LoadLongQueriesAsync starts the probe beside its read and raises LongQueriesTruncationBanner, above the grid in the existing style (one new row in the tab's grid), from the event time of each row shown (a row with no event time is ignored). A full page of the newest 200 names its oldest event.

  • Plan Corrections: GetPlanCorrectionsDataStartAsync asks the shared probe over plan_correction. A test proves the catalog's index for that table leads with (server_id, collection_time), so the probe accepts it (the table is also in the probe's acceptance theory). PlanCorrectionsRowCap (200) is pinned to the LIMIT of PlanCorrectionsSql, which is newest-first on the column the grid shows. LoadPlanCorrectionsAsync starts the probe beside its read and raises PlanCorrectionsTruncationBanner through ShowEventDataStartAsync from the collection time of each row shown, with the cap, so a full page names its oldest row. The banner is a new row of the tab's grid (now three rows), in the existing style.

  • Query Heatmap: GetQueryHeatmapDataStartAsync asks the shared probe over query_stats. The read is the raw table only (no rollup route, no LIMIT; both pinned for all five metrics), so the probe is the raw table's; a rollup route added later must move it to that rollup's floor. The chart draws one column per 5-minute bin that holds a row, so a stretch with no row has no column and the chart cannot tell a server that did not exist yet from a quiet one: it needs the notice. The read now sits in one method (ReadAndDrawQueryHeatmapAsync) behind both entry points, the range load and the metric change, with the probe beside it; the banner (QueryHeatmapTruncationBanner, a new row above the chart) names the earlier of the coverage start and the first column drawn.

  • Query Store Regressions: the read compares the range with a baseline of the 7 days before it, so the notice keys on that earlier window. QueryStoreRegressionsBaselineStart is the one place the baseline's start is computed (the read's baseline arm and the probe both take it), and GetQueryStoreRegressionsDataStartAsync asks the shared probe over query_store_stats from that start. LoadQueryStoreRegressionsAsync raises QueryStoreRegressionsTruncationBanner (a new row above the grid) against the baseline start, so a server added two days before the range, which covers the range whole but holds two days of baseline, gets a notice that a range-keyed one would not give.

  • A capped verdict gets no slack. The 90-minute slack belongs to the coverage probe alone, where it absorbs a first collection that lands a little after the window starts. A grid that filled its row cap dropped rows for certain, so ShowEventDataStartAsync compares the oldest row shown with the bare window start and raises the banner whenever that row is later than the start; before, the slack hid it, and on a range of an hour or less no row could sit past it.

  • A window no longer than the slack starts no coverage probe. The probe's answer is at or before the window's end, and a note needs it more than the slack after the window's start, so DataWindowFloor.GetForServerAsync (the form every server-tab probe goes through, except the three Queries-tab probes of query stats, Query Store and procedure stats, which share RawWindowFloor.GetAsync with the MCP tools) returns null at once for a window of 90 minutes or less. The capped path does not need the probe and keeps working on a short window.

  • Every desktop "Showing since" time prints to the second (yyyy-MM-dd HH:mm:ss in the display zone), as Lite's banner does. A capped note then never names the range's own start minute for a row some seconds into it.

  • The Query Store Regressions banner step is one shared function (ShowQueryStoreRegressionsDataStartAsync) that the tab and its store-backed tests both call. It still compares the probe's answer with the start of the 7-day baseline window.

Design choices:

  • Blocked Process Reports takes its coverage from both collectors the grid merges: the earlier of the XE collector's (blocked_process_reports) and the DMV snapshots' (dmv_blocking_snapshots). With XE collection off, the DMV rows alone fill the grid. A range that starts before the DMV collector's coverage then gets the notice, and a range it covers gets none.

  • The grids are capped (200 reports, 50 deadlocks, 200 long queries, newest first). A read that returns a full page names its oldest row in the notice. The grid reaches back no further than that row, even where the store covers the whole range. That notice comes from the rows, so it does not wait on the coverage probe. A read under its cap keeps the rule it had: the earlier of the coverage start and the earliest row shown. A read that returns exactly its cap counts as capped, since the read cannot tell whether older rows were left out. Blocked Process Reports merges two reads, each capped at 200, so the cap is judged per read. When either read fills its own cap, the notice names that read's oldest row, or the later of the two when both fill. That holds even when the merged list has fewer than 200 rows once duplicates are dropped.

  • system_health_events.event_time is UTC: it is the XE @timestamp, which the collector compares with its own UTC clock, and the grids bind naive-UTC bounds to it. So the eight grids need no clock conversion. Default Trace stores the server's local clock, so its read converts each row with the server's clock; the probe reads collection_time (UTC) on both tables, so the coverage needs no conversion, and the earliest event is measured on the rows the read already converted per row (the offset at that row's own date) and trimmed to the window in UTC.

  • The System Events and Default Trace reads have no row cap (no LIMIT), so the cap rule does not apply to them. A test pins that, so a LIMIT added later has to be wired to the rule.

  • Long Queries windows on collection_time but shows event_time. The session's first collection stores the events its ring buffer still held, so rows collected inside the range can carry earlier event times: the notice names the earlier of the coverage start and the earliest event shown, and history that reaches back past the range start gives no notice. The read sorts on event_time, which the collector uses as its watermark, so a row without one is not expected; a row that had none would sort first and take a page slot, but could not name a start.

  • Plan Corrections goes through ShowEventDataStartAsync, as Blocking and Long Queries do, so the banner call in ViewerServerTab.Queries.cs is not the plain UpdateTruncationBanner(..., await DataStartOrNullAsync(floorTask, ...), ...) shape. The census of that shape in ViewerDataStartBannerTests still moves from 6 to 7, because the Regressions load adds one such call (it has no slicer, so one); the Plan Corrections call has its own pin in ViewerPlanCorrectionsDataStartTests.

  • Query Heatmap and Query Store Regressions read query_stats and query_store_stats, which the probe answers from the oldest row the server holds at or before the range's end (the schedule gives these tables no edge, and they are dense), not from the registry's created date. Plan Corrections has a schedule edge.

  • Query Store Regressions ranks its top 50 by added duration, not by time, so its rows say nothing about how far back the comparison reached and the cap rule does not apply (pinned). Its rows carry no earlier start either, so its notice is the coverage of the baseline window alone.

  • The heatmap's first column is a 5-minute bin stamped at its start, so it can start up to 4 minutes before the first row the probe found; the notice names the column, never a time later than one on screen.

Surfaces checked

Surface Result
Blocking: Blocked Process Reports NOTICE. Event-time rule applied (the grid filters on event_time). Coverage from both the XE and DMV collectors. Cap rule applied to each source read (200 each). Read paths: range load, slicer drag.
Blocking: Deadlocks NOTICE. Event-time rule applied (the grid filters on deadlock_time); cap rule applied (50). Read paths: range load, slicer drag; the Daily Summary and drill-down paths reuse the range load.
System Events: Scheduler Issues, Severe Errors, Memory Conditions, Memory Broker, Memory Node OOM, Significant Waits, CPU Tasks, I/O Issues NOTICE. One probe over system_health_events, one banner above the sub-tab control. Event-time rule applied (the grids filter on event_time, UTC). Read paths: one per grid, each with its own probe. No row cap.
System Events: Default Trace NOTICE. Probe over default_trace_events, on the same banner. Event-time rule applied; the events are stored on the server's clock and compared in UTC after the read's per-row conversion. Read path: the one range load. No row cap.
System Events: Corruption Events, Contention Events No notice. Charts on a time axis that already shows the empty span; they take the banner down.
Long Queries NOTICE. Event-time rule applied (the read filters on collection_time, the grid shows event_time); cap rule applied (200). Read path: the one range load (LoadLongQueriesAsync); the grid has no slicer.
Plan Corrections NOTICE. Probe over plan_correction; cap rule applied (200, newest first on collection_time, the column the grid shows). Read path: the one range load (LoadPlanCorrectionsAsync); the grid has no slicer.
Query Heatmap NOTICE. The chart draws only the 5-minute bins that hold a row, so it needs one. Probe over the raw query_stats (the read has no rollup route); no cap (no LIMIT, pinned). Read paths: the range load and the metric change, through one method.
Query Store Regressions NOTICE, keyed on the baseline window (7 days before the range, the earlier of the two windows the read compares). Probe over the raw query_store_stats from the baseline start. No cap rule: the top 50 are ranked by added duration, not by time. Read path: the one range load; no slicer.
Active Queries, Current Waits, Queries tab: Query Stats, Procedure Stats, Query Store Already carry the banner (#4953, #4231). Not changed.

Every other surface of the issue's desktop list is under Handoff, not claimed here.

Test plan

  • Darling.Tests build, Debug, after merging origin/dev: 0 warnings, 0 errors.
  • Targeted classes without a store, after merging origin/dev: ViewerLongQueriesDataStartTests (11), ViewerEventDataStartTests, ViewerSystemEventsDataStartTests, ViewerDataStartBannerTests, DataWindowFloorTests, RawWindowFloor*, LongQueryBannerSessionScopeTests, ViewerGridPayloadColumnOrderPinTests, ViewerGridTimeColumnSortMemberTests, ViewerSystemEventsTests, ViewerDefaultTraceServerClockTests, RepoFileAdoptionTests, LivePostgresCollectionHygieneTests, LiveCleanupConversionRatchetTests. Total 373 run, failed 0, skipped 4 (the live RawWindowFloor* tests, which need a store).
  • Live, against a throwaway PostgreSQL with TimescaleDB, after merging origin/dev: ViewerEventDataStartLiveTests (8), ViewerSystemEventsDataStartLiveTests (12), ViewerEventCapDataStartLiveTests (6), RawWindowFloorQuietStartLiveTests (3), ComposeDataFloorLiveTests (14). Total 43, failed 0, skipped 0.
  • The live cases, for each Blocking grid: rows start inside the range (a server added 2 days ago, first event a day later) gives a notice at the coverage start; a quiet start (120 days monitored, first event 5 hours into the range) gives none; history before the coverage start (events from 5 days ago, collected at the first collection 2 days ago) gives a notice at the history's start; history that reaches the range start (events from 9 days ago) gives none though the coverage starts 5 days later.
  • The same four cases against a store for System Events (the Severe Errors read, which stands for the eight grids that share the probe), Default Trace and Long Queries (ViewerSystemEventsDataStartLiveTests). Default Trace runs on a clock 5 hours behind UTC with the events stored on that clock, and the history case names the history's start in UTC (5 days before the end of the range), not shifted by 5 hours. Long Queries stores the history collected at the first collection with event times from before it.
  • The caps against a store (ViewerEventCapDataStartLiveTests): a server monitored 120 days with minute-spaced events. A Blocked Process Reports read of 200 rows (and of 260 seeded, which returns 200) and a Deadlocks read of 50 (and of 70, which returns 50) give a notice at the oldest returned row, which is inside the range; the same rows with the cap rule left out give none, so the cap is what raises it. A read one row under the cap (199 and 49) keeps its result: no notice.
  • The same four cases as pure tests on the rule and the banner text (ViewerEventDataStartTests), plus a probe that throws and a census that every tab read of the two Blocking grids is paired with its probe.
  • RED: with the tab and XAML at their old shape (the first commit has the data layer and the tests, the second wires the tab), 4 of the 14 no-store tests fail: the two read-path pins, the census and the banner pin.
  • System Events and Default Trace, pure: each of the eight grids and Default Trace pins its probe beside its read and its banner call; a census over every tab file pairs the nine reads with the nine banner calls; the chart sub-tabs take the banner down; the banner is declared above the sub-tab control in the existing style; the probes go through the shared floor over the two tables, on collection_time; the reads carry no cap. The four notice cases run through the real Default Trace read (ReadDefaultTraceEventsAsync) with a clock 5 hours behind UTC. Two more cross a daylight-saving change (the spring and the fall day) and name the instant with that date's offset, not the snapshot's.
  • Cap rule, pure: a read that hit its cap names its oldest row whatever the store covers (even a dead probe), a read under its cap keeps its result, the full-page boundary (199 and 200 of 200), and a banner raised from a full page names the oldest row 3 days after the range start while the same rows under the cap raise none. The caps are pinned to the LIMITs of the two Blocking reads.
  • RED, System Events: with the probes and tests committed and the tab at its old shape, 12 of the 19 tests of the new class failed (the eight per-grid pins, the Default Trace pin, the census, the chart-collapse pin and the banner pin); the other 7 (the data layer and the clock cases) passed. Wiring the tab turned all green (20 with the rows-inside-the-range case added).
  • RED, cap rule: with the full-page branch of Of disabled, 2 of the 20 tests of ViewerEventDataStartTests failed (the rule test and the banner test); restored, 20 of 20 pass.
  • Long Queries, pure (ViewerLongQueriesDataStartTests, 11): the cap is the LIMIT of the read; the probe goes through the shared floor over long_query_completions on collection_time; the tab pins its probe beside its read and its banner call from rows.Select(r => r.EventTime) with the cap; a census over every tab file pairs the one read with the one probe and the one banner call; the banner is declared above the grid in the existing style. The banner cases: rows start inside the range, a quiet start, history before the coverage, history that reaches the range start, a row with no event time, and a full page against one row fewer.
  • RED, Long Queries: with the data layer and tests committed and the tab and XAML at their old shape, 3 of the 11 tests failed (the tab pin, the census and the banner declaration); the other 8 passed. Wiring the tab and the banner turned all 11 green.
  • Darling.Tests build, Debug: 0 warnings, 0 errors (this includes the viewer project).
  • Fourth batch, targeted classes without a store: ViewerPlanCorrectionsDataStartTests (9), ViewerQueryHeatmapDataStartTests (13), ViewerQueryStoreRegressionsDataStartTests (9), ViewerDataStartBannerTests, ViewerEventDataStartTests, ViewerLongQueriesDataStartTests, ViewerSystemEventsDataStartTests, DataWindowFloorTests, RawWindowFloor*, ViewerQueriesRestTests, ScopedLoadOrderingTests: 180 run, failed 0, skipped 4 (the live floor tests, which need a store). The pin classes (Capability*, RepoFileAdoptionTests, LivePostgresCollectionHygieneTests, LiveCleanupConversionRatchetTests, ViewerGridPayloadColumnOrderPinTests, ViewerGridTimeColumnSortMemberTests): 279 run, failed 0, skipped 4.
  • Fourth batch, the shared live classes against a throwaway PostgreSQL with TimescaleDB: ViewerEventCapDataStartLiveTests, ViewerSystemEventsDataStartLiveTests, ViewerEventDataStartLiveTests, RawWindowFloorQuietStartLiveTests, ComposeDataFloorLiveTests, DataWindowFloorTests: 82 run, failed 0, skipped 0.
  • RED, Plan Corrections: with the data layer and tests committed and the tab and XAML at their old shape, 3 of the 9 tests failed (the tab pin, the census and the banner declaration); the other 6 passed. Wiring the tab turned all 9 green.
  • RED, Query Heatmap and Query Store Regressions: with the data layers and tests committed and the tabs and XAML at their old shape, 6 of the 22 tests failed (the same three pins for each grid); the other 16 passed. Wiring the tabs turned all 22 green, and the Queries-tab census moved from 6 to 7.
  • Pure cases for the three grids: rows start inside the range, a quiet start, and for Plan Corrections a full page against one row fewer (and a dead probe with a full page); for the heatmap a first column that starts before the coverage and an empty chart; for Regressions a baseline that starts inside its window though the range is covered, coverage inside the recent window, and a quiet start.
  • Store-backed cases for the three grids against a throwaway PostgreSQL with TimescaleDB (ViewerQueryGridsDataStartLiveTests.cs, 9 tests, each class on its own scratch database with the registry row and the collector's runs from EventGridSeed.AddServerAsync). ViewerPlanCorrectionsDataStartLiveTests (5): rows that start inside the range (a server added 2 days ago, its first row a day later) name the coverage start; a quiet start (120 days monitored, first row 5 hours into the range) names none; a read that fills the 200-row cap (200 seeded, and 220, which returns 200) names its oldest row, 199 minutes before the range's end and inside the range, where the same rows with the cap rule left out give none; one row under the cap (199) keeps its result. ViewerQueryHeatmapDataStartLiveTests (2): rows that start inside the range (first row 3 minutes past a 5-minute boundary) name the boundary, the first column drawn; an older row before the range (10 days back, then a gap) names none. A quiet start does not apply to the heatmap: query_stats has no schedule edge, so a server's coverage is its oldest row, and a server with nothing older reads as one added late. ViewerQueryStoreRegressionsDataStartLiveTests (2): a server added 2 days before a one-day range names where the baseline window's data starts (the day it was added), though the same coverage compared with the range's own start is silent; rows reaching back 10 days, before the baseline window starts, name none. Three runs in a row: 9 run, failed 0, skipped 0 each time.
  • RED, the store-backed classes: with the wiring broken (the cap rule returning false, the heatmap probe on another table, the baseline-start helper returning the range start), 5 of the 9 failed and 4 passed. Plan Corrections: both full-page cases, at the named-oldest-row assertion (line 90 of the file). Heatmap: both cases, at the coverage assertions (lines 243 and 261). Regressions: the baseline case, at the notice assertion (line 378). Restored, all 9 green.
  • Targeted classes after these changes: every *DataStartTests, *DataStartLiveTests and ViewerDataStartBannerTests, RawWindowFloor*, DataWindowFloorTests, RepoFileAdoptionTests, LivePostgresCollectionHygieneTests, LiveCleanupConversionRatchetTests and DocCommentHygieneTests. Without a store: 313 run, failed 0, skipped 39 (the live classes). Against a store: 313 run, failed 0, skipped 0. Build: 0 warnings, 0 errors. The hygiene census flagged the shared scratch helper until it carried the own-store note; it does now.
  • A capped verdict gets no slack (ViewerShortWindowDataStartTests, pure, through the real banner control): a capped read on a one-hour range whose oldest row came 20 minutes in shows its banner, with and without a coverage answer; one whose oldest row is at the window's start shows none; the slack stays on the coverage probe (a first collection 85 minutes in is absorbed, 95 minutes in is named), and a page one row under the cap is not capped. RED: with the old tab, the capped case fails (line 59).
  • A window no longer than the slack starts no probe (same class, no store): the shared probe returns null at once for a 60- and a 90-minute window against a store nothing listens on, and starts, and fails, its query at 91 minutes; the Plan Corrections, Heatmap, Blocked Process Reports, Deadlocks, Long Queries and System Events probes skip a one-hour window, and the Regressions probe, keyed on the 7-day baseline, still starts. RED: with the old probe, 4 of the 7 tests fail (both window sizes, the tab's probes and the capped case); restored, all 7 green.
  • A range that ends before the server's first collection names the earliest event shown (ViewerEventDataStartTests). RED: the notice was null.
  • Blocked Process Reports, live (ViewerBlockedProcessReportsCoverageLiveTests, 4 tests). With XE collection off and the first collection two days before the range's end, the notice names the DMV collector's coverage (RED: null). A quiet DMV start over 40 days gets no notice (RED: the probe found no coverage). 300 DMV rows, 3 a minute, that merge to fewer than 200 name the DMV read's oldest row (RED: null).
  • 9 new cases in ViewerEventDataStartTests cover a failed probe against one that found no coverage, the per-read cap and the no-coverage range. Changed on purpose: the dead-probe pin passes probeFailed: true, the tab's call-site pins take the new argument, and the census counts ReadRecentBlockedProcessReportsAsync.
  • The Plan Corrections, Query Heatmap and Query Store Regressions store-backed tests run the tab's own banner step. With ShowEventDataStartAsync ignoring its rows, 2 of 5 Plan Corrections and 1 of 2 Heatmap tests fail.
  • The Regressions tests run over 1-day, 90-minute and 60-minute ranges. With the probe keyed on the range start, 4 of 6 fail. With the banner compared with the range start, 3 of 6 fail.
  • A capped read whose oldest row is 10:15:42 on a range from 10:15:00 names 10:15:42. The minutes-only format fails it.
  • After both fix rounds, the build has 0 warnings. Every *DataStart* class and the censuses pass: 0 failed without a store (the live tests skipped), and 0 failed in 317 live tests.
  • Full suite: not run (not part of this slice).
  • The banners were not looked at in a running viewer: no viewer was started.

Not in this PR

Not done in this PR, in the order I would take them:

  • The three Queries-tab probes that go through RawWindowFloor.GetAsync (query stats, Query Store, procedure stats) still start for a window of 90 minutes or less: the same skip belongs in those three viewer methods. The MCP tools share the function, so it is not put in the shared one.
  • Config Changes (3 grids) and the Collection Log: done on the probe sources The data-start probe can read the config snapshots and the collection log (#4966) #5002 added, in the next desktop PR, which builds on this one.
  • The store-backed tests of the event grids (ViewerEventDataStartLiveTests.cs, ViewerEventGridsDataStartLiveTests.cs) still restate the tab's rule instead of running the tab's step. They move to the tab's step in the next desktop PR, as ViewerQueryGridsDataStartLiveTests.cs now does.
  • Query Store Clutter, Memory Pressure Events, Job History: reads not yet inspected.
  • Charts pinned to the asked range, newest-snapshot-only tabs, Daily Summary, Health Summary, Overview and the whole PostgreSQL target set (Overview, Activity, Vacuum, Waits, I/O, Replication, Storage): not yet checked against Darling's own reads.
  • The block-chain window opened from Blocked Process Reports reads the slicer's range (ViewerServerTab.BlockChain.cs): not yet checked.

CHANGELOG

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

Proposed entry:

  • The Darling desktop viewer's Blocked Process Reports, Deadlocks, System Events, Default Trace, Long Queries, Plan Corrections, Query Heatmap and Query Store Regressions grids now say where their data starts (Say where the data starts on every time-ranged tab, in Lite and both Darling viewers #4966) - A range that reached back past a server's coverage, such as a newly added server or a custom range past the retention horizon, drew a shorter list with no notice, and the missing span read as a quiet period. Each grid now shows the "Showing since" banner the Queries tab uses, naming the earlier of where the collector's coverage starts and the earliest event or row the grid shows, so a server's stored event history is never named later than a row on screen. The eight System Events grids and Default Trace share one banner above the sub-tab control, which the Corruption and Contention charts take down, and Default Trace events, stored on the server's own clock, are compared in UTC. Long Queries windows on the collection time but shows the event time, so its banner compares the event times it shows. The Query Heatmap draws only the 5-minute bins that hold a row, so its banner names the earlier of the coverage start and the first column drawn, and it refreshes when the metric changes. The Query Store Regressions grid compares the range with the 7 days before it, so its banner names where the data of that earlier window starts. A Blocked Process Reports, Deadlocks, Long Queries or Plan Corrections grid that shows a full page of its newest rows (200, 50, 200 and 200) now names its oldest row, since it reaches back no further. Blocked Process Reports also lists the blocking that the DMV collector sees, so its banner takes coverage from both collectors. A full page from either collector names that page's oldest row. Every banner names its time to the second.

…cked Process Reports and Deadlocks grids, with their tests (#4966)

The probes read where the collector's coverage starts through the shared floor. An event grid names the earlier of that
and the earliest event it shows, so a server's stored history never makes a notice name a later time than a row on screen.
The tab is wired in the next commit; the read-path and banner pins fail until then.
…here their data starts (#4966)

Each grid gets a banner in the Queries tab's existing style, raised at every read of the grid: the range load and the
slicer drag. The notice names the earlier of the collector's coverage start and the earliest event the grid shows.
…s at its oldest row (#4966)

A read that keeps the newest N rows reaches back no further than the oldest it returned, whatever the store covers. The
Blocked Process Reports (200) and Deadlocks (50) grids now name that row in the Showing since banner once their read returns a
full page. The notice comes from the rows, so it does not wait on the coverage probe; a read under its cap keeps its result.
…race grids, with their tests (#4966)

The eight system_health grids share one probe over system_health_events, and Default Trace has its own over default_trace_events.
Both read the collector's UTC collection_time, so the coverage needs no server clock; the Default Trace events are compared in UTC
after the read's own per-row conversion, so a clock behind UTC or a day that crosses a daylight-saving change names the right
instant. The tab is wired in the next commit; the read-path, banner and chart pins fail until then.
…heir data starts (#4966)

One banner above the sub-tab control, in the Queries tab's existing style, covers the nine grids that read system_health_events
and default_trace_events. Each grid starts its probe beside its read and names the earlier of the collector's coverage start and the
earliest event it shows. The Corruption and Contention chart sub-tabs take the banner down.
…ce and capped Blocking grids' data-start notices (#4966)

System Events and Default Trace, against a store: rows that start inside the range give a notice at the coverage start, a quiet start gives none, history from before the coverage gives a notice at the history's start (Default Trace on a clock 5 hours behind UTC names it in UTC), and history that reaches the range start gives none. The two Blocking caps: a 200-row Blocked Process Reports read and a 50-row Deadlocks read give a notice at the oldest returned row, and a read one row under the cap keeps its result.
…e tests that pin the tab to them (#4966)

GetLongQueriesDataStartAsync asks the shared probe over long_query_completions, and LongQueriesRowCap is the LIMIT of the read. The tests pin the tab's probe beside its read, the banner call from the event time of each row the grid shows, the cap rule, and the banner declared above the grid; they also run Long Queries against a store. The tab and the banner are wired in the next commit.
LoadLongQueriesAsync starts the coverage probe beside its read and raises a Showing since banner above the grid, in the style the Blocking grids use. The banner names the earlier of the coverage start and the earliest event the grid shows (the read windows on collection time but the grid shows event time), and the oldest event when the read returned a full page of its newest 200.
@erikdarlingdata erikdarlingdata changed the title DESKTOP: the Blocked Process Reports and Deadlocks grids say where their data starts (#4966) DESKTOP: the Blocked Process Reports, Deadlocks, System Events, Default Trace and Long Queries grids say where their data starts (#4966) Oct 3, 2026
…ry_stats and the regressions' baseline window (#4966)
…nce banner, the Regressions keyed on its baseline window (#4966)
…cked tests for the Showing since notice (#4966)

Plan Corrections: rows that start inside the range name the coverage start, a quiet start (monitored for months, first row 5 hours in) names nothing, a read that fills the 200-row cap names its oldest row over a range the store covers (200 and 220 seeded), and one row under the cap names nothing.

Query Heatmap: the table has no schedule edge, so its coverage is its oldest row. Rows that start inside the range name the first 5-minute column drawn (a first row 3 minutes past a boundary names the boundary), and an older row before the range names nothing.

Query Store Regressions: the notice keys on the baseline window, 7 days before the range, through the shared baseline-start helper. A server added two days before a one-day range names where that window's data starts, where a notice keyed on the range stays silent, and rows reaching back before the baseline name nothing.
…an the slack starts no coverage probe (#4966)

The 90-minute slack belongs to the coverage probe alone, where it absorbs a first collection that lands a little after the window starts. A grid that filled its row cap dropped rows for certain, so its banner now shows whenever the oldest row shown is later than the window's start; before, the slack hid it, and on a range of an hour or less no row could sit past it.

A window no longer than the slack can never get a coverage note (the probe's answer is at or before the window's end, and a note needs a start more than the slack after the window's start), so the shared server probe returns at once for such a window and starts no query. The capped path does not need the probe.
…he store-backed tests run the tab's own banner step (#4966)

The banner's time was printed in whole minutes. A capped read has no slack, so a grid whose oldest row came 42 seconds into the first minute of its range showed a banner that read "10:15" for a range from 10:15:00, as if the grid started at the range's start. The one formatter every desktop "Showing since" note goes through now prints yyyy-MM-dd HH:mm:ss, as Lite's banner does.

The Query Store Regressions banner step moves out of the load method into ViewerServerTab.ShowQueryStoreRegressionsDataStartAsync, which compares the probe's answer with the baseline window's start. The load method and the tests both call it.

The nine store-backed tests for Plan Corrections, the Query Heatmap and Query Store Regressions no longer copy the tab's decision. Each runs the tab's own step (ShowEventDataStartAsync, or the Regressions step) on a real banner control and reads the banner text, so a change to the rule fails a test. The two Regressions cases also run on ranges of 90 and 60 minutes: the probe asks about the baseline window, which is longer than the shared probe's slack whatever the range, so it still runs there, and a probe keyed on the range's own start would be skipped and answer nothing.
…re the first collection, the DMV collector, and a capped DMV read (#4966)

A range that ends before the server's first collection finds no coverage, yet the grid lists the history that collection stored. The event
surfaces now name the earliest event shown when the probe found no coverage (a probe that threw still names nothing), so the grid no longer
starts late with no note. The probe's answer keeps "no coverage" apart from "failed" (DataStartAnswerAsync).

The Blocked Process Reports coverage is the earlier of the XE collector's and the DMV snapshot collector's, so a server with XE collection
off reads as covered from the DMV collector's first collection, and a quiet DMV start is not mistaken for a cut.

Each source read now reports whether it filled its own 200-row cap. The merge drops rows an XE report already covers and rows that repeat a
pair within a minute, so the merged list can hold fewer than 200 rows while the DMV read left older reports out; the notice names the oldest
row that read returned, with no slack.
@erikdarlingdata erikdarlingdata changed the title DESKTOP: the Blocked Process Reports, Deadlocks, System Events, Default Trace and Long Queries grids say where their data starts (#4966) DESKTOP: the Blocked Process Reports, Deadlocks, System Events, Default Trace, Long Queries, Plan Corrections, Query Heatmap and Query Store Regressions grids say where their data starts (#4966) Oct 3, 2026
@erikdarlingdata
erikdarlingdata marked this pull request as ready for review October 3, 2026 08:52
@erikdarlingdata
erikdarlingdata merged commit dfc69e3 into dev Oct 3, 2026
25 of 26 checks passed
@erikdarlingdata
erikdarlingdata deleted the feature/4966-darling-desktop branch October 3, 2026 08:53
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