|
| 1 | +// Copyright The OpenTelemetry Authors |
| 2 | +// SPDX-License-Identifier: Apache-2.0 |
| 3 | + |
| 4 | +// Licensed to the .NET Foundation under one or more agreements. |
| 5 | +// The .NET Foundation licenses this file to you under the MIT license. |
| 6 | +// https://github.com/dotnet/runtime/blob/75662173e3918f2176b74e467dc8e41d4f01d4d4/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.DateTime.netfx.cs |
| 7 | + |
| 8 | +// Adjusted to our coding standards and moved to separate class |
| 9 | + |
| 10 | +using System.Diagnostics; |
| 11 | + |
| 12 | +namespace OpenTelemetry.Instrumentation.AspNet; |
| 13 | + |
| 14 | +internal static class ActivityDateTimeHelper |
| 15 | +{ |
| 16 | +#pragma warning disable CA1823 // suppress unused field warning, as it's used to keep the timer alive |
| 17 | + private static readonly Timer SyncTimeUpdater = InitializeSyncTimer(); |
| 18 | +#pragma warning restore CA1823 |
| 19 | + private static readonly double TickFrequency = (double)TimeSpan.TicksPerSecond / Stopwatch.Frequency; |
| 20 | + |
| 21 | + private static TimeSync timeSync = new(); |
| 22 | + |
| 23 | + /// <summary> |
| 24 | + /// Returns high resolution (1 DateTime tick) current UTC DateTime. |
| 25 | + /// </summary> |
| 26 | + /// <returns>High resolution UTC DateTime.</returns> |
| 27 | + internal static DateTime GetUtcNow() |
| 28 | + { |
| 29 | + // DateTime.UtcNow accuracy on .NET Framework is ~16ms, this method |
| 30 | + // uses combination of Stopwatch and DateTime to calculate accurate UtcNow. |
| 31 | + |
| 32 | + var tmp = timeSync; |
| 33 | + |
| 34 | + // Timer ticks need to be converted to DateTime ticks |
| 35 | + long dateTimeTicksDiff = (long)((Stopwatch.GetTimestamp() - tmp.SyncStopwatchTicks) * TickFrequency); |
| 36 | + |
| 37 | + // DateTime.AddSeconds (or Milliseconds) rounds value to 1 ms, use AddTicks to prevent it |
| 38 | + return tmp.SyncUtcNow.AddTicks(dateTimeTicksDiff); |
| 39 | + } |
| 40 | + |
| 41 | + private static void Sync() |
| 42 | + { |
| 43 | + // wait for DateTime.UtcNow update to the next granular value |
| 44 | + Thread.Sleep(1); |
| 45 | + timeSync = new TimeSync(); |
| 46 | + } |
| 47 | + |
| 48 | + [System.Security.SecuritySafeCritical] |
| 49 | + private static Timer InitializeSyncTimer() |
| 50 | + { |
| 51 | + Timer timer; |
| 52 | + |
| 53 | + // Don't capture the current ExecutionContext and its AsyncLocals onto the timer causing them to live forever |
| 54 | + bool restoreFlow = false; |
| 55 | + try |
| 56 | + { |
| 57 | + if (!ExecutionContext.IsFlowSuppressed()) |
| 58 | + { |
| 59 | + ExecutionContext.SuppressFlow(); |
| 60 | + restoreFlow = true; |
| 61 | + } |
| 62 | + |
| 63 | + timer = new Timer(static _ => Sync(), null, 0, 7_200_000); // 2 hours |
| 64 | + } |
| 65 | + finally |
| 66 | + { |
| 67 | + // Restore the current ExecutionContext |
| 68 | + if (restoreFlow) |
| 69 | + { |
| 70 | + ExecutionContext.RestoreFlow(); |
| 71 | + } |
| 72 | + } |
| 73 | + |
| 74 | + return timer; |
| 75 | + } |
| 76 | + |
| 77 | + private sealed class TimeSync |
| 78 | + { |
| 79 | + public readonly DateTime SyncUtcNow = DateTime.UtcNow; |
| 80 | + public readonly long SyncStopwatchTicks = Stopwatch.GetTimestamp(); |
| 81 | + } |
| 82 | +} |
0 commit comments