Skip to content

Bucket Lite's Performance Trends duration/execution charts (#4234) - #4338

Merged
erikdarlingdata merged 5 commits into
devfrom
fix/4234-lite-performance-trends-buckets
Sep 25, 2026
Merged

erikdarlingdata merged 5 commits into
devfrom
fix/4234-lite-performance-trends-buckets

Conversation

@erikdarlingdata

@erikdarlingdata erikdarlingdata commented Sep 25, 2026 •

Copy link
Copy Markdown
Owner

Part of #4234.

Why

Lite's Performance Trends chart (Queries tab, "Performance Trends" sub-tab) read three series at the
per-collection tier: GetQueryDurationTrendAsync, GetProcedureDurationTrendAsync and
GetExecutionCountTrendAsync (Lite/Services/LocalDataService.QueryStats.cs), each one row per
collection over v_query_stats/v_procedure_stats. A 7-day chart on a 1-minute collection cadence
shipped thousands of rows per series to the desktop, the same defect #4234's earlier lanes already
fixed for Wait Stats, Perfmon, File I/O and memory-clerk charts. These are their only product callers
(Lite/Controls/ServerTab.Refresh.cs ~240-243 and ~318-321); the MCP tools already read their own
bucketed twins (GetBucketedQueryDurationTrendAsync / ReadBucketedDurationTrendAsync,
LocalDataService.TrendBuckets.cs), which this lane left untouched.

What changes

Lite/Services/LocalDataService.QueryStats.cs:

  • All three reads now bucket server-side, sized by TrendBuckets.AutoMinutes against
    TrendBudget.Chart.AutoPoints, computed from the read's own window (the desktop's own
    hoursBack/fromDate/toDate, so seriesCount is always 1). Public signatures are unchanged; each
    method still takes exactly the parameters it did before.
  • A bucket's rate is its RATED collections' summed work over their summed seconds, never the mean of
    the per-collection rates. A bucket with no rated collection (every collection in it a restart, or
    the window's first pre-v61 collection) still gets a row with a NULL rate rather than being dropped
    (no HAVING) — the existing per-collection MCP payload-contract campaign: the agent-facing API tells the truth (~30 defects + an 11-rule contract) #3541 A12 contract ("an unrated collection is a point
    with no rate, not a missing point"), now applied per bucket. This is a deliberate difference from
    the wait/perfmon trend reads, which drop an all-unrated bucket; these three reads already kept an
    unrated row before this change, and bucketing does not alter that.
  • Every point stamps at its bucket's start UNLESS every bucket the call returns holds exactly one
    physical collection, in which case each stamps at that collection's own raw time — so a window
    narrow enough to need no merging (in practice, most of what the chart shows today) renders exactly
    as the unbucketed read did.
  • GetQueryDurationTrendAsync and GetProcedureDurationTrendAsync now share one SQL builder,
    DurationTrendChartSql(relation, dbClause, widthParamIndex) (internal static, so a source test can
    read the text), and one row-reading body, ReadDurationTrendChartAsync. relation is one of two
    constants (v_query_stats, v_procedure_stats), never caller text. GetExecutionCountTrendAsync has
    no shared builder (it is the only one of the three projecting a single rate column) and buckets in
    place with its own ExecutionCountTrendChartSql.
  • The bucket-width parameter is appended as its OWN trailing parameter, after the dynamic
    BuildDbInClause database list, so that list's own $4.. numbering never shifts — the same choice
    the wait/perfmon trend reads made.
  • QueryTrendPoint's fields stay filled exactly as before (Value, ExecutionCount,
    ExecutionsPerSecond); the MCP-only bucket fields (FirstCollectionTime, PeakElapsedMsPerSecond,
    UnratedInBucket) are not populated for these three chart reads, matching the brief.
  • DuckDB's SUM over an INTEGER column returns HUGEINT; all bucket sums are read through the existing
    ToDouble/ToInt64 helpers, never GetDouble/GetInt64.
  • Each read's doc comment now says the chart gets bucketed points and why (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), alongside the
    existing MCP payload-contract campaign: the agent-facing API tells the truth (~30 defects + an 11-rule contract) #3541 A12 / Brains-review campaign: deferred structural residue (from #3538 / #3539 / #3540 / #3541) #3653 A11 contract explanation it already carried.

Lite.Tests/PerformanceTrendDurationBucketingTests.cs (new): 10 tests against embedded DuckDB,
covering all three reads:

  • a source check that DurationTrendChartSql/ExecutionCountTrendChartSql carry the bucket-width
    parameter and keep the database filter's own numbering when it shifts the width's slot;
  • a dense 7-day window (1,600 one-minute-apart collections, bulk-seeded in one generate_series
    insert) stays at or under TrendBudget.Chart.AutoPoints (1,500) and meaningfully compresses (fewer
    points than collections), for each of the three reads;
  • singleton 1-minute buckets reproduce the old per-collection read exactly — seeded off the minute
    grid (:37 seconds) with a database filter excluding a third collection — for each of the three
    reads;
  • a bucket merging two rated collections rates as summed work over summed seconds, not the mean of
    the two per-collection rates; an unrated collection alone in a bucket gives a NULL point (kept, not
    dropped); a bucket mixing an unrated collection with a rated one rates only the rated one — all
    three in one seeded call per read, since none of them can be a singleton-only call.

DeltaFamilyUnknowableRowReadTests (existing, untouched) still exercises these three reads at 1-minute
buckets and passes unchanged, because its fixtures space collections 5 minutes apart — each its own
singleton bucket at the auto-chosen 1-minute width for its 3-hour window.

Test plan

  • Lite.Tests/Lite.Tests.csproj builds with 0 warnings.
  • New class alone: PerformanceTrendDurationBucketingTests — 10/10 passed.
  • Regression filters (*DeltaFamilyUnknowable*, *QueryStats*, *TrendBucket*, *Duration*,
    DocCommentHygiene*) — 43/43 passed. (DocCommentHygiene only exists in Darling.Tests, so it
    matched zero tests here, as expected.)
  • Proved the new tests fail on the pre-bucketing code: swapped in origin/dev's version of
    LocalDataService.QueryStats.cs. The source-check test fails to compile (the new SQL builders
    do not exist yet). With that one test temporarily removed so the rest could build, the three
    budget tests failed (1,600 rows returned, over the 1,500-point budget) and the three
    merged-bucket tests failed (5 rows instead of 3 — one per collection, not one per bucket). The
    three singleton-bucket tests passed unchanged against the old code too, which is correct: a
    1-minute bucket holding one collection must render identically to the unbucketed read. Restored
    both files afterward.
  • Merged origin/dev (no conflicts; the merged files don't touch anything this PR changed).
  • Full Lite.Tests.exe run once after the merge: 5,425 total, 0 failed, 0 errors.
  • No live/manual run against the actual WPF Lite desktop app (Lite only uses embedded DuckDB; no
    rig was started or needed).

Scope note

Only these three reads were in this lane's brief. The MCP-facing bucketed twins in
LocalDataService.TrendBuckets.cs (GetBucketedQueryDurationTrendAsync,
GetBucketedProcedureDurationTrendAsync, ReadBucketedDurationTrendAsync) were left exactly as they
were, per the brief. Other #4234 reads (Wait Stats/Perfmon pickers, the Darling viewer's own
query/procedure/execution-count charts, Overview lanes) are other lanes' work and were not touched.

CI fix

The first CI run failed one Darling source pin, McpZeroIsAMeasurementTests.EveryDifferencedTrend_LeavesTheFirstPointUnrated_NeverZero, which reads this file and knew only the per-collection shape. The product code is unchanged. The pin now also accepts the bucketed shape's no-ELSE ... END AS rated_x columns, counts two three-state interval statements instead of three (the query and procedure trends share DurationTrendChartSql), and requires each of the two statements to null an unrated collection's seconds (be69745a, after a merge of origin/dev). Report: PR comment 5837766944, with three revert-proofs.

CHANGELOG entry

SECTION: Changed
ENTRY:

🤖 Generated with Claude Code

https://claude.ai/code/session_01FVjn4PBJN71NQXdFo6ZxNQ

erikdarlingdata and others added 5 commits September 25, 2026 13:56
GetQueryDurationTrendAsync, GetProcedureDurationTrendAsync and GetExecutionCountTrendAsync
used to return one row per collection over v_query_stats/v_procedure_stats -- a 7-day chart
on a 1-minute cadence shipped thousands of rows. Buckets them server-side, sized by
TrendBuckets.AutoMinutes against TrendBudget.Chart, the same shape the MCP twins in
LocalDataService.TrendBuckets.cs already read. A bucket's rate is its rated collections'
summed work over summed seconds (never the mean of per-collection rates); a bucket with no
rated collection keeps its row with a NULL rate rather than being dropped, matching the
existing per-collection #3541 A12 contract. Points stamp at bucket_start unless every bucket
the call returns holds exactly one physical collection, in which case each stamps at that
collection's own time.

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

Pins the new server-side bucketing in GetQueryDurationTrendAsync, GetProcedureDurationTrendAsync
and GetExecutionCountTrendAsync against embedded DuckDB: the SQL carries the bucket-width
parameter, a dense 7-day window stays under TrendBudget.Chart's point budget, singleton
1-minute buckets reproduce the old per-collection read exactly (seeded off the minute grid,
with a database filter), and a merged bucket sums rated work over rated seconds rather than
averaging per-collection rates, keeping an all-unrated bucket's row with a NULL rate while a
mixed bucket counts only its rated collection. Also makes DurationTrendChartSql and
ExecutionCountTrendChartSql internal (they were private) so this suite can read the statement
text directly.

Proved these fail against the pre-bucketing code: the source-check test fails to compile
(the new SQL builders do not exist yet), the three budget tests fail (1600 one-minute-apart
collections return 1600 rows, over the 1500-point budget), and the three merged-bucket tests
fail (5 rows instead of 3, one per collection rather than one per bucket). The three
singleton-bucket tests pass unchanged against the old code too, which is correct: a 1-minute
bucket holding exactly one collection must render identically to the unbucketed read.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
McpZeroIsAMeasurementTests.EveryDifferencedTrend_LeavesTheFirstPointUnrated_NeverZero
still expected three per-collection LocalDataService.QueryStats.cs statements rating
through `... END AS x_per_second`. #4338 merged the query and procedure duration
trends into one bucketed statement (DurationTrendChartSql) that nulls an unrated
bucket's work and seconds through no-ELSE CASEs (`END AS rated_x`) and divides the
bucket's sums instead. Widen the `rated` regex to also match `rated_\w+`, drop the
three delta-family counts to two (one merged statement replaces two), and add an
assert that both statements null `rated_seconds` the same way.

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

Copy link
Copy Markdown
Owner Author

Lane report for the CI fix (test-only pin update).

Commit: be69745a8cbca0915791024f6cb18722119cc21b on fix/4234-lite-performance-trends-buckets (was 6c4c79575959486778d38951503983e24bc4385b). Merged origin/dev first (clean, no conflicts: brought in RawChunkIntervalPlanner, ViewerQueryTrendBucketingReviewTests, OverviewLaneBucketingTests, and related trend-bucketing work from other PRs). Only Darling/Darling.Tests/McpZeroIsAMeasurementTests.cs changed, exactly as scoped: Lite/ untouched.

What changed in the pin (EveryDifferencedTrend_LeavesTheFirstPointUnrated_NeverZero):

Build: Darling/Darling.Tests/Darling.Tests.csproj -c Debug — 0 Warning(s), 0 Error(s), each of the five times it was rebuilt across the edit and the three revert-proofs.

Test totals (Darling.Tests.exe -class "*McpZeroIsAMeasurementTests*"):

  • Clean run (after the fix): Total: 20, Errors: 0, Failed: 0, Skipped: 0, Not Run: 0.
  • Final run (after restoring all three revert-proof edits): Total: 20, Errors: 0, Failed: 0, Skipped: 0, Not Run: 0, matching the clean run.

Revert-proofs (each rebuilt, run, and restored):

  1. Added ELSE 0 before one of the two END AS rated_seconds occurrences in Lite/Services/LocalDataService.QueryStats.cs (line 1282). Failed: Assert.Equal() Failure: Values differ / Expected: 2 / Actual: 1 at McpZeroIsAMeasurementTests.cs(269,0) — the new rated_seconds assert.
  2. Restored the old rated regex (\w+_per_second only, dropping the rated_\w+ alternate). Failed with the original CI message: LocalDataService.QueryStats.cs: 2 differenced statement(s) but only 0 no-ELSE rate column(s) at McpZeroIsAMeasurementTests.cs(257,0).
  3. Set the first Assert.Equal(2, ...) (the MAX(sample_interval_seconds) IS NULL count) back to 3. Failed: Assert.Equal() Failure: Values differ / Expected: 3 / Actual: 2 at McpZeroIsAMeasurementTests.cs(267,0).

Each revert-proof's target file was restored to its pre-edit content immediately after observing the failure; git status showed only the one intended test file modified before commit.

DocCommentHygiene*: Total: 77, Errors: 0, Failed: 0, Skipped: 0, Not Run: 0.

Not run: the full suite — CI runs it. No PostgreSQL rig was needed (unit tests only).

Scope: test-only pin update per brief. Did not touch Lite/, did not open/ready/edit the PR itself, did not edit CHANGELOG.md, did not run the plain-English checker (per Erik's instruction for this lane).

@erikdarlingdata
erikdarlingdata marked this pull request as ready for review September 25, 2026 18:48
@erikdarlingdata
erikdarlingdata enabled auto-merge (squash) September 25, 2026 18:48
@erikdarlingdata
erikdarlingdata merged commit 8acabdc into dev Sep 25, 2026
16 of 18 checks passed
@erikdarlingdata
erikdarlingdata deleted the fix/4234-lite-performance-trends-buckets branch September 25, 2026 19:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant