RuntimeMetrics
using System.Collections.Generic;
using System.Runtime;
using System.Runtime.ExceptionServices;
using System.Runtime.Versioning;
using System.Threading;
namespace System.Diagnostics.Metrics
{
internal static class RuntimeMetrics
{
[ThreadStatic]
private static bool t_handlingFirstChanceException;
private static readonly Meter s_meter;
private static readonly string[] s_genNames;
private static readonly int s_maxGenerations;
private static readonly ObservableCounter<long> s_gcCollections;
private static readonly ObservableUpDownCounter<long> s_processWorkingSet;
private static readonly ObservableCounter<long> s_gcHeapTotalAllocated;
private static readonly ObservableUpDownCounter<long> s_gcLastCollectionMemoryCommitted;
private static readonly ObservableUpDownCounter<long> s_gcLastCollectionHeapSize;
private static readonly ObservableUpDownCounter<long> s_gcLastCollectionFragmentationSize;
private static readonly ObservableCounter<double> s_gcPauseTime;
private static readonly ObservableCounter<long> s_jitCompiledSize;
private static readonly ObservableCounter<long> s_jitCompiledMethodCount;
private static readonly ObservableCounter<double> s_jitCompilationTime;
private static readonly ObservableCounter<long> s_monitorLockContention;
private static readonly ObservableCounter<long> s_threadPoolThreadCount;
private static readonly ObservableCounter<long> s_threadPoolCompletedWorkItems;
private static readonly ObservableCounter<long> s_threadPoolQueueLength;
private static readonly ObservableUpDownCounter<long> s_timerCount;
private static readonly ObservableUpDownCounter<long> s_assembliesCount;
private static readonly Counter<long> s_exceptions;
private static readonly ObservableUpDownCounter<long> s_processCpuCount;
private static readonly ObservableCounter<double> s_processCpuTime;
static RuntimeMetrics()
{
s_meter = new Meter("System.Runtime");
s_genNames = new string[5] {
"gen0",
"gen1",
"gen2",
"loh",
"poh"
};
s_maxGenerations = Math.Min(GC.GetGCMemoryInfo().GenerationInfo.Length, s_genNames.Length);
s_gcCollections = s_meter.CreateObservableCounter("dotnet.gc.collections", GetGarbageCollectionCounts, "{collection}", "The number of garbage collections that have occurred since the process has started.");
s_processWorkingSet = s_meter.CreateObservableUpDownCounter("dotnet.process.memory.working_set", () => Environment.WorkingSet, "By", "The number of bytes of physical memory mapped to the process context.");
s_gcHeapTotalAllocated = s_meter.CreateObservableCounter("dotnet.gc.heap.total_allocated", () => GC.GetTotalAllocatedBytes(false), "By", "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.");
s_gcLastCollectionMemoryCommitted = s_meter.CreateObservableUpDownCounter("dotnet.gc.last_collection.memory.committed_size", delegate {
GCMemoryInfo gCMemoryInfo = GC.GetGCMemoryInfo();
if (gCMemoryInfo.Index != 0)
return new Measurement<long>[1] {
new Measurement<long>(gCMemoryInfo.TotalCommittedBytes)
};
return Array.Empty<Measurement<long>>();
}, "By", "The amount of committed virtual memory in use by the .NET GC, as observed during the latest garbage collection.");
s_gcLastCollectionHeapSize = s_meter.CreateObservableUpDownCounter("dotnet.gc.last_collection.heap.size", GetHeapSizes, "By", "The managed GC heap size (including fragmentation), as observed during the latest garbage collection.");
s_gcLastCollectionFragmentationSize = s_meter.CreateObservableUpDownCounter("dotnet.gc.last_collection.heap.fragmentation.size", GetHeapFragmentation, "By", "The heap fragmentation, as observed during the latest garbage collection.");
s_gcPauseTime = s_meter.CreateObservableCounter("dotnet.gc.pause.time", () => GC.GetTotalPauseDuration().TotalSeconds, "s", "The total amount of time paused in GC since the process has started.");
s_jitCompiledSize = s_meter.CreateObservableCounter("dotnet.jit.compiled_il.size", () => JitInfo.GetCompiledILBytes(false), "By", "Count of bytes of intermediate language that have been compiled since the process has started.");
s_jitCompiledMethodCount = s_meter.CreateObservableCounter("dotnet.jit.compiled_methods", () => JitInfo.GetCompiledMethodCount(false), "{method}", "The number of times the JIT compiler (re)compiled methods since the process has started.");
s_jitCompilationTime = s_meter.CreateObservableCounter("dotnet.jit.compilation.time", () => JitInfo.GetCompilationTime(false).TotalSeconds, "s", "The number of times the JIT compiler (re)compiled methods since the process has started.");
s_monitorLockContention = s_meter.CreateObservableCounter("dotnet.monitor.lock_contentions", () => Monitor.LockContentionCount, "{contention}", "The number of times there was contention when trying to acquire a monitor lock since the process has started.");
s_threadPoolThreadCount = s_meter.CreateObservableCounter("dotnet.thread_pool.thread.count", (Func<long>)(() => ThreadPool.ThreadCount), "{thread}", "The number of thread pool threads that currently exist.");
s_threadPoolCompletedWorkItems = s_meter.CreateObservableCounter("dotnet.thread_pool.work_item.count", () => ThreadPool.CompletedWorkItemCount, "{work_item}", "The number of work items that the thread pool has completed since the process has started.");
s_threadPoolQueueLength = s_meter.CreateObservableCounter("dotnet.thread_pool.queue.length", () => ThreadPool.PendingWorkItemCount, "{work_item}", "The number of work items that are currently queued to be processed by the thread pool.");
s_timerCount = s_meter.CreateObservableUpDownCounter("dotnet.timer.count", () => Timer.ActiveCount, "{timer}", "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.");
s_assembliesCount = s_meter.CreateObservableUpDownCounter("dotnet.assembly.count", (Func<long>)(() => AppDomain.CurrentDomain.GetAssemblies().Length), "{assembly}", "The number of .NET assemblies that are currently loaded.");
s_exceptions = s_meter.CreateCounter<long>("dotnet.exceptions", "{exception}", "The number of exceptions that have been thrown in managed code.");
s_processCpuCount = s_meter.CreateObservableUpDownCounter("dotnet.process.cpu.count", (Func<long>)(() => Environment.ProcessorCount), "{cpu}", "The number of processors available to the process.");
s_processCpuTime = ((OperatingSystem.IsBrowser() || OperatingSystem.IsTvOS() || (OperatingSystem.IsIOS() && !OperatingSystem.IsMacCatalyst())) ? null : s_meter.CreateObservableCounter("dotnet.process.cpu.time", GetCpuTime, "s", "CPU time used by the process."));
AppDomain.CurrentDomain.FirstChanceException += delegate(object source, FirstChanceExceptionEventArgs e) {
if (!t_handlingFirstChanceException) {
t_handlingFirstChanceException = true;
s_exceptions.Add(1, new KeyValuePair<string, object>("error.type", e.Exception.GetType().Name));
t_handlingFirstChanceException = false;
}
};
}
public static bool IsEnabled()
{
if (!s_gcCollections.Enabled && !s_processWorkingSet.Enabled && !s_gcHeapTotalAllocated.Enabled && !s_gcLastCollectionMemoryCommitted.Enabled && !s_gcLastCollectionHeapSize.Enabled && !s_gcLastCollectionFragmentationSize.Enabled && !s_gcPauseTime.Enabled && !s_jitCompiledSize.Enabled && !s_jitCompiledMethodCount.Enabled && !s_jitCompilationTime.Enabled && !s_monitorLockContention.Enabled && !s_timerCount.Enabled && !s_threadPoolThreadCount.Enabled && !s_threadPoolCompletedWorkItems.Enabled && !s_threadPoolQueueLength.Enabled && !s_assembliesCount.Enabled && !s_exceptions.Enabled && !s_processCpuCount.Enabled)
return s_processCpuTime?.Enabled ?? false;
return true;
}
private static IEnumerable<Measurement<long>> GetGarbageCollectionCounts()
{
long num = 0;
int num2;
for (int gen = GC.MaxGeneration; gen >= 0; gen = num2) {
long collectionsFromThisGeneration = GC.CollectionCount(gen);
long value = collectionsFromThisGeneration - num;
KeyValuePair<string, object> reference = new KeyValuePair<string, object>("gc.heap.generation", s_genNames[gen]);
yield return new Measurement<long>(value, new ReadOnlySpan<KeyValuePair<string, object>>(ref reference));
num = collectionsFromThisGeneration;
num2 = gen - 1;
}
}
[UnsupportedOSPlatform("ios")]
[UnsupportedOSPlatform("tvos")]
[UnsupportedOSPlatform("browser")]
[SupportedOSPlatform("maccatalyst")]
private static IEnumerable<Measurement<double>> GetCpuTime()
{
Environment.ProcessCpuUsage processCpuUsage = Environment.CpuUsage;
TimeSpan timeSpan = processCpuUsage.UserTime;
double totalSeconds = timeSpan.TotalSeconds;
KeyValuePair<string, object> reference = new KeyValuePair<string, object>("cpu.mode", "user");
yield return new Measurement<double>(totalSeconds, new ReadOnlySpan<KeyValuePair<string, object>>(ref reference));
timeSpan = processCpuUsage.PrivilegedTime;
double totalSeconds2 = timeSpan.TotalSeconds;
KeyValuePair<string, object> reference2 = new KeyValuePair<string, object>("cpu.mode", "system");
yield return new Measurement<double>(totalSeconds2, new ReadOnlySpan<KeyValuePair<string, object>>(ref reference2));
}
private static IEnumerable<Measurement<long>> GetHeapSizes()
{
GCMemoryInfo gcInfo = GC.GetGCMemoryInfo();
if (gcInfo.Index != 0) {
int num;
for (int i = 0; i < s_maxGenerations; i = num) {
long sizeAfterBytes = gcInfo.GenerationInfo[i].SizeAfterBytes;
KeyValuePair<string, object> reference = new KeyValuePair<string, object>("gc.heap.generation", s_genNames[i]);
yield return new Measurement<long>(sizeAfterBytes, new ReadOnlySpan<KeyValuePair<string, object>>(ref reference));
num = i + 1;
}
}
}
private static IEnumerable<Measurement<long>> GetHeapFragmentation()
{
GCMemoryInfo gcInfo = GC.GetGCMemoryInfo();
if (gcInfo.Index != 0) {
int num;
for (int i = 0; i < s_maxGenerations; i = num) {
long fragmentationAfterBytes = gcInfo.GenerationInfo[i].FragmentationAfterBytes;
KeyValuePair<string, object> reference = new KeyValuePair<string, object>("gc.heap.generation", s_genNames[i]);
yield return new Measurement<long>(fragmentationAfterBytes, new ReadOnlySpan<KeyValuePair<string, object>>(ref reference));
num = i + 1;
}
}
}
}
}