File: Linux\AcceptanceTest.cs
Project: ..\..\..\test\Libraries\Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes.Tests\Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes.Tests.csproj (Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes.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.Diagnostics.Metrics;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Microsoft.Extensions.Diagnostics.Metrics.Testing;
using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes;
using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux;
using Microsoft.Extensions.Logging.Testing;
using Microsoft.Extensions.Time.Testing;
using Microsoft.Shared.Instruments;
using Moq;
using Xunit;
 
namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Test.Linux;
 
public class AcceptanceTest
{
    public AcceptanceTest()
    {
        Assert.SkipUnless(RuntimeInformation.IsOSPlatform(OSPlatform.Linux), "Skipped on Windows/macOS");
    }
 
    [Fact]
    public async Task LinuxUtilizationProvider_MeasuredWithKubernetesMetadata()
    {
        const ulong LimitsMemory = 2_000;
        const ulong LimitsCpu = 2_000; // 2 cores in millicores
        const ulong RequestsMemory = 1_000;
        const ulong RequestsCpu = 1_000; // 1 core in millicores
 
        using var environmentSetup = new TestKubernetesEnvironmentSetup();
 
        environmentSetup.SetupKubernetesEnvironment(
            "ACCEPTANCE_TEST_LINUX_",
            LimitsMemory,
            LimitsCpu,
            RequestsMemory,
            RequestsCpu);
 
        var logger = new FakeLogger<LinuxUtilizationProvider>();
        var parserMock = new Mock<ILinuxUtilizationParser>();
 
        ulong initialMemoryUsage = 400UL;
        ulong updatedMemoryUsage = 1200UL;
 
        var memoryCallCount = 0;
        parserMock.Setup(p => p.GetMemoryUsageInBytes())
            .Returns(() =>
            {
                memoryCallCount++;
                return memoryCallCount <= 1 ? initialMemoryUsage : updatedMemoryUsage;
            });
 
        long initialHostCpuTime = 1000L;
        long initialCgroupCpuTime = 100L;
        long updatedHostCpuTime = 2000L;
        long updatedCgroupCpuTime = 300L;
 
        var hostCpuCallCount = 0;
        parserMock.Setup(p => p.GetHostCpuUsageInNanoseconds())
            .Returns(() =>
            {
                hostCpuCallCount++;
                return hostCpuCallCount <= 1 ? initialHostCpuTime : updatedHostCpuTime;
            });
 
        var cgroupCpuCallCount = 0;
        parserMock.Setup(p => p.GetCgroupCpuUsageInNanoseconds())
            .Returns(() =>
            {
                cgroupCpuCallCount++;
                return cgroupCpuCallCount <= 1 ? initialCgroupCpuTime : updatedCgroupCpuTime;
            });
 
        parserMock.Setup(p => p.GetHostCpuCount()).Returns(2.0f);
        parserMock.Setup(p => p.GetAvailableMemoryInBytes()).Returns(LimitsMemory);
 
        var fakeClock = new FakeTimeProvider();
        using var meter = new Meter(nameof(LinuxUtilizationProvider_MeasuredWithKubernetesMetadata));
        var meterFactoryMock = new Mock<IMeterFactory>();
        meterFactoryMock.Setup(x => x.Create(It.IsAny<MeterOptions>())).Returns(meter);
 
        using var containerCpuLimitMetricCollector = new MetricCollector<double>(meter, ResourceUtilizationInstruments.ContainerCpuLimitUtilization, fakeClock);
        using var containerMemoryLimitMetricCollector = new MetricCollector<double>(meter, ResourceUtilizationInstruments.ContainerMemoryLimitUtilization, fakeClock);
        using var containerCpuRequestMetricCollector = new MetricCollector<double>(meter, ResourceUtilizationInstruments.ContainerCpuRequestUtilization, fakeClock);
        using var containerMemoryRequestMetricCollector = new MetricCollector<double>(meter, ResourceUtilizationInstruments.ContainerMemoryRequestUtilization, fakeClock);
 
        var options = Microsoft.Extensions.Options.Options.Create(new ResourceMonitoringOptions
        {
            MemoryConsumptionRefreshInterval = TimeSpan.FromMilliseconds(2),
            CpuConsumptionRefreshInterval = TimeSpan.FromMilliseconds(2),
            UseZeroToOneRangeForMetrics = false
        });
 
        var kubernetesMetadata = new KubernetesMetadata
        {
            LimitsMemory = LimitsMemory,
            LimitsCpu = LimitsCpu,
            RequestsMemory = RequestsMemory,
            RequestsCpu = RequestsCpu
        };
        var kubernetesResourceQuotaProvider = new KubernetesResourceQuotaProvider(kubernetesMetadata);
 
        var utilizationProvider = new LinuxUtilizationProvider(
            options,
            parserMock.Object,
            meterFactoryMock.Object,
            kubernetesResourceQuotaProvider,
            logger,
            fakeClock);
 
        containerCpuLimitMetricCollector.RecordObservableInstruments();
        containerMemoryLimitMetricCollector.RecordObservableInstruments();
        containerCpuRequestMetricCollector.RecordObservableInstruments();
        containerMemoryRequestMetricCollector.RecordObservableInstruments();
 
        Assert.NotNull(containerCpuLimitMetricCollector.LastMeasurement?.Value);
        Assert.NotNull(containerMemoryLimitMetricCollector.LastMeasurement?.Value);
        Assert.NotNull(containerCpuRequestMetricCollector.LastMeasurement?.Value);
        Assert.NotNull(containerMemoryRequestMetricCollector.LastMeasurement?.Value);
 
        Assert.True(double.IsNaN(containerCpuLimitMetricCollector.LastMeasurement.Value));
        Assert.True(double.IsNaN(containerCpuRequestMetricCollector.LastMeasurement.Value));
 
        // Container memory: 400 bytes / 2000 bytes limit = 20%
        Assert.Equal(0.2, containerMemoryLimitMetricCollector.LastMeasurement.Value);
 
        // Container memory: 400 bytes / 1000 bytes request = 40%
        Assert.Equal(0.4, containerMemoryRequestMetricCollector.LastMeasurement.Value);
 
        fakeClock.Advance(options.Value.MemoryConsumptionRefreshInterval);
        containerCpuLimitMetricCollector.RecordObservableInstruments();
        containerMemoryLimitMetricCollector.RecordObservableInstruments();
        containerCpuRequestMetricCollector.RecordObservableInstruments();
        containerMemoryRequestMetricCollector.RecordObservableInstruments();
 
        // CPU calculation: (300-100)/(2000-1000) = 200/1000 = 0.2 = 20%
        // With 2 host CPUs and 2 core limit: 20% * 2/2 = 20%
        Assert.Equal(0.2, containerCpuLimitMetricCollector.LastMeasurement.Value);
 
        // CPU against 1 core request: 20% * 2/1 = 40%
        Assert.Equal(0.4, containerCpuRequestMetricCollector.LastMeasurement.Value);
 
        // Container memory: 1200 bytes / 2000 bytes limit = 60%
        Assert.Equal(0.6, containerMemoryLimitMetricCollector.LastMeasurement.Value);
 
        // Container memory: 1200 bytes / 1000 bytes request = 120%
        Assert.Equal(1, containerMemoryRequestMetricCollector.LastMeasurement.Value);
 
        await Task.CompletedTask;
    }
}