Repository navigation
Bucket Lite's memory clerk and File I/O trend reads (#4234) - #4340
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
) 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
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
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
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
…r 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
|
Fixed the four
All four moved line ranges matched what the CI failure and the brief pointed at exactly. One commit, Builds: Tests:
No live test run (no rig requested). No full suite run. No Installer.Tests run. |
* 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.
…trends (#4349) Mirrors #4353's ViewerCpuTempDbTests and #4340's LiteMemoryFileIoTrendBucketingTests shape: a source check per read (bucket width, origin, first_collection_time/ collection_count columns, gauge averaging), a 7-day row-budget cap against a real store (bulk-seeded at each collector's own cadence), and point equality (a merged bucket's averaged value against the hand-computed mean). Session Stats also pins claude-desktop's ruling addition: a bucket containing two collections carries the NEWER collection's top_application_name/top_host_name (and their counts), not a blended or oldest answer, on both Darling (DISTINCT ON) and Lite (QUALIFY ROW_NUMBER()). Refs #4349
…double-count Adds source-check and live-store tests for the lock-wait, waiting-task and blocked-session trend reads, mirroring ViewerCpuTempDbTests (#4234) and LiteMemoryFileIoTrendBucketingTests (#4340): a 7-day row-budget cap, point equality for the rate/count formulas, and singleton-window raw-timestamp stamping, for both products. Fixes a defect the tests surfaced: BlockedSessionTrendSql/its Lite twin summed a per-snapshot gauge (COUNT(*) blocked sessions) across every merged collection in a bucket, double- (or N-tuple-) counting purely because a wide bucket merged more snapshots, with no more blocking having happened. Both reads now average the per-collection count over the merged bucket (matching the CPU tab's gauge-averaging idiom), via a per_collection CTE that keeps the un-bucketed read's own count unchanged. Refs #4349.
…essions (#4349) (#4362) * Bucket the blocking-trend reads: lock waits, waiting tasks, blocked sessions (#4234) (#4349) GetLockWaitTrendAsync, GetWaitingTaskTrendAsync, and GetBlockedSessionTrendAsync returned one row per collection for both products, unbucketed at the 7-day window unlike #4353's (Darling) and #4340's (Lite) already-bucketed twins for the other trend charts. Each now buckets to TrendBudget.Chart's per-series point budget via TrendBuckets.AutoMinutes (date_bin on Darling, time_bucket on Lite, both anchored to the shared bucket origin), stamps points at their own raw collection time instead of the bucket grid when every returned bucket holds exactly one physical collection, and carries the #3540 unrated-collection rule for the lock-wait rate (nulled into a rated CTE, not filtered, so collection_count still counts an unrated row). Waiting-task duration and blocked-session count have no interval-based delta to rate (a waiting-task row is a point-in-time snapshot, not a counter), so every row in those two reads is unconditionally "rated" and collection_count is a plain COUNT(*). Darling: ViewerDataService.BlockingTrends.cs. Lite: LocalDataService.Blocking.cs, LocalDataService.WaitingTasks.cs. * Add #4349 bucketing tests for the blocking trio; fix blocked-session double-count Adds source-check and live-store tests for the lock-wait, waiting-task and blocked-session trend reads, mirroring ViewerCpuTempDbTests (#4234) and LiteMemoryFileIoTrendBucketingTests (#4340): a 7-day row-budget cap, point equality for the rate/count formulas, and singleton-window raw-timestamp stamping, for both products. Fixes a defect the tests surfaced: BlockedSessionTrendSql/its Lite twin summed a per-snapshot gauge (COUNT(*) blocked sessions) across every merged collection in a bucket, double- (or N-tuple-) counting purely because a wide bucket merged more snapshots, with no more blocking having happened. Both reads now average the per-collection count over the merged bucket (matching the CPU tab's gauge-averaging idiom), via a per_collection CTE that keeps the un-bucketed read's own count unchanged. Refs #4349. * Fix two #4349 test pins: outer-select scope and the rated-CTE rewrite (#4349) BlockedSessionTrendSql_CarriesABucketWidth test asserted no COUNT(*) AS blocked_count anywhere in the SQL, but the inner per_collection CTE legitimately keeps that per-snapshot COUNT(*) - it's what the outer AVG averages. Scoped the assertion to the outer SELECT (after the CTE closes) instead of the whole SQL text. LockWaitTrendSql_FiltersLckPrefix_PerSecondRateViaLag pinned the old per-row "delta_wait_time_ms / interval_seconds" text. The bucketed SQL now computes the same per-second rate through a time-weighted rated CTE (SUM(rated_wait_ms) / SUM(rated_seconds)), matching WaitStatsTrendsSql's existing idiom. Updated the pin to the new text and kept every behavioural assertion. * Fix CRLF-sensitive outer-SELECT locator in blocking trend bucket test * Fix outer-SELECT locator: raw-string dedent strips leading indentation
…4349) (#4361) * Bucket the CPU scheduler, session stats, and plan cache trend reads (#4234) CpuSchedulerTrendSql, SessionStatsSql, and PlanCacheTrendSql returned one row per collection, unbounded over the window, unlike #4353/#4340's already-bucketed twins. Measured (issue #4234): CPU scheduler pressure 10,080 rows over 7 days at 1-minute cadence; Sessions 2,016 and Plan cache 2,016 at 5-minute cadence (plan cache: two objtype groups per collection, summed before bucketing). All three read shapes are point-in-time gauges (no delta math), so each new date_bin/time_bucket bucket averages its collections' values, following the #4234 ruling. Session Stats also carries four non-averageable attribution columns (top application/host name and their connection counts); those come from the bucket's LAST physical collection (DISTINCT ON / QUALIFY newest-first) rather than any blended or dropped answer. GetCpuSchedulerTrendAsync, GetSessionStatsAsync, and GetPlanCacheTrendAsync now compute their bucket width from TrendBuckets.AutoMinutes and stamp points at their own raw collection time instead of the bucket grid when every returned bucket is a singleton, matching GetTempDbFileIoTrendAsync's rule. Lite's three DuckDB twins get the same treatment with time_bucket/to_minutes. No chart-consumer changes needed: all three reads already take (startUtc, endUtc)/(fromDate, toDate) and none of the three chart callers post-filtered on the client side. * Add bucketing tests for CPU scheduler, Session Stats, and Plan Cache trends (#4349) Mirrors #4353's ViewerCpuTempDbTests and #4340's LiteMemoryFileIoTrendBucketingTests shape: a source check per read (bucket width, origin, first_collection_time/ collection_count columns, gauge averaging), a 7-day row-budget cap against a real store (bulk-seeded at each collector's own cadence), and point equality (a merged bucket's averaged value against the hand-computed mean). Session Stats also pins claude-desktop's ruling addition: a bucket containing two collections carries the NEWER collection's top_application_name/top_host_name (and their counts), not a blended or oldest answer, on both Darling (DISTINCT ON) and Lite (QUALIFY ROW_NUMBER()). Refs #4349 * Update three stale trend-order pins for the #4234/#4349 bucketed reads Each pin asserted the pre-bucketing raw-row ORDER BY (collection_time). The rewritten trend reads bucket first, then order by bucket_start; the assertions now pin that shape instead of a literal that no longer appears in the SQL. GROUP BY collection_time still holds inside each per-collection/raw CTE and is left in place. * Normalize line endings before the GROUP BY/ORDER BY substring assert in the #4349 trend-bucket pins * Assert GROUP BY/ORDER BY order in the trend-bucket pins independent of raw-string indentation
…4364) * Bucket the memory-grant and TempDB usage trend reads (#4234) MemoryGrantTrendSql, MemoryGrantChartDataSql (Darling) and their Lite twins GetMemoryGrantTrendAsync/GetMemoryGrantChartDataAsync returned one row per collection (or per collection+pool) for the 7-day window, unbucketed like the reads #4353 and #4340 already fixed. TempDbTrendSql (Darling) and Lite's GetTempDbTrendAsync were the same, plus start-only on the window end. All four now bucket to TrendBudget.Chart's point budget with the GREATEST(date_bin/time_bucket(...), window start) shape #4234's other PRs use: every gauge (sizing MB, grantee/waiter counts, tempdb space) averages per bucket; the two true deltas in the grant chart (timeout_error_count_delta/forced_grant_count_delta) sum over a rated CTE that nulls (not drops) an unrated row per #3540. TempDB's top_session_id/top_session_tempdb_mb are not averaged (a session ID average is meaningless) -- both take the bucket's last raw collection's pair. A point is stamped at its bucket's raw first_collection_time only when every bucket the call returned holds exactly one physical collection. GetTempDbTrendAsync (Darling) now takes (serverId, startUtc, endUtc) like the other bucketed reads; LoadTempDbAsync passes endUtc through and drops the now-redundant client-side post-filter, matching #4353's fileIo change to the same method. * Add bucketing tests for memory-grant/tempdb trend reads (#4349); fix TempDbTrendAsync reader-reuse bug * Route memory-grant chart's derived interval_seconds through NULLIF (#4349); update stale per-collection pins to the bucketed shape Fixes item 4 (Lite/Darling MeasurementContractCensusTests): MemoryGrantChartDataSql's per_collection CTE aliased MAX(sample_interval_seconds) AS interval_seconds bare, so the rated CTE's IS DISTINCT FROM 0 test read the value outside NULLIF -- the census's rule-1 shape. Wrapped in NULLIF(...,0) like every other derived interval_seconds alias on the tree; the rated CASE now tests IS NOT NULL (equivalent once the value is NULLIF'd). Same fix in Lite's MemoryGrants.cs mirror. Also fixes items 1-2 (ViewerMemorySqlTests.MemoryGrantChartDataSql_GroupsPerPool.../MemoryGrantTrendSql_SumsGrantedMb...): the pins asserted the pre-#4349 per-collection-only text (bare GROUP BY / ORDER BY collection_time). Updated to assert the surviving per_collection shape PLUS the new outer bucket (date_bin, AVG, GROUP BY pool_id/1, MIN/COUNT singleton columns) -- every prior behavioral assertion (MB cast to double, counts to bigint, grouping) kept. * Resolve #4364's MCP/viewer parity pin: extract the shared per-collection read (#3548, #4349) Both DarlingTrendReader.MemoryGrantTrendSql (MCP) and ViewerDataService.MemoryGrantTrendSql (viewer) now build on TrendBucketSql.MemoryGrantPerCollectionSql, the byte-for-byte shared per-collection CTE body #3548's one-shared-read doctrine requires. Each side keeps its own outer bucketing wrapper: MCP's unbucketed read (fed into its own MemoryGrantTrendBucketedSql) is unchanged in behavior, and the viewer's bucketed overlay is unchanged in behavior. The pin now asserts both SKUs' SQL contains the shared constant, and that MCP's read is exactly the shared constant plus its trailing ORDER BY. No Lite MCP/viewer parity pin for this read exists (Lite's get_memory_trend calls GetGrantBucketsAsync, which already shares Lite's own DuckDB SQL with the chart — no duplicate string to pin). * Fix Memory Grants chart: SUM true deltas per bucket, don't average them (#4349) The outer bucketed select in MemoryGrantChartDataSql (Darling) averaged timeout_error_count_delta and forced_grant_count_delta across a bucket's merged collections instead of summing them. Those two columns are true accumulating event counts, not gauges - averaging them silently drops real events (2 collections with deltas 3 and 4 averaged to 3.5, CAST to bigint rounded to 4, and small counts like a single-digit total rounded to 0). Fixed both the per_collection CTE (CAST(SUM(...) AS bigint), matching dev's pre-bucket shape) and the outer rated-CTE aggregate (CAST(SUM(...) AS bigint) instead of the implicit SUM-without-cast that the pin expected, and no longer AVG). Lite's twin (LocalDataService.MemoryGrants.cs) was already correct - it already summed the rated deltas at the outer level; no change needed there. The memory-grant overlay (MemoryGrantTrendSql) and TempDbTrendSql carry no delta/counter columns (only gauges), so no fix was needed on those sibling reads. Verified with a throwaway net10.0 harness against a live TimescaleDB container reproducing the exact per_collection/rated CTE shape: the old outer-AVG form returned 4 for two merged collections with deltas 3 and 4 (matching the CI failure's Expected 7 / Actual 0 pattern - a larger merge of ~10 collections carrying one 7-count event averaged to <1, rounding to 0); the fixed outer-SUM form returns 7. Added a merged-bucket live pin (MemoryGrantChart_MergedBucket_SumsDeltas_AveragesGauges_AgainstDevPostgres) asserting gauges AVERAGE and deltas SUM when two collections merge into one bucket, plus a SQL-shape assertion that the outer select uses CAST(SUM(rated_*_delta) AS bigint), never AVG. * Fix Memory Grants chart's unknown-interval delta drop; redesign merged-bucket pin (#4364) The NULLIF(sample_interval_seconds, 0) guard collapses a genuine restart's known-zero interval and a pre-V128 row's true NULL (never recorded) into the same NULL, so the rated CTE's IS NOT NULL test dropped BOTH cases' deltas instead of only the restart's. Track the raw MAX(sample_interval_seconds) alongside the NULLIF alias and rate off IS DISTINCT FROM 0 against the raw value, so an unknown interval keeps its delta while a known-zero interval still nulls it -- Lite's twin gets the same fix. Also fixes the merged-bucket live pin: two collections 7 days apart do not share a date_bin bucket at a 7-day window's auto-chosen width (tens of minutes), so the pin never exercised the merge it claimed to test. Both collections now land in the same floored minute instead. * Anchor the merged-bucket test seeds to a minute boundary (#4349)
Part of #4234.
This branch was built on top of #4331, which has now merged to dev (
fa05e9fd). The merge oforigin/dev(fd1fdb20) took dev's copy ofLocalDataService.WaitStats.csandLocalDataService.Perfmon.cs, which this PR does not change. The diff now shows only this PR's files:Lite/Services/LocalDataService.Memory.cs,Lite/Services/LocalDataService.FileIo.cs,Lite/Controls/ServerTab.Refresh.cs,Lite/Mcp/McpIoTools.cs, and the newLite.Tests/LiteMemoryFileIoTrendBucketingTests.cs.Why
Same ruling as #4331/#4304: Lite's memory-clerk and File I/O charts ran one query per collection over the whole visible window, so a 7-day view could return thousands of points per file/clerk-type series. This bucket those reads to the chart's point budget, the same
GREATEST(time_bucket(...), $2)shape #4331 gave the wait-stats and perfmon reads, and caches the memory-clerk picker's name list the same way #4331 cached the wait-type picker's.What changes
GetDistinctMemoryClerkTypesForPickerAsync(new): wraps the existing uncachedGetDistinctMemoryClerkTypesAsyncwithLiteNameListCache, keyed on (server, window length), TTL 15 minutes — exactlyGetDistinctWaitTypesForPickerAsync's shape. Only the two picker call sites inServerTab.Refresh.cs(sub-tab refresh and full refresh) move to it; the shared read stays uncached for any future MCP caller.GetMemoryClerkTrendsByTypesAsync: now buckets via a newMemoryClerkTrendsSql(clerkTypeCount). A clerk's memory is a gauge, so a bucket is the plainAVG(memory_mb)of its collections — no rated/unrated split, noHAVING, since Measurement-layer campaign: the delta honesty contract (11 findings, one keystone) #3540 only marks delta rows.GetFileIoLatencyTrendAsync: buckets via a newFileIoLatencyTrendSql. The top-10-files ranking (top_files) stays an unbucketed whole-window scan, unchanged. AratedCTE nulls (not filters) an unrated row's read/write/stall contributions, so an unrated collection still counts towardcollection_count;HAVING COUNT(rated_reads) > 0drops a bucket with no rated row. A bucket's latency isSUM(stall) / SUM(operations), never a mean of the collections' own ratios. This read also feeds the Overview correlated timeline's I/O lane (CorrelatedTimelineLanesControl.xaml.cs, both the live fetch and the comparison-period fetch) — that control just groups the already-bucketed points byCollectionTimeand averages across files client-side, so it needed no changes of its own.GetFileIoThroughputTrendAsync: buckets via a newFileIoThroughputTrendSql. The LAG-derived interval (falling back for pre-v60 rows with no storedsample_interval_seconds) is still computed once over the raw per-collection rows, before bucketing. A bucket's rate isSUM(bytes) / SUM(rated seconds), time-weighted, never a mean of per-collection rates.GetTempDbFileIoTrendAsync: buckets the same way as the latency read (ownTempDbFileIoTrendSql) since it feedsUpdateTempDbFileIoChartover the same windows. No top-10 ranking pass (tempdb's file count is already small) and no queued-latency columns (this chart never drew them). Preserves a pre-existing quirk exactly: the reader maps the SQL'sfile_namecolumn intoFileIoTrendPoint.DatabaseName, because that's the fieldUpdateTempDbFileIoChartgroups its series on — documented in the new doc comment rather than changed, to keep this PR to bucketing only.first_collection_time; otherwisebucket_start" singleton rule Bucket Lite's Wait Stats and Perfmon trend and picker reads (#4234) #4331 established.McpIoTools.cscalled the desktop chart'sGetFileIoLatencyTrendAsync"untouched: a chart wants every collection" — true before this PR, false after. Updated it to say the chart read is bucketed too now, on its own budget, separate from the MCP tool's ownGetFileIoTrendAsync.I left the
FileIoTrendPoint.DatabaseName-holds-file_namenaming quirk inGetTempDbFileIoTrendAsyncalone rather than renaming it — fixing that touches a DTO shared with the top-10 latency read (whereDatabaseNameis used correctly) and a chart grouping call, and is unrelated to bucketing. Flagging it here rather than filing a separate issue, since it's cosmetic (the data is correct, just confusingly named) and not worth a queue entry.Test plan
dotnet build Lite.Tests/Lite.Tests.csproj: 0 Warning(s), 0 Error(s), both before and after theorigin/devmerge.Lite.Tests/LiteMemoryFileIoTrendBucketingTests.cs(18 tests): source checks that each of the four new SQL statements carries its bucket-width parameter and projectsfirst_collection_time/collection_count; live-DuckDB tests for the 7-day row-budget cap (one per read), point equality when every bucket holds one collection seeded off the minute grid (:37seconds), an unrated collection alone in a bucket leaving that point absent (latency and throughput), a merged bucket provingSUM/SUMrather than a mean of per-collection ratios/rates (latency and throughput), and the clerk picker's TTL cache (hit inside 15 minutes, miss past it, shared read never caches).LocalDataService.Memory.cs,LocalDataService.FileIo.csandServerTab.Refresh.csto theirHEAD~2(pre-bucketing) content and rebuilt with the new test file in place. It fails to compile —GetDistinctMemoryClerkTypesForPickerAsync,MemoryClerkTrendsSql,FileIoLatencyTrendSql,FileIoThroughputTrendSqlandTempDbFileIoTrendSqldon't exist on the old shape at all, which is a stronger guarantee than a runtime assertion failure would be. Restored the bucketed files from my own commits (git checkout HEAD -- <paths>) afterward and confirmed the build is clean again.*LiteMemoryFileIoTrendBucket*,*Overview*,*CorrelatedTimeline*,*Memory*,*FileIo*,*TrendBucket*,*DocCommentHygiene*— 133 total, 0 failed.origin/dev(brought in the Overview CPU/wait/memory lane bucketing from Bucket Lite's Overview CPU, wait and memory lane reads (#4234) #4337 and other unrelated work); rebuilt clean, no conflict markers left in any touched file.Lite.Tests.exe— 5455 total, 0 errors, 0 failed, 0 skipped (386.9s).Installer.Tests(excluded per lane orders), and no live SQL Server/Postgres rig (Lite only, embedded DuckDB, per the brief).CHANGELOG entry
SECTION: Changed
ENTRY:
REF:
[Bucket Lite's memory clerk and File I/O trend reads (#4234) #4340]: Bucket Lite's memory clerk and File I/O trend reads (#4234) #4340
🤖 Generated with Claude Code
https://claude.ai/code/session_01FVjn4PBJN71NQXdFo6ZxNQ