From ec5de822083a40fb58341ed230d8d8f554bdf575 Mon Sep 17 00:00:00 2001 From: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Thu, 31 Oct 2024 16:46:26 -0500 Subject: [PATCH 01/10] WIP - instrumentation for Azure Service Bus. --- FullAgent.sln | 12 +- .../AzureServiceBus/AzureServiceBus.csproj | 22 ++++ .../AzureServiceBusReceiveWrapper.cs | 117 ++++++++++++++++++ .../AzureServiceBusSendWrapper.cs | 66 ++++++++++ .../AzureServiceBus/Instrumentation.xml | 31 +++++ 5 files changed, 246 insertions(+), 2 deletions(-) create mode 100644 src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBus.csproj create mode 100644 src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusReceiveWrapper.cs create mode 100644 src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusSendWrapper.cs create mode 100644 src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/Instrumentation.xml diff --git a/FullAgent.sln b/FullAgent.sln index 45d2bc0a90..d0e2164e47 100644 --- a/FullAgent.sln +++ b/FullAgent.sln @@ -152,6 +152,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Home", "src\Agent\NewRelic\ {338AD83A-ED68-438A-8FB1-E93A3AE87EA8} = {338AD83A-ED68-438A-8FB1-E93A3AE87EA8} {37262C22-6A3A-4AD7-AB78-3853D2B2931D} = {37262C22-6A3A-4AD7-AB78-3853D2B2931D} {3D69B4C9-FD16-461F-95AF-6FCA6EAA914E} = {3D69B4C9-FD16-461F-95AF-6FCA6EAA914E} + {4078E594-E738-48F7-A7ED-B208ADD04900} = {4078E594-E738-48F7-A7ED-B208ADD04900} {44434B8F-EE14-49B0-855D-6EA0B48048BF} = {44434B8F-EE14-49B0-855D-6EA0B48048BF} {4F5D77F3-B41A-44A7-AF10-2D5462CE0162} = {4F5D77F3-B41A-44A7-AF10-2D5462CE0162} {570429FD-C785-4673-82DF-643D06B6DC53} = {570429FD-C785-4673-82DF-643D06B6DC53} @@ -219,7 +220,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzureFunction", "src\Agent\ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PublicApiChangeTests", "tests\Agent\UnitTests\PublicApiChangeTests\PublicApiChangeTests.csproj", "{A8F6EFEA-1C31-4461-A7B4-25C30D954EE2}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Memcached", "src\Agent\NewRelic\Agent\Extensions\Providers\Wrapper\Memcached\Memcached.csproj", "{5D74E5C5-9BA3-423B-86F7-14C2D1A14661}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Memcached", "src\Agent\NewRelic\Agent\Extensions\Providers\Wrapper\Memcached\Memcached.csproj", "{5D74E5C5-9BA3-423B-86F7-14C2D1A14661}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AzureServiceBus", "src\Agent\NewRelic\Agent\Extensions\Providers\Wrapper\AzureServiceBus\AzureServiceBus.csproj", "{4078E594-E738-48F7-A7ED-B208ADD04900}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -467,6 +470,10 @@ Global {5D74E5C5-9BA3-423B-86F7-14C2D1A14661}.Debug|Any CPU.Build.0 = Debug|Any CPU {5D74E5C5-9BA3-423B-86F7-14C2D1A14661}.Release|Any CPU.ActiveCfg = Release|Any CPU {5D74E5C5-9BA3-423B-86F7-14C2D1A14661}.Release|Any CPU.Build.0 = Release|Any CPU + {4078E594-E738-48F7-A7ED-B208ADD04900}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4078E594-E738-48F7-A7ED-B208ADD04900}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4078E594-E738-48F7-A7ED-B208ADD04900}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4078E594-E738-48F7-A7ED-B208ADD04900}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -538,10 +545,11 @@ Global {338AD83A-ED68-438A-8FB1-E93A3AE87EA8} = {5E86E10A-C38F-48CB-ADE9-67B22BB2F50A} {A8F6EFEA-1C31-4461-A7B4-25C30D954EE2} = {E5B988C0-5D19-407E-8210-71FFB90C579A} {5D74E5C5-9BA3-423B-86F7-14C2D1A14661} = {5E86E10A-C38F-48CB-ADE9-67B22BB2F50A} + {4078E594-E738-48F7-A7ED-B208ADD04900} = {5E86E10A-C38F-48CB-ADE9-67B22BB2F50A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - EnterpriseLibraryConfigurationToolBinariesPath = packages\Unity.2.1.505.2\lib\NET35 SolutionGuid = {D8B98070-6B8E-403C-A07F-A3F2E4A3A3D0} + EnterpriseLibraryConfigurationToolBinariesPath = packages\Unity.2.1.505.2\lib\NET35 EndGlobalSection GlobalSection(TestCaseManagementSettings) = postSolution CategoryFile = FullAgent.vsmdi diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBus.csproj b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBus.csproj new file mode 100644 index 0000000000..9e5be87e32 --- /dev/null +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBus.csproj @@ -0,0 +1,22 @@ + + + net462;netstandard2.0 + NewRelic.Providers.Wrapper.AzureServiceBus + NewRelic.Providers.Wrapper.AzureServiceBus + Azure Service Bus Wrapper Provider for New Relic .NET Agent + + + + + PreserveNewest + + + + + + + + + + + diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusReceiveWrapper.cs b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusReceiveWrapper.cs new file mode 100644 index 0000000000..94956c0bb9 --- /dev/null +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusReceiveWrapper.cs @@ -0,0 +1,117 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Threading.Tasks; +using NewRelic.Agent.Api; +using NewRelic.Agent.Extensions.Providers.Wrapper; +using NewRelic.Reflection; + +namespace NewRelic.Providers.Wrapper.AzureServiceBus; + +public class AzureServiceBusReceiveWrapper : IWrapper +{ + private const string BrokerVendorName = "AzureServiceBus"; + private static ConcurrentDictionary> _getResultFromGenericTask = new(); + + public bool IsTransactionRequired => false; + + public CanWrapResponse CanWrap(InstrumentedMethodInfo instrumentedMethodInfo) + { + var canWrap = instrumentedMethodInfo.RequestedWrapperName.Equals(nameof(AzureServiceBusReceiveWrapper)); + return new CanWrapResponse(canWrap); + } + + public AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall instrumentedMethodCall, IAgent agent, ITransaction transaction) + { + dynamic serviceBusReceiver = instrumentedMethodCall.MethodCall.InvocationTarget; + string queueName = serviceBusReceiver.EntityPath; + string identifier = serviceBusReceiver.Identifier; + string fqns = serviceBusReceiver.FullyQualifiedNamespace; + + // create a transaction + transaction = agent.CreateTransaction( + destinationType: MessageBrokerDestinationType.Queue, + brokerVendorName: BrokerVendorName, + destination: queueName); + + if (instrumentedMethodCall.IsAsync) + { + transaction.AttachToAsync(); + transaction.DetachFromPrimary(); //Remove from thread-local type storage + } + + // start a message broker segment + var segment = transaction.StartMessageBrokerSegment( + instrumentedMethodCall.MethodCall, + MessageBrokerDestinationType.Queue, + MessageBrokerAction.Consume, + BrokerVendorName, queueName); + + // return an async delegate + return Delegates.GetAsyncDelegateFor( + agent, + segment, + true, + HandleResponse, + TaskContinuationOptions.ExecuteSynchronously); + + void HandleResponse(Task responseTask) + { + try + { + if (responseTask.IsFaulted) + { + // TODO: handle error here? + return; + } + + // get the first message from the response and extract DT headers + // per https://github.com/Azure/azure-sdk-for-net/issues/33652#issuecomment-1451320679 + // the headers are in the ApplicationProperties dictionary + dynamic resultObj = GetTaskResult(responseTask); + if (resultObj != null && resultObj.Count > 0) + { + var msg = resultObj[0]; + if (msg.ApplicationProperties is ReadOnlyDictionary applicationProperties) + { + transaction.AcceptDistributedTraceHeaders(applicationProperties, ProcessHeaders, TransportType.Queue); + } + } + } + finally + { + segment.End(); + transaction.End(); + } + } + } + + private IEnumerable ProcessHeaders(ReadOnlyDictionary applicationProperties, string key) + { + var headerValues = new List(); + foreach (var item in applicationProperties) + { + if (item.Key.Equals(key, StringComparison.OrdinalIgnoreCase)) + { + headerValues.Add(item.Value as string); + } + } + + return headerValues; + } + + private static object GetTaskResult(object task) + { + if (((Task)task).IsFaulted) + { + return null; + } + + var getResponse = _getResultFromGenericTask.GetOrAdd(task.GetType(), t => VisibilityBypasser.Instance.GeneratePropertyAccessor(t, "Result")); + return getResponse(task); + } +} diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusSendWrapper.cs b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusSendWrapper.cs new file mode 100644 index 0000000000..bebebc4350 --- /dev/null +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusSendWrapper.cs @@ -0,0 +1,66 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Threading.Tasks; +using NewRelic.Agent.Api; +using NewRelic.Agent.Extensions.Providers.Wrapper; +using NewRelic.Reflection; + +namespace NewRelic.Providers.Wrapper.AzureServiceBus; + +public class AzureServiceBusSendWrapper : IWrapper +{ + private const string BrokerVendorName = "AzureServiceBus"; + private static ConcurrentDictionary> _getResultFromGenericTask = new(); + + public bool IsTransactionRequired => true; + + public CanWrapResponse CanWrap(InstrumentedMethodInfo instrumentedMethodInfo) + { + var canWrap = instrumentedMethodInfo.RequestedWrapperName.Equals(nameof(AzureServiceBusSendWrapper)); + return new CanWrapResponse(canWrap); + } + + public AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall instrumentedMethodCall, IAgent agent, ITransaction transaction) + { + dynamic serviceBusReceiver = instrumentedMethodCall.MethodCall.InvocationTarget; + string queueName = serviceBusReceiver.EntityPath; + string identifier = serviceBusReceiver.Identifier; + string fqns = serviceBusReceiver.FullyQualifiedNamespace; + + // ??? + if (instrumentedMethodCall.IsAsync) + { + transaction.AttachToAsync(); + transaction.DetachFromPrimary(); //Remove from thread-local type storage + } + + // start a message broker segment + var segment = transaction.StartMessageBrokerSegment( + instrumentedMethodCall.MethodCall, + MessageBrokerDestinationType.Queue, + MessageBrokerAction.Consume, + BrokerVendorName, queueName); + + dynamic messages = instrumentedMethodCall.MethodCall.MethodArguments[0]; + // iterate all messages that are being sent, + // insert DT headers into each message + foreach (var message in messages) + { + if (message.ApplicationProperties is IDictionary applicationProperties) + transaction.InsertDistributedTraceHeaders(applicationProperties, ProcessHeaders); + } + + // return an async delegate + return Delegates.GetAsyncDelegateFor(agent,segment); + } + + private void ProcessHeaders(IDictionary applicationProperties, string key, string value) + { + applicationProperties.Add(key, value); + } +} diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/Instrumentation.xml b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/Instrumentation.xml new file mode 100644 index 0000000000..aea99e3713 --- /dev/null +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/Instrumentation.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + From 9393edaae771c69c4c5c66a861f1a722115d393c Mon Sep 17 00:00:00 2001 From: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Tue, 5 Nov 2024 15:46:40 -0600 Subject: [PATCH 02/10] Adding instrumentation for service bus messages --- .../AzureServiceBusReceiveWrapper.cs | 21 ++++- .../AzureServiceBusSendWrapper.cs | 28 +++++-- .../AzureServiceBus/Instrumentation.xml | 80 +++++++++++++++++++ .../Wrapper/AzureServiceBus/README.md | 2 + 4 files changed, 119 insertions(+), 12 deletions(-) create mode 100644 src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/README.md diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusReceiveWrapper.cs b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusReceiveWrapper.cs index 94956c0bb9..0ef7009b6e 100644 --- a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusReceiveWrapper.cs +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusReceiveWrapper.cs @@ -44,11 +44,25 @@ public AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall ins transaction.DetachFromPrimary(); //Remove from thread-local type storage } + MessageBrokerAction action = + instrumentedMethodCall.MethodCall.Method.MethodName switch + { + "ReceiveMessagesAsync" => MessageBrokerAction.Consume, + "ReceiveDeferredMessagesAsync" => MessageBrokerAction.Consume, + "PeekMessagesInternalAsync" => MessageBrokerAction.Peek, + "AbandonMessageAsync" => MessageBrokerAction.Purge, // TODO is this correct ???, + "CompleteMessageAsync" => MessageBrokerAction.Consume, + "DeadLetterInternalAsync" => MessageBrokerAction.Purge, // TODO is this correct ??? + "DeferMessageAsync" => MessageBrokerAction.Consume, // TODO is this correct or should we extend MessageBrokerAction with more values??? + "RenewMessageLockAsync" => MessageBrokerAction.Consume, // TODO is this correct or should we extend MessageBrokerAction with more values??? + _ => throw new ArgumentOutOfRangeException(nameof(action), $"Unexpected method call: {instrumentedMethodCall.MethodCall.Method.MethodName}") + }; + // start a message broker segment var segment = transaction.StartMessageBrokerSegment( instrumentedMethodCall.MethodCall, MessageBrokerDestinationType.Queue, - MessageBrokerAction.Consume, + action, BrokerVendorName, queueName); // return an async delegate @@ -69,11 +83,10 @@ void HandleResponse(Task responseTask) return; } + // if the response contains a list of messages, // get the first message from the response and extract DT headers - // per https://github.com/Azure/azure-sdk-for-net/issues/33652#issuecomment-1451320679 - // the headers are in the ApplicationProperties dictionary dynamic resultObj = GetTaskResult(responseTask); - if (resultObj != null && resultObj.Count > 0) + if (resultObj != null && resultObj.Count > 0) // TODO need to verify resultObj is of type IReadOnlyList { var msg = resultObj[0]; if (msg.ApplicationProperties is ReadOnlyDictionary applicationProperties) diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusSendWrapper.cs b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusSendWrapper.cs index bebebc4350..f55d1b49cf 100644 --- a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusSendWrapper.cs +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusSendWrapper.cs @@ -39,24 +39,36 @@ public AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall ins transaction.DetachFromPrimary(); //Remove from thread-local type storage } + MessageBrokerAction action = + instrumentedMethodCall.MethodCall.Method.MethodName switch + { + "SendMessagesAsync" => MessageBrokerAction.Produce, + "ScheduleMessagesAsync" => MessageBrokerAction.Produce, + "CancelScheduledMessagesAsync" => MessageBrokerAction.Purge, // TODO is this correct ???, + _ => throw new ArgumentOutOfRangeException(nameof(action), $"Unexpected method call: {instrumentedMethodCall.MethodCall.Method.MethodName}") + }; + // start a message broker segment var segment = transaction.StartMessageBrokerSegment( instrumentedMethodCall.MethodCall, MessageBrokerDestinationType.Queue, - MessageBrokerAction.Consume, + action, BrokerVendorName, queueName); - dynamic messages = instrumentedMethodCall.MethodCall.MethodArguments[0]; - // iterate all messages that are being sent, - // insert DT headers into each message - foreach (var message in messages) + if (action == MessageBrokerAction.Produce) { - if (message.ApplicationProperties is IDictionary applicationProperties) - transaction.InsertDistributedTraceHeaders(applicationProperties, ProcessHeaders); + dynamic messages = instrumentedMethodCall.MethodCall.MethodArguments[0]; + // iterate all messages that are being sent, + // insert DT headers into each message + foreach (var message in messages) + { + if (message.ApplicationProperties is IDictionary applicationProperties) + transaction.InsertDistributedTraceHeaders(applicationProperties, ProcessHeaders); + } } // return an async delegate - return Delegates.GetAsyncDelegateFor(agent,segment); + return Delegates.GetAsyncDelegateFor(agent, segment); } private void ProcessHeaders(IDictionary applicationProperties, string key, string value) diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/Instrumentation.xml b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/Instrumentation.xml index aea99e3713..c1ea3db422 100644 --- a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/Instrumentation.xml +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/Instrumentation.xml @@ -17,7 +17,71 @@ SPDX-License-Identifier: Apache-2.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/README.md b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/README.md new file mode 100644 index 0000000000..c31da8e241 --- /dev/null +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/README.md @@ -0,0 +1,2 @@ + +Based largely on https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/servicebus/Azure.Messaging.ServiceBus/TROUBLESHOOTING.md#distributed-tracing From 49a7159d98871fa834bc20d42f2d1e0f81bd5862 Mon Sep 17 00:00:00 2001 From: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Fri, 8 Nov 2024 11:05:42 -0600 Subject: [PATCH 03/10] Cleanup --- .../AzureServiceBusReceiveWrapper.cs | 157 ++++++++++-------- .../AzureServiceBusSendWrapper.cs | 39 ++--- .../AzureServiceBusWrapperBase.cs | 19 +++ 3 files changed, 117 insertions(+), 98 deletions(-) create mode 100644 src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusWrapperBase.cs diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusReceiveWrapper.cs b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusReceiveWrapper.cs index 0ef7009b6e..6ebcf4bd1e 100644 --- a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusReceiveWrapper.cs +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusReceiveWrapper.cs @@ -12,51 +12,36 @@ namespace NewRelic.Providers.Wrapper.AzureServiceBus; -public class AzureServiceBusReceiveWrapper : IWrapper +public class AzureServiceBusReceiveWrapper : AzureServiceBusWrapperBase { - private const string BrokerVendorName = "AzureServiceBus"; - private static ConcurrentDictionary> _getResultFromGenericTask = new(); + private static readonly ConcurrentDictionary> _getResultFromGenericTask = new(); - public bool IsTransactionRequired => false; - - public CanWrapResponse CanWrap(InstrumentedMethodInfo instrumentedMethodInfo) + public override CanWrapResponse CanWrap(InstrumentedMethodInfo instrumentedMethodInfo) { var canWrap = instrumentedMethodInfo.RequestedWrapperName.Equals(nameof(AzureServiceBusReceiveWrapper)); return new CanWrapResponse(canWrap); } - public AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall instrumentedMethodCall, IAgent agent, ITransaction transaction) + public override AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall instrumentedMethodCall, IAgent agent, ITransaction transaction) { dynamic serviceBusReceiver = instrumentedMethodCall.MethodCall.InvocationTarget; - string queueName = serviceBusReceiver.EntityPath; - string identifier = serviceBusReceiver.Identifier; - string fqns = serviceBusReceiver.FullyQualifiedNamespace; - - // create a transaction - transaction = agent.CreateTransaction( - destinationType: MessageBrokerDestinationType.Queue, - brokerVendorName: BrokerVendorName, - destination: queueName); - - if (instrumentedMethodCall.IsAsync) - { - transaction.AttachToAsync(); - transaction.DetachFromPrimary(); //Remove from thread-local type storage - } + string queueName = serviceBusReceiver.EntityPath; // marty-test-queue + //string identifier = serviceBusReceiver.Identifier; // -9e860ed4-b16b-4d02-96e4-d8ed224ae24b + //string fqns = serviceBusReceiver.FullyQualifiedNamespace; // mt-test-servicebus.servicebus.windows.net MessageBrokerAction action = instrumentedMethodCall.MethodCall.Method.MethodName switch - { - "ReceiveMessagesAsync" => MessageBrokerAction.Consume, - "ReceiveDeferredMessagesAsync" => MessageBrokerAction.Consume, - "PeekMessagesInternalAsync" => MessageBrokerAction.Peek, - "AbandonMessageAsync" => MessageBrokerAction.Purge, // TODO is this correct ???, - "CompleteMessageAsync" => MessageBrokerAction.Consume, - "DeadLetterInternalAsync" => MessageBrokerAction.Purge, // TODO is this correct ??? - "DeferMessageAsync" => MessageBrokerAction.Consume, // TODO is this correct or should we extend MessageBrokerAction with more values??? - "RenewMessageLockAsync" => MessageBrokerAction.Consume, // TODO is this correct or should we extend MessageBrokerAction with more values??? - _ => throw new ArgumentOutOfRangeException(nameof(action), $"Unexpected method call: {instrumentedMethodCall.MethodCall.Method.MethodName}") - }; + { + "ReceiveMessagesAsync" => MessageBrokerAction.Consume, + "ReceiveDeferredMessagesAsync" => MessageBrokerAction.Consume, + "PeekMessagesInternalAsync" => MessageBrokerAction.Peek, + "AbandonMessageAsync" => MessageBrokerAction.Purge, // TODO is this correct ???, + "CompleteMessageAsync" => MessageBrokerAction.Consume, + "DeadLetterInternalAsync" => MessageBrokerAction.Purge, // TODO is this correct ??? + "DeferMessageAsync" => MessageBrokerAction.Consume, // TODO is this correct or should we extend MessageBrokerAction with more values??? + "RenewMessageLockAsync" => MessageBrokerAction.Consume, // TODO is this correct or should we extend MessageBrokerAction with more values??? + _ => throw new ArgumentOutOfRangeException(nameof(action), $"Unexpected instrumented method call: {instrumentedMethodCall.MethodCall.Method.MethodName}") + }; // start a message broker segment var segment = transaction.StartMessageBrokerSegment( @@ -65,61 +50,89 @@ public AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall ins action, BrokerVendorName, queueName); - // return an async delegate - return Delegates.GetAsyncDelegateFor( - agent, - segment, - true, - HandleResponse, - TaskContinuationOptions.ExecuteSynchronously); - - void HandleResponse(Task responseTask) + if (instrumentedMethodCall.IsAsync) { - try + // return an async delegate + return Delegates.GetAsyncDelegateFor( + agent, + segment, + false, + HandleResponse, + TaskContinuationOptions.ExecuteSynchronously); + + void HandleResponse(Task responseTask) { - if (responseTask.IsFaulted) + try { - // TODO: handle error here? - return; - } - - // if the response contains a list of messages, - // get the first message from the response and extract DT headers - dynamic resultObj = GetTaskResult(responseTask); - if (resultObj != null && resultObj.Count > 0) // TODO need to verify resultObj is of type IReadOnlyList - { - var msg = resultObj[0]; - if (msg.ApplicationProperties is ReadOnlyDictionary applicationProperties) + if (responseTask.IsFaulted) { - transaction.AcceptDistributedTraceHeaders(applicationProperties, ProcessHeaders, TransportType.Queue); + transaction.NoticeError(responseTask.Exception); // TODO ??? + return; } + + var resultObj = GetTaskResultFromObject(responseTask); + ExtractDTHeadersIfAvailable(resultObj); + } + finally + { + segment.End(); } - } - finally - { - segment.End(); - transaction.End(); } } - } - private IEnumerable ProcessHeaders(ReadOnlyDictionary applicationProperties, string key) - { - var headerValues = new List(); - foreach (var item in applicationProperties) + return Delegates.GetDelegateFor( + onFailure: transaction.NoticeError, + onComplete: () => segment.End(), + onSuccess: ExtractDTHeadersIfAvailable); + + + void ExtractDTHeadersIfAvailable(object resultObj) { - if (item.Key.Equals(key, StringComparison.OrdinalIgnoreCase)) + if (resultObj != null) { - headerValues.Add(item.Value as string); + switch (instrumentedMethodCall.MethodCall.Method.MethodName) + { + case "ReceiveMessagesAsync": + case "ReceiveDeferredMessagesAsync": + case "PeekMessagesInternalAsync": + // if the response contains a list of messages, + // get the first message from the response and extract DT headers + dynamic messages = resultObj; + if (messages.Count > 0) + { + var msg = messages[0]; + if (msg.ApplicationProperties is ReadOnlyDictionary applicationProperties) + { + transaction.AcceptDistributedTraceHeaders(applicationProperties, ProcessHeaders, TransportType.Queue); + } + } + break; + } } - } + IEnumerable ProcessHeaders(ReadOnlyDictionary applicationProperties, string key) + { + var headerValues = new List(); + foreach (var item in applicationProperties) + { + if (item.Key.Equals(key, StringComparison.OrdinalIgnoreCase)) + { + headerValues.Add(item.Value as string); + } + } - return headerValues; + return headerValues; + } + } } - private static object GetTaskResult(object task) + private static object GetTaskResultFromObject(object taskObj) { - if (((Task)task).IsFaulted) + var task = taskObj as Task; + if (task == null) + { + return null; + } + if (task.IsFaulted) { return null; } diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusSendWrapper.cs b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusSendWrapper.cs index f55d1b49cf..07b6f6b98c 100644 --- a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusSendWrapper.cs +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusSendWrapper.cs @@ -2,50 +2,36 @@ // SPDX-License-Identifier: Apache-2.0 using System; -using System.Collections.Concurrent; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Threading.Tasks; using NewRelic.Agent.Api; using NewRelic.Agent.Extensions.Providers.Wrapper; -using NewRelic.Reflection; namespace NewRelic.Providers.Wrapper.AzureServiceBus; -public class AzureServiceBusSendWrapper : IWrapper +public class AzureServiceBusSendWrapper : AzureServiceBusWrapperBase { - private const string BrokerVendorName = "AzureServiceBus"; - private static ConcurrentDictionary> _getResultFromGenericTask = new(); - - public bool IsTransactionRequired => true; - - public CanWrapResponse CanWrap(InstrumentedMethodInfo instrumentedMethodInfo) + public override CanWrapResponse CanWrap(InstrumentedMethodInfo instrumentedMethodInfo) { var canWrap = instrumentedMethodInfo.RequestedWrapperName.Equals(nameof(AzureServiceBusSendWrapper)); return new CanWrapResponse(canWrap); } - public AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall instrumentedMethodCall, IAgent agent, ITransaction transaction) + public override AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall instrumentedMethodCall, IAgent agent, ITransaction transaction) { dynamic serviceBusReceiver = instrumentedMethodCall.MethodCall.InvocationTarget; - string queueName = serviceBusReceiver.EntityPath; - string identifier = serviceBusReceiver.Identifier; - string fqns = serviceBusReceiver.FullyQualifiedNamespace; - - // ??? - if (instrumentedMethodCall.IsAsync) - { - transaction.AttachToAsync(); - transaction.DetachFromPrimary(); //Remove from thread-local type storage - } + string queueName = serviceBusReceiver.EntityPath; // marty-test-queue + //string identifier = serviceBusReceiver.Identifier; // -9e860ed4-b16b-4d02-96e4-d8ed224ae24b + //string fqns = serviceBusReceiver.FullyQualifiedNamespace; // mt-test-servicebus.servicebus.windows.net + // determine message broker action based on method name MessageBrokerAction action = instrumentedMethodCall.MethodCall.Method.MethodName switch { "SendMessagesAsync" => MessageBrokerAction.Produce, "ScheduleMessagesAsync" => MessageBrokerAction.Produce, "CancelScheduledMessagesAsync" => MessageBrokerAction.Purge, // TODO is this correct ???, - _ => throw new ArgumentOutOfRangeException(nameof(action), $"Unexpected method call: {instrumentedMethodCall.MethodCall.Method.MethodName}") + _ => throw new ArgumentOutOfRangeException(nameof(action), $"Unexpected instrumented method call: {instrumentedMethodCall.MethodCall.Method.MethodName}") }; // start a message broker segment @@ -58,6 +44,7 @@ public AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall ins if (action == MessageBrokerAction.Produce) { dynamic messages = instrumentedMethodCall.MethodCall.MethodArguments[0]; + // iterate all messages that are being sent, // insert DT headers into each message foreach (var message in messages) @@ -69,10 +56,10 @@ public AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall ins // return an async delegate return Delegates.GetAsyncDelegateFor(agent, segment); - } - private void ProcessHeaders(IDictionary applicationProperties, string key, string value) - { - applicationProperties.Add(key, value); + void ProcessHeaders(IDictionary applicationProperties, string key, string value) + { + applicationProperties.Add(key, value); + } } } diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusWrapperBase.cs b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusWrapperBase.cs new file mode 100644 index 0000000000..b6ab4c376d --- /dev/null +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusWrapperBase.cs @@ -0,0 +1,19 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using NewRelic.Agent.Api; +using NewRelic.Agent.Extensions.Providers.Wrapper; + +namespace NewRelic.Providers.Wrapper.AzureServiceBus +{ + public abstract class AzureServiceBusWrapperBase : IWrapper + { + protected const string BrokerVendorName = "AzureServiceBus"; + + public bool IsTransactionRequired => true; // only instrument service bus methods if we're already in a transaction + public abstract CanWrapResponse CanWrap(InstrumentedMethodInfo instrumentedMethodInfo); + + public abstract AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall instrumentedMethodCall, IAgent agent,ITransaction transaction); + + } +} From be27089a4feb21c176b604bd29912f678206aa5d Mon Sep 17 00:00:00 2001 From: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Fri, 8 Nov 2024 16:47:46 -0600 Subject: [PATCH 04/10] Integration tests --- .../Api/ITransaction.cs | 3 + .../AzureServiceBusReceiveWrapper.cs | 6 +- .../AzureServiceBusSendWrapper.cs | 6 +- .../NewRelicConfigModifier.cs | 3 +- .../Shared/AzureServiceBusConfiguration.cs | 33 +++ .../MFALatestPackages.csproj | 3 + .../MultiFunctionApplicationHelpers.csproj | 15 +- .../AzureServiceBusExerciser.cs | 138 +++++++++++++ .../AzureServiceBus/AzureServiceBusTests.cs | 195 ++++++++++++++++++ 9 files changed, 394 insertions(+), 8 deletions(-) create mode 100644 tests/Agent/IntegrationTests/Shared/AzureServiceBusConfiguration.cs create mode 100644 tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/AzureServiceBus/AzureServiceBusExerciser.cs create mode 100644 tests/Agent/IntegrationTests/UnboundedIntegrationTests/AzureServiceBus/AzureServiceBusTests.cs diff --git a/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Api/ITransaction.cs b/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Api/ITransaction.cs index c8fd50cfaf..6bcf07aebc 100644 --- a/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Api/ITransaction.cs +++ b/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Api/ITransaction.cs @@ -92,6 +92,9 @@ public interface ITransaction /// /// /// + /// + /// + /// /// /// an opaque object that will be needed when you want to end the segment. ISegment StartMessageBrokerSegment(MethodCall methodCall, MessageBrokerDestinationType destinationType, diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusReceiveWrapper.cs b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusReceiveWrapper.cs index 6ebcf4bd1e..29cb85d7fa 100644 --- a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusReceiveWrapper.cs +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusReceiveWrapper.cs @@ -27,7 +27,7 @@ public override AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMetho dynamic serviceBusReceiver = instrumentedMethodCall.MethodCall.InvocationTarget; string queueName = serviceBusReceiver.EntityPath; // marty-test-queue //string identifier = serviceBusReceiver.Identifier; // -9e860ed4-b16b-4d02-96e4-d8ed224ae24b - //string fqns = serviceBusReceiver.FullyQualifiedNamespace; // mt-test-servicebus.servicebus.windows.net + string fqns = serviceBusReceiver.FullyQualifiedNamespace; // mt-test-servicebus.servicebus.windows.net MessageBrokerAction action = instrumentedMethodCall.MethodCall.Method.MethodName switch @@ -48,7 +48,9 @@ public override AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMetho instrumentedMethodCall.MethodCall, MessageBrokerDestinationType.Queue, action, - BrokerVendorName, queueName); + BrokerVendorName, + queueName, + serverAddress: fqns ); if (instrumentedMethodCall.IsAsync) { diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusSendWrapper.cs b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusSendWrapper.cs index 07b6f6b98c..7268b14c06 100644 --- a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusSendWrapper.cs +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusSendWrapper.cs @@ -22,7 +22,7 @@ public override AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMetho dynamic serviceBusReceiver = instrumentedMethodCall.MethodCall.InvocationTarget; string queueName = serviceBusReceiver.EntityPath; // marty-test-queue //string identifier = serviceBusReceiver.Identifier; // -9e860ed4-b16b-4d02-96e4-d8ed224ae24b - //string fqns = serviceBusReceiver.FullyQualifiedNamespace; // mt-test-servicebus.servicebus.windows.net + string fqns = serviceBusReceiver.FullyQualifiedNamespace; // mt-test-servicebus.servicebus.windows.net // determine message broker action based on method name MessageBrokerAction action = @@ -39,7 +39,9 @@ public override AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMetho instrumentedMethodCall.MethodCall, MessageBrokerDestinationType.Queue, action, - BrokerVendorName, queueName); + BrokerVendorName, + queueName, + serverAddress: fqns); if (action == MessageBrokerAction.Produce) { diff --git a/tests/Agent/IntegrationTests/IntegrationTestHelpers/NewRelicConfigModifier.cs b/tests/Agent/IntegrationTests/IntegrationTestHelpers/NewRelicConfigModifier.cs index 230d2fffeb..cf4256673a 100644 --- a/tests/Agent/IntegrationTests/IntegrationTestHelpers/NewRelicConfigModifier.cs +++ b/tests/Agent/IntegrationTests/IntegrationTestHelpers/NewRelicConfigModifier.cs @@ -175,10 +175,11 @@ public void ForceSqlTraces() "explainThreshold", "1"); } - public void SetLogLevel(string level) + public NewRelicConfigModifier SetLogLevel(string level) { CommonUtils.ModifyOrCreateXmlAttributeInNewRelicConfig(_configFilePath, new[] { "configuration", "log" }, "level", level); + return this; } public void LogToConsole() diff --git a/tests/Agent/IntegrationTests/Shared/AzureServiceBusConfiguration.cs b/tests/Agent/IntegrationTests/Shared/AzureServiceBusConfiguration.cs new file mode 100644 index 0000000000..282441ca92 --- /dev/null +++ b/tests/Agent/IntegrationTests/Shared/AzureServiceBusConfiguration.cs @@ -0,0 +1,33 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; + +namespace NewRelic.Agent.IntegrationTests.Shared +{ + public class AzureServiceBusConfiguration + { + private static string _connectionString; + + public static string ConnectionString + { + get + { + if (_connectionString == null) + { + try + { + var testConfiguration = IntegrationTestConfiguration.GetIntegrationTestConfiguration("AzureServiceBusTests"); + _connectionString = testConfiguration["ConnectionString"]; + } + catch (Exception ex) + { + throw new Exception("Azure Service Bus configuration is invalid.", ex); + } + } + + return _connectionString; + } + } + } +} diff --git a/tests/Agent/IntegrationTests/SharedApplications/Common/MFALatestPackages/MFALatestPackages.csproj b/tests/Agent/IntegrationTests/SharedApplications/Common/MFALatestPackages/MFALatestPackages.csproj index b72937d49d..5614a05652 100644 --- a/tests/Agent/IntegrationTests/SharedApplications/Common/MFALatestPackages/MFALatestPackages.csproj +++ b/tests/Agent/IntegrationTests/SharedApplications/Common/MFALatestPackages/MFALatestPackages.csproj @@ -8,6 +8,9 @@ + + + diff --git a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/MultiFunctionApplicationHelpers.csproj b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/MultiFunctionApplicationHelpers.csproj index 5e11abdd2e..3449479fc6 100644 --- a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/MultiFunctionApplicationHelpers.csproj +++ b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/MultiFunctionApplicationHelpers.csproj @@ -28,7 +28,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -91,7 +91,7 @@ - + @@ -260,7 +260,7 @@ - + @@ -275,6 +275,15 @@ + + + + + + + + + diff --git a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/AzureServiceBus/AzureServiceBusExerciser.cs b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/AzureServiceBus/AzureServiceBusExerciser.cs new file mode 100644 index 0000000000..c7191e737c --- /dev/null +++ b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/AzureServiceBus/AzureServiceBusExerciser.cs @@ -0,0 +1,138 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using Azure.Messaging.ServiceBus; +using Azure.Messaging.ServiceBus.Administration; +using MongoDB.Driver.Core.Configuration; +using NewRelic.Agent.IntegrationTests.Shared; +using NewRelic.Agent.IntegrationTests.Shared.ReflectionHelpers; +using NewRelic.Api.Agent; + +namespace MultiFunctionApplicationHelpers.NetStandardLibraries.AzureServiceBus +{ + [Library] + internal class AzureServiceBusExerciser + { + [LibraryMethod] + public static async Task InitializeQueue(string queueName) + { + ServiceBusAdministrationClient adminClient = new(AzureServiceBusConfiguration.ConnectionString); + // if the queue exists, delete it and re-create it + if (await adminClient.QueueExistsAsync(queueName)) + { + await adminClient.DeleteQueueAsync(queueName); + } + await adminClient.CreateQueueAsync(queueName); + + } + + [LibraryMethod] + public static async Task DeleteQueue(string queueName) + { + ServiceBusAdministrationClient adminClient = new(AzureServiceBusConfiguration.ConnectionString); + if (await adminClient.QueueExistsAsync(queueName)) + { + await adminClient.DeleteQueueAsync(queueName); + } + } + + [LibraryMethod] + [Transaction] + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public static async Task ExerciseMultipleReceiveOperationsOnAMessage(string queueName) + { + await using var client = new ServiceBusClient(AzureServiceBusConfiguration.ConnectionString); + + await SendAMessage(client, queueName, "Hello world!"); + + await using var receiver = client.CreateReceiver(queueName, new ServiceBusReceiverOptions() { ReceiveMode = ServiceBusReceiveMode.PeekLock }); + + await receiver.PeekMessageAsync(); + + // receive the message in peek lock mode + var receivedMessage = await receiver.ReceiveMessageAsync(); + + // renew message lock + await receiver.RenewMessageLockAsync(receivedMessage); + + // defer the message + await receiver.DeferMessageAsync(receivedMessage); + + // receive the deferred message + var deferredMessage = await receiver.ReceiveDeferredMessageAsync(receivedMessage.SequenceNumber); + + // complete the message + await receiver.CompleteMessageAsync(deferredMessage); + } + + [LibraryMethod] + [Transaction] + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public static async Task ScheduleAndReceiveAMessage(string queueName) + { + await using var client = new ServiceBusClient(AzureServiceBusConfiguration.ConnectionString); + await using var sender = client.CreateSender(queueName); + + ServiceBusMessage message = new("Hello world!"); + await sender.ScheduleMessageAsync(message, DateTime.UtcNow.AddSeconds(5)); + + await Task.Delay(TimeSpan.FromSeconds(10)); + + await using var receiver = client.CreateReceiver(queueName, new ServiceBusReceiverOptions() { ReceiveMode = ServiceBusReceiveMode.ReceiveAndDelete }); + var receivedMsg = await receiver.ReceiveMessageAsync(); + } + + [LibraryMethod] + [Transaction] + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public static async Task ReceiveAndDeadLetterAMessage(string queueName) + { + await using var client = new ServiceBusClient(AzureServiceBusConfiguration.ConnectionString); + + await SendAMessage(client, queueName, "Hello world!"); + + await using var receiver = client.CreateReceiver(queueName, new ServiceBusReceiverOptions() { ReceiveMode = ServiceBusReceiveMode.PeekLock }); + + // receive the message in peek lock mode + var receivedMessage = await receiver.ReceiveMessageAsync(); + + // dead-letter the message + await receiver.DeadLetterMessageAsync(receivedMessage); + } + + [LibraryMethod] + [Transaction] + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public static async Task ReceiveAndAbandonAMessage(string queueName) + { + await using var client = new ServiceBusClient(AzureServiceBusConfiguration.ConnectionString); + + await SendAMessage(client, queueName, "Hello world!"); + + await using var receiver = client.CreateReceiver(queueName, new ServiceBusReceiverOptions() { ReceiveMode = ServiceBusReceiveMode.PeekLock }); + + // receive the message in peek lock mode + var receivedMessage = await receiver.ReceiveMessageAsync(); + + // abandon the message - it'll go back on the queue + await receiver.AbandonMessageAsync(receivedMessage); + + // receive the message again and complete it to remove it from the queue + var receivedMessage2 = await receiver.ReceiveMessageAsync(); + await receiver.CompleteMessageAsync(receivedMessage2); } + + + private static async Task SendAMessage(ServiceBusClient client, string queueName, string messageBody) + { + await using var sender = client.CreateSender(queueName); + ServiceBusMessage message = new(messageBody); + await sender.SendMessageAsync(message); + } + } +} diff --git a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/AzureServiceBus/AzureServiceBusTests.cs b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/AzureServiceBus/AzureServiceBusTests.cs new file mode 100644 index 0000000000..f82d5f9d89 --- /dev/null +++ b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/AzureServiceBus/AzureServiceBusTests.cs @@ -0,0 +1,195 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Collections.Generic; +using System.Linq; +using NewRelic.Agent.IntegrationTestHelpers; +using NewRelic.Agent.IntegrationTestHelpers.RemoteServiceFixtures; +using NewRelic.Agent.UnboundedIntegrationTests.AzureServiceBus; +using NewRelic.Testing.Assertions; +using Xunit; +using Xunit.Abstractions; + +namespace NewRelic.Agent.UnboundedIntegrationTests.AzureServiceBus; + +public abstract class AzureServiceBusTestsBase : NewRelicIntegrationTest + where TFixture : ConsoleDynamicMethodFixture +{ + private readonly TFixture _fixture; + private readonly string _queueName; + + protected AzureServiceBusTestsBase(TFixture fixture, ITestOutputHelper output) : base(fixture) + { + _fixture = fixture; + _fixture.SetTimeout(TimeSpan.FromMinutes(1)); + _fixture.TestLogger = output; + + _queueName = $"test-queue-{Guid.NewGuid()}"; + + _fixture.AddCommand($"AzureServiceBusExerciser InitializeQueue {_queueName}"); + _fixture.AddCommand($"AzureServiceBusExerciser ExerciseMultipleReceiveOperationsOnAMessage {_queueName}"); + _fixture.AddCommand($"AzureServiceBusExerciser ScheduleAndReceiveAMessage {_queueName}"); + _fixture.AddCommand($"AzureServiceBusExerciser ReceiveAndDeadLetterAMessage {_queueName}"); + _fixture.AddCommand($"AzureServiceBusExerciser ReceiveAndAbandonAMessage {_queueName}"); + _fixture.AddCommand($"AzureServiceBusExerciser DeleteQueue {_queueName}"); + + _fixture.AddActions + ( + setupConfiguration: () => + { + var configModifier = new NewRelicConfigModifier(fixture.DestinationNewRelicConfigFilePath); + + configModifier + .SetLogLevel("finest") + .EnableDistributedTrace() + .ForceTransactionTraces() + .ConfigureFasterMetricsHarvestCycle(20) + .ConfigureFasterSpanEventsHarvestCycle(20) + .ConfigureFasterTransactionTracesHarvestCycle(25) + ; + } + //, + //exerciseApplication: () => + //{ + // _fixture.AgentLog.WaitForLogLine(AgentLogBase.ShutdownLogLineRegex, TimeSpan.FromMinutes(2)); + //} + ); + + _fixture.Initialize(); + } + + private readonly string _metricScopeBase = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.AzureServiceBus.AzureServiceBusExerciser"; + + [Fact] + public void Test() + { + var metrics = _fixture.AgentLog.GetMetrics().ToList(); + + var expectedMetrics = new List + { + new Assertions.ExpectedMetric { metricName = $"MessageBroker/AzureServiceBus/Queue/Produce/Named/{_queueName}", callCount = 4}, + new Assertions.ExpectedMetric { metricName = $"MessageBroker/AzureServiceBus/Queue/Produce/Named/{_queueName}", callCount = 1, metricScope = $"{_metricScopeBase}/ExerciseMultipleReceiveOperationsOnAMessage"}, + new Assertions.ExpectedMetric { metricName = $"MessageBroker/AzureServiceBus/Queue/Produce/Named/{_queueName}", callCount = 1, metricScope = $"{_metricScopeBase}/ScheduleAndReceiveAMessage"}, + new Assertions.ExpectedMetric { metricName = $"MessageBroker/AzureServiceBus/Queue/Produce/Named/{_queueName}", callCount = 1, metricScope = $"{_metricScopeBase}/ReceiveAndDeadLetterAMessage"}, + new Assertions.ExpectedMetric { metricName = $"MessageBroker/AzureServiceBus/Queue/Produce/Named/{_queueName}", callCount = 1, metricScope = $"{_metricScopeBase}/ReceiveAndAbandonAMessage"}, + + new Assertions.ExpectedMetric { metricName = $"MessageBroker/AzureServiceBus/Queue/Consume/Named/{_queueName}", callCount = 10}, + new Assertions.ExpectedMetric { metricName = $"MessageBroker/AzureServiceBus/Queue/Consume/Named/{_queueName}", callCount = 5, metricScope = $"{_metricScopeBase}/ExerciseMultipleReceiveOperationsOnAMessage"}, + new Assertions.ExpectedMetric { metricName = $"MessageBroker/AzureServiceBus/Queue/Consume/Named/{_queueName}", callCount = 1, metricScope = $"{_metricScopeBase}/ScheduleAndReceiveAMessage"}, + new Assertions.ExpectedMetric { metricName = $"MessageBroker/AzureServiceBus/Queue/Consume/Named/{_queueName}", callCount = 1, metricScope = $"{_metricScopeBase}/ReceiveAndDeadLetterAMessage"}, + new Assertions.ExpectedMetric { metricName = $"MessageBroker/AzureServiceBus/Queue/Consume/Named/{_queueName}", callCount = 3, metricScope = $"{_metricScopeBase}/ReceiveAndAbandonAMessage"}, + + new Assertions.ExpectedMetric { metricName = $"MessageBroker/AzureServiceBus/Queue/Peek/Named/{_queueName}", callCount = 1 }, + new Assertions.ExpectedMetric { metricName = $"MessageBroker/AzureServiceBus/Queue/Peek/Named/{_queueName}", callCount = 1, metricScope = $"{_metricScopeBase}/ExerciseMultipleReceiveOperationsOnAMessage" }, + + new Assertions.ExpectedMetric { metricName = $"MessageBroker/AzureServiceBus/Queue/Purge/Named/{_queueName}", callCount = 2 }, + new Assertions.ExpectedMetric { metricName = $"MessageBroker/AzureServiceBus/Queue/Purge/Named/{_queueName}", callCount = 1, metricScope = $"{_metricScopeBase}/ReceiveAndDeadLetterAMessage" }, + new Assertions.ExpectedMetric { metricName = $"MessageBroker/AzureServiceBus/Queue/Purge/Named/{_queueName}", callCount = 1, metricScope = $"{_metricScopeBase}/ReceiveAndAbandonAMessage" }, + }; + + var exerciseMultipleReceiveOperationsOnAMessageTransactionEvent = _fixture.AgentLog.TryGetTransactionEvent($"{_metricScopeBase}/ExerciseMultipleReceiveOperationsOnAMessage"); + var scheduleAndReceiveAMessageTransactionEvent = _fixture.AgentLog.TryGetTransactionEvent($"{_metricScopeBase}/ScheduleAndReceiveAMessage"); + + var expectedTransactionTraceSegments = new List + { + $"MessageBroker/AzureServiceBus/Queue/Consume/Named/{_queueName}" + }; + + var transactionSample = _fixture.AgentLog.TryGetTransactionSample($"{_metricScopeBase}/ScheduleAndReceiveAMessage"); // this will always be the slowest transaction + + var queueProduceSpanEvents = _fixture.AgentLog.TryGetSpanEvent($"MessageBroker/AzureServiceBus/Queue/Produce/Named/{_queueName}"); + var queueConsumeSpanEvents = _fixture.AgentLog.TryGetSpanEvent($"MessageBroker/AzureServiceBus/Queue/Consume/Named/{_queueName}"); + var queuePeekSpanEvents = _fixture.AgentLog.TryGetSpanEvent($"MessageBroker/AzureServiceBus/Queue/Peek/Named/{_queueName}"); + var queuePurgeSpanEvents = _fixture.AgentLog.TryGetSpanEvent($"MessageBroker/AzureServiceBus/Queue/Purge/Named/{_queueName}"); + + var expectedProduceAgentAttributes = new List + { + "server.address", + "messaging.destination.name", + }; + + var expectedConsumeAgentAttributes = new List + { + "server.address", + "messaging.destination.name", + }; + + + var expectedPeekAgentAttributes = new List + { + "server.address", + "messaging.destination.name", + }; + + var expectedPurgeAgentAttributes = new List + { + "server.address", + "messaging.destination.name", + }; + + var expectedIntrinsicAttributes = new List { "span.kind", }; + + Assertions.MetricsExist(expectedMetrics, metrics); + + NrAssert.Multiple( + () => Assert.True(exerciseMultipleReceiveOperationsOnAMessageTransactionEvent != null, "ExerciseMultipleReceiveOperationsOnAMessageTransactionEvent should not be null"), + () => Assert.True(scheduleAndReceiveAMessageTransactionEvent != null, "ScheduleAndReceiveAMessageTransactionEvent should not be null"), + () => Assert.True(transactionSample != null, "transactionSample should not be null"), + () => Assertions.TransactionTraceSegmentsExist(expectedTransactionTraceSegments, transactionSample), + + () => Assertions.SpanEventHasAttributes(expectedProduceAgentAttributes, + Tests.TestSerializationHelpers.Models.SpanEventAttributeType.Agent, queueProduceSpanEvents), + () => Assertions.SpanEventHasAttributes(expectedIntrinsicAttributes, + Tests.TestSerializationHelpers.Models.SpanEventAttributeType.Intrinsic, queueProduceSpanEvents), + + () => Assertions.SpanEventHasAttributes(expectedConsumeAgentAttributes, + Tests.TestSerializationHelpers.Models.SpanEventAttributeType.Agent, queueConsumeSpanEvents), + () => Assertions.SpanEventHasAttributes(expectedIntrinsicAttributes, + Tests.TestSerializationHelpers.Models.SpanEventAttributeType.Intrinsic, queueConsumeSpanEvents), + + () => Assertions.SpanEventHasAttributes(expectedPeekAgentAttributes, + Tests.TestSerializationHelpers.Models.SpanEventAttributeType.Agent, queuePeekSpanEvents), + // peek doesn't emit a span.kind attribute + //() => Assertions.SpanEventHasAttributes(expectedIntrinsicAttributes, + // Tests.TestSerializationHelpers.Models.SpanEventAttributeType.Intrinsic, queuePeekSpanEvents) + + () => Assertions.SpanEventHasAttributes(expectedPurgeAgentAttributes, + Tests.TestSerializationHelpers.Models.SpanEventAttributeType.Agent, queuePurgeSpanEvents) + //() => Assertions.SpanEventHasAttributes(expectedIntrinsicAttributes, + // Tests.TestSerializationHelpers.Models.SpanEventAttributeType.Intrinsic, queuePurgeSpanEvents) + ); + } +} + +[NetFrameworkTest] +public class AzureServiceBusTestsFWLatest : AzureServiceBusTestsBase +{ + public AzureServiceBusTestsFWLatest(ConsoleDynamicMethodFixtureFWLatest fixture, ITestOutputHelper output) : base(fixture, output) + { + } +} + +[NetFrameworkTest] +public class AzureServiceBusTestsFW462 : AzureServiceBusTestsBase +{ + public AzureServiceBusTestsFW462(ConsoleDynamicMethodFixtureFW462 fixture, ITestOutputHelper output) : base(fixture, output) + { + } +} + +[NetCoreTest] +public class AzureServiceBusTestsCoreOldest : AzureServiceBusTestsBase +{ + public AzureServiceBusTestsCoreOldest(ConsoleDynamicMethodFixtureCoreOldest fixture, ITestOutputHelper output) : base(fixture, output) + { + } +} + +[NetCoreTest] +public class AzureServiceBusTestsCoreLatest : AzureServiceBusTestsBase +{ + public AzureServiceBusTestsCoreLatest(ConsoleDynamicMethodFixtureCoreLatest fixture, ITestOutputHelper output) : base(fixture, output) + { + } +} From 2880aae3016bb06ae0467a98194d5b2e8e718998 Mon Sep 17 00:00:00 2001 From: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Mon, 11 Nov 2024 10:07:28 -0600 Subject: [PATCH 05/10] Update artifact builder, installer, solution dependencies --- FullAgent.sln | 5 +++-- build/ArtifactBuilder/CoreAgentComponents.cs | 2 ++ build/ArtifactBuilder/FrameworkAgentComponents.cs | 2 ++ src/Agent/MsiInstaller/Installer/Product.wxs | 13 +++++++++++-- 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/FullAgent.sln b/FullAgent.sln index d0e2164e47..d2bcf704d3 100644 --- a/FullAgent.sln +++ b/FullAgent.sln @@ -157,6 +157,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Home", "src\Agent\NewRelic\ {4F5D77F3-B41A-44A7-AF10-2D5462CE0162} = {4F5D77F3-B41A-44A7-AF10-2D5462CE0162} {570429FD-C785-4673-82DF-643D06B6DC53} = {570429FD-C785-4673-82DF-643D06B6DC53} {5BBEEC11-B753-4631-BCDD-43BE73B5CCAC} = {5BBEEC11-B753-4631-BCDD-43BE73B5CCAC} + {5D74E5C5-9BA3-423B-86F7-14C2D1A14661} = {5D74E5C5-9BA3-423B-86F7-14C2D1A14661} {614988AD-7C73-4E71-8F95-520D5F485984} = {614988AD-7C73-4E71-8F95-520D5F485984} {64C7F267-5185-4AB7-9EB5-0183D8BB0171} = {64C7F267-5185-4AB7-9EB5-0183D8BB0171} {686AE051-BC8C-4AEC-803D-237AEB807CA9} = {686AE051-BC8C-4AEC-803D-237AEB807CA9} @@ -222,7 +223,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PublicApiChangeTests", "tes EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Memcached", "src\Agent\NewRelic\Agent\Extensions\Providers\Wrapper\Memcached\Memcached.csproj", "{5D74E5C5-9BA3-423B-86F7-14C2D1A14661}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AzureServiceBus", "src\Agent\NewRelic\Agent\Extensions\Providers\Wrapper\AzureServiceBus\AzureServiceBus.csproj", "{4078E594-E738-48F7-A7ED-B208ADD04900}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzureServiceBus", "src\Agent\NewRelic\Agent\Extensions\Providers\Wrapper\AzureServiceBus\AzureServiceBus.csproj", "{4078E594-E738-48F7-A7ED-B208ADD04900}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -548,8 +549,8 @@ Global {4078E594-E738-48F7-A7ED-B208ADD04900} = {5E86E10A-C38F-48CB-ADE9-67B22BB2F50A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {D8B98070-6B8E-403C-A07F-A3F2E4A3A3D0} EnterpriseLibraryConfigurationToolBinariesPath = packages\Unity.2.1.505.2\lib\NET35 + SolutionGuid = {D8B98070-6B8E-403C-A07F-A3F2E4A3A3D0} EndGlobalSection GlobalSection(TestCaseManagementSettings) = postSolution CategoryFile = FullAgent.vsmdi diff --git a/build/ArtifactBuilder/CoreAgentComponents.cs b/build/ArtifactBuilder/CoreAgentComponents.cs index 94bbafde91..6067173c07 100644 --- a/build/ArtifactBuilder/CoreAgentComponents.cs +++ b/build/ArtifactBuilder/CoreAgentComponents.cs @@ -60,6 +60,7 @@ protected override void CreateAgentComponents() $@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.AwsSdk.dll", $@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.AzureFunction.dll", $@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.Memcached.dll", + $@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.AzureServiceBus.dll", }; var wrapperXmls = new[] @@ -88,6 +89,7 @@ protected override void CreateAgentComponents() $@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.AwsSdk.Instrumentation.xml", $@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.AzureFunction.Instrumentation.xml", $@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.Memcached.Instrumentation.xml", + $@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.AzureServiceBus.Instrumentation.xml", }; ExtensionXsd = $@"{SourceHomeBuilderPath}\extensions\extension.xsd"; diff --git a/build/ArtifactBuilder/FrameworkAgentComponents.cs b/build/ArtifactBuilder/FrameworkAgentComponents.cs index 7f6bd9666a..f50969353c 100644 --- a/build/ArtifactBuilder/FrameworkAgentComponents.cs +++ b/build/ArtifactBuilder/FrameworkAgentComponents.cs @@ -67,6 +67,7 @@ protected override void CreateAgentComponents() $@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.AwsSdk.dll", $@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.AzureFunction.dll", $@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.Memcached.dll", + $@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.AzureServiceBus.dll", }; var wrapperXmls = new[] @@ -109,6 +110,7 @@ protected override void CreateAgentComponents() $@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.AwsSdk.Instrumentation.xml", $@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.AzureFunction.Instrumentation.xml", $@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.Memcached.Instrumentation.xml", + $@"{SourceHomeBuilderPath}\extensions\NewRelic.Providers.Wrapper.AzureServiceBus.Instrumentation.xml", }; ExtensionXsd = $@"{SourceHomeBuilderPath}\extensions\extension.xsd"; diff --git a/src/Agent/MsiInstaller/Installer/Product.wxs b/src/Agent/MsiInstaller/Installer/Product.wxs index 83adb71f46..6449cfa661 100644 --- a/src/Agent/MsiInstaller/Installer/Product.wxs +++ b/src/Agent/MsiInstaller/Installer/Product.wxs @@ -404,6 +404,9 @@ SPDX-License-Identifier: Apache-2.0 + + + @@ -479,8 +482,8 @@ SPDX-License-Identifier: Apache-2.0 - - + + @@ -600,6 +603,9 @@ SPDX-License-Identifier: Apache-2.0 + + + @@ -675,6 +681,9 @@ SPDX-License-Identifier: Apache-2.0 + + + From a0919a5dd5d061e642b8546a64aabc81025e992f Mon Sep 17 00:00:00 2001 From: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Mon, 11 Nov 2024 10:28:38 -0600 Subject: [PATCH 06/10] fix Product.wxs --- src/Agent/MsiInstaller/Installer/Product.wxs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Agent/MsiInstaller/Installer/Product.wxs b/src/Agent/MsiInstaller/Installer/Product.wxs index 6449cfa661..c61a5f6c77 100644 --- a/src/Agent/MsiInstaller/Installer/Product.wxs +++ b/src/Agent/MsiInstaller/Installer/Product.wxs @@ -482,6 +482,9 @@ SPDX-License-Identifier: Apache-2.0 + + + From f843d475f1dc60e53c39c28f459d88000f504afb Mon Sep 17 00:00:00 2001 From: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Mon, 11 Nov 2024 10:41:18 -0600 Subject: [PATCH 07/10] Cleanup --- .../AzureServiceBusReceiveWrapper.cs | 52 +++++++++---------- .../AzureServiceBusSendWrapper.cs | 20 ++++--- .../AzureServiceBusWrapperBase.cs | 1 + 3 files changed, 35 insertions(+), 38 deletions(-) diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusReceiveWrapper.cs b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusReceiveWrapper.cs index 29cb85d7fa..01e58c33f8 100644 --- a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusReceiveWrapper.cs +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusReceiveWrapper.cs @@ -25,9 +25,8 @@ public override CanWrapResponse CanWrap(InstrumentedMethodInfo instrumentedMetho public override AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall instrumentedMethodCall, IAgent agent, ITransaction transaction) { dynamic serviceBusReceiver = instrumentedMethodCall.MethodCall.InvocationTarget; - string queueName = serviceBusReceiver.EntityPath; // marty-test-queue - //string identifier = serviceBusReceiver.Identifier; // -9e860ed4-b16b-4d02-96e4-d8ed224ae24b - string fqns = serviceBusReceiver.FullyQualifiedNamespace; // mt-test-servicebus.servicebus.windows.net + string queueName = serviceBusReceiver.EntityPath; // some-queue-name + string fqns = serviceBusReceiver.FullyQualifiedNamespace; // some-service-bus-entity.servicebus.windows.net MessageBrokerAction action = instrumentedMethodCall.MethodCall.Method.MethodName switch @@ -35,7 +34,7 @@ public override AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMetho "ReceiveMessagesAsync" => MessageBrokerAction.Consume, "ReceiveDeferredMessagesAsync" => MessageBrokerAction.Consume, "PeekMessagesInternalAsync" => MessageBrokerAction.Peek, - "AbandonMessageAsync" => MessageBrokerAction.Purge, // TODO is this correct ???, + "AbandonMessageAsync" => MessageBrokerAction.Purge, // TODO is this correct ??? Abandon sends the message back to the queue for re-delivery "CompleteMessageAsync" => MessageBrokerAction.Consume, "DeadLetterInternalAsync" => MessageBrokerAction.Purge, // TODO is this correct ??? "DeferMessageAsync" => MessageBrokerAction.Consume, // TODO is this correct or should we extend MessageBrokerAction with more values??? @@ -52,40 +51,39 @@ public override AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMetho queueName, serverAddress: fqns ); - if (instrumentedMethodCall.IsAsync) - { + return instrumentedMethodCall.IsAsync + ? // return an async delegate - return Delegates.GetAsyncDelegateFor( + Delegates.GetAsyncDelegateFor( agent, segment, false, HandleResponse, - TaskContinuationOptions.ExecuteSynchronously); + TaskContinuationOptions.ExecuteSynchronously) + : Delegates.GetDelegateFor( + onFailure: transaction.NoticeError, + onComplete: segment.End, + onSuccess: ExtractDTHeadersIfAvailable); - void HandleResponse(Task responseTask) + void HandleResponse(Task responseTask) + { + try { - try - { - if (responseTask.IsFaulted) - { - transaction.NoticeError(responseTask.Exception); // TODO ??? - return; - } - - var resultObj = GetTaskResultFromObject(responseTask); - ExtractDTHeadersIfAvailable(resultObj); - } - finally + if (responseTask.IsFaulted) { - segment.End(); + transaction.NoticeError(responseTask.Exception); + return; } + + var resultObj = GetTaskResultFromObject(responseTask); + ExtractDTHeadersIfAvailable(resultObj); + } + finally + { + segment.End(); } } - return Delegates.GetDelegateFor( - onFailure: transaction.NoticeError, - onComplete: () => segment.End(), - onSuccess: ExtractDTHeadersIfAvailable); void ExtractDTHeadersIfAvailable(object resultObj) @@ -97,7 +95,7 @@ void ExtractDTHeadersIfAvailable(object resultObj) case "ReceiveMessagesAsync": case "ReceiveDeferredMessagesAsync": case "PeekMessagesInternalAsync": - // if the response contains a list of messages, + // the response contains a list of messages. // get the first message from the response and extract DT headers dynamic messages = resultObj; if (messages.Count > 0) diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusSendWrapper.cs b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusSendWrapper.cs index 7268b14c06..e8d8b4a9a7 100644 --- a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusSendWrapper.cs +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusSendWrapper.cs @@ -20,9 +20,8 @@ public override CanWrapResponse CanWrap(InstrumentedMethodInfo instrumentedMetho public override AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall instrumentedMethodCall, IAgent agent, ITransaction transaction) { dynamic serviceBusReceiver = instrumentedMethodCall.MethodCall.InvocationTarget; - string queueName = serviceBusReceiver.EntityPath; // marty-test-queue - //string identifier = serviceBusReceiver.Identifier; // -9e860ed4-b16b-4d02-96e4-d8ed224ae24b - string fqns = serviceBusReceiver.FullyQualifiedNamespace; // mt-test-servicebus.servicebus.windows.net + string queueName = serviceBusReceiver.EntityPath; // some-queue-name + string fqns = serviceBusReceiver.FullyQualifiedNamespace; // some-service-bus-entity.servicebus.windows.net // determine message broker action based on method name MessageBrokerAction action = @@ -30,7 +29,7 @@ public override AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMetho { "SendMessagesAsync" => MessageBrokerAction.Produce, "ScheduleMessagesAsync" => MessageBrokerAction.Produce, - "CancelScheduledMessagesAsync" => MessageBrokerAction.Purge, // TODO is this correct ???, + "CancelScheduledMessagesAsync" => MessageBrokerAction.Purge, // TODO is this correct ??? _ => throw new ArgumentOutOfRangeException(nameof(action), $"Unexpected instrumented method call: {instrumentedMethodCall.MethodCall.Method.MethodName}") }; @@ -54,14 +53,13 @@ public override AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMetho if (message.ApplicationProperties is IDictionary applicationProperties) transaction.InsertDistributedTraceHeaders(applicationProperties, ProcessHeaders); } - } - - // return an async delegate - return Delegates.GetAsyncDelegateFor(agent, segment); - void ProcessHeaders(IDictionary applicationProperties, string key, string value) - { - applicationProperties.Add(key, value); + void ProcessHeaders(IDictionary applicationProperties, string key, string value) + { + applicationProperties.Add(key, value); + } } + + return instrumentedMethodCall.IsAsync ? Delegates.GetAsyncDelegateFor(agent, segment) : Delegates.GetDelegateFor(segment); } } diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusWrapperBase.cs b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusWrapperBase.cs index b6ab4c376d..97d9332c93 100644 --- a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusWrapperBase.cs +++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureServiceBus/AzureServiceBusWrapperBase.cs @@ -11,6 +11,7 @@ public abstract class AzureServiceBusWrapperBase : IWrapper protected const string BrokerVendorName = "AzureServiceBus"; public bool IsTransactionRequired => true; // only instrument service bus methods if we're already in a transaction + public abstract CanWrapResponse CanWrap(InstrumentedMethodInfo instrumentedMethodInfo); public abstract AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall instrumentedMethodCall, IAgent agent,ITransaction transaction); From 2d8a4bb637dbab90720e59e67bb0c2167335908c Mon Sep 17 00:00:00 2001 From: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Mon, 11 Nov 2024 11:15:45 -0600 Subject: [PATCH 08/10] Add AzureServiceBus namespace to all_solutions workflow --- .github/workflows/all_solutions.yml | 1 + .../AzureServiceBus/AzureServiceBusTests.cs | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/all_solutions.yml b/.github/workflows/all_solutions.yml index 481015bb8d..f8e744a46a 100644 --- a/.github/workflows/all_solutions.yml +++ b/.github/workflows/all_solutions.yml @@ -423,6 +423,7 @@ jobs: matrix: namespace: [ + AzureServiceBus, CosmosDB, Couchbase, Elasticsearch, diff --git a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/AzureServiceBus/AzureServiceBusTests.cs b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/AzureServiceBus/AzureServiceBusTests.cs index f82d5f9d89..116c31ff41 100644 --- a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/AzureServiceBus/AzureServiceBusTests.cs +++ b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/AzureServiceBus/AzureServiceBusTests.cs @@ -6,7 +6,6 @@ using System.Linq; using NewRelic.Agent.IntegrationTestHelpers; using NewRelic.Agent.IntegrationTestHelpers.RemoteServiceFixtures; -using NewRelic.Agent.UnboundedIntegrationTests.AzureServiceBus; using NewRelic.Testing.Assertions; using Xunit; using Xunit.Abstractions; From f6a6a9e9b4c9397a19572bada6778bdbcb5ee640 Mon Sep 17 00:00:00 2001 From: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Mon, 11 Nov 2024 11:51:22 -0600 Subject: [PATCH 09/10] integration test cleanup, adjusted build dependencies --- .../AzureServiceBusExerciser.cs | 177 +++++++++--------- .../UnboundedIntegrationTests.sln | 11 +- .../AzureServiceBus/AzureServiceBusTests.cs | 46 ++--- 3 files changed, 114 insertions(+), 120 deletions(-) diff --git a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/AzureServiceBus/AzureServiceBusExerciser.cs b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/AzureServiceBus/AzureServiceBusExerciser.cs index c7191e737c..a815b46072 100644 --- a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/AzureServiceBus/AzureServiceBusExerciser.cs +++ b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/AzureServiceBus/AzureServiceBusExerciser.cs @@ -2,137 +2,132 @@ // SPDX-License-Identifier: Apache-2.0 using System; -using System.Collections.Generic; -using System.Linq; using System.Runtime.CompilerServices; -using System.Text; using System.Threading.Tasks; using Azure.Messaging.ServiceBus; using Azure.Messaging.ServiceBus.Administration; -using MongoDB.Driver.Core.Configuration; using NewRelic.Agent.IntegrationTests.Shared; using NewRelic.Agent.IntegrationTests.Shared.ReflectionHelpers; using NewRelic.Api.Agent; -namespace MultiFunctionApplicationHelpers.NetStandardLibraries.AzureServiceBus +namespace MultiFunctionApplicationHelpers.NetStandardLibraries.AzureServiceBus; + +[Library] +internal class AzureServiceBusExerciser { - [Library] - internal class AzureServiceBusExerciser + [LibraryMethod] + public static async Task InitializeQueue(string queueName) { - [LibraryMethod] - public static async Task InitializeQueue(string queueName) + var adminClient = new ServiceBusAdministrationClient(AzureServiceBusConfiguration.ConnectionString); + // if the queue exists, delete it and re-create it + if (await adminClient.QueueExistsAsync(queueName)) { - ServiceBusAdministrationClient adminClient = new(AzureServiceBusConfiguration.ConnectionString); - // if the queue exists, delete it and re-create it - if (await adminClient.QueueExistsAsync(queueName)) - { - await adminClient.DeleteQueueAsync(queueName); - } - await adminClient.CreateQueueAsync(queueName); - + await adminClient.DeleteQueueAsync(queueName); } + await adminClient.CreateQueueAsync(queueName); + + } - [LibraryMethod] - public static async Task DeleteQueue(string queueName) + [LibraryMethod] + public static async Task DeleteQueue(string queueName) + { + var adminClient = new ServiceBusAdministrationClient(AzureServiceBusConfiguration.ConnectionString); + if (await adminClient.QueueExistsAsync(queueName)) { - ServiceBusAdministrationClient adminClient = new(AzureServiceBusConfiguration.ConnectionString); - if (await adminClient.QueueExistsAsync(queueName)) - { - await adminClient.DeleteQueueAsync(queueName); - } + await adminClient.DeleteQueueAsync(queueName); } + } - [LibraryMethod] - [Transaction] - [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] - public static async Task ExerciseMultipleReceiveOperationsOnAMessage(string queueName) - { - await using var client = new ServiceBusClient(AzureServiceBusConfiguration.ConnectionString); + [LibraryMethod] + [Transaction] + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public static async Task ExerciseMultipleReceiveOperationsOnAMessage(string queueName) + { + await using var client = new ServiceBusClient(AzureServiceBusConfiguration.ConnectionString); - await SendAMessage(client, queueName, "Hello world!"); + await SendAMessage(client, queueName, "Hello world!"); - await using var receiver = client.CreateReceiver(queueName, new ServiceBusReceiverOptions() { ReceiveMode = ServiceBusReceiveMode.PeekLock }); + await using var receiver = client.CreateReceiver(queueName, new ServiceBusReceiverOptions() { ReceiveMode = ServiceBusReceiveMode.PeekLock }); - await receiver.PeekMessageAsync(); + await receiver.PeekMessageAsync(); - // receive the message in peek lock mode - var receivedMessage = await receiver.ReceiveMessageAsync(); + // receive the message in peek lock mode + var receivedMessage = await receiver.ReceiveMessageAsync(); - // renew message lock - await receiver.RenewMessageLockAsync(receivedMessage); + // renew message lock + await receiver.RenewMessageLockAsync(receivedMessage); - // defer the message - await receiver.DeferMessageAsync(receivedMessage); + // defer the message + await receiver.DeferMessageAsync(receivedMessage); - // receive the deferred message - var deferredMessage = await receiver.ReceiveDeferredMessageAsync(receivedMessage.SequenceNumber); + // receive the deferred message + var deferredMessage = await receiver.ReceiveDeferredMessageAsync(receivedMessage.SequenceNumber); - // complete the message - await receiver.CompleteMessageAsync(deferredMessage); - } + // complete the message + await receiver.CompleteMessageAsync(deferredMessage); + } - [LibraryMethod] - [Transaction] - [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] - public static async Task ScheduleAndReceiveAMessage(string queueName) - { - await using var client = new ServiceBusClient(AzureServiceBusConfiguration.ConnectionString); - await using var sender = client.CreateSender(queueName); + [LibraryMethod] + [Transaction] + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public static async Task ScheduleAndReceiveAMessage(string queueName) + { + await using var client = new ServiceBusClient(AzureServiceBusConfiguration.ConnectionString); + await using var sender = client.CreateSender(queueName); - ServiceBusMessage message = new("Hello world!"); - await sender.ScheduleMessageAsync(message, DateTime.UtcNow.AddSeconds(5)); + var message = new ServiceBusMessage("Hello world!"); + await sender.ScheduleMessageAsync(message, DateTime.UtcNow.AddSeconds(5)); - await Task.Delay(TimeSpan.FromSeconds(10)); + await Task.Delay(TimeSpan.FromSeconds(10)); - await using var receiver = client.CreateReceiver(queueName, new ServiceBusReceiverOptions() { ReceiveMode = ServiceBusReceiveMode.ReceiveAndDelete }); - var receivedMsg = await receiver.ReceiveMessageAsync(); - } + await using var receiver = client.CreateReceiver(queueName, new ServiceBusReceiverOptions() { ReceiveMode = ServiceBusReceiveMode.ReceiveAndDelete }); + await receiver.ReceiveMessageAsync(); + } - [LibraryMethod] - [Transaction] - [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] - public static async Task ReceiveAndDeadLetterAMessage(string queueName) - { - await using var client = new ServiceBusClient(AzureServiceBusConfiguration.ConnectionString); + [LibraryMethod] + [Transaction] + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public static async Task ReceiveAndDeadLetterAMessage(string queueName) + { + await using var client = new ServiceBusClient(AzureServiceBusConfiguration.ConnectionString); - await SendAMessage(client, queueName, "Hello world!"); + await SendAMessage(client, queueName, "Hello world!"); - await using var receiver = client.CreateReceiver(queueName, new ServiceBusReceiverOptions() { ReceiveMode = ServiceBusReceiveMode.PeekLock }); + await using var receiver = client.CreateReceiver(queueName, new ServiceBusReceiverOptions() { ReceiveMode = ServiceBusReceiveMode.PeekLock }); - // receive the message in peek lock mode - var receivedMessage = await receiver.ReceiveMessageAsync(); + // receive the message in peek lock mode + var receivedMessage = await receiver.ReceiveMessageAsync(); - // dead-letter the message - await receiver.DeadLetterMessageAsync(receivedMessage); - } + // dead-letter the message + await receiver.DeadLetterMessageAsync(receivedMessage); + } - [LibraryMethod] - [Transaction] - [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] - public static async Task ReceiveAndAbandonAMessage(string queueName) - { - await using var client = new ServiceBusClient(AzureServiceBusConfiguration.ConnectionString); + [LibraryMethod] + [Transaction] + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public static async Task ReceiveAndAbandonAMessage(string queueName) + { + await using var client = new ServiceBusClient(AzureServiceBusConfiguration.ConnectionString); - await SendAMessage(client, queueName, "Hello world!"); + await SendAMessage(client, queueName, "Hello world!"); - await using var receiver = client.CreateReceiver(queueName, new ServiceBusReceiverOptions() { ReceiveMode = ServiceBusReceiveMode.PeekLock }); + await using var receiver = client.CreateReceiver(queueName, new ServiceBusReceiverOptions() { ReceiveMode = ServiceBusReceiveMode.PeekLock }); - // receive the message in peek lock mode - var receivedMessage = await receiver.ReceiveMessageAsync(); + // receive the message in peek lock mode + var receivedMessage = await receiver.ReceiveMessageAsync(); - // abandon the message - it'll go back on the queue - await receiver.AbandonMessageAsync(receivedMessage); + // abandon the message - it'll go back on the queue + await receiver.AbandonMessageAsync(receivedMessage); - // receive the message again and complete it to remove it from the queue - var receivedMessage2 = await receiver.ReceiveMessageAsync(); - await receiver.CompleteMessageAsync(receivedMessage2); } + // receive the message again and complete it to remove it from the queue + var receivedMessage2 = await receiver.ReceiveMessageAsync(); + await receiver.CompleteMessageAsync(receivedMessage2); } - private static async Task SendAMessage(ServiceBusClient client, string queueName, string messageBody) - { - await using var sender = client.CreateSender(queueName); - ServiceBusMessage message = new(messageBody); - await sender.SendMessageAsync(message); - } + private static async Task SendAMessage(ServiceBusClient client, string queueName, string messageBody) + { + await using var sender = client.CreateSender(queueName); + var message = new ServiceBusMessage(messageBody); + await sender.SendMessageAsync(message); } } diff --git a/tests/Agent/IntegrationTests/UnboundedIntegrationTests.sln b/tests/Agent/IntegrationTests/UnboundedIntegrationTests.sln index ee5a75db36..f7c614bfa8 100644 --- a/tests/Agent/IntegrationTests/UnboundedIntegrationTests.sln +++ b/tests/Agent/IntegrationTests/UnboundedIntegrationTests.sln @@ -5,8 +5,17 @@ VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnboundedIntegrationTests", "UnboundedIntegrationTests\UnboundedIntegrationTests.csproj", "{1E17AA8A-B8BA-4A64-892C-B58151293142}" ProjectSection(ProjectDependencies) = postProject + {1D93182F-CF1D-47A1-BC0F-4E27DE9ACEF9} = {1D93182F-CF1D-47A1-BC0F-4E27DE9ACEF9} + {3E8423E9-1FBF-451F-8117-88CE3DADEF7F} = {3E8423E9-1FBF-451F-8117-88CE3DADEF7F} + {5350D3F3-CE09-4992-9756-ACCC92A9129C} = {5350D3F3-CE09-4992-9756-ACCC92A9129C} {5CF71C91-EE5E-4F8D-8FEE-AE497A32BF8B} = {5CF71C91-EE5E-4F8D-8FEE-AE497A32BF8B} + {81A13A3C-2008-4756-8A5F-1E1B4F97CCAD} = {81A13A3C-2008-4756-8A5F-1E1B4F97CCAD} + {8D07A4F7-70FD-40D4-AA95-BA9A98F14CD0} = {8D07A4F7-70FD-40D4-AA95-BA9A98F14CD0} + {A8308E64-6659-4EFB-A9C4-0C48D4C99A61} = {A8308E64-6659-4EFB-A9C4-0C48D4C99A61} + {B64766CA-8731-493F-8417-61A70F783EB7} = {B64766CA-8731-493F-8417-61A70F783EB7} + {C544A8F0-A222-42AF-91C8-9F556C293C11} = {C544A8F0-A222-42AF-91C8-9F556C293C11} {EA98CDD2-655D-4239-9B9F-44EB806DEE53} = {EA98CDD2-655D-4239-9B9F-44EB806DEE53} + {F35861EC-9861-4776-AF51-9C2B7F1DBA5C} = {F35861EC-9861-4776-AF51-9C2B7F1DBA5C} EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IntegrationTestHelpers", "IntegrationTestHelpers\IntegrationTestHelpers.csproj", "{9AABBBB0-32FC-4DA3-A38C-96A50A7ABAC1}" @@ -140,7 +149,7 @@ Global {C544A8F0-A222-42AF-91C8-9F556C293C11} = {129FF113-1F3A-4DAA-8D6F-287DF67A68FA} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {656848E2-BFD2-4092-9328-C6CDF39B1274} EnterpriseLibraryConfigurationToolBinariesPathV6 = packages\EnterpriseLibrary.Common.6.0.1304.0\lib\NET45;packages\EnterpriseLibrary.Data.6.0.1304.0\lib\NET45 + SolutionGuid = {656848E2-BFD2-4092-9328-C6CDF39B1274} EndGlobalSection EndGlobal diff --git a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/AzureServiceBus/AzureServiceBusTests.cs b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/AzureServiceBus/AzureServiceBusTests.cs index 116c31ff41..9b8f41c568 100644 --- a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/AzureServiceBus/AzureServiceBusTests.cs +++ b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/AzureServiceBus/AzureServiceBusTests.cs @@ -48,11 +48,6 @@ protected AzureServiceBusTestsBase(TFixture fixture, ITestOutputHelper output) : .ConfigureFasterTransactionTracesHarvestCycle(25) ; } - //, - //exerciseApplication: () => - //{ - // _fixture.AgentLog.WaitForLogLine(AgentLogBase.ShutdownLogLineRegex, TimeSpan.FromMinutes(2)); - //} ); _fixture.Initialize(); @@ -67,24 +62,24 @@ public void Test() var expectedMetrics = new List { - new Assertions.ExpectedMetric { metricName = $"MessageBroker/AzureServiceBus/Queue/Produce/Named/{_queueName}", callCount = 4}, - new Assertions.ExpectedMetric { metricName = $"MessageBroker/AzureServiceBus/Queue/Produce/Named/{_queueName}", callCount = 1, metricScope = $"{_metricScopeBase}/ExerciseMultipleReceiveOperationsOnAMessage"}, - new Assertions.ExpectedMetric { metricName = $"MessageBroker/AzureServiceBus/Queue/Produce/Named/{_queueName}", callCount = 1, metricScope = $"{_metricScopeBase}/ScheduleAndReceiveAMessage"}, - new Assertions.ExpectedMetric { metricName = $"MessageBroker/AzureServiceBus/Queue/Produce/Named/{_queueName}", callCount = 1, metricScope = $"{_metricScopeBase}/ReceiveAndDeadLetterAMessage"}, - new Assertions.ExpectedMetric { metricName = $"MessageBroker/AzureServiceBus/Queue/Produce/Named/{_queueName}", callCount = 1, metricScope = $"{_metricScopeBase}/ReceiveAndAbandonAMessage"}, - - new Assertions.ExpectedMetric { metricName = $"MessageBroker/AzureServiceBus/Queue/Consume/Named/{_queueName}", callCount = 10}, - new Assertions.ExpectedMetric { metricName = $"MessageBroker/AzureServiceBus/Queue/Consume/Named/{_queueName}", callCount = 5, metricScope = $"{_metricScopeBase}/ExerciseMultipleReceiveOperationsOnAMessage"}, - new Assertions.ExpectedMetric { metricName = $"MessageBroker/AzureServiceBus/Queue/Consume/Named/{_queueName}", callCount = 1, metricScope = $"{_metricScopeBase}/ScheduleAndReceiveAMessage"}, - new Assertions.ExpectedMetric { metricName = $"MessageBroker/AzureServiceBus/Queue/Consume/Named/{_queueName}", callCount = 1, metricScope = $"{_metricScopeBase}/ReceiveAndDeadLetterAMessage"}, - new Assertions.ExpectedMetric { metricName = $"MessageBroker/AzureServiceBus/Queue/Consume/Named/{_queueName}", callCount = 3, metricScope = $"{_metricScopeBase}/ReceiveAndAbandonAMessage"}, - - new Assertions.ExpectedMetric { metricName = $"MessageBroker/AzureServiceBus/Queue/Peek/Named/{_queueName}", callCount = 1 }, - new Assertions.ExpectedMetric { metricName = $"MessageBroker/AzureServiceBus/Queue/Peek/Named/{_queueName}", callCount = 1, metricScope = $"{_metricScopeBase}/ExerciseMultipleReceiveOperationsOnAMessage" }, - - new Assertions.ExpectedMetric { metricName = $"MessageBroker/AzureServiceBus/Queue/Purge/Named/{_queueName}", callCount = 2 }, - new Assertions.ExpectedMetric { metricName = $"MessageBroker/AzureServiceBus/Queue/Purge/Named/{_queueName}", callCount = 1, metricScope = $"{_metricScopeBase}/ReceiveAndDeadLetterAMessage" }, - new Assertions.ExpectedMetric { metricName = $"MessageBroker/AzureServiceBus/Queue/Purge/Named/{_queueName}", callCount = 1, metricScope = $"{_metricScopeBase}/ReceiveAndAbandonAMessage" }, + new() { metricName = $"MessageBroker/AzureServiceBus/Queue/Produce/Named/{_queueName}", callCount = 4}, + new() { metricName = $"MessageBroker/AzureServiceBus/Queue/Produce/Named/{_queueName}", callCount = 1, metricScope = $"{_metricScopeBase}/ExerciseMultipleReceiveOperationsOnAMessage"}, + new() { metricName = $"MessageBroker/AzureServiceBus/Queue/Produce/Named/{_queueName}", callCount = 1, metricScope = $"{_metricScopeBase}/ScheduleAndReceiveAMessage"}, + new() { metricName = $"MessageBroker/AzureServiceBus/Queue/Produce/Named/{_queueName}", callCount = 1, metricScope = $"{_metricScopeBase}/ReceiveAndDeadLetterAMessage"}, + new() { metricName = $"MessageBroker/AzureServiceBus/Queue/Produce/Named/{_queueName}", callCount = 1, metricScope = $"{_metricScopeBase}/ReceiveAndAbandonAMessage"}, + + new() { metricName = $"MessageBroker/AzureServiceBus/Queue/Consume/Named/{_queueName}", callCount = 10}, + new() { metricName = $"MessageBroker/AzureServiceBus/Queue/Consume/Named/{_queueName}", callCount = 5, metricScope = $"{_metricScopeBase}/ExerciseMultipleReceiveOperationsOnAMessage"}, + new() { metricName = $"MessageBroker/AzureServiceBus/Queue/Consume/Named/{_queueName}", callCount = 1, metricScope = $"{_metricScopeBase}/ScheduleAndReceiveAMessage"}, + new() { metricName = $"MessageBroker/AzureServiceBus/Queue/Consume/Named/{_queueName}", callCount = 1, metricScope = $"{_metricScopeBase}/ReceiveAndDeadLetterAMessage"}, + new() { metricName = $"MessageBroker/AzureServiceBus/Queue/Consume/Named/{_queueName}", callCount = 3, metricScope = $"{_metricScopeBase}/ReceiveAndAbandonAMessage"}, + + new() { metricName = $"MessageBroker/AzureServiceBus/Queue/Peek/Named/{_queueName}", callCount = 1 }, + new() { metricName = $"MessageBroker/AzureServiceBus/Queue/Peek/Named/{_queueName}", callCount = 1, metricScope = $"{_metricScopeBase}/ExerciseMultipleReceiveOperationsOnAMessage" }, + + new() { metricName = $"MessageBroker/AzureServiceBus/Queue/Purge/Named/{_queueName}", callCount = 2 }, + new() { metricName = $"MessageBroker/AzureServiceBus/Queue/Purge/Named/{_queueName}", callCount = 1, metricScope = $"{_metricScopeBase}/ReceiveAndDeadLetterAMessage" }, + new() { metricName = $"MessageBroker/AzureServiceBus/Queue/Purge/Named/{_queueName}", callCount = 1, metricScope = $"{_metricScopeBase}/ReceiveAndAbandonAMessage" }, }; var exerciseMultipleReceiveOperationsOnAMessageTransactionEvent = _fixture.AgentLog.TryGetTransactionEvent($"{_metricScopeBase}/ExerciseMultipleReceiveOperationsOnAMessage"); @@ -149,14 +144,9 @@ public void Test() () => Assertions.SpanEventHasAttributes(expectedPeekAgentAttributes, Tests.TestSerializationHelpers.Models.SpanEventAttributeType.Agent, queuePeekSpanEvents), - // peek doesn't emit a span.kind attribute - //() => Assertions.SpanEventHasAttributes(expectedIntrinsicAttributes, - // Tests.TestSerializationHelpers.Models.SpanEventAttributeType.Intrinsic, queuePeekSpanEvents) () => Assertions.SpanEventHasAttributes(expectedPurgeAgentAttributes, Tests.TestSerializationHelpers.Models.SpanEventAttributeType.Agent, queuePurgeSpanEvents) - //() => Assertions.SpanEventHasAttributes(expectedIntrinsicAttributes, - // Tests.TestSerializationHelpers.Models.SpanEventAttributeType.Intrinsic, queuePurgeSpanEvents) ); } } From 81f48aec351a16f92c66c8282796d11e021cf64c Mon Sep 17 00:00:00 2001 From: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com> Date: Mon, 11 Nov 2024 13:08:03 -0600 Subject: [PATCH 10/10] Add tests to validate DT headers --- .../AzureServiceBusExerciser.cs | 13 ++ .../AzureServiceBusW3CTests.cs | 119 ++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 tests/Agent/IntegrationTests/UnboundedIntegrationTests/AzureServiceBus/AzureServiceBusW3CTests.cs diff --git a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/AzureServiceBus/AzureServiceBusExerciser.cs b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/AzureServiceBus/AzureServiceBusExerciser.cs index a815b46072..397e2f0953 100644 --- a/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/AzureServiceBus/AzureServiceBusExerciser.cs +++ b/tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/AzureServiceBus/AzureServiceBusExerciser.cs @@ -130,4 +130,17 @@ private static async Task SendAMessage(ServiceBusClient client, string queueName var message = new ServiceBusMessage(messageBody); await sender.SendMessageAsync(message); } + + [LibraryMethod] + [Transaction] + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public static async Task SendAndReceiveAMessage(string queueName) + { + await using var client = new ServiceBusClient(AzureServiceBusConfiguration.ConnectionString); + + await SendAMessage(client, queueName, "Hello world!"); + + await using var receiver = client.CreateReceiver(queueName, new ServiceBusReceiverOptions() { ReceiveMode = ServiceBusReceiveMode.ReceiveAndDelete }); + await receiver.ReceiveMessageAsync(); + } } diff --git a/tests/Agent/IntegrationTests/UnboundedIntegrationTests/AzureServiceBus/AzureServiceBusW3CTests.cs b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/AzureServiceBus/AzureServiceBusW3CTests.cs new file mode 100644 index 0000000000..4c5d699d04 --- /dev/null +++ b/tests/Agent/IntegrationTests/UnboundedIntegrationTests/AzureServiceBus/AzureServiceBusW3CTests.cs @@ -0,0 +1,119 @@ +// Copyright 2020 New Relic, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Collections.Generic; +using System.Linq; +using NewRelic.Agent.IntegrationTestHelpers; +using NewRelic.Agent.IntegrationTestHelpers.RemoteServiceFixtures; +using NewRelic.Testing.Assertions; +using Xunit; +using Xunit.Abstractions; + +namespace NewRelic.Agent.UnboundedIntegrationTests.AzureServiceBus; + +public abstract class AzureServiceBusW3CTestsBase : NewRelicIntegrationTest + where TFixture : ConsoleDynamicMethodFixture +{ + private readonly TFixture _fixture; + private readonly string _queueName; + + protected AzureServiceBusW3CTestsBase(TFixture fixture, ITestOutputHelper output) : base(fixture) + { + _fixture = fixture; + _fixture.SetTimeout(TimeSpan.FromMinutes(1)); + _fixture.TestLogger = output; + + _queueName = $"test-queue-{Guid.NewGuid()}"; + + _fixture.AddCommand($"AzureServiceBusExerciser InitializeQueue {_queueName}"); + _fixture.AddCommand($"AzureServiceBusExerciser SendAndReceiveAMessage {_queueName}"); + _fixture.AddCommand($"AzureServiceBusExerciser DeleteQueue {_queueName}"); + + _fixture.AddActions + ( + setupConfiguration: () => + { + var configModifier = new NewRelicConfigModifier(fixture.DestinationNewRelicConfigFilePath); + + configModifier.ForceTransactionTraces(); + + configModifier.SetOrDeleteDistributedTraceEnabled(true); + configModifier.SetOrDeleteSpanEventsEnabled(true); + } + ); + + _fixture.Initialize(); + } + + private readonly string _metricScopeBase = "OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.AzureServiceBus.AzureServiceBusExerciser"; + + [Fact] + public void Test() + { + // attributes + + var headerValueTx = _fixture.AgentLog.TryGetTransactionEvent($"{_metricScopeBase}/SendAndReceiveAMessage"); + + var spanEvents = _fixture.AgentLog.GetSpanEvents(); + + var produceSpan = spanEvents.Where(@event => @event.IntrinsicAttributes["name"].ToString().Contains("MessageBroker/AzureServiceBus/Queue/Produce/Named/")) + .FirstOrDefault(); + + var consumeSpan = spanEvents.Where(@event => @event.IntrinsicAttributes["name"].ToString().Contains("MessageBroker/AzureServiceBus/Queue/Consume/Named/")) + .FirstOrDefault(); + + Assert.Equal(headerValueTx.IntrinsicAttributes["guid"], produceSpan.IntrinsicAttributes["transactionId"]); + Assert.Equal(headerValueTx.IntrinsicAttributes["traceId"], produceSpan.IntrinsicAttributes["traceId"]); + Assert.True(AttributeComparer.IsEqualTo(headerValueTx.IntrinsicAttributes["priority"], produceSpan.IntrinsicAttributes["priority"]), + $"priority: expected: {headerValueTx.IntrinsicAttributes["priority"]}, actual: {produceSpan.IntrinsicAttributes["priority"]}"); + + Assert.Equal(headerValueTx.IntrinsicAttributes["guid"], consumeSpan.IntrinsicAttributes["transactionId"]); + Assert.Equal(headerValueTx.IntrinsicAttributes["traceId"], consumeSpan.IntrinsicAttributes["traceId"]); + Assert.True(AttributeComparer.IsEqualTo(headerValueTx.IntrinsicAttributes["priority"], consumeSpan.IntrinsicAttributes["priority"]), + $"priority: expected: {headerValueTx.IntrinsicAttributes["priority"]}, actual: {consumeSpan.IntrinsicAttributes["priority"]}"); + + // metrics + + var expectedMetrics = new List + { + new Assertions.ExpectedMetric { metricName = $"Supportability/DistributedTrace/CreatePayload/Success", callCount = 1}, + new Assertions.ExpectedMetric { metricName = $"Supportability/TraceContext/Create/Success", callCount = 1}, + }; + + var metrics = _fixture.AgentLog.GetMetrics(); + Assertions.MetricsExist(expectedMetrics, metrics); + } +} + +[NetFrameworkTest] +public class AzureServiceBusW3CTestsFWLatest : AzureServiceBusW3CTestsBase +{ + public AzureServiceBusW3CTestsFWLatest(ConsoleDynamicMethodFixtureFWLatest fixture, ITestOutputHelper output) : base(fixture, output) + { + } +} + +[NetFrameworkTest] +public class AzureServiceBusW3CTestsFW462 : AzureServiceBusW3CTestsBase +{ + public AzureServiceBusW3CTestsFW462(ConsoleDynamicMethodFixtureFW462 fixture, ITestOutputHelper output) : base(fixture, output) + { + } +} + +[NetCoreTest] +public class AzureServiceBusW3CTestsCoreOldest : AzureServiceBusW3CTestsBase +{ + public AzureServiceBusW3CTestsCoreOldest(ConsoleDynamicMethodFixtureCoreOldest fixture, ITestOutputHelper output) : base(fixture, output) + { + } +} + +[NetCoreTest] +public class AzureServiceBusW3CTestsCoreLatest : AzureServiceBusW3CTestsBase +{ + public AzureServiceBusW3CTestsCoreLatest(ConsoleDynamicMethodFixtureCoreLatest fixture, ITestOutputHelper output) : base(fixture, output) + { + } +}