File: Linux\LinuxResourceHealthCheckTests.cs
Web Access
Project: src\test\Libraries\Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests\Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests.csproj (Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests)
// 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<object[]> Data =>
        new List<object[]>
        {
            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<IResourceMonitor>();
        var logger = new FakeLogger<LinuxUtilizationProvider>();
        using var meter = new Meter("Microsoft.Extensions.Diagnostics.ResourceMonitoring");
        var meterFactoryMock = new Mock<IMeterFactory>();
        meterFactoryMock.Setup(x => x.Create(It.IsAny<MeterOptions>()))
            .Returns(meter);
 
        var parser = new Mock<ILinuxUtilizationParser>();
        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 resourceMonitoringOptions = Options.Options.Create<ResourceMonitoringOptions>(new());
        var resourceQuotaProvider = new LinuxResourceQuotaProvider(parser.Object, resourceMonitoringOptions);
        var provider = new LinuxUtilizationProvider(resourceMonitoringOptions, parser.Object, meterFactoryMock.Object, resourceQuotaProvider, logger, fakeClock);
 
        var checkOptions = new ResourceUtilizationHealthCheckOptions
        {
            CpuThresholds = cpuThresholds,
            MemoryThresholds = memoryThresholds,
            UseObservableResourceMonitoringInstruments = true
        };
 
        // Use local MeterListener to collect measurements from this specific meter instance to avoid flakiness
        double cpuUsedPercentage = 0;
        double memoryUsedPercentage = 0;
 
        using var listener = new MeterListener
        {
            InstrumentPublished = (instrument, listener) =>
            {
                if (ReferenceEquals(meter, instrument.Meter))
                {
                    listener.EnableMeasurementEvents(instrument);
                }
            }
        };
 
        listener.SetMeasurementEventCallback<double>((instrument, measurement, _, _) =>
        {
            switch (instrument.Name)
            {
                case "process.cpu.utilization":
                case "container.cpu.limit.utilization":
                    cpuUsedPercentage = measurement * 100;
                    break;
                case "dotnet.process.memory.virtual.utilization":
                case "container.memory.limit.utilization":
                    memoryUsedPercentage = measurement * 100;
                    break;
            }
        });
 
        listener.Start();
 
        // Act
        fakeClock.Advance(TimeSpan.FromMilliseconds(1));
        listener.RecordObservableInstruments();
 
        var healthCheckResult = await ResourceUtilizationHealthCheck.EvaluateHealthStatusAsync(
            cpuUsedPercentage,
            memoryUsedPercentage,
            checkOptions);
 
        // Also create ResourceUtilizationHealthCheck and call CheckHealthAsync for code coverage
        var options = Microsoft.Extensions.Options.Options.Create(checkOptions);
        using var healthCheck = new ResourceUtilizationHealthCheck(
            options,
            dataTracker.Object,
            Microsoft.Extensions.Options.Options.Create(new ResourceMonitoringOptions()));
        _ = await healthCheck.CheckHealthAsync(new HealthCheckContext());
 
        // Assert
        Assert.Equal(expected, healthCheckResult.Status);
        Assert.NotEmpty(healthCheckResult.Data);
        if (healthCheckResult.Status != HealthStatus.Healthy)
        {
            Assert.Equal(expectedDescription, healthCheckResult.Description);
        }
    }
}