Repository navigation
Lite CPU chart hover: a bucket read from the stored server wall clock shows no UTC offset in the repeated autumn hour - #4857
Merged
Conversation
…wall clock without a UTC offset in the repeated autumn hour (#4766)
… time names two instants (#4766) The hover marked every point cut on the server's stored wall clock as one whose offset it could not name. A stored wall time that happens once converts to one instant exactly, and on a machine whose display zone differs from the server's such a point can land in the display zone's repeated hour, where dropping the offset made two points read the same time. The read now marks a point only when its wall time happens twice on the server's own clock (SampleTimeNamesTwoInstants, formerly SampleTimeIsStoredWallClock); a server on a fixed offset or on UTC marks nothing.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follows #4766 and #4848.
Why
Lite's CPU chart plots every point at a UTC instant. Rows collected before the UTC column existed have only the server's stored wall clock, so their buckets have no instant of their own. The chart plots such a bucket at the first instant its wall time names. In the repeated autumn hour, the hover then adds that pass's UTC offset.
Example: an Eastern server, a bucket stored as 01:30 on 2026-11-01. The wall time happens twice, at 05:30Z (-04:00) and at 06:30Z (-05:00). A stored 01:30 bucket that came from the second pass, or that merged readings from both, is plotted at 05:30Z and its hover read "01:30:00 -04:00". That names a pass the data never recorded.
Only a wall time that happens twice on the server's own clock has this problem. A stored wall time that happens once names one instant, and the read converts it exactly. A machine can show the chart in a zone that differs from the server's. That exact instant can then land in the machine zone's repeated hour. Dropping the offset there makes two different instants read the same time.
Example: a Tokyo server has no repeated hour, so its stored 14:30 on 2026-11-01 is 05:30Z exactly. A machine in Eastern time shows that instant as the first pass of its 01:30, so the hover must keep "-04:00". So the mark is only where the stored wall time names two instants.
Behavior change
What changes
Lite/Services/LocalDataService.Cpu.cs:CpuUtilizationRowgetsSampleTimeNamesTwoInstants. It is true when the bucket was cut on the server's stored wall clock and that wall time happens twice there. That is the repeated autumn hour, andSampleTimeUtcis then only the first of the two instants it names. The stamping loop takes the server's zone once (clock.AsTimeZone()). It sets the flag torow.BucketIsServerLocal && serverZone.IsAmbiguousTime(stamp)in the UTC frame and toserverZone.IsAmbiguousTime(stamp)in the server-local frame.stampis the server's wall time with Kind Unspecified, which the zone reads as its own time.PerformanceMonitor.Ui/ChartHoverHelper.cs: a settablePlainTimeAtpredicate that says whether a plotted X is one whose stored wall time names two instants.Clear()sets it back to null, so a predicate never outlives its data.FormatHoverTimegainsbool plain = false. With a zone andplain, the line is the zone's wall time alone, worded in the invariant culture like theDisplayZone.Formatline it replaces. Otherwise the line is unchanged. The hover passesPlainTimeAt?.Invoke(x) == truefor the plotted X.Lite/Controls/ServerTab.Charts.cs:CpuHoverPlainTimes(rows)returns null when no row is marked. Otherwise it returns theContainsof a set built from the marked rows. Each entry isFromOADate(ToOADate(SampleTimeUtc)), the same round trip the hover's X takes. The match is exact, even for a stamp finer than a millisecond.UpdateCpuChartassigns it after the hover'sClear()andAddcalls.Darling/Darling.Tests/ConsumedTimestampFrameDisciplineTests.cs:FromOADateis declared as a call on a census column that does not render it (one entry, with its reason).Lite/ControlsandLite/Windows,SampleTimeUtcis read in two places: the CPU tab chart (this change) and the Overview's correlated timeline charts. Those label the cursor's instant throughCorrelatedCrosshairManager, which is a true instant, so they are left alone. In the Darling viewer,ToUtc(appears only in job history run times, system events andViewerTimeHelper.ConvertToDisplay. Its CPU read uses the stored UTC instant, or de-skews in SQL, so it has no stored wall-clock CPU bucket. Its hover does not change, and it compiles against the shared hover.Test plan
CpuHoverStoredWallClockTests(14 tests):FormatHoverTimeon Eastern time reads "01:30:00 -04:00" at 05:30Z and "01:30:00 -05:00" at 06:30Z, and "01:30:00" for both withplain: true. Outside the repeated hour, plain and not plain match. In the UTC frame, a bucket of rows with no stored instant is marked only in the repeated hour. 01:15 is marked. 00:30 and 03:00 are not. Rows that have their instant are not marked, from either pass. The edges of the hour: 01:00 and 01:45 are marked, 00:45 and 02:00 are not. A bucket that merged both passes is marked, and its hover reads "01:30:00" while a post-change second-pass reading keeps "01:30:00 -05:00". The server-local frame marks the 01:30 point and not the 03:00 one. A server on a fixed offset (-300) or on UTC marks no point, in either frame. That includes 01:30 on 2026-11-01, where a US zone repeats an hour. A Tokyo server's stored 14:30 on 2026-11-01 converts to 05:30Z, is not marked, gives no predicate, and reads "01:30:00 -04:00" in Eastern time (APreRungBucketOnAServerWithNoRepeatedHour_IsNotMarked_AndTheHoverInAnotherZonesRepeatedHourKeepsItsOffset).CpuHoverPlainTimesis true for a marked row's plotted X, false for an unmarked row's, and null when no row is marked.Clear()drops the predicate.UpdateCpuChartsets it after the clear.FormatHoverTimeignoresplain. Measured when the class had 11 tests,Total: 11, Failed: 3:ThePlainTime_IsWordedInTheInvariantCulture_LikeTheLineItReplaces,TheHoverTime_InTheRepeatedHour_DropsTheOffsetForAPlainX_AndKeepsItOtherwise,ThePreRungBucketThatMergedBothPasses_IsMarked_AndTheHoverDropsItsOffset[FAIL].Total: 14, Failed: 4:TheUtcFrame_MarksAPreRungBucketFromTheFirstToTheLastQuarterHourOfTheRepeatedHour_AndNoOther,ThePreRungBucketThatMergedBothPasses_IsMarked_AndTheHoverDropsItsOffset,TheUtcFrame_MarksAPreRungBucketInTheRepeatedHour_AndNotAPreRungBucketOutsideItNorAPostRungOne,TheServerLocalFrame_MarksOnlyThePointsWhoseWallTimeHappensTwice[FAIL].Clear()does not reset the predicate. (d) The plain line drops the invariant culture. (e) The set is keyed on the rawSampleTimeUtc. Measured when the class had 11 tests,Total: 11, Failed: 3, one failure each:ClearingTheHover_DropsThePredicate,ThePlainTime_IsWordedInTheInvariantCulture_LikeTheLineItReplacesandTheHoverPredicate_MatchesAMarkedRowsPlottedX_AndNotAnUnmarkedOnes.row.BucketIsServerLocalalone, so every pre-change bucket is marked.Total: 14, Failed: 4:TheUtcFrame_MarksAPreRungBucketFromTheFirstToTheLastQuarterHourOfTheRepeatedHour_AndNoOther,AServerWithNoRepeatedHour_MarksNoPoint_InEitherFrame,TheUtcFrame_MarksAPreRungBucketInTheRepeatedHour_AndNotAPreRungBucketOutsideItNorAPostRungOne,APreRungBucketOnAServerWithNoRepeatedHour_IsNotMarked_AndTheHoverInAnotherZonesRepeatedHourKeepsItsOffset[FAIL].true.Total: 14, Failed: 2:AServerWithNoRepeatedHour_MarksNoPoint_InEitherFrameandTheServerLocalFrame_MarksOnlyThePointsWhoseWallTimeHappensTwice[FAIL]. Every plant is restored, and the diff holds none.Lite.TestsandDarling.Testsbuild with 0 warnings.origin/devhad nothing new to merge.FormatHoverTime,ChartHoverHelper,GetCpuUtilizationAsyncorBucketIsServerLocalran together:Total: 135, Errors: 0, Failed: 0.CpuHoverStoredWallClockTests,AxesExtensionsTests,ChartClickIsolateTests,ChartTimeConversionClockTests,DisplayZoneTextFrameTests,ServerTabChartUtcFrameTests,TimeRangeSlicerLabelsTests,AlertBodyClockFrameDisciplineTests,OverviewLaneBucketingTests,OverviewComparisonWindowOffsetTests,OverviewLanesUtcFrameTests,TimeHonestyRungTests,WatermarkFrameReadTestsandTimeHonestyRungReadTests.ViewerChartUtcFrameTests,ConsumedTimestampFrameDisciplineTests,TsqlConventionGuardTests,DocCommentHygieneTests:Total: 115, Errors: 0, Failed: 0.ConsumedTimestampFrameDisciplineTestsfirst flaggedFromOADateon a census column, and passes with the one declared entry. Lite.Tests has noTsqlConventionGuardTestsorDocCommentHygieneTests.DARLING_TEST_PG:Lite.Tests Total: 6365, Errors: 0, Failed: 0, Skipped: 0, Not Run: 0, Time: 290.434s.DARLING_TEST_PG, measured before the mark was narrowed:Darling.Tests Total: 18794, Errors: 0, Failed: 0, Skipped: 1188, Not Run: 1, Time: 114.448s. The skips are the live tests that need a Postgres connection string, which that run did not set. The one Not Run is not a test this change adds or touches. The narrowing touches no Darling code, and the four Darling classes above pass on the new head. CI runs the full Darling suite.CHANGELOG
SECTION: None