diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/LinuxUtilizationParser.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/LinuxUtilizationParser.cs index 27f9398d506..69708073722 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/LinuxUtilizationParser.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/LinuxUtilizationParser.cs @@ -277,40 +277,78 @@ public ulong GetHostAvailableMemory() } /// - /// The file format is the following: - /// 0-18 - /// So, it is array indexed number of cpus. + /// Comma-separated list of integers, with dashes ("-") to represent ranges. For example "0-1,5", or "0", or "1,2,3". + /// Each value represents the zero-based index of a CPU. /// public float GetHostCpuCount() { _fileSystem.ReadFirstLine(_cpuSetCpus, _buffer); var stats = _buffer.WrittenSpan; - var start = stats.IndexOf("-", StringComparison.Ordinal); - - if (stats.IsEmpty || start == -1 || start == 0) + if (stats.IsEmpty) { - Throw.InvalidOperationException($"Could not parse '{_cpuSetCpus}'. Expected integer based range separated by dash (like 0-8) but got '{new string(stats)}'."); + ThrowException(stats); } - var first = stats.Slice(0, start); - var second = stats.Slice(start + 1, stats.Length - start - 1); - - _ = GetNextNumber(first, out var startCpu); - var next = GetNextNumber(second, out var endCpu); + var cpuCount = 0L; - if (startCpu == -1 || endCpu == -1 || endCpu < startCpu || next != -1) + // Iterate over groups (comma-separated) + while (true) { - Throw.InvalidOperationException($"Could not parse '{_cpuSetCpus}'. Expected integer based range separated by dash (like 0-8) but got '{new string(stats)}'."); + var groupIndex = stats.IndexOf(','); + + var group = groupIndex == -1 ? stats : stats.Slice(0, groupIndex); + + var rangeIndex = group.IndexOf('-'); + + if (rangeIndex == -1) + { + // Single number + _ = GetNextNumber(group, out var singleCpu); + + if (singleCpu == -1) + { + ThrowException(stats); + } + + cpuCount += 1; + } + else + { + // Range + var first = group.Slice(0, rangeIndex); + _ = GetNextNumber(first, out var startCpu); + + var second = group.Slice(rangeIndex + 1); + var next = GetNextNumber(second, out var endCpu); + + if (endCpu == -1 || startCpu == -1 || endCpu < startCpu || next != -1) + { + ThrowException(stats); + } + + cpuCount += endCpu - startCpu + 1; + } + + if (groupIndex == -1) + { + break; + } + + stats = stats.Slice(groupIndex + 1); } _buffer.Reset(); - return endCpu - startCpu + 1; + return cpuCount; + + static void ThrowException(ReadOnlySpan content) => + Throw.InvalidOperationException( + $"Could not parse '{_cpuSetCpus}'. Expected comma-separated list of integers, with dashes (\"-\") based ranges (\"0\", \"2-6,12\") but got '{new string(content)}'."); } /// - /// The input must contain only number. If there is something more than whitespace before the number, it will return failure. + /// The input must contain only number. If there is something more than whitespace before the number, it will return failure (-1). /// [SuppressMessage("Major Code Smell", "S109:Magic numbers should not be used", Justification = "We are adding another digit, so we need to multiply by ten.")] diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationParserTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationParserTests.cs index 959dcb99242..559ac792e08 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationParserTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationParserTests.cs @@ -177,12 +177,23 @@ public void When_Calling_GetHostAvailableMemory_Parser_Correctly_Transforms_Supp Assert.Equal(bytes, memory); } - [ConditionalFact] - public void When_No_Cgroup_Cpu_Limits_Are_Not_Set_We_Get_Available_Cpus_From_CpuSetCpus() + [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 When_No_Cgroup_Cpu_Limits_Are_Not_Set_We_Get_Available_Cpus_From_CpuSetCpus(string content, int result) { var f = new HardcodedValueFileSystem(new Dictionary { - { new FileInfo("/sys/fs/cgroup/cpuset/cpuset.cpus"), $"0-11" }, + { new FileInfo("/sys/fs/cgroup/cpuset/cpuset.cpus"), content }, { new FileInfo("/sys/fs/cgroup/cpu/cpu.cfs_quota_us"), "-1" }, { new FileInfo("/sys/fs/cgroup/cpu/cpu.cfs_period_us"), "-1" } }); @@ -190,7 +201,7 @@ public void When_No_Cgroup_Cpu_Limits_Are_Not_Set_We_Get_Available_Cpus_From_Cpu var p = new LinuxUtilizationParser(f, new FakeUserHz(100)); var cpus = p.GetCgroupLimitedCpus(); - Assert.Equal(12, cpus); + Assert.Equal(result, cpus); } [ConditionalTheory]