Skip to content

WEB AND DESKTOP: the Config Changes and Collection Log grids say where their data starts (#4966) - #5010

Merged
erikdarlingdata merged 42 commits into
devfrom
feature/4966-w1-config-collection-log
Oct 3, 2026
Merged

erikdarlingdata merged 42 commits into
devfrom
feature/4966-w1-config-collection-log

Conversation

@erikdarlingdata

@erikdarlingdata erikdarlingdata commented Oct 3, 2026 •

Copy link
Copy Markdown
Owner

Part of #4966.

What changes

The Config Changes and Collection Log grids, in the Darling viewer and on the web server page, say where their data starts when the range reaches before it, and a Custom Views panel's "partial window" notice is written in the browser's time zone.

Web:

  • Config Changes (web). The Server, Database and Trace Flag Changes grids (SQL Server) and the Configuration Changes grid (PostgreSQL) now get the same notice the other listed grids get. Each is read through the existing data-start probe over the snapshot table it diffs: server_config and database_config use the config snapshot sources, trace_flags and pg_server_config are ordinary collector tables with a time index. No new probe source was needed.
  • PostgreSQL Configuration Changes cap. The read lists the newest changes under a limit. When it returns a full page cut by that limit, the note names the earliest change on the page (no slack, no store read). The grid's status word (config_changes) names the page and is no longer taken for an empty-state answer.
  • Collection Log (web). Both Collection Log grids (Collection Health on a SQL Server page, Overview on a PostgreSQL page) use the collection log's own source (ForCollectionLog()), whose edge is the log's fixed 60-day horizon. A page cut by its limit names its oldest run. A page a duration floor ranked slowest first is a sample of the whole window, so it keeps the coverage rule. The note also works for windows longer than 168 hours, which this read allows.
  • Custom Views notice in the page's clock. The run's answer carries data_start_utc, window_start_utc and window_end_utc (the names the grid notes use) plus the data-start sentence. compose.js writes the sentence again through windowNoteText and puts it back in notice, so a row-cap sentence beside it stays as sent. An answer without the fields draws the sentence as the server wrote it. The MCP text stays in UTC.

Darling viewer:

  • Configuration Changes (viewer). Each of the three grids on the Configuration Changes tab (Server, Database, Trace Flag) has its own "Showing since" note above it, from a probe over the snapshot table the grid diffs (server_config, database_config, trace_flags) through the same ShowEventDataStartAsync the other viewer grids use. The three probes start beside the three reads, so six reads are in flight and the width declared to the command deadline is six. None of the three reads is capped (each reads every snapshot up to the range's end), so none passes a row cap; the note names the earlier of the probe's answer and the earliest change shown. The viewer shows no PostgreSQL configuration-changes grid (its Configuration tab lists the current settings only), so there is nothing to wire there, and a test fails the day one is added without a note.
  • Collection Log (viewer). The Collection Log grid on the Collection Health tab has a "Showing since" note from the collection log's own source (ForCollectionLog()). Its read keeps the newest 500 runs; that number is now one constant, ViewerDataService.CollectionLogRowCap, which is both the read's default row limit and the cap the note is given, so a page that fills it names its oldest run, whatever the store covers. A page under the cap names the earlier of the probe's answer and its earliest run. The declared read width is released at the join, before the note awaits its probe. The tab's note call is its own step, ViewerServerTab.ShowCollectionLogDataStartAsync, which hands the cap to the shared note function, so a test drives that call against a store.
  • Collection Log drill (viewer). The per-collector window opened from the Health Summary grid reads the trailing 7 days (ViewerDataService.CollectionLogDrillHours, one constant for the read and the note) with no row cap, so its note follows the coverage rule: it names where the server's collection log starts when that is later than the week's start, and a collector that first ran late in a covered week says nothing. The read and the note are one call, CollectionLogWindow.ReadDrillAsync. The read and the probe now share one window: GetCollectionLogByCollectorAsync takes a start (startUtc), and the drill passes the start it works out once from its end, so a pinned end moves the rows as it moves the probe. Before, the read worked its own start from the clock, so a drill pinned to an earlier week asked the probe about that week and read the trailing week from now. The hours-back form is kept and delegates to the new one.
  • Duration Trends chart (viewer). The chart on the Collection Health tab drew the Collection Log grid's page, the newest 500 runs, against an axis that spans the whole range. At about 20 runs a minute, "Last 24 hours" drew the newest ~25 minutes and left the rest of the axis empty, though the store held those runs. The chart now has its own read, ViewerDataService.GetCollectorDurationTrendAsync (CollectorDurationTrendSql, beside RecentCollectionLogSql), over the whole range from v_collection_log: per collector and bucket, the longest and the average duration and the run count of the successful runs (status SUCCESS with a duration, as the chart filtered before), with no row cap. The bucket width is the shared helper's, TrendBuckets.AutoMinutes(windowMinutes, 1, TrendBudget.Chart.AutoPoints), as the CPU and blocking trends size theirs: one minute for 24 hours and ten minutes for 7 days. The bucket origin is the one every trend read shares, and the first bucket's start is clamped to the range's start. Parameters bind as naive UTC like the file's other reads. The read starts beside the grid's read inside the Collection Health load and is joined with the others, so the declared read width goes from four to five.
  • What the chart draws. Each collector's line is its bucket maximum, so a slow run still shows as it did when every run was a point. The vertical axis now reads "Slowest run per bucket (ms)". The hover is unchanged: it names the collector, the maximum and the bucket's start. The bucket's average and run count are on the data (CollectorDurationSeries) and are not drawn yet (see Handoff). A collector still needs two buckets to draw a line, as it needed two runs. The grid keeps its 500-row page and its cap note.
  • Empty range (viewer chart). An empty range returned before the hover was cleared, so the lines of the previous range stayed registered and could still show tooltips over the empty chart. The hover is now cleared before that return.
  • Web. The web server page draws no chart from the Collection Log. Both Collection Log panels (Collection Health on a SQL Server page, Overview on a PostgreSQL page) are table panels over get_collection_log, so there was nothing to change there; a test pins it.

Every viewer note names its times in the viewer's display zone, as the other viewer notes do.

Rules kept: the note keys on the later of the server's first collection and the purge edge, so a covered range with a late first row gets none; the 90-minute slack applies to the coverage probe only (a window of 90 minutes or less makes no probe call); a capped newest-first list names its oldest row with no slack.

Later fixes

Web:

  • An empty change grid on the web page now says where its data starts. When a read looked and found nothing, its answer is empty (no_changes for the PostgreSQL changes). That answer now goes through the same data-start check as rows do, and carries the note and its three instants. This applies to the server, database and trace-flag config changes, and to the PostgreSQL config changes. It also applies to the collection log, both when the window was quiet and when no run matched the filter. Waiting tasks, PostgreSQL wait sampling, kernel stats, lock stats and predicate stats get it too. Latch stats, spinlock stats, wait stats and PostgreSQL wait events are left out. Their no-rows answer is unavailable, which keeps its own message and gets no note. not_collected, unavailable, invalid and errors get no note on any read.
  • The page draws that note above the empty message, in the browser's zone like the note over rows. The envelope's note is now composed in the browser's clock too, and an empty answer without the note fields still draws only its message.
  • As before, a server with no row and no logged run in the window gets no note. The data-start check counts a server only when it has one or the other.
  • A capped web grid judges its note against the window it read. Waiting Tasks, the Collection Log and the PostgreSQL configuration changes list the newest rows under a limit. When the request carries no as_of, the window's end is now taken once and used by both the read and the note check. Before, the check read the clock again after the read, so a full page whose oldest row fell in that small gap got no note. A request that sends its own as_of is left as sent.
  • A Custom Views panel's note fields go to the web page only. The web page gets data_start_note and its times, so it can write the sentence in the browser's zone. The run_custom_view_panel MCP tool now leaves them out, so its answer carries the sentence once, in notice.

Darling viewer (the hover text is shared with Lite):

  • The Collection Log drill window stops at the window's end. Double-clicking a Health Summary row opens the drill for one collector. Its list now covers the same window as its "Showing since" note: from the window's start to the window's end. Before, a pinned end bounded only the note, so a run after that end was still listed. The drill's two reads (the list and the note's check) now count as a pair toward the viewer's read deadline. Every tab's reads already do. The pair is released once the list is in.
  • A failing Duration Trends read now blanks only the chart. On the Collection Health tab the chart has its own read over the whole range. If that read timed out, the whole tab load failed and both grids, the note and the caveats stayed empty. The read is now wrapped the way Lite wraps its trend reads: the failure is logged, the chart draws empty, and everything else draws. A cancelled read still stops the load.
  • The chart and the caveats draw before the "Showing since" note, and a full page does not wait for the note's check. The two grids draw first, then the chart and the caveats, then the note. When the Collection Log page is full, the note names its oldest run without waiting for the start-of-data check. A slow check no longer holds it back. A check that fails later is still logged.
  • Removed a fleet health statement that no screen ran. The status bar and the Overview cards read the by-server fleet statement. The older fleet-wide one had no caller. Its checks now guard the by-server statement. They check two counts (abandoned runs, and runs with a missing extension) and both regression times. They also check the newest run's note, and that a fleet row carries no message text. The per-server statement keeps its own checks. The service's own fleet statement and its checks are untouched.
  • The Duration Trends hover now says how many runs a point is the slowest of and what the bucket averaged. Both apps print the same line, "Slowest of N runs; average X ms", from one shared piece of code next to the hover helper. Lite's text is unchanged. In the viewer, the lines are keyed by each point's time. A bucket after a gap in collection shows its own numbers, and so does the last bucket. The point that breaks the line shows none.

Test plan

  • WebDataStartNoteConfigAndLogTests (new): each of the five reads is listed, served and over a probe-able source; a 1-hour range makes no probe call for any of them while the same read over a week does; a capped collection log names its oldest run at 720 hours; a slowest-first page keeps the coverage rule; a capped PostgreSQL changes page names its earliest change and its status is not an envelope.
  • ComposedPanelNoticeZoneTests (new, runs the shipped compose.js under Node in a zone 5.5 hours from UTC): the notice shows no UTC and keeps the row-cap sentence; an answer without the fields is drawn as sent.
  • RED (web): with the page composition removed the zone test fails (the UTC sentence shows); with the five reads off the list four of the five new web tests fail.
  • Updated pins: the list of capped reads (now three), the probe-source check (collection log has its own source), the live read sweep (six PostgreSQL reads), the rendered-panel live test (the page now writes the notice in the browser's zone).
  • ViewerConfigChangesDataStartTests (new, 18 tests, no store): each probe goes through the shared floor over the table its read draws from; the tab starts one probe per grid beside the reads and names each change's time with no cap; a census over the tab files pairs each read with its probe and banner; each banner sits above its grid in the existing style; a range that starts before coverage names the coverage start; the note is written in the display zone (server time at +05:30); a quiet start says nothing; a 1000-change uncapped list names no oldest row; an empty grid still names the coverage start; a 60- and a 90-minute range make no probe call for any of the three grids while a 91-minute range does; the viewer shows no PostgreSQL configuration-changes grid.
  • RED (viewer, Config Changes): with the three banner calls removed from the tab, 2 of the 18 fail (the tab wiring pin and the census); restored, all 18 pass.
  • The expected banner text in the new class comes from the viewer's own banner formatter (the one every "Showing since" note goes through, to the second since the desktop banner change merged in), under the same display mode, so no test copies the time format.
  • Targeted classes without a store (the data-start, data-window-floor, config-changes, daily-health, command-timeout, collection-health, grid-text, repo-file, comment-hygiene classes), with the desktop banner branch merged: 772 run, 0 failed, 91 skipped (live classes). One class failed on the first run, NoFanOutScope_OutlivesItsJoin: the Collection Health load awaited the note's probe after its join without releasing the declared width. It now releases at the join; the class passes.
  • The same data-start, data-window-floor, config-changes, daily-health and collection-health classes live against a throwaway PostgreSQL 18 cluster: 371 run, 0 failed, 0 skipped. After the latest web test branch merged: the web data-start note classes, the new Config Changes class and ComposeDataFloorLiveTests live, 87 run, 0 failed (73 run, 0 failed, 23 skipped without a store).
  • Live against a throwaway PostgreSQL 18 cluster (web, earlier run): WebDataStartNoteLiveTests and ComposeDataFloorLiveTests 21 run, 0 failed; the data-window floor classes 101 run, 0 failed.
  • Darling.Tests, the service and the viewer build with 0 warnings.
  • ViewerCollectionLogDataStartTests (new, 16 tests, no store): the cap constant is the read's default row limit and the cap the tab's note step passes; the tab starts the probe beside its read inside the declared width of five (the Duration Trends chart's own read is the fifth), joins without the probe, and releases the width before the note awaits the probe; the tab reads its page once with the default cap; both banners are declared in their XAML in the existing style; the drill window calls ReadDrillAsync with its banner and is the only caller of the per-collector read; the drill uses CollectionLogDrillHours for both the read and the probe from one start, with no second clock read; the read from a start binds it as naive UTC; a range before the first collection names it to the second in the display zone (+05:30); a quiet start says nothing; a full page names its oldest run with no slack (one second after the start shows, the start itself does not); one run under the cap keeps the coverage rule; a 60- and a 90-minute range make no probe call while a 91-minute range does; a one-hour range with a page under the cap shows no note.
  • ViewerCollectionLogDataStartLiveTests (new, 9 tests, seeded collection_log rows, the real probe, the real reads and the real banner): a range that starts before the first collection names the first collection (server added 2 days in, 49 runs); a quiet start says nothing (73 runs, coverage before the range); a 1-hour range makes no probe call (answer null) and 5 runs show no note, while the same server over 6 hours does get a probe answer; a full page of exactly 500 runs names its oldest run on a store that covers the range; 499 runs keep the coverage rule and show nothing; 600 stored runs name the oldest run returned, not the oldest stored; the drill of a collector that began 3 days back in a covered week says nothing (73 runs); the drill of a server added 2 days in names its first collection for a collector that began a day later and for the one that began with it; a drill pinned 3 days back reads the runs 9 and 8 days back.
  • RED (item 1, one window): before the read took the drill's start, the pinned-drill test failed with the 9-days-back run missing (expected 3 runs, got only the run 1 day back); with the fix it passes, and the pin on the drill's one start fails before the fix and passes after.
  • RED (wiring): with the cap argument dropped from the tab's note call, AFullPageOfTheCap_NamesItsOldestRun_EvenWhereTheStoreCoversTheRange, AFullPageWithOlderRunsBehindIt_NamesTheOldestReturnedRun and the tab's cap pin fail; with the drill's ReadDrillAsync call replaced by the bare read, the drill pin TheDrillWindow_CallsReadDrillWithItsBanner_AndTheBannerIsDeclaredInItsXaml fails; both restored, all pass.
  • Targeted classes on the tree merged with origin/dev, without a store: the new class and the data-start, data-window-floor, read-fan-out, config-changes, web data-start-note, doc-comment, comment-filter, repo-file and live-collection-hygiene classes, 406 run, 0 failed, 83 skipped (live classes); the daily-health, collection-health, collector-coverage, command-timeout, grid-sort, purge-watch and the other classes that name the touched members, 384 run, 0 failed, 8 skipped.
  • The same classes live against a throwaway PostgreSQL 18 cluster: 406 run, 0 failed, 0 skipped; and the second group with the new classes, 641 run, 0 failed, 0 skipped. Darling.Tests and the viewer build with 0 warnings.
  • ViewerCollectorDurationChartTests (new, 11 tests, no store): the chart is fed its own read and its render takes the buckets (it cannot be handed the grid's page); the grid keeps its one read and its cap; the SQL has no LIMIT, reads successful runs with a duration, groups per collector and bucket, and uses the shared origin clamped to the window start; the read binds its window as naive UTC and the bucket width under the interactive deadline; the bucket width for a 24-hour and a 7-day range equals TrendBuckets.AutoMinutes(windowMinutes, 1, TrendBudget.Chart.AutoPoints), a partial minute rounds up, and the width function is the same call the CPU and blocking trends make; the lines are each collector's bucket maxima (not averages) in collector then time order with the average and count alongside; a collector with one bucket draws no line; the hover is cleared before the empty-range return; the web server page's two Collection Log reads are table panels only.
  • ViewerCollectorDurationChartLiveTests (new, 8 tests, seeded collection_log rows, the real read and the real line-building step): a 24-hour range holding 2,162 runs (the grid keeps the newest 500, which cover only the last 5.5 hours) draws each collector's line from the range's first instant to its last, 1,441 and 721 points, one-minute buckets, well over the 500-row page; a 7-day range reads ten-minute buckets, 1,441 runs merged into 145 or 146 buckets with every run counted; a bucket holding one run of 5,000 ms among nine of 100 ms has maximum 5,000, average 590 and count 10, and the line plots 5,000; an ERROR run of 99,999 ms and a SUCCESS run with no duration are not counted; a range starting 5 minutes into a bucket gets a first bucket starting at the range's start and holding the 5 runs from there; the range's end instant is included and nothing later is read; another server's collector does not leak in; a range before any run reads nothing.
  • RED (viewer chart): with the chart's read returning the grid's page of 500 runs, one point per run, A24HourRange_HoldingMoreThanTheGridsPage_DrawsPointsAcrossTheWholeRange fails: the first plotted point is 18.5 hours after the range's start (OLE date 46298.16 against 46297.39). On that shape 6 of the 8 live tests fail (no buckets: 180 points where 18 are expected, and the range-start, width, merge and range-end facts as well); with the bucketed read restored, all 8 pass.
  • Web: no chart draws from the Collection Log page, so there is no web change for the chart (the pin above).
  • Targeted classes without a store on this branch merged with origin/dev: ViewerCollectorDurationChartTests 11, ViewerCollectionLogDataStartTests 16, ViewerConfigChangesDataStartTests 18, ViewerCommandTimeoutTests 57, McpPayloadContractCensusTests 68, RepoFileAdoptionTests 2, DocCommentHygieneTests 77, CommentFilterAdoptionTests 4, LivePostgresCollectionHygieneTests 11: 264 run, 0 failed, 0 skipped. The same classes plus ViewerCollectorDurationChartLiveTests 8, ViewerCollectionLogDataStartLiveTests 9 and the live daily-health reads of the recent log and the per-collector log (4) and the SQL dialect check (1), against a throwaway PostgreSQL 18 cluster: 286 run, 0 failed, 0 skipped.
  • Darling.Tests, the viewer and the web service project build with 0 warnings.
  • RED, then green: an empty answer on each admitted read reaches the data-start check. A store that never connects shows it. Every other status on every listed read comes back untouched. This fails on the old code, which returned the empty answer before the check.
  • RED, then green: the page runs under Node in a zone away from UTC. It draws the note above an empty grid's message, with its times in that zone. For an unavailable answer it draws only the message. This fails on the old code, where an empty answer drew no note.
  • A source check holds both halves of the list. Each admitted read's tool answers its word. Each left-out read answers unavailable, never empty.
  • Live (throwaway store): a server added two days ago has two identical snapshots, and is read over 7 days. For the server, database, trace-flag and PostgreSQL change reads, it answers empty (no_changes for PostgreSQL). The answer carries the note naming its first collection, and the three instants. The same answer for a server monitored for months, with snapshots inside the window, carries no note. 28 live tests in the two note classes passed.
  • Two existing checks were changed on purpose, because the rule changed. The untouched-envelope check used empty for waiting tasks and no_changes for the PostgreSQL changes. Both now expect the data-start check, and the untouched cases use unavailable and not_collected.
  • RED, then green: WebDataStartNoteCappedWindowTests.ACappedPage_WhoseOldestRowFallsAfterTheReadsStart_GetsItsNote_WhenTheRequestSentNoAnchor failed on the old code (1 of 2 failed). Both pass now. A full page whose oldest row falls just after the read's window start gets the note, with and without an as_of.
  • RED, then green: with the note fields on every panel answer, ComposeClientTimeoutAttributionTests.TheComposeRunRoute_PassesTheHeadroomAndTheRemapFlag failed on the call text. ComposeDataFloorLiveTests.TheRenderedPanel_ShowsWhereTheDataStarts (with a store) failed because the default answer still carried data_start_note. Now the default answer has notice only, and the web answer also has the note fields. 17 of 17 pass, and 14 of 14 with a store.
  • ACollectionLogRankedSlowestFirst_KeepsTheCoverageRule keeps its no-store case and now also runs over a store holding a server added two days ago. It asserts that the slowest-first page gets the coverage note, not the capped one. With the slowest-first read sent down the capped path, it failed.
  • Classes for the web fixes, with 0 failed. The live ones ran against a throwaway store.
    • WebDataStartNoteTests 20, WebDataStartNoteLiveTests 7, WebDataStartNoteConfigAndLogTests 17, WebDataStartNoteConfigAndLogLiveTests 21
    • WebDataStartNoteCappedWindowTests 2, ComposeClientTimeoutAttributionTests 17, ComposedPanelNoticeZoneTests 2, WebRangeKeptHistoryBehaviourTests 9
    • DarlingMcpConfigHistoryToolsSurfaceAndSqlTests 30, DarlingMcpConfigHistoryToolsLivePostgresTests 1
    • ComposeDataFloorLiveTests 14, ComposeOpenPhaseTimeoutTests 3, ComposeRollupDataFloorLiveTests 2
    • Source checks: RepoFileAdoptionTests 2, DocCommentHygieneTests 77, CommentFilterAdoptionTests 4, TsqlConventionGuardTests 14
  • Viewer tests that need no store: 548 run, 0 failed (23 skipped because they need a store).
  • The same classes with a store: 548 run, 0 failed, 0 skipped. A second batch of related classes with a store: 92 run, 0 failed.
  • Lite tests for the hover, the chart and the data-start notes: 322 run, 0 failed.
  • Each new check fails on the old shape (the old line put back, then restored):
    • Drill end: APinnedDrillWindow_ReadsTheProbesWindow_FromItsStartToItsEnd (with a store) failed when the read had no end, because the run after the pinned end came back. TheDrill_UsesTheDrillHours_ForItsReadAndItsProbe_FromOneStart failed too.
    • Drill reads declared as a pair: TheDrill_DeclaresItsTwoReadsWidth_BeforeTheFirstRead_AndReleasesItBeforeTheNoteAwaitsItsProbe failed with the declaration removed.
    • Chart read isolated: AChartReadThatFails_BlanksOnlyTheChart_TheGridsTheNoteAndTheCaveatsStillDraw and TheChart_IsFedItsOwnRead_NotTheGridsPage failed with the bare read.
    • Draw order and the full page: TheChartAndTheCaveats_DrawBeforeTheNoteAwaitsItsProbe, AFullPage_NamesItsOldestRun_WithoutWaitingForAProbeThatNeverAnswers and AFullPage_StillLogsAProbeThatFailsLater failed with the note first and no shortcut for a full page. TheTabsBannerStep_NamesTheSameCap_ToTheBanner_FromTheRunTimes failed too.
    • Moved health checks: the matching column was renamed away in the by-server statement. Then EveryBandingRead_SelectsTheAbandonedCount_..., EveryBandingRead_SelectsTheExtensionMissingCount_..., EveryBandingRead_SelectsBothRegressionInstants, EveryDarlingHealthRead_ProjectsTheNewestRunsNote, BothRawFleetReads_SelectTheNewestRunNote_WithPlainAggregatesOnly and The_Per_Server_Health_Read_Projects_The_Columns_Its_Mapper_Reads_And_The_Fleet_Read_Projects_No_Message failed. Health_Reads_Surface_The_Note_Gated_On_SUCCESS failed with the per-server note count renamed.
    • Viewer hover: ABucketAfterABreak_ShowsItsOwnRunsAndAverage_NotTheNextBucketsAndTheLastOneShowsItsOwn and TheRender_DrawsItsLinesThroughThePlotStep_WithTheTabsHover failed with the lines registered without their details. The test draws 20 buckets with one gap on a real chart and asks the hover what it says over each point.
    • One shared text: TheMethod_HandsTheSharedHelperTheRunCountAndTheAverage_AndItsChartFileHoldsNoCopyOfTheWords (Lite) failed with Lite's own copy of the words put back. The exact strings are pinned in both apps: 1 run, 12 runs, a thousands separator, a whole average and a fractional average. They pass on the old Lite text too, since its output did not change.
  • Not measured: the new read's plan over a store holding a week of runs at about 20 a minute.
  • Full suite.

CHANGELOG

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

Proposed entry:

SECTION: Fixed
ENTRY:
- **The Config Changes and Collection Log grids, in the Darling viewer and on the web server page, say where their data starts, the viewer's Duration Trends chart draws the whole range, and a Custom Views notice names its times in your time zone** ([#4966]) - A range that reached back before a server's data began showed a short history with nothing saying so. In the viewer, the Server, Database and Trace Flag Changes grids, the Collection Log grid and the per-collector collection history window now show a "Showing since" note naming where the data starts. On the web page, the Server, Database, Trace Flag and PostgreSQL Configuration Changes grids and both Collection Log grids show the same note. A Collection Log page cut by its row limit (500 runs in the viewer) and a PostgreSQL changes page cut by its limit name their oldest row. A Custom Views panel's "partial window" notice now names its times in your browser's time zone, like the rest of the page, instead of UTC. On the web page, an empty grid whose range began before the data now shows the note above its empty message as well. This covers the change grids, both Collection Log grids, Waiting Tasks, and the PostgreSQL wait sampling, kernel, lock and predicate statistics grids. In the viewer, the Collection Health tab's Duration Trends chart drew only the newest 500 runs of the Collection Log grid's page against an axis spanning the whole range (about 25 minutes of "Last 24 hours" on a server logging about 20 runs a minute, with the rest of the axis empty). It now reads its own buckets over the whole range (one minute wide for 24 hours, ten minutes for 7 days) and draws each collector's slowest run in each bucket, and its vertical axis now says "Slowest run per bucket (ms)". Its hover now also says how many runs each point is the slowest of and what the bucket averaged, in the same words as Lite's. An empty range no longer leaves the previous range's lines answering the hover. The grid keeps its 500-row page.
REF:
[#4966]: https://github.com/erikdarlingdata/PerformanceMonitor/issues/4966

Notes

…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.
…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.
… starts, and a Custom Views notice is written in the browser's zone (#4966)
…nges, and the class notes name the three capped lists (#4966)
…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.
…nd the per-collector drill, say where their data starts (#4966)

The three SQL Server config-change grids probe server_config, database_config and trace_flags; the Collection Log grid and its per-collector drill probe the collection log's own source. The grid's 500-run read names its oldest run when it fills the cap, through one shared constant.
…ter than the window's start, and the config and collection-log reads are tested end to end (#4966)

The web's capped-list note named the oldest returned row for any newest-first read that hit its row cap, even
when that row was at the window's start (or before it) and the page therefore showed the whole range. It now
shows only when the oldest row is strictly later than the window's start, with no slack, as Lite's capped grids
judge it. A page cut exactly at the start says nothing; one a second later gets the note.

New live tests run the real tool for get_server_config_changes, get_database_config_changes,
get_trace_flag_changes, get_pg_server_config_changes and get_collection_log against a store and pass the answer
through WebDataStartNote.AddAsync: a range that starts before coverage gets the note naming the coverage start
and the three *_utc fields, a covered range whose first row comes late gets none, a capped collection log names
its oldest returned run (including past 168 hours, and keeping the coverage rule under a duration floor), a cut
PostgreSQL changes page names its earliest changed_at and is read as rows, and the window-start edge is exact.
A no-store theory pins the same edge for the three newest-first capped reads.
…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.
…the product's own banner formatter, not a copied minutes-only string (#4966)
…s "Showing since" probe, and tests for both notes (#4966)

The per-collector drill worked its probe's start from the pinned end of its window but let the read take its own start from the
clock, so a pinned end moved the probe and not the rows. The read now takes a start (the hours-back overload delegates to it) and
the drill passes the one start to both.

The Collection Log grid's banner call is now a step of its own (ShowCollectionLogDataStartAsync), so a test drives the tab's
cap argument against a real store.

Tests: pins for the cap, the probe beside the read inside the declared width and its release before the banner awaits it, the
drill's wiring and one start; store-backed cases for a range before the first collection, a quiet start, a one-hour range, a full
page of 500 runs against 499, and the drill (a late collector, a first collection inside the week, a pinned end).
… own buckets over the whole range, not the Collection Log grid's page of 500 runs (#4966)
…here its data starts (#4966)

The web mirror left every answer carrying a status word untouched, so a grid whose
read said it looked and found nothing (empty, or no_changes for the PostgreSQL
changes) drew no note, though empty is these grids' usual state and a new server's
empty week reads as a quiet one. Each read that answers empty or no_changes for
nothing found in the window now goes through the coverage probe, and the page draws
the note above the empty message in the browser's zone. not_collected, unavailable
and every other status keep their own message and get none.
…lds on the web route (#4966)

A newest-first capped read now has its window end taken once, before the read, and the same end goes to the read and to the note check. The Custom Views panel's data-start sentence and instants are added only when the web route asks for them, so the MCP tool's answer carries the sentence once, in notice. The ranked collection log test now runs over a store and checks that the ranked page gets the coverage note.
…g Duration Trends read blanks only the chart, and the Duration Trends hover names each bucket's runs (#4966)

- The Collection Log drill reads from the window's start to its end, as its "Showing since" check already did, and declares its two reads to the read deadline as a pair.
- On the Collection Health tab the chart's read is wrapped the way Lite wraps its trend reads, so a timeout blanks the chart and nothing else. The chart and the caveats draw before the "Showing since" note, and a full page names its oldest run without waiting for the start-of-data check.
- The fleet-wide health statement that no screen ran is removed; its checks now guard the by-server statement the status bar and the Overview cards run.
- The Duration Trends hover line ("Slowest of N runs; average X ms") comes from one shared helper next to the hover helper. Lite's text is unchanged; the viewer's hover now carries the line, keyed by each point's time so a bucket after a gap shows its own numbers.
@erikdarlingdata
erikdarlingdata marked this pull request as ready for review October 3, 2026 16:07
@erikdarlingdata
erikdarlingdata merged commit d679f7f into dev Oct 3, 2026
21 of 22 checks passed
@erikdarlingdata
erikdarlingdata deleted the feature/4966-w1-config-collection-log branch October 3, 2026 16:10
erikdarlingdata added a commit that referenced this pull request Oct 3, 2026
…) (#5031)

Lite's per-collector run-history window, opened from a Collection Health row, now shows a "Showing since <time>" note under its summary when the server's collection log starts after the 7 days the window reads. It is the same band, wording and 90-minute slack as the Collection Log grid's note, in the zone the window prints its rows in. The Darling viewer's drill window already does this (#5010).

The note keys on the server's whole collection log, not the drilled collector's first run, so a collector that started late in a covered week shows nothing. The read and the probe share one window, computed once from its end (or a pinned end), and the read is now bounded at that end too. The read has no row cap, so no cap rule applies. A probe that throws costs only the note; the rows still load.

Part of #4966
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