| File: Windows\AcceptanceTest.cs | Web Access |
| Project: src\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.Threading.Tasks; using Microsoft.Extensions.Diagnostics.Metrics.Testing; using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes; using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows; using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Interop; using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Time.Testing; using Microsoft.Shared.Instruments; using Moq; using Xunit; using static Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Interop.JobObjectInfo; namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Test.Windows; public class AcceptanceTest { [Fact] public async Task WindowsContainerSnapshotProvider_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_WINDOWS_", LimitsMemory, LimitsCpu, RequestsMemory, RequestsCpu); var logger = new FakeLogger<WindowsContainerSnapshotProvider>(); var memoryInfoMock = new Mock<IMemoryInfo>(); var systemInfoMock = new Mock<ISystemInfo>(); var jobHandleMock = new Mock<IJobHandle>(); var processInfoMock = new Mock<IProcessInfo>(); var memStatus = new MEMORYSTATUSEX { TotalPhys = 3000UL }; memoryInfoMock.Setup(m => m.GetMemoryStatus()).Returns(() => memStatus); var sysInfo = new SYSTEM_INFO { NumberOfProcessors = 2 }; systemInfoMock.Setup(s => s.GetSystemInfo()).Returns(() => sysInfo); // Setup CPU accounting info with initial and updated values JOBOBJECT_BASIC_ACCOUNTING_INFORMATION initialAccountingInfo = new() { TotalKernelTime = 1000, TotalUserTime = 1000 }; JOBOBJECT_BASIC_ACCOUNTING_INFORMATION updatedAccountingInfo = new() { TotalKernelTime = 1500, // 500 ticks increase TotalUserTime = 1500 // 500 ticks increase }; var accountingCallCount = 0; jobHandleMock.Setup(j => j.GetBasicAccountingInfo()) .Returns(() => { accountingCallCount++; return accountingCallCount <= 1 ? initialAccountingInfo : updatedAccountingInfo; }); var cpuLimit = new JOBOBJECT_CPU_RATE_CONTROL_INFORMATION { CpuRate = 10_000 }; jobHandleMock.Setup(j => j.GetJobCpuLimitInfo()).Returns(() => cpuLimit); var limitInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION { JobMemoryLimit = new UIntPtr(LimitsMemory) }; jobHandleMock.Setup(j => j.GetExtendedLimitInfo()).Returns(() => limitInfo); ulong initialAppMemoryUsage = 200UL; ulong initialContainerMemoryUsage = 400UL; ulong updatedAppMemoryUsage = 600UL; ulong updatedContainerMemoryUsage = 1200UL; var processMemoryCallCount = 0; processInfoMock.Setup(p => p.GetCurrentProcessMemoryUsage()) .Returns(() => { processMemoryCallCount++; return processMemoryCallCount <= 1 ? initialAppMemoryUsage : updatedAppMemoryUsage; }); var containerMemoryCallCount = 0; processInfoMock.Setup(p => p.GetMemoryUsage()) .Returns(() => { containerMemoryCallCount++; return containerMemoryCallCount <= 1 ? initialContainerMemoryUsage : updatedContainerMemoryUsage; }); var fakeClock = new FakeTimeProvider(); using var meter = new Meter(nameof(WindowsContainerSnapshotProvider_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 = 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 snapshotProvider = new WindowsContainerSnapshotProvider( processInfoMock.Object, logger, meterFactoryMock.Object, () => jobHandleMock.Object, fakeClock, options, kubernetesResourceQuotaProvider); // Initial state 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); // Initial CPU metrics should be NaN as no time has passed for calculation Assert.True(double.IsNaN(containerCpuLimitMetricCollector.LastMeasurement.Value)); Assert.True(double.IsNaN(containerCpuRequestMetricCollector.LastMeasurement.Value)); // Container memory: 400 bytes / 2000 bytes limit = 20% Assert.Equal(20, containerMemoryLimitMetricCollector.LastMeasurement.Value); // Container memory: 400 bytes / 1000 bytes request = 40% Assert.Equal(40, containerMemoryRequestMetricCollector.LastMeasurement.Value); // Advance time to trigger refresh fakeClock.Advance(options.MemoryConsumptionRefreshInterval); containerCpuLimitMetricCollector.RecordObservableInstruments(); containerMemoryLimitMetricCollector.RecordObservableInstruments(); containerCpuRequestMetricCollector.RecordObservableInstruments(); containerMemoryRequestMetricCollector.RecordObservableInstruments(); // CPU: 1000 ticks increase over 2ms = 2.5% utilization against 2 core limit Assert.Equal(2.5, containerCpuLimitMetricCollector.LastMeasurement.Value); // CPU: 2.5% of 2 cores against 1 core request = 5% Assert.Equal(5, containerCpuRequestMetricCollector.LastMeasurement.Value); // Container memory: 1200 bytes / 2000 bytes limit = 60% Assert.Equal(60, containerMemoryLimitMetricCollector.LastMeasurement.Value); // Container memory: 1200 bytes / 1000 bytes request = 120%, but Min() to 100% Assert.Equal(100, containerMemoryRequestMetricCollector.LastMeasurement.Value); await Task.CompletedTask; } }