Skip to content

Commit d64fcc6

Browse files
committed
Fix runtime metrics
1 parent ccde8de commit d64fcc6

3 files changed

Lines changed: 45 additions & 19 deletions

File tree

tracer/src/Datadog.Trace/RuntimeMetrics/DiagnosticsMetricsRuntimeMetricsListener.cs

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -120,25 +120,12 @@ public void Refresh()
120120
}
121121

122122
// memory load
123-
// This is attempting to emulate the GcGlobalHeapHistory.MemoryLoad event details
124-
// That value is calculated using
125-
// - `current_gc_data_global->mem_pressure` (src/coreclr/gc/gc.cpp#L3288)
126-
// - which fetches the value set via `history->mem_pressure = entry_memory_load` (src/coreclr/gc/gc.cpp#L7912)
127-
// - which is set by calling `gc_heap::get_memory_info()` (src/coreclr/gc/gc.cpp#L29438)
128-
// - which then calls GCToOSInterface::GetMemoryStatus(...) which has platform-specific implementations
129-
// - On linux, memory_load is calculated differently depending if there's a restriction (src/coreclr/gc/unix/gcenv.unix.cpp#L1191)
130-
// - Physical Memory Used / Limit
131-
// - (g_totalPhysicalMemSize - GetAvailablePhysicalMemory()) / total
132-
// - On Windows, memory_load is calculated differently depending if there's a restriction (src/coreclr/gc/unix/gcenv.windows.cpp#L1000)
133-
// - Working Set Size / Limit
134-
// - GlobalMemoryStatusEx -> (ullTotalVirtual - ullAvailVirtual) * 100.0 / (float)ms.ullTotalVirtual
135-
//
136-
// We try to roughly emulate that using the info in gcInfo:
137-
var availableBytes = gcInfo.TotalAvailableMemoryBytes;
138-
139-
if (availableBytes > 0)
123+
// GCMemoryInfo.MemoryLoadBytes and GCMemoryInfo.HighMemoryLoadThresholdBytes are both scaled by the
124+
// GC's total_physical_mem, but TotalAvailableMemoryBytes switches to heap_hard_limit whenever a GC
125+
// hard limit is in play, so getting the memory load is not simple
126+
if (GcMemoryLoadCalculator.TryGetMemoryLoadPercentage(gcInfo) is { } memoryLoad)
140127
{
141-
statsd.Gauge(MetricsNames.GcMemoryLoad, (double)gcInfo.MemoryLoadBytes * 100.0 / availableBytes);
128+
statsd.Gauge(MetricsNames.GcMemoryLoad, memoryLoad);
142129
}
143130
}
144131
else

tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/RuntimeMetricsTests.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,41 @@ public async Task DiagnosticsMetricsApiSubmitsMetrics()
5555
EnvironmentHelper.EnableDefaultTransport();
5656
await RunTest();
5757
}
58+
59+
[SkippableFact]
60+
[Trait("Category", "EndToEnd")]
61+
[Trait("RunOnWindows", "True")]
62+
[Trait("SupportsInstrumentationVerification", "True")]
63+
public async Task DiagnosticsMetricsApiMemoryLoad_StaysWithinValidRange_UnderGcHardLimit()
64+
{
65+
// DOTNET_GCHeapHardLimitPercent (parsed as hex, so "19" == 0x19 == 25%) sets
66+
// gc_heap::heap_hard_limit != 0 (similar to a memory-limited container).
67+
SetEnvironmentVariable(ConfigurationKeys.RuntimeMetricsDiagnosticsMetricsApiEnabled, "1");
68+
SetEnvironmentVariable("DOTNET_GCHeapHardLimitPercent", "19");
69+
EnvironmentHelper.EnableDefaultTransport();
70+
71+
using var agent = EnvironmentHelper.GetMockAgent(useStatsD: true);
72+
using var processResult = await RunSampleAndWaitForExit(agent);
73+
var requests = agent.StatsdRequests;
74+
requests.Should().NotBeEmpty();
75+
76+
var metrics = requests.SelectMany(x => x.Split('\n')).ToList();
77+
var memoryLoadSamples = metrics
78+
.Where(m => m.StartsWith(MetricsNames.GcMemoryLoad + ":", StringComparison.Ordinal))
79+
.Select(
80+
m =>
81+
{
82+
var separator = m.IndexOf(':');
83+
var endIndex = m.IndexOf('|', separator + 1);
84+
return double.Parse(m.Substring(separator + 1, endIndex - separator - 1));
85+
})
86+
.ToList();
87+
88+
memoryLoadSamples.Should().NotBeEmpty();
89+
memoryLoadSamples.Should().OnlyContain(v => v > 0 && v <= 100);
90+
91+
agent.Exceptions.Should().BeEmpty();
92+
}
5893
#endif
5994

6095
[SkippableFact]

tracer/test/Datadog.Trace.Tests/RuntimeMetrics/DiagnosticMetricsRuntimeMetricsListenerTests.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
using Datadog.Trace.RuntimeMetrics;
2121
using Datadog.Trace.TestHelpers.Stats;
2222
using Datadog.Trace.Vendors.StatsdClient;
23+
using FluentAssertions;
2324
using Moq;
2425
using Xunit;
2526
using Range = Moq.Range;
@@ -43,7 +44,10 @@ public void PushEvents()
4344

4445
// some metrics are only recorded the _second_ time this is called, to avoid skewing the results at the start, so we just check for a couple
4546
statsd.Verify(s => s.Gauge(MetricsNames.Gen0HeapSize, It.IsAny<double>(), 1, null), Times.Once);
46-
statsd.Verify(s => s.Gauge(MetricsNames.GcMemoryLoad, It.IsInRange(0d, 100, Range.Inclusive), It.IsAny<double>(), null), Times.AtLeastOnce);
47+
48+
var expectedMemoryLoad = GcMemoryLoadCalculator.TryGetMemoryLoadPercentage(GC.GetGCMemoryInfo());
49+
expectedMemoryLoad.Should().NotBeNull();
50+
statsd.Verify(s => s.Gauge(MetricsNames.GcMemoryLoad, expectedMemoryLoad!.Value, It.IsAny<double>(), null), Times.AtLeastOnce);
4751
}
4852

4953
[Fact]

0 commit comments

Comments
 (0)