From 7e9dc883669195f777007f9b9b3d35efbfcc3026 Mon Sep 17 00:00:00 2001 From: Makazeu Date: Mon, 28 Apr 2025 15:31:42 +0800 Subject: [PATCH 1/6] Add Disk IO time metric for Windows in ResourceMonitoring --- .../Disk/WindowsDiskIoRatePerfCounter.cs | 9 +-- .../Disk/WindowsDiskIoTimePerfCounter.cs | 75 +++++++++++++++++++ .../Windows/Disk/WindowsDiskMetrics.cs | 59 +++++++++++++-- .../Disk/WindowsDiskPerfCounterNames.cs | 1 + .../ResourceUtilizationInstruments.cs | 8 ++ .../Disk/WindowsDiskIoRatePerfCounterTests.cs | 4 +- .../Disk/WindowsDiskIoTimePerfCounterTests.cs | 61 +++++++++++++++ 7 files changed, 202 insertions(+), 15 deletions(-) create mode 100644 src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Disk/WindowsDiskIoTimePerfCounter.cs create mode 100644 test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskIoTimePerfCounterTests.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Disk/WindowsDiskIoRatePerfCounter.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Disk/WindowsDiskIoRatePerfCounter.cs index 5d35efc98ad..1c236939357 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Disk/WindowsDiskIoRatePerfCounter.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Disk/WindowsDiskIoRatePerfCounter.cs @@ -43,15 +43,9 @@ internal void InitializeDiskCounters() { foreach (string instanceName in _instanceNames) { - // Skip the total instance - if (instanceName.Equals("_Total", StringComparison.OrdinalIgnoreCase)) - { - continue; - } - // Create counters for each disk _counters.Add(_performanceCounterFactory.Create(_categoryName, _counterName, instanceName)); - TotalCountDict.Add(instanceName, 0); + TotalCountDict[instanceName] = 0L; } // Initialize the counters to get the first value @@ -71,6 +65,7 @@ internal void UpdateDiskCounters() // For the kind of "rate" perf counters, this algorithm calculates the total value over a time interval // by multiplying the per-second rate (e.g., Disk Bytes/sec) by the time interval between two samples. // This effectively reverses the per-second rate calculation to a total amount (e.g., total bytes transferred) during that period. + // See https://learn.microsoft.com/zh-cn/archive/blogs/askcore/windows-performance-monitor-disk-counters-explained#windows-performance-monitor-disk-counters-explained foreach (IPerformanceCounter counter in _counters) { // total value = per-second rate * elapsed seconds diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Disk/WindowsDiskIoTimePerfCounter.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Disk/WindowsDiskIoTimePerfCounter.cs new file mode 100644 index 00000000000..0ca7e62e0bd --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Disk/WindowsDiskIoTimePerfCounter.cs @@ -0,0 +1,75 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; + +namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Disk; + +internal sealed class WindowsDiskIoTimePerfCounter +{ + private readonly List _counters = []; + private readonly IPerformanceCounterFactory _performanceCounterFactory; + private readonly TimeProvider _timeProvider; + private readonly string _categoryName; + private readonly string _counterName; + private readonly string[] _instanceNames; + private long _lastTimestamp; + + internal WindowsDiskIoTimePerfCounter( + IPerformanceCounterFactory performanceCounterFactory, + TimeProvider timeProvider, + string categoryName, + string counterName, + string[] instanceNames) + { + _performanceCounterFactory = performanceCounterFactory; + _timeProvider = timeProvider; + _categoryName = categoryName; + _counterName = counterName; + _instanceNames = instanceNames; + } + + /// + /// Gets the disk time measurements. + /// Key: Disk name, Value: Real elapsed time used in busy state. + /// + internal IDictionary TotalSeconds { get; } = new ConcurrentDictionary(); + + internal void InitializeDiskCounters() + { + foreach (string instanceName in _instanceNames) + { + // Create counters for each disk + _counters.Add(_performanceCounterFactory.Create(_categoryName, _counterName, instanceName)); + TotalSeconds[instanceName] = 0f; + } + + // Initialize the counters to get the first value + foreach (IPerformanceCounter counter in _counters) + { + _ = counter.NextValue(); + } + + _lastTimestamp = _timeProvider.GetUtcNow().ToUnixTimeMilliseconds(); + } + + internal void UpdateDiskCounters() + { + long currentTimestamp = _timeProvider.GetUtcNow().ToUnixTimeMilliseconds(); + double elapsedSeconds = (currentTimestamp - _lastTimestamp) / 1000.0; // Convert to seconds + + // The real elapsed time ("wall clock") used in the I/O path (time from operations running in parallel are not counted). + // Measured as the complement of "Disk\% Idle Time" performance counter: uptime * (100 - "Disk\% Idle Time") / 100 + // See https://opentelemetry.io/docs/specs/semconv/system/system-metrics/#metric-systemdiskio_time + foreach (IPerformanceCounter counter in _counters) + { + // io busy time = (1 - (% idle time / 100)) * elapsed seconds + double value = (1 - (counter.NextValue() / 100f)) * elapsedSeconds; + TotalSeconds[counter.InstanceName] += value; + } + + _lastTimestamp = currentTimestamp; + } +} diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Disk/WindowsDiskMetrics.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Disk/WindowsDiskMetrics.cs index 6c3f3faea34..3383f777804 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Disk/WindowsDiskMetrics.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Disk/WindowsDiskMetrics.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.Metrics; +using System.Linq; using System.Runtime.Versioning; using Microsoft.Extensions.Options; using Microsoft.Shared.Instruments; @@ -20,6 +21,7 @@ internal sealed class WindowsDiskMetrics private static readonly KeyValuePair _directionReadTag = new(DirectionKey, "read"); private static readonly KeyValuePair _directionWriteTag = new(DirectionKey, "write"); private readonly Dictionary _diskIoRateCounters = new(); + private WindowsDiskIoTimePerfCounter? _diskIoTimePerfCounter; public WindowsDiskMetrics( IMeterFactory meterFactory, @@ -42,7 +44,7 @@ public WindowsDiskMetrics( InitializeDiskCounters(performanceCounterFactory, timeProvider); // The metric is aligned with - // https://github.com/open-telemetry/semantic-conventions/blob/main/docs/system/system-metrics.md#metric-systemdiskio + // https://opentelemetry.io/docs/specs/semconv/system/system-metrics/#metric-systemdiskio _ = meter.CreateObservableCounter( ResourceUtilizationInstruments.SystemDiskIo, GetDiskIoMeasurements, @@ -50,31 +52,61 @@ public WindowsDiskMetrics( description: "Disk bytes transferred"); // The metric is aligned with - // https://github.com/open-telemetry/semantic-conventions/blob/main/docs/system/system-metrics.md#metric-systemdiskoperations + // https://opentelemetry.io/docs/specs/semconv/system/system-metrics/#metric-systemdiskoperations _ = meter.CreateObservableCounter( ResourceUtilizationInstruments.SystemDiskOperations, GetDiskOperationMeasurements, unit: "{operation}", description: "Disk operations"); + + // The metric is aligned with + // https://opentelemetry.io/docs/specs/semconv/system/system-metrics/#metric-systemdiskio_time + _ = meter.CreateObservableCounter( + ResourceUtilizationInstruments.SystemDiskIoTime, + GetDiskIoTimeMeasurements, + unit: "s", + description: "Time disk spent activated"); } private void InitializeDiskCounters(IPerformanceCounterFactory performanceCounterFactory, TimeProvider timeProvider) { const string DiskCategoryName = "LogicalDisk"; - string[] instanceNames = performanceCounterFactory.GetCategoryInstances(DiskCategoryName); + string[] instanceNames = performanceCounterFactory.GetCategoryInstances(DiskCategoryName) + .Where(instanceName => !instanceName.Equals("_Total", StringComparison.OrdinalIgnoreCase)) + .ToArray(); if (instanceNames.Length == 0) { return; } - List diskIoRatePerformanceCounters = + // Initialize disk performance counters for "system.disk.io_time" metric + try + { + var ioTimePerfCounter = new WindowsDiskIoTimePerfCounter( + performanceCounterFactory, + timeProvider, + DiskCategoryName, + WindowsDiskPerfCounterNames.DiskIdleTimeCounter, + instanceNames); + ioTimePerfCounter.InitializeDiskCounters(); + _diskIoTimePerfCounter = ioTimePerfCounter; + } +#pragma warning disable CA1031 + catch (Exception ex) +#pragma warning restore CA1031 + { + Debug.WriteLine("Error initializing disk io time perf counter: " + ex.Message); + } + + // Initialize disk performance counters for "system.disk.io" and "system.disk.operations" metrics + List diskIoRatePerfCounterNames = [ WindowsDiskPerfCounterNames.DiskWriteBytesCounter, WindowsDiskPerfCounterNames.DiskReadBytesCounter, WindowsDiskPerfCounterNames.DiskWritesCounter, WindowsDiskPerfCounterNames.DiskReadsCounter, ]; - foreach (string counterName in diskIoRatePerformanceCounters) + foreach (string counterName in diskIoRatePerfCounterNames) { try { @@ -91,7 +123,7 @@ private void InitializeDiskCounters(IPerformanceCounterFactory performanceCounte catch (Exception ex) #pragma warning restore CA1031 { - Debug.WriteLine("Error initializing disk performance counter: " + ex.Message); + Debug.WriteLine("Error initializing disk io rate perf counter: " + ex.Message); } } } @@ -145,4 +177,19 @@ private IEnumerable> GetDiskOperationMeasurements() return measurements; } + + private IEnumerable> GetDiskIoTimeMeasurements() + { + List> measurements = []; + if (_diskIoTimePerfCounter != null) + { + _diskIoTimePerfCounter.UpdateDiskCounters(); + foreach (KeyValuePair pair in _diskIoTimePerfCounter.TotalSeconds) + { + measurements.Add(new Measurement(pair.Value, new TagList { new(DeviceKey, pair.Key) })); + } + } + + return measurements; + } } diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Disk/WindowsDiskPerfCounterNames.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Disk/WindowsDiskPerfCounterNames.cs index b791bdea3c7..2bc12a7ab82 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Disk/WindowsDiskPerfCounterNames.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Disk/WindowsDiskPerfCounterNames.cs @@ -9,4 +9,5 @@ internal static class WindowsDiskPerfCounterNames internal const string DiskReadBytesCounter = "Disk Read Bytes/sec"; internal const string DiskWritesCounter = "Disk Writes/sec"; internal const string DiskReadsCounter = "Disk Reads/sec"; + internal const string DiskIdleTimeCounter = "% Idle Time"; } diff --git a/src/Shared/Instruments/ResourceUtilizationInstruments.cs b/src/Shared/Instruments/ResourceUtilizationInstruments.cs index fe18e7ac4fa..3b3e4f80ea2 100644 --- a/src/Shared/Instruments/ResourceUtilizationInstruments.cs +++ b/src/Shared/Instruments/ResourceUtilizationInstruments.cs @@ -66,6 +66,14 @@ internal static class ResourceUtilizationInstruments /// public const string SystemDiskIo = "system.disk.io"; + /// + /// The name of an instrument to retrieve the time disk spent activated. + /// + /// + /// The type of an instrument is . + /// + public const string SystemDiskIoTime = "system.disk.io_time"; + /// /// The name of an instrument to retrieve disk operations. /// diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskIoRatePerfCounterTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskIoRatePerfCounterTests.cs index 1e163e6ca44..f2131afd3f5 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskIoRatePerfCounterTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskIoRatePerfCounterTests.cs @@ -29,7 +29,7 @@ public void DiskReadsPerfCounter_Per60Seconds() fakeTimeProvider, CategoryName, CounterName, - instanceNames: ["C:", "D:", "_Total"]); + instanceNames: ["C:", "D:"]); // Set up var counterC = new FakePerformanceCounter("C:", [0, 1, 1.5f, 2, 2.5f]); @@ -75,7 +75,7 @@ public void DiskWriteBytesPerfCounter_Per30Seconds() fakeTimeProvider, CategoryName, counterName: CounterName, - instanceNames: ["C:", "D:", "_Total"]); + instanceNames: ["C:", "D:"]); // Set up var counterC = new FakePerformanceCounter("C:", [0, 100, 150.5f, 20, 3.1416f]); diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskIoTimePerfCounterTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskIoTimePerfCounterTests.cs new file mode 100644 index 00000000000..4daf808240b --- /dev/null +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskIoTimePerfCounterTests.cs @@ -0,0 +1,61 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.Versioning; +using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Test; +using Microsoft.Extensions.Time.Testing; +using Microsoft.TestUtilities; +using Moq; +using Xunit; + +namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Disk.Test; + +[SupportedOSPlatform("windows")] +[OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX, SkipReason = "Windows specific.")] +public class WindowsDiskIoTimePerfCounterTests +{ + private const string CategoryName = "LogicalDisk"; + + [ConditionalFact] + public void DiskReadsPerfCounter_Per60Seconds() + { + const string CounterName = WindowsDiskPerfCounterNames.DiskReadsCounter; + var performanceCounterFactory = new Mock(); + var fakeTimeProvider = new FakeTimeProvider { AutoAdvanceAmount = TimeSpan.FromSeconds(60) }; + + var perfCounters = new WindowsDiskIoTimePerfCounter( + performanceCounterFactory.Object, + fakeTimeProvider, + CategoryName, + CounterName, + instanceNames: ["C:", "D:"]); + + // Set up + var counterC = new FakePerformanceCounter("C:", [100, 100, 0, 99.5f]); + var counterD = new FakePerformanceCounter("D:", [100, 99.9f, 88.8f, 66.6f]); + performanceCounterFactory.Setup(x => x.Create(CategoryName, CounterName, "C:")).Returns(counterC); + performanceCounterFactory.Setup(x => x.Create(CategoryName, CounterName, "D:")).Returns(counterD); + + // Initialize the counters + perfCounters.InitializeDiskCounters(); + Assert.Equal(2, perfCounters.TotalSeconds.Count); + Assert.Equal(0, perfCounters.TotalSeconds["C:"]); + Assert.Equal(0, perfCounters.TotalSeconds["D:"]); + + // Simulate the first tick + perfCounters.UpdateDiskCounters(); + Assert.Equal(0, perfCounters.TotalSeconds["C:"], precision: 2); // (100-100)% * 60 = 0 + Assert.Equal(0.06, perfCounters.TotalSeconds["D:"], precision: 2); // (100-99.9)% * 60 = 0.06 + + // Simulate the second tick + perfCounters.UpdateDiskCounters(); + Assert.Equal(60, perfCounters.TotalSeconds["C:"], precision: 2); // 0 + (100-0)% * 60 = 60 + Assert.Equal(6.78, perfCounters.TotalSeconds["D:"], precision: 2); // 0.06 + (100-88.8)% * 60 = 6.78 + + // Simulate the third tick + perfCounters.UpdateDiskCounters(); + Assert.Equal(60.3, perfCounters.TotalSeconds["C:"], precision: 2); // 60 + (100-99.5)% * 60 = 60.3 + Assert.Equal(26.82, perfCounters.TotalSeconds["D:"], precision: 2); // 6.78 + (100-66.6)% * 60 = 6.78 + 20.04 = 26.82 + } +} From ef35ec4e03fc75132939e8d94d11c0bf719d5035 Mon Sep 17 00:00:00 2001 From: Makazeu Date: Tue, 29 Apr 2025 03:28:58 +0800 Subject: [PATCH 2/6] Fix calculation of busy time in Windows disk I/O performance counter --- .../Windows/Disk/WindowsDiskIoTimePerfCounter.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Disk/WindowsDiskIoTimePerfCounter.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Disk/WindowsDiskIoTimePerfCounter.cs index 0ca7e62e0bd..33814311899 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Disk/WindowsDiskIoTimePerfCounter.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Disk/WindowsDiskIoTimePerfCounter.cs @@ -66,8 +66,9 @@ internal void UpdateDiskCounters() foreach (IPerformanceCounter counter in _counters) { // io busy time = (1 - (% idle time / 100)) * elapsed seconds - double value = (1 - (counter.NextValue() / 100f)) * elapsedSeconds; - TotalSeconds[counter.InstanceName] += value; + float idleTimePercentage = Math.Min(counter.NextValue(), 100f); + double busyTimeSeconds = (1 - (idleTimePercentage / 100f)) * elapsedSeconds; + TotalSeconds[counter.InstanceName] += busyTimeSeconds; } _lastTimestamp = currentTimestamp; From d5a34c998ee38bee7dcb859ce417100b6e8a1130 Mon Sep 17 00:00:00 2001 From: Makazeu Date: Sun, 4 May 2025 14:26:03 +0800 Subject: [PATCH 3/6] Improve with feedback --- .../Windows/Disk/WindowsDiskMetrics.cs | 9 +++++++-- .../Windows/Log.cs | 4 ++++ .../Windows/Disk/WindowsDiskMetricsTests.cs | 5 +++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Disk/WindowsDiskMetrics.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Disk/WindowsDiskMetrics.cs index 3383f777804..47706b3ce6a 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Disk/WindowsDiskMetrics.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Disk/WindowsDiskMetrics.cs @@ -7,6 +7,8 @@ using System.Diagnostics.Metrics; using System.Linq; using System.Runtime.Versioning; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Microsoft.Shared.Instruments; @@ -20,15 +22,18 @@ internal sealed class WindowsDiskMetrics private static readonly KeyValuePair _directionReadTag = new(DirectionKey, "read"); private static readonly KeyValuePair _directionWriteTag = new(DirectionKey, "write"); + private readonly ILogger _logger; private readonly Dictionary _diskIoRateCounters = new(); private WindowsDiskIoTimePerfCounter? _diskIoTimePerfCounter; public WindowsDiskMetrics( + ILogger? logger, IMeterFactory meterFactory, IPerformanceCounterFactory performanceCounterFactory, TimeProvider timeProvider, IOptions options) { + _logger = logger ?? NullLogger.Instance; if (!options.Value.EnableDiskIoMetrics) { return; @@ -95,7 +100,7 @@ private void InitializeDiskCounters(IPerformanceCounterFactory performanceCounte catch (Exception ex) #pragma warning restore CA1031 { - Debug.WriteLine("Error initializing disk io time perf counter: " + ex.Message); + Log.DiskIoPerfCounterException(_logger, WindowsDiskPerfCounterNames.DiskIdleTimeCounter, ex.Message); } // Initialize disk performance counters for "system.disk.io" and "system.disk.operations" metrics @@ -123,7 +128,7 @@ private void InitializeDiskCounters(IPerformanceCounterFactory performanceCounte catch (Exception ex) #pragma warning restore CA1031 { - Debug.WriteLine("Error initializing disk io rate perf counter: " + ex.Message); + Log.DiskIoPerfCounterException(_logger, counterName, ex.Message); } } } diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Log.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Log.cs index 78c19524f75..3d23f87dff9 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Log.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Log.cs @@ -45,4 +45,8 @@ public static partial void CpuContainerUsageData(ILogger logger, [LoggerMessage(6, LogLevel.Debug, "System resources information: CpuLimit = {cpuLimit}, CpuRequest = {cpuRequest}, MemoryLimit = {memoryLimit}, MemoryRequest = {memoryRequest}.")] public static partial void SystemResourcesInfo(ILogger logger, double cpuLimit, double cpuRequest, ulong memoryLimit, ulong memoryRequest); + + [LoggerMessage(7, LogLevel.Warning, + "Error initializing disk io perf counter: PerfCounter={counterName}, Error={errorMessage}")] + public static partial void DiskIoPerfCounterException(ILogger logger, string counterName, string errorMessage); } diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskMetricsTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskMetricsTests.cs index c42ee0b4db7..25587a6ad59 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskMetricsTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskMetricsTests.cs @@ -9,6 +9,7 @@ using Microsoft.Extensions.Diagnostics.Metrics.Testing; using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Test.Helpers; using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Test; +using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Time.Testing; using Microsoft.Shared.Instruments; using Microsoft.TestUtilities; @@ -22,6 +23,7 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Disk.Test; public class WindowsDiskMetricsTests { private const string CategoryName = "LogicalDisk"; + private readonly FakeLogger _fakeLogger = new(); [ConditionalFact] public void Creates_Meter_With_Correct_Name() @@ -31,6 +33,7 @@ public void Creates_Meter_With_Correct_Name() var options = new ResourceMonitoringOptions { EnableDiskIoMetrics = true }; _ = new WindowsDiskMetrics( + _fakeLogger, meterFactory, performanceCounterFactoryMock.Object, TimeProvider.System, @@ -62,6 +65,7 @@ public void DiskOperationMetricsTest() performanceCounterFactory.Setup(x => x.GetCategoryInstances(CategoryName)).Returns(["_Total", "C:", "D:"]); _ = new WindowsDiskMetrics( + _fakeLogger, meterFactory, performanceCounterFactory.Object, fakeTimeProvider, @@ -135,6 +139,7 @@ public void DiskIoBytesMetricsTest() performanceCounterFactory.Setup(x => x.GetCategoryInstances(CategoryName)).Returns(["_Total", "C:", "D:"]); _ = new WindowsDiskMetrics( + _fakeLogger, meterFactory, performanceCounterFactory.Object, fakeTimeProvider, From a88ad1db53f0d9219d6cebcbe0f40747cfccbf64 Mon Sep 17 00:00:00 2001 From: Makazeu Date: Sun, 4 May 2025 17:35:35 +0800 Subject: [PATCH 4/6] Use ticks instead of Milliseconds when calculating disk io metrics --- .../Windows/Disk/WindowsDiskIoRatePerfCounter.cs | 14 +++++++------- .../Windows/Disk/WindowsDiskIoTimePerfCounter.cs | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Disk/WindowsDiskIoRatePerfCounter.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Disk/WindowsDiskIoRatePerfCounter.cs index 1c236939357..eceb907c6df 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Disk/WindowsDiskIoRatePerfCounter.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Disk/WindowsDiskIoRatePerfCounter.cs @@ -17,7 +17,7 @@ internal sealed class WindowsDiskIoRatePerfCounter private readonly string _categoryName; private readonly string _counterName; private readonly string[] _instanceNames; - private long _lastTimestamp; + private long _lastTimeTick; internal WindowsDiskIoRatePerfCounter( IPerformanceCounterFactory performanceCounterFactory, @@ -54,13 +54,13 @@ internal void InitializeDiskCounters() _ = counter.NextValue(); } - _lastTimestamp = _timeProvider.GetUtcNow().ToUnixTimeMilliseconds(); + _lastTimeTick = _timeProvider.GetUtcNow().Ticks; } internal void UpdateDiskCounters() { - long currentTimestamp = _timeProvider.GetUtcNow().ToUnixTimeMilliseconds(); - double elapsedSeconds = (currentTimestamp - _lastTimestamp) / 1000.0; // Convert to seconds + long currentTimeTicks = _timeProvider.GetUtcNow().Ticks; + long elapsedTimeTicks = currentTimeTicks - _lastTimeTick; // For the kind of "rate" perf counters, this algorithm calculates the total value over a time interval // by multiplying the per-second rate (e.g., Disk Bytes/sec) by the time interval between two samples. @@ -69,10 +69,10 @@ internal void UpdateDiskCounters() foreach (IPerformanceCounter counter in _counters) { // total value = per-second rate * elapsed seconds - double value = counter.NextValue() * elapsedSeconds; - TotalCountDict[counter.InstanceName] += (long)value; + double value = counter.NextValue() * (double)elapsedTimeTicks; + TotalCountDict[counter.InstanceName] += (long)value / TimeSpan.TicksPerSecond; } - _lastTimestamp = currentTimestamp; + _lastTimeTick = currentTimeTicks; } } diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Disk/WindowsDiskIoTimePerfCounter.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Disk/WindowsDiskIoTimePerfCounter.cs index 33814311899..f8e3e424aa5 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Disk/WindowsDiskIoTimePerfCounter.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Disk/WindowsDiskIoTimePerfCounter.cs @@ -15,7 +15,7 @@ internal sealed class WindowsDiskIoTimePerfCounter private readonly string _categoryName; private readonly string _counterName; private readonly string[] _instanceNames; - private long _lastTimestamp; + private long _lastTimeTick; internal WindowsDiskIoTimePerfCounter( IPerformanceCounterFactory performanceCounterFactory, @@ -52,13 +52,13 @@ internal void InitializeDiskCounters() _ = counter.NextValue(); } - _lastTimestamp = _timeProvider.GetUtcNow().ToUnixTimeMilliseconds(); + _lastTimeTick = _timeProvider.GetUtcNow().Ticks; } internal void UpdateDiskCounters() { - long currentTimestamp = _timeProvider.GetUtcNow().ToUnixTimeMilliseconds(); - double elapsedSeconds = (currentTimestamp - _lastTimestamp) / 1000.0; // Convert to seconds + long currentTimeTicks = _timeProvider.GetUtcNow().Ticks; + long elapsedTimeTicks = currentTimeTicks - _lastTimeTick; // The real elapsed time ("wall clock") used in the I/O path (time from operations running in parallel are not counted). // Measured as the complement of "Disk\% Idle Time" performance counter: uptime * (100 - "Disk\% Idle Time") / 100 @@ -67,10 +67,10 @@ internal void UpdateDiskCounters() { // io busy time = (1 - (% idle time / 100)) * elapsed seconds float idleTimePercentage = Math.Min(counter.NextValue(), 100f); - double busyTimeSeconds = (1 - (idleTimePercentage / 100f)) * elapsedSeconds; - TotalSeconds[counter.InstanceName] += busyTimeSeconds; + double busyTimeTicks = (1 - (idleTimePercentage / 100f)) * (double)elapsedTimeTicks; + TotalSeconds[counter.InstanceName] += busyTimeTicks / TimeSpan.TicksPerSecond; } - _lastTimestamp = currentTimestamp; + _lastTimeTick = currentTimeTicks; } } From 1e6b19fc8c517d3500258bbc84384b067d2c9df6 Mon Sep 17 00:00:00 2001 From: Makazeu Date: Sun, 4 May 2025 22:08:23 +0800 Subject: [PATCH 5/6] update --- .../Windows/Disk/WindowsDiskIoRatePerfCounter.cs | 8 ++++---- .../Windows/Disk/WindowsDiskIoTimePerfCounter.cs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Disk/WindowsDiskIoRatePerfCounter.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Disk/WindowsDiskIoRatePerfCounter.cs index eceb907c6df..841c4a05b86 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Disk/WindowsDiskIoRatePerfCounter.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Disk/WindowsDiskIoRatePerfCounter.cs @@ -17,7 +17,7 @@ internal sealed class WindowsDiskIoRatePerfCounter private readonly string _categoryName; private readonly string _counterName; private readonly string[] _instanceNames; - private long _lastTimeTick; + private long _lastTimeTicks; internal WindowsDiskIoRatePerfCounter( IPerformanceCounterFactory performanceCounterFactory, @@ -54,13 +54,13 @@ internal void InitializeDiskCounters() _ = counter.NextValue(); } - _lastTimeTick = _timeProvider.GetUtcNow().Ticks; + _lastTimeTicks = _timeProvider.GetUtcNow().Ticks; } internal void UpdateDiskCounters() { long currentTimeTicks = _timeProvider.GetUtcNow().Ticks; - long elapsedTimeTicks = currentTimeTicks - _lastTimeTick; + long elapsedTimeTicks = currentTimeTicks - _lastTimeTicks; // For the kind of "rate" perf counters, this algorithm calculates the total value over a time interval // by multiplying the per-second rate (e.g., Disk Bytes/sec) by the time interval between two samples. @@ -73,6 +73,6 @@ internal void UpdateDiskCounters() TotalCountDict[counter.InstanceName] += (long)value / TimeSpan.TicksPerSecond; } - _lastTimeTick = currentTimeTicks; + _lastTimeTicks = currentTimeTicks; } } diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Disk/WindowsDiskIoTimePerfCounter.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Disk/WindowsDiskIoTimePerfCounter.cs index f8e3e424aa5..2b23c300423 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Disk/WindowsDiskIoTimePerfCounter.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Disk/WindowsDiskIoTimePerfCounter.cs @@ -15,7 +15,7 @@ internal sealed class WindowsDiskIoTimePerfCounter private readonly string _categoryName; private readonly string _counterName; private readonly string[] _instanceNames; - private long _lastTimeTick; + private long _lastTimeTicks; internal WindowsDiskIoTimePerfCounter( IPerformanceCounterFactory performanceCounterFactory, @@ -52,13 +52,13 @@ internal void InitializeDiskCounters() _ = counter.NextValue(); } - _lastTimeTick = _timeProvider.GetUtcNow().Ticks; + _lastTimeTicks = _timeProvider.GetUtcNow().Ticks; } internal void UpdateDiskCounters() { long currentTimeTicks = _timeProvider.GetUtcNow().Ticks; - long elapsedTimeTicks = currentTimeTicks - _lastTimeTick; + long elapsedTimeTicks = currentTimeTicks - _lastTimeTicks; // The real elapsed time ("wall clock") used in the I/O path (time from operations running in parallel are not counted). // Measured as the complement of "Disk\% Idle Time" performance counter: uptime * (100 - "Disk\% Idle Time") / 100 @@ -71,6 +71,6 @@ internal void UpdateDiskCounters() TotalSeconds[counter.InstanceName] += busyTimeTicks / TimeSpan.TicksPerSecond; } - _lastTimeTick = currentTimeTicks; + _lastTimeTicks = currentTimeTicks; } } From 9dbc8fe83c3ac764209231c78dffb2f2b2ecfc45 Mon Sep 17 00:00:00 2001 From: Makazeu Date: Sun, 4 May 2025 22:39:09 +0800 Subject: [PATCH 6/6] re-run the pipeline --- .../Linux/Network/LinuxNetworkUtilizationParser.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkUtilizationParser.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkUtilizationParser.cs index 3b9860b31a7..5ca8fb6a20d 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkUtilizationParser.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkUtilizationParser.cs @@ -18,12 +18,12 @@ internal sealed class LinuxNetworkUtilizationParser private static readonly ObjectPool> _sharedBufferWriterPool = BufferWriterPool.CreateBufferWriterPool(); /// - /// File that provide information about currently active TCP_IPv4 connections. + /// File that provides information about currently active TCP_IPv4 connections. /// private static readonly FileInfo _tcp = new("/proc/net/tcp"); /// - /// File that provide information about currently active TCP_IPv6 connections. + /// File that provides information about currently active TCP_IPv6 connections. /// private static readonly FileInfo _tcp6 = new("/proc/net/tcp6");