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 @@ -37,7 +37,7 @@ internal sealed class LinuxUtilizationProvider : ISnapshotProvider
private DateTimeOffset _refreshAfterMemory;
private double _cpuPercentage = double.NaN;
private double _lastCpuCoresUsed = double.NaN;
private double _memoryPercentage;
private ulong _memoryUsage;
private long _previousCgroupCpuTime;
private long _previousHostCpuTime;
private long _previousCgroupCpuPeriodCounter;
Expand Down Expand Up @@ -116,12 +116,18 @@ public LinuxUtilizationProvider(IOptions<ResourceMonitoringOptions> options, ILi

_ = meter.CreateObservableGauge(
name: ResourceUtilizationInstruments.ContainerMemoryLimitUtilization,
observeValues: () => GetMeasurementWithRetry(MemoryUtilization),
observeValues: () => GetMeasurementWithRetry(MemoryPercentage),
unit: "1");

_ = meter.CreateObservableUpDownCounter(
name: ResourceUtilizationInstruments.ContainerMemoryUsage,
observeValues: () => GetMeasurementWithRetry(() => (long)MemoryUsage()),
unit: "By",
description: "Memory usage of the container.");

_ = meter.CreateObservableGauge(
name: ResourceUtilizationInstruments.ProcessMemoryUtilization,
observeValues: () => GetMeasurementWithRetry(MemoryUtilization),
observeValues: () => GetMeasurementWithRetry(MemoryPercentage),
unit: "1");

// cpuRequest is a CPU request (aka guaranteed number of CPU units) for pod, for host its 1 core
Expand Down Expand Up @@ -216,34 +222,32 @@ public double CpuUtilization()
return _cpuPercentage;
}

public double MemoryUtilization()
public ulong MemoryUsage()
{
DateTimeOffset now = _timeProvider.GetUtcNow();

lock (_memoryLocker)
{
if (now < _refreshAfterMemory)
{
return _memoryPercentage;
return _memoryUsage;
}
}

ulong memoryUsed = _parser.GetMemoryUsageInBytes();
ulong memoryUsage = _parser.GetMemoryUsageInBytes();

lock (_memoryLocker)
{
if (now >= _refreshAfterMemory)
{
double memoryPercentage = Math.Min(One, (double)memoryUsed / _memoryLimit);

_memoryPercentage = memoryPercentage;
_memoryUsage = memoryUsage;
_refreshAfterMemory = now.Add(_memoryRefreshInterval);
}
}

_logger.MemoryUsageData(memoryUsed, _memoryLimit, _memoryPercentage);
_logger.MemoryUsageData(_memoryUsage);

return _memoryPercentage;
return _memoryUsage;
}

/// <remarks>
Expand All @@ -264,14 +268,24 @@ public Snapshot GetSnapshot()
memoryUsageInBytes: memoryUsed);
}

private Measurement<double>[] GetMeasurementWithRetry(Func<double> func)
private double MemoryPercentage()
{
ulong memoryUsage = MemoryUsage();
double memoryPercentage = Math.Min(One, (double)memoryUsage / _memoryLimit);

_logger.MemoryPercentageData(memoryUsage, _memoryLimit, memoryPercentage);
return memoryPercentage;
}

private Measurement<T>[] GetMeasurementWithRetry<T>(Func<T> func)
where T : struct
{
if (!TryGetValueWithRetry(func, out double value))
if (!TryGetValueWithRetry(func, out T value))
{
return Array.Empty<Measurement<double>>();
return Array.Empty<Measurement<T>>();
}

return new[] { new Measurement<double>(value) };
return new[] { new Measurement<T>(value) };
}

private bool TryGetValueWithRetry<T>(Func<T> func, out T value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public static partial void CpuUsageData(

[LoggerMessage(2, LogLevel.Debug,
"Computed memory usage with MemoryUsedInBytes = {memoryUsed}, MemoryLimit = {memoryLimit}, MemoryPercentage = {memoryPercentage}.")]
public static partial void MemoryUsageData(
public static partial void MemoryPercentageData(
this ILogger logger,
ulong memoryUsed,
double memoryLimit,
Expand Down Expand Up @@ -55,4 +55,10 @@ public static partial void CpuUsageDataV2(
public static partial void HandleDiskStatsException(
this ILogger logger,
string errorMessage);

[LoggerMessage(6, LogLevel.Debug,
"Computed memory usage = {memoryUsed}.")]
public static partial void MemoryUsageData(
this ILogger logger,
ulong memoryUsed);
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,11 @@ public static partial void CpuUsageData(
double cpuPercentage);

[LoggerMessage(4, LogLevel.Debug,
"Computed memory usage with CurrentMemoryUsage = {currentMemoryUsage}, TotalMemory = {totalMemory}, MemoryPercentage = {memoryPercentage}.")]
public static partial void MemoryUsageData(
"Computed memory usage for container: CurrentMemoryUsage = {currentMemoryUsage}, TotalMemory = {totalMemory}")]
public static partial void ContainerMemoryUsageData(
this ILogger logger,
ulong currentMemoryUsage,
double totalMemory,
double memoryPercentage);
double totalMemory);

#pragma warning disable S103 // Lines should not be too long
[LoggerMessage(5, LogLevel.Debug, "Computed CPU usage with CpuUsageKernelTicks = {cpuUsageKernelTicks}, CpuUsageUserTicks = {cpuUsageUserTicks}, OldCpuUsageTicks = {oldCpuUsageTicks}, TimeTickDelta = {timeTickDelta}, CpuUnits = {cpuUnits}, CpuPercentage = {cpuPercentage}.")]
Expand Down Expand Up @@ -60,4 +59,12 @@ public static partial void DiskIoPerfCounterException(
this ILogger logger,
string counterName,
string errorMessage);

[LoggerMessage(8, LogLevel.Debug,
"Computed memory usage for current process: ProcessMemoryUsage = {processMemoryUsage}, TotalMemory = {totalMemory}, MemoryPercentage = {memoryPercentage}")]
public static partial void ProcessMemoryPercentageData(
this ILogger logger,
ulong processMemoryUsage,
double totalMemory,
double memoryPercentage);
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ internal sealed class WindowsContainerSnapshotProvider : ISnapshotProvider

private readonly object _cpuLocker = new();
private readonly object _memoryLocker = new();
private readonly object _processMemoryLocker = new();
private readonly TimeProvider _timeProvider;
private readonly IProcessInfo _processInfo;
private readonly ILogger<WindowsContainerSnapshotProvider> _logger;
Expand All @@ -42,8 +43,10 @@ internal sealed class WindowsContainerSnapshotProvider : ISnapshotProvider
private long _oldCpuTimeTicks;
private DateTimeOffset _refreshAfterCpu;
private DateTimeOffset _refreshAfterMemory;
private DateTimeOffset _refreshAfterProcessMemory;
private double _cpuPercentage = double.NaN;
private double _memoryPercentage;
private ulong _memoryUsage;
private double _processMemoryPercentage;

public SystemResources Resources { get; }

Expand Down Expand Up @@ -107,6 +110,7 @@ internal WindowsContainerSnapshotProvider(
_memoryRefreshInterval = options.MemoryConsumptionRefreshInterval;
_refreshAfterCpu = _timeProvider.GetUtcNow();
_refreshAfterMemory = _timeProvider.GetUtcNow();
_refreshAfterProcessMemory = _timeProvider.GetUtcNow();

#pragma warning disable CA2000 // Dispose objects before losing scope
// We don't dispose the meter because IMeterFactory handles that
Expand All @@ -116,13 +120,34 @@ internal WindowsContainerSnapshotProvider(
#pragma warning restore CA2000 // Dispose objects before losing scope

// Container based metrics:
_ = meter.CreateObservableCounter(name: ResourceUtilizationInstruments.ContainerCpuTime, observeValues: GetCpuTime, unit: "s", description: "CPU time used by the container.");
_ = meter.CreateObservableGauge(name: ResourceUtilizationInstruments.ContainerCpuLimitUtilization, observeValue: CpuPercentage);
_ = meter.CreateObservableGauge(name: ResourceUtilizationInstruments.ContainerMemoryLimitUtilization, observeValue: () => MemoryPercentage(() => _processInfo.GetMemoryUsage()));
_ = meter.CreateObservableCounter(
name: ResourceUtilizationInstruments.ContainerCpuTime,
observeValues: GetCpuTime,
unit: "s",
description: "CPU time used by the container.");

_ = meter.CreateObservableGauge(
name: ResourceUtilizationInstruments.ContainerCpuLimitUtilization,
observeValue: CpuPercentage);

_ = meter.CreateObservableGauge(
name: ResourceUtilizationInstruments.ContainerMemoryLimitUtilization,
observeValue: () => Math.Min(_metricValueMultiplier, MemoryUsage() / _memoryLimit * _metricValueMultiplier));

_ = meter.CreateObservableUpDownCounter(
name: ResourceUtilizationInstruments.ContainerMemoryUsage,
observeValue: () => (long)MemoryUsage(),
unit: "By",
description: "Memory usage of the container.");

// Process based metrics:
_ = meter.CreateObservableGauge(name: ResourceUtilizationInstruments.ProcessCpuUtilization, observeValue: CpuPercentage);
_ = meter.CreateObservableGauge(name: ResourceUtilizationInstruments.ProcessMemoryUtilization, observeValue: () => MemoryPercentage(() => _processInfo.GetCurrentProcessMemoryUsage()));
_ = meter.CreateObservableGauge(
name: ResourceUtilizationInstruments.ProcessCpuUtilization,
observeValue: CpuPercentage);

_ = meter.CreateObservableGauge(
name: ResourceUtilizationInstruments.ProcessMemoryUtilization,
observeValue: ProcessMemoryPercentage);
}

public Snapshot GetSnapshot()
Expand Down Expand Up @@ -185,32 +210,58 @@ private ulong GetMemoryLimit(IJobHandle jobHandle)
return memoryLimitInBytes;
}

private double MemoryPercentage(Func<ulong> getMemoryUsage)
private double ProcessMemoryPercentage()
{
DateTimeOffset now = _timeProvider.GetUtcNow();

lock (_processMemoryLocker)
{
if (now < _refreshAfterProcessMemory)
{
return _processMemoryPercentage;
}
}

ulong processMemoryUsage = _processInfo.GetCurrentProcessMemoryUsage();

lock (_processMemoryLocker)
{
if (now >= _refreshAfterProcessMemory)
{
_processMemoryPercentage = Math.Min(_metricValueMultiplier, processMemoryUsage / _memoryLimit * _metricValueMultiplier);
_refreshAfterProcessMemory = now.Add(_memoryRefreshInterval);

_logger.ProcessMemoryPercentageData(processMemoryUsage, _memoryLimit, _processMemoryPercentage);
}

return _processMemoryPercentage;
}
}

private ulong MemoryUsage()
{
DateTimeOffset now = _timeProvider.GetUtcNow();

lock (_memoryLocker)
{
if (now < _refreshAfterMemory)
{
return _memoryPercentage;
return _memoryUsage;
}
}

ulong memoryUsage = getMemoryUsage();
ulong memoryUsage = _processInfo.GetMemoryUsage();

lock (_memoryLocker)
{
if (now >= _refreshAfterMemory)
{
// Don't change calculation order, otherwise we loose some precision:
_memoryPercentage = Math.Min(_metricValueMultiplier, memoryUsage / _memoryLimit * _metricValueMultiplier);
_memoryUsage = memoryUsage;
_refreshAfterMemory = now.Add(_memoryRefreshInterval);
_logger.ContainerMemoryUsageData(_memoryUsage, _memoryLimit);
}

_logger.MemoryUsageData(memoryUsage, _memoryLimit, _memoryPercentage);

return _memoryPercentage;
return _memoryUsage;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ private double MemoryPercentage()
_refreshAfterMemory = now.Add(_memoryRefreshInterval);
}

_logger.MemoryUsageData((ulong)currentMemoryUsage, _totalMemory, _memoryPercentage);
_logger.ProcessMemoryPercentageData((ulong)currentMemoryUsage, _totalMemory, _memoryPercentage);

return _memoryPercentage;
}
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 usage measured in bytes of all processes running inside a container or control group.
/// </summary>
/// <remarks>
/// The type of an instrument is <see cref="System.Diagnostics.Metrics.ObservableUpDownCounter{T}"/>.
/// </remarks>
public const string ContainerMemoryUsage = "container.memory.usage";

/// <summary>
/// The name of an instrument to retrieve CPU consumption share of the running process in range <c>[0, 1]</c>.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,7 @@ public async Task TestCpuAndMemoryChecks_WithMetrics(
Mock<IProcessInfo> processInfoMock = new();
var appMemoryUsage = memoryUsed;
processInfoMock.Setup(p => p.GetMemoryUsage()).Returns(() => appMemoryUsage);
processInfoMock.Setup(p => p.GetCurrentProcessMemoryUsage()).Returns(() => appMemoryUsage);

JOBOBJECT_EXTENDED_LIMIT_INFORMATION limitInfo = default;
limitInfo.JobMemoryLimit = new UIntPtr(totalMemory);
Expand Down
Loading
Loading