File: Linux\LinuxUtilizationParserCgroupV2Tests.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.Globalization;
using System.IO;
using System.Threading.Tasks;
using Microsoft.Shared.Pools;
using Microsoft.TestUtilities;
using Moq;
using VerifyXunit;
using Xunit;
 
namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Test;
 
[OSSkipCondition(OperatingSystems.Windows | OperatingSystems.MacOSX, SkipReason = "Linux specific tests")]
[UsesVerify]
public sealed class LinuxUtilizationParserCgroupV2Tests
{
    private const string VerifiedDataDirectory = "Verified";
 
    [ConditionalTheory]
    [InlineData("DFIJEUWGHFWGBWEFWOMDOWKSLA")]
    [InlineData("")]
    [InlineData("________________________Asdasdasdas          dd")]
    [InlineData(" ")]
    [InlineData("!@#!$%!@")]
    public void Throws_When_Data_Is_Invalid(string line)
    {
        var parser = new LinuxUtilizationParserCgroupV2(new HardcodedValueFileSystem(line), new FakeUserHz(100));
 
        Assert.Throws<InvalidOperationException>(() => parser.GetHostAvailableMemory());
        Assert.Throws<InvalidOperationException>(() => parser.GetAvailableMemoryInBytes());
        Assert.Throws<InvalidOperationException>(() => parser.GetMemoryUsageInBytes());
        Assert.Throws<InvalidOperationException>(() => parser.GetCgroupLimitedCpus());
        Assert.Throws<InvalidOperationException>(() => parser.GetHostCpuUsageInNanoseconds());
        Assert.Throws<InvalidOperationException>(() => parser.GetHostCpuCount());
        Assert.Throws<InvalidOperationException>(() => parser.GetCgroupCpuUsageInNanoseconds());
        Assert.Throws<InvalidOperationException>(() => parser.GetCgroupRequestCpu());
    }
 
    [ConditionalFact]
    public void Can_Read_Host_And_Cgroup_Available_Cpu_Count()
    {
        var parser = new LinuxUtilizationParserCgroupV2(new FileNamesOnlyFileSystem(TestResources.TestFilesLocation), new FakeUserHz(100));
        var hostCpuCount = parser.GetHostCpuCount();
        var cgroupCpuCount = parser.GetCgroupLimitedCpus();
 
        Assert.Equal(2.0, hostCpuCount);
        Assert.Equal(2.0, cgroupCpuCount);
    }
 
    [ConditionalFact]
    public void Provides_Total_Available_Memory_In_Bytes()
    {
        var fs = new FileNamesOnlyFileSystem(TestResources.TestFilesLocation);
        var parser = new LinuxUtilizationParserCgroupV2(fs, new FakeUserHz(100));
 
        var totalMem = parser.GetHostAvailableMemory();
 
        Assert.Equal(16_233_760UL * 1024, totalMem);
    }
 
    [ConditionalTheory]
    [InlineData("----------------------")]
    [InlineData("@ @#dddada")]
    [InlineData("1231234124124")]
    [InlineData("1024 KB")]
    [InlineData("1024 KB  d \n\r 1024")]
    [InlineData("\n\r")]
    [InlineData("")]
    [InlineData("Suspicious")]
    [InlineData("string@")]
    [InlineData("string12312")]
    [InlineData("total-inactive-file")]
    [InlineData("total_inactive-file")]
    [InlineData("total_active_file")]
    [InlineData("Total_Inactive_File 2")]
    [InlineData("total_inactive_file:_ 21391")]
    [InlineData("string@ -1")]
    public Task Throws_When_TotalInactiveFile_Is_Invalid(string content)
    {
        var f = new HardcodedValueFileSystem(new Dictionary<FileInfo, string>
        {
            { new FileInfo("/sys/fs/cgroup/memory.stat"), content }
        });
 
        var p = new LinuxUtilizationParserCgroupV2(f, new FakeUserHz(100));
        var r = Record.Exception(() => p.GetMemoryUsageInBytes());
 
        return Verifier.Verify(r).UseParameters(content).UseDirectory(VerifiedDataDirectory);
    }
 
    [ConditionalTheory]
    [InlineData("----------------------")]
    [InlineData("@ @#dddada")]
    [InlineData("_1231234124124")]
    [InlineData("eee 1024 KB")]
    [InlineData("\n\r")]
    [InlineData("")]
    [InlineData("Suspicious")]
    [InlineData("Suspicious12312312")]
    [InlineData("string@")]
    [InlineData("string12312")]
    public Task Throws_When_UsageInBytes_Is_Invalid(string content)
    {
        var f = new HardcodedValueFileSystem(new Dictionary<FileInfo, string>
        {
            { new FileInfo("/sys/fs/cgroup/memory.stat"), "inactive_file 14340" },
            { new FileInfo("/sys/fs/cgroup/memory.current"), content }
        });
 
        var p = new LinuxUtilizationParserCgroupV2(f, new FakeUserHz(100));
        var r = Record.Exception(() => p.GetMemoryUsageInBytes());
 
        return Verifier.Verify(r).UseParameters(content).UseDirectory(VerifiedDataDirectory);
    }
 
    [ConditionalTheory]
    [InlineData("max\n", 134_796_910_592ul)]
    [InlineData("1000000\n", 1_000_000ul)]
    public void Returns_Available_Memory_When_AvailableMemoryInBytes_Is_Valid(string content, ulong expectedResult)
    {
        var f = new HardcodedValueFileSystem(new Dictionary<FileInfo, string>
        {
            { new FileInfo("/sys/fs/cgroup/memory.max"), content },
            { new FileInfo("/proc/meminfo"), "MemTotal:       131637608 kB" }
        });
 
        var p = new LinuxUtilizationParserCgroupV2(f, new FakeUserHz(100));
        var result = p.GetAvailableMemoryInBytes();
 
        Assert.Equal(expectedResult, result);
    }
 
    [ConditionalTheory]
    [InlineData("Suspicious12312312")]
    [InlineData("string@")]
    [InlineData("string12312")]
    public Task Throws_When_AvailableMemoryInBytes_Doesnt_Contain_Just_A_Number(string content)
    {
        var f = new HardcodedValueFileSystem(new Dictionary<FileInfo, string>
        {
            { new FileInfo("/sys/fs/cgroup/memory.max"), content },
        });
 
        var p = new LinuxUtilizationParserCgroupV2(f, new FakeUserHz(100));
        var r = Record.Exception(() => p.GetAvailableMemoryInBytes());
 
        return Verifier.Verify(r).UseParameters(content).UseDirectory(VerifiedDataDirectory);
    }
 
    [ConditionalFact]
    public Task Throws_When_UsageInBytes_Doesnt_Contain_A_Number()
    {
        var regexPatternforSlices = @"\w+.slice";
        var f = new HardcodedValueFileSystem(new Dictionary<FileInfo, string>
        {
            { new FileInfo("/sys/fs/cgroup/system.slice/memory.current"), "dasda"},
        });
 
        var p = new LinuxUtilizationParserCgroupV2(f, new FakeUserHz(100));
        var r = Record.Exception(() => p.GetMemoryUsageInBytesFromSlices(regexPatternforSlices));
 
        return Verifier.Verify(r).UseDirectory(VerifiedDataDirectory);
    }
 
    [ConditionalFact]
    public void Returns_Memory_Usage_When_Memory_Usage_Is_Valid()
    {
        var regexPatternforSlices = @"\w+.slice";
        var f = new HardcodedValueFileSystem(new Dictionary<FileInfo, string>
        {
            { new FileInfo("/sys/fs/cgroup/system.slice/memory.current"), "5342342"},
        });
 
        var p = new LinuxUtilizationParserCgroupV2(f, new FakeUserHz(100));
        var r = p.GetMemoryUsageInBytesFromSlices(regexPatternforSlices);
 
        Assert.Equal(5_342_342, r);
    }
 
    [ConditionalTheory]
    [InlineData(104343, 1)]
    [InlineData(23423, 22)]
    [InlineData(10000, 100)]
    public Task Throws_When_Inactive_Memory_Is_Bigger_Than_Total_Memory(int inactive, int total)
    {
        var f = new HardcodedValueFileSystem(new Dictionary<FileInfo, string>
        {
            { new FileInfo("/sys/fs/cgroup/memory.stat"), $"inactive_file {inactive}" },
            { new FileInfo("/sys/fs/cgroup/memory.current"), total.ToString(CultureInfo.CurrentCulture) }
        });
 
        var p = new LinuxUtilizationParserCgroupV2(f, new FakeUserHz(100));
        var r = Record.Exception(() => p.GetMemoryUsageInBytes());
 
        return Verifier.Verify(r).UseParameters(inactive, total).UseDirectory(VerifiedDataDirectory);
    }
 
    [ConditionalTheory]
    [InlineData("Mem")]
    [InlineData("MemTotal:")]
    [InlineData("MemTotal: 120")]
    [InlineData("MemTotal: kb")]
    [InlineData("MemTotal: MB")]
    [InlineData("MemTotal: PB")]
    [InlineData("MemTotal: 1024 PB")]
    [InlineData("MemTotal: 1024   ")]
    [InlineData("MemTotal: 1024 @@  ")]
    [InlineData("MemoryTotal: 1024 MB ")]
    [InlineData("MemoryTotal: 123123123123123123")]
    public Task Throws_When_MemInfo_Does_Not_Contain_TotalMemory(string totalMemory)
    {
        var f = new HardcodedValueFileSystem(new Dictionary<FileInfo, string>
        {
            { new FileInfo("/proc/meminfo"), totalMemory },
        });
 
        var p = new LinuxUtilizationParserCgroupV2(f, new FakeUserHz(100));
        var r = Record.Exception(() => p.GetHostAvailableMemory());
 
        return Verifier.Verify(r).UseParameters(totalMemory).UseDirectory(VerifiedDataDirectory);
    }
 
    [ConditionalTheory]
    [InlineData("kB", 231, 236_544)]
    [InlineData("MB", 287, 300_941_312)]
    [InlineData("GB", 372, 399_431_958_528)]
    [InlineData("TB", 2, 2_199_023_255_552)]
    public void Transforms_Supported_Units_To_Bytes(string unit, int value, ulong bytes)
    {
        var f = new HardcodedValueFileSystem(new Dictionary<FileInfo, string>
        {
            { new FileInfo("/proc/meminfo"), $"MemTotal: {value} {unit}" },
        });
 
        var p = new LinuxUtilizationParserCgroupV2(f, new FakeUserHz(100));
        var memory = p.GetHostAvailableMemory();
 
        Assert.Equal(bytes, memory);
    }
 
    [ConditionalTheory]
    [InlineData("0-11", 12)]
    [InlineData("0", 1)]
    [InlineData("1000", 1)]
    [InlineData("0,1", 2)]
    [InlineData("0,1,2", 3)]
    [InlineData("0,1,2,4", 4)]
    [InlineData("0,1-2,3", 4)]
    [InlineData("0,1,2-3,4", 5)]
    [InlineData("0-1,2-3", 4)]
    [InlineData("0-1,2-3,4-5", 6)]
    [InlineData("0-2,3-5,6-8", 9)]
    public void Gets_Available_Cpus_From_CpuSetCpus_When_Cpu_Limits_Not_Set(string content, int result)
    {
        var f = new HardcodedValueFileSystem(new Dictionary<FileInfo, string>
        {
            { new FileInfo("/sys/fs/cgroup/cpuset.cpus.effective"), content },
            { new FileInfo("/sys/fs/cgroup/cpu.max"), "-1" },
        });
 
        var p = new LinuxUtilizationParserCgroupV2(f, new FakeUserHz(100));
        var cpus = p.GetCgroupLimitedCpus();
 
        Assert.Equal(result, cpus);
    }
 
    [ConditionalFact]
    public void Gets_Available_Cpus_From_CpuSetCpus_When_Cpu_Max_Set_To_Max_()
    {
        var f = new HardcodedValueFileSystem(new Dictionary<FileInfo, string>
        {
            { new FileInfo("/sys/fs/cgroup/cpuset.cpus.effective"), "0,1,2" },
            { new FileInfo("/sys/fs/cgroup/cpu.max"), "max 100000" },
        });
 
        var p = new LinuxUtilizationParserCgroupV2(f, new FakeUserHz(100));
        var cpus = p.GetCgroupLimitedCpus();
 
        Assert.Equal(3, cpus);
    }
 
    [ConditionalTheory]
    [InlineData("-11")]
    [InlineData("0-")]
    [InlineData("d-22")]
    [InlineData("22-d")]
    [InlineData("22-18")]
    [InlineData("aaaa")]
    [InlineData("    d  182-1923")]
    [InlineData("")]
    [InlineData("1-18-22")]
    [InlineData("1-18                   \r\n")]
    [InlineData("\r\n")]
    public Task Throws_When_CpuSet_Has_Invalid_Content(string content)
    {
        var f = new HardcodedValueFileSystem(new Dictionary<FileInfo, string>
        {
            { new FileInfo("/sys/fs/cgroup/cpuset.cpus.effective"), content },
            { new FileInfo("/sys/fs/cgroup/cpu.max"), "-1" }
        });
 
        var p = new LinuxUtilizationParserCgroupV2(f, new FakeUserHz(100));
        var r = Record.Exception(() => p.GetHostCpuCount());
 
        return Verifier.Verify(r).UseParameters(content).UseDirectory(VerifiedDataDirectory);
    }
 
    [ConditionalFact]
    public Task Fallsback_To_Cpuset_When_Quota_And_Period_Are_Minus_One_()
    {
        var f = new HardcodedValueFileSystem(new Dictionary<FileInfo, string>
        {
            { new FileInfo("/sys/fs/cgroup/cpuset.cpus.effective"), "@" },
            { new FileInfo("/sys/fs/cgroup/cpu.max"), "-1" }
        });
 
        var p = new LinuxUtilizationParserCgroupV2(f, new FakeUserHz(100));
        var r = Record.Exception(() => p.GetCgroupLimitedCpus());
 
        return Verifier.Verify(r).UseDirectory(VerifiedDataDirectory);
    }
 
    [ConditionalTheory]
    [InlineData("dd1d", "18")]
    [InlineData("-18", "18")]
    [InlineData("\r\r\r\r\r", "18")]
    [InlineData("123", "\r\r\r\r\r")]
    [InlineData("-", "d'")]
    [InlineData("-", "d/:")]
    [InlineData("2", "d/:")]
    [InlineData("2d2d2d", "e3")]
    [InlineData("3d", "d3")]
    [InlineData("           12", "eeeee 12")]
    [InlineData("12       ", "")]
    public Task Throws_When_Cgroup_Cpu_Files_Contain_Invalid_Data(string quota, string period)
    {
        var f = new HardcodedValueFileSystem(new Dictionary<FileInfo, string>
        {
            { new FileInfo("/sys/fs/cgroup/cpu.max"), $"{quota} {period}"},
        });
 
        var p = new LinuxUtilizationParserCgroupV2(f, new FakeUserHz(100));
        var r = Record.Exception(() => p.GetCgroupLimitedCpus());
 
        return Verifier.Verify(r).UseParameters(quota, period).UseDirectory(VerifiedDataDirectory);
    }
 
    [ConditionalFact]
    public void Reads_CpuUsage_When_Valid_Input()
    {
        var f = new HardcodedValueFileSystem(new Dictionary<FileInfo, string>
        {
            { new FileInfo("/proc/stat"), "cpu  2569530 36700 245693 4860924 82283 0 4360 0 0 0" }
        });
 
        var p = new LinuxUtilizationParserCgroupV2(f, new FakeUserHz(100));
        var r = p.GetHostCpuUsageInNanoseconds();
 
        Assert.Equal(77_994_900_000_000, r);
    }
 
    [ConditionalFact]
    public void Reads_TotalMemory_When_Valid_Input()
    {
        var f = new HardcodedValueFileSystem(new Dictionary<FileInfo, string>
        {
            { new FileInfo("/sys/fs/cgroup/memory.current"), "32493514752" },
            { new FileInfo("/sys/fs/cgroup/memory.stat"), "inactive_file 100" }
        });
 
        var p = new LinuxUtilizationParserCgroupV2(f, new FakeUserHz(100));
        var r = Record.Exception(() => p.GetMemoryUsageInBytes());
 
        Assert.Null(r);
    }
 
    [ConditionalTheory]
    [InlineData("2569530367000")]
    [InlineData("  2569530 36700 245693 4860924 82283 0 4360 0dsa")]
    [InlineData("asdasd  2569530 36700 245693 4860924 82283 0 4360 0 0 0")]
    [InlineData("  2569530 36700 245693")]
    [InlineData("cpu  2569530 36700 245693")]
    [InlineData("  2")]
    public Task Throws_When_CpuUsage_Invalid(string content)
    {
        var f = new HardcodedValueFileSystem(new Dictionary<FileInfo, string>
        {
            { new FileInfo("/proc/stat"), content }
        });
 
        var p = new LinuxUtilizationParserCgroupV2(f, new FakeUserHz(100));
        var r = Record.Exception(() => p.GetHostCpuUsageInNanoseconds());
 
        return Verifier.Verify(r).UseParameters(content).UseDirectory(VerifiedDataDirectory);
    }
 
    [ConditionalTheory]
    [InlineData("usage_", 12222)]
    [InlineData("dasd", -1)]
    [InlineData("@#dddada", 342322)]
    public Task Throws_When_CpuAcctUsage_Has_Invalid_Content_Both_Parts(string content, int value)
    {
        var f = new HardcodedValueFileSystem(new Dictionary<FileInfo, string>
        {
            { new FileInfo("/sys/fs/cgroup/cpu.stat"), $"{content} {value}"},
        });
 
        var p = new LinuxUtilizationParserCgroupV2(f, new FakeUserHz(100));
        var r = Record.Exception(() => p.GetCgroupCpuUsageInNanoseconds());
 
        return Verifier.Verify(r).UseParameters(content, value).UseDirectory(VerifiedDataDirectory);
    }
 
    [ConditionalTheory]
    [InlineData(-32131)]
    [InlineData(-1)]
    [InlineData(-15.323)]
    public Task Throws_When_Usage_Usec_Has_Negative_Value(int value)
    {
        var f = new HardcodedValueFileSystem(new Dictionary<FileInfo, string>
        {
            { new FileInfo("/sys/fs/cgroup/cpu.stat"), $"usage_usec {value}"},
        });
 
        var p = new LinuxUtilizationParserCgroupV2(f, new FakeUserHz(100));
        var r = Record.Exception(() => p.GetCgroupCpuUsageInNanoseconds());
 
        return Verifier.Verify(r).UseParameters(value).UseDirectory(VerifiedDataDirectory);
    }
 
    [ConditionalTheory]
    [InlineData("-1")]
    [InlineData("dasrz3424")]
    [InlineData("0")]
    [InlineData("10001")]
    public Task Throws_When_Cgroup_Cpu_Weight_Files_Contain_Invalid_Data(string content)
    {
        var f = new HardcodedValueFileSystem(new Dictionary<FileInfo, string>
        {
            { new FileInfo("/sys/fs/cgroup/cpu.weight"), content },
        });
 
        var p = new LinuxUtilizationParserCgroupV2(f, new FakeUserHz(100));
        var r = Record.Exception(() => p.GetCgroupRequestCpu());
 
        return Verifier.Verify(r).UseParameters(content).UseDirectory(VerifiedDataDirectory);
    }
 
    [ConditionalTheory]
    [InlineData("2500", 64.0)]
    [InlineData("10000", 256.0)]
    public void Calculates_Cpu_Request_From_Cpu_Weight(string content, float result)
    {
        var f = new HardcodedValueFileSystem(new Dictionary<FileInfo, string>
        {
            { new FileInfo("/sys/fs/cgroup/cpu.weight"), content },
        });
 
        var p = new LinuxUtilizationParserCgroupV2(f, new FakeUserHz(100));
        var r = Math.Round(p.GetCgroupRequestCpu());
 
        Assert.Equal(result, r);
    }
 
    [ConditionalFact]
    public async Task Is_Thread_Safe_Async()
    {
        var f1 = new HardcodedValueFileSystem(new Dictionary<FileInfo, string>
        {
            { new FileInfo("/proc/stat"), "cpu  6163 0 3853 4222848 614 0 1155 0 0 0\r\ncpu0 240 0 279 210987 59 0 927 0 0 0" },
        });
        var f2 = new HardcodedValueFileSystem(new Dictionary<FileInfo, string>
        {
            { new FileInfo("/proc/stat"), "cpu  9137 0 9296 13972503 1148 0 2786 0 0 0\r\ncpu0 297 0 431 698663 59 0 2513 0 0 0" },
        });
 
        int callCount = 0;
        Mock<IFileSystem> fs = new();
        fs.Setup(x => x.ReadFirstLine(It.IsAny<FileInfo>(), It.IsAny<BufferWriter<char>>()))
             .Callback<FileInfo, BufferWriter<char>>((fileInfo, buffer) =>
             {
                 callCount++;
                 if (callCount % 2 == 0)
                 {
                     f1.ReadFirstLine(fileInfo, buffer);
                 }
                 else
                 {
                     f2.ReadFirstLine(fileInfo, buffer);
                 }
             })
             .Verifiable();
 
        var p = new LinuxUtilizationParserCgroupV2(fs.Object, new FakeUserHz(100));
 
        Task[] tasks = new Task[1_000];
        for (int i = 0; i < tasks.Length; i++)
        {
            tasks[i] = Task.Run(p.GetHostCpuUsageInNanoseconds);
        }
 
        await Task.WhenAll(tasks);
 
        Assert.True(true);
    }
}