File: Windows\WindowsSnapshotProviderTests.cs
Web Access
Project: src\test\Libraries\Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests\Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests.csproj (Microsoft.Extensions.Diagnostics.ResourceMonitoring.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.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Diagnostics.Metrics.Testing;
using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Test.Helpers;
using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Interop;
using Microsoft.Extensions.Logging.Testing;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Time.Testing;
using Microsoft.TestUtilities;
using Moq;
using VerifyXunit;
using Xunit;
 
namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Test;
 
[OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX, SkipReason = "Windows specific.")]
[UsesVerify]
public sealed class WindowsSnapshotProviderTests
{
    private const string VerifiedDataDirectory = "Verified";
 
    private readonly Mock<IMeterFactory> _meterFactoryMock;
    private readonly FakeLogger<WindowsSnapshotProvider> _fakeLogger;
    private readonly IOptions<ResourceMonitoringOptions> _options;
 
    public WindowsSnapshotProviderTests()
    {
        _options = Options.Options.Create<ResourceMonitoringOptions>(new());
        using var meter = new Meter(nameof(BasicConstructor));
        _meterFactoryMock = new Mock<IMeterFactory>();
        _meterFactoryMock.Setup(x => x.Create(It.IsAny<MeterOptions>()))
            .Returns(meter);
 
        _fakeLogger = new FakeLogger<WindowsSnapshotProvider>();
    }
 
    [ConditionalFact]
    public void BasicConstructor()
    {
        var provider = new WindowsSnapshotProvider(_fakeLogger, _meterFactoryMock.Object, _options);
        var memoryStatus = new MemoryInfo().GetMemoryStatus();
 
        Assert.Equal(Environment.ProcessorCount, provider.Resources.GuaranteedCpuUnits);
        Assert.Equal(Environment.ProcessorCount, provider.Resources.MaximumCpuUnits);
        Assert.Equal(memoryStatus.TotalPhys, provider.Resources.GuaranteedMemoryInBytes);
        Assert.Equal(memoryStatus.TotalPhys, provider.Resources.MaximumMemoryInBytes);
    }
 
    [ConditionalFact]
    public void GetSnapshot_DoesNotThrowExceptions()
    {
        var provider = new WindowsSnapshotProvider(_fakeLogger, _meterFactoryMock.Object, _options);
 
        var exception = Record.Exception(() => provider.GetSnapshot());
        Assert.Null(exception);
    }
 
    [ConditionalFact]
    public Task SnapshotProvider_EmitsLogRecord()
    {
        var provider = new WindowsSnapshotProvider(_fakeLogger, _meterFactoryMock.Object, _options);
        var logRecords = _fakeLogger.Collector.GetSnapshot();
        Assert.Equal(2, logRecords.Count);
        Assert.StartsWith("System resources information", logRecords[1].Message);
 
        return Verifier.Verify(logRecords[0]).UseDirectory(VerifiedDataDirectory);
    }
 
    [ConditionalFact]
    public void SnapshotProvider_EmitsCpuMetrics()
    {
        var fakeClock = new FakeTimeProvider();
        var cpuTicks = 500L;
        var options = new ResourceMonitoringOptions { CpuConsumptionRefreshInterval = TimeSpan.FromMilliseconds(2) };
        using var meter = new Meter(nameof(SnapshotProvider_EmitsCpuMetrics));
        var meterFactoryMock = new Mock<IMeterFactory>();
        meterFactoryMock.Setup(x => x.Create(It.IsAny<MeterOptions>())).Returns(meter);
 
        var snapshotProvider = new WindowsSnapshotProvider(_fakeLogger, meterFactoryMock.Object, options, fakeClock,
            static () => 2, () => cpuTicks, static () => 0L, static () => 1UL);
 
        cpuTicks = 1_500L;
 
        using var metricCollector = new MetricCollector<double>(meter, ResourceUtilizationInstruments.ProcessCpuUtilization, fakeClock);
 
        // Step #0 - state in the beginning:
        metricCollector.RecordObservableInstruments();
        Assert.NotNull(metricCollector.LastMeasurement);
        Assert.True(double.IsNaN(metricCollector.LastMeasurement.Value));
 
        // Step #1 - simulate 1 millisecond passing and collect metrics again:
        fakeClock.Advance(TimeSpan.FromMilliseconds(1));
        metricCollector.RecordObservableInstruments();
        Assert.Equal(5, metricCollector.LastMeasurement?.Value); // Consuming 5% of the CPU (2 CPUs, 1000 ticks, 1ms).
 
        // Step #2 - simulate another 1 millisecond passing and collect metrics again:
        fakeClock.Advance(TimeSpan.FromMilliseconds(1));
        metricCollector.RecordObservableInstruments();
 
        // CPU usage should be the same as before, as we're not simulating any CPU usage:
        Assert.Equal(5, metricCollector.LastMeasurement?.Value); // Still consuming 5% of the CPU
    }
 
    [ConditionalFact]
    public void SnapshotProvider_EmitsMemoryMetrics()
    {
        var fakeClock = new FakeTimeProvider();
        long memoryUsed = 300L;
        var options = new ResourceMonitoringOptions { MemoryConsumptionRefreshInterval = TimeSpan.FromMilliseconds(2) };
        using var meter = new Meter(nameof(SnapshotProvider_EmitsMemoryMetrics));
        var meterFactoryMock = new Mock<IMeterFactory>();
        meterFactoryMock.Setup(x => x.Create(It.IsAny<MeterOptions>()))
            .Returns(meter);
 
        var snapshotProvider = new WindowsSnapshotProvider(_fakeLogger, meterFactoryMock.Object, options, fakeClock, static () => 1, static () => 0, () => memoryUsed, static () => 3000UL);
 
        using var metricCollector = new MetricCollector<double>(meter, ResourceUtilizationInstruments.ProcessMemoryUtilization, fakeClock);
 
        // Step #0 - state in the beginning:
        metricCollector.RecordObservableInstruments();
        Assert.NotNull(metricCollector.LastMeasurement);
        Assert.Equal(10, metricCollector.LastMeasurement.Value); // Consuming 5% of the memory initially
 
        memoryUsed = 900L; // Simulate 30% memory usage.
 
        // Step #1 - simulate 1 millisecond passing and collect metrics again:
        fakeClock.Advance(TimeSpan.FromMilliseconds(1));
        metricCollector.RecordObservableInstruments();
 
        Assert.Equal(10, metricCollector.LastMeasurement.Value); // Still consuming 10% as gauge wasn't updated.
 
        // Step #2 - simulate 1 millisecond passing and collect metrics again:
        fakeClock.Advance(TimeSpan.FromMilliseconds(1));
        metricCollector.RecordObservableInstruments();
 
        Assert.Equal(30, metricCollector.LastMeasurement.Value); // Consuming 30% of the memory afterwards
 
        memoryUsed = 3_100L; // Simulate more than 100% memory usage
 
        // Step #3 - simulate 1 millisecond passing and collect metrics again:
        fakeClock.Advance(options.MemoryConsumptionRefreshInterval);
        metricCollector.RecordObservableInstruments();
 
        // Memory usage should be the same as before, as we're not simulating any CPU usage:
        Assert.Equal(100, metricCollector.LastMeasurement.Value); // Consuming 100% of the memory
    }
 
    [ConditionalFact]
    public void Provider_Returns_MemoryConsumption()
    {
        // This is a synthetic test to have full test coverage:
        var usage = WindowsSnapshotProvider.GetMemoryUsageInBytes();
        Assert.InRange(usage, 0, long.MaxValue);
    }
 
    [ConditionalFact]
    public void Provider_Creates_Meter_With_Correct_Name()
    {
        using var meterFactory = new TestMeterFactory();
 
        _ = new WindowsSnapshotProvider(_fakeLogger, meterFactory, _options);
 
        var meter = meterFactory.Meters.Single();
        Assert.Equal(ResourceUtilizationInstruments.MeterName, meter.Name);
    }
}