Skip to content

Commit 572bc2a

Browse files
committed
Add runtime metrics
1 parent c21a1f6 commit 572bc2a

File tree

3 files changed

+208
-0
lines changed

3 files changed

+208
-0
lines changed

src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ System.Diagnostics.DiagnosticSource</PackageDescription>
4545
<Compile Include="System\Diagnostics\DiagLinkedList.cs" />
4646
<Compile Include="System\Diagnostics\DistributedContextPropagator.cs" />
4747
<Compile Include="System\Diagnostics\LegacyPropagator.cs" />
48+
<Compile Include="System\Diagnostics\Metrics\RuntimeMetrics.cs" />
4849
<Compile Include="System\Diagnostics\NoOutputPropagator.cs" />
4950
<Compile Include="System\Diagnostics\PassThroughPropagator.cs" />
5051
<Compile Include="System\Diagnostics\RandomNumberGenerator.cs" />

src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/MeterListener.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ public sealed class MeterListener : IDisposable
3333
private MeasurementCallback<double> _doubleMeasurementCallback = (instrument, measurement, tags, state) => { /* no-op */ };
3434
private MeasurementCallback<decimal> _decimalMeasurementCallback = (instrument, measurement, tags, state) => { /* no-op */ };
3535

36+
static MeterListener()
37+
{
38+
_ = RuntimeMetrics.IsEnabled();
39+
}
40+
3641
/// <summary>
3742
/// Creates a MeterListener object.
3843
/// </summary>
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Collections.Generic;
5+
using System.Runtime.CompilerServices;
6+
#if !NETFRAMEWORK && !NETSTANDARD
7+
using System.Threading;
8+
#endif
9+
10+
namespace System.Diagnostics.Metrics
11+
{
12+
internal static class RuntimeMetrics
13+
{
14+
private const string MeterName = "System.Runtime";
15+
16+
private static readonly Meter s_meter = new(MeterName);
17+
18+
// These MUST align to the possible attribute values defined in the semantic conventions (TODO: link to the spec)
19+
private static readonly string[] s_genNames = ["gen0", "gen1", "gen2", "loh", "poh"];
20+
#if !NETFRAMEWORK && !NETSTANDARD
21+
private static readonly int s_maxGenerations = Math.Min(GC.GetGCMemoryInfo().GenerationInfo.Length, s_genNames.Length);
22+
#endif
23+
24+
static RuntimeMetrics()
25+
{
26+
AppDomain.CurrentDomain.FirstChanceException += (source, e) =>
27+
{
28+
s_exceptionCount.Add(1, new KeyValuePair<string, object?>("error.type", e.Exception.GetType().Name));
29+
};
30+
}
31+
32+
// GC Metrics
33+
34+
private static readonly ObservableCounter<long> s_gcCollectionsCounter = s_meter.CreateObservableCounter(
35+
"dotnet.gc.collections.count",
36+
GetGarbageCollectionCounts,
37+
unit: "{collection}",
38+
description: "Number of garbage collections that have occurred since the process has started.");
39+
40+
private static readonly ObservableUpDownCounter<long> s_gcObjectsSize = s_meter.CreateObservableUpDownCounter(
41+
"dotnet.gc.objects.size",
42+
() => GC.GetTotalMemory(forceFullCollection: false),
43+
unit: "By",
44+
description: "The number of bytes currently allocated on the managed GC heap. Fragmentation and other GC committed memory pools are excluded.");
45+
46+
#if !NETFRAMEWORK && !NETSTANDARD
47+
private static readonly ObservableCounter<long> s_gcMemoryTotalAllocated = s_meter.CreateObservableCounter(
48+
"dotnet.gc.memory.total_allocated",
49+
() => GC.GetTotalAllocatedBytes(),
50+
unit: "By",
51+
description: "The approximate number of bytes allocated on the managed GC heap since the process has started. The returned value does not include any native allocations.");
52+
53+
private static readonly ObservableUpDownCounter<long> s_gcMemoryCommited = s_meter.CreateObservableUpDownCounter(
54+
"dotnet.gc.memory.commited",
55+
() =>
56+
{
57+
GCMemoryInfo gcInfo = GC.GetGCMemoryInfo();
58+
59+
return gcInfo.Index == 0
60+
? Array.Empty<Measurement<long>>()
61+
: [new(GC.GetGCMemoryInfo().TotalCommittedBytes)];
62+
},
63+
unit: "By",
64+
description: "The amount of committed virtual memory for the managed GC heap, as observed during the latest garbage collection.");
65+
66+
private static readonly ObservableUpDownCounter<long> s_gcHeapSize = s_meter.CreateObservableUpDownCounter(
67+
"dotnet.gc.heap.size",
68+
GetHeapSizes,
69+
unit: "By",
70+
description: "The managed GC heap size (including fragmentation), as observed during the latest garbage collection.");
71+
72+
private static readonly ObservableUpDownCounter<long> s_gcHeapFragmentation = s_meter.CreateObservableUpDownCounter(
73+
"dotnet.gc.heap.fragmentation",
74+
GetHeapFragmentation,
75+
unit: "By",
76+
description: "The heap fragmentation, as observed during the latest garbage collection.");
77+
78+
private static readonly ObservableCounter<double> s_gcPauseTime = s_meter.CreateObservableCounter(
79+
"dotnet.gc.pause.time",
80+
() => GC.GetTotalPauseDuration().TotalSeconds,
81+
unit: "s",
82+
description: "The total amount of time paused in GC since the process has started.");
83+
84+
// JIT Metrics
85+
86+
private static readonly ObservableCounter<long> s_jitCompiledSize = s_meter.CreateObservableCounter(
87+
"dotnet.jit.compiled_il.size",
88+
() => Runtime.JitInfo.GetCompiledILBytes(),
89+
unit: "By",
90+
description: "Count of bytes of intermediate language that have been compiled since the process has started.");
91+
92+
private static readonly ObservableCounter<long> s_jitCompiledMethodCount = s_meter.CreateObservableCounter(
93+
"dotnet.jit.compiled_method.count",
94+
() => Runtime.JitInfo.GetCompiledMethodCount(),
95+
unit: "{method}",
96+
description: "The number of times the JIT compiler (re)compiled methods since the process has started.");
97+
98+
private static readonly ObservableCounter<double> s_jitCompilationTime = s_meter.CreateObservableCounter(
99+
"dotnet.jit.compilation.time",
100+
() => Runtime.JitInfo.GetCompilationTime().TotalSeconds,
101+
unit: "s",
102+
description: "The number of times the JIT compiler (re)compiled methods since the process has started.");
103+
104+
// Monitor Metrics
105+
106+
private static readonly ObservableCounter<long> s_monitorLockContention = s_meter.CreateObservableCounter(
107+
"dotnet.monitor.lock_contention.count",
108+
() => Monitor.LockContentionCount,
109+
unit: "s",
110+
description: "The number of times there was contention when trying to acquire a monitor lock since the process has started.");
111+
112+
// Thread Pool Metrics
113+
114+
//private static readonly ObservableCounter<long> s_threadPoolThreadCount = s_meter.CreateObservableCounter(
115+
// "dotnet.thread_pool.thread_count",
116+
// () => (long)ThreadPool.ThreadCount,
117+
// unit: "{thread}",
118+
// description: "The number of thread pool threads that currently exist.");
119+
120+
// TODO
121+
122+
// Timer Metrics
123+
124+
private static readonly ObservableUpDownCounter<long> s_timerCount = s_meter.CreateObservableUpDownCounter(
125+
"dotnet.timer.count",
126+
() => Timer.ActiveCount,
127+
unit: "{timer}",
128+
description: "The number of timer instances that are currently active. An active timer is registered to tick at some point in the future and has not yet been canceled.");
129+
#endif
130+
131+
private static readonly ObservableUpDownCounter<long> s_assemblyCount = s_meter.CreateObservableUpDownCounter(
132+
"dotnet.assemblies.count",
133+
() => (long)AppDomain.CurrentDomain.GetAssemblies().Length,
134+
unit: "{assembly}",
135+
description: "The number of .NET assemblies that are currently loaded.");
136+
137+
private static readonly Counter<long> s_exceptionCount = s_meter.CreateCounter<long>(
138+
"dotnet.exceptions.count",
139+
unit: "{exception}",
140+
description: "The number of exceptions that have been thrown in managed code.");
141+
142+
public static bool IsEnabled()
143+
{
144+
return s_gcCollectionsCounter.Enabled
145+
|| s_gcObjectsSize.Enabled
146+
#if !NETFRAMEWORK && !NETSTANDARD
147+
|| s_gcMemoryTotalAllocated.Enabled
148+
|| s_gcMemoryCommited.Enabled
149+
|| s_gcHeapSize.Enabled
150+
|| s_gcHeapFragmentation.Enabled
151+
|| s_gcPauseTime.Enabled
152+
|| s_jitCompiledSize.Enabled
153+
|| s_jitCompiledMethodCount.Enabled
154+
|| s_jitCompilationTime.Enabled
155+
|| s_monitorLockContention.Enabled
156+
|| s_timerCount.Enabled
157+
#endif
158+
|| s_assemblyCount.Enabled
159+
|| s_exceptionCount.Enabled;
160+
}
161+
162+
private static IEnumerable<Measurement<long>> GetGarbageCollectionCounts()
163+
{
164+
long collectionsFromHigherGeneration = 0;
165+
166+
for (int gen = 2; gen >= 0; --gen)
167+
{
168+
long collectionsFromThisGeneration = GC.CollectionCount(gen);
169+
yield return new(collectionsFromThisGeneration - collectionsFromHigherGeneration, new KeyValuePair<string, object?>("generation", s_genNames[gen]));
170+
collectionsFromHigherGeneration = collectionsFromThisGeneration;
171+
}
172+
}
173+
174+
#if !NETFRAMEWORK && !NETSTANDARD
175+
private static IEnumerable<Measurement<long>> GetHeapSizes()
176+
{
177+
GCMemoryInfo gcInfo = GC.GetGCMemoryInfo();
178+
179+
if (gcInfo.Index == 0)
180+
yield break;
181+
182+
for (int i = 0; i < s_maxGenerations; ++i)
183+
{
184+
yield return new(gcInfo.GenerationInfo[i].SizeAfterBytes, new KeyValuePair<string, object?>("generation", s_genNames[i]));
185+
}
186+
}
187+
188+
private static IEnumerable<Measurement<long>> GetHeapFragmentation()
189+
{
190+
GCMemoryInfo gcInfo = GC.GetGCMemoryInfo();
191+
192+
if (gcInfo.Index == 0)
193+
yield break;
194+
195+
for (int i = 0; i < s_maxGenerations; ++i)
196+
{
197+
yield return new(gcInfo.GenerationInfo[i].FragmentationAfterBytes, new KeyValuePair<string, object?>("generation", s_genNames[i]));
198+
}
199+
}
200+
#endif
201+
}
202+
}

0 commit comments

Comments
 (0)