Skip to content

Lite Overview baseline bands use the server's actual hour under a custom range (#4320) - #4325

Merged
erikdarlingdata merged 3 commits into
devfrom
fix/4320-lite-baseline-custom-range-utc
Sep 25, 2026
Merged

erikdarlingdata merged 3 commits into
devfrom
fix/4320-lite-baseline-custom-range-utc

Conversation

@erikdarlingdata

@erikdarlingdata erikdarlingdata commented Sep 25, 2026 •

Copy link
Copy Markdown
Owner

Closes #4320.

Why

Lite's Overview tab renders baseline bands (CPU, wait ms/sec, I/O latency, blocking/minute) behind the
correlated timeline lanes. CorrelatedTimelineLanesControl.RefreshAsync computed a referenceTime to key
those baseline lookups and fed it to GetBaselineForLaneAsync -> BaselineProvider.GetBaselineAsync ->
BaselineLocalClock.LocalKey, which expects a UTC DateTime and converts it to the server's local hour and
day-of-week itself.

Under a CUSTOM time range, fromDate is already server-local (ServerTab's pickers convert it before
RefreshOverviewAsync ever sees it — the same fact GetCurrentWindowServerLocal's doc comment records for
the #4296 ghost-line fix). The old code, fromDate ?? DateTime.UtcNow.AddHours(-hoursBack), passed that
server-local value straight through as if it were UTC. LocalKey then converted it to server-local A SECOND
TIME, shifting the baseline lookup by the server's own UTC offset. On a server at UTC+5, a custom range
starting at server-local 09:00 was looked up as if it were UTC 09:00, i.e. server-local 14:00 — a 5-hour
bucket error; on a UTC-7 server the same 09:00 start looked up server-local 02:00, a 7-hour error the other
way. A PRESET range was already correct: DateTime.UtcNow.AddHours(-hoursBack) is genuinely UTC, so no
conversion was needed there.

I confirmed this by reading BaselineLocalClock.LocalKey (PerformanceMonitor.Analysis/Baselines/, ~line
280) and GetBaselineForLaneAsync/BaselineProvider.GetBaselineAsync, and I did not find any other place
that re-corrects the offset before the lookup — the bug is exactly where the issue's correction comment
placed it.

What changes

  • Lite/Controls/CorrelatedTimelineLanesControl.xaml.cs: add GetBaselineReferenceTimeUtc(hoursBack, fromDate, utcNow, utcOffsetMinutes), beside the existing GetCurrentWindowServerLocal. Under a custom
    range it converts fromDate back to UTC (fromDate - utcOffsetMinutes); under a preset range it returns
    utcNow.AddHours(-hoursBack) unchanged. RefreshAsync now calls this helper instead of using fromDate
    directly.
  • RefreshAsync takes utcOffsetMinutes as an explicit parameter instead of reading
    ServerTimeHelper.UtcOffsetMinutes ambiently — matching the existing pattern on
    GetCurrentWindowServerLocal/GetOverviewComparisonRange, both of which already take utcNow/
    utcOffsetMinutes explicitly so a test can drive them deterministically.
  • Lite/Controls/ServerTab.Refresh.cs: the one caller now passes ServerTimeHelper.UtcOffsetMinutes, the
    same value it already passes to GetOverviewComparisonRange on the line above.
  • The Dashboard's own copy of this control is out of scope here — PR Dashboard query comparison grids use the server's local time (#4313) #4321 fixes it separately, correctly,
    for its server-local baseline handling.

Test plan

  • Lite.Tests.csproj builds with 0 Warning(s), 0 Error(s).
  • New pins in Lite.Tests/OverviewComparisonWindowOffsetTests.cs, the existing class for this file:
    • GetBaselineReferenceTimeUtc_CustomRange_ConvertsFromDateBackToUtc (UTC+5 and UTC-7): a custom range's
      result is fromDate - utcOffsetMinutes.
    • GetBaselineReferenceTimeUtc_PresetRange_UsesUtcNowUnchanged (UTC+5 and UTC-7): a preset range's result
      is utcNow - hoursBack, unchanged by the offset.
    • BaselineReferenceTime_UsesHelper_NotFromDateDirectly: a source pin that RefreshAsync calls the new
      helper rather than falling back to a raw fromDate.
    • Proved these fail on the pre-fix code: reverted both production files to this branch's parent commit
      (before either method/parameter existed), which broke the build for the two Theory tests (CS0117,
      the method doesn't exist yet); guarded those two out and reran the source pin alone against the reverted
      source, which failed its Assert.False on the old fromDate ?? DateTime.UtcNow.AddHours(-hoursBack)
      string. Restored both files from HEAD and removed the guard before committing; all 14 tests in the
      class pass again on the fixed code.
  • Full Lite.Tests.exe suite, run once after merging origin/dev (which had advanced by three unrelated
    PRs while this branch was in progress — merged clean, no conflicts, confirmed the merge added nothing of
    mine beyond the three files below): 5413 total, 0 errors, 0 failed, 0 skipped, 291.4s.
  • No live/manual verification against a real non-UTC server clock was run (none of Lite's rig facilities
    apply here — this is a pure WPF/desktop app fix with no PostgreSQL or SQL Server rig involved).

Diff (git diff --numstat origin/dev...HEAD):

54  0  Lite.Tests/OverviewComparisonWindowOffsetTests.cs
21  3  Lite/Controls/CorrelatedTimelineLanesControl.xaml.cs
1   1  Lite/Controls/ServerTab.Refresh.cs

For the coordinator

CHANGELOG entry

SECTION: Fixed
ENTRY:

🤖 Generated with Claude Code

https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3

erikdarlingdata and others added 3 commits September 25, 2026 12:51
…rver (#4320)

CorrelatedTimelineLanesControl.RefreshAsync fed a custom range's server-local
fromDate straight into GetBaselineForLaneAsync as if it were UTC.
BaselineLocalClock.LocalKey expects UTC and converts to server-local itself, so
the baseline's local-hour/day-of-week lookup was shifted a second time, by the
server's own UTC offset, on any server not on UTC. A preset range's
utcNow.AddHours(-hoursBack) was already UTC and unaffected.

Add GetBaselineReferenceTimeUtc, which converts a custom range's fromDate back
to UTC (fromDate - utcOffsetMinutes) and leaves a preset range's fallback
unchanged. RefreshAsync now takes utcOffsetMinutes as an explicit parameter
(matching GetCurrentWindowServerLocal/GetOverviewComparisonRange's existing
pattern) instead of reading ambient state, and ServerTab.Refresh.cs passes
ServerTimeHelper.UtcOffsetMinutes, as it already does for the comparison range.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
Add GetBaselineReferenceTimeUtc_CustomRange_ConvertsFromDateBackToUtc and
_PresetRange_UsesUtcNowUnchanged (UTC+5 and UTC-7), plus a source pin that
RefreshAsync's referenceTime calls the new helper instead of falling back to a
raw fromDate.

Proved these fail on the pre-fix code: reverted CorrelatedTimelineLanesControl.xaml.cs
and ServerTab.Refresh.cs to the parent commit, which broke the build (the two
Theory tests call a method that didn't exist yet -- CS0117), then guarded those
two tests out and reran the source pin alone, which failed its Assert.False on
the old "fromDate ?? DateTime.UtcNow.AddHours(-hoursBack)" fallback string.
Restored both production files from HEAD and removed the guard before this
commit; all 14 tests in the class pass again.

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