| File: ResourceUtilizationHealthCheck.cs | Web Access |
| Project: src\src\Libraries\Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization\Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.csproj (Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization) |
// 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; using Microsoft.Extensions.Options; using Microsoft.Shared.Diagnostics; using Microsoft.Shared.Instruments; namespace Microsoft.Extensions.Diagnostics.HealthChecks; /// <summary> /// Represents a health check for in-container resources <see cref="IHealthCheck"/>. /// </summary> internal sealed partial class ResourceUtilizationHealthCheck : IHealthCheck, IDisposable { #if !NETFRAMEWORK private const double Hundred = 100.0; #endif private readonly double _multiplier; private readonly MeterListener? _meterListener; private readonly ResourceUtilizationHealthCheckOptions _options; private IResourceMonitor? _dataTracker; private double _cpuUsedPercentage; private double _memoryUsedPercentage; #pragma warning disable EA0014 // The async method doesn't support cancellation public static Task<HealthCheckResult> EvaluateHealthStatusAsync(double cpuUsedPercentage, double memoryUsedPercentage, ResourceUtilizationHealthCheckOptions options) { IReadOnlyDictionary<string, object> data = new Dictionary<string, object> { { "CpuUsedPercentage", cpuUsedPercentage }, { "MemoryUsedPercentage", memoryUsedPercentage }, }; bool cpuUnhealthy = cpuUsedPercentage > options.CpuThresholds.UnhealthyUtilizationPercentage; bool memoryUnhealthy = memoryUsedPercentage > options.MemoryThresholds.UnhealthyUtilizationPercentage; if (cpuUnhealthy || memoryUnhealthy) { string message; if (cpuUnhealthy && memoryUnhealthy) { message = "CPU and memory usage is above the limit"; } else if (cpuUnhealthy) { message = "CPU usage is above the limit"; } else { message = "Memory usage is above the limit"; } return Task.FromResult(HealthCheckResult.Unhealthy(message, data: data)); } bool cpuDegraded = cpuUsedPercentage > options.CpuThresholds.DegradedUtilizationPercentage; bool memoryDegraded = memoryUsedPercentage > options.MemoryThresholds.DegradedUtilizationPercentage; if (cpuDegraded || memoryDegraded) { string message; if (cpuDegraded && memoryDegraded) { message = "CPU and memory usage is close to the limit"; } else if (cpuDegraded) { message = "CPU usage is close to the limit"; } else { message = "Memory usage is close to the limit"; } return Task.FromResult(HealthCheckResult.Degraded(message, data: data)); } return Task.FromResult(HealthCheckResult.Healthy(data: data)); } #pragma warning restore EA0014 // The async method doesn't support cancellation #if !NETFRAMEWORK /// <summary> /// Initializes a new instance of the <see cref="ResourceUtilizationHealthCheck"/> class. /// </summary> /// <param name="options">The options.</param> /// <param name="dataTracker">The datatracker.</param> /// <param name="rmOptions">The Resource Monitoring options.</param> public ResourceUtilizationHealthCheck(IOptions<ResourceUtilizationHealthCheckOptions> options, IResourceMonitor dataTracker, IOptions<ResourceMonitoringOptions> rmOptions) { _options = Throw.IfMemberNull(options, options.Value); if (!_options.UseObservableResourceMonitoringInstruments) { ObsoleteConstructor(dataTracker); return; } // On Windows there was a bug https://github.com/dotnet/extensions/issues/5472, // so the CPU utilization might come in the range [0, 100], which means we don't need to multiply it by 100. var rmOptionsValue = Throw.IfMemberNull(rmOptions, rmOptions.Value); if (OperatingSystem.IsWindows() && !rmOptionsValue.UseZeroToOneRangeForMetrics) { _multiplier = 1; } // On Linux, the CPU utilization always comes in the correct range [0, 1], which we need to convert to percentage. else { _multiplier = Hundred; } _meterListener = new() { InstrumentPublished = OnInstrumentPublished }; _meterListener.SetMeasurementEventCallback<double>(OnMeasurementRecorded); _meterListener.Start(); } #else /// <summary> /// Initializes a new instance of the <see cref="ResourceUtilizationHealthCheck"/> class. /// </summary> /// <param name="options">The options.</param> /// <param name="dataTracker">The datatracker.</param> public ResourceUtilizationHealthCheck(IOptions<ResourceUtilizationHealthCheckOptions> options, IResourceMonitor dataTracker) { _options = Throw.IfMemberNull(options, options.Value); if (!_options.UseObservableResourceMonitoringInstruments) { ObsoleteConstructor(dataTracker); return; } _multiplier = 1; _meterListener = new() { InstrumentPublished = OnInstrumentPublished }; _meterListener.SetMeasurementEventCallback<double>(OnMeasurementRecorded); _meterListener.Start(); } #endif /// <summary> /// Runs the health check. /// </summary> /// <param name="context">A context object associated with the current execution.</param> /// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the health check.</param> /// <returns>A <see cref="Task{HealthCheckResult}"/> that completes when the health check has finished, yielding the status of the component being checked.</returns> public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) { if (!_options.UseObservableResourceMonitoringInstruments) { return ObsoleteCheckHealthAsync(cancellationToken); } _meterListener!.RecordObservableInstruments(); return EvaluateHealthStatusAsync(_cpuUsedPercentage, _memoryUsedPercentage, _options); } /// <inheritdoc /> 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 == ResourceUtilizationInstruments.MeterName) { listener.EnableMeasurementEvents(instrument); } } private void OnMeasurementRecorded( Instrument instrument, double measurement, ReadOnlySpan<KeyValuePair<string, object?>> tags, object? state) { switch (instrument.Name) { case ResourceUtilizationInstruments.ProcessCpuUtilization: case ResourceUtilizationInstruments.ContainerCpuLimitUtilization: _cpuUsedPercentage = measurement * _multiplier; break; case ResourceUtilizationInstruments.ProcessMemoryUtilization: case ResourceUtilizationInstruments.ContainerMemoryLimitUtilization: _memoryUsedPercentage = measurement * _multiplier; break; } } }