1111using System . Collections . Concurrent ;
1212using System . Collections . Generic ;
1313using System . Diagnostics . Metrics ;
14+ using System . Runtime . CompilerServices ;
1415using Datadog . Trace . Logging ;
1516
1617namespace Datadog . Trace . OTelMetrics
@@ -19,7 +20,7 @@ internal static class MeterListenerHandler
1920 {
2021 private static readonly IDatadogLogger Log = DatadogLogging . GetLoggerFor ( typeof ( MeterListenerHandler ) ) ;
2122
22- private static readonly ConcurrentDictionary < string , MetricData > CapturedMetrics = new ( ) ;
23+ private static readonly ConcurrentDictionary < string , MetricPoint > CapturedMetrics = new ( ) ;
2324
2425 public static void OnInstrumentPublished ( Instrument instrument , System . Diagnostics . Metrics . MeterListener listener )
2526 {
@@ -48,6 +49,18 @@ public static void OnInstrumentPublished(Instrument instrument, System.Diagnosti
4849 }
4950
5051 public static void OnMeasurementRecordedLong ( Instrument instrument , long value , ReadOnlySpan < KeyValuePair < string , object ? > > tags , object ? state )
52+ {
53+ ProcessMeasurement ( instrument , value , tags ) ;
54+ }
55+
56+ public static void OnMeasurementRecordedDouble ( Instrument instrument , double value , ReadOnlySpan < KeyValuePair < string , object ? > > tags , object ? state )
57+ {
58+ // Reuse the same logic but for double values
59+ ProcessMeasurement ( instrument , value , tags ) ;
60+ }
61+
62+ private static void ProcessMeasurement < T > ( Instrument instrument , T value , ReadOnlySpan < KeyValuePair < string , object ? > > tags )
63+ where T : struct
5164 {
5265 var instrumentType = instrument . GetType ( ) . FullName ;
5366 if ( instrumentType is null )
@@ -56,40 +69,94 @@ public static void OnMeasurementRecordedLong(Instrument instrument, long value,
5669 return ;
5770 }
5871
59- // Only handle Counter<long> for now
60- if ( ! instrumentType . StartsWith ( "System.Diagnostics.Metrics.Counter`1" ) )
72+ // Handle synchronous instruments as per RFC
73+ string aggregationType ;
74+ if ( instrumentType . StartsWith ( "System.Diagnostics.Metrics.Counter`1" ) ||
75+ instrumentType . StartsWith ( "System.Diagnostics.Metrics.UpDownCounter`1" ) )
76+ {
77+ aggregationType = "Counter" ; // Both use Sum Aggregation -> Sum Metric Point
78+ }
79+ else if ( instrumentType . StartsWith ( "System.Diagnostics.Metrics.Gauge`1" ) )
6180 {
62- Log . Debug ( "Skipping non-counter instrument: {InstrumentName} of type: {InstrumentType}" , instrument . Name , instrumentType ) ;
81+ aggregationType = "Gauge" ; // Last Value Aggregation -> Gauge Metric Point
82+ }
83+ else if ( instrumentType . StartsWith ( "System.Diagnostics.Metrics.Histogram`1" ) )
84+ {
85+ aggregationType = "Histogram" ; // Histogram Aggregation -> Histogram Metric Point
86+ }
87+ else
88+ {
89+ Log . Debug ( "Skipping unsupported instrument: {InstrumentName} of type: {InstrumentType}" , instrument . Name , instrumentType ) ;
6390 return ;
6491 }
6592
6693 var tagsDict = new Dictionary < string , object ? > ( tags . Length ) ;
6794 var tagsArray = new string [ tags . Length ] ;
68- for ( int i = 0 ; i < tags . Length ; i ++ )
95+ for ( var i = 0 ; i < tags . Length ; i ++ )
6996 {
7097 var tag = tags [ i ] ;
7198 tagsDict [ tag . Key ] = tag . Value ;
7299 tagsArray [ i ] = tag . Key + "=" + tag . Value ;
73100 }
74101
75- // Create metric data
76- var metricData = new MetricData
77- {
78- InstrumentName = instrument . Name ,
79- MeterName = instrument . Meter . Name ?? "unknown" ,
80- InstrumentType = "Counter" ,
81- Value = value ,
82- Tags = tagsDict ,
83- Timestamp = DateTimeOffset . UtcNow
84- } ;
102+ // High-performance aggregation (like OTel)
103+ var doubleValue = Convert . ToDouble ( value ) ;
104+ var meterName = instrument . Meter . Name ;
105+ var key = $ "{ meterName } .{ instrument . Name } ";
106+ var temporality = Tracer . Instance . Settings . OtlpMetricsTemporalityPreference . ToString ( ) ;
107+
108+ var metricPoint = CapturedMetrics . AddOrUpdate (
109+ key ,
110+ // Create new MetricPoint
111+ _ => new MetricPoint ( instrument . Name , meterName , aggregationType , temporality , tagsDict ) ,
112+ // Update existing MetricPoint (lock-free where possible)
113+ ( _ , existing ) =>
114+ {
115+ UpdateMetricPoint ( existing , aggregationType , doubleValue ) ;
116+ return existing ;
117+ } ) ;
85118
86- var key = $ "{ metricData . MeterName } .{ metricData . InstrumentName } ";
87- CapturedMetrics . AddOrUpdate ( key , metricData , ( k , existing ) => metricData ) ;
119+ // For new MetricPoints, record the first measurement
120+ if ( metricPoint is { SnapshotCount : 0 , SnapshotSum : 0 , SnapshotGaugeValue : 0 } )
121+ {
122+ UpdateMetricPoint ( metricPoint , aggregationType , doubleValue ) ;
123+ }
88124
89- Log . Debug ( "Captured counter measurement: {InstrumentName} = {Value}" , instrument . Name , value ) ;
125+ Log . Debug ( "Captured {InstrumentType} measurement: {InstrumentName} = {Value}" , aggregationType , instrument . Name , value ) ;
90126
91127 var tagsString = string . Join ( "," , tagsArray ) ;
92- Console . WriteLine ( $ "[METRICS_CAPTURE] { key } |{ metricData . InstrumentType } |{ value } |{ tagsString } ") ;
128+
129+ // Take snapshot for display (like OTel export)
130+ metricPoint . TakeSnapshot ( outputDelta : false ) ;
131+
132+ // Output the appropriate aggregated value for console testing
133+ var displayValue = aggregationType switch
134+ {
135+ "Counter" => metricPoint . SnapshotSum . ToString ( System . Globalization . CultureInfo . InvariantCulture ) ,
136+ "Gauge" => metricPoint . SnapshotGaugeValue . ToString ( System . Globalization . CultureInfo . InvariantCulture ) ,
137+ "Histogram" => $ "count={ metricPoint . SnapshotCount . ToString ( System . Globalization . CultureInfo . InvariantCulture ) } ,sum={ metricPoint . SnapshotSum . ToString ( System . Globalization . CultureInfo . InvariantCulture ) } ",
138+ _ => value . ToString ( )
139+ } ;
140+
141+ Console . WriteLine ( $ "[METRICS_CAPTURE] { key } |{ metricPoint . InstrumentType } |{ displayValue } |{ tagsString } ") ;
142+ }
143+
144+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
145+ private static void UpdateMetricPoint ( MetricPoint metricPoint , string aggregationType , double value )
146+ {
147+ // High-performance updates (like OTel)
148+ switch ( aggregationType )
149+ {
150+ case "Counter" :
151+ metricPoint . UpdateCounter ( value ) ;
152+ break ;
153+ case "Gauge" :
154+ metricPoint . UpdateGauge ( value ) ;
155+ break ;
156+ case "Histogram" :
157+ metricPoint . UpdateHistogram ( value ) ;
158+ break ;
159+ }
93160 }
94161 }
95162}
0 commit comments