diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/CompatibilitySuppressions.xml b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/CompatibilitySuppressions.xml
new file mode 100644
index 00000000000..6526176c304
--- /dev/null
+++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/CompatibilitySuppressions.xml
@@ -0,0 +1,46 @@
+
+
+
+
+ CP0002
+ M:Microsoft.Extensions.Diagnostics.ResourceMonitoring.ResourceMonitoringOptions.get_EnableDiskIoMetrics
+ lib/net462/Microsoft.Extensions.Diagnostics.ResourceMonitoring.dll
+ lib/net462/Microsoft.Extensions.Diagnostics.ResourceMonitoring.dll
+ true
+
+
+ CP0002
+ M:Microsoft.Extensions.Diagnostics.ResourceMonitoring.ResourceMonitoringOptions.set_EnableDiskIoMetrics(System.Boolean)
+ lib/net462/Microsoft.Extensions.Diagnostics.ResourceMonitoring.dll
+ lib/net462/Microsoft.Extensions.Diagnostics.ResourceMonitoring.dll
+ true
+
+
+ CP0002
+ M:Microsoft.Extensions.Diagnostics.ResourceMonitoring.ResourceMonitoringOptions.get_EnableDiskIoMetrics
+ lib/net8.0/Microsoft.Extensions.Diagnostics.ResourceMonitoring.dll
+ lib/net8.0/Microsoft.Extensions.Diagnostics.ResourceMonitoring.dll
+ true
+
+
+ CP0002
+ M:Microsoft.Extensions.Diagnostics.ResourceMonitoring.ResourceMonitoringOptions.set_EnableDiskIoMetrics(System.Boolean)
+ lib/net8.0/Microsoft.Extensions.Diagnostics.ResourceMonitoring.dll
+ lib/net8.0/Microsoft.Extensions.Diagnostics.ResourceMonitoring.dll
+ true
+
+
+ CP0002
+ M:Microsoft.Extensions.Diagnostics.ResourceMonitoring.ResourceMonitoringOptions.get_EnableDiskIoMetrics
+ lib/net9.0/Microsoft.Extensions.Diagnostics.ResourceMonitoring.dll
+ lib/net9.0/Microsoft.Extensions.Diagnostics.ResourceMonitoring.dll
+ true
+
+
+ CP0002
+ M:Microsoft.Extensions.Diagnostics.ResourceMonitoring.ResourceMonitoringOptions.set_EnableDiskIoMetrics(System.Boolean)
+ lib/net9.0/Microsoft.Extensions.Diagnostics.ResourceMonitoring.dll
+ lib/net9.0/Microsoft.Extensions.Diagnostics.ResourceMonitoring.dll
+ true
+
+
\ No newline at end of file
diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Disk/DiskStats.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Disk/DiskStats.cs
new file mode 100644
index 00000000000..5b1315e7a50
--- /dev/null
+++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Disk/DiskStats.cs
@@ -0,0 +1,36 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Disk;
+
+///
+/// Represents one line of statistics from "/proc/diskstats"
+/// See https://www.kernel.org/doc/Documentation/ABI/testing/procfs-diskstats for details.
+///
+internal sealed class DiskStats
+{
+ public int MajorNumber { get; set; }
+ public int MinorNumber { get; set; }
+ public string DeviceName { get; set; } = string.Empty;
+ public ulong ReadsCompleted { get; set; }
+ public ulong ReadsMerged { get; set; }
+ public ulong SectorsRead { get; set; }
+ public uint TimeReadingMs { get; set; }
+ public ulong WritesCompleted { get; set; }
+ public ulong WritesMerged { get; set; }
+ public ulong SectorsWritten { get; set; }
+ public uint TimeWritingMs { get; set; }
+ public uint IoInProgress { get; set; }
+ public uint TimeIoMs { get; set; }
+ public uint WeightedTimeIoMs { get; set; }
+
+ // The following fields are available starting from kernel 4.18; if absent, remain 0
+ public ulong DiscardsCompleted { get; set; }
+ public ulong DiscardsMerged { get; set; }
+ public ulong SectorsDiscarded { get; set; }
+ public uint TimeDiscardingMs { get; set; }
+
+ // The following fields are available starting from kernel 5.5; if absent, remain 0
+ public ulong FlushRequestsCompleted { get; set; }
+ public uint TimeFlushingMs { get; set; }
+}
diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Disk/DiskStatsReader.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Disk/DiskStatsReader.cs
new file mode 100644
index 00000000000..11a6f72ccc8
--- /dev/null
+++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Disk/DiskStatsReader.cs
@@ -0,0 +1,111 @@
+// 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.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.IO;
+using Microsoft.Extensions.ObjectPool;
+using Microsoft.Shared.Pools;
+
+namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Disk;
+
+///
+/// Handles reading and parsing of Linux procfs-diskstats file(/proc/diskstats).
+///
+internal sealed class DiskStatsReader(IFileSystem fileSystem) : IDiskStatsReader
+{
+ private static readonly FileInfo _diskStatsFile = new("/proc/diskstats");
+ private static readonly ObjectPool> _sharedBufferWriterPool = BufferWriterPool.CreateBufferWriterPool();
+
+ ///
+ /// Reads and returns all disk statistics entries.
+ ///
+ /// List of .
+ public List ReadAll()
+ {
+ var diskStatsList = new List();
+
+ using ReturnableBufferWriter bufferWriter = new(_sharedBufferWriterPool);
+ using IEnumerator> enumerableLines = fileSystem.ReadAllByLines(_diskStatsFile, bufferWriter.Buffer).GetEnumerator();
+
+ while (enumerableLines.MoveNext())
+ {
+ string line = enumerableLines.Current.Trim().ToString();
+ if (string.IsNullOrWhiteSpace(line))
+ {
+ continue;
+ }
+
+ try
+ {
+ DiskStats stat = DiskStatsReader.ParseLine(line);
+ diskStatsList.Add(stat);
+ }
+#pragma warning disable CA1031
+ catch (Exception)
+#pragma warning restore CA1031
+ {
+ // ignore parsing errors
+ }
+ }
+
+ return diskStatsList;
+ }
+
+ ///
+ /// Parses one line of text into a DiskStats object.
+ ///
+ /// one line in "/proc/diskstats".
+ /// parsed DiskStats object.
+ [SuppressMessage("Major Code Smell", "S109:Magic numbers should not be used", Justification = "These numbers represent fixed field indices in the Linux /proc/diskstats format")]
+ private static DiskStats ParseLine(string line)
+ {
+ // Split by any whitespace and remove empty entries
+#pragma warning disable EA0009
+ string[] parts = line.Split(Array.Empty(), StringSplitOptions.RemoveEmptyEntries);
+#pragma warning restore EA0009
+
+ if (parts.Length < 14)
+ {
+ throw new FormatException($"Not enough fields: expected at least 14, got {parts.Length}");
+ }
+
+ // See https://www.kernel.org/doc/Documentation/ABI/testing/procfs-diskstats
+ var diskStats = new DiskStats
+ {
+ MajorNumber = int.Parse(parts[0], CultureInfo.InvariantCulture),
+ MinorNumber = int.Parse(parts[1], CultureInfo.InvariantCulture),
+ DeviceName = parts[2],
+ ReadsCompleted = ulong.Parse(parts[3], CultureInfo.InvariantCulture),
+ ReadsMerged = ulong.Parse(parts[4], CultureInfo.InvariantCulture),
+ SectorsRead = ulong.Parse(parts[5], CultureInfo.InvariantCulture),
+ TimeReadingMs = uint.Parse(parts[6], CultureInfo.InvariantCulture),
+ WritesCompleted = ulong.Parse(parts[7], CultureInfo.InvariantCulture),
+ WritesMerged = ulong.Parse(parts[8], CultureInfo.InvariantCulture),
+ SectorsWritten = ulong.Parse(parts[9], CultureInfo.InvariantCulture),
+ TimeWritingMs = uint.Parse(parts[10], CultureInfo.InvariantCulture),
+ IoInProgress = uint.Parse(parts[11], CultureInfo.InvariantCulture),
+ TimeIoMs = uint.Parse(parts[12], CultureInfo.InvariantCulture),
+ WeightedTimeIoMs = uint.Parse(parts[13], CultureInfo.InvariantCulture)
+ };
+
+ // Parse additional fields if present
+ if (parts.Length >= 18)
+ {
+ diskStats.DiscardsCompleted = ulong.Parse(parts[14], CultureInfo.InvariantCulture);
+ diskStats.DiscardsMerged = ulong.Parse(parts[15], CultureInfo.InvariantCulture);
+ diskStats.SectorsDiscarded = ulong.Parse(parts[16], CultureInfo.InvariantCulture);
+ diskStats.TimeDiscardingMs = uint.Parse(parts[17], CultureInfo.InvariantCulture);
+ }
+
+ if (parts.Length >= 20)
+ {
+ diskStats.FlushRequestsCompleted = ulong.Parse(parts[18], CultureInfo.InvariantCulture);
+ diskStats.TimeFlushingMs = uint.Parse(parts[19], CultureInfo.InvariantCulture);
+ }
+
+ return diskStats;
+ }
+}
diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Disk/IDiskStatsReader.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Disk/IDiskStatsReader.cs
new file mode 100644
index 00000000000..df9d0d7c020
--- /dev/null
+++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Disk/IDiskStatsReader.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+
+namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Disk;
+
+///
+/// An interface for reading disk statistics.
+///
+internal interface IDiskStatsReader
+{
+ ///
+ /// Gets all the disk statistics from the system.
+ ///
+ /// List of instances.
+ List ReadAll();
+}
diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Disk/LinuxSystemDiskMetrics.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Disk/LinuxSystemDiskMetrics.cs
new file mode 100644
index 00000000000..d70a65ed1b0
--- /dev/null
+++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Disk/LinuxSystemDiskMetrics.cs
@@ -0,0 +1,172 @@
+// 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.Generic;
+using System.Diagnostics;
+using System.Diagnostics.Metrics;
+using System.Linq;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Extensions.Options;
+using Microsoft.Shared.Instruments;
+
+namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Disk;
+
+internal sealed class LinuxSystemDiskMetrics
+{
+ // The kernel's block layer always reports counts in 512-byte "sectors" regardless of the underlying device's real block size
+ // https://docs.kernel.org/block/stat.html#read-sectors-write-sectors-discard-sectors
+ private const int LinuxDiskSectorSize = 512;
+ private const int MinimumDiskStatsRefreshIntervalInSeconds = 10;
+ private const string DeviceKey = "system.device";
+ private const string DirectionKey = "disk.io.direction";
+
+ private static readonly KeyValuePair _directionReadTag = new(DirectionKey, "read");
+ private static readonly KeyValuePair _directionWriteTag = new(DirectionKey, "write");
+ private readonly ILogger _logger;
+ private readonly TimeProvider _timeProvider;
+ private readonly IDiskStatsReader _diskStatsReader;
+ private readonly object _lock = new();
+ private readonly Dictionary _baselineDiskStatsDict = [];
+ private List _diskStatsSnapshot = [];
+ private DateTimeOffset _lastRefreshTime = DateTimeOffset.MinValue;
+
+ public LinuxSystemDiskMetrics(
+ ILogger? logger,
+ IMeterFactory meterFactory,
+ IOptions options,
+ TimeProvider timeProvider,
+ IDiskStatsReader diskStatsReader)
+ {
+ _logger = logger ?? NullLogger.Instance;
+ _timeProvider = timeProvider;
+ _diskStatsReader = diskStatsReader;
+ if (!options.Value.EnableSystemDiskIoMetrics)
+ {
+ return;
+ }
+
+ // We need to read the disk stats once to get the baseline values
+ _baselineDiskStatsDict = GetAllDiskStats().ToDictionary(d => d.DeviceName);
+
+#pragma warning disable CA2000 // Dispose objects before losing scope
+ // We don't dispose the meter because IMeterFactory handles that
+ // It's a false-positive, see: https://github.com/dotnet/roslyn-analyzers/issues/6912.
+ // Related documentation: https://github.com/dotnet/docs/pull/37170
+ Meter meter = meterFactory.Create(ResourceUtilizationInstruments.MeterName);
+#pragma warning restore CA2000 // Dispose objects before losing scope
+
+ // The metric is aligned with
+ // https://opentelemetry.io/docs/specs/semconv/system/system-metrics/#metric-systemdiskio
+ _ = meter.CreateObservableCounter(
+ ResourceUtilizationInstruments.SystemDiskIo,
+ GetDiskIoMeasurements,
+ unit: "By",
+ description: "Disk bytes transferred");
+
+ // The metric is aligned with
+ // 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 IEnumerable> GetDiskIoMeasurements()
+ {
+ List> measurements = [];
+ List diskStatsSnapshot = GetDiskStatsSnapshot();
+
+ foreach (DiskStats diskStats in diskStatsSnapshot)
+ {
+ _ = _baselineDiskStatsDict.TryGetValue(diskStats.DeviceName, out DiskStats? baselineDiskStats);
+ long readBytes = (long)(diskStats.SectorsRead - baselineDiskStats?.SectorsRead ?? 0L) * LinuxDiskSectorSize;
+ long writeBytes = (long)(diskStats.SectorsWritten - baselineDiskStats?.SectorsWritten ?? 0L) * LinuxDiskSectorSize;
+ measurements.Add(new Measurement(readBytes, new TagList { _directionReadTag, new(DeviceKey, diskStats.DeviceName) }));
+ measurements.Add(new Measurement(writeBytes, new TagList { _directionWriteTag, new(DeviceKey, diskStats.DeviceName) }));
+ }
+
+ return measurements;
+ }
+
+ private IEnumerable> GetDiskOperationMeasurements()
+ {
+ List> measurements = [];
+ List diskStatsSnapshot = GetDiskStatsSnapshot();
+
+ foreach (DiskStats diskStats in diskStatsSnapshot)
+ {
+ _ = _baselineDiskStatsDict.TryGetValue(diskStats.DeviceName, out DiskStats? baselineDiskStats);
+ long readCount = (long)(diskStats.ReadsCompleted - baselineDiskStats?.ReadsCompleted ?? 0L);
+ long writeCount = (long)(diskStats.WritesCompleted - baselineDiskStats?.WritesCompleted ?? 0L);
+ measurements.Add(new Measurement(readCount, new TagList { _directionReadTag, new(DeviceKey, diskStats.DeviceName) }));
+ measurements.Add(new Measurement(writeCount, new TagList { _directionWriteTag, new(DeviceKey, diskStats.DeviceName) }));
+ }
+
+ return measurements;
+ }
+
+ private IEnumerable> GetDiskIoTimeMeasurements()
+ {
+ List> measurements = [];
+ List diskStatsSnapshot = GetDiskStatsSnapshot();
+
+ foreach (DiskStats diskStats in diskStatsSnapshot)
+ {
+ _ = _baselineDiskStatsDict.TryGetValue(diskStats.DeviceName, out DiskStats? baselineDiskStats);
+ double ioTimeSeconds = (diskStats.TimeIoMs - baselineDiskStats?.TimeIoMs ?? 0) / 1000.0; // Convert to seconds
+ measurements.Add(new Measurement(ioTimeSeconds, new TagList { new(DeviceKey, diskStats.DeviceName) }));
+ }
+
+ return measurements;
+ }
+
+ private List GetDiskStatsSnapshot()
+ {
+ lock (_lock)
+ {
+ DateTimeOffset now = _timeProvider.GetUtcNow();
+ if (_diskStatsSnapshot.Count == 0 || (now - _lastRefreshTime).TotalSeconds > MinimumDiskStatsRefreshIntervalInSeconds)
+ {
+ _diskStatsSnapshot = GetAllDiskStats();
+ _lastRefreshTime = now;
+ }
+ }
+
+ return _diskStatsSnapshot;
+ }
+
+ private List GetAllDiskStats()
+ {
+ try
+ {
+ List diskStatsList = _diskStatsReader.ReadAll();
+
+ // We should not include ram, loop, or dm(device-mapper) devices in the disk stats, should we?
+ diskStatsList = diskStatsList
+ .Where(d => !d.DeviceName.StartsWith("ram", StringComparison.OrdinalIgnoreCase)
+ && !d.DeviceName.StartsWith("loop", StringComparison.OrdinalIgnoreCase)
+ && !d.DeviceName.StartsWith("dm-", StringComparison.OrdinalIgnoreCase))
+ .ToList();
+ return diskStatsList;
+ }
+#pragma warning disable CA1031
+ catch (Exception ex)
+#pragma warning restore CA1031
+ {
+ Log.HandleDiskStatsException(_logger, ex.Message);
+ }
+
+ return [];
+ }
+}
diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Log.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Log.cs
index 918087b1b78..d2f9c8f5070 100644
--- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Log.cs
+++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Log.cs
@@ -56,4 +56,8 @@ public static partial void CounterMessage100(
public static partial void CounterMessage110(
ILogger logger,
long counterValue);
+
+ [LoggerMessage(7, LogLevel.Warning,
+ "Error while getting disk stats: Error={errorMessage}")]
+ public static partial void HandleDiskStatsException(ILogger logger, string errorMessage);
}
diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceMonitoringOptions.Windows.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceMonitoringOptions.Windows.cs
index f042d892ab1..9e8636506c7 100644
--- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceMonitoringOptions.Windows.cs
+++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceMonitoringOptions.Windows.cs
@@ -10,12 +10,6 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring;
public partial class ResourceMonitoringOptions
{
- ///
- /// Gets or sets a value indicating whether disk I/O metrics should be enabled.
- ///
- [Experimental(diagnosticId: DiagnosticIds.Experiments.ResourceMonitoring, UrlFormat = DiagnosticIds.UrlFormat)]
- public bool EnableDiskIoMetrics { get; set; }
-
///
/// Gets or sets the list of source IPv4 addresses to track the connections for in telemetry.
///
diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceMonitoringOptions.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceMonitoringOptions.cs
index 3946add711d..420d6001f57 100644
--- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceMonitoringOptions.cs
+++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceMonitoringOptions.cs
@@ -117,4 +117,11 @@ public partial class ResourceMonitoringOptions
///
[Experimental(diagnosticId: DiagnosticIds.Experiments.ResourceMonitoring, UrlFormat = DiagnosticIds.UrlFormat)]
public bool UseDeltaNrPeriodsForCpuCalculation { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether disk I/O metrics should be enabled.
+ ///
+ /// Previously EnableDiskIoMetrics.
+ [Experimental(diagnosticId: DiagnosticIds.Experiments.ResourceMonitoring, UrlFormat = DiagnosticIds.UrlFormat)]
+ public bool EnableSystemDiskIoMetrics { get; set; }
}
diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceMonitoringServiceCollectionExtensions.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceMonitoringServiceCollectionExtensions.cs
index f018038c614..541984db78d 100644
--- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceMonitoringServiceCollectionExtensions.cs
+++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceMonitoringServiceCollectionExtensions.cs
@@ -8,6 +8,7 @@
using Microsoft.Extensions.Diagnostics.ResourceMonitoring;
#if !NETFRAMEWORK
using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux;
+using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Disk;
using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network;
#endif
@@ -129,6 +130,7 @@ private static ResourceMonitorBuilder AddLinuxProvider(this ResourceMonitorBuild
builder.Services.TryAddActivatedSingleton();
+ builder.Services.TryAddSingleton(TimeProvider.System);
builder.Services.TryAddSingleton();
builder.Services.TryAddSingleton();
builder.PickLinuxParser();
@@ -136,7 +138,9 @@ private static ResourceMonitorBuilder AddLinuxProvider(this ResourceMonitorBuild
_ = builder.Services
.AddActivatedSingleton()
.AddActivatedSingleton()
- .AddActivatedSingleton();
+ .AddActivatedSingleton()
+ .AddActivatedSingleton()
+ .AddActivatedSingleton();
return builder;
}
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 47706b3ce6a..2927fa657e3 100644
--- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Disk/WindowsDiskMetrics.cs
+++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Disk/WindowsDiskMetrics.cs
@@ -34,7 +34,7 @@ public WindowsDiskMetrics(
IOptions options)
{
_logger = logger ?? NullLogger.Instance;
- if (!options.Value.EnableDiskIoMetrics)
+ if (!options.Value.EnableSystemDiskIoMetrics)
{
return;
}
@@ -73,6 +73,7 @@ public WindowsDiskMetrics(
description: "Time disk spent activated");
}
+#pragma warning disable CA1031 // Do not catch general exception types
private void InitializeDiskCounters(IPerformanceCounterFactory performanceCounterFactory, TimeProvider timeProvider)
{
const string DiskCategoryName = "LogicalDisk";
@@ -96,9 +97,7 @@ private void InitializeDiskCounters(IPerformanceCounterFactory performanceCounte
ioTimePerfCounter.InitializeDiskCounters();
_diskIoTimePerfCounter = ioTimePerfCounter;
}
-#pragma warning disable CA1031
catch (Exception ex)
-#pragma warning restore CA1031
{
Log.DiskIoPerfCounterException(_logger, WindowsDiskPerfCounterNames.DiskIdleTimeCounter, ex.Message);
}
@@ -124,14 +123,13 @@ private void InitializeDiskCounters(IPerformanceCounterFactory performanceCounte
ratePerfCounter.InitializeDiskCounters();
_diskIoRateCounters.Add(counterName, ratePerfCounter);
}
-#pragma warning disable CA1031
catch (Exception ex)
-#pragma warning restore CA1031
{
Log.DiskIoPerfCounterException(_logger, counterName, ex.Message);
}
}
}
+#pragma warning restore CA1031 // Do not catch general exception types
private IEnumerable> GetDiskIoMeasurements()
{
diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Disk/DiskStatsReaderTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Disk/DiskStatsReaderTests.cs
new file mode 100644
index 00000000000..c5098b2d284
--- /dev/null
+++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Disk/DiskStatsReaderTests.cs
@@ -0,0 +1,135 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Test;
+using Microsoft.TestUtilities;
+using Xunit;
+
+namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Disk.Test;
+
+[OSSkipCondition(OperatingSystems.Windows | OperatingSystems.MacOSX, SkipReason = "Linux specific tests")]
+public class DiskStatsReaderTests
+{
+ [Fact]
+ public void Test_ReadAll_Valid_DiskStats()
+ {
+ string diskStatsFileContent =
+ " 7 0 loop0 269334 0 12751202 147117 11604772 0 97447664 1402945 0 12193892 2255752 0 0 0 0 1206808 705690\n" +
+ " 7 1 loop1 965348 0 28605866 474103 73636257 0 1211288288 14086242 0 60580032 24777643 0 0 0 0 18723136 10217297\n" +
+ " 7 2 loop2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n" +
+ " 259 1 nvme1n1 4180498 5551 247430002 746099 96474435 12677267 2160066791 23514624 0 68786140 29777259 0 0 0 0 22111407 5516535\n" +
+ " 259 2 nvme1n1p1 4180387 5551 247422458 746080 96474435 12677267 2160066791 23514624 0 68786108 24260705 0 0 0 0 0 0\n" +
+ " 259 0 nvme0n1 6090587 689465 1120208521 1810566 19069165 8947684 406356430 3897150 0 38134844 6246643 69106 0 271818368 23139 1659742 515787\n" +
+ " 259 3 nvme0n1p1 378 0 26406 96 0 0 0 0 0 760 96 0 0 0 0 0 0\n" +
+ " 259 4 nvme0n1p2 7301 26408 116617 3628 600 47 59970 98 0 1196 3767 48 0 33106424 40 0 0\n" +
+ " 259 5 nvme0n1p3 6079544 663057 1119819306 1806337 19068535 8947637 406296460 3897045 0 38130316 5726482 69058 0 238711944 23098 0 0\n" +
+ " 252 0 dm-0 1303410 0 10434296 166616 1812455 0 14879824 1213588 0 397256 1380204 0 0 0 0 0 0\n" +
+ " 252 1 dm-1 712122 0 38299466 140852 18159197 0 286348832 1552768 0 14182384 1716692 69058 0 238711944 23072 0 0\n" +
+ " 252 5 dm-5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n" +
+ " 252 7 dm-7 6828 0 360325 2100 14438 0 1149672 1508 0 7524 3608 0 0 0 0 0 0\n" +
+ " 8 0 sda 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n" +
+ " 252 8 dm-8 100601 0 2990980 23940 3097278 0 32037680 1410540 0 5488608 1434496 513 0 67108872 16 0 0\n";
+
+ var fileSystem = new HardcodedValueFileSystem(new Dictionary
+ {
+ { new FileInfo("/proc/diskstats"), diskStatsFileContent }
+ });
+
+ var reader = new DiskStatsReader(fileSystem);
+ var dictionary = reader.ReadAll().ToDictionary(x => x.DeviceName);
+ Assert.Equal(15, dictionary.Count);
+
+ var disk1 = dictionary["nvme0n1"];
+ Assert.Equal(6_090_587u, disk1.ReadsCompleted);
+ Assert.Equal(689_465u, disk1.ReadsMerged);
+ Assert.Equal(1_120_208_521u, disk1.SectorsRead);
+ Assert.Equal(1_810_566u, disk1.TimeReadingMs);
+ Assert.Equal(19_069_165u, disk1.WritesCompleted);
+ Assert.Equal(8_947_684u, disk1.WritesMerged);
+ Assert.Equal(406_356_430u, disk1.SectorsWritten);
+ Assert.Equal(3_897_150u, disk1.TimeWritingMs);
+ Assert.Equal(0u, disk1.IoInProgress);
+ Assert.Equal(38_134_844u, disk1.TimeIoMs);
+ Assert.Equal(6_246_643u, disk1.WeightedTimeIoMs);
+ Assert.Equal(69_106u, disk1.DiscardsCompleted);
+ Assert.Equal(0u, disk1.DiscardsMerged);
+ Assert.Equal(271_818_368u, disk1.SectorsDiscarded);
+ Assert.Equal(23_139u, disk1.TimeDiscardingMs);
+ Assert.Equal(1_659_742u, disk1.FlushRequestsCompleted);
+ Assert.Equal(515_787u, disk1.TimeFlushingMs);
+
+ var disk2 = dictionary["dm-8"];
+ Assert.Equal(100_601u, disk2.ReadsCompleted);
+ Assert.Equal(0u, disk2.ReadsMerged);
+ Assert.Equal(2_990_980u, disk2.SectorsRead);
+ Assert.Equal(23_940u, disk2.TimeReadingMs);
+ Assert.Equal(3_097_278u, disk2.WritesCompleted);
+ Assert.Equal(0u, disk2.WritesMerged);
+ Assert.Equal(32_037_680u, disk2.SectorsWritten);
+ Assert.Equal(1_410_540u, disk2.TimeWritingMs);
+ Assert.Equal(0u, disk2.IoInProgress);
+ Assert.Equal(5_488_608u, disk2.TimeIoMs);
+ Assert.Equal(1_434_496u, disk2.WeightedTimeIoMs);
+
+ var disk3 = dictionary["sda"];
+ Assert.Equal(0u, disk3.ReadsCompleted);
+ Assert.Equal(0u, disk3.ReadsMerged);
+ Assert.Equal(0u, disk3.SectorsRead);
+ Assert.Equal(0u, disk3.TimeReadingMs);
+ Assert.Equal(0u, disk3.WritesCompleted);
+ Assert.Equal(0u, disk3.WritesMerged);
+ Assert.Equal(0u, disk3.SectorsWritten);
+ Assert.Equal(0u, disk3.TimeWritingMs);
+ Assert.Equal(0u, disk3.IoInProgress);
+ Assert.Equal(0u, disk3.TimeIoMs);
+ Assert.Equal(0u, disk3.WeightedTimeIoMs);
+ }
+
+ [Fact]
+ public void Test_ReadAll_With_Invalid_Lines()
+ {
+ string diskStatsFileContent =
+ " 259 1 nvme1n1 4180498 5551 247430002 746099 96474435 12677267 2160066791 23514624 0 68786140 29777259 0 0 0 0 22111407 5516535\n" +
+ " 259 2 nvme1n1p1 4180387 5551 247422458\n" +
+ " 259 2 nvme1n1p1 4180387 5551 247422458 746080 96474435 12677267 2160066791 23514624 0 68786108 24260705 0 0 0 0 0 0\n" +
+ " 259 0 nvme0n1 6090587 689465 1120208521 1810566 19069165 8947684 406356430 3897150 0 38134844 6246643 69106 0 271818368 23139 1659742 515787\n" +
+ " 259 nvme0n1p1 378 0 26406 96 0 0 0 0 0 760 96 0 0 0 0 0 0\n";
+
+ var fileSystem = new HardcodedValueFileSystem(new Dictionary
+ {
+ { new FileInfo("/proc/diskstats"), diskStatsFileContent }
+ });
+
+ var reader = new DiskStatsReader(fileSystem);
+ var dictionary = reader.ReadAll().ToDictionary(x => x.DeviceName);
+ Assert.Equal(3, dictionary.Count);
+
+ var disk1 = dictionary["nvme1n1"];
+ Assert.Equal(4_180_498u, disk1.ReadsCompleted);
+ Assert.Equal(5_551u, disk1.ReadsMerged);
+ Assert.Equal(247_430_002u, disk1.SectorsRead);
+ Assert.Equal(746_099u, disk1.TimeReadingMs);
+ Assert.Equal(96_474_435u, disk1.WritesCompleted);
+ Assert.Equal(12_677_267u, disk1.WritesMerged);
+ Assert.Equal(2_160_066_791u, disk1.SectorsWritten);
+ Assert.Equal(23_514_624u, disk1.TimeWritingMs);
+ Assert.Equal(0u, disk1.IoInProgress);
+ Assert.Equal(68_786_140u, disk1.TimeIoMs);
+ Assert.Equal(29_777_259u, disk1.WeightedTimeIoMs);
+ Assert.Equal(0u, disk1.DiscardsCompleted);
+ Assert.Equal(0u, disk1.DiscardsMerged);
+ Assert.Equal(0u, disk1.SectorsDiscarded);
+ Assert.Equal(0u, disk1.TimeDiscardingMs);
+ Assert.Equal(22_111_407u, disk1.FlushRequestsCompleted);
+ Assert.Equal(5_516_535u, disk1.TimeFlushingMs);
+
+ var disk2 = dictionary["nvme1n1p1"];
+ Assert.NotNull(disk2);
+
+ var disk3 = dictionary["nvme0n1"];
+ Assert.NotNull(disk3);
+ }
+}
diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Disk/FakeDiskStatsReader.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Disk/FakeDiskStatsReader.cs
new file mode 100644
index 00000000000..1c69be74709
--- /dev/null
+++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Disk/FakeDiskStatsReader.cs
@@ -0,0 +1,25 @@
+// 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.Generic;
+using System.Linq;
+
+namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Disk.Test;
+
+internal class FakeDiskStatsReader(Dictionary> stats) : IDiskStatsReader
+{
+ private int _index;
+
+ public List ReadAll()
+ {
+ if (_index >= stats.Values.First().Count)
+ {
+ throw new InvalidOperationException("No more values available.");
+ }
+
+ List list = stats.Values.Select(x => x[_index]).ToList();
+ _index++;
+ return list;
+ }
+}
diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Disk/LinuxSystemDiskMetricsTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Disk/LinuxSystemDiskMetricsTests.cs
new file mode 100644
index 00000000000..c6aba8e0b53
--- /dev/null
+++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Disk/LinuxSystemDiskMetricsTests.cs
@@ -0,0 +1,216 @@
+// 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.Generic;
+using System.Diagnostics.Metrics;
+using System.Linq;
+using Microsoft.Extensions.Diagnostics.Metrics.Testing;
+using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Test.Helpers;
+using Microsoft.Extensions.Logging.Testing;
+using Microsoft.Extensions.Time.Testing;
+using Microsoft.Shared.Instruments;
+using Microsoft.TestUtilities;
+using Moq;
+using Xunit;
+
+namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Disk.Test;
+
+[OSSkipCondition(OperatingSystems.Windows | OperatingSystems.MacOSX, SkipReason = "Linux specific tests")]
+public class LinuxSystemDiskMetricsTests
+{
+ private readonly FakeLogger _fakeLogger = new();
+
+ [Fact]
+ public void Creates_Meter_With_Correct_Name()
+ {
+ using var meterFactory = new TestMeterFactory();
+ var diskStatsReaderMock = new Mock();
+ var options = new ResourceMonitoringOptions { EnableSystemDiskIoMetrics = true };
+
+ _ = new LinuxSystemDiskMetrics(
+ _fakeLogger,
+ meterFactory,
+ Options.Options.Create(options),
+ TimeProvider.System,
+ diskStatsReaderMock.Object);
+
+ Meter meter = meterFactory.Meters.Single();
+ Assert.Equal(ResourceUtilizationInstruments.MeterName, meter.Name);
+ }
+
+ [Fact]
+ public void Test_MetricValues()
+ {
+ using var meterFactory = new TestMeterFactory();
+ var fakeTimeProvider = new FakeTimeProvider();
+ var options = new ResourceMonitoringOptions { EnableSystemDiskIoMetrics = true };
+
+ // Set up
+ var diskStatsReader = new FakeDiskStatsReader(new Dictionary>
+ {
+ {
+ "sda", [
+ new DiskStats
+ {
+ DeviceName = "sda",
+ SectorsRead = 0,
+ SectorsWritten = 0,
+ ReadsCompleted = 0,
+ WritesCompleted = 0,
+ TimeIoMs = 0
+ },
+ new DiskStats
+ {
+ DeviceName = "sda",
+ SectorsRead = 500,
+ SectorsWritten = 1000,
+ ReadsCompleted = 600,
+ WritesCompleted = 1200,
+ TimeIoMs = 1234
+ },
+ new DiskStats
+ {
+ DeviceName = "sda",
+ SectorsRead = 700,
+ SectorsWritten = 1100,
+ ReadsCompleted = 800,
+ WritesCompleted = 1300,
+ TimeIoMs = 2234
+ },
+ new DiskStats
+ {
+ DeviceName = "sda",
+ SectorsRead = 1000,
+ SectorsWritten = 1600,
+ ReadsCompleted = 1300,
+ WritesCompleted = 1350,
+ TimeIoMs = 4444
+ }
+ ]
+ },
+ {
+ "sdb", [
+ new DiskStats
+ {
+ DeviceName = "sdb",
+ SectorsRead = 200,
+ SectorsWritten = 300,
+ ReadsCompleted = 400,
+ WritesCompleted = 500,
+ TimeIoMs = 6000
+ },
+ new DiskStats
+ {
+ DeviceName = "sdb",
+ SectorsRead = 350,
+ SectorsWritten = 450,
+ ReadsCompleted = 550,
+ WritesCompleted = 650,
+ TimeIoMs = 7500
+ },
+ new DiskStats
+ {
+ DeviceName = "sdb",
+ SectorsRead = 400,
+ SectorsWritten = 500,
+ ReadsCompleted = 600,
+ WritesCompleted = 700,
+ TimeIoMs = 7500
+ },
+ new DiskStats
+ {
+ DeviceName = "sdb",
+ SectorsRead = 550,
+ SectorsWritten = 650,
+ ReadsCompleted = 750,
+ WritesCompleted = 850,
+ TimeIoMs = 9500
+ }
+ ]
+ },
+ });
+
+ _ = new LinuxSystemDiskMetrics(
+ _fakeLogger,
+ meterFactory,
+ Options.Options.Create(options),
+ fakeTimeProvider,
+ diskStatsReader);
+ Meter meter = meterFactory.Meters.Single();
+
+ var readTag = new KeyValuePair("disk.io.direction", "read");
+ var writeTag = new KeyValuePair("disk.io.direction", "write");
+ var deviceTagSda = new KeyValuePair("system.device", "sda");
+ var deviceTagSdb = new KeyValuePair("system.device", "sdb");
+
+ using var diskIoCollector = new MetricCollector(meter, ResourceUtilizationInstruments.SystemDiskIo);
+ using var operationCollector = new MetricCollector(meter, ResourceUtilizationInstruments.SystemDiskOperations);
+ using var ioTimeCollector = new MetricCollector(meter, ResourceUtilizationInstruments.SystemDiskIoTime);
+
+ // 1st measurement
+ diskIoCollector.RecordObservableInstruments();
+ operationCollector.RecordObservableInstruments();
+ ioTimeCollector.RecordObservableInstruments();
+
+ // Assert the 1st measurement
+ var diskIoMeasurement = diskIoCollector.GetMeasurementSnapshot();
+ Assert.Equal(4, diskIoMeasurement.Count);
+ Assert.Equal(256_000, diskIoMeasurement.Last(x => x.MatchesTags(readTag, deviceTagSda)).Value); // (500 - 0) * 512 = 256000
+ Assert.Equal(76_800, diskIoMeasurement.Last(x => x.MatchesTags(readTag, deviceTagSdb)).Value); // (350 - 200) * 512 = 76800
+ Assert.Equal(512_000, diskIoMeasurement.Last(x => x.MatchesTags(writeTag, deviceTagSda)).Value); // (1000 - 0) * 512 = 512000
+ Assert.Equal(76_800, diskIoMeasurement.Last(x => x.MatchesTags(writeTag, deviceTagSdb)).Value); // (450 - 300) * 512 = 76800
+ var operationMeasurement = operationCollector.GetMeasurementSnapshot();
+ Assert.Equal(4, operationMeasurement.Count);
+ Assert.Equal(600, operationMeasurement.Last(x => x.MatchesTags(readTag, deviceTagSda)).Value); // 600 - 0 = 600
+ Assert.Equal(150, operationMeasurement.Last(x => x.MatchesTags(readTag, deviceTagSdb)).Value); // 550 - 400 = 150
+ Assert.Equal(1200, operationMeasurement.Last(x => x.MatchesTags(writeTag, deviceTagSda)).Value); // 1200 - 0 = 1200
+ Assert.Equal(150, operationMeasurement.Last(x => x.MatchesTags(writeTag, deviceTagSdb)).Value); // 650 - 500 = 150
+ var ioTimeMeasurement = ioTimeCollector.GetMeasurementSnapshot();
+ Assert.Equal(2, ioTimeMeasurement.Count);
+ Assert.Equal(1.234, ioTimeMeasurement.Last(x => x.MatchesTags(deviceTagSda)).Value, 0.01); // (1234 - 0) / 1000 = 1.234
+ Assert.Equal(1.5, ioTimeMeasurement.Last(x => x.MatchesTags(deviceTagSdb)).Value, 0.01); // (7500 - 6000) / 1000 = 6.0
+
+ // 2nd measurement
+ fakeTimeProvider.Advance(TimeSpan.FromMinutes(1));
+ diskIoCollector.RecordObservableInstruments();
+ operationCollector.RecordObservableInstruments();
+ ioTimeCollector.RecordObservableInstruments();
+
+ // Assert the 2nd measurement
+ diskIoMeasurement = diskIoCollector.GetMeasurementSnapshot();
+ Assert.Equal(358_400, diskIoMeasurement.Last(x => x.MatchesTags(readTag, deviceTagSda)).Value); // (700 - 0) * 512 = 358400
+ Assert.Equal(102_400, diskIoMeasurement.Last(x => x.MatchesTags(readTag, deviceTagSdb)).Value); // (400 - 200) * 512 = 102400
+ Assert.Equal(563_200, diskIoMeasurement.Last(x => x.MatchesTags(writeTag, deviceTagSda)).Value); // (1100 - 0) * 512 = 563200
+ Assert.Equal(102_400, diskIoMeasurement.Last(x => x.MatchesTags(writeTag, deviceTagSdb)).Value); // (500 - 300) * 512 = 102400
+ operationMeasurement = operationCollector.GetMeasurementSnapshot();
+ Assert.Equal(800, operationMeasurement.Last(x => x.MatchesTags(readTag, deviceTagSda)).Value); // 800 - 0 = 800
+ Assert.Equal(200, operationMeasurement.Last(x => x.MatchesTags(readTag, deviceTagSdb)).Value); // 600 - 400 = 200
+ Assert.Equal(1300, operationMeasurement.Last(x => x.MatchesTags(writeTag, deviceTagSda)).Value); // 1300 - 0 = 1300
+ Assert.Equal(200, operationMeasurement.Last(x => x.MatchesTags(writeTag, deviceTagSdb)).Value); // 700 - 500 = 200
+ ioTimeMeasurement = ioTimeCollector.GetMeasurementSnapshot();
+ Assert.Equal(2.234, ioTimeMeasurement.Last(x => x.MatchesTags(deviceTagSda)).Value, 0.01); // (2234 - 0) / 1000 = 2.234
+ Assert.Equal(1.5, ioTimeMeasurement.Last(x => x.MatchesTags(deviceTagSdb)).Value, 0.01); // (7500 - 6000) / 1000 = 1.5
+
+ // 3rd measurement
+ fakeTimeProvider.Advance(TimeSpan.FromMinutes(1));
+ diskIoCollector.RecordObservableInstruments();
+ operationCollector.RecordObservableInstruments();
+ ioTimeCollector.RecordObservableInstruments();
+
+ // Assert the 3rd measurement
+ diskIoMeasurement = diskIoCollector.GetMeasurementSnapshot();
+ Assert.Equal(512_000, diskIoMeasurement.Last(x => x.MatchesTags(readTag, deviceTagSda)).Value); // (1000 - 0) * 512 = 512000
+ Assert.Equal(179_200, diskIoMeasurement.Last(x => x.MatchesTags(readTag, deviceTagSdb)).Value); // (550 - 200) * 512 = 179200
+ Assert.Equal(819_200, diskIoMeasurement.Last(x => x.MatchesTags(writeTag, deviceTagSda)).Value); // (1600 - 0) * 512 = 819200
+ Assert.Equal(179_200, diskIoMeasurement.Last(x => x.MatchesTags(writeTag, deviceTagSdb)).Value); // (650 - 300) * 512 = 179200
+ operationMeasurement = operationCollector.GetMeasurementSnapshot();
+ Assert.Equal(1300, operationMeasurement.Last(x => x.MatchesTags(readTag, deviceTagSda)).Value); // 1300 - 0 = 1300
+ Assert.Equal(350, operationMeasurement.Last(x => x.MatchesTags(readTag, deviceTagSdb)).Value); // 750 - 400 = 350
+ Assert.Equal(1350, operationMeasurement.Last(x => x.MatchesTags(writeTag, deviceTagSda)).Value); // 1350 - 0 = 1350
+ Assert.Equal(350, operationMeasurement.Last(x => x.MatchesTags(writeTag, deviceTagSdb)).Value); // 850 - 500 = 350
+ ioTimeMeasurement = ioTimeCollector.GetMeasurementSnapshot();
+ Assert.Equal(4.444, ioTimeMeasurement.Last(x => x.MatchesTags(deviceTagSda)).Value, 0.01); // (4444 - 0) / 1000 = 4.444
+ Assert.Equal(3.5, ioTimeMeasurement.Last(x => x.MatchesTags(deviceTagSdb)).Value, 0.01); // (9500 - 6000) / 1000 = 3.5
+ }
+}
diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests.csproj b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests.csproj
index b7fcf503d96..ff2cd26412f 100644
--- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests.csproj
+++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests.csproj
@@ -6,8 +6,7 @@
-
-
+
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 25587a6ad59..a592aacce19 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
@@ -30,7 +30,7 @@ public void Creates_Meter_With_Correct_Name()
{
using var meterFactory = new TestMeterFactory();
var performanceCounterFactoryMock = new Mock();
- var options = new ResourceMonitoringOptions { EnableDiskIoMetrics = true };
+ var options = new ResourceMonitoringOptions { EnableSystemDiskIoMetrics = true };
_ = new WindowsDiskMetrics(
_fakeLogger,
@@ -49,7 +49,7 @@ public void DiskOperationMetricsTest()
using var meterFactory = new TestMeterFactory();
var performanceCounterFactory = new Mock();
var fakeTimeProvider = new FakeTimeProvider();
- var options = new ResourceMonitoringOptions { EnableDiskIoMetrics = true };
+ var options = new ResourceMonitoringOptions { EnableSystemDiskIoMetrics = true };
// Set up
const string ReadCounterName = WindowsDiskPerfCounterNames.DiskReadsCounter;
@@ -123,7 +123,7 @@ public void DiskIoBytesMetricsTest()
using var meterFactory = new TestMeterFactory();
var performanceCounterFactory = new Mock();
var fakeTimeProvider = new FakeTimeProvider();
- var options = new ResourceMonitoringOptions { EnableDiskIoMetrics = true };
+ var options = new ResourceMonitoringOptions { EnableSystemDiskIoMetrics = true };
// Set up
const string ReadCounterName = WindowsDiskPerfCounterNames.DiskReadBytesCounter;