Repository navigation
Bucket Lite's Wait Stats and Perfmon trend and picker reads (#4234) - #4331
Conversation
GetWaitStatsTrendsByTypesAsync now buckets to TrendBudget.Chart's per-series point budget instead of returning every collection (196,800 rows at 7 days for 20 types, per #4234's measured number), following the #4304 ruling: a rated CTE sums wait and signal wait over rated seconds per bucket, avg_ms_per_wait is the summed wait over summed waiting tasks, and a point is stamped at its bucket's raw first_collection_time only when every bucket in the call holds exactly one physical collection. GetDistinctWaitTypesAsync is memoized through the new LiteNameListCache (per server and window length, 15-minute TTL, a hit also requires the cached window's end to stay within the TTL of the requested end) so the picker's full-window DISTINCT no longer reruns on every 1-minute auto-refresh. The cache is a standalone file so the Perfmon picker (and a later memory-clerk picker) can reuse it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
GetPerfmonTrendsByCountersAsync now wraps the unchanged per-collection SUM as a subquery and re-aggregates into TrendBudget.Chart-sized buckets instead of returning every collection (118,080 rows at 7 days for 12 counters, per #4234's measured number), following the #4304 ruling: Value is the bucket's rounded gauge average, DeltaValue/SampleIntervalSeconds are summed only over rated collections so their ratio downstream stays the ruling's summed-deltas-over-summed-intervals rate, and a point is stamped at its bucket's raw first_collection_time only when every bucket in the call holds exactly one physical collection. GetDistinctPerfmonCountersAsync is memoized through the LiteNameListCache T3b added alongside the Wait Stats picker, so this picker's full-window DISTINCT also stops rerunning on every 1-minute auto-refresh. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
…oInt64/ToDouble DuckDB promotes SUM() over an INTEGER column to HUGEINT, which DuckDB.NET hands back as a boxed BigInteger; Convert.ToInt64/Convert.ToInt32 and the reader's typed GetInt64/ GetDouble accessors cannot cast that. PerfmonCounterTypeReadTests caught this against the new GetPerfmonTrendsByCountersAsync bucketing (its SUM(sample_interval_seconds) FILTER column). Both new readers now route their SUM-derived columns through the existing ToInt64/ToDouble helpers, matching GetWaitBucketsAsync/GetPerfmonBucketsAsync's own established pattern for the same FILTER-summed shape. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
WaitTrendsSql(waitTypeCount) and PerfmonTrendsSql(counterCount) mirror Darling's ViewerDataService.WaitTrendsSql / PerfmonTrendsSql (#4234, PR #4304): the SQL text generation moves out of the two Get*TrendsByTypesAsync/ByCountersAsync methods into its own testable method, so a source check for the bucket width and the first_collection_time/collection_count columns needs no live DuckDB. No behavior change, same generated text. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
…eads Four groups, all against embedded DuckDB (no live SQL Server/Postgres needed): LiteTrendBucketWidthSqlTests (source checks: WaitTrendsSql/PerfmonTrendsSql carry a time_bucket width plus first_collection_time/collection_count), LiteNameListCacheTests (the cache's hit/miss rules, ported from Darling's ViewerNameListCacheTests), LiteTrendBucketingLiveTests' two budget-cap tests (a 7-day, one-per-minute window returns at most TrendBudget.Chart.AutoPoints * series rows) and two point-equality tests (three :37-second collections come back with their raw timestamps and values unchanged, not floored to the bucket grid), plus the two pickers' cache proof (a second call inside 15 minutes misses a wait type/counter the store gained since, a call past the TTL picks it up). Proven by hand against the pre-#4234 text (read directly while building this PR): the old GetWaitStatsTrendsByTypesAsync/GetPerfmonTrendsByCountersAsync had no time_bucket, first_collection_time or collection_count anywhere, so the source checks fail there outright, and the old per-collection read returns one row per collection -- 10,080 for the 7-day one-per-minute seed, over the budget the cap tests assert. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
… aggregation PerfmonIntervalAggregationTests read the whole Perfmon.cs source and pinned exact occurrence counts from before bucketing existed. The bucketed batched trend adds a second, correct layer of aggregation on top of the unchanged per-collection one: the outer GROUP BY counter_name, bucket_start sums each collection's own already-MAX'd interval and already-summed delta across the collections a bucket holds (the ruling's summed-deltas-over-summed-intervals rate) -- the full suite caught both stale pins. NoPerfmonTrendRead_SumsTheInterval (renamed *AcrossInstanceRows) now strips the new SUM(sample_interval_seconds) FILTER (WHERE sample_interval_seconds > 0) form before asserting no bare SUM(sample_interval_seconds) remains, so it still catches the original bug shape (summing the interval across a collection's instance rows) while allowing the new bucket-level one, which always carries that exact FILTER. TheAdditiveColumnsStaySummed's SUM(delta_cntr_value) count moves from 2 to 3 for the same reason; SUM(cntr_value) stays 2 because that outer layer AVERAGES (the ruling's gauge rule), adding no third SUM. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
#4234 ruling: GetDistinctWaitTypesAsync and GetDistinctPerfmonCountersAsync went back to their dev shape (uncached, no nowUtc seam) since MCP's wait and perfmon tools call them directly and must never answer with a picker snapshot up to 15 minutes stale. The TTL cache moved to two new entry points, GetDistinctWaitTypesForPickerAsync and GetDistinctPerfmonCountersForPickerAsync, which only ServerTab's picker refresh calls now. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
PerfmonIntervalAggregationTests pins the inner MAX(sample_interval_seconds) in source text, but no live test seeded more than one instance row per collection, so a regression turning that MAX into a SUM would have passed every DuckDB test in the file. Adds two: a single multi-instance collection (12 rows) landing alone in its bucket, and three such collections merged into one bucket, both asserting the bucket's DeltaValue/SampleIntervalSeconds land on the ruling's summed-deltas-over-summed-intervals rate. Proven by hand: reverting the inner MAX to SUM fails exactly these two tests. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
GetFileIoLatencyTrendAsync, GetFileIoThroughputTrendAsync and GetTempDbFileIoTrendAsync now bucket to TrendBudget.Chart's per-series point budget, the GREATEST(time_bucket(...), $2) shape PR #4331 gave the wait/perfmon trend reads. Latency stays a ratio (summed stall over summed operations, over rated rows only); throughput stays a rate (summed bytes over summed rated seconds). An unrated collection alone in a bucket still leaves that point absent (HAVING drops the bucket), same as before. The top-10-files ranking in the latency and throughput queries stays an unbucketed whole-window scan, unchanged. These reads feed the File I/O tab charts and the Overview correlated timeline's I/O lane (both its live and reference-period fetches), which read GetFileIoLatencyTrendAsync directly and need no changes of their own. Also fixes a stale comment in McpIoTools.cs that called the desktop chart's read "untouched" — it is bucketed now too, on its own budget, separately from the MCP tool's own GetFileIoTrendAsync. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
GetPerfmonTrendsByCountersAsync and GetWaitStatsTrendsByTypesAsync each got a new internal SQL-text member inserted between their own <summary> and their signature. Move each displaced block back to the method it describes; the block already sitting on PerfmonTrendsSql/WaitTrendsSql does not move. PerfmonCounterTypeRungTests pinned Lite's agreed-type expression count at 2; Lite's Perfmon.cs now holds it 3 times (once in the single-counter read, twice in the bucketed batched read, per collection inside and per bucket outside, the same double aggregation both Darling twins use). Update the pin and its summary text to match. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
|
CI fix pushed: Failure 1:
Failure 2: Changed, in
Revert-proof: set the pin back to Verification (all
PR left as draft, not readied, not merged, not armed for auto-merge. 🤖 Generated with Claude Code |
Unstacks this PR from #4331, which squash-merged to dev as fa05e9f with its CI fix. LocalDataService.WaitStats.cs (a conflict) and LocalDataService.Perfmon.cs (which auto-merged with PerfmonTrendsSql twice) take dev's copy, since this PR's own commits never touch either file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FVjn4PBJN71NQXdFo6ZxNQ
* Bucket Lite's Wait Stats trend and picker reads (#4234) GetWaitStatsTrendsByTypesAsync now buckets to TrendBudget.Chart's per-series point budget instead of returning every collection (196,800 rows at 7 days for 20 types, per #4234's measured number), following the #4304 ruling: a rated CTE sums wait and signal wait over rated seconds per bucket, avg_ms_per_wait is the summed wait over summed waiting tasks, and a point is stamped at its bucket's raw first_collection_time only when every bucket in the call holds exactly one physical collection. GetDistinctWaitTypesAsync is memoized through the new LiteNameListCache (per server and window length, 15-minute TTL, a hit also requires the cached window's end to stay within the TTL of the requested end) so the picker's full-window DISTINCT no longer reruns on every 1-minute auto-refresh. The cache is a standalone file so the Perfmon picker (and a later memory-clerk picker) can reuse it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3 * Bucket Lite's Perfmon trend and picker reads (#4234) GetPerfmonTrendsByCountersAsync now wraps the unchanged per-collection SUM as a subquery and re-aggregates into TrendBudget.Chart-sized buckets instead of returning every collection (118,080 rows at 7 days for 12 counters, per #4234's measured number), following the #4304 ruling: Value is the bucket's rounded gauge average, DeltaValue/SampleIntervalSeconds are summed only over rated collections so their ratio downstream stays the ruling's summed-deltas-over-summed-intervals rate, and a point is stamped at its bucket's raw first_collection_time only when every bucket in the call holds exactly one physical collection. GetDistinctPerfmonCountersAsync is memoized through the LiteNameListCache T3b added alongside the Wait Stats picker, so this picker's full-window DISTINCT also stops rerunning on every 1-minute auto-refresh. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3 * Fix InvalidCastException: read the new bucketed SUM columns through ToInt64/ToDouble DuckDB promotes SUM() over an INTEGER column to HUGEINT, which DuckDB.NET hands back as a boxed BigInteger; Convert.ToInt64/Convert.ToInt32 and the reader's typed GetInt64/ GetDouble accessors cannot cast that. PerfmonCounterTypeReadTests caught this against the new GetPerfmonTrendsByCountersAsync bucketing (its SUM(sample_interval_seconds) FILTER column). Both new readers now route their SUM-derived columns through the existing ToInt64/ToDouble helpers, matching GetWaitBucketsAsync/GetPerfmonBucketsAsync's own established pattern for the same FILTER-summed shape. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3 * Pull the two bucketed statements into internal static SQL-text methods WaitTrendsSql(waitTypeCount) and PerfmonTrendsSql(counterCount) mirror Darling's ViewerDataService.WaitTrendsSql / PerfmonTrendsSql (#4234, PR #4304): the SQL text generation moves out of the two Get*TrendsByTypesAsync/ByCountersAsync methods into its own testable method, so a source check for the bucket width and the first_collection_time/collection_count columns needs no live DuckDB. No behavior change, same generated text. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3 * Pin the #4234 ruling for Lite's Wait Stats and Perfmon trend/picker reads Four groups, all against embedded DuckDB (no live SQL Server/Postgres needed): LiteTrendBucketWidthSqlTests (source checks: WaitTrendsSql/PerfmonTrendsSql carry a time_bucket width plus first_collection_time/collection_count), LiteNameListCacheTests (the cache's hit/miss rules, ported from Darling's ViewerNameListCacheTests), LiteTrendBucketingLiveTests' two budget-cap tests (a 7-day, one-per-minute window returns at most TrendBudget.Chart.AutoPoints * series rows) and two point-equality tests (three :37-second collections come back with their raw timestamps and values unchanged, not floored to the bucket grid), plus the two pickers' cache proof (a second call inside 15 minutes misses a wait type/counter the store gained since, a call past the TTL picks it up). Proven by hand against the pre-#4234 text (read directly while building this PR): the old GetWaitStatsTrendsByTypesAsync/GetPerfmonTrendsByCountersAsync had no time_bucket, first_collection_time or collection_count anywhere, so the source checks fail there outright, and the old per-collection read returns one row per collection -- 10,080 for the 7-day one-per-minute seed, over the budget the cap tests assert. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3 * Narrow the pre-#4234 interval/delta SUM pins for the new bucket-level aggregation PerfmonIntervalAggregationTests read the whole Perfmon.cs source and pinned exact occurrence counts from before bucketing existed. The bucketed batched trend adds a second, correct layer of aggregation on top of the unchanged per-collection one: the outer GROUP BY counter_name, bucket_start sums each collection's own already-MAX'd interval and already-summed delta across the collections a bucket holds (the ruling's summed-deltas-over-summed-intervals rate) -- the full suite caught both stale pins. NoPerfmonTrendRead_SumsTheInterval (renamed *AcrossInstanceRows) now strips the new SUM(sample_interval_seconds) FILTER (WHERE sample_interval_seconds > 0) form before asserting no bare SUM(sample_interval_seconds) remains, so it still catches the original bug shape (summing the interval across a collection's instance rows) while allowing the new bucket-level one, which always carries that exact FILTER. TheAdditiveColumnsStaySummed's SUM(delta_cntr_value) count moves from 2 to 3 for the same reason; SUM(cntr_value) stays 2 because that outer layer AVERAGES (the ruling's gauge rule), adding no third SUM. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3 * MCP wait/perfmon name-list tools stay uncached, only the picker memoizes #4234 ruling: GetDistinctWaitTypesAsync and GetDistinctPerfmonCountersAsync went back to their dev shape (uncached, no nowUtc seam) since MCP's wait and perfmon tools call them directly and must never answer with a picker snapshot up to 15 minutes stale. The TTL cache moved to two new entry points, GetDistinctWaitTypesForPickerAsync and GetDistinctPerfmonCountersForPickerAsync, which only ServerTab's picker refresh calls now. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3 * Add a live multi-instance Perfmon test for the 12-17x interval bug PerfmonIntervalAggregationTests pins the inner MAX(sample_interval_seconds) in source text, but no live test seeded more than one instance row per collection, so a regression turning that MAX into a SUM would have passed every DuckDB test in the file. Adds two: a single multi-instance collection (12 rows) landing alone in its bucket, and three such collections merged into one bucket, both asserting the bucket's DeltaValue/SampleIntervalSeconds land on the ruling's summed-deltas-over-summed-intervals rate. Proven by hand: reverting the inner MAX to SUM fails exactly these two tests. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3 * Bucket Lite's memory clerk trend and cache its picker's name list (#4234) GetMemoryClerkTrendsByTypesAsync now buckets to TrendBudget.Chart's per-series point budget, the same GREATEST(time_bucket(...), $2) shape PR #4331 gave GetWaitStatsTrendsByTypesAsync / GetPerfmonTrendsByCountersAsync. A clerk's memory is a gauge, so a bucket is the plain AVG(memory_mb) of its collections, with no rated/unrated split. GetDistinctMemoryClerkTypesForPickerAsync wraps the existing uncached read with LiteNameListCache, exactly like GetDistinctWaitTypesForPickerAsync, so the clerk picker's full-window DISTINCT runs at most once per 15 minutes instead of on every 1-minute auto-refresh. Only the picker's two callers in ServerTab.Refresh.cs move to it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3 * Bucket Lite's File I/O latency and throughput trend reads (#4234) GetFileIoLatencyTrendAsync, GetFileIoThroughputTrendAsync and GetTempDbFileIoTrendAsync now bucket to TrendBudget.Chart's per-series point budget, the GREATEST(time_bucket(...), $2) shape PR #4331 gave the wait/perfmon trend reads. Latency stays a ratio (summed stall over summed operations, over rated rows only); throughput stays a rate (summed bytes over summed rated seconds). An unrated collection alone in a bucket still leaves that point absent (HAVING drops the bucket), same as before. The top-10-files ranking in the latency and throughput queries stays an unbucketed whole-window scan, unchanged. These reads feed the File I/O tab charts and the Overview correlated timeline's I/O lane (both its live and reference-period fetches), which read GetFileIoLatencyTrendAsync directly and need no changes of their own. Also fixes a stale comment in McpIoTools.cs that called the desktop chart's read "untouched" — it is bucketed now too, on its own budget, separately from the MCP tool's own GetFileIoTrendAsync. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3 * Add regression tests for Lite's clerk and File I/O bucketing (#4234) Source-check tests pin each new SQL statement's bucket-width parameter and first_collection_time/collection_count projection. Live DuckDB tests cover the 7-day row-budget cap, point equality when every bucket holds one collection, an unrated collection alone leaving its bucket absent, a merged bucket using summed-over-summed (not a mean of per-collection ratios/rates), and the clerk picker's TTL cache (hit inside 15 minutes, miss past it, shared read never caches). Proven against the pre-#4234 shape: none of these tests can even compile against it, since GetDistinctMemoryClerkTypesForPickerAsync, MemoryClerkTrendsSql, FileIoLatencyTrendSql, FileIoThroughputTrendSql and TempDbFileIoTrendSql did not exist before this branch. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3 * Fix doc-comment displacement: move 4 method summaries back above their methods CI's DocCommentHygieneTests.NoMemberCarriesTwoStackedSummaryBlocks caught four method summaries stranded above the SQL-text members this PR pulled out of each method. Each stranded block is the METHOD's own summary; the SQL member already had its own summary directly below it, leaving the method itself undocumented. Moved each block verbatim to directly above its method, after the SQL member's closing line and the blank line: - LocalDataService.FileIo.cs: GetFileIoLatencyTrendAsync, GetFileIoThroughputTrendAsync, GetTempDbFileIoTrendAsync - LocalDataService.Memory.cs: GetMemoryClerkTrendsByTypesAsync No wording changed, no lines added or removed beyond the move itself. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3 --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Part of #4234.
Why
Lite's Wait Stats and Perfmon trend charts read one row per collection over the whole window. At 7
days that is 196,800 rows for 20 wait types and 118,080 rows for 12 counters (#4234's measured
numbers). The two picker reads (
GetDistinctWaitTypesAsync,GetDistinctPerfmonCountersAsync) alsoran a full-window
DISTINCTon every 1-minute auto-refresh. This is the Lite twin of PR #4304 (theDarling/WPF viewer side, now merged to dev), applying the same #4234 ruling to Lite's DuckDB reads.
What changes
Lite/Services/LocalDataService.WaitStats.cs:GetWaitStatsTrendsByTypesAsyncnow buckets toTrendBudget.Chart's point budget PER SERIES via a newratedCTE (summed wait/signal over summedrated seconds,
avg_ms_per_waitas summed wait over summed waiting tasks) grouped ontime_bucket(...). When every bucket the whole call returns holds exactly one physical collection,points are stamped at that collection's own raw
first_collection_time; a single merged bucketanywhere keeps the
bucket_startgrid line throughout. The SQL text moved into a new internal staticWaitTrendsSql(waitTypeCount)so its shape is checkable without a live DuckDB, mirroringViewerDataService.WaitTrendsSql.Lite/Services/LocalDataService.Perfmon.cs:GetPerfmonTrendsByCountersAsyncnow wraps theunchanged per-collection SUM as a subquery and re-aggregates into buckets:
Valueis the bucket'srounded gauge average,
DeltaValue/SampleIntervalSecondsare summed only over rated collections sotheir ratio downstream stays the ruling's rate,
CntrTypekeeps the per-collection MIN=MAX rule nowdouble-aggregated. Same singleton-bucket raw-timestamp rule and same
PerfmonTrendsSql(counterCount)extraction.
Lite/Services/LiteNameListCache.cs(new): a small TTL cache — per (server, window length), 15minutes, a hit also requires the cached window's end to sit within the TTL of the requested end —
ported from Darling's
ViewerNameListCache.GetDistinctWaitTypesAsyncandGetDistinctPerfmonCountersAsynceach get their own cache instance, but see the T3b2 section below:the cache check and the
nowUtcclock seam ended up on new picker-only wrapper methods instead of onthese two shared ones. A later lane can reuse this cache for the memory-clerk picker.
Lite.Tests/PerfmonIntervalAggregationTests.cs: two pre-WPF trend charts never adopted the #3897/#3960 bucket contract: at the "Last 7 days" window one chart reads 85–170 k raw rows, and the pickers re-run a week-long DISTINCT every refresh #4234 source-census pins asserted exacttextual counts of
SUM(sample_interval_seconds)/SUM(delta_cntr_value)across the whole file. Thenew bucket-level aggregation legitimately adds one more of each (summing each collection's own
already-aggregated value across the collections in a bucket — the ruling's rate, not the original
12-17x-inflation bug the pins guard against). Narrowed/updated rather than weakened: see the commit
and in-file comments for exactly what each pin now allows and why.
Both
Get*ByTypesAsync/Get*ByCountersAsyncreadcollection_timeas UTC already (GetTimeRangewithout
fromDate/toDateusesDateTime.UtcNowdirectly, "since collection_time is stored in UTC"per its own comment) — bucketing keeps that, no timezone handling changed.
Diagnosis check
Confirmed by reading the pre-#4234 method bodies directly (both were plain per-collection
SELECT ... GROUP BY counter_name/wait_type, collection_timewith no bucketing of any kind) ratherthan re-measuring row counts against a live store — the brief pointed at #4234's body and
issuecomment-5835625728 for those numbers rather than asking this lane to re-measure them.
A real regression caught and fixed in-lane
DuckDB promotes
SUM()over anINTEGERcolumn toHUGEINT, which DuckDB.NET hands back as a boxedBigInteger;Convert.ToInt64/Convert.ToInt32and the reader's typedGetInt64/GetDoubleaccessors can't cast that.
PerfmonCounterTypeReadTests(an existing test, not one I added) caughtthis immediately against the new
SUM(sample_interval_seconds) FILTER (...)column. Fixed by routingevery new aggregate-derived column through the existing
ToInt64/ToDoublehelpers, matchingGetWaitBucketsAsync/GetPerfmonBucketsAsync's own established pattern for the same FILTER-summedshape. Caught and fixed before the full-suite run, in the same PR.
T3b2: three review follow-ups, all decided by ruling
GetDistinctWaitTypesAsync(
Lite/Services/LocalDataService.WaitStats.cs) andGetDistinctPerfmonCountersAsync(
LocalDataService.Perfmon.cs) went back to their dev shape — uncached, nonowUtcparameter —because
McpWaitTools.cs/McpPerfmonTools.cscall them directly and must never answer with apicker snapshot up to 15 minutes stale. The TTL cache moved onto two new entry points,
GetDistinctWaitTypesForPickerAsyncandGetDistinctPerfmonCountersForPickerAsync, which onlyServerTab.Refresh.cs's wait-type/perfmon-counter picker refresh calls now. Updated the twoexisting cache-TTL tests to call the picker methods (only they take
nowUtc), and added one testper list proving the shared method never caches: two back-to-back calls with a new name seeded
between them, and the second call sees it.
PerfmonIntervalAggregationTestspins the innerMAX(sample_interval_seconds)in source text, but no live test seeded more than one instance rowper collection, so a regression turning that inner MAX into a SUM would have passed every DuckDB
test in the file. Added two tests to
LiteWaitPerfmonTrendBucketingTests.cs, both Transactions/sec-shaped with 12 instance rows per collection (delta 100, interval 300 each): one collection alone in
its bucket returns
DeltaValue1,200 /SampleIntervalSeconds300 (not 3,600 — the 12x-inflationshape); three collections merged into one bucket return 3,600 / 900, still 4/second regardless of
how many collections merged. Proven by hand: reverting the inner
MAXtoSUMinPerfmonTrendsSql's inner CTE fails exactly these two new tests and no others (10/10 passed beforethe revert, 8/10 after, reverted back afterward).
ServerTab.Pickers.cs'sDeltaSample/DeltaSeriesShaping.Shape(PerformanceMonitor.Common/DeltaSeriesShaping.cs). Thedecisive line is 220:
if (s.Delta is not long delta || s.IntervalSeconds == 0)→NaN(no ratepoint), checked before any basis-specific branch. dev's raw pass-through for a lone unrated
collection (a real, non-null
delta_cntr_valuepaired withsample_interval_secondsliterally0)hits this via the
IntervalSeconds == 0arm;PerfmonTrendsSql's FILTER-drivenNULL/NULLforthat same shape hits it via the
Delta is not longarm instead. Both land on the sameNaN, forevery basis except
Level(a gauge), which reads onlyValueand never touchesDelta/IntervalSecondsat all. So the two shapes are not distinguishable downstream — no code change, nonew test (the brief's own fallback: pin only the branch where they diverge).
Test plan (T3b2 addendum)
Lite.Tests/Lite.Tests.csprojbuilds 0 Warning(s)/0 Error(s) after each of the three items above.LiteWaitPerfmonTrendBucketingTests.cs's three classes, run by exact class name: 18 total (3LiteTrendBucketWidthSqlTests+ 5LiteNameListCacheTests+ 10LiteTrendBucketingLiveTests,including the 2 renamed picker-cache tests, the 2 new shared-method-never-caches tests, and the 2
new multi-instance tests), 0 failed.
PerfmonIntervalAggregationTests(4),PerformanceMonitorLite.Tests.PerfmonCounterTypeReadTests(2),
McpStatusEnvelopeTests(9) — 0 failed. (*McpWait*/*McpPerfmon*as literal class-namewildcards matched 0 classes; no test file in
Lite.Testshas "McpWait" or "McpPerfmon" in a classname today.
McpWaitTools.cs/McpPerfmonTools.csthemselves are untouched by item 1 — samemethod names, same call shape — so this is a naming mismatch in the brief, not a gap; the full
suite below covers whatever does exercise those tool classes under their actual names.)
origin/dev(brought in PR Bucket the Darling viewer's File I/O and memory clerk trend charts (#4234) #4329, the Darling viewer's File I/O/memory bucketing, andLite Overview baseline bands use the server's actual hour under a custom range (#4320) #4325's baseline-band fix; unrelated to this lane), rebuilt clean, ran the FULL
Lite.Tests.exesuite once: 5,433 total, 0 errors, 0 failed, 0 skipped.
Test plan
Lite/PerformanceMonitorLite.csprojbuilds with 0 Warning(s), 0 Error(s).Lite.Tests/Lite.Tests.csprojbuilds with 0 Warning(s), 0 Error(s).Lite.Tests/LiteWaitPerfmonTrendBucketingTests.cs(14 tests, all against embedded DuckDB orin-memory, no live SQL Server/Postgres): source checks that
WaitTrendsSql/PerfmonTrendsSqlcarry a
time_bucketwidth plusfirst_collection_time/collection_count;LiteNameListCachehit/miss unit tests (ported from Darling's
ViewerNameListCacheTests); a 7-day, one-collection-per-minute seed returns at most
TrendBudget.Chart.AutoPoints * seriesCountrows for both reads;three collections seeded off the minute grid (
:37seconds) come back with raw timestamps andvalues unchanged, not floored to the bucket grid, for both reads; both pickers' cache proof (a
second call inside 15 minutes misses a wait type/counter the store gained since that call, a call
past the TTL picks it up).
file's own top-of-file comment): the old method bodies had no
time_bucket,first_collection_timeorcollection_countanywhere, so the source checks fail there outright;the old per-collection read returns one row per collection (10,080 for the 7-day one-per-minute
seed), over the budget the cap tests assert.
name (not file name, per the skill's "filter by every class" rule):
DeltaFamilyUnknowableRowReadTests(12),PerfmonCounterTypeTests(17, two classes in that onefile —
PerfmonCounterTypeTestsandPerfmonCounterTypeReadTests),PerfmonIntervalAggregationTests(4, after the pin fix). All pass.
Lite.Tests.exesuite, run once after mergingorigin/dev: 5424 total, 0 errors, 0failed (first run found 2 failures in
PerfmonIntervalAggregationTests, both fixed andindividually re-verified as described above; not re-run at full scale a second time given this
lane's context budget, but the fix is narrow, mechanical, and independently verified).
Lite.Testshas noDocCommentHygiene*class (that pin is Darling-only); nothing to run there.CI fix
The CI run at
29411369failed two Darling tests. The product code is unchanged;c7f6e21c(after a merge oforigin/dev) fixes both. Report: PR comment 5837999969.DocCommentHygieneTests.NoMemberCarriesTwoStackedSummaryBlocks: the newPerfmonTrendsSqlandWaitTrendsSqlmembers had been inserted between an existing method's<summary>and the method. Each "Batched sibling of ..." block moved back to its own method (GetPerfmonTrendsByCountersAsync,GetWaitStatsTrendsByTypesAsync); the moved lines are unchanged.PerfmonCounterTypeRungTests: Lite'sLocalDataService.Perfmon.csnow holds the agreed counter-type expression three times, not two: once in the single-counter read and twice in the bucketed batched read (per collection inside, per bucket outside), as both Darling twins do. The pin says 3 and why.CHANGELOG entry
SECTION: Changed
ENTRY:
REF:
[Bucket Lite's Wait Stats and Perfmon trend and picker reads (#4234) #4331]: Bucket Lite's Wait Stats and Perfmon trend and picker reads (#4234) #4331
For the coordinator to double-check
GetWaitStatsTrendsByTypesAsync,GetDistinctWaitTypesAsync,GetPerfmonTrendsByCountersAsync,GetDistinctPerfmonCountersAsync).Lanes T2a/T2b cover the rest of WPF trend charts never adopted the #3897/#3960 bucket contract: at the "Last 7 days" window one chart reads 85–170 k raw rows, and the pickers re-run a week-long DISTINCT every refresh #4234; this PR does not close the issue.
origin/devalready carries PR WPF Wait Stats and Perfmon trend charts bucket server-side (#4234) #4304 (merged during this lane's run) and itsViewerNameListCache/ViewerTrendBucketingReviewTests; this branch was merged withorigin/devafter that landed, rebuilt, and re-tested clean.
PerfmonIntervalAggregationTests's two updated pins (SUM(delta_cntr_value)now 3, not 2;
SUM(sample_interval_seconds)check now strips the FILTER-guarded bucketed formbefore asserting none remains) — the reasoning is in the commit message and in-file doc comments,
but it's a pin that guards against a real, previously-shipped bug (12-17x denominator inflation), so
it deserves a deliberate look rather than a rubber stamp.
🤖 Generated with Claude Code
https://claude.ai/code/session_01FVjn4PBJN71NQXdFo6ZxNQ