File: Linux\LinuxNetworkMetricsTests.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.Collections.Generic;
using System.Diagnostics.Metrics;
using System.IO;
using System.Linq;
using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network;
using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Test.Helpers;
using Microsoft.Extensions.Time.Testing;
using Microsoft.Shared.Instruments;
using Microsoft.TestUtilities;
using Moq;
using Xunit;
 
namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Test;
 
[OSSkipCondition(OperatingSystems.Windows | OperatingSystems.MacOSX, SkipReason = "Linux specific tests")]
public class LinuxNetworkMetricsTests
{
    private readonly Mock<ITcpStateInfoProvider> _tcpStateInfoProvider = new();
    private readonly DateTimeOffset _startTime = DateTimeOffset.UtcNow;
    private FakeTimeProvider _timeProvider;
 
    public LinuxNetworkMetricsTests()
    {
        _timeProvider = new FakeTimeProvider(_startTime);
 
        _tcpStateInfoProvider.Setup(p => p.GetIpV4TcpStateInfo()).Returns(new TcpStateInfo());
        _tcpStateInfoProvider.Setup(p => p.GetIpV6TcpStateInfo()).Returns(new TcpStateInfo());
    }
 
    [Fact]
    public void CreatesMeter_WithCorrectName()
    {
        using var meterFactory = new TestMeterFactory();
        _ = new LinuxNetworkMetrics(
            meterFactory,
            _tcpStateInfoProvider.Object,
            _timeProvider);
 
        Meter meter = meterFactory.Meters.Single();
        Assert.Equal(ResourceUtilizationInstruments.MeterName, meter.Name);
    }
 
    [Fact]
    public void GetTcpStateInfoWithRetry_SuccessfulCall_ReturnsState()
    {
        var expectedV4 = new TcpStateInfo { ClosedCount = 42 };
        var expectedV6 = new TcpStateInfo { EstabCount = 24 };
        _tcpStateInfoProvider.Setup(p => p.GetIpV4TcpStateInfo()).Returns(expectedV4);
        _tcpStateInfoProvider.Setup(p => p.GetIpV6TcpStateInfo()).Returns(expectedV6);
 
        LinuxNetworkMetrics metrics = CreateMetrics();
        List<Measurement<long>> measurements = metrics.GetMeasurements().ToList();
 
        Assert.Contains(measurements, m => HasTagWithValue(m, "network.type", "ipv4", 42));
        Assert.Contains(measurements, m => HasTagWithValue(m, "system.network.state", "close", 42));
        Assert.Contains(measurements, m => HasTagWithValue(m, "network.type", "ipv6", 24));
        Assert.Contains(measurements, m => HasTagWithValue(m, "system.network.state", "established", 24));
    }
 
    [Theory]
    [InlineData(typeof(FileNotFoundException))]
    [InlineData(typeof(DirectoryNotFoundException))]
    [InlineData(typeof(UnauthorizedAccessException))]
    public void GetTcpStateInfoWithRetry_Failure_SetsUnavailableAndReturnsDefault(Type exceptionType)
    {
        _tcpStateInfoProvider.Setup(p => p.GetIpV4TcpStateInfo()).Throws((Exception)Activator.CreateInstance(exceptionType)!);
 
        LinuxNetworkMetrics metrics = CreateMetrics();
        List<Measurement<long>> measurements = metrics.GetMeasurements().ToList();
 
        Assert.All(measurements.Take(11), m => Assert.Equal(0, m.Value));
    }
 
    [Fact]
    public void GetTcpStateInfoWithRetry_DuringRetryInterval_ReturnsDefault()
    {
        _tcpStateInfoProvider.SetupSequence(p => p.GetIpV4TcpStateInfo())
            .Throws(new FileNotFoundException())
            .Returns(new TcpStateInfo { ClosedCount = 123 });
 
        LinuxNetworkMetrics metrics = CreateMetrics();
        List<Measurement<long>> first = metrics.GetMeasurements().ToList();
 
        _timeProvider.Advance(TimeSpan.FromMinutes(2));
        List<Measurement<long>> second = metrics.GetMeasurements().ToList();
 
        Assert.All(first.Take(11), m => Assert.Equal(0, m.Value));
        Assert.All(second.Take(11), m => Assert.Equal(0, m.Value));
        _tcpStateInfoProvider.Verify(p => p.GetIpV4TcpStateInfo(), Times.Once);
    }
 
    [Fact]
    public void GetTcpStateInfoWithRetry_AfterRetryInterval_ResetsUnavailableOnSuccess()
    {
        _tcpStateInfoProvider.SetupSequence(p => p.GetIpV4TcpStateInfo())
            .Throws(new FileNotFoundException())
            .Returns(new TcpStateInfo { ClosedCount = 99 });
 
        LinuxNetworkMetrics metrics = CreateMetrics();
        List<Measurement<long>> first = metrics.GetMeasurements().ToList();
 
        _timeProvider.Advance(TimeSpan.FromMinutes(6));
        List<Measurement<long>> second = metrics.GetMeasurements().ToList();
 
        Assert.All(first.Take(11), m => Assert.Equal(0, m.Value));
        Assert.Equal(99, second[0].Value);
        Assert.Contains(second, m => HasTagWithValue(m, "network.type", "ipv4", 99));
        Assert.Contains(second, m => HasTagWithValue(m, "system.network.state", "close", 99));
 
        _tcpStateInfoProvider.Verify(p => p.GetIpV4TcpStateInfo(), Times.Exactly(2));
    }
 
    private static bool HasTagWithValue(Measurement<long> measurement, string tagKey, string tagValue, long expectedValue)
    {
        foreach (KeyValuePair<string, object?> tag in measurement.Tags)
        {
            if (tag.Key == tagKey && string.Equals(tag.Value as string, tagValue, StringComparison.Ordinal))
            {
                return measurement.Value == expectedValue;
            }
        }
 
        return false;
    }
 
    private LinuxNetworkMetrics CreateMetrics()
    {
        using var meterFactory = new TestMeterFactory();
        return new LinuxNetworkMetrics(
            meterFactory,
            _tcpStateInfoProvider.Object,
            _timeProvider);
    }
}