Skip to content

[BUG] Lite query and procedure grids render server-local DMV timestamps through the UTC formatter #3221

Closed

Description

@SalmanRajwani

Component

Lite

Performance Monitor Version

Confirmed in v3.6.0 source and current dev

SQL Server Version

Not version-specific; affects supported versions exposing these DMV timestamps

Windows Version

Not version-specific; Lite WPF client

Describe the Bug

Lite renders server-local plan-cache timestamps through ServerTimeHelper.FormatServerTime, whose parameter and implementation define the input as UTC. The values are therefore converted from the wrong source frame.

Confirmed affected surfaces:

  • Top Queries by Duration: Last Execution and Creation Time
  • Query execution history: Last Execution and Creation Time; Collection Time is genuinely UTC, so each row mixes correctly and incorrectly rendered frames
  • Top Procedures by Duration: Last Execution and Cached Time
  • Procedure execution history: Last Execution and Cached Time, alongside correctly UTC-based Collection Time

The related Running Jobs, Blocking, and Active Queries timestamp defects are already reported in #3207; this report does not duplicate their details.

Source evidence

  • SQL Server documents sys.dm_exec_query_stats.creation_time and last_execution_time as times in the SQL Server instance's current timezone. The corresponding procedure/function/trigger cache timestamps are likewise server-clock values.
  • Lite collects and stores those values without normalizing them to UTC.
  • In Lite/Services/LocalDataService.QueryStats.cs, QueryStatsRow, ProcedureStatsRow, QueryStatsHistoryRow, and ProcedureStatsHistoryRow pass those stored values to ServerTimeHelper.FormatServerTime.
  • Lite/Services/ServerTimeHelper.cs names that method's input utcTime and first adds the server UTC offset, so its input contract is UTC.
  • collection_time is genuinely UTC and is correctly sent through the same formatter. That is why the history rows contain a visible mixed-frame inconsistency.

Custom-range behavior

The active tab's custom-range conversion generally reaches the correct UTC collection_time window. The rows can therefore be selected correctly while their DMV timestamps render offset from the selected Local Time range, making the range filter itself appear incorrect.

There was also a separate shared-offset custom-range defect. Its fix was merged in #2987 after v3.6.0; that fix does not change the per-column source-frame mismatch reported here.

Steps to Reproduce

Use only synthetic times; no production data is required.

  1. Run Lite on a workstation at UTC+02 and monitor a SQL Server at UTC-05.
  2. Select Local Time display mode and choose a custom range around 20:00 workstation time.
  3. Arrange for a cached query or procedure to execute at the equivalent instant: 18:00 UTC / 13:00 server time.
  4. Open Top Queries by Duration or Top Procedures by Duration, then open the corresponding execution-history grid.
  5. Compare Collection Time with Last Execution plus Creation Time or Cached Time for the same interval.

Collection Time renders around 20:00 as expected. A DMV value stored as 13:00 server time is treated as 13:00 UTC; with these synthetic offsets it renders around 15:00 Local Time instead of 20:00. The discrepancy equals the server UTC offset and appears in every display mode, although the selected row still belongs to the correct UTC collection_time window.

Expected Behavior

Every timestamp is classified by its source frame and converted exactly once to the selected Local/Server/UTC display mode. For the synthetic example, timestamps describing the same instant align at 20:00 Local Time, 13:00 Server Time, or 18:00 UTC.

Actual Behavior

UTC collection_time and server-local DMV/cache timestamps use the same UTC-input renderer. Collection Time displays correctly, while Last Execution, Creation Time, and Cached Time are shifted, producing inconsistent times in the same grid or history row and making Local Time custom-range filtering appear wrong.

Error Messages / Log Output

Not applicable. This is a deterministic display-frame defect confirmed from source.

Screenshots

None. No environment-specific data is included.

Additional Context

Related renderer/frame report: #3207
Separate custom-range shared-offset fix: #2987

Activity

  1. erikdarlingdata commented on Sep 9, 2026

    @erikdarlingdata
    Owner

    Claude posting for Erik Darling

    Fixed, and thank you — this was a good report. It named the affected grids, distinguished the columns that were wrong from the ones that were right in the same row, and pointed at #3207 for the overlap instead of restating it. That is most of the diagnosis done before anyone read the code.

    Merged to dev as 7083eac0e34b; it will be in the next release.

    Every surface you listed is converted. Top Queries by Duration, Query execution history, Top Procedures by Duration and Procedure execution history now render Last Execution, Creation Time and Cached Time through a renderer that expects the monitored server's own clock, instead of one whose parameter and implementation define the input as UTC. Your observation that Collection Time was genuinely UTC in the same row — so each row mixed a correctly and an incorrectly rendered frame — is exactly the shape that made this worth fixing rather than shrugging at: a grid where some columns are right is harder to distrust than one where none are.

    Two things your report led to that went beyond it.

    An internal census, built while fixing #3207, found four more sites in the same file you named that neither your report nor #3207 listed — creation_time and cached_time in two further row types. So the Lite side of this class was wider than either account had it, and your four were the visible part of twelve.

    And fixing the frame turned out to introduce a separate defect on an axis nobody was watching: the corrected renderer ignored the Time Display Mode setting entirely, so selecting UTC would have shown you the server's clock anyway. That is now fixed too, and pinned — the same instant is asserted to render differently under Server and UTC mode. Had you upgraded into that intermediate state you would have traded one wrong answer for another, which is worth saying plainly since your report is what set the work in motion.

    One limit, stated rather than left for you to find: the conversion uses the server's UTC offset as collected, so a timestamp from the far side of a daylight-saving boundary can be an hour out. That is a known approximation shared with the rest of the product's clock handling and not specific to these grids.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions