-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Add runtime metrics in System.Diagnostics.DiagnosticSource
#104680
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
1451145
Add runtime metrics
stevejgordon 6812fe5
Add additional metrics
stevejgordon 1ca6ff9
Add an initial test
stevejgordon 6a61279
Use GC.MaxGeneration
stevejgordon 98f6b05
Make test less brittle
stevejgordon 101e8aa
PR feedback and additional tests
stevejgordon 34685d9
Add code comment
stevejgordon 9cd6303
Update runtime metrics to align with latest semcon changes
stevejgordon 7294037
Fix/update field names
stevejgordon 6162a29
PR feedback and naming updates
stevejgordon 298b7fa
Fix exceptions count test
stevejgordon 024d2d0
Add some test logging
stevejgordon 8457396
Fix MetricsTests
stevejgordon 7708d0a
More test logging and extended wait for metrics
stevejgordon fef38e1
More logging and a test fix
stevejgordon a5c348d
Remove System.Diagnostics.Process
stevejgordon e3662c9
Update HeapTags test to assert correctly if no GC has run
stevejgordon 24af281
Only include System.Threading.ThreadPool reference for net9.0
stevejgordon 087756c
Merge branch 'main' into runtime-metrics
tarekgh 5105ee8
Skip tests on browser
stevejgordon File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
229 changes: 229 additions & 0 deletions
229
...ries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/RuntimeMetrics.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,229 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using System.Collections.Generic; | ||
| using System.Threading; | ||
|
|
||
| namespace System.Diagnostics.Metrics | ||
| { | ||
| internal static class RuntimeMetrics | ||
| { | ||
| [ThreadStatic] private static bool t_handlingFirstChanceException; | ||
|
|
||
| private const string MeterName = "System.Runtime"; | ||
|
|
||
| private static readonly Meter s_meter = new(MeterName); | ||
|
|
||
| // These MUST align to the possible attribute values defined in the semantic conventions (TODO: link to the spec) | ||
| private static readonly string[] s_genNames = ["gen0", "gen1", "gen2", "loh", "poh"]; | ||
|
|
||
| private static readonly int s_maxGenerations = Math.Min(GC.GetGCMemoryInfo().GenerationInfo.Length, s_genNames.Length); | ||
|
|
||
| static RuntimeMetrics() | ||
| { | ||
| AppDomain.CurrentDomain.FirstChanceException += (source, e) => | ||
| { | ||
| // Avoid recursion if the listener itself throws an exception while recording the measurement | ||
| // in its `OnMeasurementRecorded` callback. | ||
| if (t_handlingFirstChanceException) return; | ||
stevejgordon marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| t_handlingFirstChanceException = true; | ||
| s_exceptions.Add(1, new KeyValuePair<string, object?>("error.type", e.Exception.GetType().Name)); | ||
| t_handlingFirstChanceException = false; | ||
| }; | ||
| } | ||
|
|
||
| private static readonly ObservableCounter<long> s_gcCollections = s_meter.CreateObservableCounter( | ||
| "dotnet.gc.collections", | ||
| GetGarbageCollectionCounts, | ||
| unit: "{collection}", | ||
| description: "The number of garbage collections that have occurred since the process has started."); | ||
|
|
||
| private static readonly ObservableUpDownCounter<long> s_processWorkingSet = s_meter.CreateObservableUpDownCounter( | ||
| "dotnet.process.memory.working_set", | ||
| () => Environment.WorkingSet, | ||
| unit: "By", | ||
| description: "The number of bytes of physical memory mapped to the process context."); | ||
|
|
||
| private static readonly ObservableCounter<long> s_gcHeapTotalAllocated = s_meter.CreateObservableCounter( | ||
| "dotnet.gc.heap.total_allocated", | ||
| () => GC.GetTotalAllocatedBytes(), | ||
| unit: "By", | ||
| 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."); | ||
|
|
||
| private static readonly ObservableUpDownCounter<long> s_gcLastCollectionMemoryCommitted = s_meter.CreateObservableUpDownCounter( | ||
| "dotnet.gc.last_collection.memory.committed_size", | ||
| () => | ||
| { | ||
| GCMemoryInfo gcInfo = GC.GetGCMemoryInfo(); | ||
|
|
||
| return gcInfo.Index == 0 | ||
| ? Array.Empty<Measurement<long>>() | ||
| : [new(gcInfo.TotalCommittedBytes)]; | ||
| }, | ||
| unit: "By", | ||
| description: "The amount of committed virtual memory in use by the .NET GC, as observed during the latest garbage collection."); | ||
|
|
||
| private static readonly ObservableUpDownCounter<long> s_gcLastCollectionHeapSize = s_meter.CreateObservableUpDownCounter( | ||
| "dotnet.gc.last_collection.heap.size", | ||
| GetHeapSizes, | ||
| unit: "By", | ||
| description: "The managed GC heap size (including fragmentation), as observed during the latest garbage collection."); | ||
|
|
||
| private static readonly ObservableUpDownCounter<long> s_gcLastCollectionFragmentationSize = s_meter.CreateObservableUpDownCounter( | ||
| "dotnet.gc.last_collection.heap.fragmentation.size", | ||
| GetHeapFragmentation, | ||
| unit: "By", | ||
| description: "The heap fragmentation, as observed during the latest garbage collection."); | ||
|
|
||
| private static readonly ObservableCounter<double> s_gcPauseTime = s_meter.CreateObservableCounter( | ||
| "dotnet.gc.pause.time", | ||
| () => GC.GetTotalPauseDuration().TotalSeconds, | ||
| unit: "s", | ||
| description: "The total amount of time paused in GC since the process has started."); | ||
|
|
||
| private static readonly ObservableCounter<long> s_jitCompiledSize = s_meter.CreateObservableCounter( | ||
| "dotnet.jit.compiled_il.size", | ||
| () => Runtime.JitInfo.GetCompiledILBytes(), | ||
| unit: "By", | ||
| description: "Count of bytes of intermediate language that have been compiled since the process has started."); | ||
|
|
||
| private static readonly ObservableCounter<long> s_jitCompiledMethodCount = s_meter.CreateObservableCounter( | ||
| "dotnet.jit.compiled_methods", | ||
| () => Runtime.JitInfo.GetCompiledMethodCount(), | ||
| unit: "{method}", | ||
| description: "The number of times the JIT compiler (re)compiled methods since the process has started."); | ||
|
|
||
| private static readonly ObservableCounter<double> s_jitCompilationTime = s_meter.CreateObservableCounter( | ||
| "dotnet.jit.compilation.time", | ||
| () => Runtime.JitInfo.GetCompilationTime().TotalSeconds, | ||
| unit: "s", | ||
| description: "The number of times the JIT compiler (re)compiled methods since the process has started."); | ||
|
|
||
| private static readonly ObservableCounter<long> s_monitorLockContention = s_meter.CreateObservableCounter( | ||
| "dotnet.monitor.lock_contentions", | ||
| () => Monitor.LockContentionCount, | ||
| unit: "{contention}", | ||
| description: "The number of times there was contention when trying to acquire a monitor lock since the process has started."); | ||
|
|
||
| private static readonly ObservableCounter<long> s_threadPoolThreadCount = s_meter.CreateObservableCounter( | ||
| "dotnet.thread_pool.thread.count", | ||
| () => (long)ThreadPool.ThreadCount, | ||
| unit: "{thread}", | ||
| description: "The number of thread pool threads that currently exist."); | ||
|
|
||
| private static readonly ObservableCounter<long> s_threadPoolCompletedWorkItems = s_meter.CreateObservableCounter( | ||
| "dotnet.thread_pool.work_item.count", | ||
| () => ThreadPool.CompletedWorkItemCount, | ||
| unit: "{work_item}", | ||
| description: "The number of work items that the thread pool has completed since the process has started."); | ||
|
|
||
| private static readonly ObservableCounter<long> s_threadPoolQueueLength = s_meter.CreateObservableCounter( | ||
| "dotnet.thread_pool.queue.length", | ||
| () => ThreadPool.PendingWorkItemCount, | ||
| unit: "{work_item}", | ||
| description: "The number of work items that are currently queued to be processed by the thread pool."); | ||
|
|
||
| private static readonly ObservableUpDownCounter<long> s_timerCount = s_meter.CreateObservableUpDownCounter( | ||
| "dotnet.timer.count", | ||
| () => Timer.ActiveCount, | ||
| unit: "{timer}", | ||
| 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."); | ||
|
|
||
| private static readonly ObservableUpDownCounter<long> s_assembliesCount = s_meter.CreateObservableUpDownCounter( | ||
| "dotnet.assembly.count", | ||
| () => (long)AppDomain.CurrentDomain.GetAssemblies().Length, | ||
| unit: "{assembly}", | ||
| description: "The number of .NET assemblies that are currently loaded."); | ||
|
|
||
| private static readonly Counter<long> s_exceptions = s_meter.CreateCounter<long>( | ||
| "dotnet.exceptions", | ||
| unit: "{exception}", | ||
| description: "The number of exceptions that have been thrown in managed code."); | ||
|
|
||
| private static readonly ObservableUpDownCounter<long> s_processCpuCount = s_meter.CreateObservableUpDownCounter( | ||
| "dotnet.process.cpu.count", | ||
| () => (long)Environment.ProcessorCount, | ||
| unit: "{cpu}", | ||
| description: "The number of processors available to the process."); | ||
|
|
||
| // TODO - Uncomment once an implementation for https://github.com/dotnet/runtime/issues/104844 is available. | ||
| //private static readonly ObservableCounter<double> s_processCpuTime = s_meter.CreateObservableCounter( | ||
| // "dotnet.process.cpu.time", | ||
| // GetCpuTime, | ||
| // unit: "s", | ||
| // description: "CPU time used by the process as reported by the CLR."); | ||
|
|
||
| public static bool IsEnabled() | ||
| { | ||
| return 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; | ||
| //|| s_processCpuTime.Enabled; | ||
| } | ||
|
|
||
| private static IEnumerable<Measurement<long>> GetGarbageCollectionCounts() | ||
| { | ||
| long collectionsFromHigherGeneration = 0; | ||
|
|
||
| for (int gen = GC.MaxGeneration; gen >= 0; --gen) | ||
| { | ||
| long collectionsFromThisGeneration = GC.CollectionCount(gen); | ||
| yield return new(collectionsFromThisGeneration - collectionsFromHigherGeneration, new KeyValuePair<string, object?>("gc.heap.generation", s_genNames[gen])); | ||
| collectionsFromHigherGeneration = collectionsFromThisGeneration; | ||
| } | ||
| } | ||
|
|
||
| // TODO - Uncomment once an implementation for https://github.com/dotnet/runtime/issues/104844 is available. | ||
| //private static IEnumerable<Measurement<double>> GetCpuTime() | ||
| //{ | ||
| // if (OperatingSystem.IsBrowser() || OperatingSystem.IsTvOS() || OperatingSystem.IsIOS()) | ||
| // yield break; | ||
|
|
||
| // ProcessCpuUsage processCpuUsage = Environment.CpuUsage; | ||
|
|
||
| // yield return new(processCpuUsage.UserTime.TotalSeconds, [new KeyValuePair<string, object?>("cpu.mode", "user")]); | ||
| // yield return new(processCpuUsage.PrivilegedTime.TotalSeconds, [new KeyValuePair<string, object?>("cpu.mode", "system")]); | ||
| //} | ||
|
|
||
| private static IEnumerable<Measurement<long>> GetHeapSizes() | ||
| { | ||
| GCMemoryInfo gcInfo = GC.GetGCMemoryInfo(); | ||
|
|
||
| if (gcInfo.Index == 0) | ||
| yield break; | ||
|
|
||
| for (int i = 0; i < s_maxGenerations; ++i) | ||
| { | ||
| yield return new(gcInfo.GenerationInfo[i].SizeAfterBytes, new KeyValuePair<string, object?>("gc.heap.generation", s_genNames[i])); | ||
| } | ||
| } | ||
|
|
||
| private static IEnumerable<Measurement<long>> GetHeapFragmentation() | ||
| { | ||
| GCMemoryInfo gcInfo = GC.GetGCMemoryInfo(); | ||
|
|
||
| if (gcInfo.Index == 0) | ||
| yield break; | ||
|
|
||
| for (int i = 0; i < s_maxGenerations; ++i) | ||
| { | ||
| yield return new(gcInfo.GenerationInfo[i].FragmentationAfterBytes, new KeyValuePair<string, object?>("gc.heap.generation", s_genNames[i])); | ||
| } | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.