From f91ca746afda893b2a8f7874d26883c59ec63582 Mon Sep 17 00:00:00 2001 From: Chris Ventura <45495992+nrcventura@users.noreply.github.com> Date: Mon, 4 Nov 2024 15:45:48 -0800 Subject: [PATCH] Add support for ObservableCounter instruments. --- .../Core/Samplers/MeterListenerBridge.cs | 65 ++++++++++++++++++- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/src/Agent/NewRelic/Agent/Core/Samplers/MeterListenerBridge.cs b/src/Agent/NewRelic/Agent/Core/Samplers/MeterListenerBridge.cs index 4f75efb69..12e5f2668 100644 --- a/src/Agent/NewRelic/Agent/Core/Samplers/MeterListenerBridge.cs +++ b/src/Agent/NewRelic/Agent/Core/Samplers/MeterListenerBridge.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using System; +using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.Metrics; @@ -25,6 +26,7 @@ public class MeterListenerBridge : IDisposable private static Meter NewRelicBridgeMeter = new Meter("NewRelicOTelBridgeMeter"); private static ConcurrentDictionary _bridgedMeters = new ConcurrentDictionary(); private static ConcurrentDictionary> _createInstrumentDelegates = new ConcurrentDictionary>(); + private static ConcurrentDictionary _bridgeMeasurementDelegates = new ConcurrentDictionary(); private MeterProvider _meterProvider; public MeterListenerBridge() @@ -144,12 +146,30 @@ private static object GetStateForInstrument(object instrument) var instrumentTypeName = instrument.GetType().Name; var genericType = instrument.GetType().GetGenericArguments().FirstOrDefault(); - if (genericType == null || dynamicInstrument.IsObservable) + if (genericType == null) { return null; } - var meter = _bridgedMeters.GetOrAdd(dynamicInstrument.Meter.Name, (Func)CreateBridgedMeterFromInstrument); + Meter meter = _bridgedMeters.GetOrAdd(dynamicInstrument.Meter.Name, (Func)CreateBridgedMeterFromInstrument); + + if (dynamicInstrument.IsObservable) + { + if (instrumentTypeName.Contains("ObservableCounter") && genericType == typeof(long)) + { + // Need to create a function that can call the Observe() method on the instrument (the protected parameterless method) + // and transform the IEnumerable> into the bridged IEnumerable> that the ilrepaced code expects + var observeMethod = instrument.GetType().GetMethod("Observe", BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null); + meter.CreateObservableCounter((string)dynamicInstrument.Name, ForwardObservedMeasurements, (string)dynamicInstrument.Unit, (string)dynamicInstrument.Description); + + IEnumerable> ForwardObservedMeasurements() where T : struct + { + return BridgeMeasurements((IEnumerable)observeMethod.Invoke(instrument, null)); + } + } + return null; + } + var createInstrumentDelegate = _createInstrumentDelegates.GetOrAdd(instrument.GetType(), CreateBridgedInstrumentDelegate); if (createInstrumentDelegate == null) @@ -166,6 +186,47 @@ Meter CreateBridgedMeterFromInstrument(string _) } } + private static IEnumerable> BridgeMeasurements(IEnumerable originalMeasurements) where T : struct + { + var bridgedMeasurements = new List>(); + Func> createBridgedMeasurement = null; + foreach (object measurement in originalMeasurements) + { + // Initializing the delegate to create the bridged measurment within the loop because the type of the original measurements + // is not IEnumerable> but is the concrete collection instead (which may change in the future). + if (createBridgedMeasurement == null) + { + createBridgedMeasurement = (Func>)_bridgeMeasurementDelegates.GetOrAdd(measurement.GetType(), GetMethodToBridgeMeasurement); + } + bridgedMeasurements.Add(createBridgedMeasurement(measurement)); + } + + return bridgedMeasurements; + } + + private static Func> GetMethodToBridgeMeasurement(Type originalMeasurementType) where T : struct + { + var measurementConstructor = typeof(Measurement).GetConstructor([typeof(T), typeof(ReadOnlySpan>)]); + if (measurementConstructor == null) + { + return null; + } + + var originalMeasurementParameter = Expression.Parameter(typeof(object), "originalMeasurement"); + var typedOriginalMeasurement = Expression.Convert(originalMeasurementParameter, originalMeasurementType); + + var valueProperty = originalMeasurementType.GetProperty("Value"); + var tagsProperty = originalMeasurementType.GetProperty("Tags"); + + var valuePropertyAccess = Expression.Property(typedOriginalMeasurement, originalMeasurementType, "Value"); + var tagsPropertyAccess = Expression.Property(typedOriginalMeasurement, originalMeasurementType, "Tags"); + + var newMeasurement = Expression.New(measurementConstructor, valuePropertyAccess, tagsPropertyAccess); + var lambda = Expression.Lambda>>(newMeasurement, originalMeasurementParameter); + + return lambda.Compile(); + } + private static Func CreateBridgedInstrumentDelegate(Type originalInstrumentType) { var genericType = originalInstrumentType.GetGenericArguments().FirstOrDefault();