Skip to content

Commit

Permalink
Add GC heap size and count in Runtime metrics (#412)
Browse files Browse the repository at this point in the history
* Add `gc.heapsize` and change `gc.count` to multi-dimensional metrics
  • Loading branch information
xiang17 authored Jun 11, 2022
1 parent 5ac01fa commit 667cf28
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 27 deletions.
2 changes: 2 additions & 0 deletions src/OpenTelemetry.Instrumentation.Runtime/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
([#411](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/411))
* Fix some bugs in Runtime metrics
([#409](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/409))
* Add GC heap size and refactor GC count as multi-dimensional metrics in Runtime
metrics ([#412](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/412))

## 0.1.0-alpha.1

Expand Down
75 changes: 49 additions & 26 deletions src/OpenTelemetry.Instrumentation.Runtime/RuntimeMetrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ internal class RuntimeMetrics : IDisposable
internal static readonly string InstrumentationName = AssemblyName.Name;
internal static readonly string InstrumentationVersion = AssemblyName.Version.ToString();
private const long NanosecondsPerTick = 100;
private static readonly string[] GenNames = new string[] { "gen0", "gen1", "gen2", "loh", "poh" };
private static readonly int NumberOfGenerations = 3;
private static string metricPrefix = "process.runtime.dotnet.";
private readonly Meter meter;

Expand All @@ -49,53 +51,53 @@ public RuntimeMetrics(RuntimeMetricsOptions options)
{
// TODO: Almost all the ObservableGauge should be ObservableUpDownCounter (except for CPU utilization and fragmentation.ratio.
// Replace them once ObservableUpDownCounter is available.
this.meter.CreateObservableGauge($"{metricPrefix}gc.heap", () => GC.GetTotalMemory(false), "By", "GC Heap Size");
this.meter.CreateObservableGauge($"{metricPrefix}gen_0-gc.count", () => GC.CollectionCount(0), description: "Gen 0 GC Count");
this.meter.CreateObservableGauge($"{metricPrefix}gen_1-gc.count", () => GC.CollectionCount(1), description: "Gen 1 GC Count");
this.meter.CreateObservableGauge($"{metricPrefix}gen_2-gc.count", () => GC.CollectionCount(2), description: "Gen 2 GC Count");
this.meter.CreateObservableGauge($"{metricPrefix}gc.heap", () => GC.GetTotalMemory(false), "By", "GC Heap Size.");
this.meter.CreateObservableGauge($"{metricPrefix}gc.count", () => GetGarbageCollectionCounts(), description: "GC Count for all generations.");

#if NETCOREAPP3_1_OR_GREATER
this.meter.CreateObservableCounter($"{metricPrefix}gc.allocated.bytes", () => GC.GetTotalAllocatedBytes(), "By", "Allocation Rate");
this.meter.CreateObservableGauge($"{metricPrefix}gc.fragmentation.ratio", GetFragmentation, description: "GC Fragmentation");
this.meter.CreateObservableCounter($"{metricPrefix}gc.allocated.bytes", () => GC.GetTotalAllocatedBytes(), "By", "Allocation Rate.");
this.meter.CreateObservableGauge($"{metricPrefix}gc.fragmentation.ratio", GetFragmentation, description: "GC Fragmentation.");
#endif

#if NET6_0_OR_GREATER
this.meter.CreateObservableGauge($"{metricPrefix}gc.committed", () => GC.GetGCMemoryInfo().TotalCommittedBytes, "By", description: "GC Committed Bytes");
this.meter.CreateObservableGauge($"{metricPrefix}gc.committed", () => GC.GetGCMemoryInfo().TotalCommittedBytes, "By", description: "GC Committed Bytes.");
this.meter.CreateObservableGauge($"{metricPrefix}gc.heapsize", () => GetGarbageCollectionHeapSizes(), "By", "Heap size for all generations.");
#endif
}

#if NET6_0_OR_GREATER
if (options.IsJitEnabled)
{
this.meter.CreateObservableCounter($"{metricPrefix}il.bytes.jitted", () => System.Runtime.JitInfo.GetCompiledILBytes(), "By", description: "IL Bytes Jitted");
this.meter.CreateObservableCounter($"{metricPrefix}methods.jitted.count", () => System.Runtime.JitInfo.GetCompiledMethodCount(), description: "Number of Methods Jitted");
this.meter.CreateObservableGauge($"{metricPrefix}time.in.jit", () => System.Runtime.JitInfo.GetCompilationTime().Ticks * NanosecondsPerTick, "ns", description: "Time spent in JIT");
this.meter.CreateObservableCounter($"{metricPrefix}il.bytes.jitted", () => System.Runtime.JitInfo.GetCompiledILBytes(), "By", description: "IL Bytes Jitted.");
this.meter.CreateObservableCounter($"{metricPrefix}methods.jitted.count", () => System.Runtime.JitInfo.GetCompiledMethodCount(), description: "Number of Methods Jitted.");
this.meter.CreateObservableGauge($"{metricPrefix}time.in.jit", () => System.Runtime.JitInfo.GetCompilationTime().Ticks * NanosecondsPerTick, "ns", description: "Time spent in JIT.");
}
#endif

#if NETCOREAPP3_1_OR_GREATER
if (options.IsThreadingEnabled)
{
this.meter.CreateObservableGauge($"{metricPrefix}monitor.lock.contention.count", () => Monitor.LockContentionCount, description: "Monitor Lock Contention Count");
this.meter.CreateObservableGauge($"{metricPrefix}threadpool.thread.count", () => (long)ThreadPool.ThreadCount, description: "ThreadPool Thread Count");
this.meter.CreateObservableGauge($"{metricPrefix}threadpool.completed.items.count", () => ThreadPool.CompletedWorkItemCount, description: "ThreadPool Completed Work Item Count");
this.meter.CreateObservableGauge($"{metricPrefix}threadpool.queue.length", () => ThreadPool.PendingWorkItemCount, description: "ThreadPool Queue Length");
this.meter.CreateObservableGauge($"{metricPrefix}active.timer.count", () => Timer.ActiveCount, description: "Number of Active Timers");
this.meter.CreateObservableGauge($"{metricPrefix}monitor.lock.contention.count", () => Monitor.LockContentionCount, description: "Monitor Lock Contention Count.");
this.meter.CreateObservableGauge($"{metricPrefix}threadpool.thread.count", () => (long)ThreadPool.ThreadCount, description: "ThreadPool Thread Count.");
this.meter.CreateObservableGauge($"{metricPrefix}threadpool.completed.items.count", () => ThreadPool.CompletedWorkItemCount, description: "ThreadPool Completed Work Item Count.");
this.meter.CreateObservableGauge($"{metricPrefix}threadpool.queue.length", () => ThreadPool.PendingWorkItemCount, description: "ThreadPool Queue Length.");
this.meter.CreateObservableGauge($"{metricPrefix}active.timer.count", () => Timer.ActiveCount, description: "Number of Active Timers.");
}
#endif

if (options.IsProcessEnabled)
{
this.meter.CreateObservableCounter("process.cpu.time", this.GetProcessorTimes, "ns", "Processor time of this process");
this.meter.CreateObservableCounter("process.cpu.time", GetProcessorTimes, "s", "Processor time of this process.");

// Not yet official: https://github.com/open-telemetry/opentelemetry-specification/pull/2392
this.meter.CreateObservableGauge("process.cpu.count", () => (long)Environment.ProcessorCount, description: "The number of available logical CPUs");
this.meter.CreateObservableGauge("process.memory.usage", () => Process.GetCurrentProcess().WorkingSet64, "By", "The amount of physical memory in use");
this.meter.CreateObservableGauge("process.memory.virtual", () => Process.GetCurrentProcess().VirtualMemorySize64, "By", "The amount of committed virtual memory");
this.meter.CreateObservableGauge("process.cpu.count", () => (long)Environment.ProcessorCount, description: "The number of available logical CPUs.");
this.meter.CreateObservableGauge("process.memory.usage", () => Process.GetCurrentProcess().WorkingSet64, "By", "The amount of physical memory in use.");
this.meter.CreateObservableGauge("process.memory.virtual", () => Process.GetCurrentProcess().VirtualMemorySize64, "By", "The amount of committed virtual memory.");
}

if (options.IsAssembliesEnabled)
{
this.meter.CreateObservableCounter($"{metricPrefix}assembly.count", () => (long)AppDomain.CurrentDomain.GetAssemblies().Length, description: "Number of Assemblies Loaded");
this.meter.CreateObservableCounter($"{metricPrefix}assembly.count", () => (long)AppDomain.CurrentDomain.GetAssemblies().Length, description: "Number of Assemblies Loaded.");
}
}

Expand All @@ -105,6 +107,14 @@ public void Dispose()
this.meter?.Dispose();
}

private static IEnumerable<Measurement<long>> GetGarbageCollectionCounts()
{
for (int i = 0; i < NumberOfGenerations; ++i)
{
yield return new(GC.CollectionCount(i), new KeyValuePair<string, object>("gen", GenNames[i]));
}
}

#if NETCOREAPP3_1_OR_GREATER
private static double GetFragmentation()
{
Expand All @@ -113,14 +123,27 @@ private static double GetFragmentation()
}
#endif

private IEnumerable<Measurement<long>> GetProcessorTimes()
#if NET6_0_OR_GREATER
private static IEnumerable<Measurement<long>> GetGarbageCollectionHeapSizes()
{
var process = Process.GetCurrentProcess();
return new[]
var generationInfo = GC.GetGCMemoryInfo().GenerationInfo;

Measurement<long>[] measurements = new Measurement<long>[generationInfo.Length];
int maxSupportedLength = Math.Min(generationInfo.Length, GenNames.Length);
for (int i = 0; i < maxSupportedLength; ++i)
{
new Measurement<long>(process.UserProcessorTime.Ticks * NanosecondsPerTick, new KeyValuePair<string, object>("state", "user")),
new Measurement<long>(process.PrivilegedProcessorTime.Ticks * NanosecondsPerTick, new KeyValuePair<string, object>("state", "system")),
};
measurements[i] = new(generationInfo[i].SizeAfterBytes, new KeyValuePair<string, object>("gen", GenNames[i]));
}

return measurements;
}
#endif

private static IEnumerable<Measurement<double>> GetProcessorTimes()
{
var process = Process.GetCurrentProcess();
yield return new(process.UserProcessorTime.TotalSeconds, new KeyValuePair<string, object>("state", "user"));
yield return new(process.PrivilegedProcessorTime.TotalSeconds, new KeyValuePair<string, object>("state", "system"));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public void ProcessMetricsAreCaptured()
Assert.Equal(4, exportedItems.Count);

var cpuTimeMetric = exportedItems.First(i => i.Name == "process.cpu.time");
var sumReceived = GetLongSum(cpuTimeMetric);
var sumReceived = GetDoubleSum(cpuTimeMetric);
Assert.True(sumReceived > 0);

var cpuCountMetric = exportedItems.First(i => i.Name == "process.cpu.count");
Expand All @@ -91,6 +91,25 @@ public void ProcessMetricsAreCaptured()
Assert.True(GetLongSum(virtualMemoryMetric) > 0);
}

private static double GetDoubleSum(Metric metric)
{
double sum = 0;

foreach (ref readonly var metricPoint in metric.GetMetricPoints())
{
if (metric.MetricType.IsSum())
{
sum += metricPoint.GetSumDouble();
}
else
{
sum += metricPoint.GetGaugeLastValueDouble();
}
}

return sum;
}

private static double GetLongSum(Metric metric)
{
double sum = 0;
Expand Down

0 comments on commit 667cf28

Please sign in to comment.