From dc1ba70804189b12ada17bd8b3ac56be93ebdf66 Mon Sep 17 00:00:00 2001 From: Weikai1997 <107915017+Weikai1997@users.noreply.github.com> Date: Wed, 4 Sep 2024 13:44:32 +0800 Subject: [PATCH] Make capping happen after scale of the CPU usage in ResourceUtilization (#5388) --- .../Calculator.cs | 3 +- .../ResourceUtilization.cs | 2 +- .../CalculatorTests.cs | 53 ++++++++++--------- 3 files changed, 30 insertions(+), 28 deletions(-) diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Calculator.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Calculator.cs index cd0e67158d3..e834c73fcb9 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Calculator.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Calculator.cs @@ -38,8 +38,7 @@ public static ResourceUtilization CalculateUtilization(in Snapshot first, in Sna long newUsageTicks = second.KernelTimeSinceStart.Ticks + second.UserTimeSinceStart.Ticks; long totalUsageTickDelta = newUsageTicks - oldUsageTicks; - var utilization = Math.Max(0.0, totalUsageTickDelta / totalSystemTicks * Hundred); - var cpuUtilization = Math.Min(Hundred, utilization); + double cpuUtilization = Math.Max(0.0, totalUsageTickDelta / totalSystemTicks * Hundred); return new ResourceUtilization(cpuUtilization, second.MemoryUsageInBytes, systemResources, second); } diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceUtilization.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceUtilization.cs index ff62aea2218..adaade51b3d 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceUtilization.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceUtilization.cs @@ -62,7 +62,7 @@ public ResourceUtilization(double cpuUsedPercentage, ulong memoryUsedInBytes, Sy guaranteedCpuUnits = 1; } - CpuUsedPercentage = Throw.IfLessThan(cpuUsedPercentage / guaranteedCpuUnits, 0.0); + CpuUsedPercentage = Math.Min(Hundred, Throw.IfLessThan(cpuUsedPercentage / guaranteedCpuUnits, 0.0)); MemoryUsedInBytes = Throw.IfLessThan(memoryUsedInBytes, 0); SystemResources = systemResources; MemoryUsedPercentage = Math.Min(Hundred, (double)MemoryUsedInBytes / systemResources.GuaranteedMemoryInBytes * Hundred); diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/CalculatorTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/CalculatorTests.cs index 6f105f1fb75..db4de43c503 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/CalculatorTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/CalculatorTests.cs @@ -28,12 +28,12 @@ public sealed class CalculatorTests [Fact] public void BasicCalculation() { - var secondSnapshotTimeSpan = _firstSnapshot.TotalTimeSinceStart.Add(TimeSpan.FromSeconds(5)); + TimeSpan secondSnapshotTimeSpan = _firstSnapshot.TotalTimeSinceStart.Add(TimeSpan.FromSeconds(5)); // Now, what's the total number of available ticks between the two samples (for a single core) - var totalAvailableTicks = secondSnapshotTimeSpan.Ticks - _firstSnapshot.TotalTimeSinceStart.Ticks; + long totalAvailableTicks = secondSnapshotTimeSpan.Ticks - _firstSnapshot.TotalTimeSinceStart.Ticks; - var second = new Snapshot( + Snapshot second = new( totalTimeSinceStart: secondSnapshotTimeSpan, // assign 25% to kernel time @@ -44,7 +44,7 @@ public void BasicCalculation() memoryUsageInBytes: 500); // Now, when we run the calculator, CPU should be at 50%. - var record = Calculator.CalculateUtilization(_firstSnapshot, second, _resources); + ResourceUtilization record = Calculator.CalculateUtilization(_firstSnapshot, second, _resources); Assert.Equal(50.0, record.CpuUsedPercentage); // Because we set it basically, memory should also clearly be at 50%. @@ -61,14 +61,14 @@ public void BasicCalculation() [Fact] public void BasicCalculation_WithHalfCpuUnits() { - var limitedResources = new SystemResources(0.5, 0.5, TotalMemoryInBytes, TotalMemoryInBytes); + SystemResources limitedResources = new(guaranteedCpuUnits: 0.5, maximumCpuUnits: 0.5, TotalMemoryInBytes, TotalMemoryInBytes); - var secondSnapshotTimeSpan = _firstSnapshot.TotalTimeSinceStart.Add(TimeSpan.FromSeconds(5)); + TimeSpan secondSnapshotTimeSpan = _firstSnapshot.TotalTimeSinceStart.Add(TimeSpan.FromSeconds(5)); // Now, what's the total number of available ticks between the two samples (for a single core) - var totalAvailableTicks = secondSnapshotTimeSpan.Ticks - _firstSnapshot.TotalTimeSinceStart.Ticks; + long totalAvailableTicks = secondSnapshotTimeSpan.Ticks - _firstSnapshot.TotalTimeSinceStart.Ticks; - var second = new Snapshot( + Snapshot second = new( totalTimeSinceStart: secondSnapshotTimeSpan, // assign 25% to kernel time @@ -80,7 +80,7 @@ public void BasicCalculation_WithHalfCpuUnits() // Using the limited resources, CPU time is now cut in half. So, when we run // the calculator, the CPU utilization should be at 100%. - var record = Calculator.CalculateUtilization(_firstSnapshot, second, limitedResources); + ResourceUtilization record = Calculator.CalculateUtilization(_firstSnapshot, second, limitedResources); Assert.Equal(100.0, record.CpuUsedPercentage); } @@ -91,7 +91,7 @@ public void BasicCalculation_WithHalfCpuUnits() public void Zeroes() { // No changes in the second snapshot - var secondSnapshot = new Snapshot( + Snapshot secondSnapshot = new( totalTimeSinceStart: _firstSnapshot.TotalTimeSinceStart.Add(TimeSpan.FromSeconds(5)), memoryUsageInBytes: 0, kernelTimeSinceStart: _firstSnapshot.KernelTimeSinceStart, @@ -100,7 +100,7 @@ public void Zeroes() // Now, let's set each of kernel and user time to no time elapsed. // Now, when we run the calculator, CPU should be at 0%. - var record = Calculator.CalculateUtilization(_firstSnapshot, secondSnapshot, _resources); + ResourceUtilization record = Calculator.CalculateUtilization(_firstSnapshot, secondSnapshot, _resources); Assert.Equal(0.0, record.CpuUsedPercentage); // Because we set it basically, memory should also clearly be at 0%. @@ -118,19 +118,19 @@ public void Zeroes() [Fact] public void TimeGoesBackwards() { - var firstSnapshot = new Snapshot( + Snapshot firstSnapshot = new( totalTimeSinceStart: TimeSpan.FromTicks(new FakeTimeProvider().GetUtcNow().Ticks), kernelTimeSinceStart: TimeSpan.FromTicks(1000), userTimeSinceStart: TimeSpan.FromTicks(1000), memoryUsageInBytes: 0); - var secondSnapshot = new Snapshot( + Snapshot secondSnapshot = new( totalTimeSinceStart: firstSnapshot.TotalTimeSinceStart.Add(TimeSpan.FromSeconds(5)), memoryUsageInBytes: 0, kernelTimeSinceStart: TimeSpan.FromTicks(firstSnapshot.KernelTimeSinceStart.Ticks - 1), userTimeSinceStart: TimeSpan.FromTicks(firstSnapshot.UserTimeSinceStart.Ticks - 1)); // Now, when we run the calculator, CPU should be at 0%. - var record = Calculator.CalculateUtilization(firstSnapshot, secondSnapshot, _resources); + ResourceUtilization record = Calculator.CalculateUtilization(firstSnapshot, secondSnapshot, _resources); Assert.Equal(0.0, record.CpuUsedPercentage); } @@ -141,19 +141,19 @@ public void TimeGoesBackwards() [Fact] public void FullyUtilized() { - var secondSnapshotTimeSpan = _firstSnapshot.TotalTimeSinceStart.Add(TimeSpan.FromSeconds(5)); + TimeSpan secondSnapshotTimeSpan = _firstSnapshot.TotalTimeSinceStart.Add(TimeSpan.FromSeconds(5)); // Now, what's the total number of available ticks between the two samples. - var totalAvailableTicks = secondSnapshotTimeSpan.Ticks - _firstSnapshot.TotalTimeSinceStart.Ticks; + long totalAvailableTicks = secondSnapshotTimeSpan.Ticks - _firstSnapshot.TotalTimeSinceStart.Ticks; - var secondSnapshot = new Snapshot( + Snapshot secondSnapshot = new( totalTimeSinceStart: secondSnapshotTimeSpan, kernelTimeSinceStart: TimeSpan.FromTicks(totalAvailableTicks / 2), userTimeSinceStart: TimeSpan.FromTicks(totalAvailableTicks / 2), memoryUsageInBytes: 1000); // Now, when we run the calculator, CPU should 100%. - var record = Calculator.CalculateUtilization(_firstSnapshot, secondSnapshot, _resources); + ResourceUtilization record = Calculator.CalculateUtilization(_firstSnapshot, secondSnapshot, _resources); Assert.Equal(100.0, record.CpuUsedPercentage); // Assert that memory is at 100%. @@ -166,15 +166,18 @@ public void FullyUtilized() /// Ensure that stats work appropriately if the resources are overutilized. /// /// The highest possible CPU and memory percentages should be 100%. - [Fact] - public void OverUtilized() + [Theory] + [InlineData(1, 100.0)] + [InlineData(2, 100.0)] + public void OverUtilized(double cpuUnits, double expectedCpuUsage) { - var secondSnapshotTimeSpan = _firstSnapshot.TotalTimeSinceStart.Add(TimeSpan.FromSeconds(5)); + SystemResources limitedResources = new(cpuUnits, cpuUnits, TotalMemoryInBytes, TotalMemoryInBytes); + TimeSpan secondSnapshotTimeSpan = _firstSnapshot.TotalTimeSinceStart.Add(TimeSpan.FromSeconds(5)); // Now, what's the total number of available ticks between the two samples. - var totalAvailableTicks = secondSnapshotTimeSpan.Ticks - _firstSnapshot.TotalTimeSinceStart.Ticks; + long totalAvailableTicks = secondSnapshotTimeSpan.Ticks - _firstSnapshot.TotalTimeSinceStart.Ticks; - var secondSnapshot = new Snapshot( + Snapshot secondSnapshot = new( totalTimeSinceStart: secondSnapshotTimeSpan, // Set each of kernel and uesr time to all the available ticks between @@ -184,8 +187,8 @@ public void OverUtilized() memoryUsageInBytes: 1500); // Now, when we run the calculator, CPU should be at 100%. - var record = Calculator.CalculateUtilization(_firstSnapshot, secondSnapshot, _resources); - Assert.Equal(100.0, record.CpuUsedPercentage); + ResourceUtilization record = Calculator.CalculateUtilization(_firstSnapshot, secondSnapshot, _resources); + Assert.Equal(expectedCpuUsage, record.CpuUsedPercentage); // Assert that memory is at 100% Assert.Equal(100.0, record.MemoryUsedPercentage);