Skip to content

Lite group B: the Blocked Process Reports and Deadlocks grids say where their data starts (#4966) - #4990

Merged
erikdarlingdata merged 7 commits into
devfrom
feature/4966-lite-b
Oct 3, 2026
Merged

erikdarlingdata merged 7 commits into
devfrom
feature/4966-lite-b

Conversation

@erikdarlingdata

@erikdarlingdata erikdarlingdata commented Oct 3, 2026 •

Copy link
Copy Markdown
Owner

Part of #4966.

Why

When a time range starts before a server's data coverage, Lite's Blocking tab drew the shorter Blocked Process Reports and Deadlocks grids with no notice, and a reader could take the missing span for a quiet period. Coverage starts at the later of the collector's first run and the retention edge. This covers the Lite surfaces of one slice of #4966 (the Running Jobs, Daily Summary, Latches and Spinlocks, CPU Scheduler, Plan Cache, Session Stats, Blocking and Configuration tabs). Each surface was checked against its read first.

What changes

Two surfaces get the notice. Each gets one QueryWindowRelation member, its view, its coverage collector, a banner next to the grid, and the probe call at every read path of the grid: the sub-tab switch, the full refresh, the slicer handler and the chart drill-down.

Surface Result
Blocking: Blocked Process Reports NOTICE. New BlockedProcessReports relation, view v_blocked_process_reports, coverage from the blocked_process_report collector's runs. Banner BlockedProcessReportsWindowTruncatedBanner. Read paths: sub-tab switch, full refresh, OnBlockingSlicerChanged, OnBlockingDrillDown.
Blocking: Deadlocks NOTICE. New Deadlocks relation, view v_deadlocks, coverage from the deadlocks collector's runs. Banner DeadlocksWindowTruncatedBanner. Read paths: sub-tab switch, full refresh, OnDeadlockSlicerChanged, OnDeadlockDrillDown.
Running Jobs No notice: it reads the newest snapshot only (GetRunningJobsAsync takes no window).
Daily Summary No notice: the month calendar draws every day of the month, and a day with no collection is a grey No-Data cell, so the empty span is visible. Day states already carry the retention horizon (#3541).
Latches and Spinlocks No notice: both trend charts pin their X axis to the asked range (GroupedTrendChartRenderer), and the two grids read the newest snapshot inside the range.
CPU Scheduler No notice: the chart pins its X axis to the range (CpuSchedulerChartRenderer); the metric grid is the newest snapshot.
Plan Cache No notice: the chart pins its X axis to the range (SetLimitsX); the summary strip and grid read the newest snapshot in the range.
Session Stats No notice: the chart pins its X axis to the range (SessionStatsChartRenderer); the summary strip reads the latest point.
Blocking: Trends No notice: all three charts call SetLimitsX on the asked range.
Blocking: Blocking Stats No notice: all four charts pin their X axis to the range (StatsChartRange plus SetLimitsX); the summary strip aggregates the same range beside charts that show the empty span.
Configuration (Server, Database, Scoped, Query Store, Automatic Tuning, Trace Flags) No notice on any of the six: each reads the newest snapshot only (GetLatest* keyed on MAX(capture_time) or MAX(collection_time)) and takes no window.

Choices to check:

  • The Blocked Process Reports grid also lists the always-on DMV blocking snapshots beside the XE reports. The probe takes one collector per relation, so coverage is read from the XE collector (the table the grid is named for). A collector that ran and captured nothing still logs its runs, so a covered range shows no notice.
  • Both grids window on the event time (event_time, deadlock_time) while the probe windows on collection_time. The notice keys on coverage (collector runs and retention), not on row times, and the existing 90-minute slack in IsWindowTruncated absorbs the collection lag.
  • The Blocking sub-tab banners sit in the row between the slicer and the grid, in a StackPanel with the existing separately-monitored note.
  • The probe, the banner step and the shared maps were not changed beyond the new members and their arms.
  • QueryWindowTruncationTests.WindowTruncatedBannerCallSites_TakeUtcBounds_NotServerLocalOnes pins exact call counts. It moves from 4 to 6 slicer calls, from 10 to 14 refresh calls, and from 10 to 13 GetQueriesTabWindowUtc declarations. Any other change that adds banner calls moves the same three numbers, so a merge of such changes needs their sum.
  • Event time, not run time (Blocked Process Reports, Deadlocks). Both grids filter on the event's own time (event_time, deadlock_time; both are the XE timestamp, UTC, so neither is a server-clock relation), but the notice's probe measured collection_time, so a first collection that stored older history made the note name a time later than rows the grid shows. QueryWindowRelationTimeColumn now maps the two relations to those columns, as it does for System Events and Default Trace. The probe file is listed for both views in the stored-event-copies sweep (a MIN cannot change with duplicate copies).
  • Cap rule. The grids read the newest 200 reports and the newest 50 deadlocks. All eight read paths (full refresh, sub-tab switch, slicer drag, chart drill-down) now go through RefreshCappedGridBannerAsync with each row's event time, and one constant per grid (BlockedProcessReportGridCap, DeadlockGridCap) is both the read's limit and the notice's cap. A full page names its oldest row with no slack, even on a one-hour range; a page under the cap keeps the coverage rule. No literal LIMIT changed (the limit was already a parameter), so the MCP payload census roster is unchanged.
  • Two collectors for Blocked Process Reports. The grid also lists the always-on DMV blocking snapshots. A new DmvBlockingSnapshots relation (its event time is its collection time) is probed beside the XE one, and the shared probe answers the earlier of the two coverages for this grid: with XE collection off, a range the DMV collector covers shows no notice, and one that starts before it names the DMV collector's coverage. A full read can merge to fewer rows than its cap (the DMV read fetches the cap plus the XE rows in hand, and the merge keeps one DMV row per blocked/blocking pair per minute), so ReadRecentBlockedProcessReportsAsync also returns the oldest event time of the read that filled its own page (the later of the two when both did), and the notice names it even when the merged list is under the cap.
  • Call-count pins. Banner calls on a GetQueriesTabWindowUtc pair in ServerTab.Refresh.cs stay at 14 probe-or-cap-aware calls once the Collection Log's cap-aware call is counted: the pattern now counts both steps, 14 -> 15 (four of the 14 moved from the probe-only step to the cap-aware one; the Collection Log's call is the one more). Slicer banner calls 6 -> 6 (4 probe-only, 2 cap-aware). GetQueriesTabWindowUtc( occurrences in ServerTab.Refresh.cs: 13 on this branch and 11 on dev before the merge, 14 after it.
  • Query Heatmap notice names the first column drawn. The heatmap draws one column per 5-minute bucket, from the bucket that holds the first stored row, so a first row at 10:07 left the notice naming 10:07 over a chart whose first column starts at 10:05, up to one column later than the column on screen. The notice now names the earlier of the coverage start and the first column drawn, through EarlierOfFloorAndRowShown, the rule the event grids use and the Darling viewer's heatmap already follows. RefreshQueryHeatmapBannerAsync takes the drawn result at all three read paths (sub-tab switch, full refresh, metric picker) and hands the shared step its first column (FirstColumnDrawn). The 90-minute slack and the skipped probe for a range of 90 minutes or less are unchanged.
  • The chart and the notice agree at the slack. The column trim now judges a first row on the start of its column, the time the notice names. A first row just past the slack whose column starts at the slack (91 minutes into a range, column at 90) used to trim the chart while the new notice stayed hidden; it now neither trims nor shows a notice.
  • A pin in SecondOccurrenceReadTests that still named the earlier Blocked Process Reports read in the slicer handler now names ReadRecentBlockedProcessReportsAsync, the read the handler makes.

Test plan

  • New class DataStartBannerBlockingEventsTests: 14 tests pass. For each of the two relations: rows starting inside the range read the notice back; the notice names the first collection, not the first row; a quiet start (collector ran over the whole range, first event 5 hours in) shows no notice; a quiet start with an older row shows no notice; a range with no event and no run shows no notice. Plus the relation maps, the two XAML banners, and a wiring pin per relation over every read path (sub-tab switch, full refresh, slicer, drill-down), checked to run after the rows are bound.
  • RED: before the call sites existed, the two wiring pins failed (OnBlockingSlicerChanged must refresh the BlockedProcessReports banner exactly once over e.StartUtc, e.EndUtc; found 0) while the 12 probe tests passed. After the calls were added all 14 pass.
  • DataStartBannerTests, DataStartBannerProbeFailureTests, QueryWindowTruncationTests, ComparisonWindowUtcTests, DisplayZoneTextFrameTests, RepoFileAdoptionTests and SeparatelyMonitoredListNoteTests together with the new class: 71 passed, 0 failed, after merging origin/dev.
  • The other 21 Lite.Tests classes that name a touched file (ServerTab.Refresh.cs, ServerTab.Slicers.cs, ServerTab.DrillDown.cs, ServerTab.xaml, the floor file): 344 passed, 0 failed.
  • Lite.Tests builds with 0 warnings and 0 errors.
  • Full Lite.Tests suite not run.
  • The two banners were not looked at in the running app (placement and colour copy the Active Queries banner).
  • The MCP tools that take a range (get_blocked_process_reports, get_deadlocks and the rest) are not covered here.
  • Merged dev: one conflict, QueryWindowTruncationTests.cs (the GetQueriesTabWindowUtc( count), resolved to 14, the sum of both sides' calls.
  • RED on the old shape (event-time arms, second-collector coverage and capped-source start reverted): 8 of the 93 DataStartBannerSurfaceTests fail (the first-run-history theory and the reaches-back theory for both relations, the DMV-coverage test, the DMV-read-fills-its-cap test, and the map rows for both relations). Green restored.
  • New tests: a full page names its oldest row over a covered range (both grids); a one-hour full page names it with no probe; a page under the cap keeps the coverage rule; XE off with the DMV collector starting two days before the range's end names the DMV coverage; XE off over a range the DMV collector covers shows no notice; a DMV read that fills its page and merges to fewer rows names its oldest snapshot.
  • Targeted Lite.Tests classes (DataStartBanner*, QueryWindowTruncationTests, StoredEventCopies*, DisplayZoneTextFrameTests, BlockedProcess*, Deadlock*, McpPayloadContractCensusTests, McpPageContractTests): 548 pass, 0 fail; 0 warnings.
  • RED on the old call shape for both notices: with the probe-only call put back at all eight read paths (Blocked Process Reports and Deadlocks, in the refresh, slicer and drill-down paths), 2 of the 222 tests in DataStartBannerBlockingEventsTests, DataStartBannerSurfaceTests, QueryWindowTruncationTests, DataStartBannerQueriesTabTests, DataStartBannerProbeFailureTests, DataStartBannerTests, StoredEventCopiesSweepTests and DisplayZoneTextFrameTests fail: both rows of DataStartBannerBlockingEventsTests.EveryReadPath_RefreshesTheBanner_OverTheWindowItRead_AfterTheRowsAreBound (BlockedProcessReports and Deadlocks). The other classes pass on either shape. The calls were restored and all pass.
  • Darling.Tests, no store, the 16 classes that read the touched Lite files (McpPayloadContractCensusTests, RepoFileAdoptionTests, DocCommentHygieneTests, CommentFilterAdoptionTests, TsqlConventionGuardTests, ScopedLoadOrderingTests, the Viewer drill-down, memory and blocking-depth classes and the rest): 321 tests, 0 failed, 21 skipped (the live classes that need a store). Darling.Tests builds with 0 warnings.
  • Query Heatmap notice, new tests in DataStartBannerQueriesTabTests: a first stored row inside the first column names that column's start (10:07 names 10:05, 10:05:00 names 10:05, 10:09:59 names 10:05, 10:04:59 names 10:00; the probe is called once); a range the coverage covers shows no notice even when its start sits inside a column; a range of 60 or 90 minutes shows no notice and makes no probe call; a first row 37 minutes into a six-hour range keeps the slack and shows no notice; a first row 91 minutes in, whose column starts at the slack, neither trims the chart nor shows a notice; FirstColumnDrawn answers the first drawn column, and null for a chart that drew none. The existing trim test now expects the notice to name 10:15:00 for a first row at 10:17:40.
  • RED on the old shape (the heatmap handing the banner step no column, the call sites as before): 8 of the 70 DataStartBannerQueriesTabTests fail. Three coverage rows name the row's time ("Showing since 2026-06-01 10:07:00" where 10:05:00 is expected, and the same for 10:09:59 and 10:04:59); the signature pin and the three-read-path pin fail; the FirstColumnDrawn test fails; the 91-minute test fails; the trim test names 10:17:40 where 10:15:00 is expected. After the change all 70 pass. The 91-minute test also failed with the wiring in place and the trim unchanged, and passed once the trim judged the column's start.
  • The 35 Lite.Tests classes that name a touched file (614 tests): 613 passed. The one failure was SecondOccurrenceReadTests.EverySlicerHandler_PassesTheEventsUtcBoundsToItsRead for the Blocked Process Reports slicer, whose pin still named the earlier read; repointed, and that class with DataStartBannerQueriesTabTests, QueryHeatmapBudgetTests, QueryHeatmapToolTests and StoredEventCopiesSweepTests re-run: 88 passed, 0 failed. Lite.Tests builds with 0 warnings.
  • Merged origin/dev again (to the MCP data-start change): no conflicts.

CHANGELOG

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

Proposed entry:

SECTION: Fixed
ENTRY:
- **Lite's Blocked Process Reports and Deadlocks grids now say where their data starts** ([#4966]) - A range that reached back before a server's first collection, or before retention, drew a shorter grid with no notice, which could pass for a quiet period. Both grids now show "Showing since <time>" when the range starts before coverage, on every read of the grid (refresh, slicer drag, chart drill-down). The time named is the oldest event shown where a first collection stored older history, a grid that fills its page (the newest 200 blocked process reports, the newest 50 deadlocks) names its oldest row, and the Blocked Process Reports notice counts the always-on DMV blocking snapshots as coverage too. A server that was collected over the whole range shows no notice, even when its first event came late. The Query Heatmap's "Showing since" notice now names the start of the first column the chart draws, not the time of the first stored row inside it, which could be up to one 5-minute column later.
REF:
[#4966]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/4966

# Conflicts:
#	Lite.Tests/QueryWindowTruncationTests.cs
…st event, judge their caps, and the Blocked Process Reports notice covers both collectors (#4966)
…e chart draws (#4966)

The heatmap draws one column per 5-minute bucket, from the bucket that holds the
first stored row. When the first row sat inside that bucket (10:07 inside the
10:05 column), the "Showing since" notice named the row's time, up to one column
after the column on screen. The notice now names the earlier of the coverage
start and the first column drawn, through the same rule the event grids use, and
the Darling viewer's heatmap already follows it. The 90-minute slack and the
skipped probe for a short range are unchanged.

The columns now judge a first row on the start of its column, the time the
notice names, so the chart and the notice agree for a first row just past the
slack: it neither trims the chart nor shows a notice, instead of trimming it
with none.

Also repoints the slicer-handler pin at the Blocked Process Reports read the
handler now makes.
@erikdarlingdata
erikdarlingdata marked this pull request as ready for review October 3, 2026 16:51
@erikdarlingdata
erikdarlingdata merged commit b35f6b9 into dev Oct 3, 2026
20 of 22 checks passed
@erikdarlingdata
erikdarlingdata deleted the feature/4966-lite-b branch October 3, 2026 17:05
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