Skip to content

Bucket the Darling viewer's File I/O and memory clerk trend charts (#4234) - #4329

Merged
erikdarlingdata merged 6 commits into
devfrom
fix/4234-viewer-fileio-memory-buckets
Sep 25, 2026
Merged

erikdarlingdata merged 6 commits into
devfrom
fix/4234-viewer-fileio-memory-buckets

Conversation

@erikdarlingdata

@erikdarlingdata erikdarlingdata commented Sep 25, 2026 •

Copy link
Copy Markdown
Owner

Part of #4234.

Why

The WPF Darling viewer's File I/O tab and Memory tab's clerk chart read every collection in the
window instead of bucketing, the same problem #4234 already fixed for Wait Stats and Perfmon in
PR #4304. Per #4234's body, one production store returned 85,010 File I/O rows for a 7-day window.
The memory clerk trend has the same shape (up to 20 clerks, one row per collection each).

What changes

Following PR #4304's pattern (WaitTrendsSql, ViewerNameListCache) exactly, per the ruling at
issuecomment-5834669118:

  • ViewerDataService.FileIo.cs - FileIoLatencyTrendSql and FileIoThroughputTrendSql now
    group into date_bin buckets sized by TrendBuckets.AutoMinutes(windowMinutes, 1, TrendBudget.Chart.AutoPoints), the same sizing the MCP trend reads use. Latency averages summed
    stall-ms over summed read/write count deltas per bucket; throughput sums byte deltas over summed
    interval-seconds (time-weighted, never an average of per-collection rates). The existing top-10-files
    limit is unchanged. A bucket holding exactly one physical collection (rated or not — matching
    WaitTrendsSql's documented rule) is stamped at that collection's own raw collection_time instead
    of the bucket's date_bin grid line.
  • A rated CTE now carries the Measurement-layer campaign: the delta honesty contract (11 findings, one keystone) #3540 "interval 0 is unknowable" rule (previously a WHERE
    filter that dropped the row outright) as a per-row CASE null-out, so an unrated row still counts
    toward collection_count for singleton detection but never contributes to a bucket's sums. A new
    HAVING COUNT(rated_reads) > 0 / HAVING COUNT(rated_seconds) > 0 preserves the pre-bucket "absent,
    not 0.00" answer for a bucket where every row was unrated.
  • ViewerDataService.Memory.cs - MemoryClerkTrendsSql (up to 20 clerks) now buckets as a gauge
    (AVG(memory_mb) per bucket, no rate math — a clerk's memory_mb is a point-in-time reading, not an
    accumulating counter), with the same singleton-stamping rule. DistinctMemoryClerkTypesSql (the
    clerk picker's population read) now sits behind ViewerNameListCache
    (GetDistinctMemoryClerkTypesAsync gained an optional nowUtc clock seam, default null so every
    existing 3-arg call site is unaffected), same as DistinctWaitTypesSql in PR WPF Wait Stats and Perfmon trend charts bucket server-side (#4234) #4304: a full-window
    DISTINCT runs at most once per 15 minutes per (server, window length), and a hit also requires the
    cached window's end to sit within the TTL of the requested end.
  • MemoryGrantTrendSql, MemoryGrantChartDataSql, LatestMemoryStatsSql, and
    MemoryPressureEventsSql are untouched, per the brief's scope.
  • PerformanceMonitor.Common.csproj gained the PerformanceMonitor.Darling.Viewer InternalsVisibleTo
    grant and Darling/PerformanceMonitor.Darling.Viewer/ViewerNameListCache.cs was added — both copied
    verbatim from PR WPF Wait Stats and Perfmon trend charts bucket server-side (#4234) #4304's pattern before that PR merged into dev out from under this branch (see
    "Timing note" below); after merging origin/dev, both turned out byte-identical to what WPF Wait Stats and Perfmon trend charts bucket server-side (#4234) #4304 landed,
    so the merge picked them up cleanly with no conflict.
  • All four downstream callers (ViewerServerTab.FileIo.cs, ViewerServerTab.Memory.cs,
    CorrelatedTimelineLanesControl.xaml.cs) consume the same record shapes unchanged; none assume a
    fixed point spacing, and the two chart renderers already do their own per-series GroupBy +
    OrderBy(CollectionTime), so the SQL's new series-first ORDER BY (mirroring WaitTrendsSql,
    instead of the old collection-time-first order) needed no renderer changes.

Timing note

origin/dev didn't have PR #4304 merged when this branch was created, so the branch started by
copying ViewerNameListCache.cs and the csproj edit from that PR's branch by hand. #4304 merged to
dev partway through this lane's work; merging origin/dev afterward picked up the real files
cleanly (byte-identical, no conflict) and also brought in the real ViewerTrendBucketingReviewTests.cs
this brief pointed at.

Test plan

Ran from Darling.Tests.exe (MTP), against a throwaway PostgreSQL 18 + TimescaleDB rig
(postgresql.conf forced to UTC to match CI), darlingtest database, after merging origin/dev:

  • Darling/Darling.Tests/Darling.Tests.csproj builds with 0 Warning(s), 0 Error(s).
  • ViewerFileIoBlockingSqlTests, ViewerFileIoBlockingLivePostgresTests,
    ViewerMemorySqlTests, ViewerMemoryPickerLogicTests, ViewerMemoryLivePostgresTests,
    ViewerTrendBucketWidthSqlTests, ViewerNameListCacheTests, ViewerTrendBucketingLiveTests,
    DocCommentHygieneTests - 149/149 pass, 0 failed.
  • Updated the pre-existing SQL-text assertions in ViewerFileIoBlockingSqlTests /
    ViewerMemorySqlTests that the bucketing rewrite made stale (GROUP BY/ORDER BY shape, the
    rated-CTE column names, the CAST(memory_mb...) literal).
  • Added the ruling's four required tests per read (source bucket-width check; live 7-day-window
    row-budget cap; live point equality with the old shape when every bucket holds one collection,
    seeded off the minute grid at :37 seconds; for the clerk picker, a live proof that a second call
    inside 15 minutes answers from cache and a window end past the TTL misses even within the
    wall-clock TTL).
  • Proved the regression pins fail on the pre-bucketing code: temporarily swapped in
    origin/dev's (pre-this-PR) ViewerDataService.FileIo.cs and ViewerDataService.Memory.cs,
    rebuilt, and re-ran just the two new 7-day-budget-cap tests - both failed (too many rows) as
    expected, confirming they're real regression pins. The two singleton point-equality tests
    passed even against old code too, which is expected and consistent with WaitTrendsSql's own
    test suite: old code always returned raw unbucketed points, so it trivially satisfies "raw
    timestamps unchanged" - that test's value is catching a future regression that buckets without
    singleton detection, not distinguishing old from new. Restored both files to the bucketed version
    afterward and re-ran the full 149 to confirm.
  • Caught one bug this way: my first cut of the throughput singleton test used a byte delta
    calibrated for a 60-second collection interval, but its three seeded points are 5 minutes apart -
    it read 0.2 MB/s instead of the intended 1.0. Fixed in a follow-up commit once I actually ran it.
  • Full suite: not run here, per this lane's brief (CI runs it).
  • Live Darling viewer app: not exercised manually.

CHANGELOG entry

SECTION: Changed
ENTRY:

🤖 Generated with Claude Code

https://claude.ai/code/session_01FVjn4PBJN71NQXdFo6ZxNQ

erikdarlingdata and others added 6 commits September 25, 2026 13:04
Both reads used to return every collection (measured 85,010 rows at 7 days for one
store on the File I/O tab). Groups now by TrendBudget.Chart-sized date_bin buckets,
same sizing as the MCP trend reads: latency averages summed stall-ms over summed
read/write count deltas per bucket, throughput sums byte deltas over summed interval
seconds. A bucket holding exactly one physical collection (rated or not) keeps that
collection's own raw timestamp instead of the bucket_start grid line, matching
WaitTrendsSql's pattern from PR #4304. Also pulls in the InternalsVisibleTo grant and
ViewerNameListCache dependency shared with that PR's pattern.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
The clerk trend read (up to 20 clerks) returned every collection; it now buckets as
a gauge (average memory_mb per bucket), same sizing as the MCP trend reads and the
File I/O reads just bucketed. A bucket holding exactly one physical collection keeps
that collection's own raw timestamp, matching WaitTrendsSql. DistinctMemoryClerkTypesSql
(the clerk picker's population read) now sits behind ViewerNameListCache, same as
DistinctWaitTypesSql in PR #4304, so the full-window DISTINCT runs at most once per
15 minutes instead of on every auto-refresh.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
#4234)

Updates the existing SQL-text assertions that the bucketing rewrite made stale
(GROUP BY/ORDER BY shape, the rated-CTE column names, the CAST(memory_mb...) literal),
and adds the ruling's four required tests per read: a bucket-width source check, a
live 7-day-window row-budget cap, live point equality with the old shape when every
bucket holds one collection (seeded off the minute grid at :37 seconds), and for the
memory clerk picker, a live proof that a second call inside 15 minutes answers from
cache and that a window end past the TTL misses.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
…ton test

The singleton test's t1..t3 are 5 minutes apart, but the byte delta was calibrated
for a 60-second interval (copied from the other throughput test, which is 60-second
spaced) -- it read 0.2 MB/s instead of the intended 1.0. Caught by actually running
the new test.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
@erikdarlingdata
erikdarlingdata marked this pull request as ready for review September 25, 2026 17:29
@erikdarlingdata
erikdarlingdata enabled auto-merge (squash) September 25, 2026 17:29
@erikdarlingdata
erikdarlingdata merged commit 1845191 into dev Sep 25, 2026
18 of 20 checks passed
@erikdarlingdata
erikdarlingdata deleted the fix/4234-viewer-fileio-memory-buckets branch September 25, 2026 17:46
erikdarlingdata added a commit that referenced this pull request Sep 25, 2026
* Bucket the Darling viewer's TempDB file I/O trend (#4234)

TempDbFileIoTrendSql returned one row per (collection_time, file_name)
for a tempdb file, unbounded on the window end, unlike the File I/O
tab's own reads (#4329) and Lite's already-bucketed twin (#4340). A
7-day window at 1-minute cadence for 4 files measured 40,324 rows on
a throwaway rig seed; the same seed through the new date_bin bucket
(auto-picked to 10 minutes for that window) measures 4,036.

GetTempDbFileIoTrendAsync now takes (serverId, startUtc, endUtc) like
the other bucketed trend reads, computes its bucket width from
TrendBuckets.AutoMinutes, and stamps points at their own raw
collection time instead of the bucket grid when every returned bucket
is a singleton. The #3540 "no delta knowable" marker is carried into
a rated CTE (nulled, not filtered) so collection_count still counts
an unrated row and a bucket is never mistaken for a true singleton.

LoadTempDbAsync passes endUtc through and drops the now-redundant
client-side post-filter on the file-I/O series.
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