From bf28441742798c3e7a9367b9144343bd2c19e73c Mon Sep 17 00:00:00 2001 From: evgenyfedorov2 Date: Tue, 7 Jan 2025 09:30:42 +0100 Subject: [PATCH 1/2] Make HealthChecks.ResourceUtilization use observable instruments --- ...cs.HealthChecks.ResourceUtilization.csproj | 2 + .../ResourceUtilizationHealthCheck.cs | 107 ++++++++-- .../ResourceUtilizationHealthCheckOptions.cs | 27 ++- ...ions.Diagnostics.ResourceMonitoring.csproj | 1 + .../HealthCheckTestData.cs | 138 ++++++++++++ .../Linux/LinuxResourceHealthCheckTests.cs | 200 ++++++++++++++++++ ...lthChecks.ResourceUtilization.Tests.csproj | 7 +- .../ResourceHealthCheckExtensionsTests.cs | 83 ++++++++ .../ResourceHealthCheckTests.cs | 130 +----------- ...otProvider_EmitsLogRecord.Net.verified.txt | 2 +- 10 files changed, 549 insertions(+), 148 deletions(-) create mode 100644 test/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests/HealthCheckTestData.cs create mode 100644 test/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests/Linux/LinuxResourceHealthCheckTests.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.csproj b/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.csproj index 2c193e256b2..540e7b08f3d 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.csproj +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.csproj @@ -8,7 +8,9 @@ true true + true true + true diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization/ResourceUtilizationHealthCheck.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization/ResourceUtilizationHealthCheck.cs index 13ac4cac9bc..021dcd56ef7 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization/ResourceUtilizationHealthCheck.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization/ResourceUtilizationHealthCheck.cs @@ -1,7 +1,9 @@ // 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.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Diagnostics.ResourceMonitoring; @@ -13,21 +15,53 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks; /// /// Represents a health check for in-container resources . /// -internal sealed class ResourceUtilizationHealthCheck : IHealthCheck +internal sealed class ResourceUtilizationHealthCheck : IHealthCheck, IDisposable { + private readonly double _multiplier; + private readonly MeterListener? _meterListener; private readonly ResourceUtilizationHealthCheckOptions _options; private readonly IResourceMonitor _dataTracker; + private double _cpuUsedPercentage; + private double _memoryUsedPercentage; /// /// Initializes a new instance of the class. /// /// The options. /// The datatracker. - public ResourceUtilizationHealthCheck(IOptions options, - IResourceMonitor dataTracker) + public ResourceUtilizationHealthCheck(IOptions options, IResourceMonitor dataTracker) { +#if NETFRAMEWORK + _multiplier = 1; +#else + // Due to a bug on Windows https://github.com/dotnet/extensions/issues/5472, + // the CPU utilization comes in the range [0, 100]. + if (OperatingSystem.IsWindows()) + { + _multiplier = 1; + } + + // On Linux, the CPU utilization comes in the correct range [0, 1], which we will be converting to percentage. + else + { +#pragma warning disable S109 // Magic numbers should not be used + _multiplier = 100; +#pragma warning restore S109 // Magic numbers should not be used + } +#endif _options = Throw.IfMemberNull(options, options.Value); _dataTracker = Throw.IfNull(dataTracker); + + if (_options.UseObservableResourceMonitoringInstruments) + { + _meterListener = new() + { + InstrumentPublished = OnInstrumentPublished + }; + + _meterListener.SetMeasurementEventCallback(OnMeasurementRecorded); + _meterListener.Start(); + } } /// @@ -38,19 +72,29 @@ public ResourceUtilizationHealthCheck(IOptionsA that completes when the health check has finished, yielding the status of the component being checked. public Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) { - var utilization = _dataTracker.GetUtilization(_options.SamplingWindow); + if (_options.UseObservableResourceMonitoringInstruments) + { + _meterListener!.RecordObservableInstruments(); + } + else + { + var utilization = _dataTracker.GetUtilization(_options.SamplingWindow); + _cpuUsedPercentage = utilization.CpuUsedPercentage; + _memoryUsedPercentage = utilization.MemoryUsedPercentage; + } + IReadOnlyDictionary data = new Dictionary { - { nameof(utilization.CpuUsedPercentage), utilization.CpuUsedPercentage }, - { nameof(utilization.MemoryUsedPercentage), utilization.MemoryUsedPercentage }, + { "CpuUsedPercentage", _cpuUsedPercentage }, + { "MemoryUsedPercentage", _memoryUsedPercentage }, }; - bool cpuUnhealthy = utilization.CpuUsedPercentage > _options.CpuThresholds.UnhealthyUtilizationPercentage; - bool memoryUnhealthy = utilization.MemoryUsedPercentage > _options.MemoryThresholds.UnhealthyUtilizationPercentage; + bool cpuUnhealthy = _cpuUsedPercentage > _options.CpuThresholds.UnhealthyUtilizationPercentage; + bool memoryUnhealthy = _memoryUsedPercentage > _options.MemoryThresholds.UnhealthyUtilizationPercentage; if (cpuUnhealthy || memoryUnhealthy) { - string message = string.Empty; + string message; if (cpuUnhealthy && memoryUnhealthy) { message = "CPU and memory usage is above the limit"; @@ -67,12 +111,12 @@ public Task CheckHealthAsync(HealthCheckContext context, Canc return Task.FromResult(HealthCheckResult.Unhealthy(message, default, data)); } - bool cpuDegraded = utilization.CpuUsedPercentage > _options.CpuThresholds.DegradedUtilizationPercentage; - bool memoryDegraded = utilization.MemoryUsedPercentage > _options.MemoryThresholds.DegradedUtilizationPercentage; + bool cpuDegraded = _cpuUsedPercentage > _options.CpuThresholds.DegradedUtilizationPercentage; + bool memoryDegraded = _memoryUsedPercentage > _options.MemoryThresholds.DegradedUtilizationPercentage; if (cpuDegraded || memoryDegraded) { - string message = string.Empty; + string message; if (cpuDegraded && memoryDegraded) { message = "CPU and memory usage is close to the limit"; @@ -91,4 +135,43 @@ public Task CheckHealthAsync(HealthCheckContext context, Canc return Task.FromResult(HealthCheckResult.Healthy(default, data)); } + + /// + public void Dispose() + { + Dispose(true); + } + + private void Dispose(bool disposing) + { + if (disposing) + { + _meterListener?.Dispose(); + } + } + + private void OnInstrumentPublished(Instrument instrument, MeterListener listener) + { + if (instrument.Meter.Name is "Microsoft.Extensions.Diagnostics.ResourceMonitoring") + { + listener.EnableMeasurementEvents(instrument); + } + } + + private void OnMeasurementRecorded( + Instrument instrument, double measurement, + ReadOnlySpan> tags, object? state) + { + switch (instrument.Name) + { + case "process.cpu.utilization": + case "container.cpu.limit.utilization": + _cpuUsedPercentage = measurement * _multiplier; + break; + case "dotnet.process.memory.virtual.utilization": + case "container.memory.limit.utilization": + _memoryUsedPercentage = measurement * _multiplier; + break; + } + } } diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization/ResourceUtilizationHealthCheckOptions.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization/ResourceUtilizationHealthCheckOptions.cs index 115c94da936..c7036a30eac 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization/ResourceUtilizationHealthCheckOptions.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization/ResourceUtilizationHealthCheckOptions.cs @@ -2,9 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using Microsoft.Extensions.Diagnostics.ResourceMonitoring; +using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.Options; using Microsoft.Shared.Data.Validation; +using Microsoft.Shared.DiagnosticIds; namespace Microsoft.Extensions.Diagnostics.HealthChecks; @@ -20,8 +21,7 @@ public class ResourceUtilizationHealthCheckOptions /// Gets or sets thresholds for CPU utilization. /// /// - /// The thresholds are periodically compared against the utilization samples provided by - /// the registered . + /// The thresholds are periodically compared against the utilization samples provided by the Resource Monitoring library. /// [ValidateObjectMembers] public ResourceUsageThresholds CpuThresholds { get; set; } = new ResourceUsageThresholds(); @@ -30,18 +30,33 @@ public class ResourceUtilizationHealthCheckOptions /// Gets or sets thresholds for memory utilization. /// /// - /// The thresholds are periodically compared against the utilization samples provided by - /// the registered . + /// The thresholds are periodically compared against the utilization samples provided by the Resource Monitoring library. /// [ValidateObjectMembers] public ResourceUsageThresholds MemoryThresholds { get; set; } = new ResourceUsageThresholds(); /// - /// Gets or sets the time window for used for calculating CPU and memory utilization averages. + /// Gets or sets the time window used for calculating CPU and memory utilization averages. /// /// /// The default value is 5 seconds. /// +#pragma warning disable CS0436 // Type conflicts with imported type + [Obsolete(DiagnosticIds.Obsoletions.NonObservableResourceMonitoringApiMessage, + DiagnosticId = DiagnosticIds.Obsoletions.NonObservableResourceMonitoringApiDiagId, + UrlFormat = DiagnosticIds.UrlFormat)] +#pragma warning restore CS0436 // Type conflicts with imported type [TimeSpan(MinimumSamplingWindow, int.MaxValue)] public TimeSpan SamplingWindow { get; set; } = DefaultSamplingWindow; + + /// + /// Gets or sets a value indicating whether the observable instruments will be used for getting CPU and Memory usage + /// as opposed to the default API which is obsolete. + /// + /// + /// if the observable instruments are used. The default is . + /// In the future the default will be . + /// + [Experimental(diagnosticId: DiagnosticIds.Experiments.HealthChecks, UrlFormat = DiagnosticIds.UrlFormat)] + public bool UseObservableResourceMonitoringInstruments { get; set; } } diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Microsoft.Extensions.Diagnostics.ResourceMonitoring.csproj b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Microsoft.Extensions.Diagnostics.ResourceMonitoring.csproj index c5c9783fbe9..41140bb4395 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Microsoft.Extensions.Diagnostics.ResourceMonitoring.csproj +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Microsoft.Extensions.Diagnostics.ResourceMonitoring.csproj @@ -47,5 +47,6 @@ + diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests/HealthCheckTestData.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests/HealthCheckTestData.cs new file mode 100644 index 00000000000..881b5817320 --- /dev/null +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests/HealthCheckTestData.cs @@ -0,0 +1,138 @@ +// 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; +using System.Collections.Generic; + +namespace Microsoft.Extensions.Diagnostics.HealthChecks.Test; + +public class HealthCheckTestData : IEnumerable +{ + public static IEnumerable Data => + new List + { + new object[] + { + HealthStatus.Healthy, + 0.1, + 0UL, + 1000UL, + new ResourceUsageThresholds(), + new ResourceUsageThresholds(), + "", + }, + new object[] + { + HealthStatus.Healthy, + 0.2, + 0UL, + 1000UL, + new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.2, UnhealthyUtilizationPercentage = 0.2 }, + new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.2, UnhealthyUtilizationPercentage = 0.2 }, + "" + }, + new object[] + { + HealthStatus.Healthy, + 0.2, + 2UL, + 1000UL, + new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.2, UnhealthyUtilizationPercentage = 0.2 }, + new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.2, UnhealthyUtilizationPercentage = 0.2 }, + "" + }, + new object[] + { + HealthStatus.Degraded, + 0.4, + 3UL, + 1000UL, + new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.2, UnhealthyUtilizationPercentage = 0.4 }, + new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.2, UnhealthyUtilizationPercentage = 0.4 }, + "CPU and memory usage is close to the limit" + }, + new object[] + { + HealthStatus.Unhealthy, + 0.5, + 5UL, + 1000UL, + new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.2, UnhealthyUtilizationPercentage = 0.4 }, + new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.2, UnhealthyUtilizationPercentage = 0.4 }, + "CPU and memory usage is above the limit" + }, + new object[] + { + HealthStatus.Unhealthy, + 0.5, + 5UL, + 1000UL, + new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.4, UnhealthyUtilizationPercentage = 0.2 }, + new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.4, UnhealthyUtilizationPercentage = 0.2 }, + "CPU and memory usage is above the limit" + }, + new object[] + { + HealthStatus.Degraded, + 0.3, + 3UL, + 1000UL, + new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.2 }, + new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.2 }, + "CPU and memory usage is close to the limit" + }, + new object[] + { + HealthStatus.Unhealthy, + 0.5, + 5UL, + 1000UL, + new ResourceUsageThresholds { UnhealthyUtilizationPercentage = 0.4 }, + new ResourceUsageThresholds { UnhealthyUtilizationPercentage = 0.4 }, + "CPU and memory usage is above the limit" + }, + new object[] + { + HealthStatus.Degraded, + 0.3, + 3UL, + 1000UL, + new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.2, UnhealthyUtilizationPercentage = 0.4 }, + new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.9, UnhealthyUtilizationPercentage = 0.9 }, + "CPU usage is close to the limit" + }, + new object[] + { + HealthStatus.Degraded, + 0.1, + 3UL, + 1000UL, + new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.9, UnhealthyUtilizationPercentage = 0.9 }, + new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.2, UnhealthyUtilizationPercentage = 0.4 }, + "Memory usage is close to the limit" + }, + new object[] + { + HealthStatus.Unhealthy, + 0.5, + 5UL, + 1000UL, + new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.2, UnhealthyUtilizationPercentage = 0.4 }, + new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.9, UnhealthyUtilizationPercentage = 0.9 }, + "CPU usage is above the limit" + }, + new object[] + { + HealthStatus.Unhealthy, + 0.1, + 5UL, + 1000UL, + new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.9, UnhealthyUtilizationPercentage = 0.9 }, + new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.2, UnhealthyUtilizationPercentage = 0.4 }, + "Memory usage is above the limit" + }, + }; + + public IEnumerator GetEnumerator() => Data.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); +} diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests/Linux/LinuxResourceHealthCheckTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests/Linux/LinuxResourceHealthCheckTests.cs new file mode 100644 index 00000000000..ddf2a40a784 --- /dev/null +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests/Linux/LinuxResourceHealthCheckTests.cs @@ -0,0 +1,200 @@ +// 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.Threading.Tasks; +using Microsoft.Extensions.Diagnostics.ResourceMonitoring; +using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux; +using Microsoft.Extensions.Logging.Testing; +using Microsoft.Extensions.Time.Testing; +using Microsoft.TestUtilities; +using Moq; +using Xunit; + +namespace Microsoft.Extensions.Diagnostics.HealthChecks.Test; + +public class LinuxResourceHealthCheckTests +{ + public static IEnumerable Data => + new List + { + new object[] + { + HealthStatus.Healthy, + 0.1, + 0UL, + 1000UL, + new ResourceUsageThresholds(), + new ResourceUsageThresholds(), + "", + }, + new object[] + { + HealthStatus.Healthy, + 0.2, + 0UL, + 1000UL, + new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.2, UnhealthyUtilizationPercentage = 0.2 }, + new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.2, UnhealthyUtilizationPercentage = 0.2 }, + "" + }, + new object[] + { + HealthStatus.Healthy, + 0.2, + 2UL, + 1000UL, + new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.2, UnhealthyUtilizationPercentage = 0.2 }, + new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.2, UnhealthyUtilizationPercentage = 0.2 }, + "" + }, + new object[] + { + HealthStatus.Degraded, + 0.4, + 3UL, + 1000UL, + new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.2, UnhealthyUtilizationPercentage = 0.4 }, + new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.2, UnhealthyUtilizationPercentage = 0.4 }, + "CPU and memory usage is close to the limit" + }, + new object[] + { + HealthStatus.Unhealthy, + 0.5, + 5UL, + 1000UL, + new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.2, UnhealthyUtilizationPercentage = 0.4 }, + new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.2, UnhealthyUtilizationPercentage = 0.4 }, + "CPU and memory usage is above the limit" + }, + new object[] + { + HealthStatus.Unhealthy, + 0.5, + 5UL, + 1000UL, + new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.4, UnhealthyUtilizationPercentage = 0.2 }, + new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.4, UnhealthyUtilizationPercentage = 0.2 }, + "CPU and memory usage is above the limit" + }, + new object[] + { + HealthStatus.Degraded, + 0.3, + 3UL, + 1000UL, + new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.2 }, + new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.2 }, + "CPU and memory usage is close to the limit" + }, + new object[] + { + HealthStatus.Unhealthy, + 0.5, + 5UL, + 1000UL, + new ResourceUsageThresholds { UnhealthyUtilizationPercentage = 0.4 }, + new ResourceUsageThresholds { UnhealthyUtilizationPercentage = 0.4 }, + "CPU and memory usage is above the limit" + }, + new object[] + { + HealthStatus.Degraded, + 0.3, + 3UL, + 1000UL, + new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.2, UnhealthyUtilizationPercentage = 0.4 }, + new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.9, UnhealthyUtilizationPercentage = 0.9 }, + "CPU usage is close to the limit" + }, + new object[] + { + HealthStatus.Degraded, + 0.1, + 3UL, + 1000UL, + new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.9, UnhealthyUtilizationPercentage = 0.9 }, + new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.2, UnhealthyUtilizationPercentage = 0.4 }, + "Memory usage is close to the limit" + }, + new object[] + { + HealthStatus.Unhealthy, + 0.5, + 5UL, + 1000UL, + new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.2, UnhealthyUtilizationPercentage = 0.4 }, + new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.9, UnhealthyUtilizationPercentage = 0.9 }, + "CPU usage is above the limit" + }, + new object[] + { + HealthStatus.Unhealthy, + 0.1, + 5UL, + 1000UL, + new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.9, UnhealthyUtilizationPercentage = 0.9 }, + new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.2, UnhealthyUtilizationPercentage = 0.4 }, + "Memory usage is above the limit" + }, + }; + + [ConditionalTheory] + [MemberData(nameof(Data))] + [OSSkipCondition(OperatingSystems.Windows | OperatingSystems.MacOSX, SkipReason = "Linux-specific test.")] + public async Task TestCpuAndMemoryChecks_WithMetrics( + HealthStatus expected, double utilization, ulong memoryUsed, ulong totalMemory, + ResourceUsageThresholds cpuThresholds, ResourceUsageThresholds memoryThresholds, + string expectedDescription) + { + var fakeClock = new FakeTimeProvider(); + var dataTracker = new Mock(); + var meterName = Guid.NewGuid().ToString(); + var logger = new FakeLogger(); + using var meter = new Meter("Microsoft.Extensions.Diagnostics.ResourceMonitoring"); + var meterFactoryMock = new Mock(); + meterFactoryMock.Setup(x => x.Create(It.IsAny())) + .Returns(meter); + + var parser = new Mock(); + parser.Setup(x => x.GetHostCpuCount()).Returns(1); + parser.Setup(x => x.GetCgroupLimitedCpus()).Returns(1); + parser.Setup(x => x.GetCgroupRequestCpu()).Returns(1); + parser.SetupSequence(x => x.GetHostCpuUsageInNanoseconds()) + .Returns(0) + .Returns(1000); + parser.SetupSequence(x => x.GetCgroupCpuUsageInNanoseconds()) + .Returns(0) + .Returns((long)(10 * utilization)); + parser.Setup(x => x.GetMemoryUsageInBytes()).Returns(memoryUsed); + parser.Setup(x => x.GetAvailableMemoryInBytes()).Returns(totalMemory); + + var provider = new LinuxUtilizationProvider(Options.Options.Create(new()), parser.Object, meterFactoryMock.Object, logger, fakeClock); + + var checkContext = new HealthCheckContext(); + var checkOptions = new ResourceUtilizationHealthCheckOptions + { + CpuThresholds = cpuThresholds, + MemoryThresholds = memoryThresholds, + UseObservableResourceMonitoringInstruments = true + }; + + var options = Microsoft.Extensions.Options.Options.Create(checkOptions); + using var healthCheck = new ResourceUtilizationHealthCheck(options, dataTracker.Object); + + // Act + fakeClock.Advance(TimeSpan.FromMilliseconds(1)); + var healthCheckResult = await healthCheck.CheckHealthAsync(checkContext); + + // Assert + Assert.Equal(expected, healthCheckResult.Status); + Assert.NotEmpty(healthCheckResult.Data); + if (healthCheckResult.Status != HealthStatus.Healthy) + { + Assert.Equal(expectedDescription, healthCheckResult.Description); + } + } +} diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests.csproj b/test/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests.csproj index db56ee5e43f..dcd6d4e40db 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests.csproj +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests.csproj @@ -7,10 +7,15 @@ + - + + + + + diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests/ResourceHealthCheckExtensionsTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests/ResourceHealthCheckExtensionsTests.cs index 5319e2922d8..dcd9fccce53 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests/ResourceHealthCheckExtensionsTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests/ResourceHealthCheckExtensionsTests.cs @@ -3,14 +3,20 @@ using System; using System.Collections.Generic; +using System.Diagnostics.Metrics; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.ResourceMonitoring; +using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows; +using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Interop; +using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Options; +using Microsoft.Extensions.Time.Testing; using Microsoft.TestUtilities; using Moq; using Xunit; +using static Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Interop.JobObjectInfo; namespace Microsoft.Extensions.Diagnostics.HealthChecks.Test; @@ -454,6 +460,83 @@ public void TestNullChecks() Assert.Throws(() => ((IHealthChecksBuilder)null!).AddResourceUtilizationHealthCheck((IConfigurationSection)null!)); } + [ConditionalTheory] + [ClassData(typeof(HealthCheckTestData))] + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX, SkipReason = "Windows-specific test.")] + public async Task TestCpuAndMemoryChecks_WithMetrics( + HealthStatus expected, double utilization, ulong memoryUsed, ulong totalMemory, + ResourceUsageThresholds cpuThresholds, ResourceUsageThresholds memoryThresholds, + string expectedDescription) + { + var logger = new FakeLogger(); + var fakeClock = new FakeTimeProvider(); + var dataTracker = new Mock(); + SYSTEM_INFO sysInfo = default; + sysInfo.NumberOfProcessors = 1; + Mock systemInfoMock = new(); + systemInfoMock.Setup(s => s.GetSystemInfo()) + .Returns(() => sysInfo); + Mock memoryInfoMock = new(); + + JOBOBJECT_CPU_RATE_CONTROL_INFORMATION cpuLimit = default; + cpuLimit.CpuRate = 7_000; + Mock jobHandleMock = new(); + jobHandleMock.Setup(j => j.GetJobCpuLimitInfo()).Returns(() => cpuLimit); + + Mock processInfoMock = new(); + var appMemoryUsage = memoryUsed; + processInfoMock.Setup(p => p.GetMemoryUsage()).Returns(() => appMemoryUsage); + + JOBOBJECT_EXTENDED_LIMIT_INFORMATION limitInfo = default; + limitInfo.JobMemoryLimit = new UIntPtr(totalMemory); + jobHandleMock.Setup(j => j.GetExtendedLimitInfo()).Returns(() => limitInfo); + + JOBOBJECT_BASIC_ACCOUNTING_INFORMATION initialAccountingInfo = default; + JOBOBJECT_BASIC_ACCOUNTING_INFORMATION accountingInfoAfter1Ms = default; + accountingInfoAfter1Ms.TotalUserTime = (long)(utilization * 100); + jobHandleMock.SetupSequence(j => j.GetBasicAccountingInfo()) + .Returns(() => initialAccountingInfo) // this is called from the WindowsContainerSnapshotProvider's constructor + .Returns(() => accountingInfoAfter1Ms); // this is called from the WindowsContainerSnapshotProvider's CpuPercentage method + + using var meter = new Meter("Microsoft.Extensions.Diagnostics.ResourceMonitoring"); + var meterFactoryMock = new Mock(); + meterFactoryMock.Setup(x => x.Create(It.IsAny())) + .Returns(meter); + + var snapshotProvider = new WindowsContainerSnapshotProvider( + memoryInfoMock.Object, + systemInfoMock.Object, + processInfoMock.Object, + logger, + meterFactoryMock.Object, + () => jobHandleMock.Object, + fakeClock, + new ResourceMonitoringOptions { CpuConsumptionRefreshInterval = TimeSpan.FromMilliseconds(1) }); + + var checkContext = new HealthCheckContext(); + var checkOptions = new ResourceUtilizationHealthCheckOptions + { + CpuThresholds = cpuThresholds, + MemoryThresholds = memoryThresholds, + UseObservableResourceMonitoringInstruments = true + }; + + var options = Microsoft.Extensions.Options.Options.Create(checkOptions); + using var healthCheck = new ResourceUtilizationHealthCheck(options, dataTracker.Object); + + // Act + fakeClock.Advance(TimeSpan.FromMilliseconds(1)); + var healthCheckResult = await healthCheck.CheckHealthAsync(checkContext); + + // Assert + Assert.Equal(expected, healthCheckResult.Status); + Assert.NotEmpty(healthCheckResult.Data); + if (healthCheckResult.Status != HealthStatus.Healthy) + { + Assert.Equal(expectedDescription, healthCheckResult.Description); + } + } + private static IConfiguration SetupResourceHealthCheckConfiguration( string cpuDegradedThreshold, string cpuUnhealthyThreshold, diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests/ResourceHealthCheckTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests/ResourceHealthCheckTests.cs index 77a145c218a..c5d90fa58a1 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests/ResourceHealthCheckTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests/ResourceHealthCheckTests.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Extensions.Diagnostics.ResourceMonitoring; using Microsoft.Extensions.Options; @@ -13,133 +12,8 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks.Test; public class ResourceHealthCheckTests { - public static IEnumerable Data => - new List - { - new object[] - { - HealthStatus.Healthy, - 0.1, - 0UL, - 1000UL, - new ResourceUsageThresholds(), - new ResourceUsageThresholds(), - "", - }, - new object[] - { - HealthStatus.Healthy, - 0.2, - 0UL, - 1000UL, - new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.2, UnhealthyUtilizationPercentage = 0.2 }, - new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.2, UnhealthyUtilizationPercentage = 0.2 }, - "" - }, - new object[] - { - HealthStatus.Healthy, - 0.2, - 2UL, - 1000UL, - new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.2, UnhealthyUtilizationPercentage = 0.2 }, - new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.2, UnhealthyUtilizationPercentage = 0.2 }, - "" - }, - new object[] - { - HealthStatus.Degraded, - 0.4, - 3UL, - 1000UL, - new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.2, UnhealthyUtilizationPercentage = 0.4 }, - new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.2, UnhealthyUtilizationPercentage = 0.4 }, - "CPU and memory usage is close to the limit" - }, - new object[] - { - HealthStatus.Unhealthy, - 0.5, - 5UL, - 1000UL, - new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.2, UnhealthyUtilizationPercentage = 0.4 }, - new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.2, UnhealthyUtilizationPercentage = 0.4 }, - "CPU and memory usage is above the limit" - }, - new object[] - { - HealthStatus.Unhealthy, - 0.5, - 5UL, - 1000UL, - new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.4, UnhealthyUtilizationPercentage = 0.2 }, - new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.4, UnhealthyUtilizationPercentage = 0.2 }, - "CPU and memory usage is above the limit" - }, - new object[] - { - HealthStatus.Degraded, - 0.3, - 3UL, - 1000UL, - new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.2 }, - new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.2 }, - "CPU and memory usage is close to the limit" - }, - new object[] - { - HealthStatus.Unhealthy, - 0.5, - 5UL, - 1000UL, - new ResourceUsageThresholds { UnhealthyUtilizationPercentage = 0.4 }, - new ResourceUsageThresholds { UnhealthyUtilizationPercentage = 0.4 }, - "CPU and memory usage is above the limit" - }, - new object[] - { - HealthStatus.Degraded, - 0.3, - 3UL, - 1000UL, - new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.2, UnhealthyUtilizationPercentage = 0.4 }, - new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.9, UnhealthyUtilizationPercentage = 0.9 }, - "CPU usage is close to the limit" - }, - new object[] - { - HealthStatus.Degraded, - 0.1, - 3UL, - 1000UL, - new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.9, UnhealthyUtilizationPercentage = 0.9 }, - new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.2, UnhealthyUtilizationPercentage = 0.4 }, - "Memory usage is close to the limit" - }, - new object[] - { - HealthStatus.Unhealthy, - 0.5, - 5UL, - 1000UL, - new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.2, UnhealthyUtilizationPercentage = 0.4 }, - new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.9, UnhealthyUtilizationPercentage = 0.9 }, - "CPU usage is above the limit" - }, - new object[] - { - HealthStatus.Unhealthy, - 0.1, - 5UL, - 1000UL, - new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.9, UnhealthyUtilizationPercentage = 0.9 }, - new ResourceUsageThresholds { DegradedUtilizationPercentage = 0.2, UnhealthyUtilizationPercentage = 0.4 }, - "Memory usage is above the limit" - }, - }; - [Theory] - [MemberData(nameof(Data))] + [ClassData(typeof(HealthCheckTestData))] public async Task TestCpuAndMemoryChecks(HealthStatus expected, double utilization, ulong memoryUsed, ulong totalMemory, ResourceUsageThresholds cpuThresholds, ResourceUsageThresholds memoryThresholds, string expectedDescription) { @@ -159,7 +33,7 @@ public async Task TestCpuAndMemoryChecks(HealthStatus expected, double utilizati }; var options = Microsoft.Extensions.Options.Options.Create(checkOptions); - var healthCheck = new ResourceUtilizationHealthCheck(options, dataTracker.Object); + using var healthCheck = new ResourceUtilizationHealthCheck(options, dataTracker.Object); var healthCheckResult = await healthCheck.CheckHealthAsync(checkContext); Assert.Equal(expected, healthCheckResult.Status); Assert.NotEmpty(healthCheckResult.Data); diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Verified/WindowsContainerSnapshotProviderTests.SnapshotProvider_EmitsLogRecord.Net.verified.txt b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Verified/WindowsContainerSnapshotProviderTests.SnapshotProvider_EmitsLogRecord.Net.verified.txt index 990c5b1cbaa..294a724db56 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Verified/WindowsContainerSnapshotProviderTests.SnapshotProvider_EmitsLogRecord.Net.verified.txt +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Verified/WindowsContainerSnapshotProviderTests.SnapshotProvider_EmitsLogRecord.Net.verified.txt @@ -65,4 +65,4 @@ LevelEnabled: true, Timestamp: DateTimeOffset_1 } -] \ No newline at end of file +] From 28aaf4162a3059b91297a502caf62367af319b6e Mon Sep 17 00:00:00 2001 From: R9 Fundamentals Date: Thu, 23 Jan 2025 16:55:52 +0100 Subject: [PATCH 2/2] PR comments --- ...ResourceUtilizationHealthCheck.Obsolete.cs | 38 +++++ .../ResourceUtilizationHealthCheck.cs | 137 +++++++++--------- .../ResourceUtilizationHealthCheckOptions.cs | 4 +- 3 files changed, 111 insertions(+), 68 deletions(-) create mode 100644 src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization/ResourceUtilizationHealthCheck.Obsolete.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization/ResourceUtilizationHealthCheck.Obsolete.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization/ResourceUtilizationHealthCheck.Obsolete.cs new file mode 100644 index 00000000000..577d4d76e0e --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization/ResourceUtilizationHealthCheck.Obsolete.cs @@ -0,0 +1,38 @@ +// 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.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Diagnostics.ResourceMonitoring; +using Microsoft.Shared.DiagnosticIds; +using Microsoft.Shared.Diagnostics; + +namespace Microsoft.Extensions.Diagnostics.HealthChecks; + +/// +/// Represents a health check for in-container resources . +/// +internal sealed partial class ResourceUtilizationHealthCheck : IHealthCheck +{ +#pragma warning disable CS0436 // Type conflicts with imported type + [Obsolete(DiagnosticIds.Obsoletions.NonObservableResourceMonitoringApiMessage, + DiagnosticId = DiagnosticIds.Obsoletions.NonObservableResourceMonitoringApiDiagId, + UrlFormat = DiagnosticIds.UrlFormat)] + public void ObsoleteConstructor(IResourceMonitor dataTracker) => _dataTracker = Throw.IfNull(dataTracker); + + /// + /// Runs the health check. + /// + /// A that can be used to cancel the health check. + /// A that completes when the health check has finished, yielding the status of the component being checked. +#pragma warning disable IDE0060 // Remove unused parameter + [Obsolete(DiagnosticIds.Obsoletions.NonObservableResourceMonitoringApiMessage, + DiagnosticId = DiagnosticIds.Obsoletions.NonObservableResourceMonitoringApiDiagId, + UrlFormat = DiagnosticIds.UrlFormat)] + public Task ObsoleteCheckHealthAsync(CancellationToken cancellationToken = default) + { + var utilization = _dataTracker!.GetUtilization(_options.SamplingWindow); + return ResourceUtilizationHealthCheck.EvaluateHealthStatusAsync(utilization.CpuUsedPercentage, utilization.MemoryUsedPercentage, _options); + } +} diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization/ResourceUtilizationHealthCheck.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization/ResourceUtilizationHealthCheck.cs index 021dcd56ef7..d2e41f3a649 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization/ResourceUtilizationHealthCheck.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization/ResourceUtilizationHealthCheck.cs @@ -15,82 +15,26 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks; /// /// Represents a health check for in-container resources . /// -internal sealed class ResourceUtilizationHealthCheck : IHealthCheck, IDisposable +internal sealed partial class ResourceUtilizationHealthCheck : IHealthCheck, IDisposable { private readonly double _multiplier; private readonly MeterListener? _meterListener; private readonly ResourceUtilizationHealthCheckOptions _options; - private readonly IResourceMonitor _dataTracker; + private IResourceMonitor? _dataTracker; private double _cpuUsedPercentage; private double _memoryUsedPercentage; - /// - /// Initializes a new instance of the class. - /// - /// The options. - /// The datatracker. - public ResourceUtilizationHealthCheck(IOptions options, IResourceMonitor dataTracker) - { -#if NETFRAMEWORK - _multiplier = 1; -#else - // Due to a bug on Windows https://github.com/dotnet/extensions/issues/5472, - // the CPU utilization comes in the range [0, 100]. - if (OperatingSystem.IsWindows()) - { - _multiplier = 1; - } - - // On Linux, the CPU utilization comes in the correct range [0, 1], which we will be converting to percentage. - else - { -#pragma warning disable S109 // Magic numbers should not be used - _multiplier = 100; -#pragma warning restore S109 // Magic numbers should not be used - } -#endif - _options = Throw.IfMemberNull(options, options.Value); - _dataTracker = Throw.IfNull(dataTracker); - - if (_options.UseObservableResourceMonitoringInstruments) - { - _meterListener = new() - { - InstrumentPublished = OnInstrumentPublished - }; - - _meterListener.SetMeasurementEventCallback(OnMeasurementRecorded); - _meterListener.Start(); - } - } - - /// - /// Runs the health check. - /// - /// A context object associated with the current execution. - /// A that can be used to cancel the health check. - /// A that completes when the health check has finished, yielding the status of the component being checked. - public Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) +#pragma warning disable EA0014 // The async method doesn't support cancellation + public static Task EvaluateHealthStatusAsync(double cpuUsedPercentage, double memoryUsedPercentage, ResourceUtilizationHealthCheckOptions options) { - if (_options.UseObservableResourceMonitoringInstruments) - { - _meterListener!.RecordObservableInstruments(); - } - else - { - var utilization = _dataTracker.GetUtilization(_options.SamplingWindow); - _cpuUsedPercentage = utilization.CpuUsedPercentage; - _memoryUsedPercentage = utilization.MemoryUsedPercentage; - } - IReadOnlyDictionary data = new Dictionary { - { "CpuUsedPercentage", _cpuUsedPercentage }, - { "MemoryUsedPercentage", _memoryUsedPercentage }, + { "CpuUsedPercentage", cpuUsedPercentage }, + { "MemoryUsedPercentage", memoryUsedPercentage }, }; - bool cpuUnhealthy = _cpuUsedPercentage > _options.CpuThresholds.UnhealthyUtilizationPercentage; - bool memoryUnhealthy = _memoryUsedPercentage > _options.MemoryThresholds.UnhealthyUtilizationPercentage; + bool cpuUnhealthy = cpuUsedPercentage > options.CpuThresholds.UnhealthyUtilizationPercentage; + bool memoryUnhealthy = memoryUsedPercentage > options.MemoryThresholds.UnhealthyUtilizationPercentage; if (cpuUnhealthy || memoryUnhealthy) { @@ -111,8 +55,8 @@ public Task CheckHealthAsync(HealthCheckContext context, Canc return Task.FromResult(HealthCheckResult.Unhealthy(message, default, data)); } - bool cpuDegraded = _cpuUsedPercentage > _options.CpuThresholds.DegradedUtilizationPercentage; - bool memoryDegraded = _memoryUsedPercentage > _options.MemoryThresholds.DegradedUtilizationPercentage; + bool cpuDegraded = cpuUsedPercentage > options.CpuThresholds.DegradedUtilizationPercentage; + bool memoryDegraded = memoryUsedPercentage > options.MemoryThresholds.DegradedUtilizationPercentage; if (cpuDegraded || memoryDegraded) { @@ -135,6 +79,67 @@ public Task CheckHealthAsync(HealthCheckContext context, Canc return Task.FromResult(HealthCheckResult.Healthy(default, data)); } +#pragma warning restore EA0014 // The async method doesn't support cancellation + + /// + /// Initializes a new instance of the class. + /// + /// The options. + /// The datatracker. + public ResourceUtilizationHealthCheck(IOptions options, IResourceMonitor dataTracker) + { + _options = Throw.IfMemberNull(options, options.Value); + if (!_options.UseObservableResourceMonitoringInstruments) + { + ObsoleteConstructor(dataTracker); + return; + } + +#if NETFRAMEWORK + _multiplier = 1; +#else + // Due to a bug on Windows https://github.com/dotnet/extensions/issues/5472, + // the CPU utilization comes in the range [0, 100]. + if (OperatingSystem.IsWindows()) + { + _multiplier = 1; + } + + // On Linux, the CPU utilization comes in the correct range [0, 1], which we will be converting to percentage. + else + { +#pragma warning disable S109 // Magic numbers should not be used + _multiplier = 100; +#pragma warning restore S109 // Magic numbers should not be used + } +#endif + + _meterListener = new() + { + InstrumentPublished = OnInstrumentPublished + }; + + _meterListener.SetMeasurementEventCallback(OnMeasurementRecorded); + _meterListener.Start(); + } + + /// + /// Runs the health check. + /// + /// A context object associated with the current execution. + /// A that can be used to cancel the health check. + /// A that completes when the health check has finished, yielding the status of the component being checked. + public Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) + { + if (!_options.UseObservableResourceMonitoringInstruments) + { + return ObsoleteCheckHealthAsync(cancellationToken); + } + + _meterListener!.RecordObservableInstruments(); + + return EvaluateHealthStatusAsync(_cpuUsedPercentage, _memoryUsedPercentage, _options); + } /// public void Dispose() diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization/ResourceUtilizationHealthCheckOptions.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization/ResourceUtilizationHealthCheckOptions.cs index c7036a30eac..55832e5dcfd 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization/ResourceUtilizationHealthCheckOptions.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization/ResourceUtilizationHealthCheckOptions.cs @@ -41,11 +41,11 @@ public class ResourceUtilizationHealthCheckOptions /// /// The default value is 5 seconds. /// -#pragma warning disable CS0436 // Type conflicts with imported type +#pragma warning disable CS0436 // Type conflicts with imported type [Obsolete(DiagnosticIds.Obsoletions.NonObservableResourceMonitoringApiMessage, DiagnosticId = DiagnosticIds.Obsoletions.NonObservableResourceMonitoringApiDiagId, UrlFormat = DiagnosticIds.UrlFormat)] -#pragma warning restore CS0436 // Type conflicts with imported type +#pragma warning restore CS0436 // Type conflicts with imported type [TimeSpan(MinimumSamplingWindow, int.MaxValue)] public TimeSpan SamplingWindow { get; set; } = DefaultSamplingWindow;