diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes/KubernetesMetadata.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes/KubernetesMetadata.cs
index 80e37246e9f..be8034a4e47 100644
--- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes/KubernetesMetadata.cs
+++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes/KubernetesMetadata.cs
@@ -30,12 +30,27 @@ internal sealed class KubernetesMetadata
public static KubernetesMetadata FromEnvironmentVariables(string environmentVariablePrefix)
{
+ var limitsMemory = GetEnvironmentVariableAsUInt64($"{environmentVariablePrefix}LIMITS_MEMORY");
+ var limitsCpu = GetEnvironmentVariableAsUInt64($"{environmentVariablePrefix}LIMITS_CPU");
+ var requestsMemory = GetEnvironmentVariableAsUInt64($"{environmentVariablePrefix}REQUESTS_MEMORY");
+ var requestsCpu = GetEnvironmentVariableAsUInt64($"{environmentVariablePrefix}REQUESTS_CPU");
+
+ if (limitsMemory == 0)
+ {
+ throw new InvalidOperationException($"Environment variable '{environmentVariablePrefix}LIMITS_MEMORY' is required and cannot be zero or missing.");
+ }
+
+ if (limitsCpu == 0)
+ {
+ throw new InvalidOperationException($"Environment variable '{environmentVariablePrefix}LIMITS_CPU' is required and cannot be zero or missing.");
+ }
+
return new KubernetesMetadata
{
- LimitsMemory = GetEnvironmentVariableAsUInt64($"{environmentVariablePrefix}LIMITS_MEMORY"),
- LimitsCpu = GetEnvironmentVariableAsUInt64($"{environmentVariablePrefix}LIMITS_CPU"),
- RequestsMemory = GetEnvironmentVariableAsUInt64($"{environmentVariablePrefix}REQUESTS_MEMORY"),
- RequestsCpu = GetEnvironmentVariableAsUInt64($"{environmentVariablePrefix}REQUESTS_CPU"),
+ LimitsMemory = limitsMemory,
+ LimitsCpu = limitsCpu,
+ RequestsMemory = requestsMemory,
+ RequestsCpu = requestsCpu,
};
}
diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes.csproj b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes.csproj
index 3af3f4637d9..a60a0e4d505 100644
--- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes.csproj
+++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes.csproj
@@ -8,7 +8,6 @@
true
- true
diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/LinuxUtilizationProvider.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/LinuxUtilizationProvider.cs
index f757b09996e..85bdef8e7dd 100644
--- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/LinuxUtilizationProvider.cs
+++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/LinuxUtilizationProvider.cs
@@ -35,7 +35,7 @@ internal sealed class LinuxUtilizationProvider : ISnapshotProvider
private double _memoryLimit;
private double _cpuLimit;
#pragma warning disable S1450 // Private fields only used as local variables in methods should become local variables. This will be used once we bring relevant meters.
- private ulong _memoryRequest;
+ private double _memoryRequest;
#pragma warning restore S1450 // Private fields only used as local variables in methods should become local variables
private double _cpuRequest;
@@ -69,8 +69,8 @@ public LinuxUtilizationProvider(
_previousCgroupCpuTime = _parser.GetCgroupCpuUsageInNanoseconds();
var quota = resourceQuotaProvider.GetResourceQuota();
- _memoryLimit = quota.MaxMemoryInBytes;
_cpuLimit = quota.MaxCpuInCores;
+ _memoryLimit = quota.MaxMemoryInBytes;
_cpuRequest = quota.BaselineCpuInCores;
_memoryRequest = quota.BaselineMemoryInBytes;
@@ -127,7 +127,12 @@ public LinuxUtilizationProvider(
_ = meter.CreateObservableGauge(
name: ResourceUtilizationInstruments.ContainerMemoryLimitUtilization,
- observeValues: () => GetMeasurementWithRetry(MemoryPercentage),
+ observeValues: () => GetMeasurementWithRetry(() => MemoryPercentageLimit()),
+ unit: "1");
+
+ _ = meter.CreateObservableGauge(
+ name: ResourceUtilizationInstruments.ContainerMemoryRequestUtilization,
+ observeValues: () => GetMeasurementWithRetry(() => MemoryPercentageRequest()),
unit: "1");
_ = meter.CreateObservableUpDownCounter(
@@ -138,12 +143,11 @@ public LinuxUtilizationProvider(
_ = meter.CreateObservableGauge(
name: ResourceUtilizationInstruments.ProcessMemoryUtilization,
- observeValues: () => GetMeasurementWithRetry(MemoryPercentage),
+ observeValues: () => GetMeasurementWithRetry(() => MemoryPercentageLimit()),
unit: "1");
- ulong memoryLimitRounded = (ulong)Math.Round(_memoryLimit);
- Resources = new SystemResources(_cpuRequest, _cpuLimit, _memoryRequest, memoryLimitRounded);
- _logger.SystemResourcesInfo(_cpuLimit, _cpuRequest, memoryLimitRounded, _memoryRequest);
+ Resources = new SystemResources(_cpuRequest, _cpuLimit, quota.BaselineMemoryInBytes, quota.MaxMemoryInBytes);
+ _logger.SystemResourcesInfo(_cpuLimit, _cpuRequest, quota.MaxMemoryInBytes, quota.BaselineMemoryInBytes);
}
public double CpuUtilizationV2()
@@ -151,7 +155,7 @@ public double CpuUtilizationV2()
DateTimeOffset now = _timeProvider.GetUtcNow();
lock (_cpuLocker)
{
- if (now < _refreshAfterCpu)
+ if (now <= _refreshAfterCpu)
{
return _lastCpuCoresUsed;
}
@@ -160,7 +164,7 @@ public double CpuUtilizationV2()
(long cpuUsageTime, long cpuPeriodCounter) = _parser.GetCgroupCpuUsageInNanosecondsAndCpuPeriodsV2();
lock (_cpuLocker)
{
- if (now < _refreshAfterCpu)
+ if (now <= _refreshAfterCpu)
{
return _lastCpuCoresUsed;
}
@@ -193,7 +197,7 @@ public double CpuUtilization()
lock (_cpuLocker)
{
- if (now < _refreshAfterCpu)
+ if (now <= _refreshAfterCpu)
{
return _cpuPercentage;
}
@@ -204,7 +208,7 @@ public double CpuUtilization()
lock (_cpuLocker)
{
- if (now < _refreshAfterCpu)
+ if (now <= _refreshAfterCpu)
{
return _cpuPercentage;
}
@@ -276,12 +280,21 @@ public Snapshot GetSnapshot()
memoryUsageInBytes: memoryUsed);
}
- private double MemoryPercentage()
+ private double MemoryPercentageLimit()
{
ulong memoryUsage = MemoryUsage();
double memoryPercentage = Math.Min(One, memoryUsage / _memoryLimit);
- _logger.MemoryPercentageData(memoryUsage, _memoryLimit, memoryPercentage);
+ _logger.MemoryPercentageLimit(memoryUsage, _memoryLimit, memoryPercentage);
+ return memoryPercentage;
+ }
+
+ private double MemoryPercentageRequest()
+ {
+ ulong memoryUsage = MemoryUsage();
+ double memoryPercentage = Math.Min(One, memoryUsage / _memoryRequest);
+
+ _logger.MemoryPercentageRequest(memoryUsage, _memoryRequest, memoryPercentage);
return memoryPercentage;
}
diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Log.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Log.cs
index 0721a0ff998..010cfb41fc4 100644
--- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Log.cs
+++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Log.cs
@@ -21,7 +21,7 @@ public static partial void CpuUsageData(
[LoggerMessage(2, LogLevel.Debug,
"Computed memory usage with MemoryUsedInBytes = {memoryUsed}, MemoryLimit = {memoryLimit}, MemoryPercentage = {memoryPercentage}.")]
- public static partial void MemoryPercentageData(
+ public static partial void MemoryPercentageLimit(
this ILogger logger,
ulong memoryUsed,
double memoryLimit,
@@ -56,4 +56,12 @@ public static partial void HandleDiskStatsException(
public static partial void MemoryUsageData(
this ILogger logger,
ulong memoryUsed);
+
+ [LoggerMessage(7, LogLevel.Debug,
+ "Computed memory usage with MemoryUsedInBytes = {memoryUsed}, MemoryRequest = {memoryRequest}, MemoryPercentage = {memoryPercentage}.")]
+ public static partial void MemoryPercentageRequest(
+ this ILogger logger,
+ ulong memoryUsed,
+ double memoryRequest,
+ double memoryPercentage);
}
diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Microsoft.Extensions.Diagnostics.ResourceMonitoring.csproj b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Microsoft.Extensions.Diagnostics.ResourceMonitoring.csproj
index 7cdb4b7de49..173ae5f87eb 100644
--- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Microsoft.Extensions.Diagnostics.ResourceMonitoring.csproj
+++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Microsoft.Extensions.Diagnostics.ResourceMonitoring.csproj
@@ -50,6 +50,7 @@
+
diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceQuota.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceQuota.cs
index 2e09eb9fc86..d5131923637 100644
--- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceQuota.cs
+++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceQuota.cs
@@ -12,6 +12,9 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring;
/// Maximum values define the upper limits of resource usage, while baseline values specify
/// the minimum assured resource allocations, usually based on Kubernetes requests or Linux shares and weights distribution.
///
+///
+/// Max values will be emitted by limit metrics, and baseline values will be emmited by request metrics.
+///
[Experimental(diagnosticId: DiagnosticIds.Experiments.ResourceMonitoring, UrlFormat = DiagnosticIds.UrlFormat)]
public sealed class ResourceQuota
{
diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Log.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Log.cs
index c91cd989362..25940bb7d28 100644
--- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Log.cs
+++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Log.cs
@@ -24,11 +24,12 @@ public static partial void CpuUsageData(
double cpuPercentage);
[LoggerMessage(4, LogLevel.Debug,
- "Computed memory usage for container: CurrentMemoryUsage = {currentMemoryUsage}, TotalMemory = {totalMemory}")]
+ "Computed memory usage for container: CurrentMemoryUsage = {currentMemoryUsage}, LimitMemory = {limitMemory}, RequestMemory = {requestMemory}.")]
public static partial void ContainerMemoryUsageData(
this ILogger logger,
ulong currentMemoryUsage,
- double totalMemory);
+ double limitMemory,
+ double requestMemory);
[LoggerMessage(5, LogLevel.Debug, "Computed CPU usage with CpuUsageKernelTicks = {cpuUsageKernelTicks}, CpuUsageUserTicks = {cpuUsageUserTicks}, OldCpuUsageTicks = {oldCpuUsageTicks}, TimeTickDelta = {timeTickDelta}, CpuUnits = {cpuUnits}, CpuPercentage = {cpuPercentage}.")]
public static partial void CpuContainerUsageData(
diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/WindowsContainerSnapshotProvider.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/WindowsContainerSnapshotProvider.cs
index 0bf37035ed6..da80b428a30 100644
--- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/WindowsContainerSnapshotProvider.cs
+++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/WindowsContainerSnapshotProvider.cs
@@ -36,7 +36,7 @@ internal sealed class WindowsContainerSnapshotProvider : ISnapshotProvider
private double _memoryLimit;
private double _cpuLimit;
#pragma warning disable S1450 // Private fields only used as local variables in methods should become local variables. Those will be used once we bring relevant meters.
- private ulong _memoryRequest;
+ private double _memoryRequest;
private double _cpuRequest;
#pragma warning restore S1450 // Private fields only used as local variables in methods should become local variables
@@ -45,7 +45,8 @@ internal sealed class WindowsContainerSnapshotProvider : ISnapshotProvider
private DateTimeOffset _refreshAfterCpu;
private DateTimeOffset _refreshAfterMemory;
private DateTimeOffset _refreshAfterProcessMemory;
- private double _cpuPercentage = double.NaN;
+ private long _cachedUsageTickDelta;
+ private long _cachedTimeTickDelta;
private double _processMemoryPercentage;
private ulong _memoryUsage;
@@ -95,9 +96,8 @@ internal WindowsContainerSnapshotProvider(
_cpuRequest = quota.BaselineCpuInCores;
_memoryRequest = quota.BaselineMemoryInBytes;
- ulong memoryLimitRounded = (ulong)Math.Round(_memoryLimit);
- Resources = new SystemResources(_cpuRequest, _cpuLimit, _memoryRequest, memoryLimitRounded);
- _logger.SystemResourcesInfo(_cpuLimit, _cpuRequest, memoryLimitRounded, _memoryRequest);
+ Resources = new SystemResources(_cpuRequest, _cpuLimit, quota.BaselineMemoryInBytes, quota.MaxMemoryInBytes);
+ _logger.SystemResourcesInfo(_cpuLimit, _cpuRequest, quota.MaxMemoryInBytes, quota.BaselineMemoryInBytes);
var basicAccountingInfo = jobHandle.GetBasicAccountingInfo();
_oldCpuUsageTicks = basicAccountingInfo.TotalKernelTime + basicAccountingInfo.TotalUserTime;
@@ -123,12 +123,20 @@ internal WindowsContainerSnapshotProvider(
_ = meter.CreateObservableGauge(
name: ResourceUtilizationInstruments.ContainerCpuLimitUtilization,
- observeValue: CpuPercentage);
+ observeValue: () => CpuPercentage(_cpuLimit));
_ = meter.CreateObservableGauge(
name: ResourceUtilizationInstruments.ContainerMemoryLimitUtilization,
observeValue: () => Math.Min(_metricValueMultiplier, MemoryUsage() / _memoryLimit * _metricValueMultiplier));
+ _ = meter.CreateObservableGauge(
+ name: ResourceUtilizationInstruments.ContainerCpuRequestUtilization,
+ observeValue: () => CpuPercentage(_cpuRequest));
+
+ _ = meter.CreateObservableGauge(
+ name: ResourceUtilizationInstruments.ContainerMemoryRequestUtilization,
+ observeValue: () => Math.Min(_metricValueMultiplier, MemoryUsage() / _memoryRequest * _metricValueMultiplier));
+
_ = meter.CreateObservableUpDownCounter(
name: ResourceUtilizationInstruments.ContainerMemoryUsage,
observeValue: () => (long)MemoryUsage(),
@@ -138,7 +146,7 @@ internal WindowsContainerSnapshotProvider(
// Process based metrics:
_ = meter.CreateObservableGauge(
name: ResourceUtilizationInstruments.ProcessCpuUtilization,
- observeValue: CpuPercentage);
+ observeValue: () => CpuPercentage(_cpuLimit));
_ = meter.CreateObservableGauge(
name: ResourceUtilizationInstruments.ProcessMemoryUtilization,
@@ -207,7 +215,7 @@ private ulong MemoryUsage()
{
_memoryUsage = memoryUsage;
_refreshAfterMemory = now.Add(_memoryRefreshInterval);
- _logger.ContainerMemoryUsageData(_memoryUsage, _memoryLimit);
+ _logger.ContainerMemoryUsageData(_memoryUsage, _memoryLimit, _memoryRequest);
}
return _memoryUsage;
@@ -225,35 +233,27 @@ private IEnumerable> GetCpuTime()
[new KeyValuePair("cpu.mode", "system")]);
}
- private double CpuPercentage()
+ private double CpuPercentage(double denominator)
{
var now = _timeProvider.GetUtcNow();
-
- lock (_cpuLocker)
- {
- if (now < _refreshAfterCpu)
- {
- return _cpuPercentage;
- }
- }
-
- using var jobHandle = _createJobHandleObject();
- var basicAccountingInfo = jobHandle.GetBasicAccountingInfo();
- var currentCpuTicks = basicAccountingInfo.TotalKernelTime + basicAccountingInfo.TotalUserTime;
-
lock (_cpuLocker)
{
if (now >= _refreshAfterCpu)
{
+ using var jobHandle = _createJobHandleObject();
+ var basicAccountingInfo = jobHandle.GetBasicAccountingInfo();
+ var currentCpuTicks = basicAccountingInfo.TotalKernelTime + basicAccountingInfo.TotalUserTime;
+
var usageTickDelta = currentCpuTicks - _oldCpuUsageTicks;
- var timeTickDelta = (now.Ticks - _oldCpuTimeTicks) * _cpuLimit;
+ var timeTickDelta = now.Ticks - _oldCpuTimeTicks;
+
if (usageTickDelta > 0 && timeTickDelta > 0)
{
- // Don't change calculation order, otherwise precision is lost:
- _cpuPercentage = Math.Min(_metricValueMultiplier, usageTickDelta / timeTickDelta * _metricValueMultiplier);
+ _cachedUsageTickDelta = usageTickDelta;
+ _cachedTimeTickDelta = timeTickDelta;
_logger.CpuContainerUsageData(
- basicAccountingInfo.TotalKernelTime, basicAccountingInfo.TotalUserTime, _oldCpuUsageTicks, timeTickDelta, _cpuLimit, _cpuPercentage);
+ basicAccountingInfo.TotalKernelTime, basicAccountingInfo.TotalUserTime, _oldCpuUsageTicks, timeTickDelta, denominator, double.NaN);
_oldCpuUsageTicks = currentCpuTicks;
_oldCpuTimeTicks = now.Ticks;
@@ -261,7 +261,15 @@ private double CpuPercentage()
}
}
- return _cpuPercentage;
+ if (_cachedUsageTickDelta > 0 && _cachedTimeTickDelta > 0)
+ {
+ var timeTickDeltaWithDenominator = _cachedTimeTickDelta * denominator;
+
+ // Don't change calculation order, otherwise precision is lost:
+ return Math.Min(_metricValueMultiplier, _cachedUsageTickDelta / timeTickDeltaWithDenominator * _metricValueMultiplier);
+ }
+
+ return double.NaN;
}
}
}
diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/WindowsSnapshotProvider.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/WindowsSnapshotProvider.cs
index 82b94dad9e6..122f1813818 100644
--- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/WindowsSnapshotProvider.cs
+++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/WindowsSnapshotProvider.cs
@@ -45,6 +45,7 @@ public WindowsSnapshotProvider(ILogger? logger, IMeterF
{
}
+#pragma warning disable S107 // Methods should not have too many parameters
internal WindowsSnapshotProvider(
ILogger? logger,
IMeterFactory meterFactory,
@@ -54,6 +55,7 @@ internal WindowsSnapshotProvider(
Func getCpuTicksFunc,
Func getMemoryUsageFunc,
Func getTotalMemoryInBytesFunc)
+#pragma warning restore S107 // Methods should not have too many parameters
{
_logger = logger ?? NullLogger.Instance;
diff --git a/src/Shared/Instruments/ResourceUtilizationInstruments.cs b/src/Shared/Instruments/ResourceUtilizationInstruments.cs
index b1593edd396..f3eec4543d1 100644
--- a/src/Shared/Instruments/ResourceUtilizationInstruments.cs
+++ b/src/Shared/Instruments/ResourceUtilizationInstruments.cs
@@ -50,6 +50,14 @@ internal static class ResourceUtilizationInstruments
///
public const string ContainerMemoryLimitUtilization = "container.memory.limit.utilization";
+ ///
+ /// The name of an instrument to retrieve memory request consumption of all processes running inside a container or control group in range [0, 1].
+ ///
+ ///
+ /// The type of an instrument is .
+ ///
+ public const string ContainerMemoryRequestUtilization = "container.memory.request.utilization";
+
///
/// The name of an instrument to retrieve memory usage measured in bytes of all processes running inside a container or control group.
///
diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes.Tests/KubernetesMetadataTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes.Tests/KubernetesMetadataTests.cs
index 0d925daaf21..cb8619d7123 100644
--- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes.Tests/KubernetesMetadataTests.cs
+++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes.Tests/KubernetesMetadataTests.cs
@@ -72,7 +72,7 @@ public void Build_WithCustomPrefix_ReadsCorrectEnvironmentVariables()
}
[Fact]
- public void Build_WithMissingEnvironmentVariables_SetsZeroValues()
+ public void Build_WithMissingEnvironmentVariables_ThrowsInvalidOperationException()
{
// Arrange
var tempSetup = new TestKubernetesEnvironmentSetup();
@@ -80,14 +80,12 @@ public void Build_WithMissingEnvironmentVariables_SetsZeroValues()
try
{
- // Act
- var result = KubernetesMetadata.FromEnvironmentVariables("NONEXISTENT_PREFIX_");
+ // Act & Assert
+ var exception = Assert.Throws(() =>
+ KubernetesMetadata.FromEnvironmentVariables("NONEXISTENT_PREFIX_"));
- // Assert
- Assert.Equal(0UL, result.LimitsMemory);
- Assert.Equal(0UL, result.LimitsCpu);
- Assert.Equal(0UL, result.RequestsMemory);
- Assert.Equal(0UL, result.RequestsCpu);
+ Assert.Contains("LIMITS_MEMORY", exception.Message);
+ Assert.Contains("is required and cannot be zero or missing", exception.Message);
}
finally
{
@@ -100,7 +98,7 @@ public void Build_WithMissingEnvironmentVariables_SetsZeroValues()
[InlineData(" ")]
[InlineData("\t")]
[InlineData("\n")]
- public void Build_WithEmptyOrWhitespaceEnvironmentVariables_SetsZeroValues(string envValue)
+ public void Build_WithEmptyOrWhitespaceEnvironmentVariables_ThrowsInvalidOperationException(string envValue)
{
// Arrange
var tempSetup = new TestKubernetesEnvironmentSetup();
@@ -111,14 +109,12 @@ public void Build_WithEmptyOrWhitespaceEnvironmentVariables_SetsZeroValues(strin
try
{
- // Act
- var result = KubernetesMetadata.FromEnvironmentVariables(string.Empty);
+ // Act & Assert
+ var exception = Assert.Throws(() =>
+ KubernetesMetadata.FromEnvironmentVariables(string.Empty));
- // Assert
- Assert.Equal(0UL, result.LimitsMemory);
- Assert.Equal(0UL, result.LimitsCpu);
- Assert.Equal(0UL, result.RequestsMemory);
- Assert.Equal(0UL, result.RequestsCpu);
+ Assert.Contains("LIMITS_MEMORY", exception.Message);
+ Assert.Contains("is required and cannot be zero or missing", exception.Message);
}
finally
{
diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes.Tests/KubernetesResourceQuotasServiceCollectionExtensionsTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes.Tests/KubernetesResourceQuotasServiceCollectionExtensionsTests.cs
index f39f9fb5cf2..a7f70d1cbb5 100644
--- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes.Tests/KubernetesResourceQuotasServiceCollectionExtensionsTests.cs
+++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes.Tests/KubernetesResourceQuotasServiceCollectionExtensionsTests.cs
@@ -128,5 +128,19 @@ public void AddKubernetesResourceMonitoring_WithValidEnvironmentData_RegistersIS
Assert.Equal(limitsMemory, quota.MaxMemoryInBytes);
Assert.Equal(requestsMemory, quota.BaselineMemoryInBytes);
}
+
+ [Fact]
+ public void LinuxUtilizationProvider_ThrowsWhenLimitsNotFound()
+ {
+ using var environmentSetup = new TestKubernetesEnvironmentSetup();
+ environmentSetup.SetEnvironmentVariable("TEST_REQUESTS_MEMORY", "1000");
+ environmentSetup.SetEnvironmentVariable("TEST_REQUESTS_CPU", "1000");
+
+ var exception = Assert.Throws(() =>
+ KubernetesMetadata.FromEnvironmentVariables("TEST_"));
+
+ Assert.Contains("LIMITS_MEMORY", exception.Message);
+ Assert.Contains("is required and cannot be zero or missing", exception.Message);
+ }
#pragma warning restore CS0618
}
diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes.Tests/Linux/AcceptanceTest.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes.Tests/Linux/AcceptanceTest.cs
new file mode 100644
index 00000000000..84bab7c4e8b
--- /dev/null
+++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes.Tests/Linux/AcceptanceTest.cs
@@ -0,0 +1,151 @@
+// 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.Threading.Tasks;
+using Microsoft.Extensions.Diagnostics.Metrics.Testing;
+using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes;
+using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux;
+using Microsoft.Extensions.Logging.Testing;
+using Microsoft.Extensions.Time.Testing;
+using Microsoft.Shared.Instruments;
+using Microsoft.TestUtilities;
+using Moq;
+using Xunit;
+
+namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Test.Linux;
+
+[OSSkipCondition(OperatingSystems.Windows | OperatingSystems.MacOSX, SkipReason = "Linux specific tests")]
+public class AcceptanceTest
+{
+ [Fact]
+ public async Task LinuxUtilizationProvider_MeasuredWithKubernetesMetadata()
+ {
+ const ulong LimitsMemory = 2_000;
+ const ulong LimitsCpu = 2_000; // 2 cores in millicores
+ const ulong RequestsMemory = 1_000;
+ const ulong RequestsCpu = 1_000; // 1 core in millicores
+
+ using var environmentSetup = new TestKubernetesEnvironmentSetup();
+
+ environmentSetup.SetupKubernetesEnvironment(
+ "ACCEPTANCE_TEST_",
+ LimitsMemory,
+ LimitsCpu,
+ RequestsMemory,
+ RequestsCpu);
+
+ var logger = new FakeLogger();
+ var parserMock = new Mock();
+
+ ulong initialMemoryUsage = 400UL;
+ ulong updatedMemoryUsage = 1200UL;
+
+ var memoryCallCount = 0;
+ parserMock.Setup(p => p.GetMemoryUsageInBytes())
+ .Returns(() =>
+ {
+ memoryCallCount++;
+ return memoryCallCount <= 1 ? initialMemoryUsage : updatedMemoryUsage;
+ });
+
+ long initialHostCpuTime = 1000L;
+ long initialCgroupCpuTime = 100L;
+ long updatedHostCpuTime = 2000L;
+ long updatedCgroupCpuTime = 300L;
+
+ var hostCpuCallCount = 0;
+ parserMock.Setup(p => p.GetHostCpuUsageInNanoseconds())
+ .Returns(() =>
+ {
+ hostCpuCallCount++;
+ return hostCpuCallCount <= 1 ? initialHostCpuTime : updatedHostCpuTime;
+ });
+
+ var cgroupCpuCallCount = 0;
+ parserMock.Setup(p => p.GetCgroupCpuUsageInNanoseconds())
+ .Returns(() =>
+ {
+ cgroupCpuCallCount++;
+ return cgroupCpuCallCount <= 1 ? initialCgroupCpuTime : updatedCgroupCpuTime;
+ });
+
+ parserMock.Setup(p => p.GetHostCpuCount()).Returns(2.0f);
+ parserMock.Setup(p => p.GetAvailableMemoryInBytes()).Returns(LimitsMemory);
+
+ var fakeClock = new FakeTimeProvider();
+ using var meter = new Meter(nameof(LinuxUtilizationProvider_MeasuredWithKubernetesMetadata));
+ var meterFactoryMock = new Mock();
+ meterFactoryMock.Setup(x => x.Create(It.IsAny())).Returns(meter);
+
+ using var containerCpuLimitMetricCollector = new MetricCollector(meter, ResourceUtilizationInstruments.ContainerCpuLimitUtilization, fakeClock);
+ using var containerMemoryLimitMetricCollector = new MetricCollector(meter, ResourceUtilizationInstruments.ContainerMemoryLimitUtilization, fakeClock);
+ using var containerCpuRequestMetricCollector = new MetricCollector(meter, ResourceUtilizationInstruments.ContainerCpuRequestUtilization, fakeClock);
+ using var containerMemoryRequestMetricCollector = new MetricCollector(meter, ResourceUtilizationInstruments.ContainerMemoryRequestUtilization, fakeClock);
+
+ var options = Microsoft.Extensions.Options.Options.Create(new ResourceMonitoringOptions
+ {
+ MemoryConsumptionRefreshInterval = TimeSpan.FromMilliseconds(2),
+ CpuConsumptionRefreshInterval = TimeSpan.FromMilliseconds(2),
+ UseZeroToOneRangeForMetrics = false
+ });
+
+ var kubernetesMetadata = new KubernetesMetadata
+ {
+ LimitsMemory = LimitsMemory,
+ LimitsCpu = LimitsCpu,
+ RequestsMemory = RequestsMemory,
+ RequestsCpu = RequestsCpu
+ };
+ var kubernetesResourceQuotaProvider = new KubernetesResourceQuotaProvider(kubernetesMetadata);
+
+ var utilizationProvider = new LinuxUtilizationProvider(
+ options,
+ parserMock.Object,
+ meterFactoryMock.Object,
+ kubernetesResourceQuotaProvider,
+ logger,
+ fakeClock);
+
+ containerCpuLimitMetricCollector.RecordObservableInstruments();
+ containerMemoryLimitMetricCollector.RecordObservableInstruments();
+ containerCpuRequestMetricCollector.RecordObservableInstruments();
+ containerMemoryRequestMetricCollector.RecordObservableInstruments();
+
+ Assert.NotNull(containerCpuLimitMetricCollector.LastMeasurement?.Value);
+ Assert.NotNull(containerMemoryLimitMetricCollector.LastMeasurement?.Value);
+ Assert.NotNull(containerCpuRequestMetricCollector.LastMeasurement?.Value);
+ Assert.NotNull(containerMemoryRequestMetricCollector.LastMeasurement?.Value);
+
+ Assert.True(double.IsNaN(containerCpuLimitMetricCollector.LastMeasurement.Value));
+ Assert.True(double.IsNaN(containerCpuRequestMetricCollector.LastMeasurement.Value));
+
+ // Container memory: 400 bytes / 2000 bytes limit = 20%
+ Assert.Equal(0.2, containerMemoryLimitMetricCollector.LastMeasurement.Value);
+
+ // Container memory: 400 bytes / 1000 bytes request = 40%
+ Assert.Equal(0.4, containerMemoryRequestMetricCollector.LastMeasurement.Value);
+
+ fakeClock.Advance(options.Value.MemoryConsumptionRefreshInterval);
+ containerCpuLimitMetricCollector.RecordObservableInstruments();
+ containerMemoryLimitMetricCollector.RecordObservableInstruments();
+ containerCpuRequestMetricCollector.RecordObservableInstruments();
+ containerMemoryRequestMetricCollector.RecordObservableInstruments();
+
+ // CPU calculation: (300-100)/(2000-1000) = 200/1000 = 0.2 = 20%
+ // With 2 host CPUs and 2 core limit: 20% * 2/2 = 20%
+ Assert.Equal(0.2, containerCpuLimitMetricCollector.LastMeasurement.Value);
+
+ // CPU against 1 core request: 20% * 2/1 = 40%
+ Assert.Equal(0.4, containerCpuRequestMetricCollector.LastMeasurement.Value);
+
+ // Container memory: 1200 bytes / 2000 bytes limit = 60%
+ Assert.Equal(0.6, containerMemoryLimitMetricCollector.LastMeasurement.Value);
+
+ // Container memory: 1200 bytes / 1000 bytes request = 120%
+ Assert.Equal(1, containerMemoryRequestMetricCollector.LastMeasurement.Value);
+
+ await Task.CompletedTask;
+ }
+}
diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes.Tests/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes.Tests.csproj b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes.Tests/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes.Tests.csproj
index b931a014264..e964e6aa3e0 100644
--- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes.Tests/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes.Tests.csproj
+++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes.Tests/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes.Tests.csproj
@@ -8,8 +8,16 @@
+
+
+
+
+
+
+
+
diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes.Tests/Windows/AcceptanceTest.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes.Tests/Windows/AcceptanceTest.cs
new file mode 100644
index 00000000000..8d737a77388
--- /dev/null
+++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes.Tests/Windows/AcceptanceTest.cs
@@ -0,0 +1,180 @@
+// 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.Threading.Tasks;
+using Microsoft.Extensions.Diagnostics.Metrics.Testing;
+using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes;
+using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows;
+using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Interop;
+using Microsoft.Extensions.Logging.Testing;
+using Microsoft.Extensions.Time.Testing;
+using Microsoft.Shared.Instruments;
+using Moq;
+using Xunit;
+using static Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Interop.JobObjectInfo;
+
+namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Test.Windows;
+
+public class AcceptanceTest
+{
+ [Fact]
+ public async Task WindowsContainerSnapshotProvider_MeasuredWithKubernetesMetadata()
+ {
+ const ulong LimitsMemory = 2_000;
+ const ulong LimitsCpu = 2_000; // 2 cores in millicores
+ const ulong RequestsMemory = 1_000;
+ const ulong RequestsCpu = 1_000; // 1 core in millicores
+
+ using var environmentSetup = new TestKubernetesEnvironmentSetup();
+
+ environmentSetup.SetupKubernetesEnvironment(
+ "ACCEPTANCE_TEST_",
+ LimitsMemory,
+ LimitsCpu,
+ RequestsMemory,
+ RequestsCpu);
+
+ var logger = new FakeLogger();
+ var memoryInfoMock = new Mock();
+ var systemInfoMock = new Mock();
+ var jobHandleMock = new Mock();
+ var processInfoMock = new Mock();
+
+ var memStatus = new MEMORYSTATUSEX { TotalPhys = 3000UL };
+ memoryInfoMock.Setup(m => m.GetMemoryStatus()).Returns(() => memStatus);
+
+ var sysInfo = new SYSTEM_INFO { NumberOfProcessors = 2 };
+ systemInfoMock.Setup(s => s.GetSystemInfo()).Returns(() => sysInfo);
+
+ // Setup CPU accounting info with initial and updated values
+ JOBOBJECT_BASIC_ACCOUNTING_INFORMATION initialAccountingInfo = new()
+ {
+ TotalKernelTime = 1000,
+ TotalUserTime = 1000
+ };
+
+ JOBOBJECT_BASIC_ACCOUNTING_INFORMATION updatedAccountingInfo = new()
+ {
+ TotalKernelTime = 1500, // 500 ticks increase
+ TotalUserTime = 1500 // 500 ticks increase
+ };
+
+ var accountingCallCount = 0;
+ jobHandleMock.Setup(j => j.GetBasicAccountingInfo())
+ .Returns(() =>
+ {
+ accountingCallCount++;
+ return accountingCallCount <= 1 ? initialAccountingInfo : updatedAccountingInfo;
+ });
+
+ var cpuLimit = new JOBOBJECT_CPU_RATE_CONTROL_INFORMATION { CpuRate = 10_000 };
+ jobHandleMock.Setup(j => j.GetJobCpuLimitInfo()).Returns(() => cpuLimit);
+
+ var limitInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION
+ {
+ JobMemoryLimit = new UIntPtr(LimitsMemory)
+ };
+ jobHandleMock.Setup(j => j.GetExtendedLimitInfo()).Returns(() => limitInfo);
+
+ ulong initialAppMemoryUsage = 200UL;
+ ulong initialContainerMemoryUsage = 400UL;
+ ulong updatedAppMemoryUsage = 600UL;
+ ulong updatedContainerMemoryUsage = 1200UL;
+
+ var processMemoryCallCount = 0;
+ processInfoMock.Setup(p => p.GetCurrentProcessMemoryUsage())
+ .Returns(() =>
+ {
+ processMemoryCallCount++;
+ return processMemoryCallCount <= 1 ? initialAppMemoryUsage : updatedAppMemoryUsage;
+ });
+
+ var containerMemoryCallCount = 0;
+ processInfoMock.Setup(p => p.GetMemoryUsage())
+ .Returns(() =>
+ {
+ containerMemoryCallCount++;
+ return containerMemoryCallCount <= 1 ? initialContainerMemoryUsage : updatedContainerMemoryUsage;
+ });
+
+ var fakeClock = new FakeTimeProvider();
+ using var meter = new Meter(nameof(WindowsContainerSnapshotProvider_MeasuredWithKubernetesMetadata));
+ var meterFactoryMock = new Mock();
+ meterFactoryMock.Setup(x => x.Create(It.IsAny())).Returns(meter);
+
+ using var containerCpuLimitMetricCollector = new MetricCollector(meter, ResourceUtilizationInstruments.ContainerCpuLimitUtilization, fakeClock);
+ using var containerMemoryLimitMetricCollector = new MetricCollector(meter, ResourceUtilizationInstruments.ContainerMemoryLimitUtilization, fakeClock);
+ using var containerCpuRequestMetricCollector = new MetricCollector(meter, ResourceUtilizationInstruments.ContainerCpuRequestUtilization, fakeClock);
+ using var containerMemoryRequestMetricCollector = new MetricCollector(meter, ResourceUtilizationInstruments.ContainerMemoryRequestUtilization, fakeClock);
+
+ var options = new ResourceMonitoringOptions
+ {
+ MemoryConsumptionRefreshInterval = TimeSpan.FromMilliseconds(2),
+ CpuConsumptionRefreshInterval = TimeSpan.FromMilliseconds(2),
+ UseZeroToOneRangeForMetrics = false
+ };
+
+ var kubernetesMetadata = new KubernetesMetadata
+ {
+ LimitsMemory = LimitsMemory,
+ LimitsCpu = LimitsCpu,
+ RequestsMemory = RequestsMemory,
+ RequestsCpu = RequestsCpu
+ };
+ var kubernetesResourceQuotaProvider = new KubernetesResourceQuotaProvider(kubernetesMetadata);
+
+ var snapshotProvider = new WindowsContainerSnapshotProvider(
+ processInfoMock.Object,
+ logger,
+ meterFactoryMock.Object,
+ () => jobHandleMock.Object,
+ fakeClock,
+ options,
+ kubernetesResourceQuotaProvider);
+
+ // Initial state
+ containerCpuLimitMetricCollector.RecordObservableInstruments();
+ containerMemoryLimitMetricCollector.RecordObservableInstruments();
+ containerCpuRequestMetricCollector.RecordObservableInstruments();
+ containerMemoryRequestMetricCollector.RecordObservableInstruments();
+
+ Assert.NotNull(containerCpuLimitMetricCollector.LastMeasurement?.Value);
+ Assert.NotNull(containerMemoryLimitMetricCollector.LastMeasurement?.Value);
+ Assert.NotNull(containerCpuRequestMetricCollector.LastMeasurement?.Value);
+ Assert.NotNull(containerMemoryRequestMetricCollector.LastMeasurement?.Value);
+
+ // Initial CPU metrics should be NaN as no time has passed for calculation
+ Assert.True(double.IsNaN(containerCpuLimitMetricCollector.LastMeasurement.Value));
+ Assert.True(double.IsNaN(containerCpuRequestMetricCollector.LastMeasurement.Value));
+
+ // Container memory: 400 bytes / 2000 bytes limit = 20%
+ Assert.Equal(20, containerMemoryLimitMetricCollector.LastMeasurement.Value);
+
+ // Container memory: 400 bytes / 1000 bytes request = 40%
+ Assert.Equal(40, containerMemoryRequestMetricCollector.LastMeasurement.Value);
+
+ // Advance time to trigger refresh
+ fakeClock.Advance(options.MemoryConsumptionRefreshInterval);
+
+ containerCpuLimitMetricCollector.RecordObservableInstruments();
+ containerMemoryLimitMetricCollector.RecordObservableInstruments();
+ containerCpuRequestMetricCollector.RecordObservableInstruments();
+ containerMemoryRequestMetricCollector.RecordObservableInstruments();
+
+ // CPU: 1000 ticks increase over 2ms = 2.5% utilization against 2 core limit
+ Assert.Equal(2.5, containerCpuLimitMetricCollector.LastMeasurement.Value);
+
+ // CPU: 2.5% of 2 cores against 1 core request = 5%
+ Assert.Equal(5, containerCpuRequestMetricCollector.LastMeasurement.Value);
+
+ // Container memory: 1200 bytes / 2000 bytes limit = 60%
+ Assert.Equal(60, containerMemoryLimitMetricCollector.LastMeasurement.Value);
+
+ // Container memory: 1200 bytes / 1000 bytes request = 120%, but Min() to 100%
+ Assert.Equal(100, containerMemoryRequestMetricCollector.LastMeasurement.Value);
+
+ await Task.CompletedTask;
+ }
+}
diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationProviderTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationProviderTests.cs
index d5d6ea3a8c4..7aac3b83a43 100644
--- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationProviderTests.cs
+++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationProviderTests.cs
@@ -84,7 +84,7 @@ public void Provider_Registers_Instruments()
listener.Start();
listener.RecordObservableInstruments();
- Assert.Equal(6, samples.Count);
+ Assert.Equal(7, samples.Count);
Assert.Contains(samples, x => x.instrument.Name == ResourceUtilizationInstruments.ContainerCpuLimitUtilization);
Assert.True(double.IsNaN(samples.Single(i => i.instrument.Name == ResourceUtilizationInstruments.ContainerCpuLimitUtilization).value));
@@ -92,6 +92,9 @@ public void Provider_Registers_Instruments()
Assert.Contains(samples, x => x.instrument.Name == ResourceUtilizationInstruments.ContainerCpuRequestUtilization);
Assert.True(double.IsNaN(samples.Single(i => i.instrument.Name == ResourceUtilizationInstruments.ContainerCpuRequestUtilization).value));
+ Assert.Contains(samples, x => x.instrument.Name == ResourceUtilizationInstruments.ContainerMemoryRequestUtilization);
+ Assert.Equal(0.5, samples.Single(i => i.instrument.Name == ResourceUtilizationInstruments.ContainerMemoryRequestUtilization).value);
+
Assert.Contains(samples, x => x.instrument.Name == ResourceUtilizationInstruments.ContainerMemoryLimitUtilization);
Assert.Equal(0.5, samples.Single(i => i.instrument.Name == ResourceUtilizationInstruments.ContainerMemoryLimitUtilization).value);
@@ -158,7 +161,7 @@ public void Provider_Registers_Instruments_CgroupV2()
listener.Start();
listener.RecordObservableInstruments();
- Assert.Equal(5, samples.Count);
+ Assert.Equal(6, samples.Count);
Assert.Contains(samples, x => x.instrument.Name == ResourceUtilizationInstruments.ContainerCpuLimitUtilization);
Assert.True(double.IsNaN(samples.Single(i => i.instrument.Name == ResourceUtilizationInstruments.ContainerCpuLimitUtilization).value));
@@ -169,6 +172,9 @@ public void Provider_Registers_Instruments_CgroupV2()
Assert.Contains(samples, x => x.instrument.Name == ResourceUtilizationInstruments.ContainerMemoryLimitUtilization);
Assert.Equal(1, samples.Single(i => i.instrument.Name == ResourceUtilizationInstruments.ContainerMemoryLimitUtilization).value);
+ Assert.Contains(samples, x => x.instrument.Name == ResourceUtilizationInstruments.ContainerMemoryRequestUtilization);
+ Assert.Equal(1, samples.Single(i => i.instrument.Name == ResourceUtilizationInstruments.ContainerMemoryRequestUtilization).value);
+
Assert.Contains(samples, x => x.instrument.Name == ResourceUtilizationInstruments.ProcessCpuUtilization);
Assert.True(double.IsNaN(samples.Single(i => i.instrument.Name == ResourceUtilizationInstruments.ProcessCpuUtilization).value));
@@ -276,7 +282,7 @@ public void Provider_Registers_Instruments_CgroupV2_WithoutHostCpu()
listener.Start();
listener.RecordObservableInstruments();
- Assert.Equal(6, samples.Count);
+ Assert.Equal(7, samples.Count);
Assert.Contains(samples, x => x.instrument.Name == ResourceUtilizationInstruments.ContainerCpuLimitUtilization);
Assert.True(double.IsNaN(samples.Single(i => i.instrument.Name == ResourceUtilizationInstruments.ContainerCpuLimitUtilization).value));
@@ -290,6 +296,9 @@ public void Provider_Registers_Instruments_CgroupV2_WithoutHostCpu()
Assert.Contains(samples, x => x.instrument.Name == ResourceUtilizationInstruments.ContainerMemoryLimitUtilization);
Assert.Equal(1, samples.Single(i => i.instrument.Name == ResourceUtilizationInstruments.ContainerMemoryLimitUtilization).value);
+ Assert.Contains(samples, x => x.instrument.Name == ResourceUtilizationInstruments.ContainerMemoryRequestUtilization);
+ Assert.Equal(1, samples.Single(i => i.instrument.Name == ResourceUtilizationInstruments.ContainerMemoryRequestUtilization).value);
+
Assert.Contains(samples, x => x.instrument.Name == ResourceUtilizationInstruments.ProcessMemoryUtilization);
Assert.Equal(1, samples.Single(i => i.instrument.Name == ResourceUtilizationInstruments.ProcessMemoryUtilization).value);
}
@@ -376,8 +385,9 @@ public void Provider_GetMeasurementWithRetry_UnhandledException_DoesNotBlockFutu
var parserMock = new Mock();
parserMock.Setup(p => p.GetMemoryUsageInBytes()).Returns(() =>
{
+ // 4 represents number of calls of GetMemoryUsageInBytes() in LinuxUtilizationProvider in one interval
callCount++;
- if (callCount <= 3)
+ if (callCount <= 4)
{
throw new InvalidOperationException("Simulated unhandled exception");
}
@@ -416,12 +426,13 @@ public void Provider_GetMeasurementWithRetry_UnhandledException_DoesNotBlockFutu
Assert.Throws(() => listener.RecordObservableInstruments());
Assert.DoesNotContain(samples, x => x.instrument.Name == ResourceUtilizationInstruments.ProcessMemoryUtilization);
+ parserMock.Verify(p => p.GetMemoryUsageInBytes(), Times.Exactly(4));
fakeTime.Advance(TimeSpan.FromMinutes(1));
listener.RecordObservableInstruments();
var metric = samples.SingleOrDefault(x => x.instrument.Name == ResourceUtilizationInstruments.ProcessMemoryUtilization);
Assert.Equal(1234f / 2000f, metric.value, 0.01f);
- parserMock.Verify(p => p.GetMemoryUsageInBytes(), Times.Exactly(4));
+ parserMock.Verify(p => p.GetMemoryUsageInBytes(), Times.Exactly(5));
}
}
diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsContainerSnapshotProviderTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsContainerSnapshotProviderTests.cs
index c27fb3a7712..9abb88dc185 100644
--- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsContainerSnapshotProviderTests.cs
+++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsContainerSnapshotProviderTests.cs
@@ -256,6 +256,8 @@ public void SnapshotProvider_EmitsCpuTimeMetric()
[InlineData(ResourceUtilizationInstruments.ProcessCpuUtilization, false)]
[InlineData(ResourceUtilizationInstruments.ContainerCpuLimitUtilization, true)]
[InlineData(ResourceUtilizationInstruments.ContainerCpuLimitUtilization, false)]
+ [InlineData(ResourceUtilizationInstruments.ContainerCpuRequestUtilization, true)]
+ [InlineData(ResourceUtilizationInstruments.ContainerCpuRequestUtilization, false)]
public void SnapshotProvider_EmitsCpuMetrics(string instrumentName, bool useZeroToOneRange)
{
// Simulating 10% CPU usage (2 CPUs, 2000 ticks initially, 4000 ticks after 1 ms):
@@ -327,6 +329,8 @@ public void SnapshotProvider_EmitsCpuMetrics(string instrumentName, bool useZero
[InlineData(ResourceUtilizationInstruments.ProcessMemoryUtilization, false)]
[InlineData(ResourceUtilizationInstruments.ContainerMemoryLimitUtilization, true)]
[InlineData(ResourceUtilizationInstruments.ContainerMemoryLimitUtilization, false)]
+ [InlineData(ResourceUtilizationInstruments.ContainerMemoryRequestUtilization, true)]
+ [InlineData(ResourceUtilizationInstruments.ContainerMemoryRequestUtilization, false)]
public void SnapshotProvider_EmitsMemoryMetrics(string instrumentName, bool useZeroToOneRange)
{
_appMemoryUsage = 200UL;