Skip to content

Commit cc2317e

Browse files
evgenyfedorov2Evgenii Fedorov
andauthored
Resource Monitoring metrics on Windows - remove multiplication by 100 (#5473)
* Remove multiplication by 100 to put utilization in range [0,1] instead of [0,100] --------- Co-authored-by: Evgenii Fedorov <[email protected]>
1 parent 2534f08 commit cc2317e

File tree

5 files changed

+83
-32
lines changed

5 files changed

+83
-32
lines changed

src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceMonitoringOptions.Windows.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
using System.Collections.Generic;
55
using System.ComponentModel.DataAnnotations;
6+
using System.Diagnostics.CodeAnalysis;
7+
using Microsoft.Shared.DiagnosticIds;
68

79
namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring;
810

@@ -18,4 +20,17 @@ public partial class ResourceMonitoringOptions
1820
#pragma warning disable CA2227 // Collection properties should be read only
1921
public ISet<string> SourceIpAddresses { get; set; } = new HashSet<string>();
2022
#pragma warning restore CA2227 // Collection properties should be read only
23+
24+
/// <summary>
25+
/// Gets or sets a value indicating whether CPU and Memory utilization metric values should be in range <c>[0, 1]</c> instead of <c>[0, 100]</c>.
26+
/// </summary>
27+
/// <value>
28+
/// The default value is <see langword="false"/>.
29+
/// </value>
30+
/// <remarks>
31+
/// Use this property if you prefer to have the metric values in range <c>[0, 1]</c> instead of <c>[0, 100]</c>.
32+
/// In the long term, the default value of this property will be changed to <see langword="true"/>.
33+
/// </remarks>
34+
[Experimental(diagnosticId: DiagnosticIds.Experiments.ResourceMonitoring, UrlFormat = DiagnosticIds.UrlFormat)]
35+
public bool UseZeroToOneRangeForMetrics { get; set; }
2136
}

src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/WindowsContainerSnapshotProvider.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows;
1414

1515
internal sealed class WindowsContainerSnapshotProvider : ISnapshotProvider
1616
{
17+
private const double One = 1.0d;
1718
private const double Hundred = 100.0d;
1819

1920
private readonly Lazy<MEMORYSTATUSEX> _memoryStatus;
@@ -32,6 +33,7 @@ internal sealed class WindowsContainerSnapshotProvider : ISnapshotProvider
3233
private readonly double _cpuLimit;
3334
private readonly TimeSpan _cpuRefreshInterval;
3435
private readonly TimeSpan _memoryRefreshInterval;
36+
private readonly double _metricValueMultiplier;
3537

3638
private long _oldCpuUsageTicks;
3739
private long _oldCpuTimeTicks;
@@ -72,6 +74,8 @@ internal WindowsContainerSnapshotProvider(
7274
_logger = logger ?? NullLogger<WindowsContainerSnapshotProvider>.Instance;
7375
Log.RunningInsideJobObject(_logger);
7476

77+
_metricValueMultiplier = options.UseZeroToOneRangeForMetrics ? One : Hundred;
78+
7579
_memoryStatus = new Lazy<MEMORYSTATUSEX>(
7680
memoryInfo.GetMemoryStatus,
7781
LazyThreadSafetyMode.ExecutionAndPublication);
@@ -195,7 +199,8 @@ private double MemoryPercentage(Func<ulong> getMemoryUsage)
195199
{
196200
if (now >= _refreshAfterMemory)
197201
{
198-
_memoryPercentage = Math.Min(Hundred, memoryUsage / _memoryLimit * Hundred); // Don't change calculation order, otherwise we loose some precision
202+
// Don't change calculation order, otherwise we loose some precision:
203+
_memoryPercentage = Math.Min(_metricValueMultiplier, memoryUsage / _memoryLimit * _metricValueMultiplier);
199204
_refreshAfterMemory = now.Add(_memoryRefreshInterval);
200205
}
201206

@@ -229,7 +234,8 @@ private double CpuPercentage()
229234
var timeTickDelta = (now.Ticks - _oldCpuTimeTicks) * _cpuLimit;
230235
if (usageTickDelta > 0 && timeTickDelta > 0)
231236
{
232-
_cpuPercentage = Math.Min(Hundred, usageTickDelta / timeTickDelta * Hundred); // Don't change calculation order, otherwise precision is lost.
237+
// Don't change calculation order, otherwise precision is lost:
238+
_cpuPercentage = Math.Min(_metricValueMultiplier, usageTickDelta / timeTickDelta * _metricValueMultiplier);
233239

234240
Log.CpuContainerUsageData(
235241
_logger, basicAccountingInfo.TotalKernelTime, basicAccountingInfo.TotalUserTime, _oldCpuUsageTicks, timeTickDelta, _cpuLimit, _cpuPercentage);

src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/WindowsSnapshotProvider.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows;
1414

1515
internal sealed class WindowsSnapshotProvider : ISnapshotProvider
1616
{
17+
private const double One = 1.0d;
1718
private const double Hundred = 100.0d;
1819

1920
public SystemResources Resources { get; }
@@ -28,6 +29,7 @@ internal sealed class WindowsSnapshotProvider : ISnapshotProvider
2829
private readonly double _totalMemory;
2930
private readonly TimeSpan _cpuRefreshInterval;
3031
private readonly TimeSpan _memoryRefreshInterval;
32+
private readonly double _metricValueMultiplier;
3133

3234
private long _oldCpuUsageTicks;
3335
private long _oldCpuTimeTicks;
@@ -56,6 +58,8 @@ internal WindowsSnapshotProvider(
5658

5759
Log.RunningOutsideJobObject(_logger);
5860

61+
_metricValueMultiplier = options.UseZeroToOneRangeForMetrics ? One : Hundred;
62+
5963
_cpuUnits = getCpuUnitsFunc();
6064
var totalMemory = getTotalMemoryInBytesFunc();
6165

@@ -135,7 +139,8 @@ private double MemoryPercentage()
135139
{
136140
if (now >= _refreshAfterMemory)
137141
{
138-
_memoryPercentage = Math.Min(Hundred, currentMemoryUsage / _totalMemory * Hundred); // Don't change calculation order, otherwise we loose some precision
142+
// Don't change calculation order, otherwise we loose some precision:
143+
_memoryPercentage = Math.Min(_metricValueMultiplier, currentMemoryUsage / _totalMemory * _metricValueMultiplier);
139144
_refreshAfterMemory = now.Add(_memoryRefreshInterval);
140145
}
141146

@@ -167,7 +172,8 @@ private double CpuPercentage()
167172
var timeTickDelta = (now.Ticks - _oldCpuTimeTicks) * _cpuUnits;
168173
if (usageTickDelta > 0 && timeTickDelta > 0)
169174
{
170-
_cpuPercentage = Math.Min(Hundred, usageTickDelta / (double)timeTickDelta * Hundred); // Don't change calculation order, otherwise we loose some precision
175+
// Don't change calculation order, otherwise we loose some precision:
176+
_cpuPercentage = Math.Min(_metricValueMultiplier, usageTickDelta / (double)timeTickDelta * _metricValueMultiplier);
171177

172178
Log.CpuUsageData(_logger, currentCpuTicks, _oldCpuUsageTicks, timeTickDelta, _cpuUnits, _cpuPercentage);
173179

test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsContainerSnapshotProviderTests.cs

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -191,9 +191,11 @@ public void GetSnapshot_With_JobMemoryLimit_Set_To_Zero_ProducesCorrectSnapshot(
191191
}
192192

193193
[Theory]
194-
[InlineData(ResourceUtilizationInstruments.ProcessCpuUtilization)]
195-
[InlineData(ResourceUtilizationInstruments.ContainerCpuLimitUtilization)]
196-
public void SnapshotProvider_EmitsCpuMetrics(string instrumentName)
194+
[InlineData(ResourceUtilizationInstruments.ProcessCpuUtilization, true)]
195+
[InlineData(ResourceUtilizationInstruments.ProcessCpuUtilization, false)]
196+
[InlineData(ResourceUtilizationInstruments.ContainerCpuLimitUtilization, true)]
197+
[InlineData(ResourceUtilizationInstruments.ContainerCpuLimitUtilization, false)]
198+
public void SnapshotProvider_EmitsCpuMetrics(string instrumentName, bool useZeroToOneRange)
197199
{
198200
// Simulating 10% CPU usage (2 CPUs, 2000 ticks initially, 4000 ticks after 1 ms):
199201
JOBOBJECT_BASIC_ACCOUNTING_INFORMATION updatedAccountingInfo = default;
@@ -216,8 +218,12 @@ public void SnapshotProvider_EmitsCpuMetrics(string instrumentName)
216218
.Returns(meter);
217219
using var metricCollector = new MetricCollector<double>(meter, instrumentName, fakeClock);
218220

219-
var options = new ResourceMonitoringOptions { CpuConsumptionRefreshInterval = TimeSpan.FromMilliseconds(2) };
220-
221+
var options = new ResourceMonitoringOptions
222+
{
223+
CpuConsumptionRefreshInterval = TimeSpan.FromMilliseconds(2),
224+
UseZeroToOneRangeForMetrics = useZeroToOneRange
225+
};
226+
var multiplier = useZeroToOneRange ? 1 : 100;
221227
var snapshotProvider = new WindowsContainerSnapshotProvider(
222228
_memoryInfoMock.Object,
223229
_systemInfoMock.Object,
@@ -237,27 +243,29 @@ public void SnapshotProvider_EmitsCpuMetrics(string instrumentName)
237243
fakeClock.Advance(TimeSpan.FromMilliseconds(1));
238244
metricCollector.RecordObservableInstruments();
239245

240-
Assert.Equal(10, metricCollector.LastMeasurement.Value); // Consumed 10% of the CPU.
246+
Assert.Equal(0.1 * multiplier, metricCollector.LastMeasurement.Value); // Consumed 10% of the CPU.
241247

242248
// Step #2 - simulate 1 millisecond passing and collect metrics again:
243249
fakeClock.Advance(TimeSpan.FromMilliseconds(1));
244250
metricCollector.RecordObservableInstruments();
245251

246252
// CPU usage should be the same as before, as we didn't recalculate it:
247-
Assert.Equal(10, metricCollector.LastMeasurement.Value); // Still consuming 10% as gauge wasn't updated.
253+
Assert.Equal(0.1 * multiplier, metricCollector.LastMeasurement.Value); // Still consuming 10% as gauge wasn't updated.
248254

249255
// Step #3 - simulate 1 millisecond passing and collect metrics again:
250256
fakeClock.Advance(TimeSpan.FromMilliseconds(1));
251257
metricCollector.RecordObservableInstruments();
252258

253259
// CPU usage should be the same as before, as we're not simulating any CPU usage:
254-
Assert.Equal(10, metricCollector.LastMeasurement.Value); // Consumed 10% of the CPU.
260+
Assert.Equal(0.1 * multiplier, metricCollector.LastMeasurement.Value); // Consumed 10% of the CPU.
255261
}
256262

257263
[Theory]
258-
[InlineData(ResourceUtilizationInstruments.ProcessMemoryUtilization)]
259-
[InlineData(ResourceUtilizationInstruments.ContainerMemoryLimitUtilization)]
260-
public void SnapshotProvider_EmitsMemoryMetrics(string instrumentName)
264+
[InlineData(ResourceUtilizationInstruments.ProcessMemoryUtilization, true)]
265+
[InlineData(ResourceUtilizationInstruments.ProcessMemoryUtilization, false)]
266+
[InlineData(ResourceUtilizationInstruments.ContainerMemoryLimitUtilization, true)]
267+
[InlineData(ResourceUtilizationInstruments.ContainerMemoryLimitUtilization, false)]
268+
public void SnapshotProvider_EmitsMemoryMetrics(string instrumentName, bool useZeroToOneRange)
261269
{
262270
_appMemoryUsage = 200UL;
263271
ulong updatedAppMemoryUsage = 600UL;
@@ -279,8 +287,12 @@ public void SnapshotProvider_EmitsMemoryMetrics(string instrumentName)
279287
.Returns(meter);
280288
using var metricCollector = new MetricCollector<double>(meter, instrumentName, fakeClock);
281289

282-
var options = new ResourceMonitoringOptions { MemoryConsumptionRefreshInterval = TimeSpan.FromMilliseconds(2) };
283-
290+
var options = new ResourceMonitoringOptions
291+
{
292+
MemoryConsumptionRefreshInterval = TimeSpan.FromMilliseconds(2),
293+
UseZeroToOneRangeForMetrics = useZeroToOneRange
294+
};
295+
var multiplier = useZeroToOneRange ? 1 : 100;
284296
var snapshotProvider = new WindowsContainerSnapshotProvider(
285297
_memoryInfoMock.Object,
286298
_systemInfoMock.Object,
@@ -294,17 +306,17 @@ public void SnapshotProvider_EmitsMemoryMetrics(string instrumentName)
294306
// Step #0 - state in the beginning:
295307
metricCollector.RecordObservableInstruments();
296308
Assert.NotNull(metricCollector.LastMeasurement?.Value);
297-
Assert.Equal(10, metricCollector.LastMeasurement.Value); // Consuming 10% of the memory initially.
309+
Assert.Equal(0.1 * multiplier, metricCollector.LastMeasurement.Value); // Consuming 10% of the memory initially.
298310

299311
// Step #1 - simulate 1 millisecond passing and collect metrics again:
300312
fakeClock.Advance(options.MemoryConsumptionRefreshInterval - TimeSpan.FromMilliseconds(1));
301313
metricCollector.RecordObservableInstruments();
302-
Assert.Equal(10, metricCollector.LastMeasurement.Value); // Still consuming 10% as gauge wasn't updated.
314+
Assert.Equal(0.1 * multiplier, metricCollector.LastMeasurement.Value); // Still consuming 10% as gauge wasn't updated.
303315

304316
// Step #2 - simulate 2 milliseconds passing and collect metrics again:
305317
fakeClock.Advance(TimeSpan.FromMilliseconds(1));
306318
metricCollector.RecordObservableInstruments();
307-
Assert.Equal(30, metricCollector.LastMeasurement.Value); // Consuming 30% of the memory afterwards.
319+
Assert.Equal(0.3 * multiplier, metricCollector.LastMeasurement.Value); // Consuming 30% of the memory afterwards.
308320
}
309321

310322
[Fact]

test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsSnapshotProviderTests.cs

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,18 @@ public Task SnapshotProvider_EmitsLogRecord()
7171
return Verifier.Verify(logRecords[0]).UseDirectory(VerifiedDataDirectory);
7272
}
7373

74-
[ConditionalFact]
75-
public void SnapshotProvider_EmitsCpuMetrics()
74+
[ConditionalTheory]
75+
[CombinatorialData]
76+
public void SnapshotProvider_EmitsCpuMetrics(bool useZeroToOneRange)
7677
{
7778
var fakeClock = new FakeTimeProvider();
7879
var cpuTicks = 500L;
79-
var options = new ResourceMonitoringOptions { CpuConsumptionRefreshInterval = TimeSpan.FromMilliseconds(2) };
80+
var options = new ResourceMonitoringOptions
81+
{
82+
CpuConsumptionRefreshInterval = TimeSpan.FromMilliseconds(2),
83+
UseZeroToOneRangeForMetrics = useZeroToOneRange
84+
};
85+
var multiplier = useZeroToOneRange ? 1 : 100;
8086
using var meter = new Meter(nameof(SnapshotProvider_EmitsCpuMetrics));
8187
var meterFactoryMock = new Mock<IMeterFactory>();
8288
meterFactoryMock.Setup(x => x.Create(It.IsAny<MeterOptions>())).Returns(meter);
@@ -96,22 +102,28 @@ public void SnapshotProvider_EmitsCpuMetrics()
96102
// Step #1 - simulate 1 millisecond passing and collect metrics again:
97103
fakeClock.Advance(TimeSpan.FromMilliseconds(1));
98104
metricCollector.RecordObservableInstruments();
99-
Assert.Equal(5, metricCollector.LastMeasurement?.Value); // Consuming 5% of the CPU (2 CPUs, 1000 ticks, 1ms).
105+
Assert.Equal(0.05 * multiplier, metricCollector.LastMeasurement?.Value); // Consuming 5% of the CPU (2 CPUs, 1000 ticks, 1ms).
100106

101107
// Step #2 - simulate another 1 millisecond passing and collect metrics again:
102108
fakeClock.Advance(TimeSpan.FromMilliseconds(1));
103109
metricCollector.RecordObservableInstruments();
104110

105111
// CPU usage should be the same as before, as we're not simulating any CPU usage:
106-
Assert.Equal(5, metricCollector.LastMeasurement?.Value); // Still consuming 5% of the CPU
112+
Assert.Equal(0.05 * multiplier, metricCollector.LastMeasurement?.Value); // Still consuming 5% of the CPU
107113
}
108114

109-
[ConditionalFact]
110-
public void SnapshotProvider_EmitsMemoryMetrics()
115+
[ConditionalTheory]
116+
[CombinatorialData]
117+
public void SnapshotProvider_EmitsMemoryMetrics(bool useZeroToOneRange)
111118
{
112119
var fakeClock = new FakeTimeProvider();
113120
long memoryUsed = 300L;
114-
var options = new ResourceMonitoringOptions { MemoryConsumptionRefreshInterval = TimeSpan.FromMilliseconds(2) };
121+
var options = new ResourceMonitoringOptions
122+
{
123+
MemoryConsumptionRefreshInterval = TimeSpan.FromMilliseconds(2),
124+
UseZeroToOneRangeForMetrics = useZeroToOneRange
125+
};
126+
var multiplier = useZeroToOneRange ? 1 : 100;
115127
using var meter = new Meter(nameof(SnapshotProvider_EmitsMemoryMetrics));
116128
var meterFactoryMock = new Mock<IMeterFactory>();
117129
meterFactoryMock.Setup(x => x.Create(It.IsAny<MeterOptions>()))
@@ -124,21 +136,21 @@ public void SnapshotProvider_EmitsMemoryMetrics()
124136
// Step #0 - state in the beginning:
125137
metricCollector.RecordObservableInstruments();
126138
Assert.NotNull(metricCollector.LastMeasurement);
127-
Assert.Equal(10, metricCollector.LastMeasurement.Value); // Consuming 5% of the memory initially
139+
Assert.Equal(0.1 * multiplier, metricCollector.LastMeasurement.Value); // Consuming 5% of the memory initially
128140

129141
memoryUsed = 900L; // Simulate 30% memory usage.
130142

131143
// Step #1 - simulate 1 millisecond passing and collect metrics again:
132144
fakeClock.Advance(TimeSpan.FromMilliseconds(1));
133145
metricCollector.RecordObservableInstruments();
134146

135-
Assert.Equal(10, metricCollector.LastMeasurement.Value); // Still consuming 10% as gauge wasn't updated.
147+
Assert.Equal(0.1 * multiplier, metricCollector.LastMeasurement.Value); // Still consuming 10% as gauge wasn't updated.
136148

137149
// Step #2 - simulate 1 millisecond passing and collect metrics again:
138150
fakeClock.Advance(TimeSpan.FromMilliseconds(1));
139151
metricCollector.RecordObservableInstruments();
140152

141-
Assert.Equal(30, metricCollector.LastMeasurement.Value); // Consuming 30% of the memory afterwards
153+
Assert.Equal(0.3 * multiplier, metricCollector.LastMeasurement.Value); // Consuming 30% of the memory afterwards
142154

143155
memoryUsed = 3_100L; // Simulate more than 100% memory usage
144156

@@ -147,7 +159,7 @@ public void SnapshotProvider_EmitsMemoryMetrics()
147159
metricCollector.RecordObservableInstruments();
148160

149161
// Memory usage should be the same as before, as we're not simulating any CPU usage:
150-
Assert.Equal(100, metricCollector.LastMeasurement.Value); // Consuming 100% of the memory
162+
Assert.Equal(1 * multiplier, Math.Round(metricCollector.LastMeasurement.Value)); // Consuming 100% of the memory
151163
}
152164

153165
[ConditionalFact]

0 commit comments

Comments
 (0)