Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

<PropertyGroup>
<InjectExperimentalAttributeOnLegacy>true</InjectExperimentalAttributeOnLegacy>
<InjectSharedInstruments>true</InjectSharedInstruments>
</PropertyGroup>

<PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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(
Expand All @@ -138,20 +143,19 @@ 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()
{
DateTimeOffset now = _timeProvider.GetUtcNow();
lock (_cpuLocker)
{
if (now < _refreshAfterCpu)
if (now <= _refreshAfterCpu)
{
return _lastCpuCoresUsed;
}
Expand All @@ -160,7 +164,7 @@ public double CpuUtilizationV2()
(long cpuUsageTime, long cpuPeriodCounter) = _parser.GetCgroupCpuUsageInNanosecondsAndCpuPeriodsV2();
lock (_cpuLocker)
{
if (now < _refreshAfterCpu)
if (now <= _refreshAfterCpu)
{
return _lastCpuCoresUsed;
}
Expand Down Expand Up @@ -193,7 +197,7 @@ public double CpuUtilization()

lock (_cpuLocker)
{
if (now < _refreshAfterCpu)
if (now <= _refreshAfterCpu)
{
return _cpuPercentage;
}
Expand All @@ -204,7 +208,7 @@ public double CpuUtilization()

lock (_cpuLocker)
{
if (now < _refreshAfterCpu)
if (now <= _refreshAfterCpu)
{
return _cpuPercentage;
}
Expand Down Expand Up @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
<ItemGroup>
<InternalsVisibleToDynamicProxyGenAssembly2 Include="*" />
<InternalsVisibleToTest Include="$(AssemblyName).Tests" />
<InternalsVisibleToTest Include="$(AssemblyName).Kubernetes.Tests" />
<InternalsVisibleToTest Include="Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -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.
/// </summary>
/// <remarks>
/// Max values will be emitted by limit metrics, and baseline values will be emmited by request metrics.
/// </remarks>
[Experimental(diagnosticId: DiagnosticIds.Experiments.ResourceMonitoring, UrlFormat = DiagnosticIds.UrlFormat)]
public sealed class ResourceQuota
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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;

Expand Down Expand Up @@ -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;
Expand All @@ -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(),
Expand All @@ -138,7 +146,7 @@ internal WindowsContainerSnapshotProvider(
// Process based metrics:
_ = meter.CreateObservableGauge(
name: ResourceUtilizationInstruments.ProcessCpuUtilization,
observeValue: CpuPercentage);
observeValue: () => CpuPercentage(_cpuLimit));

_ = meter.CreateObservableGauge(
name: ResourceUtilizationInstruments.ProcessMemoryUtilization,
Expand Down Expand Up @@ -207,7 +215,7 @@ private ulong MemoryUsage()
{
_memoryUsage = memoryUsage;
_refreshAfterMemory = now.Add(_memoryRefreshInterval);
_logger.ContainerMemoryUsageData(_memoryUsage, _memoryLimit);
_logger.ContainerMemoryUsageData(_memoryUsage, _memoryLimit, _memoryRequest);
}

return _memoryUsage;
Expand All @@ -225,43 +233,43 @@ private IEnumerable<Measurement<double>> GetCpuTime()
[new KeyValuePair<string, object?>("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;
_refreshAfterCpu = now.Add(_cpuRefreshInterval);
}
}

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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public WindowsSnapshotProvider(ILogger<WindowsSnapshotProvider>? logger, IMeterF
{
}

#pragma warning disable S107 // Methods should not have too many parameters
internal WindowsSnapshotProvider(
ILogger<WindowsSnapshotProvider>? logger,
IMeterFactory meterFactory,
Expand All @@ -54,6 +55,7 @@ internal WindowsSnapshotProvider(
Func<long> getCpuTicksFunc,
Func<long> getMemoryUsageFunc,
Func<ulong> getTotalMemoryInBytesFunc)
#pragma warning restore S107 // Methods should not have too many parameters
{
_logger = logger ?? NullLogger<WindowsSnapshotProvider>.Instance;

Expand Down
8 changes: 8 additions & 0 deletions src/Shared/Instruments/ResourceUtilizationInstruments.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ internal static class ResourceUtilizationInstruments
/// </remarks>
public const string ContainerMemoryLimitUtilization = "container.memory.limit.utilization";

/// <summary>
/// The name of an instrument to retrieve memory request consumption of all processes running inside a container or control group in range <c>[0, 1]</c>.
/// </summary>
/// <remarks>
/// The type of an instrument is <see cref="System.Diagnostics.Metrics.ObservableGauge{T}"/>.
/// </remarks>
public const string ContainerMemoryRequestUtilization = "container.memory.request.utilization";

/// <summary>
/// The name of an instrument to retrieve memory usage measured in bytes of all processes running inside a container or control group.
/// </summary>
Expand Down
Loading
Loading