diff --git a/src/Agent/NewRelic/Agent/Core/AgentHealth/AgentHealthReporter.cs b/src/Agent/NewRelic/Agent/Core/AgentHealth/AgentHealthReporter.cs index 15dc94c24..2d06016df 100644 --- a/src/Agent/NewRelic/Agent/Core/AgentHealth/AgentHealthReporter.cs +++ b/src/Agent/NewRelic/Agent/Core/AgentHealth/AgentHealthReporter.cs @@ -139,6 +139,11 @@ public void ReportLibraryVersion(string assemblyName, string assemblyVersion) TrySend(_metricBuilder.TryBuildLibraryVersionMetric(assemblyName, assemblyVersion)); } + public void ReportCustomInstrumentation(string assemblyName, string className, string method) + { + TrySend(_metricBuilder.TryBuildCustomInstrumentationMetric(assemblyName, className, method)); + } + #region TransactionEvents public void ReportTransactionEventReservoirResized(int newSize) diff --git a/src/Agent/NewRelic/Agent/Core/AgentHealth/IAgentHealthReporter.cs b/src/Agent/NewRelic/Agent/Core/AgentHealth/IAgentHealthReporter.cs index 23eed86cf..145a7f2fa 100644 --- a/src/Agent/NewRelic/Agent/Core/AgentHealth/IAgentHealthReporter.cs +++ b/src/Agent/NewRelic/Agent/Core/AgentHealth/IAgentHealthReporter.cs @@ -1,7 +1,6 @@ // Copyright 2020 New Relic, Inc. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -using Grpc.Core; using NewRelic.Agent.Core.SharedInterfaces; using NewRelic.Agent.Core.Transformers.TransactionTransformer; using NewRelic.Agent.Extensions.Providers.Wrapper; @@ -18,6 +17,8 @@ public interface IAgentHealthReporter : IOutOfBandMetricSource void ReportLibraryVersion(string assemblyName, string assemblyVersion); + void ReportCustomInstrumentation(string assemblyName, string className, string method); + void ReportTransactionEventReservoirResized(int newSize); void ReportTransactionEventCollected(); diff --git a/src/Agent/NewRelic/Agent/Core/Metrics/MetricNames.cs b/src/Agent/NewRelic/Agent/Core/Metrics/MetricNames.cs index 43851cf6f..3ad1f7736 100644 --- a/src/Agent/NewRelic/Agent/Core/Metrics/MetricNames.cs +++ b/src/Agent/NewRelic/Agent/Core/Metrics/MetricNames.cs @@ -628,6 +628,12 @@ public static string GetSupportabilityLibraryVersion(string assemblyName, string return SupportabilityLibraryVersionPs + assemblyName + PathSeparator + assemblyVersion; } + private const string SupportabilityCustomInstrumentationPs = SupportabilityPs + "CustomInst" + PathSeparator; + public static string GetSupportabilityCustomInstrumentation(string assemblyName, string className, string method) + { + return SupportabilityCustomInstrumentationPs + assemblyName + PathSeparator + className + PathSeparator + method; + } + // Utilization private const string SupportabilityUtilizationPs = SupportabilityPs + "utilization" + PathSeparator; private const string SupportabilityUtilizationBootIdError = SupportabilityUtilizationPs + "boot_id" + PathSeparator + "error"; diff --git a/src/Agent/NewRelic/Agent/Core/WireModels/IMetricBuilder.cs b/src/Agent/NewRelic/Agent/Core/WireModels/IMetricBuilder.cs index 8a5536592..82a7dcf39 100644 --- a/src/Agent/NewRelic/Agent/Core/WireModels/IMetricBuilder.cs +++ b/src/Agent/NewRelic/Agent/Core/WireModels/IMetricBuilder.cs @@ -49,6 +49,8 @@ public interface IMetricBuilder MetricWireModel TryBuildLibraryVersionMetric(string assemblyName, string assemblyVersion); + MetricWireModel TryBuildCustomInstrumentationMetric(string assemblyName, string className, string method); + MetricWireModel TryBuildMetricHarvestAttemptMetric(); MetricWireModel TryBuildTransactionEventReservoirResizedMetric(); diff --git a/src/Agent/NewRelic/Agent/Core/WireModels/MetricWireModel.cs b/src/Agent/NewRelic/Agent/Core/WireModels/MetricWireModel.cs index 3507527b8..68fe7f1ad 100644 --- a/src/Agent/NewRelic/Agent/Core/WireModels/MetricWireModel.cs +++ b/src/Agent/NewRelic/Agent/Core/WireModels/MetricWireModel.cs @@ -542,7 +542,12 @@ public MetricWireModel TryBuildLibraryVersionMetric(string assemblyName, string var data = MetricDataWireModel.BuildCountData(); return BuildMetric(_metricNameService, proposedName, null, data); } - + public MetricWireModel TryBuildCustomInstrumentationMetric(string assemblyName, string className, string method) + { + var proposedName = MetricNames.GetSupportabilityCustomInstrumentation(assemblyName, className, method); + var data = MetricDataWireModel.BuildCountData(); + return BuildMetric(_metricNameService, proposedName, null, data); + } public MetricWireModel TryBuildMetricHarvestAttemptMetric() { const string proposedName = MetricNames.SupportabilityMetricHarvestTransmit; diff --git a/src/Agent/NewRelic/Agent/Core/Wrapper/WrapperService.cs b/src/Agent/NewRelic/Agent/Core/Wrapper/WrapperService.cs index 0bff391d5..052f1e8fa 100644 --- a/src/Agent/NewRelic/Agent/Core/Wrapper/WrapperService.cs +++ b/src/Agent/NewRelic/Agent/Core/Wrapper/WrapperService.cs @@ -12,6 +12,7 @@ using NewRelic.Agent.Api; using System.Reflection; using System.Threading.Tasks; +using System.Collections.Generic; namespace NewRelic.Agent.Core.Wrapper { @@ -43,6 +44,14 @@ public InstrumentedMethodInfoWrapper(InstrumentedMethodInfo instrumentedMethodIn } private readonly ConcurrentDictionary _functionIdToWrapper; + private readonly List KnownCustomTracerNames = new List + { + "NewRelic.Agent.Core.Tracer.Factories.BackgroundThreadTracerFactory", + "NewRelic.Agent.Core.Tracer.Factories.IgnoreTransactionTracerFactory", + "NewRelic.Providers.Wrapper.CustomInstrumentation.OtherTransactionWrapper", + "AsyncForceNewTransactionWrapper", + "OtherTransactionWrapper" + }; public WrapperService(IConfigurationService configurationService, IWrapperMap wrapperMap, IAgent agent, IAgentHealthReporter agentHealthReporter, IAgentTimerService agentTimerService) { @@ -68,9 +77,14 @@ public AfterWrappedMethodDelegate BeforeWrappedMethod(Type type, string methodNa } else { + bool isCustom = KnownCustomTracerNames.Contains(tracerFactoryName); var isAsync = TracerArgument.IsAsync(tracerArguments); - tracerFactoryName = ResolveTracerFactoryNameForAttributeInstrumentation(tracerArguments, isAsync, tracerFactoryName); + if (TracerArgument.IsFlagSet(tracerArguments, TracerFlags.AttributeInstrumentation)) + { + tracerFactoryName = ResolveTracerFactoryNameForAttributeInstrumentation(tracerArguments, isAsync, tracerFactoryName); + isCustom = true; + } var method = new Method(type, methodName, argumentSignature, functionId.GetHashCode()); var transactionNamePriority = TracerArgument.GetTransactionNamingPriority(tracerArguments); @@ -108,7 +122,7 @@ public AfterWrappedMethodDelegate BeforeWrappedMethod(Type type, string methodNa } _functionIdToWrapper[functionId] = new InstrumentedMethodInfoWrapper(instrumentedMethodInfo, trackedWrapper); - GenerateLibraryVersionSupportabilityMetric(instrumentedMethodInfo); + GenerateSupportabilityMetrics(instrumentedMethodInfo, isCustom); } var wrapper = trackedWrapper.Wrapper; @@ -228,7 +242,7 @@ private void HandleBeforeWrappedMethodException(ulong functionId, TrackedWrapper } } - private void GenerateLibraryVersionSupportabilityMetric(InstrumentedMethodInfo instrumentedMethodInfo) + private void GenerateSupportabilityMetrics(InstrumentedMethodInfo instrumentedMethodInfo, bool isCustom) { try { @@ -237,10 +251,16 @@ private void GenerateLibraryVersionSupportabilityMetric(InstrumentedMethodInfo i var assemblyVersion = reflectionAssemblyName.Version.ToString(); _agentHealthReporter.ReportLibraryVersion(assemblyName, assemblyVersion); + if (isCustom) + { + string method = instrumentedMethodInfo.Method.MethodName; + string className = instrumentedMethodInfo.Method.Type.FullName; + _agentHealthReporter.ReportCustomInstrumentation(assemblyName, className, method); + } } catch (Exception ex) { - Log.Error($"Failed to generate Library version Supportability Metric for {instrumentedMethodInfo.ToString()} : exception: {ex}"); + Log.Error($"Failed to generate Supportability Metrics for {instrumentedMethodInfo.ToString()} : exception: {ex}"); } } } diff --git a/tests/Agent/UnitTests/CompositeTests/WrapperServiceTests.cs b/tests/Agent/UnitTests/CompositeTests/WrapperServiceTests.cs index 4cbaaae84..cdb479114 100644 --- a/tests/Agent/UnitTests/CompositeTests/WrapperServiceTests.cs +++ b/tests/Agent/UnitTests/CompositeTests/WrapperServiceTests.cs @@ -13,6 +13,7 @@ public class WrapperServiceTests { private static CompositeTestAgent _compositeTestAgent; private IAgent _agent; + private const uint AttributeInstrumentation = 1 << 20; [SetUp] public void SetUp() @@ -43,7 +44,7 @@ public void BeforeWrappedMethod_ReturnsNoOp_IfTheRequiredTransactionIsFinished() using (var logging = new Logging()) { var wrapperService = _compositeTestAgent.GetWrapperService(); - var afterWrappedMethod = wrapperService.BeforeWrappedMethod(type, methodName, string.Empty, target, arguments, tracerFactoryName, null, 0, 0); + var afterWrappedMethod = wrapperService.BeforeWrappedMethod(type, methodName, string.Empty, target, arguments, tracerFactoryName, null, AttributeInstrumentation, 0); Assert.Multiple(() => { diff --git a/tests/Agent/UnitTests/Core.UnitTest/Wrapper/WrapperService.cs b/tests/Agent/UnitTests/Core.UnitTest/Wrapper/WrapperService.cs index f5bc7af5f..8a810ec68 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/Wrapper/WrapperService.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/Wrapper/WrapperService.cs @@ -19,6 +19,7 @@ public class Class_WrapperService { private const uint EmptyTracerArgs = 0; private const uint AsyncTracerArgs = 1 << 23; + private const uint AttributeInstrumentation = 1 << 20; private WrapperService _wrapperService; @@ -68,13 +69,15 @@ public void BeforeWrappedMethod_PassesCorrectParametersToWrapperLoader() const string tracerFactoryName = "MyTracer"; var target = new object(); var arguments = new object[0]; - _wrapperService.BeforeWrappedMethod(type, methodName, string.Empty, target, arguments, tracerFactoryName, null, EmptyTracerArgs, 0); + _wrapperService.BeforeWrappedMethod(type, methodName, string.Empty, target, arguments, tracerFactoryName, null, AttributeInstrumentation, 0); var method = new Method(type, methodName, string.Empty); var expectedMethodCall = new MethodCall(method, target, arguments, false); var instrumetedMethodInfo = new InstrumentedMethodInfo(0, expectedMethodCall.Method, tracerFactoryName, false, null, null, false); Mock.Assert(() => _wrapperMap.Get(instrumetedMethodInfo)); + Mock.Assert(() => _agentHealthReporter.ReportLibraryVersion(Arg.IsAny(), Arg.IsAny()), Occurs.Once()); + Mock.Assert(() => _agentHealthReporter.ReportCustomInstrumentation(Arg.IsAny(), Arg.IsAny(), Arg.IsAny()), Occurs.Once()); } [Test]