diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Calculator.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Calculator.cs index a64ea0bfea9..cd0e67158d3 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Calculator.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Calculator.cs @@ -25,7 +25,7 @@ public static ResourceUtilization CalculateUtilization(in Snapshot first, in Sna long runtimeTickDelta = second.TotalTimeSinceStart.Ticks - first.TotalTimeSinceStart.Ticks; // Compute the total number of ticks available on the machine during that interval - double totalSystemTicks = runtimeTickDelta * systemResources.GuaranteedCpuUnits; + double totalSystemTicks = runtimeTickDelta; // fudge to avoid divide by zero if (totalSystemTicks <= 0) diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/LinuxUtilizationParserCgroupV1.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/LinuxUtilizationParserCgroupV1.cs index 00ada5dc8ae..07d39e70178 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/LinuxUtilizationParserCgroupV1.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/LinuxUtilizationParserCgroupV1.cs @@ -444,7 +444,7 @@ private static bool TryGetCpuUnitsFromCgroups(IFileSystem fileSystem, out float /// /// In cgroup v1 the CPU shares is used to determine the CPU allocation. /// in cgroup v2 the CPU weight is used to determine the CPU allocation. - /// To calculete CPU request in cgroup v2 we need to read the CPU weight and convert it to CPU shares. + /// To calculate CPU request in cgroup v2 we need to read the CPU weight and convert it to CPU shares. /// But for cgroup v1 we can read the CPU shares directly from the file. /// 1024 equals 1 CPU core. /// In cgroup v1 on some systems the location of the CPU shares file is different. diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/LinuxUtilizationProvider.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/LinuxUtilizationProvider.cs index 8d41b5fd167..fc870ae8604 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/LinuxUtilizationProvider.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/LinuxUtilizationProvider.cs @@ -15,12 +15,13 @@ internal sealed class LinuxUtilizationProvider : ISnapshotProvider private readonly object _cpuLocker = new(); private readonly object _memoryLocker = new(); private readonly ILinuxUtilizationParser _parser; - private readonly ulong _totalMemoryInBytes; + private readonly ulong _memoryLimit; private readonly TimeSpan _cpuRefreshInterval; private readonly TimeSpan _memoryRefreshInterval; private readonly TimeProvider _timeProvider; - private readonly double _scale; - private readonly double _scaleForTrackerApi; + private readonly double _scaleRelativeToCpuLimit; + private readonly double _scaleRelativeToCpuRequest; + private readonly double _scaleRelativeToCpuRequestForTrackerApi; private DateTimeOffset _refreshAfterCpu; private DateTimeOffset _refreshAfterMemory; @@ -37,73 +38,73 @@ public LinuxUtilizationProvider(IOptions options, ILi { _parser = parser; _timeProvider = timeProvider ?? TimeProvider.System; - var now = _timeProvider.GetUtcNow(); + DateTimeOffset now = _timeProvider.GetUtcNow(); _cpuRefreshInterval = options.Value.CpuConsumptionRefreshInterval; _memoryRefreshInterval = options.Value.MemoryConsumptionRefreshInterval; _refreshAfterCpu = now; _refreshAfterMemory = now; - _totalMemoryInBytes = _parser.GetAvailableMemoryInBytes(); + _memoryLimit = _parser.GetAvailableMemoryInBytes(); _previousHostCpuTime = _parser.GetHostCpuUsageInNanoseconds(); _previousCgroupCpuTime = _parser.GetCgroupCpuUsageInNanoseconds(); - var hostMemory = _parser.GetHostAvailableMemory(); - var hostCpus = _parser.GetHostCpuCount(); - var availableCpus = _parser.GetCgroupLimitedCpus(); - var cpuGuaranteedRequest = _parser.GetCgroupRequestCpu(); - _scale = hostCpus / availableCpus; - _scaleForTrackerApi = hostCpus / availableCpus; + float hostCpus = _parser.GetHostCpuCount(); + float cpuLimit = _parser.GetCgroupLimitedCpus(); + float cpuRequest = _parser.GetCgroupRequestCpu(); + _scaleRelativeToCpuLimit = hostCpus / cpuLimit; + _scaleRelativeToCpuRequest = hostCpus / cpuRequest; + _scaleRelativeToCpuRequestForTrackerApi = hostCpus; // the division by cpuRequest is performed later on in the ResourceUtilization class #pragma warning disable CA2000 // Dispose objects before losing scope // We don't dispose the meter because IMeterFactory handles that // An issue on analyzer side: https://github.com/dotnet/roslyn-analyzers/issues/6912 // Related documentation: https://github.com/dotnet/docs/pull/37170 - var meter = meterFactory.Create("Microsoft.Extensions.Diagnostics.ResourceMonitoring"); + var meter = meterFactory.Create(nameof(Microsoft.Extensions.Diagnostics.ResourceMonitoring)); #pragma warning restore CA2000 // Dispose objects before losing scope - _ = meter.CreateObservableGauge(name: ResourceUtilizationInstruments.CpuUtilization, observeValue: CpuUtilization, unit: "1"); - _ = meter.CreateObservableGauge(name: ResourceUtilizationInstruments.MemoryUtilization, observeValue: MemoryUtilization, unit: "1"); + _ = meter.CreateObservableGauge(name: ResourceUtilizationInstruments.ContainerCpuLimitUtilization, observeValue: () => CpuUtilization() * _scaleRelativeToCpuLimit, unit: "1"); + _ = meter.CreateObservableGauge(name: ResourceUtilizationInstruments.ContainerMemoryLimitUtilization, observeValue: MemoryUtilization, unit: "1"); + _ = meter.CreateObservableGauge(name: ResourceUtilizationInstruments.ContainerCpuRequestUtilization, observeValue: () => CpuUtilization() * _scaleRelativeToCpuRequest, unit: "1"); - // cpuGuaranteedRequest is a CPU request for pod, for host its 1 core - // available CPUs is a CPU limit for a pod or for a host. - // _totalMemoryInBytes - Resource Memory Limit (in k8s terms) - // _totalMemoryInBytes - To keep the contract, this parameter will get the Host available memory - Resources = new SystemResources(cpuGuaranteedRequest, availableCpus, _totalMemoryInBytes, _totalMemoryInBytes); + _ = meter.CreateObservableGauge(name: ResourceUtilizationInstruments.ProcessCpuUtilization, observeValue: () => CpuUtilization() * _scaleRelativeToCpuRequest, unit: "1"); + _ = meter.CreateObservableGauge(name: ResourceUtilizationInstruments.ProcessMemoryUtilization, observeValue: MemoryUtilization, unit: "1"); + + // cpuRequest is a CPU request (aka guaranteed number of CPU units) for pod, for host its 1 core + // cpuLimit is a CPU limit (aka max CPU units available) for a pod or for a host. + // _memoryLimit - Resource Memory Limit (in k8s terms) + // _memoryLimit - To keep the contract, this parameter will get the Host available memory + Resources = new SystemResources(cpuRequest, cpuLimit, _memoryLimit, _memoryLimit); } public double CpuUtilization() { - var now = _timeProvider.GetUtcNow(); - bool needUpdate = false; + DateTimeOffset now = _timeProvider.GetUtcNow(); lock (_cpuLocker) { - if (now >= _refreshAfterCpu) + if (now < _refreshAfterCpu) { - needUpdate = true; + return _cpuPercentage; } } - if (needUpdate) - { - var hostCpuTime = _parser.GetHostCpuUsageInNanoseconds(); - var cgroupCpuTime = _parser.GetCgroupCpuUsageInNanoseconds(); + long hostCpuTime = _parser.GetHostCpuUsageInNanoseconds(); + long cgroupCpuTime = _parser.GetCgroupCpuUsageInNanoseconds(); - lock (_cpuLocker) + lock (_cpuLocker) + { + if (now >= _refreshAfterCpu) { - if (now >= _refreshAfterCpu) + double deltaHost = hostCpuTime - _previousHostCpuTime; + double deltaCgroup = cgroupCpuTime - _previousCgroupCpuTime; + + if (deltaHost > 0 && deltaCgroup > 0) { - var deltaHost = hostCpuTime - _previousHostCpuTime; - var deltaCgroup = cgroupCpuTime - _previousCgroupCpuTime; - - if (deltaHost > 0 && deltaCgroup > 0) - { - var percentage = Math.Min(One, deltaCgroup / deltaHost * _scale); - - _cpuPercentage = percentage; - _refreshAfterCpu = now.Add(_cpuRefreshInterval); - _previousCgroupCpuTime = cgroupCpuTime; - _previousHostCpuTime = hostCpuTime; - } + double percentage = Math.Min(One, deltaCgroup / deltaHost); + + _cpuPercentage = percentage; + _refreshAfterCpu = now.Add(_cpuRefreshInterval); + _previousCgroupCpuTime = cgroupCpuTime; + _previousHostCpuTime = hostCpuTime; } } } @@ -113,30 +114,26 @@ public double CpuUtilization() public double MemoryUtilization() { - var now = _timeProvider.GetUtcNow(); - bool needUpdate = false; + DateTimeOffset now = _timeProvider.GetUtcNow(); lock (_memoryLocker) { - if (now >= _refreshAfterMemory) + if (now < _refreshAfterMemory) { - needUpdate = true; + return _memoryPercentage; } } - if (needUpdate) - { - var memoryUsed = _parser.GetMemoryUsageInBytes(); + ulong memoryUsed = _parser.GetMemoryUsageInBytes(); - lock (_memoryLocker) + lock (_memoryLocker) + { + if (now >= _refreshAfterMemory) { - if (now >= _refreshAfterMemory) - { - var memoryPercentage = Math.Min(One, (double)memoryUsed / _totalMemoryInBytes); + double memoryPercentage = Math.Min(One, (double)memoryUsed / _memoryLimit); - _memoryPercentage = memoryPercentage; - _refreshAfterMemory = now.Add(_memoryRefreshInterval); - } + _memoryPercentage = memoryPercentage; + _refreshAfterMemory = now.Add(_memoryRefreshInterval); } } @@ -150,14 +147,14 @@ public double MemoryUtilization() /// public Snapshot GetSnapshot() { - var hostTime = _parser.GetHostCpuUsageInNanoseconds(); - var cgroupTime = _parser.GetCgroupCpuUsageInNanoseconds(); - var memoryUsed = _parser.GetMemoryUsageInBytes(); + long hostTime = _parser.GetHostCpuUsageInNanoseconds(); + long cgroupTime = _parser.GetCgroupCpuUsageInNanoseconds(); + ulong memoryUsed = _parser.GetMemoryUsageInBytes(); return new Snapshot( totalTimeSinceStart: TimeSpan.FromTicks(hostTime / Hundred), kernelTimeSinceStart: TimeSpan.Zero, - userTimeSinceStart: TimeSpan.FromTicks((long)(cgroupTime / Hundred * _scaleForTrackerApi)), + userTimeSinceStart: TimeSpan.FromTicks((long)(cgroupTime / Hundred * _scaleRelativeToCpuRequestForTrackerApi)), memoryUsageInBytes: memoryUsed); } } diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceMonitoringServiceCollectionExtensions.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceMonitoringServiceCollectionExtensions.cs index 8b4d1915769..f370d3bb289 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceMonitoringServiceCollectionExtensions.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceMonitoringServiceCollectionExtensions.cs @@ -88,7 +88,7 @@ private static ResourceMonitorBuilder AddWindowsProvider(this ResourceMonitorBui builder.PickWindowsSnapshotProvider(); _ = builder.Services - .AddActivatedSingleton(); + .AddActivatedSingleton(); _ = builder.Services .AddActivatedSingleton(); diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceUtilization.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceUtilization.cs index c46986cc581..0645f2270bb 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceUtilization.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceUtilization.cs @@ -51,10 +51,16 @@ public readonly struct ResourceUtilization /// CPU and memory limits. public ResourceUtilization(double cpuUsedPercentage, ulong memoryUsedInBytes, SystemResources systemResources) { - CpuUsedPercentage = Throw.IfLessThan(cpuUsedPercentage, 0.0); + double guaranteedCpuUnits = systemResources.GuaranteedCpuUnits; + if (guaranteedCpuUnits <= 0) + { + guaranteedCpuUnits = 1; + } + + CpuUsedPercentage = Throw.IfLessThan(cpuUsedPercentage / guaranteedCpuUnits, 0.0); MemoryUsedInBytes = Throw.IfLessThan(memoryUsedInBytes, 0); SystemResources = systemResources; - MemoryUsedPercentage = Math.Min(Hundred, (double)MemoryUsedInBytes / SystemResources.GuaranteedMemoryInBytes * Hundred); + MemoryUsedPercentage = Math.Min(Hundred, (double)MemoryUsedInBytes / systemResources.GuaranteedMemoryInBytes * Hundred); } /// diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceUtilizationInstruments.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceUtilizationInstruments.cs index da38092353b..aa3fd012029 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceUtilizationInstruments.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceUtilizationInstruments.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; + namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring; /// @@ -13,18 +15,42 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring; internal static class ResourceUtilizationInstruments { /// - /// Gets the CPU consumption of the running application in range [0, 1]. + /// The name of an instrument to retrieve CPU limit consumption of all processes running inside a container or control group in range [0, 1]. + /// + /// + /// The type of an instrument is . + /// + public const string ContainerCpuLimitUtilization = "container.cpu.limit.utilization"; + + /// + /// The name of an instrument to retrieve CPU 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 ContainerCpuRequestUtilization = "container.cpu.request.utilization"; + + /// + /// The name of an instrument to retrieve memory limit consumption of all processes running inside a container or control group in range [0, 1]. + /// + /// + /// The type of an instrument is . + /// + public const string ContainerMemoryLimitUtilization = "container.memory.limit.utilization"; + + /// + /// The name of an instrument to retrieve CPU consumption share of the running process in range [0, 1]. /// /// /// The type of an instrument is . /// - public const string CpuUtilization = "process.cpu.utilization"; + public const string ProcessCpuUtilization = "process.cpu.utilization"; /// - /// Gets the memory consumption of the running application in range [0, 1]. + /// The name of an instrument to retrieve memory consumption share of the running process in range [0, 1]. /// /// /// The type of an instrument is . /// - public const string MemoryUtilization = "dotnet.process.memory.virtual.utilization"; + public const string ProcessMemoryUtilization = "dotnet.process.memory.virtual.utilization"; } diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Interop/IProcessInfo.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Interop/IProcessInfo.cs index 4199cc19476..41b90ad50f6 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Interop/IProcessInfo.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Interop/IProcessInfo.cs @@ -9,8 +9,14 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Interop; internal interface IProcessInfo { /// - /// Retrieve the memory usage of a system. + /// Retrieves the amount of memory, in bytes, used by the current process. /// - /// Memory usage amount in bytes. + /// The number of bytes allocated by the current process. + ulong GetCurrentProcessMemoryUsage(); + + /// + /// Retrieves the amount of memory, in bytes, used by the system. + /// + /// The number of bytes allocated by the system. ulong GetMemoryUsage(); } diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Interop/ProcessInfo.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Interop/ProcessInfo.cs index b7434244686..cb5febeff55 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Interop/ProcessInfo.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Interop/ProcessInfo.cs @@ -38,4 +38,10 @@ public ulong GetMemoryUsage() return memoryUsage; } + + public ulong GetCurrentProcessMemoryUsage() + { + using Process process = Process.GetCurrentProcess(); + return (ulong)process.WorkingSet64; + } } diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/WindowsContainerSnapshotProvider.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/WindowsContainerSnapshotProvider.cs index 58c2b3f8bfd..a09bb7ecbab 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/WindowsContainerSnapshotProvider.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/WindowsContainerSnapshotProvider.cs @@ -26,8 +26,8 @@ internal sealed class WindowsContainerSnapshotProvider : ISnapshotProvider private readonly object _memoryLocker = new(); private readonly TimeProvider _timeProvider; private readonly IProcessInfo _processInfo; - private readonly double _totalMemory; - private readonly double _cpuUnits; + private readonly double _memoryLimit; + private readonly double _cpuLimit; private readonly TimeSpan _cpuRefreshInterval; private readonly TimeSpan _memoryRefreshInterval; @@ -77,15 +77,18 @@ internal WindowsContainerSnapshotProvider( _timeProvider = timeProvider; - // initialize system resources information using var jobHandle = _createJobHandleObject(); - _cpuUnits = GetGuaranteedCpuUnits(jobHandle, systemInfo); - var memory = GetMemoryLimits(jobHandle); + var memoryLimitLong = GetMemoryLimit(jobHandle); + _memoryLimit = memoryLimitLong; + _cpuLimit = GetCpuLimit(jobHandle, systemInfo); - Resources = new SystemResources(_cpuUnits, _cpuUnits, memory, memory); + // CPU request (aka guaranteed CPU units) is not supported on Windows, so we set it to the same value as CPU limit (aka maximum CPU units). + // Memory request (aka guaranteed memory) is not supported on Windows, so we set it to the same value as memory limit (aka maximum memory). + var cpuRequest = _cpuLimit; + var memoryRequest = memoryLimitLong; + Resources = new SystemResources(cpuRequest, _cpuLimit, memoryRequest, memoryLimitLong); - _totalMemory = memory; var basicAccountingInfo = jobHandle.GetBasicAccountingInfo(); _oldCpuUsageTicks = basicAccountingInfo.TotalKernelTime + basicAccountingInfo.TotalUserTime; _oldCpuTimeTicks = _timeProvider.GetUtcNow().Ticks; @@ -98,11 +101,16 @@ internal WindowsContainerSnapshotProvider( // We don't dispose the meter because IMeterFactory handles that // An issue on analyzer side: https://github.com/dotnet/roslyn-analyzers/issues/6912 // Related documentation: https://github.com/dotnet/docs/pull/37170 - var meter = meterFactory.Create("Microsoft.Extensions.Diagnostics.ResourceMonitoring"); + var meter = meterFactory.Create(nameof(Microsoft.Extensions.Diagnostics.ResourceMonitoring)); #pragma warning restore CA2000 // Dispose objects before losing scope - _ = meter.CreateObservableGauge(name: ResourceUtilizationInstruments.CpuUtilization, observeValue: CpuPercentage); - _ = meter.CreateObservableGauge(name: ResourceUtilizationInstruments.MemoryUtilization, observeValue: MemoryPercentage); + // Container based metrics: + _ = meter.CreateObservableGauge(name: ResourceUtilizationInstruments.ContainerCpuLimitUtilization, observeValue: CpuPercentage); + _ = meter.CreateObservableGauge(name: ResourceUtilizationInstruments.ContainerMemoryLimitUtilization, observeValue: () => MemoryPercentage(() => _processInfo.GetMemoryUsage())); + + // Process based metrics: + _ = meter.CreateObservableGauge(name: ResourceUtilizationInstruments.ProcessCpuUtilization, observeValue: CpuPercentage); + _ = meter.CreateObservableGauge(name: ResourceUtilizationInstruments.ProcessMemoryUtilization, observeValue: () => MemoryPercentage(() => _processInfo.GetCurrentProcessMemoryUsage())); } public Snapshot GetSnapshot() @@ -116,10 +124,10 @@ public Snapshot GetSnapshot() TimeSpan.FromTicks(_timeProvider.GetUtcNow().Ticks), TimeSpan.FromTicks(basicAccountingInfo.TotalKernelTime), TimeSpan.FromTicks(basicAccountingInfo.TotalUserTime), - GetMemoryUsage()); + _processInfo.GetCurrentProcessMemoryUsage()); } - private static double GetGuaranteedCpuUnits(IJobHandle jobHandle, ISystemInfo systemInfo) + private static double GetCpuLimit(IJobHandle jobHandle, ISystemInfo systemInfo) { // Note: This function convert the CpuRate from CPU cycles to CPU units, also it scales // the CPU units with the number of processors (cores) available in the system. @@ -149,7 +157,7 @@ private static double GetGuaranteedCpuUnits(IJobHandle jobHandle, ISystemInfo sy /// Gets memory limit of the system. /// /// Memory limit allocated to the system in bytes. - private ulong GetMemoryLimits(IJobHandle jobHandle) + private ulong GetMemoryLimit(IJobHandle jobHandle) { var memoryLimitInBytes = jobHandle.GetExtendedLimitInfo().JobMemoryLimit.ToUInt64(); @@ -165,13 +173,7 @@ private ulong GetMemoryLimits(IJobHandle jobHandle) return memoryLimitInBytes; } - /// - /// Gets memory usage within the system. - /// - /// Memory usage within the system in bytes. - private ulong GetMemoryUsage() => _processInfo.GetMemoryUsage(); - - private double MemoryPercentage() + private double MemoryPercentage(Func getMemoryUsage) { var now = _timeProvider.GetUtcNow(); @@ -183,12 +185,13 @@ private double MemoryPercentage() } } - var currentMemoryUsage = GetMemoryUsage(); + var memoryUsage = getMemoryUsage(); + lock (_memoryLocker) { if (now >= _refreshAfterMemory) { - _memoryPercentage = Math.Min(Hundred, currentMemoryUsage / _totalMemory * Hundred); // Don't change calculation order, otherwise we loose some precision + _memoryPercentage = Math.Min(Hundred, memoryUsage / _memoryLimit * Hundred); // Don't change calculation order, otherwise we loose some precision _refreshAfterMemory = now.Add(_memoryRefreshInterval); } @@ -217,7 +220,7 @@ private double CpuPercentage() if (now >= _refreshAfterCpu) { var usageTickDelta = currentCpuTicks - _oldCpuUsageTicks; - var timeTickDelta = (now.Ticks - _oldCpuTimeTicks) * _cpuUnits; + var timeTickDelta = (now.Ticks - _oldCpuTimeTicks) * _cpuLimit; if (usageTickDelta > 0 && timeTickDelta > 0) { _oldCpuUsageTicks = currentCpuTicks; diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/WindowsCounters.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/WindowsNetworkMetrics.cs similarity index 95% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/WindowsCounters.cs rename to src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/WindowsNetworkMetrics.cs index a9510355125..115f7c95962 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/WindowsCounters.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/WindowsNetworkMetrics.cs @@ -8,11 +8,11 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows; -internal sealed class WindowsCounters +internal sealed class WindowsNetworkMetrics { private readonly TcpTableInfo _tcpTableInfo; - public WindowsCounters(IMeterFactory meterFactory, TcpTableInfo tcpTableInfo) + public WindowsNetworkMetrics(IMeterFactory meterFactory, TcpTableInfo tcpTableInfo) { _tcpTableInfo = tcpTableInfo; @@ -20,7 +20,7 @@ public WindowsCounters(IMeterFactory meterFactory, TcpTableInfo tcpTableInfo) // We don't dispose the meter because IMeterFactory handles that // An issue on analyzer side: https://github.com/dotnet/roslyn-analyzers/issues/6912 // Related documentation: https://github.com/dotnet/docs/pull/37170 - var meter = meterFactory.Create("Microsoft.Extensions.Diagnostics.ResourceMonitoring"); + var meter = meterFactory.Create(nameof(Microsoft.Extensions.Diagnostics.ResourceMonitoring)); #pragma warning restore CA2000 // Dispose objects before losing scope var tcpTag = new KeyValuePair("network.transport", "tcp"); diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/WindowsSnapshotProvider.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/WindowsSnapshotProvider.cs index c53b00886f5..ac563e60c9a 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/WindowsSnapshotProvider.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/WindowsSnapshotProvider.cs @@ -54,6 +54,10 @@ internal WindowsSnapshotProvider( _cpuUnits = getCpuUnitsFunc(); var totalMemory = getTotalMemoryInBytesFunc(); + + // We are not running in a job object (e.g. container), so we don't have + // any resource requests or resource limits, therefore using physical values + // such as number of CPUs and physical memory and using it for both requests and limits (aka 'guaranteed' and 'max'): Resources = new SystemResources(_cpuUnits, _cpuUnits, totalMemory, totalMemory); _timeProvider = timeProvider; @@ -72,11 +76,11 @@ internal WindowsSnapshotProvider( // We don't dispose the meter because IMeterFactory handles that // An issue on analyzer side: https://github.com/dotnet/roslyn-analyzers/issues/6912 // Related documentation: https://github.com/dotnet/docs/pull/37170 - var meter = meterFactory.Create("Microsoft.Extensions.Diagnostics.ResourceMonitoring"); + var meter = meterFactory.Create(nameof(Microsoft.Extensions.Diagnostics.ResourceMonitoring)); #pragma warning restore CA2000 // Dispose objects before losing scope - _ = meter.CreateObservableGauge(name: ResourceUtilizationInstruments.CpuUtilization, observeValue: CpuPercentage); - _ = meter.CreateObservableGauge(name: ResourceUtilizationInstruments.MemoryUtilization, observeValue: MemoryPercentage); + _ = meter.CreateObservableGauge(name: ResourceUtilizationInstruments.ProcessCpuUtilization, observeValue: CpuPercentage); + _ = meter.CreateObservableGauge(name: ResourceUtilizationInstruments.ProcessMemoryUtilization, observeValue: MemoryPercentage); } public Snapshot GetSnapshot() diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/AcceptanceTest.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/AcceptanceTest.cs index 84de0de25c9..5611ad8ec00 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/AcceptanceTest.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/AcceptanceTest.cs @@ -182,63 +182,45 @@ public void Adding_Linux_Resource_Utilization_With_Section_Registers_SnapshotPro Assert.Equal(100_000UL, provider.Resources.GuaranteedMemoryInBytes); // read from memory.max // The main usage of this library is containers. - // To not break the contaract with the main package, we need to set the maximum memory value to the guaranteed memory. + // To not break the contract with the main package, we need to set the maximum memory value to the guaranteed memory. Assert.Equal(100_000UL, provider.Resources.MaximumMemoryInBytes); } [ConditionalFact] + [CombinatorialData] [OSSkipCondition(OperatingSystems.Windows | OperatingSystems.MacOSX, SkipReason = "Linux specific tests")] - public Task ResourceUtilizationTracker_Reports_The_Same_Values_As_One_Can_Observe_From_Gauges() + public Task ResourceUtilizationTracker_And_Metrics_Report_Same_Values_With_Cgroupsv1() { var cpuRefresh = TimeSpan.FromMinutes(13); var memoryRefresh = TimeSpan.FromMinutes(14); var fileSystem = new HardcodedValueFileSystem(new Dictionary - { - { new FileInfo("/sys/fs/cgroup/memory/memory.limit_in_bytes"), "100000" }, - { new FileInfo("/sys/fs/cgroup/memory/memory.usage_in_bytes"), "450000" }, - { new FileInfo("/proc/stat"), "cpu 10 10 10 10 10 10 10 10 10 10"}, - { new FileInfo("/sys/fs/cgroup/cpuacct/cpuacct.usage"), "102312"}, - { new FileInfo("/proc/meminfo"), "MemTotal: 102312 kB"}, - { new FileInfo("/sys/fs/cgroup/cpuset/cpuset.cpus"), "0-19"}, - { new FileInfo("/sys/fs/cgroup/cpu/cpu.cfs_quota_us"), "12"}, - { new FileInfo("/sys/fs/cgroup/cpu/cpu.cfs_period_us"), "6"}, - { new FileInfo("/sys/fs/cgroup/cpu/cpu.shares"), "1024"}, - { new FileInfo("/sys/fs/cgroup/memory/memory.stat"), "total_inactive_file 100"}, - }); + { + { new FileInfo("/sys/fs/cgroup/memory/memory.limit_in_bytes"), "100000" }, + { new FileInfo("/sys/fs/cgroup/memory/memory.usage_in_bytes"), "450000" }, + { new FileInfo("/proc/stat"), "cpu 10 10 10 10 10 10 10 10 10 10"}, + { new FileInfo("/sys/fs/cgroup/cpuacct/cpuacct.usage"), "102312"}, + { new FileInfo("/proc/meminfo"), "MemTotal: 102312 kB"}, + { new FileInfo("/sys/fs/cgroup/cpuset/cpuset.cpus"), "0-19"}, + { new FileInfo("/sys/fs/cgroup/cpu/cpu.cfs_quota_us"), "24"}, + { new FileInfo("/sys/fs/cgroup/cpu/cpu.cfs_period_us"), "6"}, + { new FileInfo("/sys/fs/cgroup/cpu/cpu.shares"), "2048"}, + { new FileInfo("/sys/fs/cgroup/memory/memory.stat"), "total_inactive_file 100"}, + }); using var listener = new MeterListener(); var clock = new FakeTimeProvider(DateTimeOffset.UtcNow); var cpuFromGauge = 0.0d; + var cpuLimitFromGauge = 0.0d; + var cpuRequestFromGauge = 0.0d; var memoryFromGauge = 0.0d; + var memoryLimitFromGauge = 0.0d; using var e = new ManualResetEventSlim(); object? meterScope = null; - listener.InstrumentPublished = (instrument, meterListener) => - { - if (!ReferenceEquals(instrument.Meter.Scope, meterScope)) - { - return; - } - - if (instrument.Name == ResourceUtilizationInstruments.CpuUtilization || - instrument.Name == ResourceUtilizationInstruments.MemoryUtilization) - { - meterListener.EnableMeasurementEvents(instrument); - } - }; - - listener.SetMeasurementEventCallback((m, f, _, _) => - { - if (m.Name == ResourceUtilizationInstruments.CpuUtilization) - { - cpuFromGauge = f; - } - else if (m.Name == ResourceUtilizationInstruments.MemoryUtilization) - { - memoryFromGauge = f; - } - }); - + listener.InstrumentPublished = (Instrument instrument, MeterListener meterListener) + => OnInstrumentPublished(instrument, meterListener, meterScope); + listener.SetMeasurementEventCallback((m, f, _, _) + => OnMeasurementReceived(m, f, ref cpuFromGauge, ref cpuLimitFromGauge, ref cpuRequestFromGauge, ref memoryFromGauge, ref memoryLimitFromGauge)); listener.Start(); using var host = FakeHost.CreateBuilder() @@ -280,10 +262,146 @@ public Task ResourceUtilizationTracker_Reports_The_Same_Values_As_One_Can_Observ utilization = tracker.GetUtilization(TimeSpan.FromSeconds(5)); Assert.Equal(1, utilization.CpuUsedPercentage); + Assert.Equal(50, utilization.MemoryUsedPercentage); + Assert.Equal(0.5, cpuLimitFromGauge * 100); + Assert.Equal(utilization.CpuUsedPercentage, cpuRequestFromGauge * 100); + Assert.Equal(utilization.MemoryUsedPercentage, memoryLimitFromGauge * 100); Assert.Equal(utilization.CpuUsedPercentage, cpuFromGauge * 100); + Assert.Equal(utilization.MemoryUsedPercentage, memoryFromGauge * 100); + + return Task.CompletedTask; + } + + [ConditionalFact] + [CombinatorialData] + [OSSkipCondition(OperatingSystems.Windows | OperatingSystems.MacOSX, SkipReason = "Linux specific tests")] + public Task ResourceUtilizationTracker_And_Metrics_Report_Same_Values_With_Cgroupsv2() + { + var cpuRefresh = TimeSpan.FromMinutes(13); + var memoryRefresh = TimeSpan.FromMinutes(14); + var fileSystem = new HardcodedValueFileSystem(new Dictionary + { + { new FileInfo("/proc/stat"), "cpu 10 10 10 10 10 10 10 10 10 10"}, + { new FileInfo("/sys/fs/cgroup/cpu.stat"), "usage_usec 102"}, + { new FileInfo("/sys/fs/cgroup/memory.max"), "1048576" }, + { new FileInfo("/proc/meminfo"), "MemTotal: 1024 kB"}, + { new FileInfo("/sys/fs/cgroup/cpuset.cpus.effective"), "0-19"}, + { new FileInfo("/sys/fs/cgroup/cpu.max"), "40000 10000"}, + { new FileInfo("/sys/fs/cgroup/cpu.weight"), "79"}, // equals to 2046,9 CPU shares (cgroups v1) which is ~2 CPU units (2 * 1024), so have to use Math.Round() in Assertions down below. + }); + + using var listener = new MeterListener(); + var clock = new FakeTimeProvider(DateTimeOffset.UtcNow); + var cpuFromGauge = 0.0d; + var cpuLimitFromGauge = 0.0d; + var cpuRequestFromGauge = 0.0d; + var memoryFromGauge = 0.0d; + var memoryLimitFromGauge = 0.0d; + using var e = new ManualResetEventSlim(); + + object? meterScope = null; + listener.InstrumentPublished = (Instrument instrument, MeterListener meterListener) + => OnInstrumentPublished(instrument, meterListener, meterScope); + listener.SetMeasurementEventCallback((m, f, _, _) + => OnMeasurementReceived(m, f, ref cpuFromGauge, ref cpuLimitFromGauge, ref cpuRequestFromGauge, ref memoryFromGauge, ref memoryLimitFromGauge)); + listener.Start(); + + using var host = FakeHost.CreateBuilder() + .ConfigureServices(x => + x.AddLogging() + .AddSingleton(clock) + .AddSingleton(new FakeUserHz(100)) + .AddSingleton(fileSystem) + .AddSingleton(new GenericPublisher(_ => e.Set())) + .AddResourceMonitoring() + .Replace(ServiceDescriptor.Singleton())) + .Build(); + + meterScope = host.Services.GetRequiredService(); + var tracker = host.Services.GetService(); + Assert.NotNull(tracker); + + _ = host.RunAsync(); + + listener.RecordObservableInstruments(); + + var utilization = tracker.GetUtilization(TimeSpan.FromSeconds(5)); + + Assert.Equal(0, utilization.CpuUsedPercentage); + Assert.Equal(100, utilization.MemoryUsedPercentage); + Assert.True(double.IsNaN(cpuFromGauge)); + + // gauge multiplied by 100 because gauges are in range [0, 1], and utilization is in range [0, 100] + Assert.Equal(utilization.MemoryUsedPercentage, memoryFromGauge * 100); + + fileSystem.ReplaceFileContent(new FileInfo("/proc/stat"), "cpu 11 10 10 10 10 10 10 10 10 10"); + fileSystem.ReplaceFileContent(new FileInfo("/sys/fs/cgroup/cpu.stat"), "usage_usec 112"); + fileSystem.ReplaceFileContent(new FileInfo("/sys/fs/cgroup/memory.current"), "524298"); + fileSystem.ReplaceFileContent(new FileInfo("/sys/fs/cgroup/memory.stat"), "inactive_file 10"); + + clock.Advance(TimeSpan.FromSeconds(6)); + listener.RecordObservableInstruments(); + + e.Wait(); + + utilization = tracker.GetUtilization(TimeSpan.FromSeconds(5)); + + var roundedCpuUsedPercentage = Math.Round(utilization.CpuUsedPercentage, 1); + + Assert.Equal(1, roundedCpuUsedPercentage); Assert.Equal(50, utilization.MemoryUsedPercentage); + Assert.Equal(0.5, cpuLimitFromGauge * 100); + Assert.Equal(roundedCpuUsedPercentage, Math.Round(cpuRequestFromGauge * 100)); + Assert.Equal(utilization.MemoryUsedPercentage, memoryLimitFromGauge * 100); + Assert.Equal(roundedCpuUsedPercentage, Math.Round(cpuFromGauge * 100)); Assert.Equal(utilization.MemoryUsedPercentage, memoryFromGauge * 100); return Task.CompletedTask; } + + private static void OnInstrumentPublished(Instrument instrument, MeterListener meterListener, object? meterScope) + { + if (!ReferenceEquals(instrument.Meter.Scope, meterScope)) + { + return; + } + +#pragma warning disable S1067 // Expressions should not be too complex + if (instrument.Name == ResourceUtilizationInstruments.ProcessCpuUtilization || + instrument.Name == ResourceUtilizationInstruments.ProcessMemoryUtilization || + instrument.Name == ResourceUtilizationInstruments.ContainerCpuRequestUtilization || + instrument.Name == ResourceUtilizationInstruments.ContainerCpuLimitUtilization || + instrument.Name == ResourceUtilizationInstruments.ContainerMemoryLimitUtilization) + { + meterListener.EnableMeasurementEvents(instrument); + } +#pragma warning restore S1067 // Expressions should not be too complex + } + + private static void OnMeasurementReceived( + Instrument instrument, double value, + ref double cpuFromGauge, ref double cpuLimitFromGauge, ref double cpuRequestFromGauge, + ref double memoryFromGauge, ref double memoryLimitFromGauge) + { + if (instrument.Name == ResourceUtilizationInstruments.ProcessCpuUtilization) + { + cpuFromGauge = value; + } + else if (instrument.Name == ResourceUtilizationInstruments.ProcessMemoryUtilization) + { + memoryFromGauge = value; + } + else if (instrument.Name == ResourceUtilizationInstruments.ContainerCpuLimitUtilization) + { + cpuLimitFromGauge = value; + } + else if (instrument.Name == ResourceUtilizationInstruments.ContainerCpuRequestUtilization) + { + cpuRequestFromGauge = value; + } + else if (instrument.Name == ResourceUtilizationInstruments.ContainerMemoryLimitUtilization) + { + memoryLimitFromGauge = value; + } + } } diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxCountersTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxCountersTests.cs index 3fb10a74865..e842e3161d1 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxCountersTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxCountersTests.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics.Metrics; using System.IO; +using System.Linq; using Microsoft.TestUtilities; using Moq; using Xunit; @@ -15,10 +16,11 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Test; public sealed class LinuxCountersTests { [ConditionalFact] + [CombinatorialData] public void LinuxCounters_Registers_Instruments() { var meterName = Guid.NewGuid().ToString(); - var options = Options.Options.Create(new()); + var options = Options.Options.Create(new ResourceMonitoringOptions()); using var meter = new Meter(nameof(LinuxCounters_Registers_Instruments)); var meterFactoryMock = new Mock(); meterFactoryMock.Setup(x => x.Create(It.IsAny())) @@ -64,14 +66,27 @@ public void LinuxCounters_Registers_Instruments() listener.Start(); listener.RecordObservableInstruments(); - Assert.Equal(2, samples.Count); - Assert.Equal(ResourceUtilizationInstruments.CpuUtilization, samples[0].instrument.Name); - Assert.Equal(double.NaN, samples[0].value); - Assert.Equal(ResourceUtilizationInstruments.MemoryUtilization, samples[1].instrument.Name); - Assert.Equal(0.5, samples[1].value); + Assert.Equal(5, samples.Count); + + Assert.Contains(samples, x => x.instrument.Name == ResourceUtilizationInstruments.ContainerCpuLimitUtilization); + Assert.True(double.IsNaN(samples.Single(i => i.instrument.Name == ResourceUtilizationInstruments.ContainerCpuLimitUtilization).value)); + + 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.ContainerMemoryLimitUtilization); + Assert.Equal(0.5, samples.Single(i => i.instrument.Name == ResourceUtilizationInstruments.ContainerMemoryLimitUtilization).value); + + Assert.Contains(samples, x => x.instrument.Name == ResourceUtilizationInstruments.ProcessCpuUtilization); + Assert.True(double.IsNaN(samples.Single(i => i.instrument.Name == ResourceUtilizationInstruments.ProcessCpuUtilization).value)); + + Assert.Contains(samples, x => x.instrument.Name == ResourceUtilizationInstruments.ProcessMemoryUtilization); + Assert.Equal(0.5, samples.Single(i => i.instrument.Name == ResourceUtilizationInstruments.ProcessMemoryUtilization).value); + } [ConditionalFact] + [CombinatorialData] public void LinuxCounters_Registers_Instruments_CgroupV2() { var meterName = Guid.NewGuid().ToString(); @@ -120,10 +135,21 @@ public void LinuxCounters_Registers_Instruments_CgroupV2() listener.Start(); listener.RecordObservableInstruments(); - Assert.Equal(2, samples.Count); - Assert.Equal(ResourceUtilizationInstruments.CpuUtilization, samples[0].instrument.Name); - Assert.Equal(double.NaN, samples[0].value); - Assert.Equal(ResourceUtilizationInstruments.MemoryUtilization, samples[1].instrument.Name); - Assert.Equal(1, samples[1].value); + Assert.Equal(5, samples.Count); + + Assert.Contains(samples, x => x.instrument.Name == ResourceUtilizationInstruments.ContainerCpuLimitUtilization); + Assert.True(double.IsNaN(samples.Single(i => i.instrument.Name == ResourceUtilizationInstruments.ContainerCpuLimitUtilization).value)); + + 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.ContainerMemoryLimitUtilization); + Assert.Equal(1, samples.Single(i => i.instrument.Name == ResourceUtilizationInstruments.ContainerMemoryLimitUtilization).value); + + Assert.Contains(samples, x => x.instrument.Name == ResourceUtilizationInstruments.ProcessCpuUtilization); + Assert.True(double.IsNaN(samples.Single(i => i.instrument.Name == ResourceUtilizationInstruments.ProcessCpuUtilization).value)); + + Assert.Contains(samples, x => x.instrument.Name == ResourceUtilizationInstruments.ProcessMemoryUtilization); + Assert.Equal(1, samples.Single(i => i.instrument.Name == ResourceUtilizationInstruments.ProcessMemoryUtilization).value); } } 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 6daae8e276e..1a6cf083a2b 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsContainerSnapshotProviderTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsContainerSnapshotProviderTests.cs @@ -61,7 +61,7 @@ public WindowsContainerSnapshotProviderTests() .Returns(() => _limitInfo); _appMemoryUsage = 1000UL; - _processInfoMock.Setup(p => p.GetMemoryUsage()) + _processInfoMock.Setup(p => p.GetCurrentProcessMemoryUsage()) .Returns(() => _appMemoryUsage); } @@ -183,8 +183,10 @@ public void GetSnapshot_With_JobMemoryLimit_Set_To_Zero_ProducesCorrectSnapshot( Assert.True(data.MemoryUsageInBytes > 0); } - [Fact] - public void SnapshotProvider_EmitsCpuMetrics() + [Theory] + [InlineData(ResourceUtilizationInstruments.ProcessCpuUtilization)] + [InlineData(ResourceUtilizationInstruments.ContainerCpuLimitUtilization)] + public void SnapshotProvider_EmitsCpuMetrics(string instrumentName) { // Simulating 10% CPU usage (2 CPUs, 2000 ticks initially, 4000 ticks after 1 ms): JOBOBJECT_BASIC_ACCOUNTING_INFORMATION updatedAccountingInfo = default; @@ -205,7 +207,7 @@ public void SnapshotProvider_EmitsCpuMetrics() var meterFactoryMock = new Mock(); meterFactoryMock.Setup(x => x.Create(It.IsAny())) .Returns(meter); - using var metricCollector = new MetricCollector(meter, ResourceUtilizationInstruments.CpuUtilization, fakeClock); + using var metricCollector = new MetricCollector(meter, instrumentName, fakeClock); var options = new ResourceMonitoringOptions { CpuConsumptionRefreshInterval = TimeSpan.FromMilliseconds(2) }; @@ -245,12 +247,19 @@ public void SnapshotProvider_EmitsCpuMetrics() Assert.Equal(10, metricCollector.LastMeasurement.Value); // Consumed 10% of the CPU. } - [Fact] - public void SnapshotProvider_EmitsMemoryMetrics() + [Theory] + [InlineData(ResourceUtilizationInstruments.ProcessMemoryUtilization)] + [InlineData(ResourceUtilizationInstruments.ContainerMemoryLimitUtilization)] + public void SnapshotProvider_EmitsMemoryMetrics(string instrumentName) { _appMemoryUsage = 200UL; - ulong updatedAppMemoryUsage = 600UL; + + _processInfoMock.SetupSequence(p => p.GetCurrentProcessMemoryUsage()) + .Returns(() => _appMemoryUsage) + .Returns(updatedAppMemoryUsage) + .Throws(new InvalidOperationException("We shouldn't hit here...")); + _processInfoMock.SetupSequence(p => p.GetMemoryUsage()) .Returns(() => _appMemoryUsage) .Returns(updatedAppMemoryUsage) @@ -261,7 +270,7 @@ public void SnapshotProvider_EmitsMemoryMetrics() var meterFactoryMock = new Mock(); meterFactoryMock.Setup(x => x.Create(It.IsAny())) .Returns(meter); - using var metricCollector = new MetricCollector(meter, ResourceUtilizationInstruments.MemoryUtilization, fakeClock); + using var metricCollector = new MetricCollector(meter, instrumentName, fakeClock); var options = new ResourceMonitoringOptions { MemoryConsumptionRefreshInterval = TimeSpan.FromMilliseconds(2) }; diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsCountersTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsCountersTests.cs index 2be00da8718..6370e26cb09 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsCountersTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsCountersTests.cs @@ -35,7 +35,7 @@ public void WindowsCounters_Registers_Instruments() var tcpTableInfo = new TcpTableInfo(Options.Options.Create(options)); tcpTableInfo.SetGetTcpTableDelegate(TcpTableInfoTests.FakeGetTcpTableWithFakeInformation); tcpTableInfo.SetGetTcp6TableDelegate(Tcp6TableInfoTests.FakeGetTcp6TableWithFakeInformation); - var windowsCounters = new WindowsCounters(meterFactoryMock.Object, tcpTableInfo); + var windowsCounters = new WindowsNetworkMetrics(meterFactoryMock.Object, tcpTableInfo); using var listener = new MeterListener { InstrumentPublished = (instrument, listener) => @@ -77,7 +77,7 @@ public void WindowsCounters_Got_Unsuccessful() var tcpTableInfo = new TcpTableInfo(Options.Options.Create(options)); tcpTableInfo.SetGetTcpTableDelegate(TcpTableInfoTests.FakeGetTcpTableWithUnsuccessfulStatusAllTheTime); tcpTableInfo.SetGetTcp6TableDelegate(Tcp6TableInfoTests.FakeGetTcp6TableWithUnsuccessfulStatusAllTheTime); - var windowsCounters = new WindowsCounters(meterFactoryMock.Object, tcpTableInfo); + var windowsCounters = new WindowsNetworkMetrics(meterFactoryMock.Object, tcpTableInfo); using var listener = new MeterListener { InstrumentPublished = (instrument, listener) => diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsSnapshotProviderTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsSnapshotProviderTests.cs index 7dd0beb18eb..807e59e8bd0 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsSnapshotProviderTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsSnapshotProviderTests.cs @@ -77,7 +77,7 @@ public void SnapshotProvider_EmitsCpuMetrics() cpuTicks = 1_500L; - using var metricCollector = new MetricCollector(meter, ResourceUtilizationInstruments.CpuUtilization, fakeClock); + using var metricCollector = new MetricCollector(meter, ResourceUtilizationInstruments.ProcessCpuUtilization, fakeClock); // Step #0 - state in the beginning: metricCollector.RecordObservableInstruments(); @@ -110,7 +110,7 @@ public void SnapshotProvider_EmitsMemoryMetrics() var snapshotProvider = new WindowsSnapshotProvider(_fakeLogger, meterFactoryMock.Object, options, fakeClock, static () => 1, static () => 0, () => memoryUsed, static () => 3000UL); - using var metricCollector = new MetricCollector(meter, ResourceUtilizationInstruments.MemoryUtilization, fakeClock); + using var metricCollector = new MetricCollector(meter, ResourceUtilizationInstruments.ProcessMemoryUtilization, fakeClock); // Step #0 - state in the beginning: metricCollector.RecordObservableInstruments();