Repository navigation
WPF Wait Stats and Perfmon trend charts bucket server-side (#4234) - #4304
Merged
Merged
Conversation
Part of #4234. WaitTrendsSql and PerfmonTrendsSql now bucket every selected series to TrendBudget.Chart points via date_bin, mirroring the MCP get_wait_trend/get_perfmon_trend twins generalized to many series in one query. The picker name lists (DistinctWaitTypesSql, DistinctPerfmonCountersSql) are now memoized per (server, window length) for 15 minutes through a new ViewerNameListCache, so the full-window DISTINCT no longer reruns on every 1-minute auto-refresh. Builds with 0 warnings. Live pins, EXPLAIN measurements and the full suite run are still outstanding (see PR body). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
…list cache window-end check Two defects the coordinator found in PR #4304 (#4234 lane 1): 1. Item 3 of the ruling ("when the budget covers every collection in the window, the chart gets the raw points unchanged") was not met: the bucketed SQL always stamped points at their date_bin bucket start, never the raw collection_time, even when a bucket held exactly one physical collection. Both WaitTrendsSql and PerfmonTrendsSql now also project first_collection_time (MIN(collection_time), mirroring DurationTrendRouting.BuildBucketedRawTrendSql's own column) and collection_count (COUNT(*)) per bucket. The two C# readers buffer every row and, only when every bucket the whole call returned is a singleton, stamp each point at its bucket's first_collection_time instead of bucket_start - exactly reproducing the pre-#4234 read's timestamps when nothing was merged, and falling back to bucket_start (what the MCP twins always serve) the moment any bucket in the call merged two or more collections. 2. ViewerNameListCache keyed a hit on (server, window length) alone, so a 7-day custom range from last month could reuse this week's 7-day list. A hit now also requires the cached entry's window end to sit within the 15-minute TTL of the requested end. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
…L, MCP-twin equality Darling/Darling.Tests/ViewerTrendBucketingReviewTests.cs, covering the #4234 ruling's four required tests plus one extra equality pin: - ViewerTrendBucketWidthSqlTests: source check that WaitTrendsSql/PerfmonTrendsSql carry a bucket width and the singleton-detection columns. - ViewerNameListCacheTests: unit tests against ViewerNameListCache directly (hit within TTL, miss at 15 minutes, and the defect-2 pin - miss when the cached window's end is far from the requested end despite a fresh fetch, plus the two-sided early-end case). - ViewerTrendBucketingLiveTests (gated on DARLING_TEST_PG): the 7-day row-budget cap, point equality when the budget covers every collection (off-grid seconds, so the old date_bin-floor behavior would fail this), and an equality pin against DarlingDataReader.GetWaitBucketsAsync / DarlingTrendReader.GetPerfmonBucketsAsync at a shared explicit width. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
…r run before now WaitTrendsSql_KeepsLitesPerTypeLagPerSecondMath and PerfmonTrendsSql_SumsValueAndDelta_ButTakesTheIntervalAsMax predate #4234 and pinned the old per-collection SQL's exact text (a per-row division, ORDER BY .. collection_time, no SUM of sample_interval_seconds anywhere). T1's original #4234 commit changed both statements' shape without running the suite, so these two silently broke. Updated both to pin the new bucketed shape: the rated CTE's SUM/SUM ratios, avg_ms_per_wait's one deliberate explicit "ELSE 0" (division_by_zero guard), the outer query's SUM(...) FILTER(...) over already-per-collection-MAX'd intervals, and ORDER BY .. 2. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
… behind WaitTrendsSql and PerfmonTrendsSql each carried two stacked <summary> blocks (the pre-#4234 summary immediately followed by a second one added for the bucketing change, never merged into one). DocCommentHygieneTests.NoMemberCarriesTwoStackedSummaryBlocks caught this on the first full-suite run. Merged into one summary with <para> breaks. 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 16:55
erikdarlingdata
enabled auto-merge (squash)
September 25, 2026 16:55
erikdarlingdata
added a commit
that referenced
this pull request
Sep 25, 2026
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
erikdarlingdata
added a commit
that referenced
this pull request
Sep 25, 2026
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
erikdarlingdata
added a commit
that referenced
this pull request
Sep 25, 2026
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
This was referenced Sep 25, 2026
erikdarlingdata
added a commit
that referenced
this pull request
Sep 25, 2026
…4234) (#4329) * Bucket the File I/O latency and throughput viewer trend reads (#4234) 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 * Bucket the memory clerk viewer trend read and cache its picker (#4234) 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 * Pin the File I/O and memory clerk bucketing with source and live tests (#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 * Fix a miscalibrated byte constant in the new FileIo throughput singleton 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 --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
6 of 7 tasks
erikdarlingdata
added a commit
that referenced
this pull request
Sep 25, 2026
…4331) * 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 * Fix #4331 CI: reposition displaced doc blocks, update the pin count to 3 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 --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
erikdarlingdata
added a commit
that referenced
this pull request
Sep 25, 2026
* 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>
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. (File I/O, memory clerks, the Overview lanes, Performance Trends and Lite are later lanes.)
Why
Lane T1 built the Wait Stats and Perfmon trend bucketing and the two name-list caches (build succeeded,
0 warnings) but never stood up a rig, wrote no tests, and ran no suite. The coordinator reviewed the diff
against the #4234 ruling and found two defects before any of that verification had happened. This PR
finishes the lane: fixes both defects, writes the ruling's four required tests plus an MCP-twin equality
pin, measures on a real rig, and runs the full suite.
What was wrong
unchanged") was not met. At a width that gave each collection its own bucket,
WaitTrendsSqlandPerfmonTrendsSqlstill stamped every point at itsdate_binbucket start, never the rawcollection_time— T1's own PR description called this out as a deliberate, accepted difference("only the timestamp changes"). Proven live: seeding three collections five minutes apart with
non-zero seconds (
:37) and reading them back at the finest width returned09:00:00, 09:05:00, 09:10:00instead of the seeded09:00:37, 09:05:37, 09:10:37.ViewerNameListCachekeyed a hit on (server, window length) alone. A 7-day custom range from lastmonth could reuse this week's cached 7-day wait-type or counter list, because both windows share one
length-keyed slot and nothing checked which window was actually fetched.
What changes
WaitTrendsSql/PerfmonTrendsSqlnow also projectfirst_collection_time(MIN(collection_time),mirroring
DurationTrendRouting.BuildBucketedRawTrendSql's own column of that name) andcollection_count(COUNT(*)) per bucket.GetWaitStatsTrendsByTypesAsyncandGetPerfmonTrendsByCountersAsyncbuffer every row and, only when EVERY bucket the whole call returnedholds exactly one physical collection, stamp each point at its bucket's
first_collection_timeinsteadof
bucket_start— reproducing the 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 read's timestamps exactly when nothing was merged, andfalling back to
bucket_start(what the MCP twins always serve) the instant any bucket in the callmerged two or more collections. The MCP twins (
WaitTrendBucketedSql,PerfmonTrendBucketedSql,DurationTrendRouting.BuildBucketedRawTrendSql) carry no such switch themselves — they stamp bucketstarts at every width — so this is the least-invention reading of item 3 available: it reuses the
twins' own
first_collection_timemetadata column rather than adding a new mechanism, and the decisionis made once per whole call (not per series, not per row), which keeps it simple to state and to test.
ViewerNameListCache.Entrynow also carries the fetched window'sWindowEndUtc. A hit requires BOTHthe existing wall-clock freshness check AND the cached entry's window end to sit within the 15-minute
TTL of the requested end (
Duration(), checked both directions — a custom range's end can move earlierthan the cached one, not just later like a sliding preset's auto-refresh always does).
GetDistinctWaitTypesAsync/GetDistinctPerfmonCountersAsyncthreadendUtcthroughTryGet/Set.WaitTrendsSql_KeepsLitesPerTypeLagPerSecondMath,PerfmonTrendsSql_SumsValueAndDelta_ButTakesTheIntervalAsMax) pinned the old per-collection SQL's exacttext and were broken by T1's original bucketing commit, silently, because the suite was never run.
Fixed to pin the new bucketed shape instead of reverting the SQL.
Darling/Darling.Tests/ViewerTrendBucketingReviewTests.cs: the ruling's four required tests plusone equality pin (see Test plan).
Measurement (rig: PostgreSQL 18 + TimescaleDB, port 55973, UTC)
Seeded 7 days at the real 1-minute cadence (
CollectorScheduleDefaults) for 20 wait types (201,620 rows,close to the issue's cited 170,020) and 12 counters (120,972 rows, close to the issue's cited 102,012) on
a dedicated
probedatabase.EXPLAIN (ANALYZE, BUFFERS), old SQL viagit show origin/dev:<file>against the new:
Both trend reads cut the row count returned by about 10x at this window/width, matching the issue's
complaint that every raw collection shipped to a chart that cannot draw more points than it has pixels.
Backend execution time is NOT uniformly faster — the wait trend's new plan does more CPU work per row
(an extra
GroupAggregatelayer over the same 201,620-row scan) and measured slightly slower in-databasedespite returning 10x fewer rows; the perfmon trend measured both faster and smaller. The real win either
way is the payload actually sent to and rendered by the WPF chart, not raw backend CPU time, and that
drops by an order of magnitude on both reads. The name-list EXPLAIN is unchanged before/after, as
expected — ruling item 4 only changes how often it runs (at most once per 15 minutes now, was every
1-minute auto-refresh), never the SQL itself.
Test plan
dotnet build Darling/PerformanceMonitor.Darling.Viewer/PerformanceMonitor.Darling.Viewer.csproj -c Debug— Build succeeded, 0 Warning(s), 0 Error(s).
dotnet build Darling/Darling.Tests/Darling.Tests.csproj -c Debug— Build succeeded, 0 Warning(s),0 Error(s), after merging
origin/dev.WaitTrendsSql/PerfmonTrendsSqlcarry a bucket width (date_bin(CAST($n AS integer)...), proven once by hand to fail against the 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 SQL:git show origin/dev:.../ViewerDataService.Waits.cs(and the Perfmon twin) contain zero occurrences ofdate_bin.budget × seriesrows(
ViewerTrendBucketingLiveTests.WaitTrend_SevenDayWindow_ReturnsAtMostBudgetTimesSeriesRowsand itsPerfmon twin).
grid) AND values unchanged
(
ViewerTrendBucketingLiveTests.WaitTrend_BudgetCoversEveryCollection_ReturnsRawTimestampsAndValuesUnchangedand its Perfmon twin). Proven to fail on the pre-fix behavior by temporarily forcing
everyBucketSingleton = falseand reverting — both failed exactly as expected (09:00:00insteadof the seeded
09:00:37), confirmed passing again after revert.ViewerNameListCacheTests, unittests directly against
ViewerNameListCache— hit within TTL, miss at 15 minutes, PLUS the defect-2pin: miss when the cached window's end is far from the requested end despite a fresh fetch, and the
two-sided early-end case).
DarlingDataReader.GetWaitBucketsAsync/DarlingTrendReader.GetPerfmonBucketsAsyncat the sameexplicit width (60 minutes, multi-collection buckets, so this exercises the shared aggregation math
rather than the singleton-collapsing wrapper the point-equality pins above already cover).
*ViewerWaitStats*,*ViewerPerfmon*,*ViewerTrendBucket*,*ViewerNameListCache*): 40/40 passed against the rig.git merge origin/dev— clean, no conflicts.Darling.Tests.exerun against a freshly dropped/recreateddarlingtest, on a UTC rig, afterthe merge: Total: 14063, Errors: 0, Failed: 4, Skipped: 49, Not Run: 1 (914.8s). All 4 failures
are outside this lane's files (
TrendPayloadBudgetLiveTests,CaptureDownChunkOrderTests,ServerListAndSummaryPlanShapeTests,DarlingCliCommandsHostCheckTests— plan capture, planshape, CLI host checks; nothing that touches Wait Stats, Perfmon, or the name-list cache). A first
run (before a fix below) additionally failed
DocCommentHygieneTestsand NOTDarlingCliCommandsHostCheckTests— the failure set changed between two back-to-back runs onunchanged code, which is the signature of environmental flakiness (likely rig contention: the first
run overlapped with the measurement seeding/EXPLAIN queries below on the same PostgreSQL instance),
not a deterministic regression. Cross-checked against dev's latest COMPLETED CI Build run
(36156144337): it fails a different, also-unrelated test (
PgTargetBlockingTests), so none of my 4are freshly confirmed by CI either way — flagged here for the coordinator's census rather than
filed as issues from this lane.
DocCommentHygieneTests.NoMemberCarriesTwoStackedSummaryBlockscaught two stacked<summary>blocks T1 left behind in bothWaitTrendsSqlandPerfmonTrendsSql(the 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 #4234summary never merged with the one added for bucketing). Merged into one with
<para>breaks.Note on scope
I did not add a raw-vs-bucketed SQL switch, because none exists anywhere in this family today — the MCP
twins (
get_wait_trend,get_perfmon_trend, the duration-trend raw tier since #3897) all stampdate_binbucket starts unconditionally, at every width, and carry no "don't bucket" branch. Item 3 ismet here by reusing the twins' own
first_collection_timemetadata column as the point's stamp exactlywhen nothing was merged, which is the smallest change that satisfies the ruling's literal wording without
inventing a mechanism the rest of the codebase doesn't have.
CHANGELOG entry
SECTION: Fixed
ENTRY:
REF:
[WPF Wait Stats and Perfmon trend charts bucket server-side (#4234) #4304]: WPF Wait Stats and Perfmon trend charts bucket server-side (#4234) #4304
🤖 Generated with Claude Code
https://claude.ai/code/session_01FVjn4PBJN71NQXdFo6ZxNQ