- 
                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 10 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 | ||
|         
                  stevejgordon marked this conversation as resolved.
              Show resolved
            Hide resolved | ||
| { | ||
| [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])); | ||
| } | ||
| } | ||
| } | ||
| } | ||
      
      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.