From babbd62984d6abb9437dbe6167b2452571c02f9a Mon Sep 17 00:00:00 2001 From: amadeuszl Date: Mon, 2 Jun 2025 15:10:16 +0200 Subject: [PATCH 1/6] Add resiliency for Linux networking metrics --- .../ITcpStateInfoProvider.cs | 4 +- .../Linux/Network/LinuxNetworkMetrics.cs | 90 ++++++--- .../Linux/Network/LinuxTcpStateInfo.cs | 4 +- .../Windows/Network/WindowsNetworkMetrics.cs | 4 +- .../Windows/Network/WindowsTcpStateInfo.cs | 4 +- .../Linux/LinuxCountersTests.cs | 4 +- .../Linux/LinuxNetworkMetricsTests.cs | 173 +++++++++++++++++- .../Windows/Tcp6TableInfoTests.cs | 10 +- .../Windows/TcpTableInfoTests.cs | 10 +- 9 files changed, 253 insertions(+), 50 deletions(-) diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ITcpStateInfoProvider.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ITcpStateInfoProvider.cs index 0c51e5fc45a..b62d8365d70 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ITcpStateInfoProvider.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ITcpStateInfoProvider.cs @@ -12,11 +12,11 @@ internal interface ITcpStateInfoProvider /// Gets the last known TCP/IP v4 state of the system. /// /// An instance of . - TcpStateInfo GetpIpV4TcpStateInfo(); + TcpStateInfo GetIpV4TcpStateInfo(); /// /// Gets the last known TCP/IP v6 state of the system. /// /// An instance of . - TcpStateInfo GetpIpV6TcpStateInfo(); + TcpStateInfo GetIpV6TcpStateInfo(); } diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkMetrics.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkMetrics.cs index a7e0c6b2303..164ff43b1e0 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkMetrics.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkMetrics.cs @@ -1,9 +1,12 @@ // 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.IO; +using System.Threading; using Microsoft.Shared.Instruments; namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network; @@ -11,10 +14,18 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network; internal sealed class LinuxNetworkMetrics { private readonly ITcpStateInfoProvider _tcpStateInfoProvider; + private readonly TimeProvider _timeProvider; - public LinuxNetworkMetrics(IMeterFactory meterFactory, ITcpStateInfoProvider tcpStateInfoProvider) + private readonly TimeSpan _retryInterval = TimeSpan.FromMinutes(5); + private DateTimeOffset _lastV4Failure = DateTimeOffset.MinValue; + private DateTimeOffset _lastV6Failure = DateTimeOffset.MinValue; + private int _v4Unavailable; + private int _v6Unavailable; + + public LinuxNetworkMetrics(IMeterFactory meterFactory, ITcpStateInfoProvider tcpStateInfoProvider, TimeProvider timeProvider) { _tcpStateInfoProvider = tcpStateInfoProvider; + _timeProvider = timeProvider; #pragma warning disable CA2000 // Dispose objects before losing scope // We don't dispose the meter because IMeterFactory handles that @@ -36,10 +47,9 @@ public LinuxNetworkMetrics(IMeterFactory meterFactory, ITcpStateInfoProvider tcp tags: commonTags); } - private IEnumerable> GetMeasurements() + public IEnumerable> GetMeasurements() { const string NetworkTypeKey = "network.type"; - const string NetworkStateKey = "system.network.state"; // These are covered in https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-metrics.md#attributes: KeyValuePair tcpVersionFourTag = new(NetworkTypeKey, "ipv4"); @@ -48,33 +58,59 @@ private IEnumerable> GetMeasurements() List> measurements = new(24); // IPv4: - TcpStateInfo stateV4 = _tcpStateInfoProvider.GetpIpV4TcpStateInfo(); - measurements.Add(new Measurement(stateV4.ClosedCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "close") })); - measurements.Add(new Measurement(stateV4.ListenCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "listen") })); - measurements.Add(new Measurement(stateV4.SynSentCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "syn_sent") })); - measurements.Add(new Measurement(stateV4.SynRcvdCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "syn_recv") })); - measurements.Add(new Measurement(stateV4.EstabCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "established") })); - measurements.Add(new Measurement(stateV4.FinWait1Count, new TagList { tcpVersionFourTag, new(NetworkStateKey, "fin_wait_1") })); - measurements.Add(new Measurement(stateV4.FinWait2Count, new TagList { tcpVersionFourTag, new(NetworkStateKey, "fin_wait_2") })); - measurements.Add(new Measurement(stateV4.CloseWaitCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "close_wait") })); - measurements.Add(new Measurement(stateV4.ClosingCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "closing") })); - measurements.Add(new Measurement(stateV4.LastAckCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "last_ack") })); - measurements.Add(new Measurement(stateV4.TimeWaitCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "time_wait") })); + var stateV4 = GetTcpStateInfoWithRetry(_tcpStateInfoProvider.GetIpV4TcpStateInfo, ref _v4Unavailable, ref _lastV4Failure); + CreateMeasurements(tcpVersionFourTag, measurements, stateV4); // IPv6: - TcpStateInfo stateV6 = _tcpStateInfoProvider.GetpIpV6TcpStateInfo(); - measurements.Add(new Measurement(stateV6.ClosedCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "close") })); - measurements.Add(new Measurement(stateV6.ListenCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "listen") })); - measurements.Add(new Measurement(stateV6.SynSentCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "syn_sent") })); - measurements.Add(new Measurement(stateV6.SynRcvdCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "syn_recv") })); - measurements.Add(new Measurement(stateV6.EstabCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "established") })); - measurements.Add(new Measurement(stateV6.FinWait1Count, new TagList { tcpVersionSixTag, new(NetworkStateKey, "fin_wait_1") })); - measurements.Add(new Measurement(stateV6.FinWait2Count, new TagList { tcpVersionSixTag, new(NetworkStateKey, "fin_wait_2") })); - measurements.Add(new Measurement(stateV6.CloseWaitCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "close_wait") })); - measurements.Add(new Measurement(stateV6.ClosingCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "closing") })); - measurements.Add(new Measurement(stateV6.LastAckCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "last_ack") })); - measurements.Add(new Measurement(stateV6.TimeWaitCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "time_wait") })); + var stateV6 = GetTcpStateInfoWithRetry(_tcpStateInfoProvider.GetIpV6TcpStateInfo, ref _v6Unavailable, ref _lastV6Failure); + CreateMeasurements(tcpVersionSixTag, measurements, stateV6); return measurements; } + + private static void CreateMeasurements(KeyValuePair tcpVersionTag, List> measurements, TcpStateInfo state) + { + const string NetworkStateKey = "system.network.state"; + + measurements.Add(new Measurement(state.ClosedCount, new TagList { tcpVersionTag, new(NetworkStateKey, "close") })); + measurements.Add(new Measurement(state.ListenCount, new TagList { tcpVersionTag, new(NetworkStateKey, "listen") })); + measurements.Add(new Measurement(state.SynSentCount, new TagList { tcpVersionTag, new(NetworkStateKey, "syn_sent") })); + measurements.Add(new Measurement(state.SynRcvdCount, new TagList { tcpVersionTag, new(NetworkStateKey, "syn_recv") })); + measurements.Add(new Measurement(state.EstabCount, new TagList { tcpVersionTag, new(NetworkStateKey, "established") })); + measurements.Add(new Measurement(state.FinWait1Count, new TagList { tcpVersionTag, new(NetworkStateKey, "fin_wait_1") })); + measurements.Add(new Measurement(state.FinWait2Count, new TagList { tcpVersionTag, new(NetworkStateKey, "fin_wait_2") })); + measurements.Add(new Measurement(state.CloseWaitCount, new TagList { tcpVersionTag, new(NetworkStateKey, "close_wait") })); + measurements.Add(new Measurement(state.ClosingCount, new TagList { tcpVersionTag, new(NetworkStateKey, "closing") })); + measurements.Add(new Measurement(state.LastAckCount, new TagList { tcpVersionTag, new(NetworkStateKey, "last_ack") })); + measurements.Add(new Measurement(state.TimeWaitCount, new TagList { tcpVersionTag, new(NetworkStateKey, "time_wait") })); + } + + private TcpStateInfo GetTcpStateInfoWithRetry( + Func getStateInfoFunc, + ref int unavailableFlag, + ref DateTimeOffset lastFailureTime) + { + if (Volatile.Read(ref unavailableFlag) == 0 || _timeProvider.GetUtcNow() - lastFailureTime > _retryInterval) + { + try + { + var state = getStateInfoFunc(); + _ = Interlocked.Exchange(ref unavailableFlag, 0); + return state; + } + catch (Exception ex) when ( + ex is FileNotFoundException || + ex is DirectoryNotFoundException || + ex is UnauthorizedAccessException) + { + lastFailureTime = _timeProvider.GetUtcNow(); + _ = Interlocked.Exchange(ref unavailableFlag, 1); + return new TcpStateInfo(); + } + } + else + { + return new TcpStateInfo(); + } + } } diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxTcpStateInfo.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxTcpStateInfo.cs index 390bcda64ea..66bc3e1501e 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxTcpStateInfo.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxTcpStateInfo.cs @@ -23,13 +23,13 @@ public LinuxTcpStateInfo(IOptions options, LinuxNetwo _parser = parser; } - public TcpStateInfo GetpIpV4TcpStateInfo() + public TcpStateInfo GetIpV4TcpStateInfo() { RefreshSnapshotIfNeeded(); return _iPv4Snapshot; } - public TcpStateInfo GetpIpV6TcpStateInfo() + public TcpStateInfo GetIpV6TcpStateInfo() { RefreshSnapshotIfNeeded(); return _iPv6Snapshot; diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Network/WindowsNetworkMetrics.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Network/WindowsNetworkMetrics.cs index 1acc8d02edd..be3b4e6983f 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Network/WindowsNetworkMetrics.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Network/WindowsNetworkMetrics.cs @@ -48,7 +48,7 @@ private IEnumerable> GetMeasurements() List> measurements = new(24); // IPv4: - TcpStateInfo stateV4 = _tcpStateInfoProvider.GetpIpV4TcpStateInfo(); + TcpStateInfo stateV4 = _tcpStateInfoProvider.GetIpV4TcpStateInfo(); measurements.Add(new Measurement(stateV4.ClosedCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "close") })); measurements.Add(new Measurement(stateV4.ListenCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "listen") })); measurements.Add(new Measurement(stateV4.SynSentCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "syn_sent") })); @@ -63,7 +63,7 @@ private IEnumerable> GetMeasurements() measurements.Add(new Measurement(stateV4.DeleteTcbCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "delete") })); // IPv6: - TcpStateInfo stateV6 = _tcpStateInfoProvider.GetpIpV6TcpStateInfo(); + TcpStateInfo stateV6 = _tcpStateInfoProvider.GetIpV6TcpStateInfo(); measurements.Add(new Measurement(stateV6.ClosedCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "close") })); measurements.Add(new Measurement(stateV6.ListenCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "listen") })); measurements.Add(new Measurement(stateV6.SynSentCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "syn_sent") })); diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Network/WindowsTcpStateInfo.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Network/WindowsTcpStateInfo.cs index 732c522cda5..cc0310fd4c8 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Network/WindowsTcpStateInfo.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Network/WindowsTcpStateInfo.cs @@ -42,13 +42,13 @@ public WindowsTcpStateInfo(IOptions options) _refreshAfter = default; } - public TcpStateInfo GetpIpV4TcpStateInfo() + public TcpStateInfo GetIpV4TcpStateInfo() { RefreshSnapshotIfNeeded(); return _iPv4Snapshot; } - public TcpStateInfo GetpIpV6TcpStateInfo() + public TcpStateInfo GetIpV6TcpStateInfo() { RefreshSnapshotIfNeeded(); return _iPv6Snapshot; diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxCountersTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxCountersTests.cs index e0ff3880075..4f8dbf9547f 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxCountersTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxCountersTests.cs @@ -1,12 +1,14 @@ // 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.IO; using System.Threading; using FluentAssertions; using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network; +using Microsoft.Extensions.Time.Testing; using Microsoft.Shared.Instruments; using Moq; using Xunit; @@ -85,7 +87,7 @@ public void LinuxNetworkCounters_Registers_Instruments() .Returns(meter); var tcpStateInfo = new LinuxTcpStateInfo(options, parser); - var lnm = new LinuxNetworkMetrics(meterFactoryMock.Object, tcpStateInfo); + var lnm = new LinuxNetworkMetrics(meterFactoryMock.Object, tcpStateInfo, new FakeTimeProvider(DateTimeOffset.UtcNow)); using var listener = new MeterListener { diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxNetworkMetricsTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxNetworkMetricsTests.cs index 6668ebe811c..83055f8caae 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxNetworkMetricsTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxNetworkMetricsTests.cs @@ -1,10 +1,13 @@ // 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.Diagnostics.Metrics; +using System.IO; using System.Linq; using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network; using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Test.Helpers; +using Microsoft.Extensions.Time.Testing; using Microsoft.Shared.Instruments; using Microsoft.TestUtilities; using Moq; @@ -15,14 +18,176 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Test; [OSSkipCondition(OperatingSystems.Windows | OperatingSystems.MacOSX, SkipReason = "Linux specific tests")] public class LinuxNetworkMetricsTests { + private readonly Mock _tcpStateInfoProvider = new(); + private readonly DateTimeOffset _startTime = DateTimeOffset.UtcNow; + private FakeTimeProvider _timeProvider; + + public LinuxNetworkMetricsTests() + { + _timeProvider = new FakeTimeProvider(_startTime); + + _tcpStateInfoProvider.Setup(p => p.GetIpV4TcpStateInfo()).Returns(new TcpStateInfo()); + _tcpStateInfoProvider.Setup(p => p.GetIpV6TcpStateInfo()).Returns(new TcpStateInfo()); + } + [Fact] - public void Creates_Meter_With_Correct_Name() + public void CreatesMeter_WithCorrectName() { using var meterFactory = new TestMeterFactory(); - var tcpStateInfoProviderMock = new Mock(); - _ = new LinuxNetworkMetrics(meterFactory, tcpStateInfoProviderMock.Object); + _ = new LinuxNetworkMetrics( + meterFactory, + _tcpStateInfoProvider.Object, + _timeProvider); - Meter meter = meterFactory.Meters.Single(); + var meter = meterFactory.Meters.Single(); Assert.Equal(ResourceUtilizationInstruments.MeterName, meter.Name); } + + [Fact] + public void GetTcpStateInfoWithRetry_SuccessfulCall_ReturnsState() + { + var expectedV4 = new TcpStateInfo { ClosedCount = 42 }; + var expectedV6 = new TcpStateInfo { EstabCount = 24 }; + _tcpStateInfoProvider.Setup(p => p.GetIpV4TcpStateInfo()).Returns(expectedV4); + _tcpStateInfoProvider.Setup(p => p.GetIpV6TcpStateInfo()).Returns(expectedV6); + + var metrics = CreateMetrics(); + var measurements = metrics.GetMeasurements().ToList(); + + Assert.Contains(measurements, m => HasTagWithValue(m, "network.type", "ipv4", 42)); + Assert.Contains(measurements, m => HasTagWithValue(m, "system.network.state", "close", 42)); + Assert.Contains(measurements, m => HasTagWithValue(m, "network.type", "ipv6", 24)); + Assert.Contains(measurements, m => HasTagWithValue(m, "system.network.state", "established", 24)); + } + + [Theory] + [InlineData(typeof(FileNotFoundException))] + [InlineData(typeof(DirectoryNotFoundException))] + [InlineData(typeof(UnauthorizedAccessException))] + public void GetTcpStateInfoWithRetry_Failure_SetsUnavailableAndReturnsDefault(Type exceptionType) + { + _tcpStateInfoProvider.Setup(p => p.GetIpV4TcpStateInfo()).Throws((Exception)Activator.CreateInstance(exceptionType)!); + + var metrics = CreateMetrics(); + var measurements = metrics.GetMeasurements().ToList(); + + Assert.All(measurements.Take(11), m => Assert.Equal(0, m.Value)); + } + + [Fact] + public void GetTcpStateInfoWithRetry_DuringRetryInterval_ReturnsDefault() + { + _tcpStateInfoProvider.SetupSequence(p => p.GetIpV4TcpStateInfo()) + .Throws(new FileNotFoundException()) + .Returns(new TcpStateInfo { ClosedCount = 123 }); + + var metrics = CreateMetrics(); + var first = metrics.GetMeasurements().ToList(); + + _timeProvider.Advance(TimeSpan.FromMinutes(2)); + var second = metrics.GetMeasurements().ToList(); + + Assert.All(first.Take(11), m => Assert.Equal(0, m.Value)); + Assert.All(second.Take(11), m => Assert.Equal(0, m.Value)); + _tcpStateInfoProvider.Verify(p => p.GetIpV4TcpStateInfo(), Times.Once); + } + + [Fact] + public void GetTcpStateInfoWithRetry_AfterRetryInterval_ResetsUnavailableOnSuccess() + { + _tcpStateInfoProvider.SetupSequence(p => p.GetIpV4TcpStateInfo()) + .Throws(new FileNotFoundException()) + .Returns(new TcpStateInfo { ClosedCount = 99 }); + + var metrics = CreateMetrics(); + var first = metrics.GetMeasurements().ToList(); + + _timeProvider.Advance(TimeSpan.FromMinutes(6)); + var second = metrics.GetMeasurements().ToList(); + + Assert.All(first.Take(11), m => Assert.Equal(0, m.Value)); + Assert.Equal(99, second[0].Value); + Assert.Contains(second, m => HasTagWithValue(m, "network.type", "ipv4", 99)); + Assert.Contains(second, m => HasTagWithValue(m, "system.network.state", "close", 99)); + + _tcpStateInfoProvider.Verify(p => p.GetIpV4TcpStateInfo(), Times.Exactly(2)); + } + + private static bool HasTagWithValue(Measurement measurement, string tagKey, string tagValue, long expectedValue) + { + foreach (var tag in measurement.Tags) + { + if (tag.Key == tagKey && (string)tag.Value == tagValue) + { + return measurement.Value == expectedValue; + } + } + + return false; + } + + private LinuxNetworkMetrics CreateMetrics() + { + using var meterFactory = new TestMeterFactory(); + return new LinuxNetworkMetrics( + meterFactory, + _tcpStateInfoProvider.Object, + _timeProvider); + } + [Fact] + public void GetTcpStateInfoWithRetry_Multithreaded_RetryIsThreadSafe() + { + // Arrange + var callCount = 0; + var lockObj = new object(); + _tcpStateInfoProvider.Setup(p => p.GetIpV4TcpStateInfo()).Returns(() => + { + lock (lockObj) + { + callCount++; + if (callCount == 1) + { + throw new FileNotFoundException(); + } + return new TcpStateInfo { ClosedCount = 55 }; + } + }); + + var metrics = CreateMetrics(); + var results = new List>>(); + var threads = new Thread[5]; + + // Act + for (int i = 0; i < threads.Length; i++) + { + threads[i] = new Thread(() => + { + // Each thread tries to get measurements + results.Add(metrics.GetMeasurements().ToList()); + }); + threads[i].Start(); + } + foreach (var t in threads) + { + t.Join(); + } + + // Advance time to after retry interval + _timeProvider.Advance(TimeSpan.FromMinutes(6)); + + // All threads should have received default (0) values due to retry interval + foreach (var measurementList in results) + { + Assert.All(measurementList.Take(11), m => Assert.Equal(0, m.Value)); + } + + // Now, after retry interval, the next call should succeed and return the new value + var afterRetry = metrics.GetMeasurements().ToList(); + Assert.Equal(55, afterRetry[0].Value); + Assert.Contains(afterRetry, m => HasTagWithValue(m, "network.type", "ipv4", 55)); + Assert.Contains(afterRetry, m => HasTagWithValue(m, "system.network.state", "close", 55)); + + // Only two calls to GetIpV4TcpStateInfo should have been made (one fail, one after retry) + _tcpStateInfoProvider.Verify(p => p.GetIpV4TcpStateInfo(), Times.Exactly(2)); + } } diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Tcp6TableInfoTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Tcp6TableInfoTests.cs index 3ef240e1274..88fa4ab4c88 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Tcp6TableInfoTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Tcp6TableInfoTests.cs @@ -238,7 +238,7 @@ public void Test_Tcp6TableInfo_Get_UnsuccessfulStatus_All_The_Time() tcp6TableInfo.SetGetTcp6TableDelegate(FakeGetTcp6TableWithUnsuccessfulStatusAllTheTime); Assert.Throws(() => { - var tcpStateInfo = tcp6TableInfo.GetpIpV6TcpStateInfo(); + var tcpStateInfo = tcp6TableInfo.GetIpV6TcpStateInfo(); }); } @@ -254,7 +254,7 @@ public void Test_Tcp6TableInfo_Get_InsufficientBuffer_Then_Get_InvalidParameter( tcp6TableInfo.SetGetTcp6TableDelegate(FakeGetTcp6TableWithInsufficientBufferAndInvalidParameter); Assert.Throws(() => { - var tcpStateInfo = tcp6TableInfo.GetpIpV6TcpStateInfo(); + var tcpStateInfo = tcp6TableInfo.GetIpV6TcpStateInfo(); }); } @@ -270,7 +270,7 @@ public void Test_Tcp6TableInfo_Get_Correct_Information() }; WindowsTcpStateInfo tcp6TableInfo = new WindowsTcpStateInfo(Options.Options.Create(options)); tcp6TableInfo.SetGetTcp6TableDelegate(FakeGetTcp6TableWithFakeInformation); - var tcpStateInfo = tcp6TableInfo.GetpIpV6TcpStateInfo(); + var tcpStateInfo = tcp6TableInfo.GetIpV6TcpStateInfo(); Assert.NotNull(tcpStateInfo); Assert.Equal(1, tcpStateInfo.ClosedCount); Assert.Equal(1, tcpStateInfo.ListenCount); @@ -286,7 +286,7 @@ public void Test_Tcp6TableInfo_Get_Correct_Information() Assert.Equal(1, tcpStateInfo.DeleteTcbCount); // Second calling in a small interval. - tcpStateInfo = tcp6TableInfo.GetpIpV6TcpStateInfo(); + tcpStateInfo = tcp6TableInfo.GetIpV6TcpStateInfo(); Assert.NotNull(tcpStateInfo); Assert.Equal(1, tcpStateInfo.ClosedCount); Assert.Equal(1, tcpStateInfo.ListenCount); @@ -303,7 +303,7 @@ public void Test_Tcp6TableInfo_Get_Correct_Information() // Third calling in a long interval. Thread.Sleep(6000); - tcpStateInfo = tcp6TableInfo.GetpIpV6TcpStateInfo(); + tcpStateInfo = tcp6TableInfo.GetIpV6TcpStateInfo(); Assert.NotNull(tcpStateInfo); Assert.Equal(2, tcpStateInfo.ClosedCount); Assert.Equal(2, tcpStateInfo.ListenCount); diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/TcpTableInfoTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/TcpTableInfoTests.cs index fb79d0cb839..8c88fc123dd 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/TcpTableInfoTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/TcpTableInfoTests.cs @@ -181,7 +181,7 @@ public void Test_TcpTableInfo_Get_UnsuccessfulStatus_All_The_Time() tcpTableInfo.SetGetTcpTableDelegate(FakeGetTcpTableWithUnsuccessfulStatusAllTheTime); Assert.Throws(() => { - var tcpStateInfo = tcpTableInfo.GetpIpV4TcpStateInfo(); + var tcpStateInfo = tcpTableInfo.GetIpV4TcpStateInfo(); }); } @@ -197,7 +197,7 @@ public void Test_TcpTableInfo_Get_InsufficientBuffer_Then_Get_InvalidParameter() tcpTableInfo.SetGetTcpTableDelegate(FakeGetTcpTableWithInsufficientBufferAndInvalidParameter); Assert.Throws(() => { - var tcpStateInfo = tcpTableInfo.GetpIpV4TcpStateInfo(); + var tcpStateInfo = tcpTableInfo.GetIpV4TcpStateInfo(); }); } @@ -213,7 +213,7 @@ public void Test_TcpTableInfo_Get_Correct_Information() }; WindowsTcpStateInfo tcpTableInfo = new WindowsTcpStateInfo(Options.Options.Create(options)); tcpTableInfo.SetGetTcpTableDelegate(FakeGetTcpTableWithFakeInformation); - var tcpStateInfo = tcpTableInfo.GetpIpV4TcpStateInfo(); + var tcpStateInfo = tcpTableInfo.GetIpV4TcpStateInfo(); Assert.NotNull(tcpStateInfo); Assert.Equal(1, tcpStateInfo.ClosedCount); Assert.Equal(1, tcpStateInfo.ListenCount); @@ -229,7 +229,7 @@ public void Test_TcpTableInfo_Get_Correct_Information() Assert.Equal(1, tcpStateInfo.DeleteTcbCount); // Second calling in a small interval. - tcpStateInfo = tcpTableInfo.GetpIpV4TcpStateInfo(); + tcpStateInfo = tcpTableInfo.GetIpV4TcpStateInfo(); Assert.NotNull(tcpStateInfo); Assert.Equal(1, tcpStateInfo.ClosedCount); Assert.Equal(1, tcpStateInfo.ListenCount); @@ -246,7 +246,7 @@ public void Test_TcpTableInfo_Get_Correct_Information() // Third calling in a long interval. Thread.Sleep(6000); - tcpStateInfo = tcpTableInfo.GetpIpV4TcpStateInfo(); + tcpStateInfo = tcpTableInfo.GetIpV4TcpStateInfo(); Assert.NotNull(tcpStateInfo); Assert.Equal(2, tcpStateInfo.ClosedCount); Assert.Equal(2, tcpStateInfo.ListenCount); From df898984dc213ec4a33f4e199007f21b5ab39782 Mon Sep 17 00:00:00 2001 From: amadeuszl Date: Mon, 2 Jun 2025 15:46:25 +0200 Subject: [PATCH 2/6] Remove unnecessary test --- .../Linux/LinuxNetworkMetricsTests.cs | 56 ------------------- 1 file changed, 56 deletions(-) diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxNetworkMetricsTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxNetworkMetricsTests.cs index 83055f8caae..97b5d4a07cd 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxNetworkMetricsTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxNetworkMetricsTests.cs @@ -134,60 +134,4 @@ private LinuxNetworkMetrics CreateMetrics() _tcpStateInfoProvider.Object, _timeProvider); } - [Fact] - public void GetTcpStateInfoWithRetry_Multithreaded_RetryIsThreadSafe() - { - // Arrange - var callCount = 0; - var lockObj = new object(); - _tcpStateInfoProvider.Setup(p => p.GetIpV4TcpStateInfo()).Returns(() => - { - lock (lockObj) - { - callCount++; - if (callCount == 1) - { - throw new FileNotFoundException(); - } - return new TcpStateInfo { ClosedCount = 55 }; - } - }); - - var metrics = CreateMetrics(); - var results = new List>>(); - var threads = new Thread[5]; - - // Act - for (int i = 0; i < threads.Length; i++) - { - threads[i] = new Thread(() => - { - // Each thread tries to get measurements - results.Add(metrics.GetMeasurements().ToList()); - }); - threads[i].Start(); - } - foreach (var t in threads) - { - t.Join(); - } - - // Advance time to after retry interval - _timeProvider.Advance(TimeSpan.FromMinutes(6)); - - // All threads should have received default (0) values due to retry interval - foreach (var measurementList in results) - { - Assert.All(measurementList.Take(11), m => Assert.Equal(0, m.Value)); - } - - // Now, after retry interval, the next call should succeed and return the new value - var afterRetry = metrics.GetMeasurements().ToList(); - Assert.Equal(55, afterRetry[0].Value); - Assert.Contains(afterRetry, m => HasTagWithValue(m, "network.type", "ipv4", 55)); - Assert.Contains(afterRetry, m => HasTagWithValue(m, "system.network.state", "close", 55)); - - // Only two calls to GetIpV4TcpStateInfo should have been made (one fail, one after retry) - _tcpStateInfoProvider.Verify(p => p.GetIpV4TcpStateInfo(), Times.Exactly(2)); - } } From 1486e530174ac9645f474964de841b716c3227de Mon Sep 17 00:00:00 2001 From: amadeuszl Date: Mon, 2 Jun 2025 15:55:34 +0200 Subject: [PATCH 3/6] Add resiliency to disk stats --- .../Linux/Disk/LinuxSystemDiskMetrics.cs | 23 +++++++ .../Linux/Disk/LinuxSystemDiskMetricsTests.cs | 66 +++++++++++++++++++ 2 files changed, 89 insertions(+) diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Disk/LinuxSystemDiskMetrics.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Disk/LinuxSystemDiskMetrics.cs index d70a65ed1b0..80e4f3f6707 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Disk/LinuxSystemDiskMetrics.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Disk/LinuxSystemDiskMetrics.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.Metrics; +using System.IO; using System.Linq; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; @@ -29,6 +30,11 @@ internal sealed class LinuxSystemDiskMetrics private readonly IDiskStatsReader _diskStatsReader; private readonly object _lock = new(); private readonly Dictionary _baselineDiskStatsDict = []; + private readonly TimeSpan _retryInterval = TimeSpan.FromMinutes(5); + + private DateTimeOffset _lastDiskStatsFailure = DateTimeOffset.MinValue; + private bool _diskStatsUnavailable; + private List _diskStatsSnapshot = []; private DateTimeOffset _lastRefreshTime = DateTimeOffset.MinValue; @@ -148,6 +154,12 @@ private List GetDiskStatsSnapshot() private List GetAllDiskStats() { + if (_diskStatsUnavailable && + _timeProvider.GetUtcNow() - _lastDiskStatsFailure < _retryInterval) + { + return []; + } + try { List diskStatsList = _diskStatsReader.ReadAll(); @@ -158,8 +170,19 @@ private List GetAllDiskStats() && !d.DeviceName.StartsWith("loop", StringComparison.OrdinalIgnoreCase) && !d.DeviceName.StartsWith("dm-", StringComparison.OrdinalIgnoreCase)) .ToList(); + + _diskStatsUnavailable = false; return diskStatsList; } + catch (Exception ex) when ( + ex is FileNotFoundException || + ex is DirectoryNotFoundException || + ex is UnauthorizedAccessException) + { + Log.HandleDiskStatsException(_logger, ex.Message); + _lastDiskStatsFailure = _timeProvider.GetUtcNow(); + _diskStatsUnavailable = true; + } #pragma warning disable CA1031 catch (Exception ex) #pragma warning restore CA1031 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 index c6aba8e0b53..ae8bd1cff74 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Disk/LinuxSystemDiskMetricsTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Disk/LinuxSystemDiskMetricsTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.Metrics; +using System.IO; using System.Linq; using Microsoft.Extensions.Diagnostics.Metrics.Testing; using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Test.Helpers; @@ -213,4 +214,69 @@ public void Test_MetricValues() 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 } + + [Fact] + public void GetAllDiskStats_RetriesAfterFailureInterval() + { + using var meterFactory = new TestMeterFactory(); + var fakeTimeProvider = new FakeTimeProvider(); + var options = new ResourceMonitoringOptions { EnableSystemDiskIoMetrics = true }; + + var diskStats = new DiskStats + { + DeviceName = "sda", + SectorsRead = 100, + SectorsWritten = 200, + ReadsCompleted = 10, + WritesCompleted = 20, + TimeIoMs = 1000 + }; + + var diskStatsReaderMock = new Mock(); + diskStatsReaderMock.Setup(r => r.ReadAll()).Throws(); + + var metrics = new LinuxSystemDiskMetrics( + _fakeLogger, + meterFactory, + Options.Options.Create(options), + fakeTimeProvider, + diskStatsReaderMock.Object); + + using var ioCollector = new MetricCollector(meterFactory.Meters.Single(), ResourceUtilizationInstruments.SystemDiskIo); + + ioCollector.RecordObservableInstruments(); + Assert.Empty(ioCollector.GetMeasurementSnapshot()); + diskStatsReaderMock.Verify(r => r.ReadAll(), Times.Once); + + ioCollector.RecordObservableInstruments(); + Assert.Empty(ioCollector.GetMeasurementSnapshot()); + diskStatsReaderMock.Verify(r => r.ReadAll(), Times.Once); + + fakeTimeProvider.Advance(TimeSpan.FromMinutes(5).Add(TimeSpan.FromSeconds(1))); + + ioCollector.RecordObservableInstruments(); + Assert.Empty(ioCollector.GetMeasurementSnapshot()); + diskStatsReaderMock.Verify(r => r.ReadAll(), Times.Exactly(2)); + + diskStatsReaderMock.Reset(); + diskStatsReaderMock.Setup(r => r.ReadAll()).Returns(new List { diskStats }); + + fakeTimeProvider.Advance(TimeSpan.FromMinutes(5).Add(TimeSpan.FromSeconds(1))); + + ioCollector.RecordObservableInstruments(); + var measurements = ioCollector.GetMeasurementSnapshot(); + Assert.NotEmpty(measurements); + Assert.Contains(measurements, m => m.Tags.Any(t => t.Value?.ToString() == "sda")); + diskStatsReaderMock.Verify(r => r.ReadAll(), Times.Once); + + fakeTimeProvider.Advance(TimeSpan.FromMinutes(5).Add(TimeSpan.FromSeconds(1))); + + ioCollector.RecordObservableInstruments(); + measurements = ioCollector.GetMeasurementSnapshot(); + Assert.NotEmpty(measurements); + Assert.Contains(measurements, m => m.Tags.Any(t => t.Value?.ToString() == "sda")); + diskStatsReaderMock.Verify(r => r.ReadAll(), Times.Exactly(2)); + } + + } From d3c765cc414c60e46b53582ffd3252bb2489c9a0 Mon Sep 17 00:00:00 2001 From: amadeuszl Date: Thu, 5 Jun 2025 08:17:21 +0200 Subject: [PATCH 4/6] Fix build issues --- .../Linux/Disk/LinuxSystemDiskMetricsTests.cs | 2 -- .../Linux/LinuxNetworkMetricsTests.cs | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) 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 index ae8bd1cff74..88757c7a1fb 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Disk/LinuxSystemDiskMetricsTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Disk/LinuxSystemDiskMetricsTests.cs @@ -277,6 +277,4 @@ public void GetAllDiskStats_RetriesAfterFailureInterval() Assert.Contains(measurements, m => m.Tags.Any(t => t.Value?.ToString() == "sda")); diskStatsReaderMock.Verify(r => r.ReadAll(), Times.Exactly(2)); } - - } diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxNetworkMetricsTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxNetworkMetricsTests.cs index 97b5d4a07cd..da410964a96 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxNetworkMetricsTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxNetworkMetricsTests.cs @@ -117,7 +117,7 @@ private static bool HasTagWithValue(Measurement measurement, string tagKey { foreach (var tag in measurement.Tags) { - if (tag.Key == tagKey && (string)tag.Value == tagValue) + if (tag.Key == tagKey && string.Equals(tag.Value as string, tagValue, StringComparison.Ordinal)) { return measurement.Value == expectedValue; } From 01b38377e10dbc50d9feafd0c0a17a68b9a22b8f Mon Sep 17 00:00:00 2001 From: amadeuszl Date: Mon, 9 Jun 2025 13:54:44 +0200 Subject: [PATCH 5/6] Address comments, improve performance, refactor --- .../Linux/Disk/DiskStatsReader.cs | 11 ++- .../Linux/Disk/IDiskStatsReader.cs | 4 +- .../Linux/Disk/LinuxSystemDiskMetrics.cs | 38 +++++----- .../Linux/Network/LinuxNetworkMetrics.cs | 6 +- .../Linux/Disk/DiskStatsReaderTests.cs | 73 ++++++++++++------- .../Linux/Disk/FakeDiskStatsReader.cs | 6 +- .../Linux/Disk/LinuxSystemDiskMetricsTests.cs | 48 ++++++++++-- .../Linux/LinuxNetworkMetricsTests.cs | 25 ++++--- .../Windows/Tcp6TableInfoTests.cs | 9 ++- 9 files changed, 139 insertions(+), 81 deletions(-) diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Disk/DiskStatsReader.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Disk/DiskStatsReader.cs index 11a6f72ccc8..660d5c4e7a1 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Disk/DiskStatsReader.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Disk/DiskStatsReader.cs @@ -6,6 +6,7 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; +using System.Linq; using Microsoft.Extensions.ObjectPool; using Microsoft.Shared.Pools; @@ -23,7 +24,7 @@ internal sealed class DiskStatsReader(IFileSystem fileSystem) : IDiskStatsReader /// Reads and returns all disk statistics entries. /// /// List of . - public List ReadAll() + public DiskStats[] ReadAll(string[] skipDevicePrefixes) { var diskStatsList = new List(); @@ -41,7 +42,11 @@ public List ReadAll() try { DiskStats stat = DiskStatsReader.ParseLine(line); - diskStatsList.Add(stat); + if (!skipDevicePrefixes.Any(prefix => + stat.DeviceName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))) + { + diskStatsList.Add(stat); + } } #pragma warning disable CA1031 catch (Exception) @@ -51,7 +56,7 @@ public List ReadAll() } } - return diskStatsList; + return diskStatsList.ToArray(); } /// diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Disk/IDiskStatsReader.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Disk/IDiskStatsReader.cs index df9d0d7c020..d4087731401 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Disk/IDiskStatsReader.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Disk/IDiskStatsReader.cs @@ -1,8 +1,6 @@ // 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; /// @@ -14,5 +12,5 @@ internal interface IDiskStatsReader /// Gets all the disk statistics from the system. /// /// List of instances. - List ReadAll(); + DiskStats[] ReadAll(string[] skipDevicePrefixes); } diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Disk/LinuxSystemDiskMetrics.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Disk/LinuxSystemDiskMetrics.cs index 80e4f3f6707..0a256444ba3 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Disk/LinuxSystemDiskMetrics.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Disk/LinuxSystemDiskMetrics.cs @@ -2,11 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Frozen; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.Metrics; using System.IO; -using System.Linq; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; @@ -23,19 +23,22 @@ internal sealed class LinuxSystemDiskMetrics private const string DeviceKey = "system.device"; private const string DirectionKey = "disk.io.direction"; + // Exclude devices with these prefixes because they represent virtual, loopback, or device-mapper disks + // that do not correspond to real physical storage. Including them would distort system disk I/O metrics. + private static readonly string[] _skipDevicePrefixes = new[] { "ram", "loop", "dm-" }; 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 readonly FrozenDictionary _baselineDiskStatsDict = FrozenDictionary.Empty; private readonly TimeSpan _retryInterval = TimeSpan.FromMinutes(5); private DateTimeOffset _lastDiskStatsFailure = DateTimeOffset.MinValue; private bool _diskStatsUnavailable; - private List _diskStatsSnapshot = []; + private DiskStats[] _diskStatsSnapshot = []; private DateTimeOffset _lastRefreshTime = DateTimeOffset.MinValue; public LinuxSystemDiskMetrics( @@ -54,7 +57,7 @@ public LinuxSystemDiskMetrics( } // We need to read the disk stats once to get the baseline values - _baselineDiskStatsDict = GetAllDiskStats().ToDictionary(d => d.DeviceName); + _baselineDiskStatsDict = GetAllDiskStats().ToFrozenDictionary(d => d.DeviceName); #pragma warning disable CA2000 // Dispose objects before losing scope // We don't dispose the meter because IMeterFactory handles that @@ -91,7 +94,7 @@ public LinuxSystemDiskMetrics( private IEnumerable> GetDiskIoMeasurements() { List> measurements = []; - List diskStatsSnapshot = GetDiskStatsSnapshot(); + DiskStats[] diskStatsSnapshot = GetDiskStatsSnapshot(); foreach (DiskStats diskStats in diskStatsSnapshot) { @@ -108,7 +111,7 @@ private IEnumerable> GetDiskIoMeasurements() private IEnumerable> GetDiskOperationMeasurements() { List> measurements = []; - List diskStatsSnapshot = GetDiskStatsSnapshot(); + DiskStats[] diskStatsSnapshot = GetDiskStatsSnapshot(); foreach (DiskStats diskStats in diskStatsSnapshot) { @@ -125,7 +128,7 @@ private IEnumerable> GetDiskOperationMeasurements() private IEnumerable> GetDiskIoTimeMeasurements() { List> measurements = []; - List diskStatsSnapshot = GetDiskStatsSnapshot(); + DiskStats[] diskStatsSnapshot = GetDiskStatsSnapshot(); foreach (DiskStats diskStats in diskStatsSnapshot) { @@ -137,12 +140,12 @@ private IEnumerable> GetDiskIoTimeMeasurements() return measurements; } - private List GetDiskStatsSnapshot() + private DiskStats[] GetDiskStatsSnapshot() { lock (_lock) { DateTimeOffset now = _timeProvider.GetUtcNow(); - if (_diskStatsSnapshot.Count == 0 || (now - _lastRefreshTime).TotalSeconds > MinimumDiskStatsRefreshIntervalInSeconds) + if (_diskStatsSnapshot.Length == 0 || (now - _lastRefreshTime).TotalSeconds > MinimumDiskStatsRefreshIntervalInSeconds) { _diskStatsSnapshot = GetAllDiskStats(); _lastRefreshTime = now; @@ -152,26 +155,19 @@ private List GetDiskStatsSnapshot() return _diskStatsSnapshot; } - private List GetAllDiskStats() + private DiskStats[] GetAllDiskStats() { if (_diskStatsUnavailable && _timeProvider.GetUtcNow() - _lastDiskStatsFailure < _retryInterval) { - return []; + return Array.Empty(); } 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(); - + DiskStats[] diskStatsList = _diskStatsReader.ReadAll(_skipDevicePrefixes); _diskStatsUnavailable = false; + return diskStatsList; } catch (Exception ex) when ( @@ -190,6 +186,6 @@ ex is DirectoryNotFoundException || Log.HandleDiskStatsException(_logger, ex.Message); } - return []; + return Array.Empty(); } } diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkMetrics.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkMetrics.cs index 164ff43b1e0..5add8ccfe75 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkMetrics.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkMetrics.cs @@ -58,11 +58,11 @@ public IEnumerable> GetMeasurements() List> measurements = new(24); // IPv4: - var stateV4 = GetTcpStateInfoWithRetry(_tcpStateInfoProvider.GetIpV4TcpStateInfo, ref _v4Unavailable, ref _lastV4Failure); + TcpStateInfo stateV4 = GetTcpStateInfoWithRetry(_tcpStateInfoProvider.GetIpV4TcpStateInfo, ref _v4Unavailable, ref _lastV4Failure); CreateMeasurements(tcpVersionFourTag, measurements, stateV4); // IPv6: - var stateV6 = GetTcpStateInfoWithRetry(_tcpStateInfoProvider.GetIpV6TcpStateInfo, ref _v6Unavailable, ref _lastV6Failure); + TcpStateInfo stateV6 = GetTcpStateInfoWithRetry(_tcpStateInfoProvider.GetIpV6TcpStateInfo, ref _v6Unavailable, ref _lastV6Failure); CreateMeasurements(tcpVersionSixTag, measurements, stateV6); return measurements; @@ -94,7 +94,7 @@ private TcpStateInfo GetTcpStateInfoWithRetry( { try { - var state = getStateInfoFunc(); + TcpStateInfo state = getStateInfoFunc(); _ = Interlocked.Exchange(ref unavailableFlag, 0); return state; } 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 index c5098b2d284..1f7738bb030 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Disk/DiskStatsReaderTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Disk/DiskStatsReaderTests.cs @@ -13,6 +13,8 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Disk.Test; [OSSkipCondition(OperatingSystems.Windows | OperatingSystems.MacOSX, SkipReason = "Linux specific tests")] public class DiskStatsReaderTests { + private static readonly string[] _skipDevicePrefixes = new[] { "ram", "loop", "dm-" }; + [Fact] public void Test_ReadAll_Valid_DiskStats() { @@ -39,8 +41,17 @@ public void Test_ReadAll_Valid_DiskStats() }); var reader = new DiskStatsReader(fileSystem); - var dictionary = reader.ReadAll().ToDictionary(x => x.DeviceName); - Assert.Equal(15, dictionary.Count); + var dictionary = reader.ReadAll(_skipDevicePrefixes).ToDictionary(x => x.DeviceName); + + var expectedDevices = new[] + { + "nvme1n1", "nvme1n1p1", "nvme0n1", "nvme0n1p1", "nvme0n1p2", "nvme0n1p3", "sda" + }; + Assert.Equal(expectedDevices.Length, dictionary.Count); + foreach (var device in expectedDevices) + { + Assert.True(dictionary.ContainsKey(device), $"Expected device {device} to be present."); + } var disk1 = dictionary["nvme0n1"]; Assert.Equal(6_090_587u, disk1.ReadsCompleted); @@ -61,31 +72,18 @@ public void Test_ReadAll_Valid_DiskStats() Assert.Equal(1_659_742u, disk1.FlushRequestsCompleted); Assert.Equal(515_787u, disk1.TimeFlushingMs); - var disk2 = dictionary["dm-8"]; - Assert.Equal(100_601u, disk2.ReadsCompleted); + var disk2 = dictionary["sda"]; + Assert.Equal(0u, 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.SectorsRead); + Assert.Equal(0u, disk2.TimeReadingMs); + Assert.Equal(0u, 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.SectorsWritten); + Assert.Equal(0u, 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); + Assert.Equal(0u, disk2.TimeIoMs); + Assert.Equal(0u, disk2.WeightedTimeIoMs); } [Fact] @@ -104,7 +102,7 @@ public void Test_ReadAll_With_Invalid_Lines() }); var reader = new DiskStatsReader(fileSystem); - var dictionary = reader.ReadAll().ToDictionary(x => x.DeviceName); + var dictionary = reader.ReadAll(_skipDevicePrefixes).ToDictionary(x => x.DeviceName); Assert.Equal(3, dictionary.Count); var disk1 = dictionary["nvme1n1"]; @@ -132,4 +130,29 @@ public void Test_ReadAll_With_Invalid_Lines() var disk3 = dictionary["nvme0n1"]; Assert.NotNull(disk3); } + + [Fact] + public void Test_ReadAll_Skips_Prefixes() + { + string diskStatsFileContent = + " 7 0 loop0 100 0 1000 10 1000 0 10000 100 0 1000 100 0 0 0 0 100 100\n" + + " 1 0 ram0 200 0 2000 20 2000 0 20000 200 0 2000 200 0 0 0 0 200 200\n" + + " 259 0 nvme0n1 300 0 3000 30 3000 0 30000 300 0 3000 300 0 0 0 0 300 300\n" + + " 252 0 dm-0 400 0 4000 40 4000 0 40000 400 0 4000 400 0 0 0 0 400 400\n" + + " 8 0 sda 500 0 5000 50 5000 0 50000 500 0 5000 500 0 0 0 0 500 500\n"; + + var fileSystem = new HardcodedValueFileSystem(new Dictionary + { + { new FileInfo("/proc/diskstats"), diskStatsFileContent } + }); + + var reader = new DiskStatsReader(fileSystem); + var dictionary = reader.ReadAll(_skipDevicePrefixes).ToDictionary(x => x.DeviceName); + + Assert.DoesNotContain("loop0", dictionary.Keys); + Assert.DoesNotContain("ram0", dictionary.Keys); + Assert.DoesNotContain("dm-0", dictionary.Keys); + Assert.Contains("nvme0n1", dictionary.Keys); + Assert.Contains("sda", dictionary.Keys); + } } 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 index 1c69be74709..4b8186c8fb7 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Disk/FakeDiskStatsReader.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Disk/FakeDiskStatsReader.cs @@ -11,15 +11,15 @@ internal class FakeDiskStatsReader(Dictionary> stats) : { private int _index; - public List ReadAll() + public DiskStats[] ReadAll(string[] skipDevicePrefixes) { if (_index >= stats.Values.First().Count) { throw new InvalidOperationException("No more values available."); } - List list = stats.Values.Select(x => x[_index]).ToList(); + DiskStats[] result = stats.Values.Select(x => x[_index]).ToArray(); _index++; - return list; + return result; } } 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 index 88757c7a1fb..80ebc818894 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Disk/LinuxSystemDiskMetricsTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Disk/LinuxSystemDiskMetricsTests.cs @@ -20,6 +20,7 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Disk.Test; [OSSkipCondition(OperatingSystems.Windows | OperatingSystems.MacOSX, SkipReason = "Linux specific tests")] public class LinuxSystemDiskMetricsTests { + private static readonly string[] _skipDevicePrefixes = new[] { "ram", "loop", "dm-" }; private readonly FakeLogger _fakeLogger = new(); [Fact] @@ -233,7 +234,7 @@ public void GetAllDiskStats_RetriesAfterFailureInterval() }; var diskStatsReaderMock = new Mock(); - diskStatsReaderMock.Setup(r => r.ReadAll()).Throws(); + diskStatsReaderMock.Setup(r => r.ReadAll(_skipDevicePrefixes)).Throws(); var metrics = new LinuxSystemDiskMetrics( _fakeLogger, @@ -246,20 +247,20 @@ public void GetAllDiskStats_RetriesAfterFailureInterval() ioCollector.RecordObservableInstruments(); Assert.Empty(ioCollector.GetMeasurementSnapshot()); - diskStatsReaderMock.Verify(r => r.ReadAll(), Times.Once); + diskStatsReaderMock.Verify(r => r.ReadAll(_skipDevicePrefixes), Times.Once); ioCollector.RecordObservableInstruments(); Assert.Empty(ioCollector.GetMeasurementSnapshot()); - diskStatsReaderMock.Verify(r => r.ReadAll(), Times.Once); + diskStatsReaderMock.Verify(r => r.ReadAll(_skipDevicePrefixes), Times.Once); fakeTimeProvider.Advance(TimeSpan.FromMinutes(5).Add(TimeSpan.FromSeconds(1))); ioCollector.RecordObservableInstruments(); Assert.Empty(ioCollector.GetMeasurementSnapshot()); - diskStatsReaderMock.Verify(r => r.ReadAll(), Times.Exactly(2)); + diskStatsReaderMock.Verify(r => r.ReadAll(_skipDevicePrefixes), Times.Exactly(2)); diskStatsReaderMock.Reset(); - diskStatsReaderMock.Setup(r => r.ReadAll()).Returns(new List { diskStats }); + diskStatsReaderMock.Setup(r => r.ReadAll(_skipDevicePrefixes)).Returns(new DiskStats[] { diskStats }); fakeTimeProvider.Advance(TimeSpan.FromMinutes(5).Add(TimeSpan.FromSeconds(1))); @@ -267,7 +268,7 @@ public void GetAllDiskStats_RetriesAfterFailureInterval() var measurements = ioCollector.GetMeasurementSnapshot(); Assert.NotEmpty(measurements); Assert.Contains(measurements, m => m.Tags.Any(t => t.Value?.ToString() == "sda")); - diskStatsReaderMock.Verify(r => r.ReadAll(), Times.Once); + diskStatsReaderMock.Verify(r => r.ReadAll(_skipDevicePrefixes), Times.Once); fakeTimeProvider.Advance(TimeSpan.FromMinutes(5).Add(TimeSpan.FromSeconds(1))); @@ -275,6 +276,39 @@ public void GetAllDiskStats_RetriesAfterFailureInterval() measurements = ioCollector.GetMeasurementSnapshot(); Assert.NotEmpty(measurements); Assert.Contains(measurements, m => m.Tags.Any(t => t.Value?.ToString() == "sda")); - diskStatsReaderMock.Verify(r => r.ReadAll(), Times.Exactly(2)); + diskStatsReaderMock.Verify(r => r.ReadAll(_skipDevicePrefixes), Times.Exactly(2)); + } + + [Fact] + public void Metrics_Are_Not_Created_When_ReadAll_Throws_FileNotFoundException() + { + using var meterFactory = new TestMeterFactory(); + var options = new ResourceMonitoringOptions { EnableSystemDiskIoMetrics = true }; + var diskStatsReaderMock = new Mock(); + diskStatsReaderMock.Setup(r => r.ReadAll(_skipDevicePrefixes)).Throws(); + + var fakeTimeProvider = new FakeTimeProvider(); + + _ = new LinuxSystemDiskMetrics( + _fakeLogger, + meterFactory, + Options.Options.Create(options), + fakeTimeProvider, + diskStatsReaderMock.Object); + + Meter meter = meterFactory.Meters.Single(); + + using var diskIoCollector = new MetricCollector(meter, ResourceUtilizationInstruments.SystemDiskIo); + using var operationCollector = new MetricCollector(meter, ResourceUtilizationInstruments.SystemDiskOperations); + using var ioTimeCollector = new MetricCollector(meter, ResourceUtilizationInstruments.SystemDiskIoTime); + + diskIoCollector.RecordObservableInstruments(); + operationCollector.RecordObservableInstruments(); + ioTimeCollector.RecordObservableInstruments(); + + Assert.Empty(diskIoCollector.GetMeasurementSnapshot()); + Assert.Empty(operationCollector.GetMeasurementSnapshot()); + Assert.Empty(ioTimeCollector.GetMeasurementSnapshot()); + diskStatsReaderMock.Verify(r => r.ReadAll(_skipDevicePrefixes), Times.Once); } } diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxNetworkMetricsTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxNetworkMetricsTests.cs index da410964a96..40536a3245c 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxNetworkMetricsTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxNetworkMetricsTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; using System.Diagnostics.Metrics; using System.IO; using System.Linq; @@ -39,7 +40,7 @@ public void CreatesMeter_WithCorrectName() _tcpStateInfoProvider.Object, _timeProvider); - var meter = meterFactory.Meters.Single(); + Meter meter = meterFactory.Meters.Single(); Assert.Equal(ResourceUtilizationInstruments.MeterName, meter.Name); } @@ -51,8 +52,8 @@ public void GetTcpStateInfoWithRetry_SuccessfulCall_ReturnsState() _tcpStateInfoProvider.Setup(p => p.GetIpV4TcpStateInfo()).Returns(expectedV4); _tcpStateInfoProvider.Setup(p => p.GetIpV6TcpStateInfo()).Returns(expectedV6); - var metrics = CreateMetrics(); - var measurements = metrics.GetMeasurements().ToList(); + LinuxNetworkMetrics metrics = CreateMetrics(); + List> measurements = metrics.GetMeasurements().ToList(); Assert.Contains(measurements, m => HasTagWithValue(m, "network.type", "ipv4", 42)); Assert.Contains(measurements, m => HasTagWithValue(m, "system.network.state", "close", 42)); @@ -68,8 +69,8 @@ public void GetTcpStateInfoWithRetry_Failure_SetsUnavailableAndReturnsDefault(Ty { _tcpStateInfoProvider.Setup(p => p.GetIpV4TcpStateInfo()).Throws((Exception)Activator.CreateInstance(exceptionType)!); - var metrics = CreateMetrics(); - var measurements = metrics.GetMeasurements().ToList(); + LinuxNetworkMetrics metrics = CreateMetrics(); + List> measurements = metrics.GetMeasurements().ToList(); Assert.All(measurements.Take(11), m => Assert.Equal(0, m.Value)); } @@ -81,11 +82,11 @@ public void GetTcpStateInfoWithRetry_DuringRetryInterval_ReturnsDefault() .Throws(new FileNotFoundException()) .Returns(new TcpStateInfo { ClosedCount = 123 }); - var metrics = CreateMetrics(); - var first = metrics.GetMeasurements().ToList(); + LinuxNetworkMetrics metrics = CreateMetrics(); + List> first = metrics.GetMeasurements().ToList(); _timeProvider.Advance(TimeSpan.FromMinutes(2)); - var second = metrics.GetMeasurements().ToList(); + List> second = metrics.GetMeasurements().ToList(); Assert.All(first.Take(11), m => Assert.Equal(0, m.Value)); Assert.All(second.Take(11), m => Assert.Equal(0, m.Value)); @@ -99,11 +100,11 @@ public void GetTcpStateInfoWithRetry_AfterRetryInterval_ResetsUnavailableOnSucce .Throws(new FileNotFoundException()) .Returns(new TcpStateInfo { ClosedCount = 99 }); - var metrics = CreateMetrics(); - var first = metrics.GetMeasurements().ToList(); + LinuxNetworkMetrics metrics = CreateMetrics(); + List> first = metrics.GetMeasurements().ToList(); _timeProvider.Advance(TimeSpan.FromMinutes(6)); - var second = metrics.GetMeasurements().ToList(); + List> second = metrics.GetMeasurements().ToList(); Assert.All(first.Take(11), m => Assert.Equal(0, m.Value)); Assert.Equal(99, second[0].Value); @@ -115,7 +116,7 @@ public void GetTcpStateInfoWithRetry_AfterRetryInterval_ResetsUnavailableOnSucce private static bool HasTagWithValue(Measurement measurement, string tagKey, string tagValue, long expectedValue) { - foreach (var tag in measurement.Tags) + foreach (KeyValuePair tag in measurement.Tags) { if (tag.Key == tagKey && string.Equals(tag.Value as string, tagValue, StringComparison.Ordinal)) { diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Tcp6TableInfoTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Tcp6TableInfoTests.cs index 88fa4ab4c88..a5dfdb5c170 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Tcp6TableInfoTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Tcp6TableInfoTests.cs @@ -234,11 +234,12 @@ public void Test_Tcp6TableInfo_Get_UnsuccessfulStatus_All_The_Time() SourceIpAddresses = new HashSet { "[::1]" }, SamplingInterval = DefaultTimeSpan }; - WindowsTcpStateInfo tcp6TableInfo = new WindowsTcpStateInfo(Options.Options.Create(options)); + + var tcp6TableInfo = new WindowsTcpStateInfo(Options.Options.Create(options)); tcp6TableInfo.SetGetTcp6TableDelegate(FakeGetTcp6TableWithUnsuccessfulStatusAllTheTime); Assert.Throws(() => { - var tcpStateInfo = tcp6TableInfo.GetIpV6TcpStateInfo(); + TcpStateInfo tcpStateInfo = tcp6TableInfo.GetIpV6TcpStateInfo(); }); } @@ -254,7 +255,7 @@ public void Test_Tcp6TableInfo_Get_InsufficientBuffer_Then_Get_InvalidParameter( tcp6TableInfo.SetGetTcp6TableDelegate(FakeGetTcp6TableWithInsufficientBufferAndInvalidParameter); Assert.Throws(() => { - var tcpStateInfo = tcp6TableInfo.GetIpV6TcpStateInfo(); + TcpStateInfo tcpStateInfo = tcp6TableInfo.GetIpV6TcpStateInfo(); }); } @@ -270,7 +271,7 @@ public void Test_Tcp6TableInfo_Get_Correct_Information() }; WindowsTcpStateInfo tcp6TableInfo = new WindowsTcpStateInfo(Options.Options.Create(options)); tcp6TableInfo.SetGetTcp6TableDelegate(FakeGetTcp6TableWithFakeInformation); - var tcpStateInfo = tcp6TableInfo.GetIpV6TcpStateInfo(); + TcpStateInfo tcpStateInfo = tcp6TableInfo.GetIpV6TcpStateInfo(); Assert.NotNull(tcpStateInfo); Assert.Equal(1, tcpStateInfo.ClosedCount); Assert.Equal(1, tcpStateInfo.ListenCount); From 59bb11efc12817100434fed3fbc47f1cacc97d2b Mon Sep 17 00:00:00 2001 From: amadeuszl Date: Tue, 10 Jun 2025 10:21:19 +0200 Subject: [PATCH 6/6] Switch logging to extension methods --- .../Linux/Disk/LinuxSystemDiskMetrics.cs | 4 +-- .../Linux/LinuxUtilizationProvider.cs | 14 +++++------ .../Linux/Log.cs | 21 ++++++++++------ .../Log.cs | 11 +++++--- .../ResourceMonitorService.cs | 6 ++--- .../Windows/Disk/WindowsDiskMetrics.cs | 4 +-- .../Windows/Log.cs | 25 +++++++++++++------ .../WindowsContainerSnapshotProvider.cs | 10 ++++---- .../Windows/WindowsSnapshotProvider.cs | 8 +++--- 9 files changed, 63 insertions(+), 40 deletions(-) diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Disk/LinuxSystemDiskMetrics.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Disk/LinuxSystemDiskMetrics.cs index 0a256444ba3..f967a1b7650 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Disk/LinuxSystemDiskMetrics.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Disk/LinuxSystemDiskMetrics.cs @@ -175,7 +175,7 @@ ex is FileNotFoundException || ex is DirectoryNotFoundException || ex is UnauthorizedAccessException) { - Log.HandleDiskStatsException(_logger, ex.Message); + _logger.HandleDiskStatsException(ex.Message); _lastDiskStatsFailure = _timeProvider.GetUtcNow(); _diskStatsUnavailable = true; } @@ -183,7 +183,7 @@ ex is DirectoryNotFoundException || catch (Exception ex) #pragma warning restore CA1031 { - Log.HandleDiskStatsException(_logger, ex.Message); + _logger.HandleDiskStatsException(ex.Message); } return Array.Empty(); diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/LinuxUtilizationProvider.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/LinuxUtilizationProvider.cs index c6dde5c0da1..4090bbb5619 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/LinuxUtilizationProvider.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/LinuxUtilizationProvider.cs @@ -112,7 +112,7 @@ public LinuxUtilizationProvider(IOptions options, ILi // _memoryLimit - Resource Memory Limit (in k8s terms) // _memoryLimit - To keep the contract, this parameter will get the Host available memory Resources = new SystemResources(cpuRequest, cpuLimit, _memoryLimit, _memoryLimit); - Log.SystemResourcesInfo(_logger, cpuLimit, cpuRequest, _memoryLimit, _memoryLimit); + _logger.SystemResourcesInfo(cpuLimit, cpuRequest, _memoryLimit, _memoryLimit); } public double CpuUtilizationWithoutHostDelta() @@ -144,7 +144,7 @@ public double CpuUtilizationWithoutHostDelta() { coresUsed = deltaCgroup / (double)deltaCpuPeriodInNanoseconds; - Log.CpuUsageDataV2(_logger, cpuUsageTime, _previousCgroupCpuTime, deltaCpuPeriodInNanoseconds, coresUsed); + _logger.CpuUsageDataV2(cpuUsageTime, _previousCgroupCpuTime, deltaCpuPeriodInNanoseconds, coresUsed); _lastCpuCoresUsed = coresUsed; _refreshAfterCpu = now.Add(_cpuRefreshInterval); @@ -158,7 +158,7 @@ public double CpuUtilizationWithoutHostDelta() { coresUsed = deltaCgroup / actualElapsedNanoseconds; - Log.CpuUsageDataV2(_logger, cpuUsageTime, _previousCgroupCpuTime, actualElapsedNanoseconds, coresUsed); + _logger.CpuUsageDataV2(cpuUsageTime, _previousCgroupCpuTime, actualElapsedNanoseconds, coresUsed); _lastCpuCoresUsed = coresUsed; _refreshAfterCpu = now.Add(_cpuRefreshInterval); @@ -188,7 +188,7 @@ public double CpuUtilizationLimit(float cpuLimit) { _cpuUtilizationLimit100PercentExceededCounter?.Add(1); _cpuUtilizationLimit100PercentExceeded++; - Log.CounterMessage100(_logger, _cpuUtilizationLimit100PercentExceeded); + _logger.CounterMessage100(_cpuUtilizationLimit100PercentExceeded); } // Increment counter if utilization exceeds 110% @@ -196,7 +196,7 @@ public double CpuUtilizationLimit(float cpuLimit) { _cpuUtilizationLimit110PercentExceededCounter?.Add(1); _cpuUtilizationLimit110PercentExceeded++; - Log.CounterMessage110(_logger, _cpuUtilizationLimit110PercentExceeded); + _logger.CounterMessage110(_cpuUtilizationLimit110PercentExceeded); } return utilization; @@ -228,7 +228,7 @@ public double CpuUtilization() { double percentage = Math.Min(One, (double)deltaCgroup / deltaHost); - Log.CpuUsageData(_logger, cgroupCpuTime, hostCpuTime, _previousCgroupCpuTime, _previousHostCpuTime, percentage); + _logger.CpuUsageData(cgroupCpuTime, hostCpuTime, _previousCgroupCpuTime, _previousHostCpuTime, percentage); _cpuPercentage = percentage; _refreshAfterCpu = now.Add(_cpuRefreshInterval); @@ -266,7 +266,7 @@ public double MemoryUtilization() } } - Log.MemoryUsageData(_logger, memoryUsed, _memoryLimit, _memoryPercentage); + _logger.MemoryUsageData(memoryUsed, _memoryLimit, _memoryPercentage); return _memoryPercentage; } diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Log.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Log.cs index d2f9c8f5070..b78f64ddfe0 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Log.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Log.cs @@ -15,7 +15,7 @@ internal static partial class Log "Computed CPU usage with CgroupCpuTime = {cgroupCpuTime}, HostCpuTime = {hostCpuTime}, PreviousCgroupCpuTime = {previousCgroupCpuTime}, PreviousHostCpuTime = {previousHostCpuTime}, CpuPercentage = {cpuPercentage}.")] #pragma warning restore S103 // Lines should not be too long public static partial void CpuUsageData( - ILogger logger, + this ILogger logger, long cgroupCpuTime, long hostCpuTime, long previousCgroupCpuTime, @@ -25,21 +25,26 @@ public static partial void CpuUsageData( [LoggerMessage(2, LogLevel.Debug, "Computed memory usage with MemoryUsedInBytes = {memoryUsed}, MemoryLimit = {memoryLimit}, MemoryPercentage = {memoryPercentage}.")] public static partial void MemoryUsageData( - ILogger logger, + this ILogger logger, ulong memoryUsed, double memoryLimit, double memoryPercentage); [LoggerMessage(3, 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); + public static partial void SystemResourcesInfo( + this ILogger logger, + double cpuLimit, + double cpuRequest, + ulong memoryLimit, + ulong memoryRequest); [LoggerMessage(4, LogLevel.Debug, #pragma warning disable S103 // Lines should not be too long "For CgroupV2, Computed CPU usage with CgroupCpuTime = {cgroupCpuTime}, PreviousCgroupCpuTime = {previousCgroupCpuTime}, ActualElapsedNanoseconds = {actualElapsedNanoseconds}, CpuCores = {cpuCores}.")] #pragma warning restore S103 // Lines should not be too long public static partial void CpuUsageDataV2( - ILogger logger, + this ILogger logger, long cgroupCpuTime, long previousCgroupCpuTime, double actualElapsedNanoseconds, @@ -48,16 +53,18 @@ public static partial void CpuUsageDataV2( [LoggerMessage(5, LogLevel.Debug, "CPU utilization exceeded 100%: Counter = {counterValue}")] public static partial void CounterMessage100( - ILogger logger, + this ILogger logger, long counterValue); [LoggerMessage(6, LogLevel.Debug, "CPU utilization exceeded 110%: Counter = {counterValue}")] public static partial void CounterMessage110( - ILogger logger, + this ILogger logger, long counterValue); [LoggerMessage(7, LogLevel.Warning, "Error while getting disk stats: Error={errorMessage}")] - public static partial void HandleDiskStatsException(ILogger logger, string errorMessage); + public static partial void HandleDiskStatsException( + this ILogger logger, + string errorMessage); } diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Log.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Log.cs index a300450a37d..f1d2b620a71 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Log.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Log.cs @@ -12,15 +12,20 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring; internal static partial class Log { [LoggerMessage(1, LogLevel.Error, "Unable to gather utilization statistics.")] - public static partial void HandledGatherStatisticsException(ILogger logger, Exception e); + public static partial void HandledGatherStatisticsException( + this ILogger logger, + Exception e); [LoggerMessage(2, LogLevel.Error, "Publisher `{Publisher}` was unable to publish utilization statistics.")] - public static partial void HandlePublishUtilizationException(ILogger logger, Exception e, string publisher); + public static partial void HandlePublishUtilizationException( + this ILogger logger, + Exception e, + string publisher); [LoggerMessage(3, LogLevel.Debug, "Snapshot received: TotalTimeSinceStart={totalTimeSinceStart}, KernelTimeSinceStart={kernelTimeSinceStart}, UserTimeSinceStart={userTimeSinceStart}, MemoryUsageInBytes={memoryUsageInBytes}.")] public static partial void SnapshotReceived( - ILogger logger, + this ILogger logger, TimeSpan totalTimeSinceStart, TimeSpan kernelTimeSinceStart, TimeSpan userTimeSinceStart, diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceMonitorService.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceMonitorService.cs index d856cedb5ec..ab77a5dfed9 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceMonitorService.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceMonitorService.cs @@ -115,7 +115,7 @@ internal async Task PublishUtilizationAsync(CancellationToken cancellationToken) { // By Design: Swallow the exception, as they're non-actionable in this code path. // Prioritize app reliability over error visibility - Log.HandlePublishUtilizationException(_logger, e, publisher.GetType().FullName!); + _logger.HandlePublishUtilizationException(e, publisher.GetType().FullName!); } } } @@ -133,13 +133,13 @@ protected override async Task ExecuteAsync(CancellationToken cancellationToken) var snapshot = _provider.GetSnapshot(); _snapshotsStore.Add(snapshot); - Log.SnapshotReceived(_logger, snapshot.TotalTimeSinceStart, snapshot.KernelTimeSinceStart, snapshot.UserTimeSinceStart, snapshot.MemoryUsageInBytes); + _logger.SnapshotReceived(snapshot.TotalTimeSinceStart, snapshot.KernelTimeSinceStart, snapshot.UserTimeSinceStart, snapshot.MemoryUsageInBytes); } catch (Exception e) { // By Design: Swallow the exception, as they're non-actionable in this code path. // Prioritize app reliability over error visibility - Log.HandledGatherStatisticsException(_logger, e); + _logger.HandledGatherStatisticsException(e); } await PublishUtilizationAsync(cancellationToken).ConfigureAwait(false); 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 2927fa657e3..fc6250e80ba 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Disk/WindowsDiskMetrics.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Disk/WindowsDiskMetrics.cs @@ -99,7 +99,7 @@ private void InitializeDiskCounters(IPerformanceCounterFactory performanceCounte } catch (Exception ex) { - Log.DiskIoPerfCounterException(_logger, WindowsDiskPerfCounterNames.DiskIdleTimeCounter, ex.Message); + _logger.DiskIoPerfCounterException(WindowsDiskPerfCounterNames.DiskIdleTimeCounter, ex.Message); } // Initialize disk performance counters for "system.disk.io" and "system.disk.operations" metrics @@ -125,7 +125,7 @@ private void InitializeDiskCounters(IPerformanceCounterFactory performanceCounte } catch (Exception ex) { - Log.DiskIoPerfCounterException(_logger, counterName, ex.Message); + _logger.DiskIoPerfCounterException(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 3d23f87dff9..fdc0d17fe44 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Log.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Log.cs @@ -10,14 +10,15 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows; internal static partial class Log { [LoggerMessage(1, LogLevel.Information, "Resource Monitoring is running inside a Job Object. For more information about Job Objects see https://aka.ms/job-objects")] - public static partial void RunningInsideJobObject(ILogger logger); + public static partial void RunningInsideJobObject(this ILogger logger); [LoggerMessage(2, LogLevel.Information, "Resource Monitoring is running outside of Job Object. For more information about Job Objects see https://aka.ms/job-objects")] - public static partial void RunningOutsideJobObject(ILogger logger); + public static partial void RunningOutsideJobObject(this ILogger logger); [LoggerMessage(3, LogLevel.Debug, "Computed CPU usage with CpuUsageTicks = {cpuUsageTicks}, OldCpuUsageTicks = {oldCpuUsageTicks}, TimeTickDelta = {timeTickDelta}, CpuUnits = {cpuUnits}, CpuPercentage = {cpuPercentage}.")] - public static partial void CpuUsageData(ILogger logger, + public static partial void CpuUsageData( + this ILogger logger, long cpuUsageTicks, long oldCpuUsageTicks, double timeTickDelta, @@ -26,7 +27,8 @@ public static partial void CpuUsageData(ILogger logger, [LoggerMessage(4, LogLevel.Debug, "Computed memory usage with CurrentMemoryUsage = {currentMemoryUsage}, TotalMemory = {totalMemory}, MemoryPercentage = {memoryPercentage}.")] - public static partial void MemoryUsageData(ILogger logger, + public static partial void MemoryUsageData( + this ILogger logger, ulong currentMemoryUsage, double totalMemory, double memoryPercentage); @@ -34,7 +36,8 @@ public static partial void MemoryUsageData(ILogger logger, #pragma warning disable S103 // Lines should not be too long [LoggerMessage(5, LogLevel.Debug, "Computed CPU usage with CpuUsageKernelTicks = {cpuUsageKernelTicks}, CpuUsageUserTicks = {cpuUsageUserTicks}, OldCpuUsageTicks = {oldCpuUsageTicks}, TimeTickDelta = {timeTickDelta}, CpuUnits = {cpuUnits}, CpuPercentage = {cpuPercentage}.")] #pragma warning restore S103 // Lines should not be too long - public static partial void CpuContainerUsageData(ILogger logger, + public static partial void CpuContainerUsageData( + this ILogger logger, long cpuUsageKernelTicks, long cpuUsageUserTicks, long oldCpuUsageTicks, @@ -44,9 +47,17 @@ 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); + public static partial void SystemResourcesInfo( + this 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); + public static partial void DiskIoPerfCounterException( + this ILogger logger, + string counterName, + string errorMessage); } diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/WindowsContainerSnapshotProvider.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/WindowsContainerSnapshotProvider.cs index 56d8bc2e578..27156ea874e 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/WindowsContainerSnapshotProvider.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/WindowsContainerSnapshotProvider.cs @@ -73,7 +73,7 @@ internal WindowsContainerSnapshotProvider( ResourceMonitoringOptions options) { _logger = logger ?? NullLogger.Instance; - Log.RunningInsideJobObject(_logger); + _logger.RunningInsideJobObject(); _metricValueMultiplier = options.UseZeroToOneRangeForMetrics ? One : Hundred; @@ -96,7 +96,7 @@ internal WindowsContainerSnapshotProvider( var cpuRequest = _cpuLimit; var memoryRequest = memoryLimitLong; Resources = new SystemResources(cpuRequest, _cpuLimit, memoryRequest, memoryLimitLong); - Log.SystemResourcesInfo(_logger, _cpuLimit, cpuRequest, memoryLimitLong, memoryRequest); + _logger.SystemResourcesInfo(_cpuLimit, cpuRequest, memoryLimitLong, memoryRequest); var basicAccountingInfo = jobHandle.GetBasicAccountingInfo(); _oldCpuUsageTicks = basicAccountingInfo.TotalKernelTime + basicAccountingInfo.TotalUserTime; @@ -205,7 +205,7 @@ private double MemoryPercentage(Func getMemoryUsage) _refreshAfterMemory = now.Add(_memoryRefreshInterval); } - Log.MemoryUsageData(_logger, memoryUsage, _memoryLimit, _memoryPercentage); + _logger.MemoryUsageData(memoryUsage, _memoryLimit, _memoryPercentage); return _memoryPercentage; } @@ -238,8 +238,8 @@ private double CpuPercentage() // Don't change calculation order, otherwise precision is lost: _cpuPercentage = Math.Min(_metricValueMultiplier, usageTickDelta / timeTickDelta * _metricValueMultiplier); - Log.CpuContainerUsageData( - _logger, basicAccountingInfo.TotalKernelTime, basicAccountingInfo.TotalUserTime, _oldCpuUsageTicks, timeTickDelta, _cpuLimit, _cpuPercentage); + _logger.CpuContainerUsageData( + basicAccountingInfo.TotalKernelTime, basicAccountingInfo.TotalUserTime, _oldCpuUsageTicks, timeTickDelta, _cpuLimit, _cpuPercentage); _oldCpuUsageTicks = currentCpuTicks; _oldCpuTimeTicks = now.Ticks; diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/WindowsSnapshotProvider.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/WindowsSnapshotProvider.cs index 3a20412424c..837cd0f9a06 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/WindowsSnapshotProvider.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/WindowsSnapshotProvider.cs @@ -57,7 +57,7 @@ internal WindowsSnapshotProvider( { _logger = logger ?? NullLogger.Instance; - Log.RunningOutsideJobObject(_logger); + _logger.RunningOutsideJobObject(); _metricValueMultiplier = options.UseZeroToOneRangeForMetrics ? One : Hundred; @@ -68,7 +68,7 @@ internal WindowsSnapshotProvider( // any resource requests or resource limits, therefore using physical values // such as number of CPUs and physical memory and using it for both requests and limits (aka 'guaranteed' and 'max'): Resources = new SystemResources(_cpuUnits, _cpuUnits, totalMemory, totalMemory); - Log.SystemResourcesInfo(_logger, _cpuUnits, _cpuUnits, totalMemory, totalMemory); + _logger.SystemResourcesInfo(_cpuUnits, _cpuUnits, totalMemory, totalMemory); _timeProvider = timeProvider; _getCpuTicksFunc = getCpuTicksFunc; @@ -144,7 +144,7 @@ private double MemoryPercentage() _refreshAfterMemory = now.Add(_memoryRefreshInterval); } - Log.MemoryUsageData(_logger, (ulong)currentMemoryUsage, _totalMemory, _memoryPercentage); + _logger.MemoryUsageData((ulong)currentMemoryUsage, _totalMemory, _memoryPercentage); return _memoryPercentage; } @@ -175,7 +175,7 @@ private double CpuPercentage() // Don't change calculation order, otherwise we loose some precision: _cpuPercentage = Math.Min(_metricValueMultiplier, usageTickDelta / (double)timeTickDelta * _metricValueMultiplier); - Log.CpuUsageData(_logger, currentCpuTicks, _oldCpuUsageTicks, timeTickDelta, _cpuUnits, _cpuPercentage); + _logger.CpuUsageData(currentCpuTicks, _oldCpuUsageTicks, timeTickDelta, _cpuUnits, _cpuPercentage); _oldCpuUsageTicks = currentCpuTicks; _oldCpuTimeTicks = now.Ticks;