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 @@
truetrue
+ truetrue
+ true
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 13ac4cac9bc..d2e41f3a649 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,44 +15,30 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks;
///
/// Represents a health check for in-container resources .
///
-internal sealed class ResourceUtilizationHealthCheck : IHealthCheck
+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)
- {
- _options = Throw.IfMemberNull(options, options.Value);
- _dataTracker = Throw.IfNull(dataTracker);
- }
-
- ///
- /// 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)
{
- var utilization = _dataTracker.GetUtilization(_options.SamplingWindow);
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 +55,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 +79,104 @@ 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()
+ {
+ 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..55832e5dcfd 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