From 389490d47a8b52d2c0fa7114f8d88491663237da Mon Sep 17 00:00:00 2001 From: Nikolay Zdravkov Date: Thu, 23 Apr 2026 01:03:51 +0300 Subject: [PATCH 1/2] fix: use Stopwatch instead of DateTime.UtcNow for timing in CounterGroup --- .../Diagnostics/Tracing/CounterGroup.cs | 35 ++++++++++++------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/CounterGroup.cs b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/CounterGroup.cs index d08c6949616c10..a7ccc06ad4644a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/CounterGroup.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/CounterGroup.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Diagnostics; using System.Runtime.Versioning; using System.Threading; @@ -142,9 +143,9 @@ internal static CounterGroup GetCounterGroup(EventSource eventSource) #region Timer Processing - private DateTime _timeStampSinceCollectionStarted; + private long _timeStampSinceCollectionStarted; private int _pollingIntervalInMilliseconds; - private DateTime _nextPollingTimeStamp; + private long _nextPollingTimeStamp; private void EnableTimer(float pollingIntervalInSeconds) { @@ -156,8 +157,9 @@ private void EnableTimer(float pollingIntervalInSeconds) // Schedule IncrementingPollingCounter reset and synchronously reset other counters HandleCountersReset(); - _timeStampSinceCollectionStarted = DateTime.UtcNow; - _nextPollingTimeStamp = DateTime.UtcNow + new TimeSpan(0, 0, (int)pollingIntervalInSeconds); + long now = Stopwatch.GetTimestamp(); + _timeStampSinceCollectionStarted = now; + _nextPollingTimeStamp = now + (long)(Stopwatch.Frequency * pollingIntervalInSeconds); // Create the polling thread and init all the shared state if needed if (s_pollingThread == null) @@ -225,14 +227,14 @@ private void OnTimer() { if (_eventSource.IsEnabled()) { - DateTime now; + long now; TimeSpan elapsed; int pollingIntervalInMilliseconds; DiagnosticCounter[] counters; lock (s_counterGroupLock) { - now = DateTime.UtcNow; - elapsed = now - _timeStampSinceCollectionStarted; + now = Stopwatch.GetTimestamp(); + elapsed = Stopwatch.GetElapsedTime(_timeStampSinceCollectionStarted, now); pollingIntervalInMilliseconds = _pollingIntervalInMilliseconds; counters = new DiagnosticCounter[_counters.Count]; _counters.CopyTo(counters); @@ -259,10 +261,17 @@ private void OnTimer() lock (s_counterGroupLock) { _timeStampSinceCollectionStarted = now; - TimeSpan delta = now - _nextPollingTimeStamp; - delta = _pollingIntervalInMilliseconds > delta.TotalMilliseconds ? TimeSpan.FromMilliseconds(_pollingIntervalInMilliseconds) : delta; + long intervalTicks = (long)((double)Stopwatch.Frequency * _pollingIntervalInMilliseconds / 1000); + long delta = now - _nextPollingTimeStamp; + if (delta < intervalTicks) + { + delta = intervalTicks; + } + if (_pollingIntervalInMilliseconds > 0) - _nextPollingTimeStamp += TimeSpan.FromMilliseconds(_pollingIntervalInMilliseconds * Math.Ceiling(delta.TotalMilliseconds / _pollingIntervalInMilliseconds)); + { + _nextPollingTimeStamp += (long)(intervalTicks * Math.Ceiling((double)delta / intervalTicks)); + } } } } @@ -292,13 +301,13 @@ private static void PollForValues() sleepEvent = s_pollingThreadSleepEvent; foreach (CounterGroup counterGroup in s_counterGroupEnabledList!) { - DateTime now = DateTime.UtcNow; - if (counterGroup._nextPollingTimeStamp < now + new TimeSpan(0, 0, 0, 0, 1)) + long now = Stopwatch.GetTimestamp(); + if (counterGroup._nextPollingTimeStamp < now + Stopwatch.Frequency / 1000) { onTimers.Add(counterGroup); } - int millisecondsTillNextPoll = (int)((counterGroup._nextPollingTimeStamp - now).TotalMilliseconds); + int millisecondsTillNextPoll = (int)Stopwatch.GetElapsedTime(now, counterGroup._nextPollingTimeStamp).TotalMilliseconds; millisecondsTillNextPoll = Math.Max(1, millisecondsTillNextPoll); sleepDurationInMilliseconds = Math.Min(sleepDurationInMilliseconds, millisecondsTillNextPoll); } From 4b3071da64d0b118bb37fcfe33648e01b0ce9530 Mon Sep 17 00:00:00 2001 From: Nikolay Zdravkov Date: Thu, 23 Apr 2026 12:42:31 +0300 Subject: [PATCH 2/2] refactor: use TimeSpan for polling interval in CounterGroup --- .../Diagnostics/Tracing/CounterGroup.cs | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/CounterGroup.cs b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/CounterGroup.cs index a7ccc06ad4644a..9c363ff74cd570 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/CounterGroup.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/CounterGroup.cs @@ -93,7 +93,7 @@ private void OnEventSourceCommand(object? sender, EventCommandEventArgs e) Debug.Assert((s_counterGroupEnabledList == null && !_eventSource.IsEnabled()) || (_eventSource.IsEnabled() && s_counterGroupEnabledList!.Contains(this)) - || (_pollingIntervalInMilliseconds == 0 && !s_counterGroupEnabledList!.Contains(this)) + || (_pollingInterval == TimeSpan.Zero && !s_counterGroupEnabledList!.Contains(this)) || (!_eventSource.IsEnabled() && !s_counterGroupEnabledList!.Contains(this))); } } @@ -144,22 +144,23 @@ internal static CounterGroup GetCounterGroup(EventSource eventSource) #region Timer Processing private long _timeStampSinceCollectionStarted; - private int _pollingIntervalInMilliseconds; + private TimeSpan _pollingInterval; private long _nextPollingTimeStamp; private void EnableTimer(float pollingIntervalInSeconds) { Debug.Assert(pollingIntervalInSeconds > 0); Debug.Assert(Monitor.IsEntered(s_counterGroupLock)); - if (_pollingIntervalInMilliseconds == 0 || pollingIntervalInSeconds * 1000 < _pollingIntervalInMilliseconds) + TimeSpan interval = TimeSpan.FromSeconds(pollingIntervalInSeconds); + if (_pollingInterval == TimeSpan.Zero || interval < _pollingInterval) { - _pollingIntervalInMilliseconds = (int)(pollingIntervalInSeconds * 1000); + _pollingInterval = interval; // Schedule IncrementingPollingCounter reset and synchronously reset other counters HandleCountersReset(); long now = Stopwatch.GetTimestamp(); _timeStampSinceCollectionStarted = now; - _nextPollingTimeStamp = now + (long)(Stopwatch.Frequency * pollingIntervalInSeconds); + _nextPollingTimeStamp = now + _pollingInterval.Ticks * Stopwatch.Frequency / TimeSpan.TicksPerSecond; // Create the polling thread and init all the shared state if needed if (s_pollingThread == null) @@ -188,7 +189,7 @@ private void EnableTimer(float pollingIntervalInSeconds) private void DisableTimer() { Debug.Assert(Monitor.IsEntered(s_counterGroupLock)); - _pollingIntervalInMilliseconds = 0; + _pollingInterval = TimeSpan.Zero; s_counterGroupEnabledList?.Remove(this); if (s_needsResetIncrementingPollingCounters.Count > 0) @@ -229,13 +230,13 @@ private void OnTimer() { long now; TimeSpan elapsed; - int pollingIntervalInMilliseconds; + TimeSpan pollingInterval; DiagnosticCounter[] counters; lock (s_counterGroupLock) { now = Stopwatch.GetTimestamp(); elapsed = Stopwatch.GetElapsedTime(_timeStampSinceCollectionStarted, now); - pollingIntervalInMilliseconds = _pollingIntervalInMilliseconds; + pollingInterval = _pollingInterval; counters = new DiagnosticCounter[_counters.Count]; _counters.CopyTo(counters); } @@ -255,22 +256,21 @@ private void OnTimer() // written to the old session or the new session. The behavior change is not being treated as a // significant problem to address for now, but we can come back and address it if it turns out to // be an actual issue. - counter.WritePayload((float)elapsed.TotalSeconds, pollingIntervalInMilliseconds); + counter.WritePayload((float)elapsed.TotalSeconds, (int)pollingInterval.TotalMilliseconds); } lock (s_counterGroupLock) { _timeStampSinceCollectionStarted = now; - long intervalTicks = (long)((double)Stopwatch.Frequency * _pollingIntervalInMilliseconds / 1000); - long delta = now - _nextPollingTimeStamp; - if (delta < intervalTicks) + TimeSpan delta = Stopwatch.GetElapsedTime(_nextPollingTimeStamp, now); + if (delta < _pollingInterval) { - delta = intervalTicks; + delta = _pollingInterval; } - if (_pollingIntervalInMilliseconds > 0) + if (_pollingInterval > TimeSpan.Zero) { - _nextPollingTimeStamp += (long)(intervalTicks * Math.Ceiling((double)delta / intervalTicks)); + _nextPollingTimeStamp += (long)Math.Ceiling(delta / _pollingInterval) * _pollingInterval.Ticks * Stopwatch.Frequency / TimeSpan.TicksPerSecond; } } } @@ -302,13 +302,13 @@ private static void PollForValues() foreach (CounterGroup counterGroup in s_counterGroupEnabledList!) { long now = Stopwatch.GetTimestamp(); - if (counterGroup._nextPollingTimeStamp < now + Stopwatch.Frequency / 1000) + TimeSpan timeUntilNextPoll = Stopwatch.GetElapsedTime(now, counterGroup._nextPollingTimeStamp); + if (timeUntilNextPoll < TimeSpan.FromMilliseconds(1)) { onTimers.Add(counterGroup); } - int millisecondsTillNextPoll = (int)Stopwatch.GetElapsedTime(now, counterGroup._nextPollingTimeStamp).TotalMilliseconds; - millisecondsTillNextPoll = Math.Max(1, millisecondsTillNextPoll); + int millisecondsTillNextPoll = Math.Max(1, (int)timeUntilNextPoll.TotalMilliseconds); sleepDurationInMilliseconds = Math.Min(sleepDurationInMilliseconds, millisecondsTillNextPoll); }