Skip to content

Lite: Queries-tab comparisons read the grid's UTC window, not a server-shifted one (#4284) - #4302

Merged
erikdarlingdata merged 2 commits into
devfrom
fix/4284-lite-comparison-utc-window
Sep 25, 2026
Merged

erikdarlingdata merged 2 commits into
devfrom
fix/4284-lite-comparison-utc-window

Conversation

@erikdarlingdata

@erikdarlingdata erikdarlingdata commented Sep 25, 2026 •

Copy link
Copy Markdown
Owner

Closes #4284.

Why

Lite stores collection_time in UTC. The Queries tab's three comparison reads
(GetQueryStatsComparisonAsync, GetProcedureStatsComparisonAsync, GetQueryStoreComparisonAsync)
compare their start and end times straight against it, with no offset conversion of their own. Two
callers handed them a server-local window instead of UTC:

  • A slicer drag converted e.StartUtc/e.EndUtc to server time (fromServer, toServer). It passed
    those to the comparison. The grid read beside it converted back to UTC.
  • A custom range built cStart/cEnd from the date pickers in server time, and
    GetComparisonRange() built its own baseline the same way.

So on a server not on UTC, the comparison read a window shifted by the server's offset. After a
slicer drag, the current window was shifted but the baseline (from a preset range) was not.

What changes

  • GetComparisonRange (Lite/Controls/ServerTab.Comparison.cs) now takes the caller's current UTC
    window and shifts it back. Yesterday shifts it 1 day, and Last week and Same day last week shift it 7 days.
    Before, it independently recomputed its own current window from UI state. The shift itself moved
    into a static ShiftComparisonRange(int selectedIndex, DateTime currentStart, DateTime currentEnd)
    helper, so a test can drive it without constructing the WPF control.
  • CompareToCombo_SelectionChanged and the six comparison call sites in ServerTab.Refresh.cs
    compute the UTC window once. They use LocalDataService.GetQueriesTabWindowUtc, the helper
    Lite MCP tools disclose a truncated query-stats window (#4231) #4279 added for the "Showing since" banner. That one tuple goes to both the comparison call and
    the banner call beside it.
  • The three OnXSlicerChanged handlers in ServerTab.Slicers.cs pass the comparison e.StartUtc/
    e.EndUtc, the same UTC bounds the banner already used. Before, they passed the server-local
    fromServer/toServer that the grid read uses.
  • IsQueryStatsComparisonActive now tests CompareToCombo.SelectedIndex directly, since
    GetComparisonRange needs a current window to call.
  • One behavior changes on purpose. After a slicer drag, the baseline is now the dragged window
    shifted back, not the whole preset range shifted back. The current and baseline windows always
    cover the same span.
  • The comparison banner's displayed baseline times (QueryStatsComparisonBanner and its two siblings) fix
    themselves. ServerTimeHelper.FormatServerTime expects UTC and adds the server's offset once.
    Before this fix, a custom-range baseline was already server-local, so the banner silently
    double-shifted it.
  • The Darling viewer's Queries tab (ViewerServerTab.QueriesComparison.cs) already passes UTC
    windows to its comparisons, so it needs no change.

Overview tab (correlated lanes) is deliberately left alone, with a filed follow-up

GetComparisonRange() had a second caller: RefreshOverviewAsync's correlated-lanes ghost-line
comparison (Tab 0). That comparison's own reads (GetCpuUtilizationAsync, GetTotalWaitTrendAsync,
and others) take fromDate/toDate in server-local time, not UTC. That is the opposite contract from
the three Queries-tab comparison reads this issue is about.

That caller is not built to use a UTC window.
So RefreshOverviewAsync now derives its own range inline, in the same
server-local basis it always used. That derivation is byte-identical to its pre-fix version. It just
reuses the pure ShiftComparisonRange day-shift arithmetic instead of duplicating it.

That leaves one pre-existing, narrower bug in place. Under a preset (non-custom) range on a server not
on UTC, RefreshOverviewAsync still hands the lanes a DateTime.UtcNow-based window. Their
server-local readers then shift it again.

A real fix needs to touch the ghost-line X-axis time-shift
math inside CorrelatedTimelineLanesControl.RefreshAsync itself. That code has no test
coverage and no Darling version to copy. Darling's viewer never ported this overlay, on purpose
(see the "Deliberately dropped for v1" comment in CorrelatedTimelineLanesControl.xaml.cs). Filed as
#4296.

Test plan

  • Lite.Tests/Lite.Tests.csproj builds with 0 warnings.
  • New Lite.Tests/ComparisonWindowUtcTests.cs. A [Theory] over UTC+5 and UTC-7 pins
    ShiftComparisonRange's day-shift: 1 and 7 days, same span, basis-agnostic. A source-scan test
    pins that every comparison call site takes a UTC bound, not fromServer/toServer or a
    cStart built from fromDate. It also pins that the six ServerTab.Refresh.cs comparison
    calls share the identical windowStart/windowEnd tuple the banner call beside them uses.
  • Updated the one existing pin this touched
    (QueryWindowTruncationTests.WindowTruncatedBannerCallSites_TakeUtcBounds_NotServerLocalOnes,
    Lite.Tests/QueryWindowTruncationTests.cs). Its regex expected the old bannerStart/
    bannerEnd names. Renamed to match windowStart/windowEnd and corrected its now-stale claim
    that the comparison call stays untouched.
  • Proved both pins fail on pre-fix code once. Reverted the three touched
    Lite/Controls/ServerTab.*.cs files to origin/dev, then reran ComparisonWindowUtcTests and
    QueryWindowTruncationTests. Both new/updated assertions failed, as expected. Restored with
    git checkout HEAD.
  • Ran the touched test classes (ComparisonWindowUtcTests, QueryWindowTruncationTests): 14
    passed, 0 failed.
  • Merged origin/dev, no conflicts. Full Lite.Tests.exe run once: 5389 total, 0 failed, 0
    errors.

CHANGELOG entry

SECTION: Fixed
ENTRY:

🤖 Generated with Claude Code

https://claude.ai/code/session_01FVjn4PBJN71NQXdFo6ZxNQ

erikdarlingdata and others added 2 commits September 25, 2026 11:12
GetQueryStatsComparisonAsync/GetProcedureStatsComparisonAsync/GetQueryStoreComparisonAsync
compare their bounds straight against UTC collection_time. A custom range and a slicer
drag both used to hand them a server-local window instead, shifting the comparison by the
server's UTC offset on any server not on UTC.

- GetComparisonRange(DateTime, DateTime) now takes the current UTC window and shifts it,
  backed by a static ShiftComparisonRange helper for testability.
- CompareToCombo_SelectionChanged and the six ServerTab.Refresh.cs call sites compute the
  UTC window once (LocalDataService.GetQueriesTabWindowUtc) and hand it to both the
  comparison and the banner.
- The three slicer handlers pass the comparison e.StartUtc/e.EndUtc instead of the
  server-local fromServer/toServer.
- RefreshOverviewAsync (correlated lanes) is decoupled from GetComparisonRange and keeps
  its byte-identical prior derivation, since its own lane reads take server-local
  fromDate/toDate, not UTC.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
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