diff --git a/src/OpenTelemetry.Instrumentation.Runtime/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.Runtime/CHANGELOG.md index 3539aa3695..895d745764 100644 --- a/src/OpenTelemetry.Instrumentation.Runtime/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.Runtime/CHANGELOG.md @@ -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 diff --git a/src/OpenTelemetry.Instrumentation.Runtime/RuntimeMetrics.cs b/src/OpenTelemetry.Instrumentation.Runtime/RuntimeMetrics.cs index 513dfc07d8..ebae341a47 100644 --- a/src/OpenTelemetry.Instrumentation.Runtime/RuntimeMetrics.cs +++ b/src/OpenTelemetry.Instrumentation.Runtime/RuntimeMetrics.cs @@ -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; @@ -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."); } } @@ -105,6 +107,14 @@ public void Dispose() this.meter?.Dispose(); } + private static IEnumerable> GetGarbageCollectionCounts() + { + for (int i = 0; i < NumberOfGenerations; ++i) + { + yield return new(GC.CollectionCount(i), new KeyValuePair("gen", GenNames[i])); + } + } + #if NETCOREAPP3_1_OR_GREATER private static double GetFragmentation() { @@ -113,14 +123,27 @@ private static double GetFragmentation() } #endif - private IEnumerable> GetProcessorTimes() +#if NET6_0_OR_GREATER + private static IEnumerable> GetGarbageCollectionHeapSizes() { - var process = Process.GetCurrentProcess(); - return new[] + var generationInfo = GC.GetGCMemoryInfo().GenerationInfo; + + Measurement[] measurements = new Measurement[generationInfo.Length]; + int maxSupportedLength = Math.Min(generationInfo.Length, GenNames.Length); + for (int i = 0; i < maxSupportedLength; ++i) { - new Measurement(process.UserProcessorTime.Ticks * NanosecondsPerTick, new KeyValuePair("state", "user")), - new Measurement(process.PrivilegedProcessorTime.Ticks * NanosecondsPerTick, new KeyValuePair("state", "system")), - }; + measurements[i] = new(generationInfo[i].SizeAfterBytes, new KeyValuePair("gen", GenNames[i])); + } + + return measurements; + } +#endif + + private static IEnumerable> GetProcessorTimes() + { + var process = Process.GetCurrentProcess(); + yield return new(process.UserProcessorTime.TotalSeconds, new KeyValuePair("state", "user")); + yield return new(process.PrivilegedProcessorTime.TotalSeconds, new KeyValuePair("state", "system")); } } } diff --git a/test/OpenTelemetry.Instrumentation.Runtime.Tests/RuntimeMetricsTests.cs b/test/OpenTelemetry.Instrumentation.Runtime.Tests/RuntimeMetricsTests.cs index 618ca36614..997a62f6d6 100644 --- a/test/OpenTelemetry.Instrumentation.Runtime.Tests/RuntimeMetricsTests.cs +++ b/test/OpenTelemetry.Instrumentation.Runtime.Tests/RuntimeMetricsTests.cs @@ -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"); @@ -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;