Repository navigation
Bucket Lite's Overview CPU, wait and memory lane reads (#4234) - #4337
Merged
Merged
Conversation
GetCpuUtilizationAsync, GetTotalWaitTrendAsync and GetMemoryTrendAsync fed the Overview correlated lanes (and, for CPU/memory, their own dedicated tab charts) one row per collection, so a multi-day window shipped thousands of points to a WPF chart. Each now buckets in DuckDB to TrendBuckets.AutoMinutes against TrendBudget.Chart, the same ladder and origin the MCP trend family already uses, with the SQL text pulled into an internal static member so a source test can check it without a live DuckDB. A bucket whose collections are all a single physical row stamps at that row's own clock instead of the bucket grid line, so a window narrow enough to never merge two collections renders exactly as the old per-collection read did. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
Four claims against the three reads bucketed in the prior commit: each statement's text carries a bucket width; a 7-day window (2,016 raw collections per lane) stays within TrendBudget.Chart's 1,500-point cap, which the raw row count alone would have missed; a window narrow enough that every bucket holds exactly one physical collection renders byte-identical to the pre-#4234 per-collection read; and the wait lane still drops an isolated unrated collection (#3540) rather than rendering it as 0. Proved the budget claim fails pre-bucketing (2,005 points, over the 1,500 cap) and the source-check claim fails to compile pre-bucketing, by building against the parent commit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
erikdarlingdata
marked this pull request as ready for review
September 25, 2026 18:08
erikdarlingdata
enabled auto-merge (squash)
September 25, 2026 18:08
This was referenced Sep 25, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #4234.
Why
Lite's Overview tab (Correlated Timeline Lanes) redraws its CPU, wait and memory lines from three reads that shipped one row per collection:
GetCpuUtilizationAsync,GetTotalWaitTrendAsyncandGetMemoryTrendAsync. A multi-day window on a busy server handed WPF thousands of points per line.GetCpuUtilizationAsyncandGetMemoryTrendAsyncalso feed their own dedicated CPU and Memory tab charts — the only other callers, both charts, same problem.What changes
All three reads now bucket in DuckDB with
time_bucket, widths chosen by the existingTrendBuckets.AutoMinutesladder againstTrendBudget.Chart's 1,500-point budget (the same machinery the MCP trend family already uses, at that family's own narrowerMcpPointBudget, per the top-of-file note inLocalDataService.TrendBuckets.cs, which I extended by one paragraph to say so).seriesCountis always 1 in that call, matching the #4234 ruling: CPU's two gauges and memory's four gauges each ride the SAME row/bucket, not separate series, and the wait lane is already a single aggregated line.GetCpuUtilizationAsync,Lite/Services/LocalDataService.Cpu.cs): a gauge,AVG(COALESCE(x, 0))per bucket, rounded and cast back toINTEGERsoCpuUtilizationRow.SqlServerCpu/OtherProcessCpukeep their existinginttype (their derivedTotalCpu/IdleCpuproperties need no change).GetTotalWaitTrendAsync,Lite/Services/LocalDataService.WaitStats.cs): a rate. Kept the existingper_collectionCTE (interval per collection, MAX over that collection's wait types, LAG fallback for pre-v60 rows, Measurement-layer campaign: the delta honesty contract (11 findings, one keystone) #3540) and added aratedCTE plus bucket aggregation on top: a bucket's rate isSUM(rated_ms) / SUM(rated_seconds)(time-weighted, never an average of per-collection rates), andHAVING COUNT(rated_seconds) > 0drops a bucket with no rated collection, so it stays absent rather than 0 — the same thing the per-collection read always did to a single unrated collection.GetMemoryTrendAsync,Lite/Services/LocalDataService.Memory.cs): four gauges,AVG(COALESCE(x, 0))per bucket, kept asdouble(unchanged type).Every statement's SQL text is pulled into an
internal staticmember (CpuUtilizationTrendSql,MemoryTrendSql,TotalWaitTrendSql(exclude)) so a source test can read it without a live DuckDB, matching the pattern in the sibling PR #4331 (WaitTrendsSql).Singleton stamping, same rule as #4331: when EVERY bucket a call returns holds exactly one physical collection, each point is stamped at that collection's own clock (
first_sample_time/first_collection_time) instead of the bucket grid line, so a window narrow enough to never merge two collections renders byte-identical to the pre-#4234 per-collection read. Any merged bucket anywhere in the call switches the whole call tobucket_start.Clamping: each statement's bucket_start is
GREATEST(time_bucket(...), <window start>), so a bucket never renders earlier than the window the caller asked for. For CPU (bucketing on server-localsample_time, not UTCcollection_time) that clamp needed a new parameter carrying the server-local window start, since the existing UTC parameters aren't in the same frame.I left
GetWaitStatsTrendAsync(single wait-type) andGetWaitStatsTrendsByTypesAsync(batched-by-type) untouched — both are PR #4331's own scope, not this one's — and left the blocking/deadlock lanes exactly as they are, per the #4234 ruling (issuecomment-5836582794).Test plan
New file
Lite.Tests/OverviewLaneBucketingTests.cs, four tests against a real embedded DuckDB:BucketedTrendSql_CarriesABucketWidth— each of the three SQL members containstime_bucket.SevenDayWindow_StaysWithinChartBudget_ForAllThreeLanes— 2,016 raw collections per lane (5-minute cadence over 7 days, bulk-seeded with onegenerate_series-basedINSERT...SELECTper table, not a C# loop), all three reads return<= TrendBudget.Chart.AutoPoints(1,500) rows.SingletonBuckets_MatchThePerCollectionReadExactly_ForAllThreeLanes— three collections five minutes apart, seeded at :37 seconds past the minute (off the bucket grid on purpose). A 3-hour window auto-sizes to a 1-minute bucket, so every bucket is a singleton; all three lanes return the exact seeded timestamps and values.TotalWaitTrend_Bucketed_DropsAnIsolatedUnratedCollection— an isolated 0-interval (restart) collection between two rated ones stays absent from the wait lane, not a 0.00 point.Proved the regression power directly: built against the parent commit (pre-bucketing) with the source-check test disabled —
SevenDayWindow_StaysWithinChartBudget_ForAllThreeLanesfailed (CPU returned 2005 points, over the 1,500 cap), and separately the source-check test fails to even compile against that commit (CpuUtilizationTrendSql/etc. don't exist yet). The singleton and unrated-drop tests pass on both commits by design — they pin behavior that must hold identically before and after bucketing, not a behavior change.*Overview*,*CorrelatedTimeline*,*Cpu*,*Memory*,*WaitStats*,*DocCommentHygiene*(184 tests) — 0 failed.dotnet build Lite.Tests/Lite.Tests.csproj— 0 Warning(s), 0 Error(s).origin/dev(clean, no conflicts — the incoming changes were Darling-side File I/O/memory viewer work, untouched by this PR).Lite.Tests.exeonce after the merge: 5419 total, 0 errors, 0 failed, 272.8s.Existing tests re-checked for regressions
Three pre-existing test files call these reads directly and all still pass (32 tests):
DeltaFamilyUnknowableRowReadTests(includingTotalWaitTrend_TakesMaxStoredIntervalPerCollection_DropsAnAllUnknowableCollection, whose four collections are exactly 1 minute apart — each its own singleton bucket at the resolved 1-minute width, so it renders identically),TimeHonestyRungTests(GetCpuUtilizationAsyncunder the v63 UTC-instant/offset-fallback logic — likewise all singleton buckets),OverviewComparisonWindowOffsetTests.What the coordinator should double-check
$6) inCpuUtilizationTrendSql— CPU is the only one of the three bucketing on a server-local column rather than UTCcollection_time, so it needed its own clamp value instead of reusing the UTC window parameter the other two clamp against.LocalDataService.TrendBuckets.cs(the The rest of the trend family still returns every collection: a week of get_tempdb_trend is 2 MB, get_memory_trend 1.5 MB, get_wait_trend 1.1 MB #3960 MCP-bucketing context block) noting that three of the per-collection reads it names are no longer left "exactly as they are" — they're bucketed in place now, at the chart's wider budget, for their own (all-chart) callers.CHANGELOG entry
SECTION: Changed
ENTRY:
REF:
[Bucket Lite's Overview CPU, wait and memory lane reads (#4234) #4337]: Bucket Lite's Overview CPU, wait and memory lane reads (#4234) #4337
🤖 Generated with Claude Code
https://claude.ai/code/session_01FVjn4PBJN71NQXdFo6ZxNQ