From 0144baf21e281e83be0b95634e083c5de2fc1acb Mon Sep 17 00:00:00 2001 From: Sara Pellegrini Date: Thu, 30 May 2024 13:55:39 +0200 Subject: [PATCH 01/76] Register handling time metrics. --- .../Incoming/InvokeHandlerTerminatorTest.cs | 2 +- .../Hosting/HostingComponent.Configuration.cs | 9 +++- .../Metrics/HandlingMetricsFactory.cs | 47 +++++++++++++++++++ .../OpenTelemetry/Metrics/MeterTags.cs | 1 + .../OpenTelemetry/Metrics/Meters.cs | 3 ++ .../Metrics/NoOpHandlingMetricsFactory.cs | 28 +++++++++++ .../Pipeline/Incoming/IHandlingMetrics.cs | 24 ++++++++++ .../Incoming/IHandlingMetricsFactory.cs | 17 +++++++ .../Incoming/InvokeHandlerTerminator.cs | 12 ++--- .../Receiving/ReceiveComponent.cs | 2 +- 10 files changed, 133 insertions(+), 12 deletions(-) create mode 100644 src/NServiceBus.Core/OpenTelemetry/Metrics/HandlingMetricsFactory.cs create mode 100644 src/NServiceBus.Core/OpenTelemetry/Metrics/NoOpHandlingMetricsFactory.cs create mode 100644 src/NServiceBus.Core/Pipeline/Incoming/IHandlingMetrics.cs create mode 100644 src/NServiceBus.Core/Pipeline/Incoming/IHandlingMetricsFactory.cs diff --git a/src/NServiceBus.Core.Tests/Pipeline/Incoming/InvokeHandlerTerminatorTest.cs b/src/NServiceBus.Core.Tests/Pipeline/Incoming/InvokeHandlerTerminatorTest.cs index 986a6219c91..c7fdd688441 100644 --- a/src/NServiceBus.Core.Tests/Pipeline/Incoming/InvokeHandlerTerminatorTest.cs +++ b/src/NServiceBus.Core.Tests/Pipeline/Incoming/InvokeHandlerTerminatorTest.cs @@ -11,7 +11,7 @@ [TestFixture] public class InvokeHandlerTerminatorTest { - InvokeHandlerTerminator terminator = new InvokeHandlerTerminator(new NoOpActivityFactory()); + InvokeHandlerTerminator terminator = new InvokeHandlerTerminator(new NoOpActivityFactory(), new NoOpHandlingMetricsFactory()); [Test] public async Task When_saga_found_and_handler_is_saga_should_invoke_handler() diff --git a/src/NServiceBus.Core/Hosting/HostingComponent.Configuration.cs b/src/NServiceBus.Core/Hosting/HostingComponent.Configuration.cs index 46459200950..9ec23baf657 100644 --- a/src/NServiceBus.Core/Hosting/HostingComponent.Configuration.cs +++ b/src/NServiceBus.Core/Hosting/HostingComponent.Configuration.cs @@ -28,7 +28,8 @@ public static Configuration PrepareConfiguration(Settings settings, AssemblyScan settings.InstallationUserName, settings.ShouldRunInstallers, settings.UserRegistrations, - settings.EnableOpenTelemetry ? new ActivityFactory() : new NoOpActivityFactory()); + settings.EnableOpenTelemetry ? new ActivityFactory() : new NoOpActivityFactory(), + settings.EnableOpenTelemetry ? new HandlingMetricsFactory(settings.EndpointName) : new NoOpHandlingMetricsFactory()); return configuration; } @@ -46,7 +47,8 @@ public Configuration(Settings settings, string installationUserName, bool shouldRunInstallers, List> userRegistrations, - IActivityFactory activityFactory) + IActivityFactory activityFactory, + IHandlingMetricsFactory handlingMetricsFactory) { AvailableTypes = availableTypes; CriticalError = criticalError; @@ -59,6 +61,7 @@ public Configuration(Settings settings, ShouldRunInstallers = shouldRunInstallers; UserRegistrations = userRegistrations; ActivityFactory = activityFactory; + HandlingMetricsFactory = handlingMetricsFactory; settings.ApplyHostIdDefaultIfNeeded(); HostInformation = new HostInformation(settings.HostId, settings.DisplayName, settings.Properties); @@ -92,5 +95,7 @@ public void AddStartupDiagnosticsSection(string sectionName, object section) public List> UserRegistrations { get; } public IActivityFactory ActivityFactory { get; set; } + + public IHandlingMetricsFactory HandlingMetricsFactory { get; set; } } } \ No newline at end of file diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/HandlingMetricsFactory.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/HandlingMetricsFactory.cs new file mode 100644 index 00000000000..314152f9f47 --- /dev/null +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/HandlingMetricsFactory.cs @@ -0,0 +1,47 @@ +namespace NServiceBus; + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Pipeline; + +class HandlingMetricsFactory(string queueName/*, string discriminator*/) : IHandlingMetricsFactory +{ + public IHandlingMetrics StartHandling(IInvokeHandlerContext context) + { + var handlerType = context.MessageHandler.Instance?.GetType(); + var messageType = context.MessageBeingHandled?.GetType(); + return new RecordHandlingMetric(new( + [ + new(MeterTags.QueueName, queueName ?? ""), + // new(MeterTags.EndpointDiscriminator, discriminator ?? ""), + new(MeterTags.MessageHandlerType, handlerType), + new(MeterTags.MessageType, messageType) + ])); + } +} + +class RecordHandlingMetric : IHandlingMetrics +{ + public RecordHandlingMetric(TagList tags) + { + this.tags = tags; + stopWatch.Start(); + } + + public void OnSuccess() + { + stopWatch.Stop(); + Meters.HandlingTime.Record(stopWatch.ElapsedMilliseconds, tags); + } + + public void OnFailure(Exception error) + { + stopWatch.Stop(); + tags.Add(new KeyValuePair(MeterTags.FailureType, error.GetType())); + Meters.HandlingTime.Record(stopWatch.ElapsedMilliseconds, tags); + } + + readonly Stopwatch stopWatch = new(); + TagList tags; +} \ No newline at end of file diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/MeterTags.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/MeterTags.cs index 4c068d9b500..3ef4be43de6 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/MeterTags.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/MeterTags.cs @@ -7,4 +7,5 @@ static class MeterTags public const string MessageType = "nservicebus.message_type"; public const string FailureType = "nservicebus.failure_type"; public const string MessageHandlerTypes = "nservicebus.message_handler_types"; + public const string MessageHandlerType = "nservicebus.message_handler_type"; } \ No newline at end of file diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/Meters.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/Meters.cs index 62722a78ea1..dcd9e9267d5 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/Meters.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/Meters.cs @@ -17,6 +17,9 @@ class Meters internal static readonly Counter TotalFailures = NServiceBusMeter.CreateCounter("nservicebus.messaging.failures", description: "Total number of messages processed unsuccessfully by the endpoint."); + internal static readonly Histogram HandlingTime = + NServiceBusMeter.CreateHistogram("nservicebus.messaging.handling_time", "ms", "The time in milliseconds for the execution of the business code."); + internal static readonly Counter TotalImmediateRetries = NServiceBusMeter.CreateCounter("nservicebus.recoverability.immediate", description: "Total number of immediate retries requested."); diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/NoOpHandlingMetricsFactory.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/NoOpHandlingMetricsFactory.cs new file mode 100644 index 00000000000..01309ac0e86 --- /dev/null +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/NoOpHandlingMetricsFactory.cs @@ -0,0 +1,28 @@ +namespace NServiceBus; + +using System; +using Pipeline; + +/// +/// Implementation of IHandlingMetricsFactory that does not perform anything. +/// +public class NoOpHandlingMetricsFactory : IHandlingMetricsFactory +{ + /// + /// Instantiates a new IHandlingMetrics that does not record any metric. + /// + /// The invocation context. + /// + public IHandlingMetrics StartHandling(IInvokeHandlerContext context) => new NoOpHandlingMetrics(); +} + +class NoOpHandlingMetrics : IHandlingMetrics +{ + public void OnSuccess() + { + } + + public void OnFailure(Exception error) + { + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Incoming/IHandlingMetrics.cs b/src/NServiceBus.Core/Pipeline/Incoming/IHandlingMetrics.cs new file mode 100644 index 00000000000..8be735120e5 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Incoming/IHandlingMetrics.cs @@ -0,0 +1,24 @@ +namespace NServiceBus; + +using System; + +/// +/// Registers the metrics related to a specific message handler execution. +/// +public interface IHandlingMetrics +{ + /// + /// Registers the metrics related to the successful completion of the handler's execution. + /// This method is invoked only after the handler execution completed successfully. + /// This method will never be invoked if OnFailure method is invoked on the same instance. + /// + void OnSuccess(); + + /// + /// Registers the metrics related to the failed handler's execution. + /// This method is invoked only after the handler execution throws an exception. + /// This method will never be invoked if OnSuccess method is invoked on the same instance. + /// + /// The exception thrown by the handler. + void OnFailure(Exception error); +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Incoming/IHandlingMetricsFactory.cs b/src/NServiceBus.Core/Pipeline/Incoming/IHandlingMetricsFactory.cs new file mode 100644 index 00000000000..a02ed1c98e2 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Incoming/IHandlingMetricsFactory.cs @@ -0,0 +1,17 @@ +namespace NServiceBus; + +using Pipeline; + +/// +/// Factory for IHandlingMetrics needed to record metrics for the handler invocation. +/// +public interface IHandlingMetricsFactory +{ + /// + /// Creates a new IHandlingMetrics instance for recording the metrics for a specific handler execution. + /// + /// Needed to properly initialize the IHandlingMetrics instance. + /// The newly created IHandlingMetrics instance. + IHandlingMetrics StartHandling(IInvokeHandlerContext context); +} + diff --git a/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs b/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs index ddccce8a2fa..f99f609a1ad 100644 --- a/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs +++ b/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs @@ -6,12 +6,8 @@ using Pipeline; using Sagas; -class InvokeHandlerTerminator : PipelineTerminator +class InvokeHandlerTerminator(IActivityFactory activityFactory, IHandlingMetricsFactory metricsFactory) : PipelineTerminator { - public InvokeHandlerTerminator(IActivityFactory activityFactory) - { - this.activityFactory = activityFactory; - } protected override async Task Terminate(IInvokeHandlerContext context) { @@ -26,7 +22,7 @@ protected override async Task Terminate(IInvokeHandlerContext context) // Might as well abort before invoking the handler if we're shutting down context.CancellationToken.ThrowIfCancellationRequested(); - + IHandlingMetrics handlingMetrics = metricsFactory.StartHandling(context); var startTime = DateTimeOffset.UtcNow; try { @@ -36,6 +32,7 @@ await messageHandler .ConfigureAwait(false); activity?.SetStatus(ActivityStatusCode.Ok); + handlingMetrics.OnSuccess(); } #pragma warning disable PS0019 // Do not catch Exception without considering OperationCanceledException - enriching and rethrowing catch (Exception ex) @@ -48,10 +45,9 @@ await messageHandler ex.Data["Handler canceled"] = context.CancellationToken.IsCancellationRequested; activity?.SetErrorStatus(ex); - + handlingMetrics.OnFailure(ex); throw; } } - readonly IActivityFactory activityFactory; } \ No newline at end of file diff --git a/src/NServiceBus.Core/Receiving/ReceiveComponent.cs b/src/NServiceBus.Core/Receiving/ReceiveComponent.cs index bcc6050f667..fecd3016ded 100644 --- a/src/NServiceBus.Core/Receiving/ReceiveComponent.cs +++ b/src/NServiceBus.Core/Receiving/ReceiveComponent.cs @@ -71,7 +71,7 @@ public static ReceiveComponent Configure( return new LoadHandlersConnector(b.GetRequiredService()); }, "Gets all the handlers to invoke from the MessageHandler registry based on the message type."); - pipelineSettings.Register("InvokeHandlers", new InvokeHandlerTerminator(hostingConfiguration.ActivityFactory), "Calls the IHandleMessages.Handle(T)"); + pipelineSettings.Register("InvokeHandlers", new InvokeHandlerTerminator(hostingConfiguration.ActivityFactory, hostingConfiguration.HandlingMetricsFactory), "Calls the IHandleMessages.Handle(T)"); var handlerDiagnostics = new Dictionary>(); From c87fbb803505d25caae1482a42fd6b757dc7dfcd Mon Sep 17 00:00:00 2001 From: Sara Pellegrini Date: Fri, 31 May 2024 08:59:43 +0200 Subject: [PATCH 02/76] The API has been updated, and accordingly the approved txt. This implied an increase in the version, that still remains 0.x since the whole metrics module is in experimental state. --- .../ApprovalFiles/MeterTagsTests.Verify_MeterTags.approved.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTagsTests.Verify_MeterTags.approved.txt b/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTagsTests.Verify_MeterTags.approved.txt index 333deef4bb9..7accce2c001 100644 --- a/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTagsTests.Verify_MeterTags.approved.txt +++ b/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTagsTests.Verify_MeterTags.approved.txt @@ -7,5 +7,6 @@ "MessageType => nservicebus.message_type", "FailureType => nservicebus.failure_type", "MessageHandlerTypes => nservicebus.message_handler_types" + "MessageHandlerType => nservicebus.message_handler_type" ] } \ No newline at end of file From 28821570ea8d7479b84655b175165cee29c0f920 Mon Sep 17 00:00:00 2001 From: Sara Pellegrini Date: Fri, 31 May 2024 09:30:32 +0200 Subject: [PATCH 03/76] Add missing documentation for return value. --- .../OpenTelemetry/Metrics/NoOpHandlingMetricsFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/NoOpHandlingMetricsFactory.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/NoOpHandlingMetricsFactory.cs index 01309ac0e86..dacea478d45 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/NoOpHandlingMetricsFactory.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/NoOpHandlingMetricsFactory.cs @@ -12,7 +12,7 @@ public class NoOpHandlingMetricsFactory : IHandlingMetricsFactory /// Instantiates a new IHandlingMetrics that does not record any metric. /// /// The invocation context. - /// + /// The instantiated IHandlingMetrics. public IHandlingMetrics StartHandling(IInvokeHandlerContext context) => new NoOpHandlingMetrics(); } From 9c88d3987d0e0dbd3ca54f2f9baf7f90aae20a40 Mon Sep 17 00:00:00 2001 From: Sara Pellegrini Date: Fri, 31 May 2024 10:03:30 +0200 Subject: [PATCH 04/76] Newly introduced classes and interfaces should not be public. --- .../OpenTelemetry/Metrics/NoOpHandlingMetricsFactory.cs | 2 +- src/NServiceBus.Core/Pipeline/Incoming/IHandlingMetrics.cs | 2 +- .../Pipeline/Incoming/IHandlingMetricsFactory.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/NoOpHandlingMetricsFactory.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/NoOpHandlingMetricsFactory.cs index dacea478d45..76a460e2ab3 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/NoOpHandlingMetricsFactory.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/NoOpHandlingMetricsFactory.cs @@ -6,7 +6,7 @@ /// /// Implementation of IHandlingMetricsFactory that does not perform anything. /// -public class NoOpHandlingMetricsFactory : IHandlingMetricsFactory +class NoOpHandlingMetricsFactory : IHandlingMetricsFactory { /// /// Instantiates a new IHandlingMetrics that does not record any metric. diff --git a/src/NServiceBus.Core/Pipeline/Incoming/IHandlingMetrics.cs b/src/NServiceBus.Core/Pipeline/Incoming/IHandlingMetrics.cs index 8be735120e5..92ee6cc9af5 100644 --- a/src/NServiceBus.Core/Pipeline/Incoming/IHandlingMetrics.cs +++ b/src/NServiceBus.Core/Pipeline/Incoming/IHandlingMetrics.cs @@ -5,7 +5,7 @@ /// /// Registers the metrics related to a specific message handler execution. /// -public interface IHandlingMetrics +interface IHandlingMetrics { /// /// Registers the metrics related to the successful completion of the handler's execution. diff --git a/src/NServiceBus.Core/Pipeline/Incoming/IHandlingMetricsFactory.cs b/src/NServiceBus.Core/Pipeline/Incoming/IHandlingMetricsFactory.cs index a02ed1c98e2..15bff49d4e0 100644 --- a/src/NServiceBus.Core/Pipeline/Incoming/IHandlingMetricsFactory.cs +++ b/src/NServiceBus.Core/Pipeline/Incoming/IHandlingMetricsFactory.cs @@ -5,7 +5,7 @@ /// /// Factory for IHandlingMetrics needed to record metrics for the handler invocation. /// -public interface IHandlingMetricsFactory +interface IHandlingMetricsFactory { /// /// Creates a new IHandlingMetrics instance for recording the metrics for a specific handler execution. From a52abb8969e89919badea6d907c77628adf29f8f Mon Sep 17 00:00:00 2001 From: Sara Pellegrini Date: Fri, 31 May 2024 10:10:48 +0200 Subject: [PATCH 05/76] For tags, replace the complete Type descriptor with the FullName, that is shorter and so cheaper. --- .../OpenTelemetry/Metrics/HandlingMetricsFactory.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/HandlingMetricsFactory.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/HandlingMetricsFactory.cs index 314152f9f47..1c70b3d8b2b 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/HandlingMetricsFactory.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/HandlingMetricsFactory.cs @@ -9,8 +9,8 @@ class HandlingMetricsFactory(string queueName/*, string discriminator*/) : IHand { public IHandlingMetrics StartHandling(IInvokeHandlerContext context) { - var handlerType = context.MessageHandler.Instance?.GetType(); - var messageType = context.MessageBeingHandled?.GetType(); + var handlerType = context.MessageHandler.Instance?.GetType().FullName; + var messageType = context.MessageBeingHandled?.GetType().FullName; return new RecordHandlingMetric(new( [ new(MeterTags.QueueName, queueName ?? ""), @@ -38,7 +38,7 @@ public void OnSuccess() public void OnFailure(Exception error) { stopWatch.Stop(); - tags.Add(new KeyValuePair(MeterTags.FailureType, error.GetType())); + tags.Add(new KeyValuePair(MeterTags.FailureType, error.GetType().FullName)); Meters.HandlingTime.Record(stopWatch.ElapsedMilliseconds, tags); } From 6822589ad25a353b3313c5410925eb6308d6ab90 Mon Sep 17 00:00:00 2001 From: Sara Pellegrini Date: Fri, 31 May 2024 17:13:51 +0200 Subject: [PATCH 06/76] Exclusion of generated internal struct from the Namespace validation - this change was initially required by the code in HandlingMetricsFactory. --- src/NServiceBus.Core.Tests/StandardsTests.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/NServiceBus.Core.Tests/StandardsTests.cs b/src/NServiceBus.Core.Tests/StandardsTests.cs index 3eb12566d96..bb5a9793bc7 100644 --- a/src/NServiceBus.Core.Tests/StandardsTests.cs +++ b/src/NServiceBus.Core.Tests/StandardsTests.cs @@ -55,7 +55,16 @@ public void NonPublicShouldHaveSimpleNamespace() static bool IsCompilerGenerated(Type x) { - return Attribute.IsDefined(x, typeof(CompilerGeneratedAttribute), false); + if (Attribute.IsDefined(x, typeof(CompilerGeneratedAttribute), false)) + { + return true; + } + // Exclusion of generated internal struct - this change was initially required by the code in HandlingMetricsFactory + if (x.Namespace is null && x.Name[0] == '<') + { + return true; + } + return false; } [Test] From 63df3f85d46c7a12390f44f59fdc68c40c2d8bb9 Mon Sep 17 00:00:00 2001 From: Sara Pellegrini Date: Mon, 3 Jun 2024 13:48:25 +0200 Subject: [PATCH 07/76] Tests massage handling time metrics. --- .../Metrics/TestingMetricListener.cs | 13 +++- ...n_incoming_message_handled_successfully.cs | 64 +++++++++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled_successfully.cs diff --git a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/Metrics/TestingMetricListener.cs b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/Metrics/TestingMetricListener.cs index a9e15433c54..7d81b2366e0 100644 --- a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/Metrics/TestingMetricListener.cs +++ b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/Metrics/TestingMetricListener.cs @@ -36,7 +36,18 @@ public TestingMetricListener(string sourceName) ReportedMeters.AddOrUpdate(instrument.Name, measurement, (_, val) => val + measurement); Tags.AddOrUpdate(instrument.Name, _ => tags, (_, _) => tags); }); - meterListener.Start(); + meterListener.SetMeasurementEventCallback((Instrument instrument, + double measurement, + ReadOnlySpan> t, + object _) => + { + TestContext.WriteLine($"{instrument.Meter.Name}\\{instrument.Name}:{measurement}"); + var tags = t.ToArray(); + ReportedMeters.AddOrUpdate(instrument.Name, 1, (_, val) => val + 1); + Tags.AddOrUpdate(instrument.Name, _ => tags, (_, _) => tags); + }); + meterListener.Start() + ; } public static TestingMetricListener SetupNServiceBusMetricsListener() => diff --git a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled_successfully.cs b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled_successfully.cs new file mode 100644 index 00000000000..6e5bdb561bd --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled_successfully.cs @@ -0,0 +1,64 @@ +namespace NServiceBus.AcceptanceTests.Core.OpenTelemetry; + +using System.Threading; +using System.Threading.Tasks; +using AcceptanceTesting; +using NUnit.Framework; +using Conventions = AcceptanceTesting.Customization.Conventions; + +public class WhenIncomingMessageHandledSuccessfully : NServiceBusAcceptanceTest +{ + [Test] + public async Task Should_record_handling_time() + { + using var metricsListener = TestingMetricListener.SetupNServiceBusMetricsListener(); + + _ = await Scenario.Define() + .WithEndpoint(b => + b.When(async (session, _) => + { + for (var x = 0; x < 5; x++) + { + await session.SendLocal(new MyMessage()); + } + })) + .Done(c => c.TotalHandledMessages == 5) + .Run(); + + string handlingTime = "nservicebus.messaging.handling_time"; + metricsListener.AssertMetric(handlingTime, 5); + var messageType = metricsListener.AssertTagKeyExists(handlingTime, "nservicebus.message_type"); + Assert.AreEqual(typeof(MyMessage).FullName, messageType); + var handlerType = metricsListener.AssertTagKeyExists(handlingTime, "nservicebus.message_handler_type"); + Assert.AreEqual(typeof(MyMessageHandler).FullName, handlerType); + var endpoint = metricsListener.AssertTagKeyExists(handlingTime, "nservicebus.queue"); + Assert.AreEqual(Conventions.EndpointNamingConvention(typeof(EndpointWithMetrics)), endpoint); + } + + class Context : ScenarioContext + { + public int TotalHandledMessages; + } + + class EndpointWithMetrics : EndpointConfigurationBuilder + { + public EndpointWithMetrics() => EndpointSetup(); + } + + class MyMessageHandler : IHandleMessages + { + readonly Context testContext; + + public MyMessageHandler(Context testContext) => this.testContext = testContext; + + public Task Handle(MyMessage message, IMessageHandlerContext context) + { + Interlocked.Increment(ref testContext.TotalHandledMessages); + return Task.CompletedTask; + } + } + + class MyMessage : IMessage + { + } +} \ No newline at end of file From c88b47e202094e8a6de42e4766dd0914cc85c3d7 Mon Sep 17 00:00:00 2001 From: Sara Pellegrini Date: Mon, 3 Jun 2024 14:42:32 +0200 Subject: [PATCH 08/76] Introduce new unit test for metrics' name validation. Fix failing tests. --- ...When_incoming_message_handled_successfully.cs | 4 ++-- .../MeterTagsTests.Verify_Metrics.approved.txt | 10 ++++++++++ .../OpenTelemetry/MeterTagsTests.cs | 16 ++++++++++++++++ .../OpenTelemetry/Metrics/Meters.cs | 8 ++++---- .../OpenTelemetry/Metrics/Metrics.cs | 9 +++++++++ 5 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 src/NServiceBus.Core.Tests/ApprovalFiles/MeterTagsTests.Verify_Metrics.approved.txt create mode 100644 src/NServiceBus.Core/OpenTelemetry/Metrics/Metrics.cs diff --git a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled_successfully.cs b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled_successfully.cs index 6e5bdb561bd..295f50f4ccd 100644 --- a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled_successfully.cs +++ b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled_successfully.cs @@ -6,7 +6,7 @@ using NUnit.Framework; using Conventions = AcceptanceTesting.Customization.Conventions; -public class WhenIncomingMessageHandledSuccessfully : NServiceBusAcceptanceTest +public class WhenIncomingMessageHandledSuccessfully : OpenTelemetryAcceptanceTest { [Test] public async Task Should_record_handling_time() @@ -58,7 +58,7 @@ public Task Handle(MyMessage message, IMessageHandlerContext context) } } - class MyMessage : IMessage + public class MyMessage : IMessage { } } \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTagsTests.Verify_Metrics.approved.txt b/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTagsTests.Verify_Metrics.approved.txt new file mode 100644 index 00000000000..4981d4e9738 --- /dev/null +++ b/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTagsTests.Verify_Metrics.approved.txt @@ -0,0 +1,10 @@ +{ + "Note": "Changes to metrics name should result in Meters version updates", + "ActivitySourceVersion": "0.2.0", + "Metrics": [ + "TotalProcessedSuccessfully" = "nservicebus.messaging.successes"; + "TotalFetched" = "nservicebus.messaging.fetches"; + "TotalFailures" = "nservicebus.messaging.failures"; + "HandlingTime" = "nservicebus.messaging.handling_time"; + ] +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTagsTests.cs b/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTagsTests.cs index 5e5fa1a47f3..e4fab96e914 100644 --- a/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTagsTests.cs +++ b/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTagsTests.cs @@ -24,4 +24,20 @@ public void Verify_MeterTags() Tags = meterTags }); } + + public void Verify_Metrics() + { + var meterTags = typeof(Meters) + .GetFields(BindingFlags.Public | BindingFlags.Static) + .Where(fi => fi.IsLiteral && !fi.IsInitOnly) + .Select(x => $"{x.Name} => {x.GetRawConstantValue()}") + .ToList(); + + Approver.Verify(new + { + Note = "Changes to metrics' names should result in Meters version updates", + ActivitySourceVersion = Meters.NServiceBusMeter.Version, + Tags = meterTags + }); + } } \ No newline at end of file diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/Meters.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/Meters.cs index dcd9e9267d5..efae9ab8997 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/Meters.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/Meters.cs @@ -9,16 +9,16 @@ class Meters "0.2.0"); internal static readonly Counter TotalProcessedSuccessfully = - NServiceBusMeter.CreateCounter("nservicebus.messaging.successes", description: "Total number of messages processed successfully by the endpoint."); + NServiceBusMeter.CreateCounter(Metrics.TotalProcessedSuccessfully, description: "Total number of messages processed successfully by the endpoint."); internal static readonly Counter TotalFetched = - NServiceBusMeter.CreateCounter("nservicebus.messaging.fetches", description: "Total number of messages fetched from the queue by the endpoint."); + NServiceBusMeter.CreateCounter(Metrics.TotalFetched, description: "Total number of messages fetched from the queue by the endpoint."); internal static readonly Counter TotalFailures = - NServiceBusMeter.CreateCounter("nservicebus.messaging.failures", description: "Total number of messages processed unsuccessfully by the endpoint."); + NServiceBusMeter.CreateCounter(Metrics.TotalFailures, description: "Total number of messages processed unsuccessfully by the endpoint."); internal static readonly Histogram HandlingTime = - NServiceBusMeter.CreateHistogram("nservicebus.messaging.handling_time", "ms", "The time in milliseconds for the execution of the business code."); + NServiceBusMeter.CreateHistogram(Metrics.HandlingTime, "ms", "The time in milliseconds for the execution of the business code."); internal static readonly Counter TotalImmediateRetries = NServiceBusMeter.CreateCounter("nservicebus.recoverability.immediate", description: "Total number of immediate retries requested."); diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/Metrics.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/Metrics.cs new file mode 100644 index 00000000000..2930e2dc7ff --- /dev/null +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/Metrics.cs @@ -0,0 +1,9 @@ +namespace NServiceBus; + +static class Metrics +{ + public const string TotalProcessedSuccessfully = "nservicebus.messaging.successes"; + public const string TotalFetched = "nservicebus.messaging.fetches"; + public const string TotalFailures = "nservicebus.messaging.failures"; + public const string HandlingTime = "nservicebus.messaging.handling_time"; +} \ No newline at end of file From 2611b84a92148851101f01f5f7163e5523b9c013 Mon Sep 17 00:00:00 2001 From: Sara Pellegrini Date: Mon, 3 Jun 2024 15:20:57 +0200 Subject: [PATCH 09/76] Fix metrics unit test. --- .../MeterTagsTests.Verify_Metrics.approved.txt | 10 +++++----- .../OpenTelemetry/MeterTagsTests.cs | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTagsTests.Verify_Metrics.approved.txt b/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTagsTests.Verify_Metrics.approved.txt index 4981d4e9738..40946bbf94d 100644 --- a/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTagsTests.Verify_Metrics.approved.txt +++ b/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTagsTests.Verify_Metrics.approved.txt @@ -1,10 +1,10 @@ { - "Note": "Changes to metrics name should result in Meters version updates", + "Note": "Changes to metrics' names should result in Meters version updates", "ActivitySourceVersion": "0.2.0", "Metrics": [ - "TotalProcessedSuccessfully" = "nservicebus.messaging.successes"; - "TotalFetched" = "nservicebus.messaging.fetches"; - "TotalFailures" = "nservicebus.messaging.failures"; - "HandlingTime" = "nservicebus.messaging.handling_time"; + "TotalProcessedSuccessfully => nservicebus.messaging.successes", + "TotalFetched => nservicebus.messaging.fetches", + "TotalFailures => nservicebus.messaging.failures", + "HandlingTime => nservicebus.messaging.handling_time" ] } \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTagsTests.cs b/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTagsTests.cs index e4fab96e914..36b5b4985e5 100644 --- a/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTagsTests.cs +++ b/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTagsTests.cs @@ -25,19 +25,19 @@ public void Verify_MeterTags() }); } + [Test] public void Verify_Metrics() { - var meterTags = typeof(Meters) + var metrics = typeof(Metrics) .GetFields(BindingFlags.Public | BindingFlags.Static) .Where(fi => fi.IsLiteral && !fi.IsInitOnly) .Select(x => $"{x.Name} => {x.GetRawConstantValue()}") .ToList(); - Approver.Verify(new { Note = "Changes to metrics' names should result in Meters version updates", ActivitySourceVersion = Meters.NServiceBusMeter.Version, - Tags = meterTags + Metrics = metrics }); } } \ No newline at end of file From 8b26cb4d31bf140a03110fbbdec9664f779ffde1 Mon Sep 17 00:00:00 2001 From: Sara Pellegrini Date: Tue, 4 Jun 2024 16:27:15 +0200 Subject: [PATCH 10/76] Tests improvements. --- .../When_incoming_message_handled.cs | 124 ++++++++++++++++++ ...n_incoming_message_handled_successfully.cs | 64 --------- ...MeterTagsTests.Verify_Metrics.approved.txt | 10 -- .../MeterTests.Verify_MeterAPI.approved.txt | 17 +++ .../{MeterTagsTests.cs => MeterTests.cs} | 19 +-- 5 files changed, 145 insertions(+), 89 deletions(-) create mode 100644 src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled.cs delete mode 100644 src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled_successfully.cs delete mode 100644 src/NServiceBus.Core.Tests/ApprovalFiles/MeterTagsTests.Verify_Metrics.approved.txt create mode 100644 src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt rename src/NServiceBus.Core.Tests/OpenTelemetry/{MeterTagsTests.cs => MeterTests.cs} (65%) diff --git a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled.cs b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled.cs new file mode 100644 index 00000000000..0a60649df58 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled.cs @@ -0,0 +1,124 @@ +namespace NServiceBus.AcceptanceTests.Core.OpenTelemetry; + +using System; +using System.Threading; +using System.Threading.Tasks; +using AcceptanceTesting; +using NUnit.Framework; +using Conventions = AcceptanceTesting.Customization.Conventions; + +public class When_incoming_message_handled : OpenTelemetryAcceptanceTest +{ + static readonly string HandlingTimeMetricName = "nservicebus.messaging.handling_time"; + + [Test] + public async Task Should_record_success_handling_time() + { + using TestingMetricListener metricsListener = await WhenMessagesHandled(() => new MyMessage()); + metricsListener.AssertMetric(HandlingTimeMetricName, 5); + AsserMandatoryTags(metricsListener, typeof(MyMessage), typeof(MyMessageHandler)); + } + + [Test] + public async Task Should_record_failure_handling_time() + { + using TestingMetricListener metricsListener = await WhenMessagesHandled(() => new MyExceptionalMessage()); + metricsListener.AssertMetric(HandlingTimeMetricName, 5); + AsserMandatoryTags(metricsListener, typeof(MyExceptionalMessage), typeof(MyExceptionalHandler)); + var exception = metricsListener.AssertTagKeyExists(HandlingTimeMetricName, "nservicebus.failure_type"); + Console.WriteLine(exception); + Assert.AreEqual(typeof(Exception).FullName, exception); + } + + static async Task WhenMessagesHandled(Func messageFactory) + { + TestingMetricListener metricsListener = null; + try + { + metricsListener = TestingMetricListener.SetupNServiceBusMetricsListener(); + + _ = await Scenario.Define() + .WithEndpoint(b => + b.DoNotFailOnErrorMessages() + .When(async (session, _) => + { + for (var x = 0; x < 5; x++) + { + try + { + await session.SendLocal(messageFactory.Invoke()); + } + catch (Exception e) + { + Console.WriteLine(e); + } + } + })) + .Done(c => c.TotalHandledMessages == 5) + .Run(); + return metricsListener; + } + catch + { + metricsListener?.Dispose(); + throw; + } + } + + + static void AsserMandatoryTags(TestingMetricListener metricsListener, Type expectedMessageType, + Type expectedHandlerType) + { + var messageType = metricsListener.AssertTagKeyExists(HandlingTimeMetricName, "nservicebus.message_type"); + Assert.AreEqual(expectedMessageType.FullName, messageType); + var handlerType = + metricsListener.AssertTagKeyExists(HandlingTimeMetricName, "nservicebus.message_handler_type"); + Assert.AreEqual(expectedHandlerType.FullName, handlerType); + var endpoint = metricsListener.AssertTagKeyExists(HandlingTimeMetricName, "nservicebus.queue"); + Assert.AreEqual(Conventions.EndpointNamingConvention(typeof(EndpointWithMetrics)), endpoint); + } + + class Context : ScenarioContext + { + public int TotalHandledMessages; + } + + class EndpointWithMetrics : EndpointConfigurationBuilder + { + public EndpointWithMetrics() => EndpointSetup(); + } + + class MyMessageHandler : IHandleMessages + { + readonly Context testContext; + + public MyMessageHandler(Context testContext) => this.testContext = testContext; + + public Task Handle(MyMessage message, IMessageHandlerContext context) + { + Interlocked.Increment(ref testContext.TotalHandledMessages); + return Task.CompletedTask; + } + } + + class MyExceptionalHandler : IHandleMessages + { + readonly Context testContext; + + public MyExceptionalHandler(Context testContext) => this.testContext = testContext; + + public Task Handle(MyExceptionalMessage message, IMessageHandlerContext context) + { + Interlocked.Increment(ref testContext.TotalHandledMessages); + throw new Exception(); + } + } + + public class MyMessage : IMessage + { + } + + public class MyExceptionalMessage : IMessage + { + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled_successfully.cs b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled_successfully.cs deleted file mode 100644 index 295f50f4ccd..00000000000 --- a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled_successfully.cs +++ /dev/null @@ -1,64 +0,0 @@ -namespace NServiceBus.AcceptanceTests.Core.OpenTelemetry; - -using System.Threading; -using System.Threading.Tasks; -using AcceptanceTesting; -using NUnit.Framework; -using Conventions = AcceptanceTesting.Customization.Conventions; - -public class WhenIncomingMessageHandledSuccessfully : OpenTelemetryAcceptanceTest -{ - [Test] - public async Task Should_record_handling_time() - { - using var metricsListener = TestingMetricListener.SetupNServiceBusMetricsListener(); - - _ = await Scenario.Define() - .WithEndpoint(b => - b.When(async (session, _) => - { - for (var x = 0; x < 5; x++) - { - await session.SendLocal(new MyMessage()); - } - })) - .Done(c => c.TotalHandledMessages == 5) - .Run(); - - string handlingTime = "nservicebus.messaging.handling_time"; - metricsListener.AssertMetric(handlingTime, 5); - var messageType = metricsListener.AssertTagKeyExists(handlingTime, "nservicebus.message_type"); - Assert.AreEqual(typeof(MyMessage).FullName, messageType); - var handlerType = metricsListener.AssertTagKeyExists(handlingTime, "nservicebus.message_handler_type"); - Assert.AreEqual(typeof(MyMessageHandler).FullName, handlerType); - var endpoint = metricsListener.AssertTagKeyExists(handlingTime, "nservicebus.queue"); - Assert.AreEqual(Conventions.EndpointNamingConvention(typeof(EndpointWithMetrics)), endpoint); - } - - class Context : ScenarioContext - { - public int TotalHandledMessages; - } - - class EndpointWithMetrics : EndpointConfigurationBuilder - { - public EndpointWithMetrics() => EndpointSetup(); - } - - class MyMessageHandler : IHandleMessages - { - readonly Context testContext; - - public MyMessageHandler(Context testContext) => this.testContext = testContext; - - public Task Handle(MyMessage message, IMessageHandlerContext context) - { - Interlocked.Increment(ref testContext.TotalHandledMessages); - return Task.CompletedTask; - } - } - - public class MyMessage : IMessage - { - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTagsTests.Verify_Metrics.approved.txt b/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTagsTests.Verify_Metrics.approved.txt deleted file mode 100644 index 40946bbf94d..00000000000 --- a/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTagsTests.Verify_Metrics.approved.txt +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Note": "Changes to metrics' names should result in Meters version updates", - "ActivitySourceVersion": "0.2.0", - "Metrics": [ - "TotalProcessedSuccessfully => nservicebus.messaging.successes", - "TotalFetched => nservicebus.messaging.fetches", - "TotalFailures => nservicebus.messaging.failures", - "HandlingTime => nservicebus.messaging.handling_time" - ] -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt b/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt new file mode 100644 index 00000000000..eeb4cce7b8c --- /dev/null +++ b/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt @@ -0,0 +1,17 @@ +{ + "Note": "Changes to metrics API should result in Meters version updates", + "ActivitySourceVersion": "0.2.0", + "Tags": [ + "EndpointDiscriminator => nservicebus.discriminator", + "QueueName => nservicebus.queue", + "MessageType => nservicebus.message_type", + "FailureType => nservicebus.failure_type", + "MessageHandlerType => nservicebus.message_handler_type" + ], + "Metrics": [ + "TotalProcessedSuccessfully => nservicebus.messaging.successes", + "TotalFetched => nservicebus.messaging.fetches", + "TotalFailures => nservicebus.messaging.failures", + "HandlingTime => nservicebus.messaging.handling_time" + ] +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTagsTests.cs b/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs similarity index 65% rename from src/NServiceBus.Core.Tests/OpenTelemetry/MeterTagsTests.cs rename to src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs index 36b5b4985e5..1969e3b6810 100644 --- a/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTagsTests.cs +++ b/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs @@ -6,28 +6,16 @@ using Particular.Approvals; [TestFixture] -public class MeterTagsTests +public class MeterTests { [Test] - public void Verify_MeterTags() + public void Verify_MeterAPI() { var meterTags = typeof(MeterTags) .GetFields(BindingFlags.Public | BindingFlags.Static) .Where(fi => fi.IsLiteral && !fi.IsInitOnly) .Select(x => $"{x.Name} => {x.GetRawConstantValue()}") .ToList(); - - Approver.Verify(new - { - Note = "Changes to meter tags should result in Meters version updates", - ActivitySourceVersion = Meters.NServiceBusMeter.Version, - Tags = meterTags - }); - } - - [Test] - public void Verify_Metrics() - { var metrics = typeof(Metrics) .GetFields(BindingFlags.Public | BindingFlags.Static) .Where(fi => fi.IsLiteral && !fi.IsInitOnly) @@ -35,8 +23,9 @@ public void Verify_Metrics() .ToList(); Approver.Verify(new { - Note = "Changes to metrics' names should result in Meters version updates", + Note = "Changes to metrics API should result in Meters version updates", ActivitySourceVersion = Meters.NServiceBusMeter.Version, + Tags = meterTags, Metrics = metrics }); } From a7a886fc847cc8bc52f0f937fb640cc0d2247f8d Mon Sep 17 00:00:00 2001 From: Sara Pellegrini Date: Tue, 4 Jun 2024 17:38:18 +0200 Subject: [PATCH 11/76] Add discriminator Tag to handling metrics. --- .../Core/OpenTelemetry/When_incoming_message_handled.cs | 3 +++ .../Hosting/HostingComponent.Configuration.cs | 2 +- src/NServiceBus.Core/Hosting/HostingComponent.Settings.cs | 2 ++ .../OpenTelemetry/Metrics/HandlingMetricsFactory.cs | 4 ++-- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled.cs b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled.cs index 0a60649df58..b06a334553d 100644 --- a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled.cs +++ b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled.cs @@ -40,6 +40,7 @@ static async Task WhenMessagesHandled(Func mess _ = await Scenario.Define() .WithEndpoint(b => b.DoNotFailOnErrorMessages() + .CustomConfig(c => c.MakeInstanceUniquelyAddressable("discriminator")) .When(async (session, _) => { for (var x = 0; x < 5; x++) @@ -76,6 +77,8 @@ static void AsserMandatoryTags(TestingMetricListener metricsListener, Type expec Assert.AreEqual(expectedHandlerType.FullName, handlerType); var endpoint = metricsListener.AssertTagKeyExists(HandlingTimeMetricName, "nservicebus.queue"); Assert.AreEqual(Conventions.EndpointNamingConvention(typeof(EndpointWithMetrics)), endpoint); + var discriminator = metricsListener.AssertTagKeyExists(HandlingTimeMetricName, "nservicebus.discriminator"); + Assert.AreEqual("discriminator", discriminator); } class Context : ScenarioContext diff --git a/src/NServiceBus.Core/Hosting/HostingComponent.Configuration.cs b/src/NServiceBus.Core/Hosting/HostingComponent.Configuration.cs index 9ec23baf657..09c713033df 100644 --- a/src/NServiceBus.Core/Hosting/HostingComponent.Configuration.cs +++ b/src/NServiceBus.Core/Hosting/HostingComponent.Configuration.cs @@ -29,7 +29,7 @@ public static Configuration PrepareConfiguration(Settings settings, AssemblyScan settings.ShouldRunInstallers, settings.UserRegistrations, settings.EnableOpenTelemetry ? new ActivityFactory() : new NoOpActivityFactory(), - settings.EnableOpenTelemetry ? new HandlingMetricsFactory(settings.EndpointName) : new NoOpHandlingMetricsFactory()); + settings.EnableOpenTelemetry ? new HandlingMetricsFactory(settings.EndpointName, settings.Discriminator) : new NoOpHandlingMetricsFactory()); return configuration; } diff --git a/src/NServiceBus.Core/Hosting/HostingComponent.Settings.cs b/src/NServiceBus.Core/Hosting/HostingComponent.Settings.cs index 9128f800c37..4cf3f4db74a 100644 --- a/src/NServiceBus.Core/Hosting/HostingComponent.Settings.cs +++ b/src/NServiceBus.Core/Hosting/HostingComponent.Settings.cs @@ -44,6 +44,8 @@ public string DisplayName public string EndpointName => settings.EndpointName(); + public string Discriminator => settings.GetOrDefault("EndpointInstanceDiscriminator"); + public Dictionary Properties { get { return settings.Get>(PropertiesSettingsKey); } diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/HandlingMetricsFactory.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/HandlingMetricsFactory.cs index 1c70b3d8b2b..d83a0c5aa05 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/HandlingMetricsFactory.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/HandlingMetricsFactory.cs @@ -5,7 +5,7 @@ using System.Diagnostics; using Pipeline; -class HandlingMetricsFactory(string queueName/*, string discriminator*/) : IHandlingMetricsFactory +class HandlingMetricsFactory(string queueName, string discriminator) : IHandlingMetricsFactory { public IHandlingMetrics StartHandling(IInvokeHandlerContext context) { @@ -14,7 +14,7 @@ public IHandlingMetrics StartHandling(IInvokeHandlerContext context) return new RecordHandlingMetric(new( [ new(MeterTags.QueueName, queueName ?? ""), - // new(MeterTags.EndpointDiscriminator, discriminator ?? ""), + new(MeterTags.EndpointDiscriminator, discriminator ?? ""), new(MeterTags.MessageHandlerType, handlerType), new(MeterTags.MessageType, messageType) ])); From b8bfc043ce169ef8d71a62b0d83c86af0e6e1876 Mon Sep 17 00:00:00 2001 From: Sara Pellegrini Date: Wed, 5 Jun 2024 10:27:40 +0200 Subject: [PATCH 12/76] Refactor the code to avoid auto-generated structure. --- src/NServiceBus.Core.Tests/StandardsTests.cs | 11 +---------- .../OpenTelemetry/Metrics/HandlingMetricsFactory.cs | 12 ++++-------- .../OpenTelemetry/Metrics/MeterTags.cs | 12 ++++++++++++ 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/NServiceBus.Core.Tests/StandardsTests.cs b/src/NServiceBus.Core.Tests/StandardsTests.cs index bb5a9793bc7..3eb12566d96 100644 --- a/src/NServiceBus.Core.Tests/StandardsTests.cs +++ b/src/NServiceBus.Core.Tests/StandardsTests.cs @@ -55,16 +55,7 @@ public void NonPublicShouldHaveSimpleNamespace() static bool IsCompilerGenerated(Type x) { - if (Attribute.IsDefined(x, typeof(CompilerGeneratedAttribute), false)) - { - return true; - } - // Exclusion of generated internal struct - this change was initially required by the code in HandlingMetricsFactory - if (x.Namespace is null && x.Name[0] == '<') - { - return true; - } - return false; + return Attribute.IsDefined(x, typeof(CompilerGeneratedAttribute), false); } [Test] diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/HandlingMetricsFactory.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/HandlingMetricsFactory.cs index d83a0c5aa05..0c874e6807c 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/HandlingMetricsFactory.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/HandlingMetricsFactory.cs @@ -9,15 +9,11 @@ class HandlingMetricsFactory(string queueName, string discriminator) : IHandling { public IHandlingMetrics StartHandling(IInvokeHandlerContext context) { - var handlerType = context.MessageHandler.Instance?.GetType().FullName; var messageType = context.MessageBeingHandled?.GetType().FullName; - return new RecordHandlingMetric(new( - [ - new(MeterTags.QueueName, queueName ?? ""), - new(MeterTags.EndpointDiscriminator, discriminator ?? ""), - new(MeterTags.MessageHandlerType, handlerType), - new(MeterTags.MessageType, messageType) - ])); + TagList tagList = MeterTags.BaseTagList(queueName, discriminator, messageType); + var handlerType = context.MessageHandler.Instance?.GetType().FullName; + tagList.Add(MeterTags.MessageHandlerType, handlerType ?? ""); + return new RecordHandlingMetric(tagList); } } diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/MeterTags.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/MeterTags.cs index 3ef4be43de6..311e43746b1 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/MeterTags.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/MeterTags.cs @@ -1,5 +1,9 @@ namespace NServiceBus; +using System; +using System.Collections.Generic; +using System.Diagnostics; + static class MeterTags { public const string EndpointDiscriminator = "nservicebus.discriminator"; @@ -8,4 +12,12 @@ static class MeterTags public const string FailureType = "nservicebus.failure_type"; public const string MessageHandlerTypes = "nservicebus.message_handler_types"; public const string MessageHandlerType = "nservicebus.message_handler_type"; + + public static TagList BaseTagList(string queueName, string discriminator, string messageType) + { + return new TagList(new KeyValuePair[] + { + new(QueueName, queueName ?? ""), new(EndpointDiscriminator, discriminator ?? ""), new(MessageType, messageType ?? "") + }.AsSpan()); + } } \ No newline at end of file From ab733a630d125fdb61444b25e8d22bd9d7c1a6b8 Mon Sep 17 00:00:00 2001 From: Sara Pellegrini Date: Wed, 5 Jun 2024 17:33:37 +0200 Subject: [PATCH 13/76] Tests improvement. --- .../When_incoming_message_handled.cs | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled.cs b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled.cs index b06a334553d..7eac2f774cc 100644 --- a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled.cs +++ b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled.cs @@ -16,7 +16,9 @@ public async Task Should_record_success_handling_time() { using TestingMetricListener metricsListener = await WhenMessagesHandled(() => new MyMessage()); metricsListener.AssertMetric(HandlingTimeMetricName, 5); - AsserMandatoryTags(metricsListener, typeof(MyMessage), typeof(MyMessageHandler)); + AsserMandatoryTags(metricsListener, HandlingTimeMetricName, typeof(MyMessage)); + var handlerType = metricsListener.AssertTagKeyExists(HandlingTimeMetricName, "nservicebus.message_handler_type"); + Assert.AreEqual(typeof(MyMessageHandler).FullName, handlerType); } [Test] @@ -24,9 +26,10 @@ public async Task Should_record_failure_handling_time() { using TestingMetricListener metricsListener = await WhenMessagesHandled(() => new MyExceptionalMessage()); metricsListener.AssertMetric(HandlingTimeMetricName, 5); - AsserMandatoryTags(metricsListener, typeof(MyExceptionalMessage), typeof(MyExceptionalHandler)); + AsserMandatoryTags(metricsListener, HandlingTimeMetricName, typeof(MyExceptionalMessage)); + var handlerType = metricsListener.AssertTagKeyExists(HandlingTimeMetricName, "nservicebus.message_handler_type"); + Assert.AreEqual(typeof(MyExceptionalHandler).FullName, handlerType); var exception = metricsListener.AssertTagKeyExists(HandlingTimeMetricName, "nservicebus.failure_type"); - Console.WriteLine(exception); Assert.AreEqual(typeof(Exception).FullName, exception); } @@ -67,17 +70,16 @@ static async Task WhenMessagesHandled(Func mess } - static void AsserMandatoryTags(TestingMetricListener metricsListener, Type expectedMessageType, - Type expectedHandlerType) + static void AsserMandatoryTags( + TestingMetricListener metricsListener, + string metricName, + Type expectedMessageType) { - var messageType = metricsListener.AssertTagKeyExists(HandlingTimeMetricName, "nservicebus.message_type"); + var messageType = metricsListener.AssertTagKeyExists(metricName, "nservicebus.message_type"); Assert.AreEqual(expectedMessageType.FullName, messageType); - var handlerType = - metricsListener.AssertTagKeyExists(HandlingTimeMetricName, "nservicebus.message_handler_type"); - Assert.AreEqual(expectedHandlerType.FullName, handlerType); - var endpoint = metricsListener.AssertTagKeyExists(HandlingTimeMetricName, "nservicebus.queue"); + var endpoint = metricsListener.AssertTagKeyExists(metricName, "nservicebus.queue"); Assert.AreEqual(Conventions.EndpointNamingConvention(typeof(EndpointWithMetrics)), endpoint); - var discriminator = metricsListener.AssertTagKeyExists(HandlingTimeMetricName, "nservicebus.discriminator"); + var discriminator = metricsListener.AssertTagKeyExists(metricName, "nservicebus.discriminator"); Assert.AreEqual("discriminator", discriminator); } From e38cfb5dc0da467151c1bd4bbeeca53d8fa31039 Mon Sep 17 00:00:00 2001 From: Sara Pellegrini Date: Thu, 6 Jun 2024 00:05:35 +0200 Subject: [PATCH 14/76] Fix typo. --- .../Core/OpenTelemetry/When_incoming_message_handled.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled.cs b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled.cs index 7eac2f774cc..845a1651565 100644 --- a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled.cs +++ b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled.cs @@ -16,7 +16,7 @@ public async Task Should_record_success_handling_time() { using TestingMetricListener metricsListener = await WhenMessagesHandled(() => new MyMessage()); metricsListener.AssertMetric(HandlingTimeMetricName, 5); - AsserMandatoryTags(metricsListener, HandlingTimeMetricName, typeof(MyMessage)); + AssertMandatoryTags(metricsListener, HandlingTimeMetricName, typeof(MyMessage)); var handlerType = metricsListener.AssertTagKeyExists(HandlingTimeMetricName, "nservicebus.message_handler_type"); Assert.AreEqual(typeof(MyMessageHandler).FullName, handlerType); } @@ -26,7 +26,7 @@ public async Task Should_record_failure_handling_time() { using TestingMetricListener metricsListener = await WhenMessagesHandled(() => new MyExceptionalMessage()); metricsListener.AssertMetric(HandlingTimeMetricName, 5); - AsserMandatoryTags(metricsListener, HandlingTimeMetricName, typeof(MyExceptionalMessage)); + AssertMandatoryTags(metricsListener, HandlingTimeMetricName, typeof(MyExceptionalMessage)); var handlerType = metricsListener.AssertTagKeyExists(HandlingTimeMetricName, "nservicebus.message_handler_type"); Assert.AreEqual(typeof(MyExceptionalHandler).FullName, handlerType); var exception = metricsListener.AssertTagKeyExists(HandlingTimeMetricName, "nservicebus.failure_type"); @@ -70,7 +70,7 @@ static async Task WhenMessagesHandled(Func mess } - static void AsserMandatoryTags( + static void AssertMandatoryTags( TestingMetricListener metricsListener, string metricName, Type expectedMessageType) From 2169aa90d2c10cb45a3167af93f1b77d5bf77659 Mon Sep 17 00:00:00 2001 From: Sara Pellegrini Date: Thu, 6 Jun 2024 00:07:27 +0200 Subject: [PATCH 15/76] Make tags field readonly. --- .../OpenTelemetry/Metrics/HandlingMetricsFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/HandlingMetricsFactory.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/HandlingMetricsFactory.cs index 0c874e6807c..59b89bbf8e3 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/HandlingMetricsFactory.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/HandlingMetricsFactory.cs @@ -39,5 +39,5 @@ public void OnFailure(Exception error) } readonly Stopwatch stopWatch = new(); - TagList tags; + readonly TagList tags; } \ No newline at end of file From 51de44d2ba1f9fb46aa9f10f92231d7a675af788 Mon Sep 17 00:00:00 2001 From: Sara Pellegrini Date: Thu, 6 Jun 2024 00:13:39 +0200 Subject: [PATCH 16/76] Fix note description. --- .../ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt | 2 +- src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt b/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt index eeb4cce7b8c..a2dc84b0bee 100644 --- a/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt +++ b/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt @@ -1,5 +1,5 @@ { - "Note": "Changes to metrics API should result in Meters version updates", + "Note": "Changes to metrics API should result in an update to NServiceBusMeter version.", "ActivitySourceVersion": "0.2.0", "Tags": [ "EndpointDiscriminator => nservicebus.discriminator", diff --git a/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs b/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs index 1969e3b6810..c62171b9faa 100644 --- a/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs +++ b/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs @@ -23,7 +23,7 @@ public void Verify_MeterAPI() .ToList(); Approver.Verify(new { - Note = "Changes to metrics API should result in Meters version updates", + Note = "Changes to metrics API should result in an update to NServiceBusMeter version.", ActivitySourceVersion = Meters.NServiceBusMeter.Version, Tags = meterTags, Metrics = metrics From 2ddf602ed3d61fc7779ea37ef370c3659fd0aa93 Mon Sep 17 00:00:00 2001 From: Sara Pellegrini Date: Thu, 6 Jun 2024 00:14:33 +0200 Subject: [PATCH 17/76] Use var instead of explicit type. --- .../Pipeline/Incoming/InvokeHandlerTerminator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs b/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs index f99f609a1ad..65b94aca06f 100644 --- a/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs +++ b/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs @@ -22,7 +22,7 @@ protected override async Task Terminate(IInvokeHandlerContext context) // Might as well abort before invoking the handler if we're shutting down context.CancellationToken.ThrowIfCancellationRequested(); - IHandlingMetrics handlingMetrics = metricsFactory.StartHandling(context); + var handlingMetrics = metricsFactory.StartHandling(context); var startTime = DateTimeOffset.UtcNow; try { From ffc739bd5475ec1daa37f41ff86ea0e340326da6 Mon Sep 17 00:00:00 2001 From: Sara Pellegrini Date: Thu, 6 Jun 2024 00:21:50 +0200 Subject: [PATCH 18/76] Rename HandlingMetrics... types to MessageHandlingMetrics... to be more explicit. --- .../Pipeline/Incoming/InvokeHandlerTerminatorTest.cs | 2 +- .../Hosting/HostingComponent.Configuration.cs | 8 ++++---- ...ricsFactory.cs => MessageHandlingMetricsFactory.cs} | 10 +++++----- ...Factory.cs => NoOpMessageHandlingMetricsFactory.cs} | 6 +++--- ...{IHandlingMetrics.cs => IMessageHandlingMetrics.cs} | 2 +- ...icsFactory.cs => IMessageHandlingMetricsFactory.cs} | 4 ++-- .../Pipeline/Incoming/InvokeHandlerTerminator.cs | 2 +- src/NServiceBus.Core/Receiving/ReceiveComponent.cs | 2 +- 8 files changed, 18 insertions(+), 18 deletions(-) rename src/NServiceBus.Core/OpenTelemetry/Metrics/{HandlingMetricsFactory.cs => MessageHandlingMetricsFactory.cs} (73%) rename src/NServiceBus.Core/OpenTelemetry/Metrics/{NoOpHandlingMetricsFactory.cs => NoOpMessageHandlingMetricsFactory.cs} (67%) rename src/NServiceBus.Core/Pipeline/Incoming/{IHandlingMetrics.cs => IMessageHandlingMetrics.cs} (96%) rename src/NServiceBus.Core/Pipeline/Incoming/{IHandlingMetricsFactory.cs => IMessageHandlingMetricsFactory.cs} (81%) diff --git a/src/NServiceBus.Core.Tests/Pipeline/Incoming/InvokeHandlerTerminatorTest.cs b/src/NServiceBus.Core.Tests/Pipeline/Incoming/InvokeHandlerTerminatorTest.cs index c7fdd688441..63354add4d8 100644 --- a/src/NServiceBus.Core.Tests/Pipeline/Incoming/InvokeHandlerTerminatorTest.cs +++ b/src/NServiceBus.Core.Tests/Pipeline/Incoming/InvokeHandlerTerminatorTest.cs @@ -11,7 +11,7 @@ [TestFixture] public class InvokeHandlerTerminatorTest { - InvokeHandlerTerminator terminator = new InvokeHandlerTerminator(new NoOpActivityFactory(), new NoOpHandlingMetricsFactory()); + InvokeHandlerTerminator terminator = new InvokeHandlerTerminator(new NoOpActivityFactory(), new NoOpMessageHandlingMetricsFactory()); [Test] public async Task When_saga_found_and_handler_is_saga_should_invoke_handler() diff --git a/src/NServiceBus.Core/Hosting/HostingComponent.Configuration.cs b/src/NServiceBus.Core/Hosting/HostingComponent.Configuration.cs index 09c713033df..f1efea7386b 100644 --- a/src/NServiceBus.Core/Hosting/HostingComponent.Configuration.cs +++ b/src/NServiceBus.Core/Hosting/HostingComponent.Configuration.cs @@ -29,7 +29,7 @@ public static Configuration PrepareConfiguration(Settings settings, AssemblyScan settings.ShouldRunInstallers, settings.UserRegistrations, settings.EnableOpenTelemetry ? new ActivityFactory() : new NoOpActivityFactory(), - settings.EnableOpenTelemetry ? new HandlingMetricsFactory(settings.EndpointName, settings.Discriminator) : new NoOpHandlingMetricsFactory()); + settings.EnableOpenTelemetry ? new MessageHandlingMetricsFactory(settings.EndpointName, settings.Discriminator) : new NoOpMessageHandlingMetricsFactory()); return configuration; } @@ -48,7 +48,7 @@ public Configuration(Settings settings, bool shouldRunInstallers, List> userRegistrations, IActivityFactory activityFactory, - IHandlingMetricsFactory handlingMetricsFactory) + IMessageHandlingMetricsFactory messageHandlingMetricsFactory) { AvailableTypes = availableTypes; CriticalError = criticalError; @@ -61,7 +61,7 @@ public Configuration(Settings settings, ShouldRunInstallers = shouldRunInstallers; UserRegistrations = userRegistrations; ActivityFactory = activityFactory; - HandlingMetricsFactory = handlingMetricsFactory; + MessageHandlingMetricsFactory = messageHandlingMetricsFactory; settings.ApplyHostIdDefaultIfNeeded(); HostInformation = new HostInformation(settings.HostId, settings.DisplayName, settings.Properties); @@ -96,6 +96,6 @@ public void AddStartupDiagnosticsSection(string sectionName, object section) public IActivityFactory ActivityFactory { get; set; } - public IHandlingMetricsFactory HandlingMetricsFactory { get; set; } + public IMessageHandlingMetricsFactory MessageHandlingMetricsFactory { get; set; } } } \ No newline at end of file diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/HandlingMetricsFactory.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs similarity index 73% rename from src/NServiceBus.Core/OpenTelemetry/Metrics/HandlingMetricsFactory.cs rename to src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs index 59b89bbf8e3..d55e3803a10 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/HandlingMetricsFactory.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs @@ -5,21 +5,21 @@ using System.Diagnostics; using Pipeline; -class HandlingMetricsFactory(string queueName, string discriminator) : IHandlingMetricsFactory +class MessageHandlingMetricsFactory(string queueName, string discriminator) : IMessageHandlingMetricsFactory { - public IHandlingMetrics StartHandling(IInvokeHandlerContext context) + public IMessageHandlingMetrics StartHandling(IInvokeHandlerContext context) { var messageType = context.MessageBeingHandled?.GetType().FullName; TagList tagList = MeterTags.BaseTagList(queueName, discriminator, messageType); var handlerType = context.MessageHandler.Instance?.GetType().FullName; tagList.Add(MeterTags.MessageHandlerType, handlerType ?? ""); - return new RecordHandlingMetric(tagList); + return new RecordMessageHandlingMetric(tagList); } } -class RecordHandlingMetric : IHandlingMetrics +class RecordMessageHandlingMetric : IMessageHandlingMetrics { - public RecordHandlingMetric(TagList tags) + public RecordMessageHandlingMetric(TagList tags) { this.tags = tags; stopWatch.Start(); diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/NoOpHandlingMetricsFactory.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/NoOpMessageHandlingMetricsFactory.cs similarity index 67% rename from src/NServiceBus.Core/OpenTelemetry/Metrics/NoOpHandlingMetricsFactory.cs rename to src/NServiceBus.Core/OpenTelemetry/Metrics/NoOpMessageHandlingMetricsFactory.cs index 76a460e2ab3..64f67b0be24 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/NoOpHandlingMetricsFactory.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/NoOpMessageHandlingMetricsFactory.cs @@ -6,17 +6,17 @@ /// /// Implementation of IHandlingMetricsFactory that does not perform anything. /// -class NoOpHandlingMetricsFactory : IHandlingMetricsFactory +class NoOpMessageHandlingMetricsFactory : IMessageHandlingMetricsFactory { /// /// Instantiates a new IHandlingMetrics that does not record any metric. /// /// The invocation context. /// The instantiated IHandlingMetrics. - public IHandlingMetrics StartHandling(IInvokeHandlerContext context) => new NoOpHandlingMetrics(); + public IMessageHandlingMetrics StartHandling(IInvokeHandlerContext context) => new NoOpMessageHandlingMetrics(); } -class NoOpHandlingMetrics : IHandlingMetrics +class NoOpMessageHandlingMetrics : IMessageHandlingMetrics { public void OnSuccess() { diff --git a/src/NServiceBus.Core/Pipeline/Incoming/IHandlingMetrics.cs b/src/NServiceBus.Core/Pipeline/Incoming/IMessageHandlingMetrics.cs similarity index 96% rename from src/NServiceBus.Core/Pipeline/Incoming/IHandlingMetrics.cs rename to src/NServiceBus.Core/Pipeline/Incoming/IMessageHandlingMetrics.cs index 92ee6cc9af5..3ed8d5a506b 100644 --- a/src/NServiceBus.Core/Pipeline/Incoming/IHandlingMetrics.cs +++ b/src/NServiceBus.Core/Pipeline/Incoming/IMessageHandlingMetrics.cs @@ -5,7 +5,7 @@ /// /// Registers the metrics related to a specific message handler execution. /// -interface IHandlingMetrics +interface IMessageHandlingMetrics { /// /// Registers the metrics related to the successful completion of the handler's execution. diff --git a/src/NServiceBus.Core/Pipeline/Incoming/IHandlingMetricsFactory.cs b/src/NServiceBus.Core/Pipeline/Incoming/IMessageHandlingMetricsFactory.cs similarity index 81% rename from src/NServiceBus.Core/Pipeline/Incoming/IHandlingMetricsFactory.cs rename to src/NServiceBus.Core/Pipeline/Incoming/IMessageHandlingMetricsFactory.cs index 15bff49d4e0..4706adf6636 100644 --- a/src/NServiceBus.Core/Pipeline/Incoming/IHandlingMetricsFactory.cs +++ b/src/NServiceBus.Core/Pipeline/Incoming/IMessageHandlingMetricsFactory.cs @@ -5,13 +5,13 @@ /// /// Factory for IHandlingMetrics needed to record metrics for the handler invocation. /// -interface IHandlingMetricsFactory +interface IMessageHandlingMetricsFactory { /// /// Creates a new IHandlingMetrics instance for recording the metrics for a specific handler execution. /// /// Needed to properly initialize the IHandlingMetrics instance. /// The newly created IHandlingMetrics instance. - IHandlingMetrics StartHandling(IInvokeHandlerContext context); + IMessageHandlingMetrics StartHandling(IInvokeHandlerContext context); } diff --git a/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs b/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs index 65b94aca06f..76f8a4ebd5e 100644 --- a/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs +++ b/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs @@ -6,7 +6,7 @@ using Pipeline; using Sagas; -class InvokeHandlerTerminator(IActivityFactory activityFactory, IHandlingMetricsFactory metricsFactory) : PipelineTerminator +class InvokeHandlerTerminator(IActivityFactory activityFactory, IMessageHandlingMetricsFactory metricsFactory) : PipelineTerminator { protected override async Task Terminate(IInvokeHandlerContext context) diff --git a/src/NServiceBus.Core/Receiving/ReceiveComponent.cs b/src/NServiceBus.Core/Receiving/ReceiveComponent.cs index fecd3016ded..d972c894b93 100644 --- a/src/NServiceBus.Core/Receiving/ReceiveComponent.cs +++ b/src/NServiceBus.Core/Receiving/ReceiveComponent.cs @@ -71,7 +71,7 @@ public static ReceiveComponent Configure( return new LoadHandlersConnector(b.GetRequiredService()); }, "Gets all the handlers to invoke from the MessageHandler registry based on the message type."); - pipelineSettings.Register("InvokeHandlers", new InvokeHandlerTerminator(hostingConfiguration.ActivityFactory, hostingConfiguration.HandlingMetricsFactory), "Calls the IHandleMessages.Handle(T)"); + pipelineSettings.Register("InvokeHandlers", new InvokeHandlerTerminator(hostingConfiguration.ActivityFactory, hostingConfiguration.MessageHandlingMetricsFactory), "Calls the IHandleMessages.Handle(T)"); var handlerDiagnostics = new Dictionary>(); From 19f5c8b759cd16afa992ee7f6f586a975fc53473 Mon Sep 17 00:00:00 2001 From: Sara Pellegrini Date: Thu, 6 Jun 2024 00:23:49 +0200 Subject: [PATCH 19/76] Rename HandlingMetrics... types to MessageHandlingMetrics... to be more explicit. --- .../Pipeline/Incoming/IMessageHandlingMetricsFactory.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/NServiceBus.Core/Pipeline/Incoming/IMessageHandlingMetricsFactory.cs b/src/NServiceBus.Core/Pipeline/Incoming/IMessageHandlingMetricsFactory.cs index 4706adf6636..1023ef60d32 100644 --- a/src/NServiceBus.Core/Pipeline/Incoming/IMessageHandlingMetricsFactory.cs +++ b/src/NServiceBus.Core/Pipeline/Incoming/IMessageHandlingMetricsFactory.cs @@ -3,15 +3,15 @@ using Pipeline; /// -/// Factory for IHandlingMetrics needed to record metrics for the handler invocation. +/// Factory for IMessageHandlingMetrics needed to record metrics for the handler invocation. /// interface IMessageHandlingMetricsFactory { /// - /// Creates a new IHandlingMetrics instance for recording the metrics for a specific handler execution. + /// Creates a new IMessageHandlingMetrics instance for recording the metrics for a specific handler execution. /// /// Needed to properly initialize the IHandlingMetrics instance. - /// The newly created IHandlingMetrics instance. + /// The newly created IMessageHandlingMetrics instance. IMessageHandlingMetrics StartHandling(IInvokeHandlerContext context); } From a19728f7c0f3383e4599af9c628393c9e687ec6d Mon Sep 17 00:00:00 2001 From: Sara Pellegrini Date: Thu, 6 Jun 2024 00:38:42 +0200 Subject: [PATCH 20/76] Make the TagList not readonly to allow to add the FailureType tag. --- .../OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs index d55e3803a10..38972dd36bc 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs @@ -39,5 +39,5 @@ public void OnFailure(Exception error) } readonly Stopwatch stopWatch = new(); - readonly TagList tags; + TagList tags; } \ No newline at end of file From 1d3b2a05a04ad9f3cf60cdd892ccc442121e3b96 Mon Sep 17 00:00:00 2001 From: Sara Pellegrini Date: Thu, 6 Jun 2024 10:54:35 +0200 Subject: [PATCH 21/76] Rename HandlingTime to MessageHandlerTime for clarity. --- .../When_incoming_message_handled.cs | 17 +++++++++-------- .../MeterTests.Verify_MeterAPI.approved.txt | 2 +- .../Metrics/MessageHandlingMetricsFactory.cs | 4 ++-- .../OpenTelemetry/Metrics/Meters.cs | 4 ++-- .../OpenTelemetry/Metrics/Metrics.cs | 2 +- 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled.cs b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled.cs index 845a1651565..792cae22923 100644 --- a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled.cs +++ b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled.cs @@ -4,20 +4,21 @@ using System.Threading; using System.Threading.Tasks; using AcceptanceTesting; +using Microsoft.VisualStudio.TestPlatform.Common.Telemetry; using NUnit.Framework; using Conventions = AcceptanceTesting.Customization.Conventions; public class When_incoming_message_handled : OpenTelemetryAcceptanceTest { - static readonly string HandlingTimeMetricName = "nservicebus.messaging.handling_time"; + static readonly string HandlerTimeMetricName = "nservicebus.messaging.handler_time"; [Test] public async Task Should_record_success_handling_time() { using TestingMetricListener metricsListener = await WhenMessagesHandled(() => new MyMessage()); - metricsListener.AssertMetric(HandlingTimeMetricName, 5); - AssertMandatoryTags(metricsListener, HandlingTimeMetricName, typeof(MyMessage)); - var handlerType = metricsListener.AssertTagKeyExists(HandlingTimeMetricName, "nservicebus.message_handler_type"); + metricsListener.AssertMetric(HandlerTimeMetricName, 5); + AssertMandatoryTags(metricsListener, HandlerTimeMetricName, typeof(MyMessage)); + var handlerType = metricsListener.AssertTagKeyExists(HandlerTimeMetricName, "nservicebus.message_handler_type"); Assert.AreEqual(typeof(MyMessageHandler).FullName, handlerType); } @@ -25,11 +26,11 @@ public async Task Should_record_success_handling_time() public async Task Should_record_failure_handling_time() { using TestingMetricListener metricsListener = await WhenMessagesHandled(() => new MyExceptionalMessage()); - metricsListener.AssertMetric(HandlingTimeMetricName, 5); - AssertMandatoryTags(metricsListener, HandlingTimeMetricName, typeof(MyExceptionalMessage)); - var handlerType = metricsListener.AssertTagKeyExists(HandlingTimeMetricName, "nservicebus.message_handler_type"); + metricsListener.AssertMetric(HandlerTimeMetricName, 5); + AssertMandatoryTags(metricsListener, HandlerTimeMetricName, typeof(MyExceptionalMessage)); + var handlerType = metricsListener.AssertTagKeyExists(HandlerTimeMetricName, "nservicebus.message_handler_type"); Assert.AreEqual(typeof(MyExceptionalHandler).FullName, handlerType); - var exception = metricsListener.AssertTagKeyExists(HandlingTimeMetricName, "nservicebus.failure_type"); + var exception = metricsListener.AssertTagKeyExists(HandlerTimeMetricName, "nservicebus.failure_type"); Assert.AreEqual(typeof(Exception).FullName, exception); } diff --git a/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt b/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt index a2dc84b0bee..55d57a12601 100644 --- a/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt +++ b/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt @@ -12,6 +12,6 @@ "TotalProcessedSuccessfully => nservicebus.messaging.successes", "TotalFetched => nservicebus.messaging.fetches", "TotalFailures => nservicebus.messaging.failures", - "HandlingTime => nservicebus.messaging.handling_time" + "MessageHandlerTime => nservicebus.messaging.handler_time" ] } \ No newline at end of file diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs index 38972dd36bc..29c10afe8e9 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs @@ -28,14 +28,14 @@ public RecordMessageHandlingMetric(TagList tags) public void OnSuccess() { stopWatch.Stop(); - Meters.HandlingTime.Record(stopWatch.ElapsedMilliseconds, tags); + Meters.MessageHandlerTime.Record(stopWatch.ElapsedMilliseconds, tags); } public void OnFailure(Exception error) { stopWatch.Stop(); tags.Add(new KeyValuePair(MeterTags.FailureType, error.GetType().FullName)); - Meters.HandlingTime.Record(stopWatch.ElapsedMilliseconds, tags); + Meters.MessageHandlerTime.Record(stopWatch.ElapsedMilliseconds, tags); } readonly Stopwatch stopWatch = new(); diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/Meters.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/Meters.cs index efae9ab8997..01bf003198e 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/Meters.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/Meters.cs @@ -17,8 +17,8 @@ class Meters internal static readonly Counter TotalFailures = NServiceBusMeter.CreateCounter(Metrics.TotalFailures, description: "Total number of messages processed unsuccessfully by the endpoint."); - internal static readonly Histogram HandlingTime = - NServiceBusMeter.CreateHistogram(Metrics.HandlingTime, "ms", "The time in milliseconds for the execution of the business code."); + internal static readonly Histogram MessageHandlerTime = + NServiceBusMeter.CreateHistogram(Metrics.MessageHandlerTime, "ms", "The time in milliseconds for the execution of the business code."); internal static readonly Counter TotalImmediateRetries = NServiceBusMeter.CreateCounter("nservicebus.recoverability.immediate", description: "Total number of immediate retries requested."); diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/Metrics.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/Metrics.cs index 2930e2dc7ff..b7f0d7e91b0 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/Metrics.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/Metrics.cs @@ -5,5 +5,5 @@ static class Metrics public const string TotalProcessedSuccessfully = "nservicebus.messaging.successes"; public const string TotalFetched = "nservicebus.messaging.fetches"; public const string TotalFailures = "nservicebus.messaging.failures"; - public const string HandlingTime = "nservicebus.messaging.handling_time"; + public const string MessageHandlerTime = "nservicebus.messaging.handler_time"; } \ No newline at end of file From b1b4e49d5a26e251dd01d7074f2517b07cb525a8 Mon Sep 17 00:00:00 2001 From: Sara Pellegrini Date: Thu, 6 Jun 2024 14:06:01 +0200 Subject: [PATCH 22/76] API test improvement to check the metric name and type mapping. --- .../OpenTelemetry/When_incoming_message_handled.cs | 1 - .../MeterTests.Verify_MeterAPI.approved.txt | 8 ++++---- src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs | 10 ++++++---- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled.cs b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled.cs index 792cae22923..d48eab173b9 100644 --- a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled.cs +++ b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled.cs @@ -4,7 +4,6 @@ using System.Threading; using System.Threading.Tasks; using AcceptanceTesting; -using Microsoft.VisualStudio.TestPlatform.Common.Telemetry; using NUnit.Framework; using Conventions = AcceptanceTesting.Customization.Conventions; diff --git a/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt b/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt index 55d57a12601..45c9178e61d 100644 --- a/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt +++ b/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt @@ -9,9 +9,9 @@ "MessageHandlerType => nservicebus.message_handler_type" ], "Metrics": [ - "TotalProcessedSuccessfully => nservicebus.messaging.successes", - "TotalFetched => nservicebus.messaging.fetches", - "TotalFailures => nservicebus.messaging.failures", - "MessageHandlerTime => nservicebus.messaging.handler_time" + "nservicebus.messaging.successes => Counter", + "nservicebus.messaging.fetches => Counter", + "nservicebus.messaging.failures => Counter", + "nservicebus.messaging.handler_time => Histogram" ] } \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs b/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs index c62171b9faa..b3d07855643 100644 --- a/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs +++ b/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs @@ -1,5 +1,6 @@ namespace NServiceBus.Core.Tests.OpenTelemetry; +using System.Diagnostics.Metrics; using System.Linq; using System.Reflection; using NUnit.Framework; @@ -16,10 +17,11 @@ public void Verify_MeterAPI() .Where(fi => fi.IsLiteral && !fi.IsInitOnly) .Select(x => $"{x.Name} => {x.GetRawConstantValue()}") .ToList(); - var metrics = typeof(Metrics) - .GetFields(BindingFlags.Public | BindingFlags.Static) - .Where(fi => fi.IsLiteral && !fi.IsInitOnly) - .Select(x => $"{x.Name} => {x.GetRawConstantValue()}") + var metrics = typeof(Meters) + .GetFields(BindingFlags.Static | BindingFlags.NonPublic) + .Where(fi => typeof(Instrument).IsAssignableFrom(fi.FieldType)) + .Select(fi => (Instrument)fi.GetValue(null)) + .Select(x => $"{x.Name} => {x.GetType().Name.Split("`").First()}") .ToList(); Approver.Verify(new { From 648c5ed74d1bfd716acec9dcad8ff3b4e10ebb1f Mon Sep 17 00:00:00 2001 From: Sara Pellegrini Date: Thu, 6 Jun 2024 14:13:16 +0200 Subject: [PATCH 23/76] Rename the static method that provides the common metric tags for clarity. --- .../OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs | 2 +- src/NServiceBus.Core/OpenTelemetry/Metrics/MeterTags.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs index 29c10afe8e9..a7aa3dfbe36 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs @@ -10,7 +10,7 @@ class MessageHandlingMetricsFactory(string queueName, string discriminator) : IM public IMessageHandlingMetrics StartHandling(IInvokeHandlerContext context) { var messageType = context.MessageBeingHandled?.GetType().FullName; - TagList tagList = MeterTags.BaseTagList(queueName, discriminator, messageType); + TagList tagList = MeterTags.CommonMessagingMetricTags(queueName, discriminator, messageType); var handlerType = context.MessageHandler.Instance?.GetType().FullName; tagList.Add(MeterTags.MessageHandlerType, handlerType ?? ""); return new RecordMessageHandlingMetric(tagList); diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/MeterTags.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/MeterTags.cs index 311e43746b1..f0ccf0658a3 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/MeterTags.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/MeterTags.cs @@ -13,7 +13,7 @@ static class MeterTags public const string MessageHandlerTypes = "nservicebus.message_handler_types"; public const string MessageHandlerType = "nservicebus.message_handler_type"; - public static TagList BaseTagList(string queueName, string discriminator, string messageType) + public static TagList CommonMessagingMetricTags(string queueName, string discriminator, string messageType) { return new TagList(new KeyValuePair[] { From e9147752044b6425e4d69b2e3c9cbed04cecf3a5 Mon Sep 17 00:00:00 2001 From: Sara Pellegrini Date: Thu, 6 Jun 2024 15:24:44 +0200 Subject: [PATCH 24/76] Test improvement to remove the field name from API verification. --- .../MeterTests.Verify_MeterAPI.approved.txt | 10 +++++----- src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt b/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt index 45c9178e61d..dbe3ae85e0f 100644 --- a/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt +++ b/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt @@ -2,11 +2,11 @@ "Note": "Changes to metrics API should result in an update to NServiceBusMeter version.", "ActivitySourceVersion": "0.2.0", "Tags": [ - "EndpointDiscriminator => nservicebus.discriminator", - "QueueName => nservicebus.queue", - "MessageType => nservicebus.message_type", - "FailureType => nservicebus.failure_type", - "MessageHandlerType => nservicebus.message_handler_type" + "nservicebus.discriminator", + "nservicebus.queue", + "nservicebus.message_type", + "nservicebus.failure_type", + "nservicebus.message_handler_type" ], "Metrics": [ "nservicebus.messaging.successes => Counter", diff --git a/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs b/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs index b3d07855643..86fe57722b1 100644 --- a/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs +++ b/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs @@ -15,7 +15,7 @@ public void Verify_MeterAPI() var meterTags = typeof(MeterTags) .GetFields(BindingFlags.Public | BindingFlags.Static) .Where(fi => fi.IsLiteral && !fi.IsInitOnly) - .Select(x => $"{x.Name} => {x.GetRawConstantValue()}") + .Select(x => x.GetRawConstantValue()) .ToList(); var metrics = typeof(Meters) .GetFields(BindingFlags.Static | BindingFlags.NonPublic) From f2cedb811cccb1fd447bb48925f46c74095147dd Mon Sep 17 00:00:00 2001 From: Sara Pellegrini Date: Fri, 7 Jun 2024 15:08:48 +0200 Subject: [PATCH 25/76] Validate also the metric unit since they are part of the API. --- .../ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt | 2 +- src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt b/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt index dbe3ae85e0f..358c3bf9fd0 100644 --- a/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt +++ b/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt @@ -12,6 +12,6 @@ "nservicebus.messaging.successes => Counter", "nservicebus.messaging.fetches => Counter", "nservicebus.messaging.failures => Counter", - "nservicebus.messaging.handler_time => Histogram" + "nservicebus.messaging.handler_time => Histogram, Unit: ms" ] } \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs b/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs index 86fe57722b1..54a06c43a6c 100644 --- a/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs +++ b/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs @@ -21,7 +21,7 @@ public void Verify_MeterAPI() .GetFields(BindingFlags.Static | BindingFlags.NonPublic) .Where(fi => typeof(Instrument).IsAssignableFrom(fi.FieldType)) .Select(fi => (Instrument)fi.GetValue(null)) - .Select(x => $"{x.Name} => {x.GetType().Name.Split("`").First()}") + .Select(x => $"{x.Name} => {x.GetType().Name.Split("`").First()}{(x.Unit == null ? "" : ", Unit: ")}{x.Unit ?? ""}") .ToList(); Approver.Verify(new { From 950d20202f1bc184792ea6d2fde96c0c94b7fb24 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Sat, 8 Jun 2024 07:21:10 +0200 Subject: [PATCH 26/76] Use seconds instead of milliseconds to record message handling time (#7059) * Use seconds instead of milliseconds to record message handling time * Fix Approved API --- .../ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt | 2 +- .../OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs | 4 ++-- src/NServiceBus.Core/OpenTelemetry/Metrics/Meters.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt b/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt index 358c3bf9fd0..cdb7f1662f7 100644 --- a/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt +++ b/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt @@ -12,6 +12,6 @@ "nservicebus.messaging.successes => Counter", "nservicebus.messaging.fetches => Counter", "nservicebus.messaging.failures => Counter", - "nservicebus.messaging.handler_time => Histogram, Unit: ms" + "nservicebus.messaging.handler_time => Histogram, Unit: s" ] } \ No newline at end of file diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs index a7aa3dfbe36..63f1da979d4 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs @@ -28,14 +28,14 @@ public RecordMessageHandlingMetric(TagList tags) public void OnSuccess() { stopWatch.Stop(); - Meters.MessageHandlerTime.Record(stopWatch.ElapsedMilliseconds, tags); + Meters.MessageHandlerTime.Record(stopWatch.Elapsed.TotalSeconds, tags); } public void OnFailure(Exception error) { stopWatch.Stop(); tags.Add(new KeyValuePair(MeterTags.FailureType, error.GetType().FullName)); - Meters.MessageHandlerTime.Record(stopWatch.ElapsedMilliseconds, tags); + Meters.MessageHandlerTime.Record(stopWatch.Elapsed.TotalSeconds, tags); } readonly Stopwatch stopWatch = new(); diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/Meters.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/Meters.cs index 01bf003198e..6ddec381589 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/Meters.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/Meters.cs @@ -18,7 +18,7 @@ class Meters NServiceBusMeter.CreateCounter(Metrics.TotalFailures, description: "Total number of messages processed unsuccessfully by the endpoint."); internal static readonly Histogram MessageHandlerTime = - NServiceBusMeter.CreateHistogram(Metrics.MessageHandlerTime, "ms", "The time in milliseconds for the execution of the business code."); + NServiceBusMeter.CreateHistogram(Metrics.MessageHandlerTime, "s", "The time in seconds for the execution of the business code."); internal static readonly Counter TotalImmediateRetries = NServiceBusMeter.CreateCounter("nservicebus.recoverability.immediate", description: "Total number of immediate retries requested."); From 6b7914ca8095648ce6f91ebed1b94165c7f03909 Mon Sep 17 00:00:00 2001 From: Sara Pellegrini Date: Tue, 11 Jun 2024 06:21:25 +0200 Subject: [PATCH 27/76] Pull request remarks. --- .../OpenTelemetry/When_incoming_message_handled.cs | 8 ++++++-- .../MeterTests.Verify_MeterAPI.approved.txt | 14 ++++++++------ .../OpenTelemetry/MeterTests.cs | 2 ++ .../Metrics/MessageHandlingMetricsFactory.cs | 4 +++- .../OpenTelemetry/Metrics/MeterTags.cs | 2 ++ 5 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled.cs b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled.cs index d48eab173b9..b1fe69b4d20 100644 --- a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled.cs +++ b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled.cs @@ -19,6 +19,8 @@ public async Task Should_record_success_handling_time() AssertMandatoryTags(metricsListener, HandlerTimeMetricName, typeof(MyMessage)); var handlerType = metricsListener.AssertTagKeyExists(HandlerTimeMetricName, "nservicebus.message_handler_type"); Assert.AreEqual(typeof(MyMessageHandler).FullName, handlerType); + var result = metricsListener.AssertTagKeyExists(HandlerTimeMetricName, "execution.result"); + Assert.AreEqual("success", result); } [Test] @@ -29,8 +31,10 @@ public async Task Should_record_failure_handling_time() AssertMandatoryTags(metricsListener, HandlerTimeMetricName, typeof(MyExceptionalMessage)); var handlerType = metricsListener.AssertTagKeyExists(HandlerTimeMetricName, "nservicebus.message_handler_type"); Assert.AreEqual(typeof(MyExceptionalHandler).FullName, handlerType); - var exception = metricsListener.AssertTagKeyExists(HandlerTimeMetricName, "nservicebus.failure_type"); - Assert.AreEqual(typeof(Exception).FullName, exception); + var error = metricsListener.AssertTagKeyExists(HandlerTimeMetricName, "error.type"); + Assert.AreEqual(typeof(Exception).FullName, error); + var result = metricsListener.AssertTagKeyExists(HandlerTimeMetricName, "execution.result"); + Assert.AreEqual("failure", result); } static async Task WhenMessagesHandled(Func messageFactory) diff --git a/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt b/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt index cdb7f1662f7..ca4172e09d1 100644 --- a/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt +++ b/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt @@ -2,16 +2,18 @@ "Note": "Changes to metrics API should result in an update to NServiceBusMeter version.", "ActivitySourceVersion": "0.2.0", "Tags": [ + "error.type", + "execution.result", "nservicebus.discriminator", - "nservicebus.queue", - "nservicebus.message_type", "nservicebus.failure_type", - "nservicebus.message_handler_type" + "nservicebus.message_handler_type", + "nservicebus.message_type", + "nservicebus.queue" ], "Metrics": [ - "nservicebus.messaging.successes => Counter", - "nservicebus.messaging.fetches => Counter", "nservicebus.messaging.failures => Counter", - "nservicebus.messaging.handler_time => Histogram, Unit: s" + "nservicebus.messaging.fetches => Counter", + "nservicebus.messaging.handler_time => Histogram, Unit: s", + "nservicebus.messaging.successes => Counter" ] } \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs b/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs index 54a06c43a6c..91814bd6fd2 100644 --- a/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs +++ b/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs @@ -16,12 +16,14 @@ public void Verify_MeterAPI() .GetFields(BindingFlags.Public | BindingFlags.Static) .Where(fi => fi.IsLiteral && !fi.IsInitOnly) .Select(x => x.GetRawConstantValue()) + .OrderBy(value => value) .ToList(); var metrics = typeof(Meters) .GetFields(BindingFlags.Static | BindingFlags.NonPublic) .Where(fi => typeof(Instrument).IsAssignableFrom(fi.FieldType)) .Select(fi => (Instrument)fi.GetValue(null)) .Select(x => $"{x.Name} => {x.GetType().Name.Split("`").First()}{(x.Unit == null ? "" : ", Unit: ")}{x.Unit ?? ""}") + .OrderBy(value => value) .ToList(); Approver.Verify(new { diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs index 63f1da979d4..459831e8295 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs @@ -28,13 +28,15 @@ public RecordMessageHandlingMetric(TagList tags) public void OnSuccess() { stopWatch.Stop(); + tags.Add(new KeyValuePair(MeterTags.ExecutionResult, "success")); Meters.MessageHandlerTime.Record(stopWatch.Elapsed.TotalSeconds, tags); } public void OnFailure(Exception error) { stopWatch.Stop(); - tags.Add(new KeyValuePair(MeterTags.FailureType, error.GetType().FullName)); + tags.Add(new KeyValuePair(MeterTags.ExecutionResult, "failure")); + tags.Add(new KeyValuePair(MeterTags.ErrorType, error.GetType().FullName)); Meters.MessageHandlerTime.Record(stopWatch.Elapsed.TotalSeconds, tags); } diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/MeterTags.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/MeterTags.cs index f0ccf0658a3..457a10b5e17 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/MeterTags.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/MeterTags.cs @@ -12,6 +12,8 @@ static class MeterTags public const string FailureType = "nservicebus.failure_type"; public const string MessageHandlerTypes = "nservicebus.message_handler_types"; public const string MessageHandlerType = "nservicebus.message_handler_type"; + public const string ExecutionResult = "execution.result"; + public const string ErrorType = "error.type"; public static TagList CommonMessagingMetricTags(string queueName, string discriminator, string messageType) { From b26f0f6c88460b43d6a975170aad4864cb2c33b3 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Thu, 20 Jun 2024 14:14:47 +0200 Subject: [PATCH 28/76] Add missing usings --- .../Core/OpenTelemetry/When_incoming_message_handled.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled.cs b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled.cs index b1fe69b4d20..1cd11fa9855 100644 --- a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled.cs +++ b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled.cs @@ -4,6 +4,7 @@ using System.Threading; using System.Threading.Tasks; using AcceptanceTesting; +using Metrics; using NUnit.Framework; using Conventions = AcceptanceTesting.Customization.Conventions; From 52b68f1cd5ecb396bf9ba196337465ea48bf532d Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Thu, 20 Jun 2024 14:18:47 +0200 Subject: [PATCH 29/76] Use collection expressions --- .../OpenTelemetry/Metrics/MeterTags.cs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/MeterTags.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/MeterTags.cs index 457a10b5e17..dda5c547752 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/MeterTags.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/MeterTags.cs @@ -1,7 +1,5 @@ namespace NServiceBus; -using System; -using System.Collections.Generic; using System.Diagnostics; static class MeterTags @@ -15,11 +13,11 @@ static class MeterTags public const string ExecutionResult = "execution.result"; public const string ErrorType = "error.type"; - public static TagList CommonMessagingMetricTags(string queueName, string discriminator, string messageType) - { - return new TagList(new KeyValuePair[] - { - new(QueueName, queueName ?? ""), new(EndpointDiscriminator, discriminator ?? ""), new(MessageType, messageType ?? "") - }.AsSpan()); - } + public static TagList CommonMessagingMetricTags(string queueName, string discriminator, string messageType) => + new( + [ + new(QueueName, queueName ?? ""), + new(EndpointDiscriminator, discriminator ?? ""), + new(MessageType, messageType ?? "") + ]); } \ No newline at end of file From b8984674294a3ad3aab670d4121818d0fba377c4 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Thu, 20 Jun 2024 15:03:01 +0200 Subject: [PATCH 30/76] Revert "Use collection expressions" This reverts commit 4b25647d9925b5ee3568257f9009c2565afbbe21. --- .../OpenTelemetry/Metrics/MeterTags.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/MeterTags.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/MeterTags.cs index dda5c547752..457a10b5e17 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/MeterTags.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/MeterTags.cs @@ -1,5 +1,7 @@ namespace NServiceBus; +using System; +using System.Collections.Generic; using System.Diagnostics; static class MeterTags @@ -13,11 +15,11 @@ static class MeterTags public const string ExecutionResult = "execution.result"; public const string ErrorType = "error.type"; - public static TagList CommonMessagingMetricTags(string queueName, string discriminator, string messageType) => - new( - [ - new(QueueName, queueName ?? ""), - new(EndpointDiscriminator, discriminator ?? ""), - new(MessageType, messageType ?? "") - ]); + public static TagList CommonMessagingMetricTags(string queueName, string discriminator, string messageType) + { + return new TagList(new KeyValuePair[] + { + new(QueueName, queueName ?? ""), new(EndpointDiscriminator, discriminator ?? ""), new(MessageType, messageType ?? "") + }.AsSpan()); + } } \ No newline at end of file From d93a2b87a72784e0ded73eb96b2577e5670e34d4 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Thu, 20 Jun 2024 17:01:32 +0200 Subject: [PATCH 31/76] Prefer being explicit. Do not use a common way for creating a TagList to prevent adding unwanted tags everywhere in the future. --- .../Metrics/MessageHandlingMetricsFactory.cs | 7 ++++++- .../OpenTelemetry/Metrics/MeterTags.cs | 12 ------------ 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs index 459831e8295..35c0af7bab8 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs @@ -10,7 +10,12 @@ class MessageHandlingMetricsFactory(string queueName, string discriminator) : IM public IMessageHandlingMetrics StartHandling(IInvokeHandlerContext context) { var messageType = context.MessageBeingHandled?.GetType().FullName; - TagList tagList = MeterTags.CommonMessagingMetricTags(queueName, discriminator, messageType); + var tagList = new TagList(new KeyValuePair[] + { + new(MeterTags.QueueName, queueName ?? ""), + new(MeterTags.EndpointDiscriminator, discriminator ?? ""), + new(MeterTags.MessageType, messageType ?? "") + }.AsSpan()); var handlerType = context.MessageHandler.Instance?.GetType().FullName; tagList.Add(MeterTags.MessageHandlerType, handlerType ?? ""); return new RecordMessageHandlingMetric(tagList); diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/MeterTags.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/MeterTags.cs index 457a10b5e17..aad3c33b8da 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/MeterTags.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/MeterTags.cs @@ -1,9 +1,5 @@ namespace NServiceBus; -using System; -using System.Collections.Generic; -using System.Diagnostics; - static class MeterTags { public const string EndpointDiscriminator = "nservicebus.discriminator"; @@ -14,12 +10,4 @@ static class MeterTags public const string MessageHandlerType = "nservicebus.message_handler_type"; public const string ExecutionResult = "execution.result"; public const string ErrorType = "error.type"; - - public static TagList CommonMessagingMetricTags(string queueName, string discriminator, string messageType) - { - return new TagList(new KeyValuePair[] - { - new(QueueName, queueName ?? ""), new(EndpointDiscriminator, discriminator ?? ""), new(MessageType, messageType ?? "") - }.AsSpan()); - } } \ No newline at end of file From e682bdf763c192470e0baab86475c59427bb95fc Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Fri, 21 Jun 2024 07:24:40 +0200 Subject: [PATCH 32/76] add comment Co-authored-by: David Boike --- .../OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs index 35c0af7bab8..f151421e4f2 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs @@ -33,6 +33,7 @@ public RecordMessageHandlingMetric(TagList tags) public void OnSuccess() { stopWatch.Stop(); + // This is what Add(string, object) does so skipping an unnecessary stack frame tags.Add(new KeyValuePair(MeterTags.ExecutionResult, "success")); Meters.MessageHandlerTime.Record(stopWatch.Elapsed.TotalSeconds, tags); } From acd155f84cbc15a9e39d497073943b30c008f40d Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Fri, 21 Jun 2024 07:25:18 +0200 Subject: [PATCH 33/76] merge tags Co-authored-by: David Boike --- .../OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs index f151421e4f2..90aefcc671b 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs @@ -10,14 +10,14 @@ class MessageHandlingMetricsFactory(string queueName, string discriminator) : IM public IMessageHandlingMetrics StartHandling(IInvokeHandlerContext context) { var messageType = context.MessageBeingHandled?.GetType().FullName; + var handlerType = context.MessageHandler.Instance?.GetType().FullName; var tagList = new TagList(new KeyValuePair[] { new(MeterTags.QueueName, queueName ?? ""), new(MeterTags.EndpointDiscriminator, discriminator ?? ""), - new(MeterTags.MessageType, messageType ?? "") + new(MeterTags.MessageType, messageType ?? ""), + new(MeterTags.MessageHandlerType, handlerType ?? "") }.AsSpan()); - var handlerType = context.MessageHandler.Instance?.GetType().FullName; - tagList.Add(MeterTags.MessageHandlerType, handlerType ?? ""); return new RecordMessageHandlingMetric(tagList); } } From 2f230443005764f49d81db7142232bd77de461d9 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Fri, 21 Jun 2024 07:32:16 +0200 Subject: [PATCH 34/76] Introduce a static NoOpMessageHandlingMetrics instance --- .../Metrics/NoOpMessageHandlingMetricsFactory.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/NoOpMessageHandlingMetricsFactory.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/NoOpMessageHandlingMetricsFactory.cs index 64f67b0be24..6959dbb2de7 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/NoOpMessageHandlingMetricsFactory.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/NoOpMessageHandlingMetricsFactory.cs @@ -13,16 +13,13 @@ class NoOpMessageHandlingMetricsFactory : IMessageHandlingMetricsFactory /// /// The invocation context. /// The instantiated IHandlingMetrics. - public IMessageHandlingMetrics StartHandling(IInvokeHandlerContext context) => new NoOpMessageHandlingMetrics(); + public IMessageHandlingMetrics StartHandling(IInvokeHandlerContext context) => NoOpMessageHandlingMetrics.Instance; } class NoOpMessageHandlingMetrics : IMessageHandlingMetrics { - public void OnSuccess() - { - } - - public void OnFailure(Exception error) - { - } + public static IMessageHandlingMetrics Instance => new NoOpMessageHandlingMetrics(); + NoOpMessageHandlingMetrics() { } + public void OnSuccess() { } + public void OnFailure(Exception error) { } } \ No newline at end of file From 00588c8655b1a325dc39de20133f4640c624a955 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Tue, 25 Jun 2024 11:05:34 +0200 Subject: [PATCH 35/76] Add the new tag to the approved tags list --- .../ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt b/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt index ca4172e09d1..02bc674380f 100644 --- a/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt +++ b/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt @@ -7,6 +7,7 @@ "nservicebus.discriminator", "nservicebus.failure_type", "nservicebus.message_handler_type", + "nservicebus.message_handler_types", "nservicebus.message_type", "nservicebus.queue" ], From 9819a40b62a48884ae897fe95208f1560884d956 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Tue, 25 Jun 2024 11:57:26 +0200 Subject: [PATCH 36/76] Do not use a factory --- .../Incoming/InvokeHandlerTerminatorTest.cs | 2 +- .../Hosting/HostingComponent.Configuration.cs | 9 +-- .../Metrics/MessageHandlingMetricsFactory.cs | 51 --------------- .../NoOpMessageHandlingMetricsFactory.cs | 25 -------- .../Metrics/RecordMessageHandlingMetric.cs | 62 +++++++++++++++++++ .../Incoming/IMessageHandlingMetrics.cs | 24 ------- .../IMessageHandlingMetricsFactory.cs | 17 ----- .../Incoming/InvokeHandlerTerminator.cs | 8 +-- .../Receiving/ReceiveComponent.cs | 2 +- 9 files changed, 70 insertions(+), 130 deletions(-) delete mode 100644 src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs delete mode 100644 src/NServiceBus.Core/OpenTelemetry/Metrics/NoOpMessageHandlingMetricsFactory.cs create mode 100644 src/NServiceBus.Core/OpenTelemetry/Metrics/RecordMessageHandlingMetric.cs delete mode 100644 src/NServiceBus.Core/Pipeline/Incoming/IMessageHandlingMetrics.cs delete mode 100644 src/NServiceBus.Core/Pipeline/Incoming/IMessageHandlingMetricsFactory.cs diff --git a/src/NServiceBus.Core.Tests/Pipeline/Incoming/InvokeHandlerTerminatorTest.cs b/src/NServiceBus.Core.Tests/Pipeline/Incoming/InvokeHandlerTerminatorTest.cs index 63354add4d8..bf096ca5af8 100644 --- a/src/NServiceBus.Core.Tests/Pipeline/Incoming/InvokeHandlerTerminatorTest.cs +++ b/src/NServiceBus.Core.Tests/Pipeline/Incoming/InvokeHandlerTerminatorTest.cs @@ -11,7 +11,7 @@ [TestFixture] public class InvokeHandlerTerminatorTest { - InvokeHandlerTerminator terminator = new InvokeHandlerTerminator(new NoOpActivityFactory(), new NoOpMessageHandlingMetricsFactory()); + InvokeHandlerTerminator terminator = new(new NoOpActivityFactory()); [Test] public async Task When_saga_found_and_handler_is_saga_should_invoke_handler() diff --git a/src/NServiceBus.Core/Hosting/HostingComponent.Configuration.cs b/src/NServiceBus.Core/Hosting/HostingComponent.Configuration.cs index f1efea7386b..46459200950 100644 --- a/src/NServiceBus.Core/Hosting/HostingComponent.Configuration.cs +++ b/src/NServiceBus.Core/Hosting/HostingComponent.Configuration.cs @@ -28,8 +28,7 @@ public static Configuration PrepareConfiguration(Settings settings, AssemblyScan settings.InstallationUserName, settings.ShouldRunInstallers, settings.UserRegistrations, - settings.EnableOpenTelemetry ? new ActivityFactory() : new NoOpActivityFactory(), - settings.EnableOpenTelemetry ? new MessageHandlingMetricsFactory(settings.EndpointName, settings.Discriminator) : new NoOpMessageHandlingMetricsFactory()); + settings.EnableOpenTelemetry ? new ActivityFactory() : new NoOpActivityFactory()); return configuration; } @@ -47,8 +46,7 @@ public Configuration(Settings settings, string installationUserName, bool shouldRunInstallers, List> userRegistrations, - IActivityFactory activityFactory, - IMessageHandlingMetricsFactory messageHandlingMetricsFactory) + IActivityFactory activityFactory) { AvailableTypes = availableTypes; CriticalError = criticalError; @@ -61,7 +59,6 @@ public Configuration(Settings settings, ShouldRunInstallers = shouldRunInstallers; UserRegistrations = userRegistrations; ActivityFactory = activityFactory; - MessageHandlingMetricsFactory = messageHandlingMetricsFactory; settings.ApplyHostIdDefaultIfNeeded(); HostInformation = new HostInformation(settings.HostId, settings.DisplayName, settings.Properties); @@ -95,7 +92,5 @@ public void AddStartupDiagnosticsSection(string sectionName, object section) public List> UserRegistrations { get; } public IActivityFactory ActivityFactory { get; set; } - - public IMessageHandlingMetricsFactory MessageHandlingMetricsFactory { get; set; } } } \ No newline at end of file diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs deleted file mode 100644 index 90aefcc671b..00000000000 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs +++ /dev/null @@ -1,51 +0,0 @@ -namespace NServiceBus; - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using Pipeline; - -class MessageHandlingMetricsFactory(string queueName, string discriminator) : IMessageHandlingMetricsFactory -{ - public IMessageHandlingMetrics StartHandling(IInvokeHandlerContext context) - { - var messageType = context.MessageBeingHandled?.GetType().FullName; - var handlerType = context.MessageHandler.Instance?.GetType().FullName; - var tagList = new TagList(new KeyValuePair[] - { - new(MeterTags.QueueName, queueName ?? ""), - new(MeterTags.EndpointDiscriminator, discriminator ?? ""), - new(MeterTags.MessageType, messageType ?? ""), - new(MeterTags.MessageHandlerType, handlerType ?? "") - }.AsSpan()); - return new RecordMessageHandlingMetric(tagList); - } -} - -class RecordMessageHandlingMetric : IMessageHandlingMetrics -{ - public RecordMessageHandlingMetric(TagList tags) - { - this.tags = tags; - stopWatch.Start(); - } - - public void OnSuccess() - { - stopWatch.Stop(); - // This is what Add(string, object) does so skipping an unnecessary stack frame - tags.Add(new KeyValuePair(MeterTags.ExecutionResult, "success")); - Meters.MessageHandlerTime.Record(stopWatch.Elapsed.TotalSeconds, tags); - } - - public void OnFailure(Exception error) - { - stopWatch.Stop(); - tags.Add(new KeyValuePair(MeterTags.ExecutionResult, "failure")); - tags.Add(new KeyValuePair(MeterTags.ErrorType, error.GetType().FullName)); - Meters.MessageHandlerTime.Record(stopWatch.Elapsed.TotalSeconds, tags); - } - - readonly Stopwatch stopWatch = new(); - TagList tags; -} \ No newline at end of file diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/NoOpMessageHandlingMetricsFactory.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/NoOpMessageHandlingMetricsFactory.cs deleted file mode 100644 index 6959dbb2de7..00000000000 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/NoOpMessageHandlingMetricsFactory.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace NServiceBus; - -using System; -using Pipeline; - -/// -/// Implementation of IHandlingMetricsFactory that does not perform anything. -/// -class NoOpMessageHandlingMetricsFactory : IMessageHandlingMetricsFactory -{ - /// - /// Instantiates a new IHandlingMetrics that does not record any metric. - /// - /// The invocation context. - /// The instantiated IHandlingMetrics. - public IMessageHandlingMetrics StartHandling(IInvokeHandlerContext context) => NoOpMessageHandlingMetrics.Instance; -} - -class NoOpMessageHandlingMetrics : IMessageHandlingMetrics -{ - public static IMessageHandlingMetrics Instance => new NoOpMessageHandlingMetrics(); - NoOpMessageHandlingMetrics() { } - public void OnSuccess() { } - public void OnFailure(Exception error) { } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/RecordMessageHandlingMetric.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/RecordMessageHandlingMetric.cs new file mode 100644 index 00000000000..a99306cbd85 --- /dev/null +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/RecordMessageHandlingMetric.cs @@ -0,0 +1,62 @@ +namespace NServiceBus; + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Pipeline; + +class RecordMessageHandlingMetric +{ + public RecordMessageHandlingMetric(IInvokeHandlerContext context) + { + this.context = context; + incomingPipelineMetricTags = context.Extensions.Get(); + if (incomingPipelineMetricTags.IsMetricTagsCollectionEnabled) + { + stopWatch.Start(); + } + } + + public void OnSuccess() + { + if (!incomingPipelineMetricTags.IsMetricTagsCollectionEnabled) + { + return; + } + + stopWatch.Stop(); + + incomingPipelineMetricTags.ApplyTags(ref tags, [MeterTags.QueueName, + MeterTags.EndpointDiscriminator, + MeterTags.MessageType, + MeterTags.MessageHandlerType]); + // This is what Add(string, object) does so skipping an unnecessary stack frame + tags.Add(new KeyValuePair(MeterTags.MessageHandlerType, context.MessageHandler.Instance.GetType().FullName)); + tags.Add(new KeyValuePair(MeterTags.ExecutionResult, "success")); + Meters.MessageHandlerTime.Record(stopWatch.Elapsed.TotalSeconds, tags); + } + + public void OnFailure(Exception error) + { + if (!incomingPipelineMetricTags.IsMetricTagsCollectionEnabled) + { + return; + } + + stopWatch.Stop(); + + incomingPipelineMetricTags.ApplyTags(ref tags, [MeterTags.QueueName, + MeterTags.EndpointDiscriminator, + MeterTags.MessageType, + MeterTags.MessageHandlerType]); + tags.Add(new KeyValuePair(MeterTags.MessageHandlerType, context.MessageHandler.Instance.GetType().FullName)); + tags.Add(new KeyValuePair(MeterTags.ExecutionResult, "failure")); + tags.Add(new KeyValuePair(MeterTags.ErrorType, error.GetType().FullName)); + Meters.MessageHandlerTime.Record(stopWatch.Elapsed.TotalSeconds, tags); + } + + readonly Stopwatch stopWatch = new(); + TagList tags; + readonly IncomingPipelineMetricTags incomingPipelineMetricTags; + readonly IInvokeHandlerContext context; +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Incoming/IMessageHandlingMetrics.cs b/src/NServiceBus.Core/Pipeline/Incoming/IMessageHandlingMetrics.cs deleted file mode 100644 index 3ed8d5a506b..00000000000 --- a/src/NServiceBus.Core/Pipeline/Incoming/IMessageHandlingMetrics.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace NServiceBus; - -using System; - -/// -/// Registers the metrics related to a specific message handler execution. -/// -interface IMessageHandlingMetrics -{ - /// - /// Registers the metrics related to the successful completion of the handler's execution. - /// This method is invoked only after the handler execution completed successfully. - /// This method will never be invoked if OnFailure method is invoked on the same instance. - /// - void OnSuccess(); - - /// - /// Registers the metrics related to the failed handler's execution. - /// This method is invoked only after the handler execution throws an exception. - /// This method will never be invoked if OnSuccess method is invoked on the same instance. - /// - /// The exception thrown by the handler. - void OnFailure(Exception error); -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Incoming/IMessageHandlingMetricsFactory.cs b/src/NServiceBus.Core/Pipeline/Incoming/IMessageHandlingMetricsFactory.cs deleted file mode 100644 index 1023ef60d32..00000000000 --- a/src/NServiceBus.Core/Pipeline/Incoming/IMessageHandlingMetricsFactory.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace NServiceBus; - -using Pipeline; - -/// -/// Factory for IMessageHandlingMetrics needed to record metrics for the handler invocation. -/// -interface IMessageHandlingMetricsFactory -{ - /// - /// Creates a new IMessageHandlingMetrics instance for recording the metrics for a specific handler execution. - /// - /// Needed to properly initialize the IHandlingMetrics instance. - /// The newly created IMessageHandlingMetrics instance. - IMessageHandlingMetrics StartHandling(IInvokeHandlerContext context); -} - diff --git a/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs b/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs index 76f8a4ebd5e..b7514617f33 100644 --- a/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs +++ b/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs @@ -6,7 +6,7 @@ using Pipeline; using Sagas; -class InvokeHandlerTerminator(IActivityFactory activityFactory, IMessageHandlingMetricsFactory metricsFactory) : PipelineTerminator +class InvokeHandlerTerminator(IActivityFactory activityFactory) : PipelineTerminator { protected override async Task Terminate(IInvokeHandlerContext context) @@ -22,7 +22,7 @@ protected override async Task Terminate(IInvokeHandlerContext context) // Might as well abort before invoking the handler if we're shutting down context.CancellationToken.ThrowIfCancellationRequested(); - var handlingMetrics = metricsFactory.StartHandling(context); + var handlingMetric = new RecordMessageHandlingMetric(context); var startTime = DateTimeOffset.UtcNow; try { @@ -32,7 +32,7 @@ await messageHandler .ConfigureAwait(false); activity?.SetStatus(ActivityStatusCode.Ok); - handlingMetrics.OnSuccess(); + handlingMetric.OnSuccess(); } #pragma warning disable PS0019 // Do not catch Exception without considering OperationCanceledException - enriching and rethrowing catch (Exception ex) @@ -45,7 +45,7 @@ await messageHandler ex.Data["Handler canceled"] = context.CancellationToken.IsCancellationRequested; activity?.SetErrorStatus(ex); - handlingMetrics.OnFailure(ex); + handlingMetric.OnFailure(ex); throw; } } diff --git a/src/NServiceBus.Core/Receiving/ReceiveComponent.cs b/src/NServiceBus.Core/Receiving/ReceiveComponent.cs index d972c894b93..bcc6050f667 100644 --- a/src/NServiceBus.Core/Receiving/ReceiveComponent.cs +++ b/src/NServiceBus.Core/Receiving/ReceiveComponent.cs @@ -71,7 +71,7 @@ public static ReceiveComponent Configure( return new LoadHandlersConnector(b.GetRequiredService()); }, "Gets all the handlers to invoke from the MessageHandler registry based on the message type."); - pipelineSettings.Register("InvokeHandlers", new InvokeHandlerTerminator(hostingConfiguration.ActivityFactory, hostingConfiguration.MessageHandlingMetricsFactory), "Calls the IHandleMessages.Handle(T)"); + pipelineSettings.Register("InvokeHandlers", new InvokeHandlerTerminator(hostingConfiguration.ActivityFactory), "Calls the IHandleMessages.Handle(T)"); var handlerDiagnostics = new Dictionary>(); From 75c9c47ceaf6d14753cb1d193596a54104dfdba6 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Tue, 25 Jun 2024 11:59:17 +0200 Subject: [PATCH 37/76] Propagate metric tags settings throughout the pipeline --- .../EnableMetricTagsCollectionBehavior.cs | 16 ++++++++++++++++ .../Metrics/MessagingMetricsFeature.cs | 5 +++++ .../Metrics/ReceiveDiagnosticsBehavior.cs | 2 ++ .../Incoming/IncomingPipelineMetricTags.cs | 7 +++++++ 4 files changed, 30 insertions(+) create mode 100644 src/NServiceBus.Core/OpenTelemetry/Metrics/EnableMetricTagsCollectionBehavior.cs diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/EnableMetricTagsCollectionBehavior.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/EnableMetricTagsCollectionBehavior.cs new file mode 100644 index 00000000000..941088b2610 --- /dev/null +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/EnableMetricTagsCollectionBehavior.cs @@ -0,0 +1,16 @@ +namespace NServiceBus; + +using System; +using System.Threading.Tasks; +using Pipeline; + +class EnableMetricTagsCollectionBehavior : IBehavior +{ + public Task Invoke(ITransportReceiveContext context, Func next) + { + var availableMetricTags = context.Extensions.Get(); + availableMetricTags.CollectMetricTags(); + + return next(context); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/MessagingMetricsFeature.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/MessagingMetricsFeature.cs index 66bc33b7470..0aa7abd1f4b 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/MessagingMetricsFeature.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/MessagingMetricsFeature.cs @@ -14,8 +14,13 @@ protected internal override void Setup(FeatureConfigurationContext context) { var discriminator = context.Receiving.InstanceSpecificQueueAddress?.Discriminator; var queueNameBase = context.Receiving.QueueNameBase; + var enableMetricTagsCollectionBehavior = new EnableMetricTagsCollectionBehavior(); var performanceDiagnosticsBehavior = new ReceiveDiagnosticsBehavior(queueNameBase, discriminator); + context.Pipeline.Register( + enableMetricTagsCollectionBehavior, + "Enables OpenTelemetry Metric Tags collection throughout the pipeline" + ); context.Pipeline.Register( performanceDiagnosticsBehavior, "Provides OpenTelemetry counters for message processing" diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/ReceiveDiagnosticsBehavior.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/ReceiveDiagnosticsBehavior.cs index 83bbd1f8bf3..93b71e16cbf 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/ReceiveDiagnosticsBehavior.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/ReceiveDiagnosticsBehavior.cs @@ -5,6 +5,8 @@ namespace NServiceBus; using System.Threading.Tasks; using Pipeline; +// This behavior is IIncomingPhysicalMessageContext and not ITransportReceiveContext +// to avoid capture successes for messages deduplicated by the Outbox class ReceiveDiagnosticsBehavior : IBehavior { public ReceiveDiagnosticsBehavior(string queueNameBase, string discriminator) diff --git a/src/NServiceBus.Core/Pipeline/Incoming/IncomingPipelineMetricTags.cs b/src/NServiceBus.Core/Pipeline/Incoming/IncomingPipelineMetricTags.cs index 702b97362ce..d5d22441579 100644 --- a/src/NServiceBus.Core/Pipeline/Incoming/IncomingPipelineMetricTags.cs +++ b/src/NServiceBus.Core/Pipeline/Incoming/IncomingPipelineMetricTags.cs @@ -11,6 +11,13 @@ namespace NServiceBus; /// public sealed class IncomingPipelineMetricTags { + internal bool IsMetricTagsCollectionEnabled { get; private set; } + + internal void CollectMetricTags() + { + IsMetricTagsCollectionEnabled = true; + } + Dictionary>? tags; /// From 6d8a041443785e1167c2e5d020db288656486b6b Mon Sep 17 00:00:00 2001 From: Sara Pellegrini Date: Thu, 30 May 2024 13:55:39 +0200 Subject: [PATCH 38/76] Register handling time metrics. --- .../Hosting/HostingComponent.Configuration.cs | 9 +++- .../Metrics/HandlingMetricsFactory.cs | 47 +++++++++++++++++++ .../OpenTelemetry/Metrics/Meters.cs | 3 ++ .../Metrics/NoOpHandlingMetricsFactory.cs | 28 +++++++++++ .../Pipeline/Incoming/IHandlingMetrics.cs | 24 ++++++++++ .../Incoming/IHandlingMetricsFactory.cs | 17 +++++++ .../Receiving/ReceiveComponent.cs | 2 +- 7 files changed, 127 insertions(+), 3 deletions(-) create mode 100644 src/NServiceBus.Core/OpenTelemetry/Metrics/HandlingMetricsFactory.cs create mode 100644 src/NServiceBus.Core/OpenTelemetry/Metrics/NoOpHandlingMetricsFactory.cs create mode 100644 src/NServiceBus.Core/Pipeline/Incoming/IHandlingMetrics.cs create mode 100644 src/NServiceBus.Core/Pipeline/Incoming/IHandlingMetricsFactory.cs diff --git a/src/NServiceBus.Core/Hosting/HostingComponent.Configuration.cs b/src/NServiceBus.Core/Hosting/HostingComponent.Configuration.cs index 46459200950..9ec23baf657 100644 --- a/src/NServiceBus.Core/Hosting/HostingComponent.Configuration.cs +++ b/src/NServiceBus.Core/Hosting/HostingComponent.Configuration.cs @@ -28,7 +28,8 @@ public static Configuration PrepareConfiguration(Settings settings, AssemblyScan settings.InstallationUserName, settings.ShouldRunInstallers, settings.UserRegistrations, - settings.EnableOpenTelemetry ? new ActivityFactory() : new NoOpActivityFactory()); + settings.EnableOpenTelemetry ? new ActivityFactory() : new NoOpActivityFactory(), + settings.EnableOpenTelemetry ? new HandlingMetricsFactory(settings.EndpointName) : new NoOpHandlingMetricsFactory()); return configuration; } @@ -46,7 +47,8 @@ public Configuration(Settings settings, string installationUserName, bool shouldRunInstallers, List> userRegistrations, - IActivityFactory activityFactory) + IActivityFactory activityFactory, + IHandlingMetricsFactory handlingMetricsFactory) { AvailableTypes = availableTypes; CriticalError = criticalError; @@ -59,6 +61,7 @@ public Configuration(Settings settings, ShouldRunInstallers = shouldRunInstallers; UserRegistrations = userRegistrations; ActivityFactory = activityFactory; + HandlingMetricsFactory = handlingMetricsFactory; settings.ApplyHostIdDefaultIfNeeded(); HostInformation = new HostInformation(settings.HostId, settings.DisplayName, settings.Properties); @@ -92,5 +95,7 @@ public void AddStartupDiagnosticsSection(string sectionName, object section) public List> UserRegistrations { get; } public IActivityFactory ActivityFactory { get; set; } + + public IHandlingMetricsFactory HandlingMetricsFactory { get; set; } } } \ No newline at end of file diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/HandlingMetricsFactory.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/HandlingMetricsFactory.cs new file mode 100644 index 00000000000..314152f9f47 --- /dev/null +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/HandlingMetricsFactory.cs @@ -0,0 +1,47 @@ +namespace NServiceBus; + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Pipeline; + +class HandlingMetricsFactory(string queueName/*, string discriminator*/) : IHandlingMetricsFactory +{ + public IHandlingMetrics StartHandling(IInvokeHandlerContext context) + { + var handlerType = context.MessageHandler.Instance?.GetType(); + var messageType = context.MessageBeingHandled?.GetType(); + return new RecordHandlingMetric(new( + [ + new(MeterTags.QueueName, queueName ?? ""), + // new(MeterTags.EndpointDiscriminator, discriminator ?? ""), + new(MeterTags.MessageHandlerType, handlerType), + new(MeterTags.MessageType, messageType) + ])); + } +} + +class RecordHandlingMetric : IHandlingMetrics +{ + public RecordHandlingMetric(TagList tags) + { + this.tags = tags; + stopWatch.Start(); + } + + public void OnSuccess() + { + stopWatch.Stop(); + Meters.HandlingTime.Record(stopWatch.ElapsedMilliseconds, tags); + } + + public void OnFailure(Exception error) + { + stopWatch.Stop(); + tags.Add(new KeyValuePair(MeterTags.FailureType, error.GetType())); + Meters.HandlingTime.Record(stopWatch.ElapsedMilliseconds, tags); + } + + readonly Stopwatch stopWatch = new(); + TagList tags; +} \ No newline at end of file diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/Meters.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/Meters.cs index 6ddec381589..e7a982d3010 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/Meters.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/Meters.cs @@ -20,6 +20,9 @@ class Meters internal static readonly Histogram MessageHandlerTime = NServiceBusMeter.CreateHistogram(Metrics.MessageHandlerTime, "s", "The time in seconds for the execution of the business code."); + internal static readonly Histogram HandlingTime = + NServiceBusMeter.CreateHistogram("nservicebus.messaging.handling_time", "ms", "The time in milliseconds for the execution of the business code."); + internal static readonly Counter TotalImmediateRetries = NServiceBusMeter.CreateCounter("nservicebus.recoverability.immediate", description: "Total number of immediate retries requested."); diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/NoOpHandlingMetricsFactory.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/NoOpHandlingMetricsFactory.cs new file mode 100644 index 00000000000..01309ac0e86 --- /dev/null +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/NoOpHandlingMetricsFactory.cs @@ -0,0 +1,28 @@ +namespace NServiceBus; + +using System; +using Pipeline; + +/// +/// Implementation of IHandlingMetricsFactory that does not perform anything. +/// +public class NoOpHandlingMetricsFactory : IHandlingMetricsFactory +{ + /// + /// Instantiates a new IHandlingMetrics that does not record any metric. + /// + /// The invocation context. + /// + public IHandlingMetrics StartHandling(IInvokeHandlerContext context) => new NoOpHandlingMetrics(); +} + +class NoOpHandlingMetrics : IHandlingMetrics +{ + public void OnSuccess() + { + } + + public void OnFailure(Exception error) + { + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Incoming/IHandlingMetrics.cs b/src/NServiceBus.Core/Pipeline/Incoming/IHandlingMetrics.cs new file mode 100644 index 00000000000..8be735120e5 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Incoming/IHandlingMetrics.cs @@ -0,0 +1,24 @@ +namespace NServiceBus; + +using System; + +/// +/// Registers the metrics related to a specific message handler execution. +/// +public interface IHandlingMetrics +{ + /// + /// Registers the metrics related to the successful completion of the handler's execution. + /// This method is invoked only after the handler execution completed successfully. + /// This method will never be invoked if OnFailure method is invoked on the same instance. + /// + void OnSuccess(); + + /// + /// Registers the metrics related to the failed handler's execution. + /// This method is invoked only after the handler execution throws an exception. + /// This method will never be invoked if OnSuccess method is invoked on the same instance. + /// + /// The exception thrown by the handler. + void OnFailure(Exception error); +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Incoming/IHandlingMetricsFactory.cs b/src/NServiceBus.Core/Pipeline/Incoming/IHandlingMetricsFactory.cs new file mode 100644 index 00000000000..a02ed1c98e2 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Incoming/IHandlingMetricsFactory.cs @@ -0,0 +1,17 @@ +namespace NServiceBus; + +using Pipeline; + +/// +/// Factory for IHandlingMetrics needed to record metrics for the handler invocation. +/// +public interface IHandlingMetricsFactory +{ + /// + /// Creates a new IHandlingMetrics instance for recording the metrics for a specific handler execution. + /// + /// Needed to properly initialize the IHandlingMetrics instance. + /// The newly created IHandlingMetrics instance. + IHandlingMetrics StartHandling(IInvokeHandlerContext context); +} + diff --git a/src/NServiceBus.Core/Receiving/ReceiveComponent.cs b/src/NServiceBus.Core/Receiving/ReceiveComponent.cs index bcc6050f667..fecd3016ded 100644 --- a/src/NServiceBus.Core/Receiving/ReceiveComponent.cs +++ b/src/NServiceBus.Core/Receiving/ReceiveComponent.cs @@ -71,7 +71,7 @@ public static ReceiveComponent Configure( return new LoadHandlersConnector(b.GetRequiredService()); }, "Gets all the handlers to invoke from the MessageHandler registry based on the message type."); - pipelineSettings.Register("InvokeHandlers", new InvokeHandlerTerminator(hostingConfiguration.ActivityFactory), "Calls the IHandleMessages.Handle(T)"); + pipelineSettings.Register("InvokeHandlers", new InvokeHandlerTerminator(hostingConfiguration.ActivityFactory, hostingConfiguration.HandlingMetricsFactory), "Calls the IHandleMessages.Handle(T)"); var handlerDiagnostics = new Dictionary>(); From cefabcf9a541dd63742e02d8cf5a0d8e7fae87b6 Mon Sep 17 00:00:00 2001 From: Sara Pellegrini Date: Wed, 5 Jun 2024 10:27:40 +0200 Subject: [PATCH 39/76] Refactor the code to avoid auto-generated structure. --- src/NServiceBus.Core/OpenTelemetry/Metrics/MeterTags.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/MeterTags.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/MeterTags.cs index aad3c33b8da..532124b16c7 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/MeterTags.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/MeterTags.cs @@ -1,5 +1,9 @@ namespace NServiceBus; +using System; +using System.Collections.Generic; +using System.Diagnostics; + static class MeterTags { public const string EndpointDiscriminator = "nservicebus.discriminator"; From 5dfb11d8f6f9f049f7541aa9397f115241613c53 Mon Sep 17 00:00:00 2001 From: Sara Pellegrini Date: Thu, 6 Jun 2024 14:13:16 +0200 Subject: [PATCH 40/76] Rename the static method that provides the common metric tags for clarity. --- .../Metrics/MessageHandlingMetricsFactory.cs | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs new file mode 100644 index 00000000000..a7aa3dfbe36 --- /dev/null +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs @@ -0,0 +1,43 @@ +namespace NServiceBus; + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Pipeline; + +class MessageHandlingMetricsFactory(string queueName, string discriminator) : IMessageHandlingMetricsFactory +{ + public IMessageHandlingMetrics StartHandling(IInvokeHandlerContext context) + { + var messageType = context.MessageBeingHandled?.GetType().FullName; + TagList tagList = MeterTags.CommonMessagingMetricTags(queueName, discriminator, messageType); + var handlerType = context.MessageHandler.Instance?.GetType().FullName; + tagList.Add(MeterTags.MessageHandlerType, handlerType ?? ""); + return new RecordMessageHandlingMetric(tagList); + } +} + +class RecordMessageHandlingMetric : IMessageHandlingMetrics +{ + public RecordMessageHandlingMetric(TagList tags) + { + this.tags = tags; + stopWatch.Start(); + } + + public void OnSuccess() + { + stopWatch.Stop(); + Meters.MessageHandlerTime.Record(stopWatch.ElapsedMilliseconds, tags); + } + + public void OnFailure(Exception error) + { + stopWatch.Stop(); + tags.Add(new KeyValuePair(MeterTags.FailureType, error.GetType().FullName)); + Meters.MessageHandlerTime.Record(stopWatch.ElapsedMilliseconds, tags); + } + + readonly Stopwatch stopWatch = new(); + TagList tags; +} \ No newline at end of file From dbaa30076a42956768ce1ba401aecc185a54cce0 Mon Sep 17 00:00:00 2001 From: Sara Pellegrini Date: Tue, 11 Jun 2024 06:21:25 +0200 Subject: [PATCH 41/76] Pull request remarks. --- .../Metrics/MessageHandlingMetricsFactory.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs index a7aa3dfbe36..459831e8295 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs @@ -28,14 +28,16 @@ public RecordMessageHandlingMetric(TagList tags) public void OnSuccess() { stopWatch.Stop(); - Meters.MessageHandlerTime.Record(stopWatch.ElapsedMilliseconds, tags); + tags.Add(new KeyValuePair(MeterTags.ExecutionResult, "success")); + Meters.MessageHandlerTime.Record(stopWatch.Elapsed.TotalSeconds, tags); } public void OnFailure(Exception error) { stopWatch.Stop(); - tags.Add(new KeyValuePair(MeterTags.FailureType, error.GetType().FullName)); - Meters.MessageHandlerTime.Record(stopWatch.ElapsedMilliseconds, tags); + tags.Add(new KeyValuePair(MeterTags.ExecutionResult, "failure")); + tags.Add(new KeyValuePair(MeterTags.ErrorType, error.GetType().FullName)); + Meters.MessageHandlerTime.Record(stopWatch.Elapsed.TotalSeconds, tags); } readonly Stopwatch stopWatch = new(); From e3cd8e57c48ae2f6036cef1ecba4406543aa7b4f Mon Sep 17 00:00:00 2001 From: Sara Pellegrini Date: Mon, 3 Jun 2024 13:48:25 +0200 Subject: [PATCH 42/76] Tests massage handling time metrics. --- ...n_incoming_message_handled_successfully.cs | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled_successfully.cs diff --git a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled_successfully.cs b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled_successfully.cs new file mode 100644 index 00000000000..6e5bdb561bd --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled_successfully.cs @@ -0,0 +1,64 @@ +namespace NServiceBus.AcceptanceTests.Core.OpenTelemetry; + +using System.Threading; +using System.Threading.Tasks; +using AcceptanceTesting; +using NUnit.Framework; +using Conventions = AcceptanceTesting.Customization.Conventions; + +public class WhenIncomingMessageHandledSuccessfully : NServiceBusAcceptanceTest +{ + [Test] + public async Task Should_record_handling_time() + { + using var metricsListener = TestingMetricListener.SetupNServiceBusMetricsListener(); + + _ = await Scenario.Define() + .WithEndpoint(b => + b.When(async (session, _) => + { + for (var x = 0; x < 5; x++) + { + await session.SendLocal(new MyMessage()); + } + })) + .Done(c => c.TotalHandledMessages == 5) + .Run(); + + string handlingTime = "nservicebus.messaging.handling_time"; + metricsListener.AssertMetric(handlingTime, 5); + var messageType = metricsListener.AssertTagKeyExists(handlingTime, "nservicebus.message_type"); + Assert.AreEqual(typeof(MyMessage).FullName, messageType); + var handlerType = metricsListener.AssertTagKeyExists(handlingTime, "nservicebus.message_handler_type"); + Assert.AreEqual(typeof(MyMessageHandler).FullName, handlerType); + var endpoint = metricsListener.AssertTagKeyExists(handlingTime, "nservicebus.queue"); + Assert.AreEqual(Conventions.EndpointNamingConvention(typeof(EndpointWithMetrics)), endpoint); + } + + class Context : ScenarioContext + { + public int TotalHandledMessages; + } + + class EndpointWithMetrics : EndpointConfigurationBuilder + { + public EndpointWithMetrics() => EndpointSetup(); + } + + class MyMessageHandler : IHandleMessages + { + readonly Context testContext; + + public MyMessageHandler(Context testContext) => this.testContext = testContext; + + public Task Handle(MyMessage message, IMessageHandlerContext context) + { + Interlocked.Increment(ref testContext.TotalHandledMessages); + return Task.CompletedTask; + } + } + + class MyMessage : IMessage + { + } +} \ No newline at end of file From a6927966a7f906f0eab3f7d8d3f36606d675be03 Mon Sep 17 00:00:00 2001 From: Sara Pellegrini Date: Thu, 6 Jun 2024 08:51:26 +0200 Subject: [PATCH 43/76] Introduce critical time metrics. --- .../When_incoming_message_handled.cs | 9 ++++ .../MeterTests.Verify_MeterAPI.approved.txt | 1 + .../Metrics/CriticalTimeMetrics.cs | 18 +++++++ .../Metrics/MessagingMetricsFeature.cs | 3 ++ .../OpenTelemetry/Metrics/Meters.cs | 4 +- .../OpenTelemetry/Metrics/Metrics.cs | 1 + .../Metrics/MetricsExtensions.cs | 48 +++++++++++++++++++ 7 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 src/NServiceBus.Core/OpenTelemetry/Metrics/CriticalTimeMetrics.cs create mode 100644 src/NServiceBus.Core/OpenTelemetry/Metrics/MetricsExtensions.cs diff --git a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled.cs b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled.cs index 1cd11fa9855..3d69e376dd1 100644 --- a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled.cs +++ b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled.cs @@ -11,7 +11,16 @@ public class When_incoming_message_handled : OpenTelemetryAcceptanceTest { static readonly string HandlerTimeMetricName = "nservicebus.messaging.handler_time"; + static readonly string CriticalTimeMetricName = "nservicebus.messaging.critical_time"; + [Test] + public async Task Should_record_critical_time() + { + using TestingMetricListener metricsListener = await WhenMessagesHandled(() => new MyMessage()); + metricsListener.AssertMetric(CriticalTimeMetricName, 5); + AssertMandatoryTags(metricsListener, CriticalTimeMetricName, typeof(MyMessage)); + } + [Test] public async Task Should_record_success_handling_time() { diff --git a/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt b/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt index 02bc674380f..2afe59a97dc 100644 --- a/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt +++ b/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt @@ -16,5 +16,6 @@ "nservicebus.messaging.fetches => Counter", "nservicebus.messaging.handler_time => Histogram, Unit: s", "nservicebus.messaging.successes => Counter" + "nservicebus.messaging.critical_time => Histogram, Unit: s" ] } \ No newline at end of file diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/CriticalTimeMetrics.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/CriticalTimeMetrics.cs new file mode 100644 index 00000000000..a887e5ac0e9 --- /dev/null +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/CriticalTimeMetrics.cs @@ -0,0 +1,18 @@ +namespace NServiceBus; + +using System.Threading.Tasks; + +class CriticalTimeMetrics(string queueName, string discriminator) +{ + public Task Record(ReceivePipelineCompleted pipeline) + { + pipeline.TryGetMessageType(out var messageType); + var tags = MeterTags.BaseTagList(queueName, discriminator, messageType); + + if (pipeline.TryGetDeliverAt(out var startTime) || pipeline.TryGetTimeSent(out startTime)) + { + Meters.CriticalTime.Record((pipeline.CompletedAt - startTime).TotalMilliseconds, tags); + } + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/MessagingMetricsFeature.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/MessagingMetricsFeature.cs index 0aa7abd1f4b..0ce9d191d67 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/MessagingMetricsFeature.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/MessagingMetricsFeature.cs @@ -12,6 +12,7 @@ class MessagingMetricsFeature : Feature /// protected internal override void Setup(FeatureConfigurationContext context) { + var queueName = context.Receiving.QueueNameBase; var discriminator = context.Receiving.InstanceSpecificQueueAddress?.Discriminator; var queueNameBase = context.Receiving.QueueNameBase; var enableMetricTagsCollectionBehavior = new EnableMetricTagsCollectionBehavior(); @@ -25,5 +26,7 @@ protected internal override void Setup(FeatureConfigurationContext context) performanceDiagnosticsBehavior, "Provides OpenTelemetry counters for message processing" ); + var criticalTimeMetrics = new CriticalTimeMetrics(queueName, discriminator); + context.Pipeline.OnReceivePipelineCompleted((pipeline, _) => criticalTimeMetrics.Record(pipeline)); } } \ No newline at end of file diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/Meters.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/Meters.cs index e7a982d3010..32f74606d09 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/Meters.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/Meters.cs @@ -20,8 +20,8 @@ class Meters internal static readonly Histogram MessageHandlerTime = NServiceBusMeter.CreateHistogram(Metrics.MessageHandlerTime, "s", "The time in seconds for the execution of the business code."); - internal static readonly Histogram HandlingTime = - NServiceBusMeter.CreateHistogram("nservicebus.messaging.handling_time", "ms", "The time in milliseconds for the execution of the business code."); + internal static readonly Histogram CriticalTime = + NServiceBusMeter.CreateHistogram(Metrics.CriticalTime, "ms", "The time in milliseconds between when the message was sent until processed by the endpoint."); internal static readonly Counter TotalImmediateRetries = NServiceBusMeter.CreateCounter("nservicebus.recoverability.immediate", description: "Total number of immediate retries requested."); diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/Metrics.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/Metrics.cs index b7f0d7e91b0..782b1b3868c 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/Metrics.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/Metrics.cs @@ -6,4 +6,5 @@ static class Metrics public const string TotalFetched = "nservicebus.messaging.fetches"; public const string TotalFailures = "nservicebus.messaging.failures"; public const string MessageHandlerTime = "nservicebus.messaging.handler_time"; + public const string CriticalTime = "nservicebus.messaging.critical_time"; } \ No newline at end of file diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/MetricsExtensions.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/MetricsExtensions.cs new file mode 100644 index 00000000000..9a2796b7730 --- /dev/null +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/MetricsExtensions.cs @@ -0,0 +1,48 @@ +namespace NServiceBus; + +using System; +using System.Collections.Generic; +using System.Linq; + +static class MetricsExtensions +{ + public static bool TryGetTimeSent(this ReceivePipelineCompleted completed, out DateTimeOffset timeSent) + { + var headers = completed.ProcessedMessage.Headers; + if (headers.TryGetValue(Headers.TimeSent, out var timeSentString)) + { + timeSent = DateTimeOffsetHelper.ToDateTimeOffset(timeSentString); + return true; + } + timeSent = DateTimeOffset.MinValue; + return false; + } + + public static bool TryGetDeliverAt(this ReceivePipelineCompleted completed, out DateTimeOffset deliverAt) + { + var headers = completed.ProcessedMessage.Headers; + if (headers.TryGetValue(Headers.DeliverAt, out var deliverAtString)) + { + deliverAt = DateTimeOffsetHelper.ToDateTimeOffset(deliverAtString); + return true; + } + deliverAt = DateTimeOffset.MinValue; + return false; + } + + public static bool TryGetMessageType(this ReceivePipelineCompleted completed, out string processedMessageType) + => completed.ProcessedMessage.Headers.TryGetMessageType(out processedMessageType); + static bool TryGetMessageType(this IReadOnlyDictionary headers, out string processedMessageType) + { + if (headers.TryGetValue(Headers.EnclosedMessageTypes, out var enclosedMessageType)) + { + string messageTypeHeader = !string.IsNullOrEmpty(enclosedMessageType) ? enclosedMessageType.Split(';').FirstOrDefault() : default; + string messageTypeName = !string.IsNullOrEmpty(messageTypeHeader) ? messageTypeHeader.Split(',').FirstOrDefault() : default; + processedMessageType = messageTypeName; + return true; + } + processedMessageType = null; + return false; + } + +} \ No newline at end of file From 0fb6a597b995a7e1350d216fb493a4257ef05f83 Mon Sep 17 00:00:00 2001 From: Sara Pellegrini Date: Mon, 10 Jun 2024 12:13:19 +0200 Subject: [PATCH 44/76] Fixes after merge. --- .../Core/OpenTelemetry/Metrics/TestingMetricListener.cs | 3 +-- .../Core/OpenTelemetry/When_incoming_message_handled.cs | 2 +- .../OpenTelemetry/Metrics/CriticalTimeMetrics.cs | 5 +++-- .../OpenTelemetry/Metrics/MessagingMetricsFeature.cs | 2 +- src/NServiceBus.Core/OpenTelemetry/Metrics/Meters.cs | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/Metrics/TestingMetricListener.cs b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/Metrics/TestingMetricListener.cs index 7d81b2366e0..469964425ad 100644 --- a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/Metrics/TestingMetricListener.cs +++ b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/Metrics/TestingMetricListener.cs @@ -46,8 +46,7 @@ public TestingMetricListener(string sourceName) ReportedMeters.AddOrUpdate(instrument.Name, 1, (_, val) => val + 1); Tags.AddOrUpdate(instrument.Name, _ => tags, (_, _) => tags); }); - meterListener.Start() - ; + meterListener.Start(); } public static TestingMetricListener SetupNServiceBusMetricsListener() => diff --git a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled.cs b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled.cs index 3d69e376dd1..ff6223f3ea1 100644 --- a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled.cs +++ b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled.cs @@ -20,7 +20,7 @@ public async Task Should_record_critical_time() metricsListener.AssertMetric(CriticalTimeMetricName, 5); AssertMandatoryTags(metricsListener, CriticalTimeMetricName, typeof(MyMessage)); } - + [Test] public async Task Should_record_success_handling_time() { diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/CriticalTimeMetrics.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/CriticalTimeMetrics.cs index a887e5ac0e9..6718d457d92 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/CriticalTimeMetrics.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/CriticalTimeMetrics.cs @@ -1,13 +1,14 @@ namespace NServiceBus; +using System.Threading; using System.Threading.Tasks; class CriticalTimeMetrics(string queueName, string discriminator) { - public Task Record(ReceivePipelineCompleted pipeline) + public Task Record(ReceivePipelineCompleted pipeline, CancellationToken cancellationToken) { pipeline.TryGetMessageType(out var messageType); - var tags = MeterTags.BaseTagList(queueName, discriminator, messageType); + var tags = MeterTags.CommonMessagingMetricTags(queueName, discriminator, messageType); if (pipeline.TryGetDeliverAt(out var startTime) || pipeline.TryGetTimeSent(out startTime)) { diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/MessagingMetricsFeature.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/MessagingMetricsFeature.cs index 0ce9d191d67..d741eabe8e1 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/MessagingMetricsFeature.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/MessagingMetricsFeature.cs @@ -27,6 +27,6 @@ protected internal override void Setup(FeatureConfigurationContext context) "Provides OpenTelemetry counters for message processing" ); var criticalTimeMetrics = new CriticalTimeMetrics(queueName, discriminator); - context.Pipeline.OnReceivePipelineCompleted((pipeline, _) => criticalTimeMetrics.Record(pipeline)); + context.Pipeline.OnReceivePipelineCompleted((pipeline, token) => criticalTimeMetrics.Record(pipeline, token)); } } \ No newline at end of file diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/Meters.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/Meters.cs index 32f74606d09..a4009e03bc8 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/Meters.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/Meters.cs @@ -21,7 +21,7 @@ class Meters NServiceBusMeter.CreateHistogram(Metrics.MessageHandlerTime, "s", "The time in seconds for the execution of the business code."); internal static readonly Histogram CriticalTime = - NServiceBusMeter.CreateHistogram(Metrics.CriticalTime, "ms", "The time in milliseconds between when the message was sent until processed by the endpoint."); + NServiceBusMeter.CreateHistogram(Metrics.CriticalTime, "s", "The time in seconds between when the message was sent until processed by the endpoint."); internal static readonly Counter TotalImmediateRetries = NServiceBusMeter.CreateCounter("nservicebus.recoverability.immediate", description: "Total number of immediate retries requested."); From ed80e4bc3d6aed14dc0dfdfb5d9faf3f73174626 Mon Sep 17 00:00:00 2001 From: Sara Pellegrini Date: Tue, 11 Jun 2024 06:36:15 +0200 Subject: [PATCH 45/76] Make CancellationToken optional. Use Dictionary i.o. IReadOnlyDictionary for performance. --- .../OpenTelemetry/Metrics/CriticalTimeMetrics.cs | 2 +- src/NServiceBus.Core/OpenTelemetry/Metrics/MetricsExtensions.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/CriticalTimeMetrics.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/CriticalTimeMetrics.cs index 6718d457d92..98eb3d3adee 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/CriticalTimeMetrics.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/CriticalTimeMetrics.cs @@ -5,7 +5,7 @@ class CriticalTimeMetrics(string queueName, string discriminator) { - public Task Record(ReceivePipelineCompleted pipeline, CancellationToken cancellationToken) + public Task Record(ReceivePipelineCompleted pipeline, CancellationToken cancellationToken = default) { pipeline.TryGetMessageType(out var messageType); var tags = MeterTags.CommonMessagingMetricTags(queueName, discriminator, messageType); diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/MetricsExtensions.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/MetricsExtensions.cs index 9a2796b7730..0f260222a66 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/MetricsExtensions.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/MetricsExtensions.cs @@ -32,7 +32,7 @@ public static bool TryGetDeliverAt(this ReceivePipelineCompleted completed, out public static bool TryGetMessageType(this ReceivePipelineCompleted completed, out string processedMessageType) => completed.ProcessedMessage.Headers.TryGetMessageType(out processedMessageType); - static bool TryGetMessageType(this IReadOnlyDictionary headers, out string processedMessageType) + static bool TryGetMessageType(this Dictionary headers, out string processedMessageType) { if (headers.TryGetValue(Headers.EnclosedMessageTypes, out var enclosedMessageType)) { From a2832976e78c355d5fdb3b1c1078cf66f9884355 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Thu, 20 Jun 2024 17:13:11 +0200 Subject: [PATCH 46/76] refer being explicit. Do not use a common way for creating a TagList to prevent adding unwanted tags everywhere in the future. --- .../OpenTelemetry/Metrics/CriticalTimeMetrics.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/CriticalTimeMetrics.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/CriticalTimeMetrics.cs index 98eb3d3adee..781b9c9e2d5 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/CriticalTimeMetrics.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/CriticalTimeMetrics.cs @@ -1,5 +1,8 @@ namespace NServiceBus; +using System; +using System.Collections.Generic; +using System.Diagnostics; using System.Threading; using System.Threading.Tasks; @@ -7,12 +10,17 @@ class CriticalTimeMetrics(string queueName, string discriminator) { public Task Record(ReceivePipelineCompleted pipeline, CancellationToken cancellationToken = default) { - pipeline.TryGetMessageType(out var messageType); - var tags = MeterTags.CommonMessagingMetricTags(queueName, discriminator, messageType); + _ = pipeline.TryGetMessageType(out var messageType); + var tagList = new TagList(new KeyValuePair[] + { + new(MeterTags.QueueName, queueName ?? ""), + new(MeterTags.EndpointDiscriminator, discriminator ?? ""), + new(MeterTags.MessageType, messageType ?? "") + }.AsSpan()); if (pipeline.TryGetDeliverAt(out var startTime) || pipeline.TryGetTimeSent(out startTime)) { - Meters.CriticalTime.Record((pipeline.CompletedAt - startTime).TotalMilliseconds, tags); + Meters.CriticalTime.Record((pipeline.CompletedAt - startTime).TotalMilliseconds, tagList); } return Task.CompletedTask; } From 48d3d5aa97ac8849d0ecfbbdf09069f5ce633eda Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Thu, 20 Jun 2024 17:13:38 +0200 Subject: [PATCH 47/76] Missing using statement --- .../OpenTelemetry/When_incoming_message_handled_successfully.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled_successfully.cs b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled_successfully.cs index 6e5bdb561bd..80483dd04c5 100644 --- a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled_successfully.cs +++ b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled_successfully.cs @@ -3,6 +3,7 @@ using System.Threading; using System.Threading.Tasks; using AcceptanceTesting; +using Metrics; using NUnit.Framework; using Conventions = AcceptanceTesting.Customization.Conventions; From 5db3bf1fe9018067254d26726b43baeb3560c70f Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Thu, 20 Jun 2024 17:18:33 +0200 Subject: [PATCH 48/76] Fix Approved API --- .../ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt b/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt index 2afe59a97dc..c173645f840 100644 --- a/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt +++ b/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt @@ -12,10 +12,10 @@ "nservicebus.queue" ], "Metrics": [ + "nservicebus.messaging.critical_time => Histogram, Unit: s", "nservicebus.messaging.failures => Counter", "nservicebus.messaging.fetches => Counter", "nservicebus.messaging.handler_time => Histogram, Unit: s", "nservicebus.messaging.successes => Counter" - "nservicebus.messaging.critical_time => Histogram, Unit: s" ] } \ No newline at end of file From 5c3e6af718b21f3290256c7357c3082b2abd9c04 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Thu, 20 Jun 2024 17:24:41 +0200 Subject: [PATCH 49/76] Fix failing test --- .../OpenTelemetry/When_incoming_message_handled_successfully.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled_successfully.cs b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled_successfully.cs index 80483dd04c5..7ef56c16b7e 100644 --- a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled_successfully.cs +++ b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled_successfully.cs @@ -26,7 +26,7 @@ public async Task Should_record_handling_time() .Done(c => c.TotalHandledMessages == 5) .Run(); - string handlingTime = "nservicebus.messaging.handling_time"; + string handlingTime = "nservicebus.messaging.handler_time"; metricsListener.AssertMetric(handlingTime, 5); var messageType = metricsListener.AssertTagKeyExists(handlingTime, "nservicebus.message_type"); Assert.AreEqual(typeof(MyMessage).FullName, messageType); From dfb6ab6081dd88903b5d5a2f1dcb9bb17bca5369 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Thu, 20 Jun 2024 17:28:08 +0200 Subject: [PATCH 50/76] Fix test --- .../When_incoming_message_handled_successfully.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled_successfully.cs b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled_successfully.cs index 7ef56c16b7e..cd5fd6e9e1f 100644 --- a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled_successfully.cs +++ b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled_successfully.cs @@ -7,6 +7,7 @@ using NUnit.Framework; using Conventions = AcceptanceTesting.Customization.Conventions; +[NonParallelizable] public class WhenIncomingMessageHandledSuccessfully : NServiceBusAcceptanceTest { [Test] @@ -59,7 +60,7 @@ public Task Handle(MyMessage message, IMessageHandlerContext context) } } - class MyMessage : IMessage + public class MyMessage : IMessage { } } \ No newline at end of file From 3b7e41cec0650ce346ef230d95bb030280ae5c56 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Thu, 20 Jun 2024 18:29:19 +0200 Subject: [PATCH 51/76] Use seconds as specified in the meter setup --- .../OpenTelemetry/Metrics/CriticalTimeMetrics.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/CriticalTimeMetrics.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/CriticalTimeMetrics.cs index 781b9c9e2d5..60643da6bfb 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/CriticalTimeMetrics.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/CriticalTimeMetrics.cs @@ -20,7 +20,7 @@ public Task Record(ReceivePipelineCompleted pipeline, CancellationToken cancella if (pipeline.TryGetDeliverAt(out var startTime) || pipeline.TryGetTimeSent(out startTime)) { - Meters.CriticalTime.Record((pipeline.CompletedAt - startTime).TotalMilliseconds, tagList); + Meters.CriticalTime.Record((pipeline.CompletedAt - startTime).TotalSeconds, tagList); } return Task.CompletedTask; } From 65bcafd610c159a776c940540759b374f48c6bf3 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Fri, 21 Jun 2024 07:28:58 +0200 Subject: [PATCH 52/76] Update src/NServiceBus.Core/OpenTelemetry/Metrics/MetricsExtensions.cs Co-authored-by: David Boike --- src/NServiceBus.Core/OpenTelemetry/Metrics/MetricsExtensions.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/MetricsExtensions.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/MetricsExtensions.cs index 0f260222a66..7c1594d1ee8 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/MetricsExtensions.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/MetricsExtensions.cs @@ -32,6 +32,7 @@ public static bool TryGetDeliverAt(this ReceivePipelineCompleted completed, out public static bool TryGetMessageType(this ReceivePipelineCompleted completed, out string processedMessageType) => completed.ProcessedMessage.Headers.TryGetMessageType(out processedMessageType); + static bool TryGetMessageType(this Dictionary headers, out string processedMessageType) { if (headers.TryGetValue(Headers.EnclosedMessageTypes, out var enclosedMessageType)) From dfa4d1aa55cf71134dfd1fac9ca3b608f3c47b35 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Fri, 21 Jun 2024 09:46:12 +0200 Subject: [PATCH 53/76] Fix formatting... --- src/NServiceBus.Core/OpenTelemetry/Metrics/MetricsExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/MetricsExtensions.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/MetricsExtensions.cs index 7c1594d1ee8..63c20a22902 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/MetricsExtensions.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/MetricsExtensions.cs @@ -32,7 +32,7 @@ public static bool TryGetDeliverAt(this ReceivePipelineCompleted completed, out public static bool TryGetMessageType(this ReceivePipelineCompleted completed, out string processedMessageType) => completed.ProcessedMessage.Headers.TryGetMessageType(out processedMessageType); - + static bool TryGetMessageType(this Dictionary headers, out string processedMessageType) { if (headers.TryGetValue(Headers.EnclosedMessageTypes, out var enclosedMessageType)) From 5c650c564707670f3584e919f7d2510dcd20a076 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Tue, 25 Jun 2024 14:18:56 +0200 Subject: [PATCH 54/76] Update code after rebase --- .../Hosting/HostingComponent.Configuration.cs | 9 +--- .../Metrics/HandlingMetricsFactory.cs | 47 ------------------- .../Metrics/MessageHandlingMetricsFactory.cs | 45 ------------------ .../OpenTelemetry/Metrics/MeterTags.cs | 4 -- .../Metrics/NoOpHandlingMetricsFactory.cs | 28 ----------- .../Pipeline/Incoming/IHandlingMetrics.cs | 24 ---------- .../Incoming/IHandlingMetricsFactory.cs | 17 ------- .../Receiving/ReceiveComponent.cs | 2 +- 8 files changed, 3 insertions(+), 173 deletions(-) delete mode 100644 src/NServiceBus.Core/OpenTelemetry/Metrics/HandlingMetricsFactory.cs delete mode 100644 src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs delete mode 100644 src/NServiceBus.Core/OpenTelemetry/Metrics/NoOpHandlingMetricsFactory.cs delete mode 100644 src/NServiceBus.Core/Pipeline/Incoming/IHandlingMetrics.cs delete mode 100644 src/NServiceBus.Core/Pipeline/Incoming/IHandlingMetricsFactory.cs diff --git a/src/NServiceBus.Core/Hosting/HostingComponent.Configuration.cs b/src/NServiceBus.Core/Hosting/HostingComponent.Configuration.cs index 9ec23baf657..46459200950 100644 --- a/src/NServiceBus.Core/Hosting/HostingComponent.Configuration.cs +++ b/src/NServiceBus.Core/Hosting/HostingComponent.Configuration.cs @@ -28,8 +28,7 @@ public static Configuration PrepareConfiguration(Settings settings, AssemblyScan settings.InstallationUserName, settings.ShouldRunInstallers, settings.UserRegistrations, - settings.EnableOpenTelemetry ? new ActivityFactory() : new NoOpActivityFactory(), - settings.EnableOpenTelemetry ? new HandlingMetricsFactory(settings.EndpointName) : new NoOpHandlingMetricsFactory()); + settings.EnableOpenTelemetry ? new ActivityFactory() : new NoOpActivityFactory()); return configuration; } @@ -47,8 +46,7 @@ public Configuration(Settings settings, string installationUserName, bool shouldRunInstallers, List> userRegistrations, - IActivityFactory activityFactory, - IHandlingMetricsFactory handlingMetricsFactory) + IActivityFactory activityFactory) { AvailableTypes = availableTypes; CriticalError = criticalError; @@ -61,7 +59,6 @@ public Configuration(Settings settings, ShouldRunInstallers = shouldRunInstallers; UserRegistrations = userRegistrations; ActivityFactory = activityFactory; - HandlingMetricsFactory = handlingMetricsFactory; settings.ApplyHostIdDefaultIfNeeded(); HostInformation = new HostInformation(settings.HostId, settings.DisplayName, settings.Properties); @@ -95,7 +92,5 @@ public void AddStartupDiagnosticsSection(string sectionName, object section) public List> UserRegistrations { get; } public IActivityFactory ActivityFactory { get; set; } - - public IHandlingMetricsFactory HandlingMetricsFactory { get; set; } } } \ No newline at end of file diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/HandlingMetricsFactory.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/HandlingMetricsFactory.cs deleted file mode 100644 index 314152f9f47..00000000000 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/HandlingMetricsFactory.cs +++ /dev/null @@ -1,47 +0,0 @@ -namespace NServiceBus; - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using Pipeline; - -class HandlingMetricsFactory(string queueName/*, string discriminator*/) : IHandlingMetricsFactory -{ - public IHandlingMetrics StartHandling(IInvokeHandlerContext context) - { - var handlerType = context.MessageHandler.Instance?.GetType(); - var messageType = context.MessageBeingHandled?.GetType(); - return new RecordHandlingMetric(new( - [ - new(MeterTags.QueueName, queueName ?? ""), - // new(MeterTags.EndpointDiscriminator, discriminator ?? ""), - new(MeterTags.MessageHandlerType, handlerType), - new(MeterTags.MessageType, messageType) - ])); - } -} - -class RecordHandlingMetric : IHandlingMetrics -{ - public RecordHandlingMetric(TagList tags) - { - this.tags = tags; - stopWatch.Start(); - } - - public void OnSuccess() - { - stopWatch.Stop(); - Meters.HandlingTime.Record(stopWatch.ElapsedMilliseconds, tags); - } - - public void OnFailure(Exception error) - { - stopWatch.Stop(); - tags.Add(new KeyValuePair(MeterTags.FailureType, error.GetType())); - Meters.HandlingTime.Record(stopWatch.ElapsedMilliseconds, tags); - } - - readonly Stopwatch stopWatch = new(); - TagList tags; -} \ No newline at end of file diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs deleted file mode 100644 index 459831e8295..00000000000 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/MessageHandlingMetricsFactory.cs +++ /dev/null @@ -1,45 +0,0 @@ -namespace NServiceBus; - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using Pipeline; - -class MessageHandlingMetricsFactory(string queueName, string discriminator) : IMessageHandlingMetricsFactory -{ - public IMessageHandlingMetrics StartHandling(IInvokeHandlerContext context) - { - var messageType = context.MessageBeingHandled?.GetType().FullName; - TagList tagList = MeterTags.CommonMessagingMetricTags(queueName, discriminator, messageType); - var handlerType = context.MessageHandler.Instance?.GetType().FullName; - tagList.Add(MeterTags.MessageHandlerType, handlerType ?? ""); - return new RecordMessageHandlingMetric(tagList); - } -} - -class RecordMessageHandlingMetric : IMessageHandlingMetrics -{ - public RecordMessageHandlingMetric(TagList tags) - { - this.tags = tags; - stopWatch.Start(); - } - - public void OnSuccess() - { - stopWatch.Stop(); - tags.Add(new KeyValuePair(MeterTags.ExecutionResult, "success")); - Meters.MessageHandlerTime.Record(stopWatch.Elapsed.TotalSeconds, tags); - } - - public void OnFailure(Exception error) - { - stopWatch.Stop(); - tags.Add(new KeyValuePair(MeterTags.ExecutionResult, "failure")); - tags.Add(new KeyValuePair(MeterTags.ErrorType, error.GetType().FullName)); - Meters.MessageHandlerTime.Record(stopWatch.Elapsed.TotalSeconds, tags); - } - - readonly Stopwatch stopWatch = new(); - TagList tags; -} \ No newline at end of file diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/MeterTags.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/MeterTags.cs index 532124b16c7..aad3c33b8da 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/MeterTags.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/MeterTags.cs @@ -1,9 +1,5 @@ namespace NServiceBus; -using System; -using System.Collections.Generic; -using System.Diagnostics; - static class MeterTags { public const string EndpointDiscriminator = "nservicebus.discriminator"; diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/NoOpHandlingMetricsFactory.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/NoOpHandlingMetricsFactory.cs deleted file mode 100644 index 01309ac0e86..00000000000 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/NoOpHandlingMetricsFactory.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace NServiceBus; - -using System; -using Pipeline; - -/// -/// Implementation of IHandlingMetricsFactory that does not perform anything. -/// -public class NoOpHandlingMetricsFactory : IHandlingMetricsFactory -{ - /// - /// Instantiates a new IHandlingMetrics that does not record any metric. - /// - /// The invocation context. - /// - public IHandlingMetrics StartHandling(IInvokeHandlerContext context) => new NoOpHandlingMetrics(); -} - -class NoOpHandlingMetrics : IHandlingMetrics -{ - public void OnSuccess() - { - } - - public void OnFailure(Exception error) - { - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Incoming/IHandlingMetrics.cs b/src/NServiceBus.Core/Pipeline/Incoming/IHandlingMetrics.cs deleted file mode 100644 index 8be735120e5..00000000000 --- a/src/NServiceBus.Core/Pipeline/Incoming/IHandlingMetrics.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace NServiceBus; - -using System; - -/// -/// Registers the metrics related to a specific message handler execution. -/// -public interface IHandlingMetrics -{ - /// - /// Registers the metrics related to the successful completion of the handler's execution. - /// This method is invoked only after the handler execution completed successfully. - /// This method will never be invoked if OnFailure method is invoked on the same instance. - /// - void OnSuccess(); - - /// - /// Registers the metrics related to the failed handler's execution. - /// This method is invoked only after the handler execution throws an exception. - /// This method will never be invoked if OnSuccess method is invoked on the same instance. - /// - /// The exception thrown by the handler. - void OnFailure(Exception error); -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Incoming/IHandlingMetricsFactory.cs b/src/NServiceBus.Core/Pipeline/Incoming/IHandlingMetricsFactory.cs deleted file mode 100644 index a02ed1c98e2..00000000000 --- a/src/NServiceBus.Core/Pipeline/Incoming/IHandlingMetricsFactory.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace NServiceBus; - -using Pipeline; - -/// -/// Factory for IHandlingMetrics needed to record metrics for the handler invocation. -/// -public interface IHandlingMetricsFactory -{ - /// - /// Creates a new IHandlingMetrics instance for recording the metrics for a specific handler execution. - /// - /// Needed to properly initialize the IHandlingMetrics instance. - /// The newly created IHandlingMetrics instance. - IHandlingMetrics StartHandling(IInvokeHandlerContext context); -} - diff --git a/src/NServiceBus.Core/Receiving/ReceiveComponent.cs b/src/NServiceBus.Core/Receiving/ReceiveComponent.cs index fecd3016ded..bcc6050f667 100644 --- a/src/NServiceBus.Core/Receiving/ReceiveComponent.cs +++ b/src/NServiceBus.Core/Receiving/ReceiveComponent.cs @@ -71,7 +71,7 @@ public static ReceiveComponent Configure( return new LoadHandlersConnector(b.GetRequiredService()); }, "Gets all the handlers to invoke from the MessageHandler registry based on the message type."); - pipelineSettings.Register("InvokeHandlers", new InvokeHandlerTerminator(hostingConfiguration.ActivityFactory, hostingConfiguration.HandlingMetricsFactory), "Calls the IHandleMessages.Handle(T)"); + pipelineSettings.Register("InvokeHandlers", new InvokeHandlerTerminator(hostingConfiguration.ActivityFactory), "Calls the IHandleMessages.Handle(T)"); var handlerDiagnostics = new Dictionary>(); From 5829bdfe35c20a97691c3cf469ab8e3185c87363 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Tue, 25 Jun 2024 14:46:59 +0200 Subject: [PATCH 55/76] Use the new IncomingPipelineMetricTags --- .../Metrics/CriticalTimeMetrics.cs | 27 ------------------- .../Metrics/MessagingMetricsFeature.cs | 9 +++---- .../Metrics/MetricsExtensions.cs | 24 ++--------------- .../Pipeline/MainPipelineExecutor.cs | 19 ++++++++++++- 4 files changed, 23 insertions(+), 56 deletions(-) delete mode 100644 src/NServiceBus.Core/OpenTelemetry/Metrics/CriticalTimeMetrics.cs diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/CriticalTimeMetrics.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/CriticalTimeMetrics.cs deleted file mode 100644 index 60643da6bfb..00000000000 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/CriticalTimeMetrics.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace NServiceBus; - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; - -class CriticalTimeMetrics(string queueName, string discriminator) -{ - public Task Record(ReceivePipelineCompleted pipeline, CancellationToken cancellationToken = default) - { - _ = pipeline.TryGetMessageType(out var messageType); - var tagList = new TagList(new KeyValuePair[] - { - new(MeterTags.QueueName, queueName ?? ""), - new(MeterTags.EndpointDiscriminator, discriminator ?? ""), - new(MeterTags.MessageType, messageType ?? "") - }.AsSpan()); - - if (pipeline.TryGetDeliverAt(out var startTime) || pipeline.TryGetTimeSent(out startTime)) - { - Meters.CriticalTime.Record((pipeline.CompletedAt - startTime).TotalSeconds, tagList); - } - return Task.CompletedTask; - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/MessagingMetricsFeature.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/MessagingMetricsFeature.cs index d741eabe8e1..baa8cbe39ed 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/MessagingMetricsFeature.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/MessagingMetricsFeature.cs @@ -12,11 +12,10 @@ class MessagingMetricsFeature : Feature /// protected internal override void Setup(FeatureConfigurationContext context) { - var queueName = context.Receiving.QueueNameBase; - var discriminator = context.Receiving.InstanceSpecificQueueAddress?.Discriminator; - var queueNameBase = context.Receiving.QueueNameBase; var enableMetricTagsCollectionBehavior = new EnableMetricTagsCollectionBehavior(); - var performanceDiagnosticsBehavior = new ReceiveDiagnosticsBehavior(queueNameBase, discriminator); + var performanceDiagnosticsBehavior = new ReceiveDiagnosticsBehavior( + context.Receiving.QueueNameBase, + context.Receiving.InstanceSpecificQueueAddress?.Discriminator); context.Pipeline.Register( enableMetricTagsCollectionBehavior, @@ -26,7 +25,5 @@ protected internal override void Setup(FeatureConfigurationContext context) performanceDiagnosticsBehavior, "Provides OpenTelemetry counters for message processing" ); - var criticalTimeMetrics = new CriticalTimeMetrics(queueName, discriminator); - context.Pipeline.OnReceivePipelineCompleted((pipeline, token) => criticalTimeMetrics.Record(pipeline, token)); } } \ No newline at end of file diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/MetricsExtensions.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/MetricsExtensions.cs index 63c20a22902..f43696f46d4 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/MetricsExtensions.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/MetricsExtensions.cs @@ -2,13 +2,11 @@ namespace NServiceBus; using System; using System.Collections.Generic; -using System.Linq; static class MetricsExtensions { - public static bool TryGetTimeSent(this ReceivePipelineCompleted completed, out DateTimeOffset timeSent) + public static bool TryGetTimeSent(this Dictionary headers, out DateTimeOffset timeSent) { - var headers = completed.ProcessedMessage.Headers; if (headers.TryGetValue(Headers.TimeSent, out var timeSentString)) { timeSent = DateTimeOffsetHelper.ToDateTimeOffset(timeSentString); @@ -18,9 +16,8 @@ public static bool TryGetTimeSent(this ReceivePipelineCompleted completed, out D return false; } - public static bool TryGetDeliverAt(this ReceivePipelineCompleted completed, out DateTimeOffset deliverAt) + public static bool TryGetDeliverAt(this Dictionary headers, out DateTimeOffset deliverAt) { - var headers = completed.ProcessedMessage.Headers; if (headers.TryGetValue(Headers.DeliverAt, out var deliverAtString)) { deliverAt = DateTimeOffsetHelper.ToDateTimeOffset(deliverAtString); @@ -29,21 +26,4 @@ public static bool TryGetDeliverAt(this ReceivePipelineCompleted completed, out deliverAt = DateTimeOffset.MinValue; return false; } - - public static bool TryGetMessageType(this ReceivePipelineCompleted completed, out string processedMessageType) - => completed.ProcessedMessage.Headers.TryGetMessageType(out processedMessageType); - - static bool TryGetMessageType(this Dictionary headers, out string processedMessageType) - { - if (headers.TryGetValue(Headers.EnclosedMessageTypes, out var enclosedMessageType)) - { - string messageTypeHeader = !string.IsNullOrEmpty(enclosedMessageType) ? enclosedMessageType.Split(';').FirstOrDefault() : default; - string messageTypeName = !string.IsNullOrEmpty(messageTypeHeader) ? messageTypeHeader.Split(',').FirstOrDefault() : default; - processedMessageType = messageTypeName; - return true; - } - processedMessageType = null; - return false; - } - } \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs b/src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs index 431d72ef07d..066155715b7 100644 --- a/src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs +++ b/src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs @@ -1,6 +1,7 @@ namespace NServiceBus; using System; +using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; @@ -64,7 +65,23 @@ public async Task Invoke(MessageContext messageContext, CancellationToken cancel throw; } - await receivePipelineNotification.Raise(new ReceivePipelineCompleted(message, pipelineStartedAt, DateTimeOffset.UtcNow), cancellationToken).ConfigureAwait(false); + var completedAt = DateTimeOffset.UtcNow; + var incomingPipelineMetricTags = transportReceiveContext.Extensions.Get(); + if (incomingPipelineMetricTags.IsMetricTagsCollectionEnabled) + { + TagList tags; + incomingPipelineMetricTags.ApplyTags(ref tags, [ + MeterTags.QueueName, + MeterTags.EndpointDiscriminator, + MeterTags.MessageType]); + + if (message.Headers.TryGetDeliverAt(out var startTime) || message.Headers.TryGetTimeSent(out startTime)) + { + Meters.CriticalTime.Record((completedAt - startTime).TotalSeconds, tags); + } + } + + await receivePipelineNotification.Raise(new ReceivePipelineCompleted(message, pipelineStartedAt, completedAt), cancellationToken).ConfigureAwait(false); } } From 5e027060d07e57115d169c115cb0507602e69163 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Tue, 25 Jun 2024 19:04:00 +0200 Subject: [PATCH 56/76] Add a test ensuring critical time is not recorded on failures --- .../Core/OpenTelemetry/When_incoming_message_handled.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled.cs b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled.cs index ff6223f3ea1..a6ed79faec3 100644 --- a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled.cs +++ b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/When_incoming_message_handled.cs @@ -47,6 +47,13 @@ public async Task Should_record_failure_handling_time() Assert.AreEqual("failure", result); } + [Test] + public async Task Should_not_record_critical_time_on_failure() + { + using TestingMetricListener metricsListener = await WhenMessagesHandled(() => new MyExceptionalMessage()); + metricsListener.AssertMetric(CriticalTimeMetricName, 0); + } + static async Task WhenMessagesHandled(Func messageFactory) { TestingMetricListener metricsListener = null; From d0d984ebcfd4fdd15282238713ac53fc254ad57f Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Tue, 25 Jun 2024 21:40:54 +0200 Subject: [PATCH 57/76] Use the IMeterFactory approach --- .../OpenTelemetry/MeterTests.cs | 4 +-- .../ReceiveDiagnosticsBehaviorTests.cs | 26 +++++++------- .../Metrics/MessagingMetricsFeature.cs | 20 ++++++----- .../Metrics/MessagingMetricsMeters.cs | 35 +++++++++++++++++++ .../OpenTelemetry/Metrics/Meters.cs | 34 ------------------ .../Metrics/ReceiveDiagnosticsBehavior.cs | 19 ++++------ .../Metrics/RecordMessageHandlingMetric.cs | 8 +++-- .../Incoming/InvokeHandlerTerminator.cs | 1 - .../Pipeline/MainPipelineExecutor.cs | 29 ++++++--------- 9 files changed, 84 insertions(+), 92 deletions(-) create mode 100644 src/NServiceBus.Core/OpenTelemetry/Metrics/MessagingMetricsMeters.cs delete mode 100644 src/NServiceBus.Core/OpenTelemetry/Metrics/Meters.cs diff --git a/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs b/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs index 91814bd6fd2..96e850582d2 100644 --- a/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs +++ b/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs @@ -18,7 +18,7 @@ public void Verify_MeterAPI() .Select(x => x.GetRawConstantValue()) .OrderBy(value => value) .ToList(); - var metrics = typeof(Meters) + var metrics = typeof(MessagingMetricsMeters) .GetFields(BindingFlags.Static | BindingFlags.NonPublic) .Where(fi => typeof(Instrument).IsAssignableFrom(fi.FieldType)) .Select(fi => (Instrument)fi.GetValue(null)) @@ -28,7 +28,7 @@ public void Verify_MeterAPI() Approver.Verify(new { Note = "Changes to metrics API should result in an update to NServiceBusMeter version.", - ActivitySourceVersion = Meters.NServiceBusMeter.Version, + ActivitySourceVersion = MessagingMetricsMeters.NServiceBusMeter.Version, Tags = meterTags, Metrics = metrics }); diff --git a/src/NServiceBus.Core.Tests/OpenTelemetry/ReceiveDiagnosticsBehaviorTests.cs b/src/NServiceBus.Core.Tests/OpenTelemetry/ReceiveDiagnosticsBehaviorTests.cs index 64456b84b44..bf0999f6cc2 100644 --- a/src/NServiceBus.Core.Tests/OpenTelemetry/ReceiveDiagnosticsBehaviorTests.cs +++ b/src/NServiceBus.Core.Tests/OpenTelemetry/ReceiveDiagnosticsBehaviorTests.cs @@ -19,9 +19,9 @@ public async Task Should_increase_total_fetched_when_processing_message() var testableIncomingPhysicalMessageContext = new TestableIncomingPhysicalMessageContext(); await behavior.Invoke(testableIncomingPhysicalMessageContext, _ => Task.CompletedTask); - metricsListener.AssertMetric(Meters.TotalFetched.Name, 1); + metricsListener.AssertMetric(MessagingMetricsMeters.TotalFetched.Name, 1); - var fetchedTags = metricsListener.Tags[Meters.TotalFetched.Name].ToImmutableDictionary(); + var fetchedTags = metricsListener.Tags[MessagingMetricsMeters.TotalFetched.Name].ToImmutableDictionary(); Assert.AreEqual("discriminator", fetchedTags[MeterTags.EndpointDiscriminator]); Assert.AreEqual("queueBaseName", fetchedTags[MeterTags.QueueName]); } @@ -37,11 +37,11 @@ public async Task Should_increase_total_successful_when_processing_message_succe tags.Add(MeterTags.MessageType, "SomeType"); await behavior.Invoke(testableIncomingPhysicalMessageContext, _ => Task.CompletedTask); - metricsListener.AssertMetric(Meters.TotalFetched.Name, 1); - metricsListener.AssertMetric(Meters.TotalProcessedSuccessfully.Name, 1); - metricsListener.AssertMetric(Meters.TotalFailures.Name, 0); + metricsListener.AssertMetric(MessagingMetricsMeters.TotalFetched.Name, 1); + metricsListener.AssertMetric(MessagingMetricsMeters.TotalProcessedSuccessfully.Name, 1); + metricsListener.AssertMetric(MessagingMetricsMeters.TotalFailures.Name, 0); - var processedTags = metricsListener.Tags[Meters.TotalProcessedSuccessfully.Name].ToImmutableDictionary(); + var processedTags = metricsListener.Tags[MessagingMetricsMeters.TotalProcessedSuccessfully.Name].ToImmutableDictionary(); Assert.AreEqual("discriminator", processedTags[MeterTags.EndpointDiscriminator]); Assert.AreEqual("queueBaseName", processedTags[MeterTags.QueueName]); Assert.AreEqual("SomeType", processedTags[MeterTags.MessageType]); @@ -56,11 +56,11 @@ public void Should_increase_failures_error_when_processing_message_fails() using var metricsListener = TestingMetricListener.SetupNServiceBusMetricsListener(); Assert.ThrowsAsync(() => behavior.Invoke(context, _ => throw new Exception("test"))); - metricsListener.AssertMetric(Meters.TotalFetched.Name, 1); - metricsListener.AssertMetric(Meters.TotalProcessedSuccessfully.Name, 0); - metricsListener.AssertMetric(Meters.TotalFailures.Name, 1); + metricsListener.AssertMetric(MessagingMetricsMeters.TotalFetched.Name, 1); + metricsListener.AssertMetric(MessagingMetricsMeters.TotalProcessedSuccessfully.Name, 0); + metricsListener.AssertMetric(MessagingMetricsMeters.TotalFailures.Name, 1); - var failureTags = metricsListener.Tags[Meters.TotalFailures.Name].ToImmutableDictionary(); + var failureTags = metricsListener.Tags[MessagingMetricsMeters.TotalFailures.Name].ToImmutableDictionary(); Assert.AreEqual(typeof(Exception), failureTags[MeterTags.FailureType]); Assert.AreEqual("discriminator", failureTags[MeterTags.EndpointDiscriminator]); Assert.AreEqual("queueBaseName", failureTags[MeterTags.QueueName]); @@ -83,8 +83,8 @@ public void Should_not_increase_total_failures_when_cancellation_exception() return Task.CompletedTask; })); - metricsListener.AssertMetric(Meters.TotalFetched.Name, 1); - metricsListener.AssertMetric(Meters.TotalProcessedSuccessfully.Name, 0); - metricsListener.AssertMetric(Meters.TotalFailures.Name, 0); + metricsListener.AssertMetric(MessagingMetricsMeters.TotalFetched.Name, 1); + metricsListener.AssertMetric(MessagingMetricsMeters.TotalProcessedSuccessfully.Name, 0); + metricsListener.AssertMetric(MessagingMetricsMeters.TotalFailures.Name, 0); } } \ No newline at end of file diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/MessagingMetricsFeature.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/MessagingMetricsFeature.cs index baa8cbe39ed..58fc6a8c2e5 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/MessagingMetricsFeature.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/MessagingMetricsFeature.cs @@ -1,6 +1,7 @@ namespace NServiceBus; using Features; +using Microsoft.Extensions.DependencyInjection; /// /// MessagingMetricsFeature captures messaging metrics @@ -12,17 +13,20 @@ class MessagingMetricsFeature : Feature /// protected internal override void Setup(FeatureConfigurationContext context) { - var enableMetricTagsCollectionBehavior = new EnableMetricTagsCollectionBehavior(); - var performanceDiagnosticsBehavior = new ReceiveDiagnosticsBehavior( - context.Receiving.QueueNameBase, - context.Receiving.InstanceSpecificQueueAddress?.Discriminator); + _ = context.Container.AddSingleton(); - context.Pipeline.Register( - enableMetricTagsCollectionBehavior, + context.Pipeline.Register( + new EnableMetricTagsCollectionBehavior(), "Enables OpenTelemetry Metric Tags collection throughout the pipeline" ); - context.Pipeline.Register( - performanceDiagnosticsBehavior, + context.Pipeline.Register(sp => + { + var messagingMetricsMetersMeter = sp.GetRequiredService(); + return new ReceiveDiagnosticsBehavior( + messagingMetricsMetersMeter, + context.Receiving.QueueNameBase, + context.Receiving.InstanceSpecificQueueAddress?.Discriminator); + }, "Provides OpenTelemetry counters for message processing" ); } diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/MessagingMetricsMeters.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/MessagingMetricsMeters.cs new file mode 100644 index 00000000000..8777a49da97 --- /dev/null +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/MessagingMetricsMeters.cs @@ -0,0 +1,35 @@ +namespace NServiceBus; + +using System; +using System.Diagnostics; +using System.Diagnostics.Metrics; + +class MessagingMetricsMeters +{ + public MessagingMetricsMeters(IMeterFactory meterFactory) + { + var meter = meterFactory.Create("NServiceBus.Core", "0.2.0"); + totalProcessedSuccessfully = meter.CreateCounter(Metrics.TotalProcessedSuccessfully, + description: "Total number of messages processed successfully by the endpoint."); + totalFetched = meter.CreateCounter(Metrics.TotalFetched, + description: "Total number of messages fetched from the queue by the endpoint."); + totalFailures = meter.CreateCounter(Metrics.TotalFailures, + description: "Total number of messages processed unsuccessfully by the endpoint."); + messageHandlerTime = meter.CreateHistogram(Metrics.MessageHandlerTime, "s", + "The time in seconds for the execution of the business code."); + criticalTime = meter.CreateHistogram(Metrics.CriticalTime, "s", + "The time in seconds between when the message was sent until processed by the endpoint."); + } + + public void RecordMessageSuccessfullyProcessed(TagList tags) => totalProcessedSuccessfully.Add(1, tags); + public void RecordMessageProcessingFailure(TagList tags) => totalFailures.Add(1, tags); + public void RecordFetchedMessage(TagList tags) => totalFetched.Add(1, tags); + public void RecordMessageCriticalTime(TimeSpan messageCriticalTime, TagList tags) => criticalTime.Record(messageCriticalTime.TotalSeconds, tags); + public void RecordMessageHandlerTime(TimeSpan messageHandlerTimeSpan, TagList tags) => messageHandlerTime.Record(messageHandlerTimeSpan.TotalSeconds, tags); + + readonly Counter totalProcessedSuccessfully; + readonly Counter totalFetched; + readonly Counter totalFailures; + readonly Histogram messageHandlerTime; + readonly Histogram criticalTime; +} \ No newline at end of file diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/Meters.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/Meters.cs deleted file mode 100644 index a4009e03bc8..00000000000 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/Meters.cs +++ /dev/null @@ -1,34 +0,0 @@ -namespace NServiceBus; - -using System.Diagnostics.Metrics; - -class Meters -{ - internal static readonly Meter NServiceBusMeter = new Meter( - "NServiceBus.Core", - "0.2.0"); - - internal static readonly Counter TotalProcessedSuccessfully = - NServiceBusMeter.CreateCounter(Metrics.TotalProcessedSuccessfully, description: "Total number of messages processed successfully by the endpoint."); - - internal static readonly Counter TotalFetched = - NServiceBusMeter.CreateCounter(Metrics.TotalFetched, description: "Total number of messages fetched from the queue by the endpoint."); - - internal static readonly Counter TotalFailures = - NServiceBusMeter.CreateCounter(Metrics.TotalFailures, description: "Total number of messages processed unsuccessfully by the endpoint."); - - internal static readonly Histogram MessageHandlerTime = - NServiceBusMeter.CreateHistogram(Metrics.MessageHandlerTime, "s", "The time in seconds for the execution of the business code."); - - internal static readonly Histogram CriticalTime = - NServiceBusMeter.CreateHistogram(Metrics.CriticalTime, "s", "The time in seconds between when the message was sent until processed by the endpoint."); - - internal static readonly Counter TotalImmediateRetries = - NServiceBusMeter.CreateCounter("nservicebus.recoverability.immediate", description: "Total number of immediate retries requested."); - - internal static readonly Counter TotalDelayedRetries = - NServiceBusMeter.CreateCounter("nservicebus.recoverability.delayed", description: "Total number of delayed retries requested."); - - internal static readonly Counter TotalSentToErrorQueue = - NServiceBusMeter.CreateCounter("nservicebus.recoverability.error", description: "Total number of messages sent to the error queue."); -} \ No newline at end of file diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/ReceiveDiagnosticsBehavior.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/ReceiveDiagnosticsBehavior.cs index 93b71e16cbf..ab41a56b4f6 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/ReceiveDiagnosticsBehavior.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/ReceiveDiagnosticsBehavior.cs @@ -7,14 +7,9 @@ namespace NServiceBus; // This behavior is IIncomingPhysicalMessageContext and not ITransportReceiveContext // to avoid capture successes for messages deduplicated by the Outbox -class ReceiveDiagnosticsBehavior : IBehavior +class ReceiveDiagnosticsBehavior(MessagingMetricsMeters metricsMeters, string queueNameBase, string discriminator) + : IBehavior { - public ReceiveDiagnosticsBehavior(string queueNameBase, string discriminator) - { - this.queueNameBase = queueNameBase; - this.discriminator = discriminator; - } - public async Task Invoke(IIncomingPhysicalMessageContext context, Func next) { var availableMetricTags = context.Extensions.Get(); @@ -23,7 +18,7 @@ public async Task Invoke(IIncomingPhysicalMessageContext context, Func(); if (incomingPipelineMetricTags.IsMetricTagsCollectionEnabled) { @@ -33,7 +34,7 @@ public void OnSuccess() // This is what Add(string, object) does so skipping an unnecessary stack frame tags.Add(new KeyValuePair(MeterTags.MessageHandlerType, context.MessageHandler.Instance.GetType().FullName)); tags.Add(new KeyValuePair(MeterTags.ExecutionResult, "success")); - Meters.MessageHandlerTime.Record(stopWatch.Elapsed.TotalSeconds, tags); + messagingMetricsMeters.RecordMessageHandlerTime(stopWatch.Elapsed, tags); } public void OnFailure(Exception error) @@ -52,11 +53,12 @@ public void OnFailure(Exception error) tags.Add(new KeyValuePair(MeterTags.MessageHandlerType, context.MessageHandler.Instance.GetType().FullName)); tags.Add(new KeyValuePair(MeterTags.ExecutionResult, "failure")); tags.Add(new KeyValuePair(MeterTags.ErrorType, error.GetType().FullName)); - Meters.MessageHandlerTime.Record(stopWatch.Elapsed.TotalSeconds, tags); + messagingMetricsMeters.RecordMessageHandlerTime(stopWatch.Elapsed, tags); } readonly Stopwatch stopWatch = new(); TagList tags; readonly IncomingPipelineMetricTags incomingPipelineMetricTags; readonly IInvokeHandlerContext context; + readonly MessagingMetricsMeters messagingMetricsMeters; } \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs b/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs index b7514617f33..9d3456cb8fa 100644 --- a/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs +++ b/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs @@ -8,7 +8,6 @@ class InvokeHandlerTerminator(IActivityFactory activityFactory) : PipelineTerminator { - protected override async Task Terminate(IInvokeHandlerContext context) { if (context.Extensions.TryGet(out ActiveSagaInstance saga) && saga.NotFound && saga.Metadata.SagaType == context.MessageHandler.Instance.GetType()) diff --git a/src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs b/src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs index 066155715b7..17fc80e90f5 100644 --- a/src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs +++ b/src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs @@ -8,18 +8,16 @@ namespace NServiceBus; using Pipeline; using Transport; -class MainPipelineExecutor : IPipelineExecutor +class MainPipelineExecutor( + IServiceProvider rootBuilder, + IPipelineCache pipelineCache, + MessageOperations messageOperations, + INotificationSubscriptions receivePipelineNotification, + IPipeline receivePipeline, + IActivityFactory activityFactory, + MessagingMetricsMeters messagingMetricsMeters) + : IPipelineExecutor { - public MainPipelineExecutor(IServiceProvider rootBuilder, IPipelineCache pipelineCache, MessageOperations messageOperations, INotificationSubscriptions receivePipelineNotification, IPipeline receivePipeline, IActivityFactory activityFactory) - { - this.rootBuilder = rootBuilder; - this.pipelineCache = pipelineCache; - this.messageOperations = messageOperations; - this.receivePipelineNotification = receivePipelineNotification; - this.receivePipeline = receivePipeline; - this.activityFactory = activityFactory; - } - public async Task Invoke(MessageContext messageContext, CancellationToken cancellationToken = default) { var pipelineStartedAt = DateTimeOffset.UtcNow; @@ -77,18 +75,11 @@ public async Task Invoke(MessageContext messageContext, CancellationToken cancel if (message.Headers.TryGetDeliverAt(out var startTime) || message.Headers.TryGetTimeSent(out startTime)) { - Meters.CriticalTime.Record((completedAt - startTime).TotalSeconds, tags); + messagingMetricsMeters.RecordMessageCriticalTime(completedAt - startTime, tags); } } await receivePipelineNotification.Raise(new ReceivePipelineCompleted(message, pipelineStartedAt, completedAt), cancellationToken).ConfigureAwait(false); } } - - readonly IServiceProvider rootBuilder; - readonly IPipelineCache pipelineCache; - readonly MessageOperations messageOperations; - readonly INotificationSubscriptions receivePipelineNotification; - readonly IPipeline receivePipeline; - readonly IActivityFactory activityFactory; } \ No newline at end of file From 3c33b5363014ef7b7e8d350317b36410c7796398 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Wed, 26 Jun 2024 09:50:34 +0200 Subject: [PATCH 58/76] Use a provider style approach and get rid of the RecordMessageHandlingMetric class --- .../Metrics/MessagingMetricsMeters.cs | 44 ++++++++++++- .../Metrics/RecordMessageHandlingMetric.cs | 64 ------------------- .../Incoming/InvokeHandlerTerminator.cs | 10 ++- .../Receiving/ReceiveComponent.cs | 5 +- 4 files changed, 54 insertions(+), 69 deletions(-) delete mode 100644 src/NServiceBus.Core/OpenTelemetry/Metrics/RecordMessageHandlingMetric.cs diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/MessagingMetricsMeters.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/MessagingMetricsMeters.cs index 8777a49da97..c812eef3906 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/MessagingMetricsMeters.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/MessagingMetricsMeters.cs @@ -1,13 +1,17 @@ namespace NServiceBus; using System; +using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.Metrics; +using Pipeline; class MessagingMetricsMeters { public MessagingMetricsMeters(IMeterFactory meterFactory) { + // TODO the problem with this approach is that it's harder to do approval testing on the exposed meters + // TODO we also probably need to keep the meter around to assert that the version did not change var meter = meterFactory.Create("NServiceBus.Core", "0.2.0"); totalProcessedSuccessfully = meter.CreateCounter(Metrics.TotalProcessedSuccessfully, description: "Total number of messages processed successfully by the endpoint."); @@ -25,7 +29,45 @@ public MessagingMetricsMeters(IMeterFactory meterFactory) public void RecordMessageProcessingFailure(TagList tags) => totalFailures.Add(1, tags); public void RecordFetchedMessage(TagList tags) => totalFetched.Add(1, tags); public void RecordMessageCriticalTime(TimeSpan messageCriticalTime, TagList tags) => criticalTime.Record(messageCriticalTime.TotalSeconds, tags); - public void RecordMessageHandlerTime(TimeSpan messageHandlerTimeSpan, TagList tags) => messageHandlerTime.Record(messageHandlerTimeSpan.TotalSeconds, tags); + + public void RecordSuccessfulMessageHandlerTime(IInvokeHandlerContext invokeHandlerContext, TimeSpan elapsed) + { + // TODO if there was a provider/factory style approach to getting the MessagingMetricsMeters + // we could inject a NoOpMessagingMetricsMeters and get rid of all these checks + if (!invokeHandlerContext.Extensions.TryGet(out var incomingPipelineMetricTags) || incomingPipelineMetricTags is not { IsMetricTagsCollectionEnabled: true }) + { + return; + } + + TagList meterTags; + incomingPipelineMetricTags.ApplyTags(ref meterTags, [MeterTags.QueueName, + MeterTags.EndpointDiscriminator, + MeterTags.MessageType, + MeterTags.MessageHandlerType]); + // This is what Add(string, object) does so skipping an unnecessary stack frame + meterTags.Add(new KeyValuePair(MeterTags.MessageHandlerType, invokeHandlerContext.MessageHandler.HandlerType.FullName)); + meterTags.Add(new KeyValuePair(MeterTags.ExecutionResult, "success")); + messageHandlerTime.Record(elapsed.TotalSeconds, meterTags); + } + + public void RecordFailedMessageHandlerTime(IInvokeHandlerContext invokeHandlerContext, TimeSpan elapsed, Exception error) + { + if (!invokeHandlerContext.Extensions.TryGet(out var incomingPipelineMetricTags) || incomingPipelineMetricTags is not { IsMetricTagsCollectionEnabled: true }) + { + return; + } + + TagList meterTags; + incomingPipelineMetricTags.ApplyTags(ref meterTags, [MeterTags.QueueName, + MeterTags.EndpointDiscriminator, + MeterTags.MessageType, + MeterTags.MessageHandlerType]); + // This is what Add(string, object) does so skipping an unnecessary stack frame + meterTags.Add(new KeyValuePair(MeterTags.MessageHandlerType, invokeHandlerContext.MessageHandler.HandlerType.FullName)); + meterTags.Add(new KeyValuePair(MeterTags.ExecutionResult, "failure")); + meterTags.Add(new KeyValuePair(MeterTags.ErrorType, error.GetType().FullName)); + messageHandlerTime.Record(elapsed.TotalSeconds, meterTags); + } readonly Counter totalProcessedSuccessfully; readonly Counter totalFetched; diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/RecordMessageHandlingMetric.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/RecordMessageHandlingMetric.cs deleted file mode 100644 index fa87a17c3b1..00000000000 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/RecordMessageHandlingMetric.cs +++ /dev/null @@ -1,64 +0,0 @@ -namespace NServiceBus; - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using Pipeline; - -class RecordMessageHandlingMetric -{ - public RecordMessageHandlingMetric(IInvokeHandlerContext context, MessagingMetricsMeters messagingMetricsMeters) - { - this.context = context; - this.messagingMetricsMeters = messagingMetricsMeters; - incomingPipelineMetricTags = context.Extensions.Get(); - if (incomingPipelineMetricTags.IsMetricTagsCollectionEnabled) - { - stopWatch.Start(); - } - } - - public void OnSuccess() - { - if (!incomingPipelineMetricTags.IsMetricTagsCollectionEnabled) - { - return; - } - - stopWatch.Stop(); - - incomingPipelineMetricTags.ApplyTags(ref tags, [MeterTags.QueueName, - MeterTags.EndpointDiscriminator, - MeterTags.MessageType, - MeterTags.MessageHandlerType]); - // This is what Add(string, object) does so skipping an unnecessary stack frame - tags.Add(new KeyValuePair(MeterTags.MessageHandlerType, context.MessageHandler.Instance.GetType().FullName)); - tags.Add(new KeyValuePair(MeterTags.ExecutionResult, "success")); - messagingMetricsMeters.RecordMessageHandlerTime(stopWatch.Elapsed, tags); - } - - public void OnFailure(Exception error) - { - if (!incomingPipelineMetricTags.IsMetricTagsCollectionEnabled) - { - return; - } - - stopWatch.Stop(); - - incomingPipelineMetricTags.ApplyTags(ref tags, [MeterTags.QueueName, - MeterTags.EndpointDiscriminator, - MeterTags.MessageType, - MeterTags.MessageHandlerType]); - tags.Add(new KeyValuePair(MeterTags.MessageHandlerType, context.MessageHandler.Instance.GetType().FullName)); - tags.Add(new KeyValuePair(MeterTags.ExecutionResult, "failure")); - tags.Add(new KeyValuePair(MeterTags.ErrorType, error.GetType().FullName)); - messagingMetricsMeters.RecordMessageHandlerTime(stopWatch.Elapsed, tags); - } - - readonly Stopwatch stopWatch = new(); - TagList tags; - readonly IncomingPipelineMetricTags incomingPipelineMetricTags; - readonly IInvokeHandlerContext context; - readonly MessagingMetricsMeters messagingMetricsMeters; -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs b/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs index 9d3456cb8fa..3c6cb24d8b6 100644 --- a/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs +++ b/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using Pipeline; using Sagas; @@ -19,9 +20,12 @@ protected override async Task Terminate(IInvokeHandlerContext context) var messageHandler = context.MessageHandler; + // TODO: this is effectively using a provider pattern. It would be better to make that explicit allowing the + // provider to return a NoOpMessagingMetricsMeters kind of meter + var messagingMetricsMeters = context.Builder.GetService(); + // Might as well abort before invoking the handler if we're shutting down context.CancellationToken.ThrowIfCancellationRequested(); - var handlingMetric = new RecordMessageHandlingMetric(context); var startTime = DateTimeOffset.UtcNow; try { @@ -31,7 +35,7 @@ await messageHandler .ConfigureAwait(false); activity?.SetStatus(ActivityStatusCode.Ok); - handlingMetric.OnSuccess(); + messagingMetricsMeters.RecordSuccessfulMessageHandlerTime(context, DateTimeOffset.UtcNow - startTime); } #pragma warning disable PS0019 // Do not catch Exception without considering OperationCanceledException - enriching and rethrowing catch (Exception ex) @@ -44,7 +48,7 @@ await messageHandler ex.Data["Handler canceled"] = context.CancellationToken.IsCancellationRequested; activity?.SetErrorStatus(ex); - handlingMetric.OnFailure(ex); + messagingMetricsMeters.RecordFailedMessageHandlerTime(context, DateTimeOffset.UtcNow - startTime, ex); throw; } } diff --git a/src/NServiceBus.Core/Receiving/ReceiveComponent.cs b/src/NServiceBus.Core/Receiving/ReceiveComponent.cs index bcc6050f667..32097264447 100644 --- a/src/NServiceBus.Core/Receiving/ReceiveComponent.cs +++ b/src/NServiceBus.Core/Receiving/ReceiveComponent.cs @@ -154,7 +154,10 @@ public async Task Initialize( var receivePipeline = pipelineComponent.CreatePipeline(builder); - var mainPipelineExecutor = new MainPipelineExecutor(builder, pipelineCache, messageOperations, configuration.PipelineCompletedSubscribers, receivePipeline, activityFactory); + // TODO: this is effectively using a provider pattern. It would be better to make that explicit allowing the + // provider to return a NoOpMessagingMetricsMeters kind of meter + var messagingMeters = builder.GetService(); + var mainPipelineExecutor = new MainPipelineExecutor(builder, pipelineCache, messageOperations, configuration.PipelineCompletedSubscribers, receivePipeline, activityFactory, messagingMeters); var recoverabilityPipelineExecutor = recoverabilityComponent.CreateRecoverabilityPipelineExecutor( builder, From 2a0edfad40258dd2e6d885a9a7fd0a9269d3bf57 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Wed, 26 Jun 2024 09:52:53 +0200 Subject: [PATCH 59/76] Another TODO/comment --- .../OpenTelemetry/Metrics/MessagingMetricsMeters.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/MessagingMetricsMeters.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/MessagingMetricsMeters.cs index c812eef3906..44ea9b05d57 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/MessagingMetricsMeters.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/MessagingMetricsMeters.cs @@ -30,6 +30,7 @@ public MessagingMetricsMeters(IMeterFactory meterFactory) public void RecordFetchedMessage(TagList tags) => totalFetched.Add(1, tags); public void RecordMessageCriticalTime(TimeSpan messageCriticalTime, TagList tags) => criticalTime.Record(messageCriticalTime.TotalSeconds, tags); + // TODO another approach to the above one could be to fully encapsulate the tag choice/set and the recording public void RecordSuccessfulMessageHandlerTime(IInvokeHandlerContext invokeHandlerContext, TimeSpan elapsed) { // TODO if there was a provider/factory style approach to getting the MessagingMetricsMeters From ed402d3352574c16002132f1ed2624131646380a Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Thu, 27 Jun 2024 17:14:52 +0200 Subject: [PATCH 60/76] Make PipelineMetrics a first class citizen in Core and follow a divide by feature pattern for Metrics This allows removing metrics dedicated features --- .../OpenTelemetry/MeterTests.cs | 9 +- .../ReceiveDiagnosticsBehaviorTests.cs | 26 ++-- src/NServiceBus.Core/EndpointCreator.cs | 3 + src/NServiceBus.Core/NServiceBus.Core.csproj | 1 + .../EnableMetricTagsCollectionBehavior.cs | 16 -- .../Metrics/MessagingMetricsFeature.cs | 33 ---- .../Metrics/MessagingMetricsMeters.cs | 78 ---------- .../OpenTelemetry/Metrics/Metrics.cs | 10 -- .../Metrics/ReceiveDiagnosticsBehavior.cs | 41 ----- .../OpenTelemetryConfigurationExtensions.cs | 1 - .../Incoming/IncomingPipelineMetricTags.cs | 7 - .../Incoming/InvokeHandlerTerminator.cs | 2 +- .../Pipeline/MainPipelineExecutor.cs | 32 ++-- .../Pipeline/PipelineComponent.cs | 3 + .../Pipeline/PipelineMetrics.cs | 141 ++++++++++++++++++ .../Receiving/ReceiveComponent.cs | 6 +- 16 files changed, 189 insertions(+), 220 deletions(-) delete mode 100644 src/NServiceBus.Core/OpenTelemetry/Metrics/EnableMetricTagsCollectionBehavior.cs delete mode 100644 src/NServiceBus.Core/OpenTelemetry/Metrics/MessagingMetricsFeature.cs delete mode 100644 src/NServiceBus.Core/OpenTelemetry/Metrics/MessagingMetricsMeters.cs delete mode 100644 src/NServiceBus.Core/OpenTelemetry/Metrics/Metrics.cs delete mode 100644 src/NServiceBus.Core/OpenTelemetry/Metrics/ReceiveDiagnosticsBehavior.cs create mode 100644 src/NServiceBus.Core/Pipeline/PipelineMetrics.cs diff --git a/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs b/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs index 96e850582d2..3226b3f9ce7 100644 --- a/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs +++ b/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs @@ -12,13 +12,18 @@ public class MeterTests [Test] public void Verify_MeterAPI() { + //TODO: Create a test IMeterFactory implementation + // - Instantiate the *Metrics call to test + // - Use the test IMeterFactory implementation to record calls + // - Dump recorded call into a file and approve it + var meterTags = typeof(MeterTags) .GetFields(BindingFlags.Public | BindingFlags.Static) .Where(fi => fi.IsLiteral && !fi.IsInitOnly) .Select(x => x.GetRawConstantValue()) .OrderBy(value => value) .ToList(); - var metrics = typeof(MessagingMetricsMeters) + var metrics = typeof(PipelineMetrics) .GetFields(BindingFlags.Static | BindingFlags.NonPublic) .Where(fi => typeof(Instrument).IsAssignableFrom(fi.FieldType)) .Select(fi => (Instrument)fi.GetValue(null)) @@ -28,7 +33,7 @@ public void Verify_MeterAPI() Approver.Verify(new { Note = "Changes to metrics API should result in an update to NServiceBusMeter version.", - ActivitySourceVersion = MessagingMetricsMeters.NServiceBusMeter.Version, + ActivitySourceVersion = PipelineMetrics.NServiceBusMeter.Version, Tags = meterTags, Metrics = metrics }); diff --git a/src/NServiceBus.Core.Tests/OpenTelemetry/ReceiveDiagnosticsBehaviorTests.cs b/src/NServiceBus.Core.Tests/OpenTelemetry/ReceiveDiagnosticsBehaviorTests.cs index bf0999f6cc2..c0161d580d1 100644 --- a/src/NServiceBus.Core.Tests/OpenTelemetry/ReceiveDiagnosticsBehaviorTests.cs +++ b/src/NServiceBus.Core.Tests/OpenTelemetry/ReceiveDiagnosticsBehaviorTests.cs @@ -19,9 +19,9 @@ public async Task Should_increase_total_fetched_when_processing_message() var testableIncomingPhysicalMessageContext = new TestableIncomingPhysicalMessageContext(); await behavior.Invoke(testableIncomingPhysicalMessageContext, _ => Task.CompletedTask); - metricsListener.AssertMetric(MessagingMetricsMeters.TotalFetched.Name, 1); + metricsListener.AssertMetric(PipelineMetrics.TotalFetched.Name, 1); - var fetchedTags = metricsListener.Tags[MessagingMetricsMeters.TotalFetched.Name].ToImmutableDictionary(); + var fetchedTags = metricsListener.Tags[PipelineMetrics.TotalFetched.Name].ToImmutableDictionary(); Assert.AreEqual("discriminator", fetchedTags[MeterTags.EndpointDiscriminator]); Assert.AreEqual("queueBaseName", fetchedTags[MeterTags.QueueName]); } @@ -37,11 +37,11 @@ public async Task Should_increase_total_successful_when_processing_message_succe tags.Add(MeterTags.MessageType, "SomeType"); await behavior.Invoke(testableIncomingPhysicalMessageContext, _ => Task.CompletedTask); - metricsListener.AssertMetric(MessagingMetricsMeters.TotalFetched.Name, 1); - metricsListener.AssertMetric(MessagingMetricsMeters.TotalProcessedSuccessfully.Name, 1); - metricsListener.AssertMetric(MessagingMetricsMeters.TotalFailures.Name, 0); + metricsListener.AssertMetric(PipelineMetrics.TotalFetched.Name, 1); + metricsListener.AssertMetric(PipelineMetrics.TotalProcessedSuccessfully.Name, 1); + metricsListener.AssertMetric(PipelineMetrics.TotalFailures.Name, 0); - var processedTags = metricsListener.Tags[MessagingMetricsMeters.TotalProcessedSuccessfully.Name].ToImmutableDictionary(); + var processedTags = metricsListener.Tags[PipelineMetrics.TotalProcessedSuccessfully.Name].ToImmutableDictionary(); Assert.AreEqual("discriminator", processedTags[MeterTags.EndpointDiscriminator]); Assert.AreEqual("queueBaseName", processedTags[MeterTags.QueueName]); Assert.AreEqual("SomeType", processedTags[MeterTags.MessageType]); @@ -56,11 +56,11 @@ public void Should_increase_failures_error_when_processing_message_fails() using var metricsListener = TestingMetricListener.SetupNServiceBusMetricsListener(); Assert.ThrowsAsync(() => behavior.Invoke(context, _ => throw new Exception("test"))); - metricsListener.AssertMetric(MessagingMetricsMeters.TotalFetched.Name, 1); - metricsListener.AssertMetric(MessagingMetricsMeters.TotalProcessedSuccessfully.Name, 0); - metricsListener.AssertMetric(MessagingMetricsMeters.TotalFailures.Name, 1); + metricsListener.AssertMetric(PipelineMetrics.TotalFetched.Name, 1); + metricsListener.AssertMetric(PipelineMetrics.TotalProcessedSuccessfully.Name, 0); + metricsListener.AssertMetric(PipelineMetrics.TotalFailures.Name, 1); - var failureTags = metricsListener.Tags[MessagingMetricsMeters.TotalFailures.Name].ToImmutableDictionary(); + var failureTags = metricsListener.Tags[PipelineMetrics.TotalFailures.Name].ToImmutableDictionary(); Assert.AreEqual(typeof(Exception), failureTags[MeterTags.FailureType]); Assert.AreEqual("discriminator", failureTags[MeterTags.EndpointDiscriminator]); Assert.AreEqual("queueBaseName", failureTags[MeterTags.QueueName]); @@ -83,8 +83,8 @@ public void Should_not_increase_total_failures_when_cancellation_exception() return Task.CompletedTask; })); - metricsListener.AssertMetric(MessagingMetricsMeters.TotalFetched.Name, 1); - metricsListener.AssertMetric(MessagingMetricsMeters.TotalProcessedSuccessfully.Name, 0); - metricsListener.AssertMetric(MessagingMetricsMeters.TotalFailures.Name, 0); + metricsListener.AssertMetric(PipelineMetrics.TotalFetched.Name, 1); + metricsListener.AssertMetric(PipelineMetrics.TotalProcessedSuccessfully.Name, 0); + metricsListener.AssertMetric(PipelineMetrics.TotalFailures.Name, 0); } } \ No newline at end of file diff --git a/src/NServiceBus.Core/EndpointCreator.cs b/src/NServiceBus.Core/EndpointCreator.cs index 072b56a9777..ff5f176b495 100644 --- a/src/NServiceBus.Core/EndpointCreator.cs +++ b/src/NServiceBus.Core/EndpointCreator.cs @@ -117,6 +117,9 @@ void Configure() } ); + // Make Metrics a first class citizen in Core by enabling once and for all them when creating the endpoint + _ = hostingConfiguration.Services.AddMetrics(); + hostingComponent = HostingComponent.Initialize(hostingConfiguration); } diff --git a/src/NServiceBus.Core/NServiceBus.Core.csproj b/src/NServiceBus.Core/NServiceBus.Core.csproj index cab99563ff3..194a5651146 100644 --- a/src/NServiceBus.Core/NServiceBus.Core.csproj +++ b/src/NServiceBus.Core/NServiceBus.Core.csproj @@ -12,6 +12,7 @@ + diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/EnableMetricTagsCollectionBehavior.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/EnableMetricTagsCollectionBehavior.cs deleted file mode 100644 index 941088b2610..00000000000 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/EnableMetricTagsCollectionBehavior.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace NServiceBus; - -using System; -using System.Threading.Tasks; -using Pipeline; - -class EnableMetricTagsCollectionBehavior : IBehavior -{ - public Task Invoke(ITransportReceiveContext context, Func next) - { - var availableMetricTags = context.Extensions.Get(); - availableMetricTags.CollectMetricTags(); - - return next(context); - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/MessagingMetricsFeature.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/MessagingMetricsFeature.cs deleted file mode 100644 index 58fc6a8c2e5..00000000000 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/MessagingMetricsFeature.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace NServiceBus; - -using Features; -using Microsoft.Extensions.DependencyInjection; - -/// -/// MessagingMetricsFeature captures messaging metrics -/// -class MessagingMetricsFeature : Feature -{ - public MessagingMetricsFeature() => Prerequisite(c => !c.Receiving.IsSendOnlyEndpoint, "Processing metrics are not supported on send-only endpoints"); - - /// - protected internal override void Setup(FeatureConfigurationContext context) - { - _ = context.Container.AddSingleton(); - - context.Pipeline.Register( - new EnableMetricTagsCollectionBehavior(), - "Enables OpenTelemetry Metric Tags collection throughout the pipeline" - ); - context.Pipeline.Register(sp => - { - var messagingMetricsMetersMeter = sp.GetRequiredService(); - return new ReceiveDiagnosticsBehavior( - messagingMetricsMetersMeter, - context.Receiving.QueueNameBase, - context.Receiving.InstanceSpecificQueueAddress?.Discriminator); - }, - "Provides OpenTelemetry counters for message processing" - ); - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/MessagingMetricsMeters.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/MessagingMetricsMeters.cs deleted file mode 100644 index 44ea9b05d57..00000000000 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/MessagingMetricsMeters.cs +++ /dev/null @@ -1,78 +0,0 @@ -namespace NServiceBus; - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.Metrics; -using Pipeline; - -class MessagingMetricsMeters -{ - public MessagingMetricsMeters(IMeterFactory meterFactory) - { - // TODO the problem with this approach is that it's harder to do approval testing on the exposed meters - // TODO we also probably need to keep the meter around to assert that the version did not change - var meter = meterFactory.Create("NServiceBus.Core", "0.2.0"); - totalProcessedSuccessfully = meter.CreateCounter(Metrics.TotalProcessedSuccessfully, - description: "Total number of messages processed successfully by the endpoint."); - totalFetched = meter.CreateCounter(Metrics.TotalFetched, - description: "Total number of messages fetched from the queue by the endpoint."); - totalFailures = meter.CreateCounter(Metrics.TotalFailures, - description: "Total number of messages processed unsuccessfully by the endpoint."); - messageHandlerTime = meter.CreateHistogram(Metrics.MessageHandlerTime, "s", - "The time in seconds for the execution of the business code."); - criticalTime = meter.CreateHistogram(Metrics.CriticalTime, "s", - "The time in seconds between when the message was sent until processed by the endpoint."); - } - - public void RecordMessageSuccessfullyProcessed(TagList tags) => totalProcessedSuccessfully.Add(1, tags); - public void RecordMessageProcessingFailure(TagList tags) => totalFailures.Add(1, tags); - public void RecordFetchedMessage(TagList tags) => totalFetched.Add(1, tags); - public void RecordMessageCriticalTime(TimeSpan messageCriticalTime, TagList tags) => criticalTime.Record(messageCriticalTime.TotalSeconds, tags); - - // TODO another approach to the above one could be to fully encapsulate the tag choice/set and the recording - public void RecordSuccessfulMessageHandlerTime(IInvokeHandlerContext invokeHandlerContext, TimeSpan elapsed) - { - // TODO if there was a provider/factory style approach to getting the MessagingMetricsMeters - // we could inject a NoOpMessagingMetricsMeters and get rid of all these checks - if (!invokeHandlerContext.Extensions.TryGet(out var incomingPipelineMetricTags) || incomingPipelineMetricTags is not { IsMetricTagsCollectionEnabled: true }) - { - return; - } - - TagList meterTags; - incomingPipelineMetricTags.ApplyTags(ref meterTags, [MeterTags.QueueName, - MeterTags.EndpointDiscriminator, - MeterTags.MessageType, - MeterTags.MessageHandlerType]); - // This is what Add(string, object) does so skipping an unnecessary stack frame - meterTags.Add(new KeyValuePair(MeterTags.MessageHandlerType, invokeHandlerContext.MessageHandler.HandlerType.FullName)); - meterTags.Add(new KeyValuePair(MeterTags.ExecutionResult, "success")); - messageHandlerTime.Record(elapsed.TotalSeconds, meterTags); - } - - public void RecordFailedMessageHandlerTime(IInvokeHandlerContext invokeHandlerContext, TimeSpan elapsed, Exception error) - { - if (!invokeHandlerContext.Extensions.TryGet(out var incomingPipelineMetricTags) || incomingPipelineMetricTags is not { IsMetricTagsCollectionEnabled: true }) - { - return; - } - - TagList meterTags; - incomingPipelineMetricTags.ApplyTags(ref meterTags, [MeterTags.QueueName, - MeterTags.EndpointDiscriminator, - MeterTags.MessageType, - MeterTags.MessageHandlerType]); - // This is what Add(string, object) does so skipping an unnecessary stack frame - meterTags.Add(new KeyValuePair(MeterTags.MessageHandlerType, invokeHandlerContext.MessageHandler.HandlerType.FullName)); - meterTags.Add(new KeyValuePair(MeterTags.ExecutionResult, "failure")); - meterTags.Add(new KeyValuePair(MeterTags.ErrorType, error.GetType().FullName)); - messageHandlerTime.Record(elapsed.TotalSeconds, meterTags); - } - - readonly Counter totalProcessedSuccessfully; - readonly Counter totalFetched; - readonly Counter totalFailures; - readonly Histogram messageHandlerTime; - readonly Histogram criticalTime; -} \ No newline at end of file diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/Metrics.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/Metrics.cs deleted file mode 100644 index 782b1b3868c..00000000000 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/Metrics.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace NServiceBus; - -static class Metrics -{ - public const string TotalProcessedSuccessfully = "nservicebus.messaging.successes"; - public const string TotalFetched = "nservicebus.messaging.fetches"; - public const string TotalFailures = "nservicebus.messaging.failures"; - public const string MessageHandlerTime = "nservicebus.messaging.handler_time"; - public const string CriticalTime = "nservicebus.messaging.critical_time"; -} \ No newline at end of file diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/ReceiveDiagnosticsBehavior.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/ReceiveDiagnosticsBehavior.cs deleted file mode 100644 index ab41a56b4f6..00000000000 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/ReceiveDiagnosticsBehavior.cs +++ /dev/null @@ -1,41 +0,0 @@ -namespace NServiceBus; - -using System; -using System.Diagnostics; -using System.Threading.Tasks; -using Pipeline; - -// This behavior is IIncomingPhysicalMessageContext and not ITransportReceiveContext -// to avoid capture successes for messages deduplicated by the Outbox -class ReceiveDiagnosticsBehavior(MessagingMetricsMeters metricsMeters, string queueNameBase, string discriminator) - : IBehavior -{ - public async Task Invoke(IIncomingPhysicalMessageContext context, Func next) - { - var availableMetricTags = context.Extensions.Get(); - availableMetricTags.Add(MeterTags.EndpointDiscriminator, discriminator); - availableMetricTags.Add(MeterTags.QueueName, queueNameBase); - - var tags = new TagList(); - availableMetricTags.ApplyTags(ref tags, [MeterTags.EndpointDiscriminator, MeterTags.QueueName]); - metricsMeters.RecordFetchedMessage(tags); - - try - { - await next(context).ConfigureAwait(false); - } - catch (Exception ex) when (!ex.IsCausedBy(context.CancellationToken)) - { - tags.Add(new(MeterTags.FailureType, ex.GetType())); - availableMetricTags.ApplyTags(ref tags, [MeterTags.MessageType, MeterTags.MessageHandlerTypes]); - metricsMeters.RecordMessageProcessingFailure(tags); - throw; - } - - availableMetricTags.ApplyTags(ref tags, [MeterTags.MessageType, MeterTags.MessageHandlerTypes]); - metricsMeters.RecordMessageSuccessfullyProcessed(tags); - } - - readonly string queueNameBase = queueNameBase; - readonly string discriminator = discriminator; -} \ No newline at end of file diff --git a/src/NServiceBus.Core/OpenTelemetry/OpenTelemetryConfigurationExtensions.cs b/src/NServiceBus.Core/OpenTelemetry/OpenTelemetryConfigurationExtensions.cs index a643354a468..455aa2fb6a6 100644 --- a/src/NServiceBus.Core/OpenTelemetry/OpenTelemetryConfigurationExtensions.cs +++ b/src/NServiceBus.Core/OpenTelemetry/OpenTelemetryConfigurationExtensions.cs @@ -16,6 +16,5 @@ public static void EnableOpenTelemetry(this EndpointConfiguration endpointConfig endpointConfiguration.Settings.Get().EnableOpenTelemetry = true; endpointConfiguration.EnableFeature(); - endpointConfiguration.EnableFeature(); } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Incoming/IncomingPipelineMetricTags.cs b/src/NServiceBus.Core/Pipeline/Incoming/IncomingPipelineMetricTags.cs index d5d22441579..702b97362ce 100644 --- a/src/NServiceBus.Core/Pipeline/Incoming/IncomingPipelineMetricTags.cs +++ b/src/NServiceBus.Core/Pipeline/Incoming/IncomingPipelineMetricTags.cs @@ -11,13 +11,6 @@ namespace NServiceBus; /// public sealed class IncomingPipelineMetricTags { - internal bool IsMetricTagsCollectionEnabled { get; private set; } - - internal void CollectMetricTags() - { - IsMetricTagsCollectionEnabled = true; - } - Dictionary>? tags; /// diff --git a/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs b/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs index 3c6cb24d8b6..1c5a691fb76 100644 --- a/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs +++ b/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs @@ -22,7 +22,7 @@ protected override async Task Terminate(IInvokeHandlerContext context) // TODO: this is effectively using a provider pattern. It would be better to make that explicit allowing the // provider to return a NoOpMessagingMetricsMeters kind of meter - var messagingMetricsMeters = context.Builder.GetService(); + var messagingMetricsMeters = context.Builder.GetService(); // Might as well abort before invoking the handler if we're shutting down context.CancellationToken.ThrowIfCancellationRequested(); diff --git a/src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs b/src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs index 17fc80e90f5..980824c16ee 100644 --- a/src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs +++ b/src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs @@ -1,7 +1,6 @@ namespace NServiceBus; using System; -using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; @@ -15,7 +14,9 @@ class MainPipelineExecutor( INotificationSubscriptions receivePipelineNotification, IPipeline receivePipeline, IActivityFactory activityFactory, - MessagingMetricsMeters messagingMetricsMeters) + PipelineMetrics pipelineMetrics, + string queueNameBase, + string endpointDiscriminator) : IPipelineExecutor { public async Task Invoke(MessageContext messageContext, CancellationToken cancellationToken = default) @@ -24,6 +25,12 @@ public async Task Invoke(MessageContext messageContext, CancellationToken cancel using var activity = activityFactory.StartIncomingPipelineActivity(messageContext); + var incomingPipelineMetricsTags = new IncomingPipelineMetricTags(); + incomingPipelineMetricsTags.Add(MeterTags.QueueName, queueNameBase); + incomingPipelineMetricsTags.Add(MeterTags.EndpointDiscriminator, endpointDiscriminator ?? ""); + + pipelineMetrics.RecordFetchedMessage(incomingPipelineMetricsTags); + var childScope = rootBuilder.CreateAsyncScope(); await using (childScope.ConfigureAwait(false)) { @@ -43,6 +50,8 @@ public async Task Invoke(MessageContext messageContext, CancellationToken cancel transportReceiveContext.SetIncomingPipelineActitvity(activity); } + transportReceiveContext.Extensions.Set(incomingPipelineMetricsTags); + try { await receivePipeline.Invoke(transportReceiveContext, activity).ConfigureAwait(false); @@ -60,23 +69,18 @@ public async Task Invoke(MessageContext messageContext, CancellationToken cancel ex.Data["Pipeline canceled"] = transportReceiveContext.CancellationToken.IsCancellationRequested; + pipelineMetrics.RecordMessageProcessingFailure(incomingPipelineMetricsTags, ex); + throw; } var completedAt = DateTimeOffset.UtcNow; - var incomingPipelineMetricTags = transportReceiveContext.Extensions.Get(); - if (incomingPipelineMetricTags.IsMetricTagsCollectionEnabled) + // TODO the following metrics should be recorded only if the Outbox did not dedup the incoming message + // We should not publish a successfully processed or critical time for a duplicate message + pipelineMetrics.RecordMessageSuccessfullyProcessed(incomingPipelineMetricsTags); + if (message.Headers.TryGetDeliverAt(out var startTime) || message.Headers.TryGetTimeSent(out startTime)) { - TagList tags; - incomingPipelineMetricTags.ApplyTags(ref tags, [ - MeterTags.QueueName, - MeterTags.EndpointDiscriminator, - MeterTags.MessageType]); - - if (message.Headers.TryGetDeliverAt(out var startTime) || message.Headers.TryGetTimeSent(out startTime)) - { - messagingMetricsMeters.RecordMessageCriticalTime(completedAt - startTime, tags); - } + pipelineMetrics.RecordMessageCriticalTime(completedAt - startTime, incomingPipelineMetricsTags); } await receivePipelineNotification.Raise(new ReceivePipelineCompleted(message, pipelineStartedAt, completedAt), cancellationToken).ConfigureAwait(false); diff --git a/src/NServiceBus.Core/Pipeline/PipelineComponent.cs b/src/NServiceBus.Core/Pipeline/PipelineComponent.cs index 207c93e86ec..d1776d38402 100644 --- a/src/NServiceBus.Core/Pipeline/PipelineComponent.cs +++ b/src/NServiceBus.Core/Pipeline/PipelineComponent.cs @@ -25,6 +25,9 @@ public static PipelineComponent Initialize(PipelineSettings settings, HostingCom step.ApplyContainerRegistration(hostingConfiguration.Services); } + // make the PipelineMetrics available to the Pipeline + hostingConfiguration.Services.AddSingleton(); + return new PipelineComponent(modifications); } diff --git a/src/NServiceBus.Core/Pipeline/PipelineMetrics.cs b/src/NServiceBus.Core/Pipeline/PipelineMetrics.cs new file mode 100644 index 00000000000..f884c9adc32 --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/PipelineMetrics.cs @@ -0,0 +1,141 @@ +namespace NServiceBus; + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.Metrics; +using Pipeline; + +class PipelineMetrics +{ + const string TotalProcessedSuccessfully = "nservicebus.messaging.successes"; + const string TotalFetched = "nservicebus.messaging.fetches"; + const string TotalFailures = "nservicebus.messaging.failures"; + const string MessageHandlerTime = "nservicebus.messaging.handler_time"; + const string CriticalTime = "nservicebus.messaging.critical_time"; + + public PipelineMetrics(IMeterFactory meterFactory) + { + // TODO the problem with this approach is that it's harder to do approval testing on the exposed meters + // TODO we also probably need to keep the meter around to assert that the version did not change + var meter = meterFactory.Create("NServiceBus.Core.Pipeline", "0.2.0"); + totalProcessedSuccessfully = meter.CreateCounter(TotalProcessedSuccessfully, + description: "Total number of messages processed successfully by the endpoint."); + totalFetched = meter.CreateCounter(TotalFetched, + description: "Total number of messages fetched from the queue by the endpoint."); + totalFailures = meter.CreateCounter(TotalFailures, + description: "Total number of messages processed unsuccessfully by the endpoint."); + messageHandlerTime = meter.CreateHistogram(MessageHandlerTime, "s", + "The time in seconds for the execution of the business code."); + criticalTime = meter.CreateHistogram(CriticalTime, "s", + "The time in seconds between when the message was sent until processed by the endpoint."); + } + + public void RecordMessageSuccessfullyProcessed(IncomingPipelineMetricTags incomingPipelineMetricTags) + { + if (!totalProcessedSuccessfully.Enabled) + { + return; + } + + TagList tags; + incomingPipelineMetricTags.ApplyTags(ref tags, [ + MeterTags.MessageType, + MeterTags.MessageHandlerTypes]); + + totalProcessedSuccessfully.Add(1, tags); + } + + public void RecordMessageProcessingFailure(IncomingPipelineMetricTags incomingPipelineMetricTags, Exception error) + { + if (!totalFailures.Enabled) + { + return; + } + + TagList tags; + tags.Add(new(MeterTags.FailureType, error.GetType())); + incomingPipelineMetricTags.ApplyTags(ref tags, [ + MeterTags.MessageType, + MeterTags.MessageHandlerTypes]); + totalFailures.Add(1, tags); + } + + public void RecordFetchedMessage(IncomingPipelineMetricTags incomingPipelineMetricTags) + { + if (!totalFetched.Enabled) + { + return; + } + + TagList tags; + incomingPipelineMetricTags.ApplyTags(ref tags, [ + MeterTags.EndpointDiscriminator, + MeterTags.QueueName]); + + totalFetched.Add(1, tags); + } + + public void RecordMessageCriticalTime(TimeSpan messageCriticalTime, IncomingPipelineMetricTags incomingPipelineMetricTags) + { + if (!criticalTime.Enabled) + { + return; + } + + TagList tags; + incomingPipelineMetricTags.ApplyTags(ref tags, [ + MeterTags.QueueName, + MeterTags.EndpointDiscriminator, + MeterTags.MessageType]); + + criticalTime.Record(messageCriticalTime.TotalSeconds, tags); + } + + public void RecordSuccessfulMessageHandlerTime(IInvokeHandlerContext invokeHandlerContext, TimeSpan elapsed) + { + if (!messageHandlerTime.Enabled) + { + return; + } + + var incomingPipelineMetricTags = invokeHandlerContext.Extensions.Get(); + TagList meterTags; + incomingPipelineMetricTags.ApplyTags(ref meterTags, [ + MeterTags.QueueName, + MeterTags.EndpointDiscriminator, + MeterTags.MessageType, + MeterTags.MessageHandlerType]); + // This is what Add(string, object) does so skipping an unnecessary stack frame + meterTags.Add(new KeyValuePair(MeterTags.MessageHandlerType, invokeHandlerContext.MessageHandler.HandlerType.FullName)); + meterTags.Add(new KeyValuePair(MeterTags.ExecutionResult, "success")); + messageHandlerTime.Record(elapsed.TotalSeconds, meterTags); + } + + public void RecordFailedMessageHandlerTime(IInvokeHandlerContext invokeHandlerContext, TimeSpan elapsed, Exception error) + { + if (!messageHandlerTime.Enabled) + { + return; + } + + var incomingPipelineMetricTags = invokeHandlerContext.Extensions.Get(); + TagList meterTags; + incomingPipelineMetricTags.ApplyTags(ref meterTags, [ + MeterTags.QueueName, + MeterTags.EndpointDiscriminator, + MeterTags.MessageType, + MeterTags.MessageHandlerType]); + // This is what Add(string, object) does so skipping an unnecessary stack frame + meterTags.Add(new KeyValuePair(MeterTags.MessageHandlerType, invokeHandlerContext.MessageHandler.HandlerType.FullName)); + meterTags.Add(new KeyValuePair(MeterTags.ExecutionResult, "failure")); + meterTags.Add(new KeyValuePair(MeterTags.ErrorType, error.GetType().FullName)); + messageHandlerTime.Record(elapsed.TotalSeconds, meterTags); + } + + readonly Counter totalProcessedSuccessfully; + readonly Counter totalFetched; + readonly Counter totalFailures; + readonly Histogram messageHandlerTime; + readonly Histogram criticalTime; +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Receiving/ReceiveComponent.cs b/src/NServiceBus.Core/Receiving/ReceiveComponent.cs index 32097264447..90390438fc2 100644 --- a/src/NServiceBus.Core/Receiving/ReceiveComponent.cs +++ b/src/NServiceBus.Core/Receiving/ReceiveComponent.cs @@ -154,10 +154,8 @@ public async Task Initialize( var receivePipeline = pipelineComponent.CreatePipeline(builder); - // TODO: this is effectively using a provider pattern. It would be better to make that explicit allowing the - // provider to return a NoOpMessagingMetricsMeters kind of meter - var messagingMeters = builder.GetService(); - var mainPipelineExecutor = new MainPipelineExecutor(builder, pipelineCache, messageOperations, configuration.PipelineCompletedSubscribers, receivePipeline, activityFactory, messagingMeters); + var messagingMeters = builder.GetService(); + var mainPipelineExecutor = new MainPipelineExecutor(builder, pipelineCache, messageOperations, configuration.PipelineCompletedSubscribers, receivePipeline, activityFactory, messagingMeters, configuration.QueueNameBase, configuration.InstanceSpecificQueueAddress?.Discriminator); var recoverabilityPipelineExecutor = recoverabilityComponent.CreateRecoverabilityPipelineExecutor( builder, From 385c60a92fd29e9b1647605175ee6d55acd1a1df Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Thu, 27 Jun 2024 17:20:22 +0200 Subject: [PATCH 61/76] Remove TODOs solved by implementing a test IMeterFactory --- src/NServiceBus.Core/Pipeline/PipelineMetrics.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/NServiceBus.Core/Pipeline/PipelineMetrics.cs b/src/NServiceBus.Core/Pipeline/PipelineMetrics.cs index f884c9adc32..d80b386a37f 100644 --- a/src/NServiceBus.Core/Pipeline/PipelineMetrics.cs +++ b/src/NServiceBus.Core/Pipeline/PipelineMetrics.cs @@ -16,8 +16,6 @@ class PipelineMetrics public PipelineMetrics(IMeterFactory meterFactory) { - // TODO the problem with this approach is that it's harder to do approval testing on the exposed meters - // TODO we also probably need to keep the meter around to assert that the version did not change var meter = meterFactory.Create("NServiceBus.Core.Pipeline", "0.2.0"); totalProcessedSuccessfully = meter.CreateCounter(TotalProcessedSuccessfully, description: "Total number of messages processed successfully by the endpoint."); From 814e8960695341dfb73735995a7da17b25c1c700 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Thu, 27 Jun 2024 17:43:25 +0200 Subject: [PATCH 62/76] Attempt to work around the ugly need to pass in queue name and discriminator --- .../Pipeline/MainPipelineExecutor.cs | 9 ++------- .../Pipeline/PipelineMetrics.cs | 17 +++++++++++++++++ .../Receiving/ReceiveComponent.cs | 7 +++++-- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs b/src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs index 980824c16ee..da67fd67546 100644 --- a/src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs +++ b/src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs @@ -14,9 +14,7 @@ class MainPipelineExecutor( INotificationSubscriptions receivePipelineNotification, IPipeline receivePipeline, IActivityFactory activityFactory, - PipelineMetrics pipelineMetrics, - string queueNameBase, - string endpointDiscriminator) + PipelineMetrics pipelineMetrics) : IPipelineExecutor { public async Task Invoke(MessageContext messageContext, CancellationToken cancellationToken = default) @@ -25,10 +23,7 @@ public async Task Invoke(MessageContext messageContext, CancellationToken cancel using var activity = activityFactory.StartIncomingPipelineActivity(messageContext); - var incomingPipelineMetricsTags = new IncomingPipelineMetricTags(); - incomingPipelineMetricsTags.Add(MeterTags.QueueName, queueNameBase); - incomingPipelineMetricsTags.Add(MeterTags.EndpointDiscriminator, endpointDiscriminator ?? ""); - + var incomingPipelineMetricsTags = pipelineMetrics.CreateDefaultIncomingPipelineMetricTags(); pipelineMetrics.RecordFetchedMessage(incomingPipelineMetricsTags); var childScope = rootBuilder.CreateAsyncScope(); diff --git a/src/NServiceBus.Core/Pipeline/PipelineMetrics.cs b/src/NServiceBus.Core/Pipeline/PipelineMetrics.cs index d80b386a37f..ff89a801958 100644 --- a/src/NServiceBus.Core/Pipeline/PipelineMetrics.cs +++ b/src/NServiceBus.Core/Pipeline/PipelineMetrics.cs @@ -29,6 +29,21 @@ public PipelineMetrics(IMeterFactory meterFactory) "The time in seconds between when the message was sent until processed by the endpoint."); } + public void Initialize(string queueNameBase, string endpointDiscriminator) + { + this.queueNameBase = queueNameBase; + this.endpointDiscriminator = endpointDiscriminator; + } + + public IncomingPipelineMetricTags CreateDefaultIncomingPipelineMetricTags() + { + var incomingPipelineMetricsTags = new IncomingPipelineMetricTags(); + incomingPipelineMetricsTags.Add(MeterTags.QueueName, queueNameBase); + incomingPipelineMetricsTags.Add(MeterTags.EndpointDiscriminator, endpointDiscriminator ?? ""); + + return incomingPipelineMetricsTags; + } + public void RecordMessageSuccessfullyProcessed(IncomingPipelineMetricTags incomingPipelineMetricTags) { if (!totalProcessedSuccessfully.Enabled) @@ -136,4 +151,6 @@ public void RecordFailedMessageHandlerTime(IInvokeHandlerContext invokeHandlerCo readonly Counter totalFailures; readonly Histogram messageHandlerTime; readonly Histogram criticalTime; + string queueNameBase; + string endpointDiscriminator; } \ No newline at end of file diff --git a/src/NServiceBus.Core/Receiving/ReceiveComponent.cs b/src/NServiceBus.Core/Receiving/ReceiveComponent.cs index 90390438fc2..e4b4645b403 100644 --- a/src/NServiceBus.Core/Receiving/ReceiveComponent.cs +++ b/src/NServiceBus.Core/Receiving/ReceiveComponent.cs @@ -154,8 +154,11 @@ public async Task Initialize( var receivePipeline = pipelineComponent.CreatePipeline(builder); - var messagingMeters = builder.GetService(); - var mainPipelineExecutor = new MainPipelineExecutor(builder, pipelineCache, messageOperations, configuration.PipelineCompletedSubscribers, receivePipeline, activityFactory, messagingMeters, configuration.QueueNameBase, configuration.InstanceSpecificQueueAddress?.Discriminator); + var pipelineMetrics = builder.GetService(); + pipelineMetrics.Initialize( + configuration.QueueNameBase, + configuration.InstanceSpecificQueueAddress?.Discriminator); + var mainPipelineExecutor = new MainPipelineExecutor(builder, pipelineCache, messageOperations, configuration.PipelineCompletedSubscribers, receivePipeline, activityFactory, pipelineMetrics); var recoverabilityPipelineExecutor = recoverabilityComponent.CreateRecoverabilityPipelineExecutor( builder, From e4e6339679e4e44083f3b7f01de62f3d76536761 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Thu, 27 Jun 2024 21:11:13 +0200 Subject: [PATCH 63/76] Make PipelineMetrics depend on IReadOnlySettings to access queue and discriminator info --- src/NServiceBus.Core/Pipeline/PipelineMetrics.cs | 11 +++++------ src/NServiceBus.Core/Receiving/ReceiveComponent.cs | 3 --- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/NServiceBus.Core/Pipeline/PipelineMetrics.cs b/src/NServiceBus.Core/Pipeline/PipelineMetrics.cs index ff89a801958..2c83b5a9d35 100644 --- a/src/NServiceBus.Core/Pipeline/PipelineMetrics.cs +++ b/src/NServiceBus.Core/Pipeline/PipelineMetrics.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.Diagnostics.Metrics; using Pipeline; +using Settings; class PipelineMetrics { @@ -14,7 +15,7 @@ class PipelineMetrics const string MessageHandlerTime = "nservicebus.messaging.handler_time"; const string CriticalTime = "nservicebus.messaging.critical_time"; - public PipelineMetrics(IMeterFactory meterFactory) + public PipelineMetrics(IMeterFactory meterFactory, IReadOnlySettings settings) { var meter = meterFactory.Create("NServiceBus.Core.Pipeline", "0.2.0"); totalProcessedSuccessfully = meter.CreateCounter(TotalProcessedSuccessfully, @@ -27,12 +28,10 @@ public PipelineMetrics(IMeterFactory meterFactory) "The time in seconds for the execution of the business code."); criticalTime = meter.CreateHistogram(CriticalTime, "s", "The time in seconds between when the message was sent until processed by the endpoint."); - } - public void Initialize(string queueNameBase, string endpointDiscriminator) - { - this.queueNameBase = queueNameBase; - this.endpointDiscriminator = endpointDiscriminator; + var config = settings.Get(); + queueNameBase = config.QueueNameBase; + endpointDiscriminator = config.InstanceSpecificQueueAddress?.Discriminator ?? ""; } public IncomingPipelineMetricTags CreateDefaultIncomingPipelineMetricTags() diff --git a/src/NServiceBus.Core/Receiving/ReceiveComponent.cs b/src/NServiceBus.Core/Receiving/ReceiveComponent.cs index e4b4645b403..3f8f18228ab 100644 --- a/src/NServiceBus.Core/Receiving/ReceiveComponent.cs +++ b/src/NServiceBus.Core/Receiving/ReceiveComponent.cs @@ -155,9 +155,6 @@ public async Task Initialize( var receivePipeline = pipelineComponent.CreatePipeline(builder); var pipelineMetrics = builder.GetService(); - pipelineMetrics.Initialize( - configuration.QueueNameBase, - configuration.InstanceSpecificQueueAddress?.Discriminator); var mainPipelineExecutor = new MainPipelineExecutor(builder, pipelineCache, messageOperations, configuration.PipelineCompletedSubscribers, receivePipeline, activityFactory, pipelineMetrics); var recoverabilityPipelineExecutor = recoverabilityComponent.CreateRecoverabilityPipelineExecutor( From 218cc0923aab93da11570e226323c4e842a3c1ba Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Thu, 27 Jun 2024 21:12:01 +0200 Subject: [PATCH 64/76] Make metrics specific to the incoming pipeline --- .../OpenTelemetry/MeterTests.cs | 4 +-- .../ReceiveDiagnosticsBehaviorTests.cs | 26 +++++++++---------- .../IncomingPipelineMetrics.cs} | 6 ++--- .../Incoming/InvokeHandlerTerminator.cs | 2 +- .../Pipeline/MainPipelineExecutor.cs | 12 ++++----- .../Pipeline/PipelineComponent.cs | 2 +- .../Receiving/ReceiveComponent.cs | 2 +- 7 files changed, 27 insertions(+), 27 deletions(-) rename src/NServiceBus.Core/Pipeline/{PipelineMetrics.cs => Incoming/IncomingPipelineMetrics.cs} (97%) diff --git a/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs b/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs index 3226b3f9ce7..cd059b1d7b8 100644 --- a/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs +++ b/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs @@ -23,7 +23,7 @@ public void Verify_MeterAPI() .Select(x => x.GetRawConstantValue()) .OrderBy(value => value) .ToList(); - var metrics = typeof(PipelineMetrics) + var metrics = typeof(IncomingPipelineMetrics) .GetFields(BindingFlags.Static | BindingFlags.NonPublic) .Where(fi => typeof(Instrument).IsAssignableFrom(fi.FieldType)) .Select(fi => (Instrument)fi.GetValue(null)) @@ -33,7 +33,7 @@ public void Verify_MeterAPI() Approver.Verify(new { Note = "Changes to metrics API should result in an update to NServiceBusMeter version.", - ActivitySourceVersion = PipelineMetrics.NServiceBusMeter.Version, + ActivitySourceVersion = IncomingPipelineMetrics.NServiceBusMeter.Version, Tags = meterTags, Metrics = metrics }); diff --git a/src/NServiceBus.Core.Tests/OpenTelemetry/ReceiveDiagnosticsBehaviorTests.cs b/src/NServiceBus.Core.Tests/OpenTelemetry/ReceiveDiagnosticsBehaviorTests.cs index c0161d580d1..4f217e60293 100644 --- a/src/NServiceBus.Core.Tests/OpenTelemetry/ReceiveDiagnosticsBehaviorTests.cs +++ b/src/NServiceBus.Core.Tests/OpenTelemetry/ReceiveDiagnosticsBehaviorTests.cs @@ -19,9 +19,9 @@ public async Task Should_increase_total_fetched_when_processing_message() var testableIncomingPhysicalMessageContext = new TestableIncomingPhysicalMessageContext(); await behavior.Invoke(testableIncomingPhysicalMessageContext, _ => Task.CompletedTask); - metricsListener.AssertMetric(PipelineMetrics.TotalFetched.Name, 1); + metricsListener.AssertMetric(IncomingPipelineMetrics.TotalFetched.Name, 1); - var fetchedTags = metricsListener.Tags[PipelineMetrics.TotalFetched.Name].ToImmutableDictionary(); + var fetchedTags = metricsListener.Tags[IncomingPipelineMetrics.TotalFetched.Name].ToImmutableDictionary(); Assert.AreEqual("discriminator", fetchedTags[MeterTags.EndpointDiscriminator]); Assert.AreEqual("queueBaseName", fetchedTags[MeterTags.QueueName]); } @@ -37,11 +37,11 @@ public async Task Should_increase_total_successful_when_processing_message_succe tags.Add(MeterTags.MessageType, "SomeType"); await behavior.Invoke(testableIncomingPhysicalMessageContext, _ => Task.CompletedTask); - metricsListener.AssertMetric(PipelineMetrics.TotalFetched.Name, 1); - metricsListener.AssertMetric(PipelineMetrics.TotalProcessedSuccessfully.Name, 1); - metricsListener.AssertMetric(PipelineMetrics.TotalFailures.Name, 0); + metricsListener.AssertMetric(IncomingPipelineMetrics.TotalFetched.Name, 1); + metricsListener.AssertMetric(IncomingPipelineMetrics.TotalProcessedSuccessfully.Name, 1); + metricsListener.AssertMetric(IncomingPipelineMetrics.TotalFailures.Name, 0); - var processedTags = metricsListener.Tags[PipelineMetrics.TotalProcessedSuccessfully.Name].ToImmutableDictionary(); + var processedTags = metricsListener.Tags[IncomingPipelineMetrics.TotalProcessedSuccessfully.Name].ToImmutableDictionary(); Assert.AreEqual("discriminator", processedTags[MeterTags.EndpointDiscriminator]); Assert.AreEqual("queueBaseName", processedTags[MeterTags.QueueName]); Assert.AreEqual("SomeType", processedTags[MeterTags.MessageType]); @@ -56,11 +56,11 @@ public void Should_increase_failures_error_when_processing_message_fails() using var metricsListener = TestingMetricListener.SetupNServiceBusMetricsListener(); Assert.ThrowsAsync(() => behavior.Invoke(context, _ => throw new Exception("test"))); - metricsListener.AssertMetric(PipelineMetrics.TotalFetched.Name, 1); - metricsListener.AssertMetric(PipelineMetrics.TotalProcessedSuccessfully.Name, 0); - metricsListener.AssertMetric(PipelineMetrics.TotalFailures.Name, 1); + metricsListener.AssertMetric(IncomingPipelineMetrics.TotalFetched.Name, 1); + metricsListener.AssertMetric(IncomingPipelineMetrics.TotalProcessedSuccessfully.Name, 0); + metricsListener.AssertMetric(IncomingPipelineMetrics.TotalFailures.Name, 1); - var failureTags = metricsListener.Tags[PipelineMetrics.TotalFailures.Name].ToImmutableDictionary(); + var failureTags = metricsListener.Tags[IncomingPipelineMetrics.TotalFailures.Name].ToImmutableDictionary(); Assert.AreEqual(typeof(Exception), failureTags[MeterTags.FailureType]); Assert.AreEqual("discriminator", failureTags[MeterTags.EndpointDiscriminator]); Assert.AreEqual("queueBaseName", failureTags[MeterTags.QueueName]); @@ -83,8 +83,8 @@ public void Should_not_increase_total_failures_when_cancellation_exception() return Task.CompletedTask; })); - metricsListener.AssertMetric(PipelineMetrics.TotalFetched.Name, 1); - metricsListener.AssertMetric(PipelineMetrics.TotalProcessedSuccessfully.Name, 0); - metricsListener.AssertMetric(PipelineMetrics.TotalFailures.Name, 0); + metricsListener.AssertMetric(IncomingPipelineMetrics.TotalFetched.Name, 1); + metricsListener.AssertMetric(IncomingPipelineMetrics.TotalProcessedSuccessfully.Name, 0); + metricsListener.AssertMetric(IncomingPipelineMetrics.TotalFailures.Name, 0); } } \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/PipelineMetrics.cs b/src/NServiceBus.Core/Pipeline/Incoming/IncomingPipelineMetrics.cs similarity index 97% rename from src/NServiceBus.Core/Pipeline/PipelineMetrics.cs rename to src/NServiceBus.Core/Pipeline/Incoming/IncomingPipelineMetrics.cs index 2c83b5a9d35..7aa207a3fee 100644 --- a/src/NServiceBus.Core/Pipeline/PipelineMetrics.cs +++ b/src/NServiceBus.Core/Pipeline/Incoming/IncomingPipelineMetrics.cs @@ -7,7 +7,7 @@ using Pipeline; using Settings; -class PipelineMetrics +class IncomingPipelineMetrics { const string TotalProcessedSuccessfully = "nservicebus.messaging.successes"; const string TotalFetched = "nservicebus.messaging.fetches"; @@ -15,9 +15,9 @@ class PipelineMetrics const string MessageHandlerTime = "nservicebus.messaging.handler_time"; const string CriticalTime = "nservicebus.messaging.critical_time"; - public PipelineMetrics(IMeterFactory meterFactory, IReadOnlySettings settings) + public IncomingPipelineMetrics(IMeterFactory meterFactory, IReadOnlySettings settings) { - var meter = meterFactory.Create("NServiceBus.Core.Pipeline", "0.2.0"); + var meter = meterFactory.Create("NServiceBus.Core.Pipeline.Incoming", "0.2.0"); totalProcessedSuccessfully = meter.CreateCounter(TotalProcessedSuccessfully, description: "Total number of messages processed successfully by the endpoint."); totalFetched = meter.CreateCounter(TotalFetched, diff --git a/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs b/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs index 1c5a691fb76..d7acb5a0bbb 100644 --- a/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs +++ b/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs @@ -22,7 +22,7 @@ protected override async Task Terminate(IInvokeHandlerContext context) // TODO: this is effectively using a provider pattern. It would be better to make that explicit allowing the // provider to return a NoOpMessagingMetricsMeters kind of meter - var messagingMetricsMeters = context.Builder.GetService(); + var messagingMetricsMeters = context.Builder.GetService(); // Might as well abort before invoking the handler if we're shutting down context.CancellationToken.ThrowIfCancellationRequested(); diff --git a/src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs b/src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs index da67fd67546..f396a8b1085 100644 --- a/src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs +++ b/src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs @@ -14,7 +14,7 @@ class MainPipelineExecutor( INotificationSubscriptions receivePipelineNotification, IPipeline receivePipeline, IActivityFactory activityFactory, - PipelineMetrics pipelineMetrics) + IncomingPipelineMetrics incomingPipelineMetrics) : IPipelineExecutor { public async Task Invoke(MessageContext messageContext, CancellationToken cancellationToken = default) @@ -23,8 +23,8 @@ public async Task Invoke(MessageContext messageContext, CancellationToken cancel using var activity = activityFactory.StartIncomingPipelineActivity(messageContext); - var incomingPipelineMetricsTags = pipelineMetrics.CreateDefaultIncomingPipelineMetricTags(); - pipelineMetrics.RecordFetchedMessage(incomingPipelineMetricsTags); + var incomingPipelineMetricsTags = incomingPipelineMetrics.CreateDefaultIncomingPipelineMetricTags(); + incomingPipelineMetrics.RecordFetchedMessage(incomingPipelineMetricsTags); var childScope = rootBuilder.CreateAsyncScope(); await using (childScope.ConfigureAwait(false)) @@ -64,7 +64,7 @@ public async Task Invoke(MessageContext messageContext, CancellationToken cancel ex.Data["Pipeline canceled"] = transportReceiveContext.CancellationToken.IsCancellationRequested; - pipelineMetrics.RecordMessageProcessingFailure(incomingPipelineMetricsTags, ex); + incomingPipelineMetrics.RecordMessageProcessingFailure(incomingPipelineMetricsTags, ex); throw; } @@ -72,10 +72,10 @@ public async Task Invoke(MessageContext messageContext, CancellationToken cancel var completedAt = DateTimeOffset.UtcNow; // TODO the following metrics should be recorded only if the Outbox did not dedup the incoming message // We should not publish a successfully processed or critical time for a duplicate message - pipelineMetrics.RecordMessageSuccessfullyProcessed(incomingPipelineMetricsTags); + incomingPipelineMetrics.RecordMessageSuccessfullyProcessed(incomingPipelineMetricsTags); if (message.Headers.TryGetDeliverAt(out var startTime) || message.Headers.TryGetTimeSent(out startTime)) { - pipelineMetrics.RecordMessageCriticalTime(completedAt - startTime, incomingPipelineMetricsTags); + incomingPipelineMetrics.RecordMessageCriticalTime(completedAt - startTime, incomingPipelineMetricsTags); } await receivePipelineNotification.Raise(new ReceivePipelineCompleted(message, pipelineStartedAt, completedAt), cancellationToken).ConfigureAwait(false); diff --git a/src/NServiceBus.Core/Pipeline/PipelineComponent.cs b/src/NServiceBus.Core/Pipeline/PipelineComponent.cs index d1776d38402..8ed438e5394 100644 --- a/src/NServiceBus.Core/Pipeline/PipelineComponent.cs +++ b/src/NServiceBus.Core/Pipeline/PipelineComponent.cs @@ -26,7 +26,7 @@ public static PipelineComponent Initialize(PipelineSettings settings, HostingCom } // make the PipelineMetrics available to the Pipeline - hostingConfiguration.Services.AddSingleton(); + hostingConfiguration.Services.AddSingleton(); return new PipelineComponent(modifications); } diff --git a/src/NServiceBus.Core/Receiving/ReceiveComponent.cs b/src/NServiceBus.Core/Receiving/ReceiveComponent.cs index 3f8f18228ab..8d1ca2b8c33 100644 --- a/src/NServiceBus.Core/Receiving/ReceiveComponent.cs +++ b/src/NServiceBus.Core/Receiving/ReceiveComponent.cs @@ -154,7 +154,7 @@ public async Task Initialize( var receivePipeline = pipelineComponent.CreatePipeline(builder); - var pipelineMetrics = builder.GetService(); + var pipelineMetrics = builder.GetService(); var mainPipelineExecutor = new MainPipelineExecutor(builder, pipelineCache, messageOperations, configuration.PipelineCompletedSubscribers, receivePipeline, activityFactory, pipelineMetrics); var recoverabilityPipelineExecutor = recoverabilityComponent.CreateRecoverabilityPipelineExecutor( From 2caf40b10509a8456c2d444b923283d58673b3a6 Mon Sep 17 00:00:00 2001 From: SzymonPobiega Date: Wed, 3 Jul 2024 15:06:08 +0200 Subject: [PATCH 65/76] Fix rebase --- .../Pipeline/MainPipelineExecutorTests.cs | 4 +- .../Incoming/IncomingPipelineMetrics.cs | 74 ++++++++++++++++++- .../Incoming/TransportReceiveContext.cs | 2 - .../Pipeline/MainPipelineExecutor.cs | 6 +- .../Recoverability/RecoverabilityComponent.cs | 3 +- .../RecoverabilityRoutingConnector.cs | 10 ++- .../Transports/MessageContext.cs | 3 + 7 files changed, 88 insertions(+), 14 deletions(-) diff --git a/src/NServiceBus.Core.Tests/Pipeline/MainPipelineExecutorTests.cs b/src/NServiceBus.Core.Tests/Pipeline/MainPipelineExecutorTests.cs index 1fc5d0a6816..60ecd9f4dcf 100644 --- a/src/NServiceBus.Core.Tests/Pipeline/MainPipelineExecutorTests.cs +++ b/src/NServiceBus.Core.Tests/Pipeline/MainPipelineExecutorTests.cs @@ -8,6 +8,7 @@ using NServiceBus.Pipeline; using NUnit.Framework; using OpenTelemetry.Helpers; +using Settings; using Transport; [TestFixture] @@ -118,7 +119,8 @@ static MainPipelineExecutor CreateMainPipelineExecutor(IPipeline(), receivePipeline, - new ActivityFactory()); + new ActivityFactory(), + new IncomingPipelineMetrics(null, new SettingsHolder())); return executor; } diff --git a/src/NServiceBus.Core/Pipeline/Incoming/IncomingPipelineMetrics.cs b/src/NServiceBus.Core/Pipeline/Incoming/IncomingPipelineMetrics.cs index 7aa207a3fee..6206f7f66f8 100644 --- a/src/NServiceBus.Core/Pipeline/Incoming/IncomingPipelineMetrics.cs +++ b/src/NServiceBus.Core/Pipeline/Incoming/IncomingPipelineMetrics.cs @@ -14,6 +14,9 @@ class IncomingPipelineMetrics const string TotalFailures = "nservicebus.messaging.failures"; const string MessageHandlerTime = "nservicebus.messaging.handler_time"; const string CriticalTime = "nservicebus.messaging.critical_time"; + const string RecoverabilityImmediate = "nservicebus.recoverability.immediate"; + const string RecoverabilityDelayed = "nservicebus.recoverability.delayed"; + const string RecoverabilityError = "nservicebus.recoverability.error"; public IncomingPipelineMetrics(IMeterFactory meterFactory, IReadOnlySettings settings) { @@ -28,19 +31,22 @@ public IncomingPipelineMetrics(IMeterFactory meterFactory, IReadOnlySettings set "The time in seconds for the execution of the business code."); criticalTime = meter.CreateHistogram(CriticalTime, "s", "The time in seconds between when the message was sent until processed by the endpoint."); + totalImmediateRetries = meter.CreateCounter(RecoverabilityImmediate, + description: "Total number of immediate retries requested."); + totalDelayedRetries = meter.CreateCounter(RecoverabilityDelayed, + description: "Total number of delayed retries requested."); + totalSentToErrorQueue = meter.CreateCounter(RecoverabilityError, + description: "Total number of messages sent to the error queue."); var config = settings.Get(); queueNameBase = config.QueueNameBase; endpointDiscriminator = config.InstanceSpecificQueueAddress?.Discriminator ?? ""; } - public IncomingPipelineMetricTags CreateDefaultIncomingPipelineMetricTags() + public void AddDefaultIncomingPipelineMetricTags(IncomingPipelineMetricTags incomingPipelineMetricsTags) { - var incomingPipelineMetricsTags = new IncomingPipelineMetricTags(); incomingPipelineMetricsTags.Add(MeterTags.QueueName, queueNameBase); incomingPipelineMetricsTags.Add(MeterTags.EndpointDiscriminator, endpointDiscriminator ?? ""); - - return incomingPipelineMetricsTags; } public void RecordMessageSuccessfullyProcessed(IncomingPipelineMetricTags incomingPipelineMetricTags) @@ -145,11 +151,71 @@ public void RecordFailedMessageHandlerTime(IInvokeHandlerContext invokeHandlerCo messageHandlerTime.Record(elapsed.TotalSeconds, meterTags); } + public void RecordImmediateRetry(IRecoverabilityContext recoverabilityContext) + { + if (!totalImmediateRetries.Enabled) + { + return; + } + + var incomingPipelineMetricTags = recoverabilityContext.Extensions.Get(); + TagList meterTags; + incomingPipelineMetricTags.ApplyTags(ref meterTags, [ + MeterTags.QueueName, + MeterTags.EndpointDiscriminator, + MeterTags.MessageType, + MeterTags.MessageHandlerType]); + // This is what Add(string, object) does so skipping an unnecessary stack frame + meterTags.Add(new KeyValuePair(MeterTags.ErrorType, recoverabilityContext.Exception.GetType().FullName)); + totalImmediateRetries.Add(1, meterTags); + } + + public void RecordDelayedRetry(IRecoverabilityContext recoverabilityContext) + { + if (!totalDelayedRetries.Enabled) + { + return; + } + + var incomingPipelineMetricTags = recoverabilityContext.Extensions.Get(); + TagList meterTags; + incomingPipelineMetricTags.ApplyTags(ref meterTags, [ + MeterTags.QueueName, + MeterTags.EndpointDiscriminator, + MeterTags.MessageType, + MeterTags.MessageHandlerType]); + // This is what Add(string, object) does so skipping an unnecessary stack frame + meterTags.Add(new KeyValuePair(MeterTags.ErrorType, recoverabilityContext.Exception.GetType().FullName)); + totalDelayedRetries.Add(1, meterTags); + } + + public void RecordSendToErrorQueue(IRecoverabilityContext recoverabilityContext) + { + if (!totalSentToErrorQueue.Enabled) + { + return; + } + + var incomingPipelineMetricTags = recoverabilityContext.Extensions.Get(); + TagList meterTags; + incomingPipelineMetricTags.ApplyTags(ref meterTags, [ + MeterTags.QueueName, + MeterTags.EndpointDiscriminator, + MeterTags.MessageType, + MeterTags.MessageHandlerType]); + // This is what Add(string, object) does so skipping an unnecessary stack frame + meterTags.Add(new KeyValuePair(MeterTags.ErrorType, recoverabilityContext.Exception.GetType().FullName)); + totalSentToErrorQueue.Add(1, meterTags); + } + readonly Counter totalProcessedSuccessfully; readonly Counter totalFetched; readonly Counter totalFailures; readonly Histogram messageHandlerTime; readonly Histogram criticalTime; + readonly Counter totalImmediateRetries; + readonly Counter totalDelayedRetries; + readonly Counter totalSentToErrorQueue; string queueNameBase; string endpointDiscriminator; } \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Incoming/TransportReceiveContext.cs b/src/NServiceBus.Core/Pipeline/Incoming/TransportReceiveContext.cs index 4e6780f43a9..7651fa07b27 100644 --- a/src/NServiceBus.Core/Pipeline/Incoming/TransportReceiveContext.cs +++ b/src/NServiceBus.Core/Pipeline/Incoming/TransportReceiveContext.cs @@ -20,8 +20,6 @@ public TransportReceiveContext(IServiceProvider serviceProvider, MessageOperatio Message = receivedMessage; Set(Message); Set(transportTransaction); - //Hack to be able to get the tags in the recoverability pipeline - _ = parentContext.GetOrCreate(); } /// diff --git a/src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs b/src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs index f396a8b1085..af850d72936 100644 --- a/src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs +++ b/src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs @@ -23,7 +23,9 @@ public async Task Invoke(MessageContext messageContext, CancellationToken cancel using var activity = activityFactory.StartIncomingPipelineActivity(messageContext); - var incomingPipelineMetricsTags = incomingPipelineMetrics.CreateDefaultIncomingPipelineMetricTags(); + var incomingPipelineMetricsTags = messageContext.Extensions.parentBag.GetOrCreate(); + + incomingPipelineMetrics.AddDefaultIncomingPipelineMetricTags(incomingPipelineMetricsTags); incomingPipelineMetrics.RecordFetchedMessage(incomingPipelineMetricsTags); var childScope = rootBuilder.CreateAsyncScope(); @@ -45,8 +47,6 @@ public async Task Invoke(MessageContext messageContext, CancellationToken cancel transportReceiveContext.SetIncomingPipelineActitvity(activity); } - transportReceiveContext.Extensions.Set(incomingPipelineMetricsTags); - try { await receivePipeline.Invoke(transportReceiveContext, activity).ConfigureAwait(false); diff --git a/src/NServiceBus.Core/Recoverability/RecoverabilityComponent.cs b/src/NServiceBus.Core/Recoverability/RecoverabilityComponent.cs index d284a3a8757..1f7a7c53c9a 100644 --- a/src/NServiceBus.Core/Recoverability/RecoverabilityComponent.cs +++ b/src/NServiceBus.Core/Recoverability/RecoverabilityComponent.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.Extensions.DependencyInjection; using NServiceBus.Hosting; using NServiceBus.Logging; using NServiceBus.Pipeline; @@ -56,7 +57,7 @@ public void Initialize( faultMetadataExtractor = CreateFaultMetadataExtractor(); - pipelineSettings.Register(new RecoverabilityRoutingConnector(messageRetryNotification, messageFaultedNotification), "Executes the configured retry policy"); + pipelineSettings.Register(sp => new RecoverabilityRoutingConnector(sp.GetRequiredService(), messageRetryNotification, messageFaultedNotification), "Executes the configured retry policy"); hostingConfiguration.AddStartupDiagnosticsSection("Recoverability", new { diff --git a/src/NServiceBus.Core/Recoverability/RecoverabilityRoutingConnector.cs b/src/NServiceBus.Core/Recoverability/RecoverabilityRoutingConnector.cs index 799ca062201..b6ef6c5de41 100644 --- a/src/NServiceBus.Core/Recoverability/RecoverabilityRoutingConnector.cs +++ b/src/NServiceBus.Core/Recoverability/RecoverabilityRoutingConnector.cs @@ -7,10 +7,14 @@ class RecoverabilityRoutingConnector : StageConnector { + readonly IncomingPipelineMetrics incomingPipelineMetrics; + public RecoverabilityRoutingConnector( + IncomingPipelineMetrics incomingPipelineMetrics, INotificationSubscriptions messageRetryNotification, INotificationSubscriptions messageFaultedNotification) { + this.incomingPipelineMetrics = incomingPipelineMetrics; notifications = new CompositeNotification(); notifications.Register(messageRetryNotification); notifications.Register(messageFaultedNotification); @@ -36,15 +40,15 @@ public override async Task Invoke(IRecoverabilityContext context, Func headers Extensions = context; ReceiveAddress = receiveAddress; TransportTransaction = transportTransaction; + + //Hack to be able to get the tags in the recoverability pipeline + context.GetOrCreate(); } /// From 5c21be4bc3a65874f1afb89cabaa324c896f1325 Mon Sep 17 00:00:00 2001 From: SzymonPobiega Date: Thu, 4 Jul 2024 13:06:27 +0200 Subject: [PATCH 66/76] Fix the acceptance tests --- .../Metrics/TestingMetricListener.cs | 11 ++- .../When_message_is_processed_successfully.cs | 22 +++-- .../Metrics/When_message_processing_fails.cs | 11 +++ .../Sub_to_scaled_out_pubs.cs | 1 - .../OpenTelemetry/MeterTests.cs | 76 ++++++++-------- .../ReceiveDiagnosticsBehaviorTests.cs | 90 ------------------- ...tReceiveToPhysicalMessageConnectorTests.cs | 3 +- .../Incoming/IncomingPipelineMetrics.cs | 42 ++++----- ...nsportReceiveToPhysicalMessageConnector.cs | 7 +- .../Pipeline/MainPipelineExecutor.cs | 10 +-- .../Receiving/ReceiveComponent.cs | 2 +- .../Transports/MessageContext.cs | 1 - 12 files changed, 106 insertions(+), 170 deletions(-) delete mode 100644 src/NServiceBus.Core.Tests/OpenTelemetry/ReceiveDiagnosticsBehaviorTests.cs diff --git a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/Metrics/TestingMetricListener.cs b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/Metrics/TestingMetricListener.cs index 469964425ad..01cce8da1dd 100644 --- a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/Metrics/TestingMetricListener.cs +++ b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/Metrics/TestingMetricListener.cs @@ -50,7 +50,7 @@ public TestingMetricListener(string sourceName) } public static TestingMetricListener SetupNServiceBusMetricsListener() => - SetupMetricsListener("NServiceBus.Core"); + SetupMetricsListener("NServiceBus.Core.Pipeline.Incoming"); public static TestingMetricListener SetupMetricsListener(string sourceName) { @@ -92,4 +92,13 @@ public object AssertTagKeyExists(string metricName, string tagKey) return meterTag.Value; } + + public void AssertTags(string metricName, Dictionary expectedTags) + { + foreach (var kvp in expectedTags) + { + var actualTagValue = AssertTagKeyExists(metricName, kvp.Key); + Assert.AreEqual(kvp.Value, actualTagValue); + } + } } \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/Metrics/When_message_is_processed_successfully.cs b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/Metrics/When_message_is_processed_successfully.cs index 584701cc036..5b67e5dd4ce 100644 --- a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/Metrics/When_message_is_processed_successfully.cs +++ b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/Metrics/When_message_is_processed_successfully.cs @@ -1,5 +1,6 @@ namespace NServiceBus.AcceptanceTests.Core.OpenTelemetry.Metrics; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using NServiceBus; @@ -15,7 +16,7 @@ public async Task Should_report_successful_message_metric() using var metricsListener = TestingMetricListener.SetupNServiceBusMetricsListener(); _ = await Scenario.Define() - .WithEndpoint(b => b + .WithEndpoint(b => b.CustomConfig(x => x.MakeInstanceUniquelyAddressable("disc")) .When(async (session, ctx) => { for (var x = 0; x < 5; x++) @@ -30,13 +31,20 @@ public async Task Should_report_successful_message_metric() metricsListener.AssertMetric("nservicebus.messaging.fetches", 5); metricsListener.AssertMetric("nservicebus.messaging.failures", 0); - var successEndpoint = metricsListener.AssertTagKeyExists("nservicebus.messaging.successes", "nservicebus.queue"); - var successType = metricsListener.AssertTagKeyExists("nservicebus.messaging.successes", "nservicebus.message_type"); - var fetchedEndpoint = metricsListener.AssertTagKeyExists("nservicebus.messaging.fetches", "nservicebus.queue"); + metricsListener.AssertTags("nservicebus.messaging.fetches", + new Dictionary + { + ["nservicebus.queue"] = Conventions.EndpointNamingConvention(typeof(EndpointWithMetrics)), + ["nservicebus.discriminator"] = "disc", + }); - Assert.AreEqual(Conventions.EndpointNamingConvention(typeof(EndpointWithMetrics)), successEndpoint); - Assert.AreEqual(Conventions.EndpointNamingConvention(typeof(EndpointWithMetrics)), fetchedEndpoint); - Assert.AreEqual(typeof(OutgoingMessage).FullName, successType); + metricsListener.AssertTags("nservicebus.messaging.successes", + new Dictionary + { + ["nservicebus.queue"] = Conventions.EndpointNamingConvention(typeof(EndpointWithMetrics)), + ["nservicebus.discriminator"] = "disc", + ["nservicebus.message_type"] = typeof(OutgoingMessage).FullName, + }); } [Test] diff --git a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/Metrics/When_message_processing_fails.cs b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/Metrics/When_message_processing_fails.cs index b3904324904..c5655566a85 100644 --- a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/Metrics/When_message_processing_fails.cs +++ b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/Metrics/When_message_processing_fails.cs @@ -1,8 +1,10 @@ namespace NServiceBus.AcceptanceTests.Core.OpenTelemetry.Metrics; +using System.Collections.Generic; using System.Threading.Tasks; using AcceptanceTesting; using NUnit.Framework; +using AcceptanceTesting.Customization; public class When_message_processing_fails : OpenTelemetryAcceptanceTest { @@ -13,6 +15,7 @@ public async Task Should_report_failing_message_metrics() _ = await Scenario.Define() .WithEndpoint(e => e .DoNotFailOnErrorMessages() + .CustomConfig(x => x.MakeInstanceUniquelyAddressable("disc")) .When(s => s.SendLocal(new FailingMessage()))) .Done(c => c.HandlerInvoked) .Run(); @@ -20,6 +23,14 @@ public async Task Should_report_failing_message_metrics() metricsListener.AssertMetric("nservicebus.messaging.fetches", 1); metricsListener.AssertMetric("nservicebus.messaging.failures", 1); metricsListener.AssertMetric("nservicebus.messaging.successes", 0); + + metricsListener.AssertTags("nservicebus.messaging.failures", + new Dictionary + { + ["nservicebus.queue"] = Conventions.EndpointNamingConvention(typeof(FailingEndpoint)), + ["nservicebus.discriminator"] = "disc", + ["nservicebus.failure_type"] = typeof(SimulatedException).FullName, + }); } class Context : ScenarioContext diff --git a/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/Sub_to_scaled_out_pubs.cs b/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/Sub_to_scaled_out_pubs.cs index 8a045025fc9..497cab5b35e 100644 --- a/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/Sub_to_scaled_out_pubs.cs +++ b/src/NServiceBus.AcceptanceTests/Routing/MessageDrivenSubscriptions/Sub_to_scaled_out_pubs.cs @@ -1,7 +1,6 @@ namespace NServiceBus.AcceptanceTests.Routing.MessageDrivenSubscriptions; using System.Collections.Concurrent; -using System.Collections.Generic; using System.Threading.Tasks; using AcceptanceTesting; using AcceptanceTesting.Customization; diff --git a/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs b/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs index cd059b1d7b8..792cda6de4c 100644 --- a/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs +++ b/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs @@ -1,41 +1,41 @@ -namespace NServiceBus.Core.Tests.OpenTelemetry; +//namespace NServiceBus.Core.Tests.OpenTelemetry; -using System.Diagnostics.Metrics; -using System.Linq; -using System.Reflection; -using NUnit.Framework; -using Particular.Approvals; +//using System.Diagnostics.Metrics; +//using System.Linq; +//using System.Reflection; +//using NUnit.Framework; +//using Particular.Approvals; -[TestFixture] -public class MeterTests -{ - [Test] - public void Verify_MeterAPI() - { - //TODO: Create a test IMeterFactory implementation - // - Instantiate the *Metrics call to test - // - Use the test IMeterFactory implementation to record calls - // - Dump recorded call into a file and approve it +//[TestFixture] +//public class MeterTests +//{ +// [Test] +// public void Verify_MeterAPI() +// { +// //TODO: Create a test IMeterFactory implementation +// // - Instantiate the *Metrics call to test +// // - Use the test IMeterFactory implementation to record calls +// // - Dump recorded call into a file and approve it - var meterTags = typeof(MeterTags) - .GetFields(BindingFlags.Public | BindingFlags.Static) - .Where(fi => fi.IsLiteral && !fi.IsInitOnly) - .Select(x => x.GetRawConstantValue()) - .OrderBy(value => value) - .ToList(); - var metrics = typeof(IncomingPipelineMetrics) - .GetFields(BindingFlags.Static | BindingFlags.NonPublic) - .Where(fi => typeof(Instrument).IsAssignableFrom(fi.FieldType)) - .Select(fi => (Instrument)fi.GetValue(null)) - .Select(x => $"{x.Name} => {x.GetType().Name.Split("`").First()}{(x.Unit == null ? "" : ", Unit: ")}{x.Unit ?? ""}") - .OrderBy(value => value) - .ToList(); - Approver.Verify(new - { - Note = "Changes to metrics API should result in an update to NServiceBusMeter version.", - ActivitySourceVersion = IncomingPipelineMetrics.NServiceBusMeter.Version, - Tags = meterTags, - Metrics = metrics - }); - } -} \ No newline at end of file +// var meterTags = typeof(MeterTags) +// .GetFields(BindingFlags.Public | BindingFlags.Static) +// .Where(fi => fi.IsLiteral && !fi.IsInitOnly) +// .Select(x => x.GetRawConstantValue()) +// .OrderBy(value => value) +// .ToList(); +// var metrics = typeof(IncomingPipelineMetrics) +// .GetFields(BindingFlags.Static | BindingFlags.NonPublic) +// .Where(fi => typeof(Instrument).IsAssignableFrom(fi.FieldType)) +// .Select(fi => (Instrument)fi.GetValue(null)) +// .Select(x => $"{x.Name} => {x.GetType().Name.Split("`").First()}{(x.Unit == null ? "" : ", Unit: ")}{x.Unit ?? ""}") +// .OrderBy(value => value) +// .ToList(); +// Approver.Verify(new +// { +// Note = "Changes to metrics API should result in an update to NServiceBusMeter version.", +// ActivitySourceVersion = IncomingPipelineMetrics.NServiceBusMeter.Version, +// Tags = meterTags, +// Metrics = metrics +// }); +// } +//} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/OpenTelemetry/ReceiveDiagnosticsBehaviorTests.cs b/src/NServiceBus.Core.Tests/OpenTelemetry/ReceiveDiagnosticsBehaviorTests.cs deleted file mode 100644 index 4f217e60293..00000000000 --- a/src/NServiceBus.Core.Tests/OpenTelemetry/ReceiveDiagnosticsBehaviorTests.cs +++ /dev/null @@ -1,90 +0,0 @@ -namespace NServiceBus.Core.Tests.OpenTelemetry; - -using System; -using System.Collections.Immutable; -using System.Threading; -using System.Threading.Tasks; -using AcceptanceTests.Core.OpenTelemetry.Metrics; -using NServiceBus.Testing; -using NUnit.Framework; - -[TestFixture] -class ReceiveDiagnosticsBehaviorTests -{ - [Test] - public async Task Should_increase_total_fetched_when_processing_message() - { - var behavior = new ReceiveDiagnosticsBehavior("queueBaseName", "discriminator"); - using var metricsListener = TestingMetricListener.SetupNServiceBusMetricsListener(); - var testableIncomingPhysicalMessageContext = new TestableIncomingPhysicalMessageContext(); - await behavior.Invoke(testableIncomingPhysicalMessageContext, _ => Task.CompletedTask); - - metricsListener.AssertMetric(IncomingPipelineMetrics.TotalFetched.Name, 1); - - var fetchedTags = metricsListener.Tags[IncomingPipelineMetrics.TotalFetched.Name].ToImmutableDictionary(); - Assert.AreEqual("discriminator", fetchedTags[MeterTags.EndpointDiscriminator]); - Assert.AreEqual("queueBaseName", fetchedTags[MeterTags.QueueName]); - } - - [Test] - public async Task Should_increase_total_successful_when_processing_message_successfully() - { - var behavior = new ReceiveDiagnosticsBehavior("queueBaseName", "discriminator"); - - using var metricsListener = TestingMetricListener.SetupNServiceBusMetricsListener(); - var testableIncomingPhysicalMessageContext = new TestableIncomingPhysicalMessageContext(); - var tags = testableIncomingPhysicalMessageContext.Extensions.Get(); - tags.Add(MeterTags.MessageType, "SomeType"); - await behavior.Invoke(testableIncomingPhysicalMessageContext, _ => Task.CompletedTask); - - metricsListener.AssertMetric(IncomingPipelineMetrics.TotalFetched.Name, 1); - metricsListener.AssertMetric(IncomingPipelineMetrics.TotalProcessedSuccessfully.Name, 1); - metricsListener.AssertMetric(IncomingPipelineMetrics.TotalFailures.Name, 0); - - var processedTags = metricsListener.Tags[IncomingPipelineMetrics.TotalProcessedSuccessfully.Name].ToImmutableDictionary(); - Assert.AreEqual("discriminator", processedTags[MeterTags.EndpointDiscriminator]); - Assert.AreEqual("queueBaseName", processedTags[MeterTags.QueueName]); - Assert.AreEqual("SomeType", processedTags[MeterTags.MessageType]); - } - - [Test] - public void Should_increase_failures_error_when_processing_message_fails() - { - var behavior = new ReceiveDiagnosticsBehavior("queueBaseName", "discriminator"); - var context = new TestableIncomingPhysicalMessageContext(); - - using var metricsListener = TestingMetricListener.SetupNServiceBusMetricsListener(); - Assert.ThrowsAsync(() => behavior.Invoke(context, _ => throw new Exception("test"))); - - metricsListener.AssertMetric(IncomingPipelineMetrics.TotalFetched.Name, 1); - metricsListener.AssertMetric(IncomingPipelineMetrics.TotalProcessedSuccessfully.Name, 0); - metricsListener.AssertMetric(IncomingPipelineMetrics.TotalFailures.Name, 1); - - var failureTags = metricsListener.Tags[IncomingPipelineMetrics.TotalFailures.Name].ToImmutableDictionary(); - Assert.AreEqual(typeof(Exception), failureTags[MeterTags.FailureType]); - Assert.AreEqual("discriminator", failureTags[MeterTags.EndpointDiscriminator]); - Assert.AreEqual("queueBaseName", failureTags[MeterTags.QueueName]); - } - - [Test] - public void Should_not_increase_total_failures_when_cancellation_exception() - { - var behavior = new ReceiveDiagnosticsBehavior("queueBaseName", "discriminator"); - - using var cts = new CancellationTokenSource(); - using var metricsListener = TestingMetricListener.SetupNServiceBusMetricsListener(); - - var context = new TestableIncomingPhysicalMessageContext { CancellationToken = cts.Token }; - - cts.Cancel(); - Assert.ThrowsAsync(() => behavior.Invoke(context, ctx => - { - ctx.CancellationToken.ThrowIfCancellationRequested(); - return Task.CompletedTask; - })); - - metricsListener.AssertMetric(IncomingPipelineMetrics.TotalFetched.Name, 1); - metricsListener.AssertMetric(IncomingPipelineMetrics.TotalProcessedSuccessfully.Name, 0); - metricsListener.AssertMetric(IncomingPipelineMetrics.TotalFailures.Name, 0); - } -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/Reliability/Outbox/TransportReceiveToPhysicalMessageConnectorTests.cs b/src/NServiceBus.Core.Tests/Reliability/Outbox/TransportReceiveToPhysicalMessageConnectorTests.cs index f63220478c6..68e25aefaae 100644 --- a/src/NServiceBus.Core.Tests/Reliability/Outbox/TransportReceiveToPhysicalMessageConnectorTests.cs +++ b/src/NServiceBus.Core.Tests/Reliability/Outbox/TransportReceiveToPhysicalMessageConnectorTests.cs @@ -10,6 +10,7 @@ using NServiceBus.Pipeline; using NServiceBus.Routing; using NUnit.Framework; +using Settings; using Testing; using Transport; using TransportOperation = Transport.TransportOperation; @@ -180,7 +181,7 @@ public void SetUp() fakeOutbox = new FakeOutboxStorage(); fakeBatchPipeline = new FakeBatchPipeline(); - behavior = new TransportReceiveToPhysicalMessageConnector(fakeOutbox); + behavior = new TransportReceiveToPhysicalMessageConnector(fakeOutbox, new IncomingPipelineMetrics(null, new SettingsHolder())); } Task Invoke(ITransportReceiveContext context, Func next = null) => behavior.Invoke(context, next ?? (_ => Task.CompletedTask)); diff --git a/src/NServiceBus.Core/Pipeline/Incoming/IncomingPipelineMetrics.cs b/src/NServiceBus.Core/Pipeline/Incoming/IncomingPipelineMetrics.cs index 6206f7f66f8..0a1c23a334e 100644 --- a/src/NServiceBus.Core/Pipeline/Incoming/IncomingPipelineMetrics.cs +++ b/src/NServiceBus.Core/Pipeline/Incoming/IncomingPipelineMetrics.cs @@ -49,19 +49,35 @@ public void AddDefaultIncomingPipelineMetricTags(IncomingPipelineMetricTags inco incomingPipelineMetricsTags.Add(MeterTags.EndpointDiscriminator, endpointDiscriminator ?? ""); } - public void RecordMessageSuccessfullyProcessed(IncomingPipelineMetricTags incomingPipelineMetricTags) + public void RecordMessageSuccessfullyProcessed(ITransportReceiveContext context, IncomingPipelineMetricTags incomingPipelineMetricTags) { - if (!totalProcessedSuccessfully.Enabled) + if (!totalProcessedSuccessfully.Enabled && !criticalTime.Enabled) { return; } TagList tags; incomingPipelineMetricTags.ApplyTags(ref tags, [ + MeterTags.QueueName, + MeterTags.EndpointDiscriminator, MeterTags.MessageType, MeterTags.MessageHandlerTypes]); - totalProcessedSuccessfully.Add(1, tags); + if (totalProcessedSuccessfully.Enabled) + { + totalProcessedSuccessfully.Add(1, tags); + } + if (criticalTime.Enabled) + { + var completedAt = DateTimeOffset.UtcNow; + + if (context.Message.Headers.TryGetDeliverAt(out var startTime) + || context.Message.Headers.TryGetTimeSent(out startTime)) + { + var criticalTimeElapsed = completedAt - startTime; + criticalTime.Record(criticalTimeElapsed.TotalSeconds, tags); + } + } } public void RecordMessageProcessingFailure(IncomingPipelineMetricTags incomingPipelineMetricTags, Exception error) @@ -72,8 +88,10 @@ public void RecordMessageProcessingFailure(IncomingPipelineMetricTags incomingPi } TagList tags; - tags.Add(new(MeterTags.FailureType, error.GetType())); + tags.Add(new(MeterTags.FailureType, error.GetType().FullName)); incomingPipelineMetricTags.ApplyTags(ref tags, [ + MeterTags.QueueName, + MeterTags.EndpointDiscriminator, MeterTags.MessageType, MeterTags.MessageHandlerTypes]); totalFailures.Add(1, tags); @@ -94,22 +112,6 @@ public void RecordFetchedMessage(IncomingPipelineMetricTags incomingPipelineMetr totalFetched.Add(1, tags); } - public void RecordMessageCriticalTime(TimeSpan messageCriticalTime, IncomingPipelineMetricTags incomingPipelineMetricTags) - { - if (!criticalTime.Enabled) - { - return; - } - - TagList tags; - incomingPipelineMetricTags.ApplyTags(ref tags, [ - MeterTags.QueueName, - MeterTags.EndpointDiscriminator, - MeterTags.MessageType]); - - criticalTime.Record(messageCriticalTime.TotalSeconds, tags); - } - public void RecordSuccessfulMessageHandlerTime(IInvokeHandlerContext invokeHandlerContext, TimeSpan elapsed) { if (!messageHandlerTime.Enabled) diff --git a/src/NServiceBus.Core/Pipeline/Incoming/TransportReceiveToPhysicalMessageConnector.cs b/src/NServiceBus.Core/Pipeline/Incoming/TransportReceiveToPhysicalMessageConnector.cs index 2bb09ec9f89..0f284c08a40 100644 --- a/src/NServiceBus.Core/Pipeline/Incoming/TransportReceiveToPhysicalMessageConnector.cs +++ b/src/NServiceBus.Core/Pipeline/Incoming/TransportReceiveToPhysicalMessageConnector.cs @@ -12,9 +12,10 @@ namespace NServiceBus; class TransportReceiveToPhysicalMessageConnector : IStageForkConnector { - public TransportReceiveToPhysicalMessageConnector(IOutboxStorage outboxStorage) + public TransportReceiveToPhysicalMessageConnector(IOutboxStorage outboxStorage, IncomingPipelineMetrics incomingPipelineMetrics) { this.outboxStorage = outboxStorage; + this.incomingPipelineMetrics = incomingPipelineMetrics; } public async Task Invoke(ITransportReceiveContext context, Func next) @@ -33,6 +34,9 @@ public async Task Invoke(ITransportReceiveContext context, Func(); + incomingPipelineMetrics.RecordMessageSuccessfullyProcessed(context, incomingPipelineMetricsTags); + var outboxMessage = new OutboxMessage(messageId, ConvertToOutboxOperations(pendingTransportOperations.Operations)); await outboxStorage.Store(outboxMessage, outboxTransaction, context.Extensions, context.CancellationToken).ConfigureAwait(false); @@ -131,4 +135,5 @@ static AddressTag DeserializeRoutingStrategy(Dictionary options) } readonly IOutboxStorage outboxStorage; + readonly IncomingPipelineMetrics incomingPipelineMetrics; } diff --git a/src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs b/src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs index af850d72936..78d9f35660e 100644 --- a/src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs +++ b/src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs @@ -23,7 +23,7 @@ public async Task Invoke(MessageContext messageContext, CancellationToken cancel using var activity = activityFactory.StartIncomingPipelineActivity(messageContext); - var incomingPipelineMetricsTags = messageContext.Extensions.parentBag.GetOrCreate(); + var incomingPipelineMetricsTags = messageContext.Extensions.Get(); incomingPipelineMetrics.AddDefaultIncomingPipelineMetricTags(incomingPipelineMetricsTags); incomingPipelineMetrics.RecordFetchedMessage(incomingPipelineMetricsTags); @@ -70,14 +70,6 @@ public async Task Invoke(MessageContext messageContext, CancellationToken cancel } var completedAt = DateTimeOffset.UtcNow; - // TODO the following metrics should be recorded only if the Outbox did not dedup the incoming message - // We should not publish a successfully processed or critical time for a duplicate message - incomingPipelineMetrics.RecordMessageSuccessfullyProcessed(incomingPipelineMetricsTags); - if (message.Headers.TryGetDeliverAt(out var startTime) || message.Headers.TryGetTimeSent(out startTime)) - { - incomingPipelineMetrics.RecordMessageCriticalTime(completedAt - startTime, incomingPipelineMetricsTags); - } - await receivePipelineNotification.Raise(new ReceivePipelineCompleted(message, pipelineStartedAt, completedAt), cancellationToken).ConfigureAwait(false); } } diff --git a/src/NServiceBus.Core/Receiving/ReceiveComponent.cs b/src/NServiceBus.Core/Receiving/ReceiveComponent.cs index 8d1ca2b8c33..20a6d3c1b89 100644 --- a/src/NServiceBus.Core/Receiving/ReceiveComponent.cs +++ b/src/NServiceBus.Core/Receiving/ReceiveComponent.cs @@ -63,7 +63,7 @@ public static ReceiveComponent Configure( pipelineSettings.Register("TransportReceiveToPhysicalMessageProcessingConnector", b => { var storage = b.GetService() ?? new NoOpOutboxStorage(); - return new TransportReceiveToPhysicalMessageConnector(storage); + return new TransportReceiveToPhysicalMessageConnector(storage, b.GetRequiredService()); }, "Allows to abort processing the message"); pipelineSettings.Register("LoadHandlersConnector", b => diff --git a/src/NServiceBus.Core/Transports/MessageContext.cs b/src/NServiceBus.Core/Transports/MessageContext.cs index 4a3323ddc50..c381b1156aa 100644 --- a/src/NServiceBus.Core/Transports/MessageContext.cs +++ b/src/NServiceBus.Core/Transports/MessageContext.cs @@ -34,7 +34,6 @@ public MessageContext(string nativeMessageId, Dictionary headers ReceiveAddress = receiveAddress; TransportTransaction = transportTransaction; - //Hack to be able to get the tags in the recoverability pipeline context.GetOrCreate(); } From e5b94d6286b44071db955ca753945770422aac3b Mon Sep 17 00:00:00 2001 From: sara pellegrini Date: Thu, 4 Jul 2024 16:08:29 +0200 Subject: [PATCH 67/76] Define IIncomingPipelineMetrics interface to allow the usage of NoOp implementation. --- .../Incoming/InvokeHandlerTerminatorTest.cs | 5 ++-- ...tReceiveToPhysicalMessageConnectorTests.cs | 3 ++- .../Incoming/IIncomingPipelineMetrics.cs | 17 +++++++++++++ .../Incoming/IncomingPipelineMetrics.cs | 2 +- .../Incoming/InvokeHandlerTerminator.cs | 5 ++-- .../Incoming/NoOpIncomingPipelineMetrics.cs | 24 +++++++++++++++++++ ...nsportReceiveToPhysicalMessageConnector.cs | 6 ++--- .../Pipeline/PipelineComponent.cs | 5 +++- .../Receiving/ReceiveComponent.cs | 2 +- 9 files changed, 57 insertions(+), 12 deletions(-) create mode 100644 src/NServiceBus.Core/Pipeline/Incoming/IIncomingPipelineMetrics.cs create mode 100644 src/NServiceBus.Core/Pipeline/Incoming/NoOpIncomingPipelineMetrics.cs diff --git a/src/NServiceBus.Core.Tests/Pipeline/Incoming/InvokeHandlerTerminatorTest.cs b/src/NServiceBus.Core.Tests/Pipeline/Incoming/InvokeHandlerTerminatorTest.cs index bf096ca5af8..f5892197e7c 100644 --- a/src/NServiceBus.Core.Tests/Pipeline/Incoming/InvokeHandlerTerminatorTest.cs +++ b/src/NServiceBus.Core.Tests/Pipeline/Incoming/InvokeHandlerTerminatorTest.cs @@ -1,9 +1,9 @@ namespace NServiceBus.Core.Tests.Pipeline.Incoming; using System; -using System.Collections.Generic; using System.Threading.Tasks; using NServiceBus.Pipeline; +using NServiceBus.Pipeline.Incoming; using NServiceBus.Sagas; using NUnit.Framework; using Testing; @@ -11,7 +11,8 @@ [TestFixture] public class InvokeHandlerTerminatorTest { - InvokeHandlerTerminator terminator = new(new NoOpActivityFactory()); + InvokeHandlerTerminator terminator = new(new NoOpActivityFactory(), + serviceProvider => new NoOpIncomingPipelineMetrics()); [Test] public async Task When_saga_found_and_handler_is_saga_should_invoke_handler() diff --git a/src/NServiceBus.Core.Tests/Reliability/Outbox/TransportReceiveToPhysicalMessageConnectorTests.cs b/src/NServiceBus.Core.Tests/Reliability/Outbox/TransportReceiveToPhysicalMessageConnectorTests.cs index 68e25aefaae..6d731127c44 100644 --- a/src/NServiceBus.Core.Tests/Reliability/Outbox/TransportReceiveToPhysicalMessageConnectorTests.cs +++ b/src/NServiceBus.Core.Tests/Reliability/Outbox/TransportReceiveToPhysicalMessageConnectorTests.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using NServiceBus.Outbox; using NServiceBus.Pipeline; +using NServiceBus.Pipeline.Incoming; using NServiceBus.Routing; using NUnit.Framework; using Settings; @@ -181,7 +182,7 @@ public void SetUp() fakeOutbox = new FakeOutboxStorage(); fakeBatchPipeline = new FakeBatchPipeline(); - behavior = new TransportReceiveToPhysicalMessageConnector(fakeOutbox, new IncomingPipelineMetrics(null, new SettingsHolder())); + behavior = new TransportReceiveToPhysicalMessageConnector(fakeOutbox, new NoOpIncomingPipelineMetrics()); } Task Invoke(ITransportReceiveContext context, Func next = null) => behavior.Invoke(context, next ?? (_ => Task.CompletedTask)); diff --git a/src/NServiceBus.Core/Pipeline/Incoming/IIncomingPipelineMetrics.cs b/src/NServiceBus.Core/Pipeline/Incoming/IIncomingPipelineMetrics.cs new file mode 100644 index 00000000000..bd7fdbc9b6c --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Incoming/IIncomingPipelineMetrics.cs @@ -0,0 +1,17 @@ +namespace NServiceBus; + +using System; +using Pipeline; + +interface IIncomingPipelineMetrics +{ + void AddDefaultIncomingPipelineMetricTags(IncomingPipelineMetricTags incomingPipelineMetricsTags); + void RecordMessageSuccessfullyProcessed(ITransportReceiveContext context, IncomingPipelineMetricTags incomingPipelineMetricTags); + void RecordMessageProcessingFailure(IncomingPipelineMetricTags incomingPipelineMetricTags, Exception error); + void RecordFetchedMessage(IncomingPipelineMetricTags incomingPipelineMetricTags); + void RecordSuccessfulMessageHandlerTime(IInvokeHandlerContext invokeHandlerContext, TimeSpan elapsed); + void RecordFailedMessageHandlerTime(IInvokeHandlerContext invokeHandlerContext, TimeSpan elapsed, Exception error); + void RecordImmediateRetry(IRecoverabilityContext recoverabilityContext); + void RecordDelayedRetry(IRecoverabilityContext recoverabilityContext); + void RecordSendToErrorQueue(IRecoverabilityContext recoverabilityContext); +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Incoming/IncomingPipelineMetrics.cs b/src/NServiceBus.Core/Pipeline/Incoming/IncomingPipelineMetrics.cs index 0a1c23a334e..6664e4866e2 100644 --- a/src/NServiceBus.Core/Pipeline/Incoming/IncomingPipelineMetrics.cs +++ b/src/NServiceBus.Core/Pipeline/Incoming/IncomingPipelineMetrics.cs @@ -7,7 +7,7 @@ using Pipeline; using Settings; -class IncomingPipelineMetrics +class IncomingPipelineMetrics : IIncomingPipelineMetrics { const string TotalProcessedSuccessfully = "nservicebus.messaging.successes"; const string TotalFetched = "nservicebus.messaging.fetches"; diff --git a/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs b/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs index d7acb5a0bbb..9633b8af0dd 100644 --- a/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs +++ b/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs @@ -3,11 +3,10 @@ using System; using System.Diagnostics; using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; using Pipeline; using Sagas; -class InvokeHandlerTerminator(IActivityFactory activityFactory) : PipelineTerminator +class InvokeHandlerTerminator(IActivityFactory activityFactory, Func incomingPipelineMetricsProvider) : PipelineTerminator { protected override async Task Terminate(IInvokeHandlerContext context) { @@ -22,7 +21,7 @@ protected override async Task Terminate(IInvokeHandlerContext context) // TODO: this is effectively using a provider pattern. It would be better to make that explicit allowing the // provider to return a NoOpMessagingMetricsMeters kind of meter - var messagingMetricsMeters = context.Builder.GetService(); + var messagingMetricsMeters = incomingPipelineMetricsProvider.Invoke(context.Builder); // Might as well abort before invoking the handler if we're shutting down context.CancellationToken.ThrowIfCancellationRequested(); diff --git a/src/NServiceBus.Core/Pipeline/Incoming/NoOpIncomingPipelineMetrics.cs b/src/NServiceBus.Core/Pipeline/Incoming/NoOpIncomingPipelineMetrics.cs new file mode 100644 index 00000000000..272118f795f --- /dev/null +++ b/src/NServiceBus.Core/Pipeline/Incoming/NoOpIncomingPipelineMetrics.cs @@ -0,0 +1,24 @@ +namespace NServiceBus.Pipeline.Incoming; + +using System; + +class NoOpIncomingPipelineMetrics : IIncomingPipelineMetrics +{ + public void AddDefaultIncomingPipelineMetricTags(IncomingPipelineMetricTags incomingPipelineMetricsTags) { } + + public void RecordMessageSuccessfullyProcessed(ITransportReceiveContext context, IncomingPipelineMetricTags incomingPipelineMetricTags) { } + + public void RecordMessageProcessingFailure(IncomingPipelineMetricTags incomingPipelineMetricTags, Exception error) { } + + public void RecordFetchedMessage(IncomingPipelineMetricTags incomingPipelineMetricTags) { } + + public void RecordSuccessfulMessageHandlerTime(IInvokeHandlerContext invokeHandlerContext, TimeSpan elapsed) { } + + public void RecordFailedMessageHandlerTime(IInvokeHandlerContext invokeHandlerContext, TimeSpan elapsed, Exception error) { } + + public void RecordImmediateRetry(IRecoverabilityContext recoverabilityContext) { } + + public void RecordDelayedRetry(IRecoverabilityContext recoverabilityContext) { } + + public void RecordSendToErrorQueue(IRecoverabilityContext recoverabilityContext) { } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Incoming/TransportReceiveToPhysicalMessageConnector.cs b/src/NServiceBus.Core/Pipeline/Incoming/TransportReceiveToPhysicalMessageConnector.cs index 0f284c08a40..5a36119eb6e 100644 --- a/src/NServiceBus.Core/Pipeline/Incoming/TransportReceiveToPhysicalMessageConnector.cs +++ b/src/NServiceBus.Core/Pipeline/Incoming/TransportReceiveToPhysicalMessageConnector.cs @@ -12,7 +12,7 @@ namespace NServiceBus; class TransportReceiveToPhysicalMessageConnector : IStageForkConnector { - public TransportReceiveToPhysicalMessageConnector(IOutboxStorage outboxStorage, IncomingPipelineMetrics incomingPipelineMetrics) + public TransportReceiveToPhysicalMessageConnector(IOutboxStorage outboxStorage, IIncomingPipelineMetrics incomingPipelineMetrics) { this.outboxStorage = outboxStorage; this.incomingPipelineMetrics = incomingPipelineMetrics; @@ -34,7 +34,7 @@ public async Task Invoke(ITransportReceiveContext context, Func(); + context.Extensions.TryGet(out IncomingPipelineMetricTags incomingPipelineMetricsTags); incomingPipelineMetrics.RecordMessageSuccessfullyProcessed(context, incomingPipelineMetricsTags); var outboxMessage = new OutboxMessage(messageId, ConvertToOutboxOperations(pendingTransportOperations.Operations)); @@ -135,5 +135,5 @@ static AddressTag DeserializeRoutingStrategy(Dictionary options) } readonly IOutboxStorage outboxStorage; - readonly IncomingPipelineMetrics incomingPipelineMetrics; + readonly IIncomingPipelineMetrics incomingPipelineMetrics; } diff --git a/src/NServiceBus.Core/Pipeline/PipelineComponent.cs b/src/NServiceBus.Core/Pipeline/PipelineComponent.cs index 8ed438e5394..b27be21c533 100644 --- a/src/NServiceBus.Core/Pipeline/PipelineComponent.cs +++ b/src/NServiceBus.Core/Pipeline/PipelineComponent.cs @@ -1,8 +1,10 @@ namespace NServiceBus; using System; +using System.Diagnostics.Metrics; using Microsoft.Extensions.DependencyInjection; using Pipeline; +using Settings; class PipelineComponent { @@ -26,7 +28,8 @@ public static PipelineComponent Initialize(PipelineSettings settings, HostingCom } // make the PipelineMetrics available to the Pipeline - hostingConfiguration.Services.AddSingleton(); + hostingConfiguration.Services.AddSingleton(sp => + new IncomingPipelineMetrics(sp.GetService(), sp.GetService())); return new PipelineComponent(modifications); } diff --git a/src/NServiceBus.Core/Receiving/ReceiveComponent.cs b/src/NServiceBus.Core/Receiving/ReceiveComponent.cs index 20a6d3c1b89..02e05a5dc94 100644 --- a/src/NServiceBus.Core/Receiving/ReceiveComponent.cs +++ b/src/NServiceBus.Core/Receiving/ReceiveComponent.cs @@ -71,7 +71,7 @@ public static ReceiveComponent Configure( return new LoadHandlersConnector(b.GetRequiredService()); }, "Gets all the handlers to invoke from the MessageHandler registry based on the message type."); - pipelineSettings.Register("InvokeHandlers", new InvokeHandlerTerminator(hostingConfiguration.ActivityFactory), "Calls the IHandleMessages.Handle(T)"); + pipelineSettings.Register("InvokeHandlers", new InvokeHandlerTerminator(hostingConfiguration.ActivityFactory, provider => provider.GetService()), "Calls the IHandleMessages.Handle(T)"); var handlerDiagnostics = new Dictionary>(); From ee5a050cf26114cf50ca3962bae54d89c997a0e1 Mon Sep 17 00:00:00 2001 From: sara pellegrini Date: Thu, 4 Jul 2024 16:19:41 +0200 Subject: [PATCH 68/76] Fix MainPipelineExecutorTests using No Op implementation for incoming pipeline metrics. --- .../Pipeline/MainPipelineExecutorTests.cs | 3 ++- src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/NServiceBus.Core.Tests/Pipeline/MainPipelineExecutorTests.cs b/src/NServiceBus.Core.Tests/Pipeline/MainPipelineExecutorTests.cs index 60ecd9f4dcf..d63a2dd2e4e 100644 --- a/src/NServiceBus.Core.Tests/Pipeline/MainPipelineExecutorTests.cs +++ b/src/NServiceBus.Core.Tests/Pipeline/MainPipelineExecutorTests.cs @@ -6,6 +6,7 @@ using Extensibility; using Microsoft.Extensions.DependencyInjection; using NServiceBus.Pipeline; +using NServiceBus.Pipeline.Incoming; using NUnit.Framework; using OpenTelemetry.Helpers; using Settings; @@ -120,7 +121,7 @@ static MainPipelineExecutor CreateMainPipelineExecutor(IPipeline(), receivePipeline, new ActivityFactory(), - new IncomingPipelineMetrics(null, new SettingsHolder())); + new NoOpIncomingPipelineMetrics()); return executor; } diff --git a/src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs b/src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs index 78d9f35660e..0e40b0bbc91 100644 --- a/src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs +++ b/src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs @@ -14,7 +14,7 @@ class MainPipelineExecutor( INotificationSubscriptions receivePipelineNotification, IPipeline receivePipeline, IActivityFactory activityFactory, - IncomingPipelineMetrics incomingPipelineMetrics) + IIncomingPipelineMetrics incomingPipelineMetrics) : IPipelineExecutor { public async Task Invoke(MessageContext messageContext, CancellationToken cancellationToken = default) From 764481f3a899e0da851509694ac2eabe0f86f538 Mon Sep 17 00:00:00 2001 From: sara pellegrini Date: Thu, 4 Jul 2024 16:21:35 +0200 Subject: [PATCH 69/76] Change NoOpIncomingPipelineMetrics namespace to NServiceBus --- .../Pipeline/Incoming/InvokeHandlerTerminatorTest.cs | 1 - .../Pipeline/MainPipelineExecutorTests.cs | 1 - .../Outbox/TransportReceiveToPhysicalMessageConnectorTests.cs | 1 - .../Pipeline/Incoming/NoOpIncomingPipelineMetrics.cs | 3 ++- 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/NServiceBus.Core.Tests/Pipeline/Incoming/InvokeHandlerTerminatorTest.cs b/src/NServiceBus.Core.Tests/Pipeline/Incoming/InvokeHandlerTerminatorTest.cs index f5892197e7c..bd1f7838c91 100644 --- a/src/NServiceBus.Core.Tests/Pipeline/Incoming/InvokeHandlerTerminatorTest.cs +++ b/src/NServiceBus.Core.Tests/Pipeline/Incoming/InvokeHandlerTerminatorTest.cs @@ -3,7 +3,6 @@ using System; using System.Threading.Tasks; using NServiceBus.Pipeline; -using NServiceBus.Pipeline.Incoming; using NServiceBus.Sagas; using NUnit.Framework; using Testing; diff --git a/src/NServiceBus.Core.Tests/Pipeline/MainPipelineExecutorTests.cs b/src/NServiceBus.Core.Tests/Pipeline/MainPipelineExecutorTests.cs index d63a2dd2e4e..13b91a1f4c5 100644 --- a/src/NServiceBus.Core.Tests/Pipeline/MainPipelineExecutorTests.cs +++ b/src/NServiceBus.Core.Tests/Pipeline/MainPipelineExecutorTests.cs @@ -6,7 +6,6 @@ using Extensibility; using Microsoft.Extensions.DependencyInjection; using NServiceBus.Pipeline; -using NServiceBus.Pipeline.Incoming; using NUnit.Framework; using OpenTelemetry.Helpers; using Settings; diff --git a/src/NServiceBus.Core.Tests/Reliability/Outbox/TransportReceiveToPhysicalMessageConnectorTests.cs b/src/NServiceBus.Core.Tests/Reliability/Outbox/TransportReceiveToPhysicalMessageConnectorTests.cs index 6d731127c44..dd7bb13985f 100644 --- a/src/NServiceBus.Core.Tests/Reliability/Outbox/TransportReceiveToPhysicalMessageConnectorTests.cs +++ b/src/NServiceBus.Core.Tests/Reliability/Outbox/TransportReceiveToPhysicalMessageConnectorTests.cs @@ -8,7 +8,6 @@ using System.Threading.Tasks; using NServiceBus.Outbox; using NServiceBus.Pipeline; -using NServiceBus.Pipeline.Incoming; using NServiceBus.Routing; using NUnit.Framework; using Settings; diff --git a/src/NServiceBus.Core/Pipeline/Incoming/NoOpIncomingPipelineMetrics.cs b/src/NServiceBus.Core/Pipeline/Incoming/NoOpIncomingPipelineMetrics.cs index 272118f795f..9e7ffb1ea95 100644 --- a/src/NServiceBus.Core/Pipeline/Incoming/NoOpIncomingPipelineMetrics.cs +++ b/src/NServiceBus.Core/Pipeline/Incoming/NoOpIncomingPipelineMetrics.cs @@ -1,6 +1,7 @@ -namespace NServiceBus.Pipeline.Incoming; +namespace NServiceBus; using System; +using Pipeline; class NoOpIncomingPipelineMetrics : IIncomingPipelineMetrics { From c63cdfbb6af04229f11de2d98fc2671a28c12c55 Mon Sep 17 00:00:00 2001 From: sara pellegrini Date: Fri, 5 Jul 2024 10:11:24 +0200 Subject: [PATCH 70/76] Mark IncomingPipelineMetrics as a required Singleton component in DI. --- ...endpoint_is_warmed_up.Make_sure_things_are_in_DI.approved.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/NServiceBus.AcceptanceTests/ApprovalFiles/When_endpoint_is_warmed_up.Make_sure_things_are_in_DI.approved.txt b/src/NServiceBus.AcceptanceTests/ApprovalFiles/When_endpoint_is_warmed_up.Make_sure_things_are_in_DI.approved.txt index f46eb014c93..c7b6e2d0f7d 100644 --- a/src/NServiceBus.AcceptanceTests/ApprovalFiles/When_endpoint_is_warmed_up.Make_sure_things_are_in_DI.approved.txt +++ b/src/NServiceBus.AcceptanceTests/ApprovalFiles/When_endpoint_is_warmed_up.Make_sure_things_are_in_DI.approved.txt @@ -16,6 +16,7 @@ NServiceBus.Pipeline.LogicalMessageFactory - Singleton NServiceBus.Settings.IReadOnlySettings - Singleton NServiceBus.Transport.ISubscriptionManager - Singleton ----------- Private registrations used by Core----------- +NServiceBus.IncomingPipelineMetrics - Singleton NServiceBus.InferredMessageTypeEnricherBehavior - Transient NServiceBus.SubscriptionReceiverBehavior - Transient NServiceBus.SubscriptionRouter - Singleton From 5b011a1c934cf121052e048b22572907b1d397a3 Mon Sep 17 00:00:00 2001 From: sara pellegrini Date: Fri, 5 Jul 2024 11:29:28 +0200 Subject: [PATCH 71/76] Simplify the constructor of InvokeHandlerTerminator by replacing the provider of IIncomingPipelineMetrics with the interface. --- .../Pipeline/Incoming/InvokeHandlerTerminatorTest.cs | 4 ++-- .../Pipeline/Incoming/InvokeHandlerTerminator.cs | 6 +----- src/NServiceBus.Core/Receiving/ReceiveComponent.cs | 2 +- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/NServiceBus.Core.Tests/Pipeline/Incoming/InvokeHandlerTerminatorTest.cs b/src/NServiceBus.Core.Tests/Pipeline/Incoming/InvokeHandlerTerminatorTest.cs index bd1f7838c91..0ec289447a6 100644 --- a/src/NServiceBus.Core.Tests/Pipeline/Incoming/InvokeHandlerTerminatorTest.cs +++ b/src/NServiceBus.Core.Tests/Pipeline/Incoming/InvokeHandlerTerminatorTest.cs @@ -1,6 +1,7 @@ namespace NServiceBus.Core.Tests.Pipeline.Incoming; using System; +using System.Diagnostics.Metrics; using System.Threading.Tasks; using NServiceBus.Pipeline; using NServiceBus.Sagas; @@ -10,8 +11,7 @@ [TestFixture] public class InvokeHandlerTerminatorTest { - InvokeHandlerTerminator terminator = new(new NoOpActivityFactory(), - serviceProvider => new NoOpIncomingPipelineMetrics()); + InvokeHandlerTerminator terminator = new(new NoOpActivityFactory(), new NoOpIncomingPipelineMetrics()); [Test] public async Task When_saga_found_and_handler_is_saga_should_invoke_handler() diff --git a/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs b/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs index 9633b8af0dd..d9e0453a0fb 100644 --- a/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs +++ b/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs @@ -6,7 +6,7 @@ using Pipeline; using Sagas; -class InvokeHandlerTerminator(IActivityFactory activityFactory, Func incomingPipelineMetricsProvider) : PipelineTerminator +class InvokeHandlerTerminator(IActivityFactory activityFactory, IIncomingPipelineMetrics messagingMetricsMeters) : PipelineTerminator { protected override async Task Terminate(IInvokeHandlerContext context) { @@ -19,10 +19,6 @@ protected override async Task Terminate(IInvokeHandlerContext context) var messageHandler = context.MessageHandler; - // TODO: this is effectively using a provider pattern. It would be better to make that explicit allowing the - // provider to return a NoOpMessagingMetricsMeters kind of meter - var messagingMetricsMeters = incomingPipelineMetricsProvider.Invoke(context.Builder); - // Might as well abort before invoking the handler if we're shutting down context.CancellationToken.ThrowIfCancellationRequested(); var startTime = DateTimeOffset.UtcNow; diff --git a/src/NServiceBus.Core/Receiving/ReceiveComponent.cs b/src/NServiceBus.Core/Receiving/ReceiveComponent.cs index 02e05a5dc94..74b9eb7d1be 100644 --- a/src/NServiceBus.Core/Receiving/ReceiveComponent.cs +++ b/src/NServiceBus.Core/Receiving/ReceiveComponent.cs @@ -71,7 +71,7 @@ public static ReceiveComponent Configure( return new LoadHandlersConnector(b.GetRequiredService()); }, "Gets all the handlers to invoke from the MessageHandler registry based on the message type."); - pipelineSettings.Register("InvokeHandlers", new InvokeHandlerTerminator(hostingConfiguration.ActivityFactory, provider => provider.GetService()), "Calls the IHandleMessages.Handle(T)"); + pipelineSettings.Register("InvokeHandlers", sp => new InvokeHandlerTerminator(hostingConfiguration.ActivityFactory, sp.GetService()), "Calls the IHandleMessages.Handle(T)"); var handlerDiagnostics = new Dictionary>(); From aa4230b72081d0ae2af1ef2c456531ac1d138596 Mon Sep 17 00:00:00 2001 From: sara pellegrini Date: Fri, 5 Jul 2024 14:52:19 +0200 Subject: [PATCH 72/76] Fix existing tests. --- ...terTagsTests.Verify_MeterTags.approved.txt | 12 --- .../MeterTests.Verify_MeterAPI.approved.txt | 5 +- .../Helpers/TestingMetricListener.cs | 6 +- .../OpenTelemetry/MeterTests.cs | 76 +++++++++---------- .../OpenTelemetry/TestMeterFactory.cs | 24 ++++++ .../Incoming/InvokeHandlerTerminatorTest.cs | 4 +- .../Pipeline/MainPipelineExecutorTests.cs | 4 +- ...tReceiveToPhysicalMessageConnectorTests.cs | 3 +- src/NServiceBus.Core/EndpointCreator.cs | 2 +- .../Incoming/IIncomingPipelineMetrics.cs | 17 ----- .../Incoming/IncomingPipelineMetrics.cs | 10 +-- .../Incoming/InvokeHandlerTerminator.cs | 2 +- .../Incoming/NoOpIncomingPipelineMetrics.cs | 25 ------ ...nsportReceiveToPhysicalMessageConnector.cs | 4 +- .../Pipeline/MainPipelineExecutor.cs | 2 +- .../Pipeline/PipelineComponent.cs | 11 ++- 16 files changed, 93 insertions(+), 114 deletions(-) delete mode 100644 src/NServiceBus.Core.Tests/ApprovalFiles/MeterTagsTests.Verify_MeterTags.approved.txt create mode 100644 src/NServiceBus.Core.Tests/OpenTelemetry/TestMeterFactory.cs delete mode 100644 src/NServiceBus.Core/Pipeline/Incoming/IIncomingPipelineMetrics.cs delete mode 100644 src/NServiceBus.Core/Pipeline/Incoming/NoOpIncomingPipelineMetrics.cs diff --git a/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTagsTests.Verify_MeterTags.approved.txt b/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTagsTests.Verify_MeterTags.approved.txt deleted file mode 100644 index 7accce2c001..00000000000 --- a/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTagsTests.Verify_MeterTags.approved.txt +++ /dev/null @@ -1,12 +0,0 @@ -{ - "Note": "Changes to meter tags should result in Meters version updates", - "ActivitySourceVersion": "0.2.0", - "Tags": [ - "EndpointDiscriminator => nservicebus.discriminator", - "QueueName => nservicebus.queue", - "MessageType => nservicebus.message_type", - "FailureType => nservicebus.failure_type", - "MessageHandlerTypes => nservicebus.message_handler_types" - "MessageHandlerType => nservicebus.message_handler_type" - ] -} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt b/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt index c173645f840..3ed86360831 100644 --- a/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt +++ b/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt @@ -16,6 +16,9 @@ "nservicebus.messaging.failures => Counter", "nservicebus.messaging.fetches => Counter", "nservicebus.messaging.handler_time => Histogram, Unit: s", - "nservicebus.messaging.successes => Counter" + "nservicebus.messaging.successes => Counter", + "nservicebus.recoverability.delayed => Counter", + "nservicebus.recoverability.error => Counter", + "nservicebus.recoverability.immediate => Counter" ] } \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/OpenTelemetry/Helpers/TestingMetricListener.cs b/src/NServiceBus.Core.Tests/OpenTelemetry/Helpers/TestingMetricListener.cs index a9e15433c54..08d3fafca80 100644 --- a/src/NServiceBus.Core.Tests/OpenTelemetry/Helpers/TestingMetricListener.cs +++ b/src/NServiceBus.Core.Tests/OpenTelemetry/Helpers/TestingMetricListener.cs @@ -10,6 +10,8 @@ namespace NServiceBus.AcceptanceTests.Core.OpenTelemetry.Metrics; class TestingMetricListener : IDisposable { readonly MeterListener meterListener; + public List metrics = []; + public string version = ""; public TestingMetricListener(string sourceName) { @@ -21,6 +23,8 @@ public TestingMetricListener(string sourceName) { TestContext.WriteLine($"Subscribing to {instrument.Meter.Name}\\{instrument.Name}"); listener.EnableMeasurementEvents(instrument); + metrics.Add(instrument); + version = instrument.Meter.Version; } } }; @@ -40,7 +44,7 @@ public TestingMetricListener(string sourceName) } public static TestingMetricListener SetupNServiceBusMetricsListener() => - SetupMetricsListener("NServiceBus.Core"); + SetupMetricsListener("NServiceBus.Core.Pipeline.Incoming"); public static TestingMetricListener SetupMetricsListener(string sourceName) { diff --git a/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs b/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs index 792cda6de4c..2732736a49d 100644 --- a/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs +++ b/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs @@ -1,41 +1,39 @@ -//namespace NServiceBus.Core.Tests.OpenTelemetry; +namespace NServiceBus.Core.Tests.OpenTelemetry; -//using System.Diagnostics.Metrics; -//using System.Linq; -//using System.Reflection; -//using NUnit.Framework; -//using Particular.Approvals; +using System.Diagnostics.Metrics; +using System.Linq; +using System.Reflection; +using AcceptanceTests.Core.OpenTelemetry.Metrics; +using Microsoft.Extensions.DependencyInjection; +using NUnit.Framework; +using Particular.Approvals; +using Settings; -//[TestFixture] -//public class MeterTests -//{ -// [Test] -// public void Verify_MeterAPI() -// { -// //TODO: Create a test IMeterFactory implementation -// // - Instantiate the *Metrics call to test -// // - Use the test IMeterFactory implementation to record calls -// // - Dump recorded call into a file and approve it - -// var meterTags = typeof(MeterTags) -// .GetFields(BindingFlags.Public | BindingFlags.Static) -// .Where(fi => fi.IsLiteral && !fi.IsInitOnly) -// .Select(x => x.GetRawConstantValue()) -// .OrderBy(value => value) -// .ToList(); -// var metrics = typeof(IncomingPipelineMetrics) -// .GetFields(BindingFlags.Static | BindingFlags.NonPublic) -// .Where(fi => typeof(Instrument).IsAssignableFrom(fi.FieldType)) -// .Select(fi => (Instrument)fi.GetValue(null)) -// .Select(x => $"{x.Name} => {x.GetType().Name.Split("`").First()}{(x.Unit == null ? "" : ", Unit: ")}{x.Unit ?? ""}") -// .OrderBy(value => value) -// .ToList(); -// Approver.Verify(new -// { -// Note = "Changes to metrics API should result in an update to NServiceBusMeter version.", -// ActivitySourceVersion = IncomingPipelineMetrics.NServiceBusMeter.Version, -// Tags = meterTags, -// Metrics = metrics -// }); -// } -//} \ No newline at end of file +[TestFixture] +public class MeterTests +{ + [Test] + public void Verify_MeterAPI() + { + var meterTags = typeof(MeterTags) + .GetFields(BindingFlags.Public | BindingFlags.Static) + .Where(fi => fi.IsLiteral && !fi.IsInitOnly) + .Select(x => x.GetRawConstantValue()) + .OrderBy(value => value) + .ToList(); + using var metricsListener = TestingMetricListener.SetupNServiceBusMetricsListener(); + //The IncomingPipelineMetrics constructor creates the meters, therefore a new instance before collecting the metrics. + new IncomingPipelineMetrics(new TestMeterFactory(), "queue", "disc"); + var metrics = metricsListener.metrics + .Select(x => $"{x.Name} => {x.GetType().Name.Split("`").First()}{(x.Unit == null ? "" : ", Unit: ")}{x.Unit ?? ""}") + .OrderBy(value => value) + .ToList(); + Approver.Verify(new + { + Note = "Changes to metrics API should result in an update to NServiceBusMeter version.", + ActivitySourceVersion = metricsListener.version, + Tags = meterTags, + Metrics = metrics + }); + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core.Tests/OpenTelemetry/TestMeterFactory.cs b/src/NServiceBus.Core.Tests/OpenTelemetry/TestMeterFactory.cs new file mode 100644 index 00000000000..8bbabf8795b --- /dev/null +++ b/src/NServiceBus.Core.Tests/OpenTelemetry/TestMeterFactory.cs @@ -0,0 +1,24 @@ +namespace NServiceBus.Core.Tests.OpenTelemetry; + +using System.Collections.Generic; +using System.Diagnostics.Metrics; + +class TestMeterFactory() : IMeterFactory +{ + List meters = []; + + public void Dispose() + { + foreach (Meter meter in meters) + { + meter.Dispose(); + } + } + + public Meter Create(MeterOptions options) + { + var meter = new Meter(options); + meters.Add(meter); + return meter; + } +} diff --git a/src/NServiceBus.Core.Tests/Pipeline/Incoming/InvokeHandlerTerminatorTest.cs b/src/NServiceBus.Core.Tests/Pipeline/Incoming/InvokeHandlerTerminatorTest.cs index 0ec289447a6..f1715defdc7 100644 --- a/src/NServiceBus.Core.Tests/Pipeline/Incoming/InvokeHandlerTerminatorTest.cs +++ b/src/NServiceBus.Core.Tests/Pipeline/Incoming/InvokeHandlerTerminatorTest.cs @@ -1,17 +1,17 @@ namespace NServiceBus.Core.Tests.Pipeline.Incoming; using System; -using System.Diagnostics.Metrics; using System.Threading.Tasks; using NServiceBus.Pipeline; using NServiceBus.Sagas; using NUnit.Framework; +using OpenTelemetry; using Testing; [TestFixture] public class InvokeHandlerTerminatorTest { - InvokeHandlerTerminator terminator = new(new NoOpActivityFactory(), new NoOpIncomingPipelineMetrics()); + InvokeHandlerTerminator terminator = new(new NoOpActivityFactory(), new IncomingPipelineMetrics(new TestMeterFactory(), "queue", "disc")); [Test] public async Task When_saga_found_and_handler_is_saga_should_invoke_handler() diff --git a/src/NServiceBus.Core.Tests/Pipeline/MainPipelineExecutorTests.cs b/src/NServiceBus.Core.Tests/Pipeline/MainPipelineExecutorTests.cs index 13b91a1f4c5..0b05c46af5d 100644 --- a/src/NServiceBus.Core.Tests/Pipeline/MainPipelineExecutorTests.cs +++ b/src/NServiceBus.Core.Tests/Pipeline/MainPipelineExecutorTests.cs @@ -7,8 +7,8 @@ using Microsoft.Extensions.DependencyInjection; using NServiceBus.Pipeline; using NUnit.Framework; +using OpenTelemetry; using OpenTelemetry.Helpers; -using Settings; using Transport; [TestFixture] @@ -120,7 +120,7 @@ static MainPipelineExecutor CreateMainPipelineExecutor(IPipeline(), receivePipeline, new ActivityFactory(), - new NoOpIncomingPipelineMetrics()); + new IncomingPipelineMetrics(new TestMeterFactory(), "queue", "disc")); return executor; } diff --git a/src/NServiceBus.Core.Tests/Reliability/Outbox/TransportReceiveToPhysicalMessageConnectorTests.cs b/src/NServiceBus.Core.Tests/Reliability/Outbox/TransportReceiveToPhysicalMessageConnectorTests.cs index dd7bb13985f..5c3e4bdd94d 100644 --- a/src/NServiceBus.Core.Tests/Reliability/Outbox/TransportReceiveToPhysicalMessageConnectorTests.cs +++ b/src/NServiceBus.Core.Tests/Reliability/Outbox/TransportReceiveToPhysicalMessageConnectorTests.cs @@ -10,6 +10,7 @@ using NServiceBus.Pipeline; using NServiceBus.Routing; using NUnit.Framework; +using OpenTelemetry; using Settings; using Testing; using Transport; @@ -181,7 +182,7 @@ public void SetUp() fakeOutbox = new FakeOutboxStorage(); fakeBatchPipeline = new FakeBatchPipeline(); - behavior = new TransportReceiveToPhysicalMessageConnector(fakeOutbox, new NoOpIncomingPipelineMetrics()); + behavior = new TransportReceiveToPhysicalMessageConnector(fakeOutbox, new IncomingPipelineMetrics(new TestMeterFactory(), "queue", "disc")); } Task Invoke(ITransportReceiveContext context, Func next = null) => behavior.Invoke(context, next ?? (_ => Task.CompletedTask)); diff --git a/src/NServiceBus.Core/EndpointCreator.cs b/src/NServiceBus.Core/EndpointCreator.cs index ff5f176b495..5d731e149d2 100644 --- a/src/NServiceBus.Core/EndpointCreator.cs +++ b/src/NServiceBus.Core/EndpointCreator.cs @@ -99,7 +99,7 @@ void Configure() hostingConfiguration, pipelineSettings); - pipelineComponent = PipelineComponent.Initialize(pipelineSettings, hostingConfiguration); + pipelineComponent = PipelineComponent.Initialize(pipelineSettings, hostingConfiguration, receiveConfiguration); // The settings can only be locked after initializing the feature component since it uses the settings to store & share feature state. // As well as all the other components have been initialized diff --git a/src/NServiceBus.Core/Pipeline/Incoming/IIncomingPipelineMetrics.cs b/src/NServiceBus.Core/Pipeline/Incoming/IIncomingPipelineMetrics.cs deleted file mode 100644 index bd7fdbc9b6c..00000000000 --- a/src/NServiceBus.Core/Pipeline/Incoming/IIncomingPipelineMetrics.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace NServiceBus; - -using System; -using Pipeline; - -interface IIncomingPipelineMetrics -{ - void AddDefaultIncomingPipelineMetricTags(IncomingPipelineMetricTags incomingPipelineMetricsTags); - void RecordMessageSuccessfullyProcessed(ITransportReceiveContext context, IncomingPipelineMetricTags incomingPipelineMetricTags); - void RecordMessageProcessingFailure(IncomingPipelineMetricTags incomingPipelineMetricTags, Exception error); - void RecordFetchedMessage(IncomingPipelineMetricTags incomingPipelineMetricTags); - void RecordSuccessfulMessageHandlerTime(IInvokeHandlerContext invokeHandlerContext, TimeSpan elapsed); - void RecordFailedMessageHandlerTime(IInvokeHandlerContext invokeHandlerContext, TimeSpan elapsed, Exception error); - void RecordImmediateRetry(IRecoverabilityContext recoverabilityContext); - void RecordDelayedRetry(IRecoverabilityContext recoverabilityContext); - void RecordSendToErrorQueue(IRecoverabilityContext recoverabilityContext); -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Incoming/IncomingPipelineMetrics.cs b/src/NServiceBus.Core/Pipeline/Incoming/IncomingPipelineMetrics.cs index 6664e4866e2..6db8c09f64b 100644 --- a/src/NServiceBus.Core/Pipeline/Incoming/IncomingPipelineMetrics.cs +++ b/src/NServiceBus.Core/Pipeline/Incoming/IncomingPipelineMetrics.cs @@ -5,9 +5,8 @@ using System.Diagnostics; using System.Diagnostics.Metrics; using Pipeline; -using Settings; -class IncomingPipelineMetrics : IIncomingPipelineMetrics +class IncomingPipelineMetrics { const string TotalProcessedSuccessfully = "nservicebus.messaging.successes"; const string TotalFetched = "nservicebus.messaging.fetches"; @@ -18,7 +17,7 @@ class IncomingPipelineMetrics : IIncomingPipelineMetrics const string RecoverabilityDelayed = "nservicebus.recoverability.delayed"; const string RecoverabilityError = "nservicebus.recoverability.error"; - public IncomingPipelineMetrics(IMeterFactory meterFactory, IReadOnlySettings settings) + public IncomingPipelineMetrics(IMeterFactory meterFactory, string queueName, string discriminator) { var meter = meterFactory.Create("NServiceBus.Core.Pipeline.Incoming", "0.2.0"); totalProcessedSuccessfully = meter.CreateCounter(TotalProcessedSuccessfully, @@ -38,9 +37,8 @@ public IncomingPipelineMetrics(IMeterFactory meterFactory, IReadOnlySettings set totalSentToErrorQueue = meter.CreateCounter(RecoverabilityError, description: "Total number of messages sent to the error queue."); - var config = settings.Get(); - queueNameBase = config.QueueNameBase; - endpointDiscriminator = config.InstanceSpecificQueueAddress?.Discriminator ?? ""; + queueNameBase = queueName; + endpointDiscriminator = discriminator; } public void AddDefaultIncomingPipelineMetricTags(IncomingPipelineMetricTags incomingPipelineMetricsTags) diff --git a/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs b/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs index d9e0453a0fb..1d5538eb7ab 100644 --- a/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs +++ b/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs @@ -6,7 +6,7 @@ using Pipeline; using Sagas; -class InvokeHandlerTerminator(IActivityFactory activityFactory, IIncomingPipelineMetrics messagingMetricsMeters) : PipelineTerminator +class InvokeHandlerTerminator(IActivityFactory activityFactory, IncomingPipelineMetrics messagingMetricsMeters) : PipelineTerminator { protected override async Task Terminate(IInvokeHandlerContext context) { diff --git a/src/NServiceBus.Core/Pipeline/Incoming/NoOpIncomingPipelineMetrics.cs b/src/NServiceBus.Core/Pipeline/Incoming/NoOpIncomingPipelineMetrics.cs deleted file mode 100644 index 9e7ffb1ea95..00000000000 --- a/src/NServiceBus.Core/Pipeline/Incoming/NoOpIncomingPipelineMetrics.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace NServiceBus; - -using System; -using Pipeline; - -class NoOpIncomingPipelineMetrics : IIncomingPipelineMetrics -{ - public void AddDefaultIncomingPipelineMetricTags(IncomingPipelineMetricTags incomingPipelineMetricsTags) { } - - public void RecordMessageSuccessfullyProcessed(ITransportReceiveContext context, IncomingPipelineMetricTags incomingPipelineMetricTags) { } - - public void RecordMessageProcessingFailure(IncomingPipelineMetricTags incomingPipelineMetricTags, Exception error) { } - - public void RecordFetchedMessage(IncomingPipelineMetricTags incomingPipelineMetricTags) { } - - public void RecordSuccessfulMessageHandlerTime(IInvokeHandlerContext invokeHandlerContext, TimeSpan elapsed) { } - - public void RecordFailedMessageHandlerTime(IInvokeHandlerContext invokeHandlerContext, TimeSpan elapsed, Exception error) { } - - public void RecordImmediateRetry(IRecoverabilityContext recoverabilityContext) { } - - public void RecordDelayedRetry(IRecoverabilityContext recoverabilityContext) { } - - public void RecordSendToErrorQueue(IRecoverabilityContext recoverabilityContext) { } -} \ No newline at end of file diff --git a/src/NServiceBus.Core/Pipeline/Incoming/TransportReceiveToPhysicalMessageConnector.cs b/src/NServiceBus.Core/Pipeline/Incoming/TransportReceiveToPhysicalMessageConnector.cs index 5a36119eb6e..6f0818a3bc0 100644 --- a/src/NServiceBus.Core/Pipeline/Incoming/TransportReceiveToPhysicalMessageConnector.cs +++ b/src/NServiceBus.Core/Pipeline/Incoming/TransportReceiveToPhysicalMessageConnector.cs @@ -12,7 +12,7 @@ namespace NServiceBus; class TransportReceiveToPhysicalMessageConnector : IStageForkConnector { - public TransportReceiveToPhysicalMessageConnector(IOutboxStorage outboxStorage, IIncomingPipelineMetrics incomingPipelineMetrics) + public TransportReceiveToPhysicalMessageConnector(IOutboxStorage outboxStorage, IncomingPipelineMetrics incomingPipelineMetrics) { this.outboxStorage = outboxStorage; this.incomingPipelineMetrics = incomingPipelineMetrics; @@ -135,5 +135,5 @@ static AddressTag DeserializeRoutingStrategy(Dictionary options) } readonly IOutboxStorage outboxStorage; - readonly IIncomingPipelineMetrics incomingPipelineMetrics; + readonly IncomingPipelineMetrics incomingPipelineMetrics; } diff --git a/src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs b/src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs index 0e40b0bbc91..78d9f35660e 100644 --- a/src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs +++ b/src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs @@ -14,7 +14,7 @@ class MainPipelineExecutor( INotificationSubscriptions receivePipelineNotification, IPipeline receivePipeline, IActivityFactory activityFactory, - IIncomingPipelineMetrics incomingPipelineMetrics) + IncomingPipelineMetrics incomingPipelineMetrics) : IPipelineExecutor { public async Task Invoke(MessageContext messageContext, CancellationToken cancellationToken = default) diff --git a/src/NServiceBus.Core/Pipeline/PipelineComponent.cs b/src/NServiceBus.Core/Pipeline/PipelineComponent.cs index b27be21c533..78360c8ce16 100644 --- a/src/NServiceBus.Core/Pipeline/PipelineComponent.cs +++ b/src/NServiceBus.Core/Pipeline/PipelineComponent.cs @@ -13,7 +13,8 @@ class PipelineComponent this.modifications = modifications; } - public static PipelineComponent Initialize(PipelineSettings settings, HostingComponent.Configuration hostingConfiguration) + public static PipelineComponent Initialize(PipelineSettings settings, + HostingComponent.Configuration hostingConfiguration, ReceiveComponent.Configuration receiveConfiguration) { var modifications = settings.modifications; @@ -28,8 +29,12 @@ public static PipelineComponent Initialize(PipelineSettings settings, HostingCom } // make the PipelineMetrics available to the Pipeline - hostingConfiguration.Services.AddSingleton(sp => - new IncomingPipelineMetrics(sp.GetService(), sp.GetService())); + hostingConfiguration.Services.AddSingleton(sp => + { + var meterFactory = sp.GetService(); + string discriminator = receiveConfiguration.InstanceSpecificQueueAddress?.Discriminator ?? ""; + return new IncomingPipelineMetrics(meterFactory, receiveConfiguration.QueueNameBase, discriminator); + }); return new PipelineComponent(modifications); } From abfa21ff804252ae66116b48b9b5bd2c3bbc9bbb Mon Sep 17 00:00:00 2001 From: SzymonPobiega Date: Sat, 6 Jul 2024 15:48:38 +0200 Subject: [PATCH 73/76] Fix the build --- src/NServiceBus.Core/Pipeline/PipelineComponent.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/NServiceBus.Core/Pipeline/PipelineComponent.cs b/src/NServiceBus.Core/Pipeline/PipelineComponent.cs index 78360c8ce16..97d2641b355 100644 --- a/src/NServiceBus.Core/Pipeline/PipelineComponent.cs +++ b/src/NServiceBus.Core/Pipeline/PipelineComponent.cs @@ -4,7 +4,6 @@ namespace NServiceBus; using System.Diagnostics.Metrics; using Microsoft.Extensions.DependencyInjection; using Pipeline; -using Settings; class PipelineComponent { From 81ede7269b8fbe72c1bac3f7060e8e4bd7cda823 Mon Sep 17 00:00:00 2001 From: SzymonPobiega Date: Sun, 7 Jul 2024 22:16:12 +0200 Subject: [PATCH 74/76] Fix inspection --- src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs b/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs index 2732736a49d..4cbf11c4cf0 100644 --- a/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs +++ b/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs @@ -1,13 +1,10 @@ namespace NServiceBus.Core.Tests.OpenTelemetry; -using System.Diagnostics.Metrics; using System.Linq; using System.Reflection; using AcceptanceTests.Core.OpenTelemetry.Metrics; -using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; using Particular.Approvals; -using Settings; [TestFixture] public class MeterTests From e3e158bedfa181a3b47dd2b9f320e8613eefd8a4 Mon Sep 17 00:00:00 2001 From: SzymonPobiega Date: Sun, 7 Jul 2024 22:36:09 +0200 Subject: [PATCH 75/76] Fix inspection --- src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs b/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs index 4cbf11c4cf0..13ab01938e2 100644 --- a/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs +++ b/src/NServiceBus.Core.Tests/OpenTelemetry/MeterTests.cs @@ -20,7 +20,9 @@ public void Verify_MeterAPI() .ToList(); using var metricsListener = TestingMetricListener.SetupNServiceBusMetricsListener(); //The IncomingPipelineMetrics constructor creates the meters, therefore a new instance before collecting the metrics. +#pragma warning disable CA1806 new IncomingPipelineMetrics(new TestMeterFactory(), "queue", "disc"); +#pragma warning restore CA1806 var metrics = metricsListener.metrics .Select(x => $"{x.Name} => {x.GetType().Name.Split("`").First()}{(x.Unit == null ? "" : ", Unit: ")}{x.Unit ?? ""}") .OrderBy(value => value) From c73f9e017896df0413c67355c31c1c89e9644720 Mon Sep 17 00:00:00 2001 From: sara pellegrini Date: Mon, 8 Jul 2024 14:13:03 +0200 Subject: [PATCH 76/76] Replace failure type tag with error type tag. --- .../OpenTelemetry/Metrics/When_message_processing_fails.cs | 2 +- .../ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt | 1 - src/NServiceBus.Core/OpenTelemetry/Metrics/MeterTags.cs | 1 - .../Pipeline/Incoming/IncomingPipelineMetrics.cs | 6 +++++- .../Recoverability/RecoverabilityRoutingConnector.cs | 7 ------- 5 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/Metrics/When_message_processing_fails.cs b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/Metrics/When_message_processing_fails.cs index c5655566a85..7179c24e2d1 100644 --- a/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/Metrics/When_message_processing_fails.cs +++ b/src/NServiceBus.AcceptanceTests/Core/OpenTelemetry/Metrics/When_message_processing_fails.cs @@ -29,7 +29,7 @@ public async Task Should_report_failing_message_metrics() { ["nservicebus.queue"] = Conventions.EndpointNamingConvention(typeof(FailingEndpoint)), ["nservicebus.discriminator"] = "disc", - ["nservicebus.failure_type"] = typeof(SimulatedException).FullName, + ["error.type"] = typeof(SimulatedException).FullName, }); } diff --git a/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt b/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt index 3ed86360831..331b25092e3 100644 --- a/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt +++ b/src/NServiceBus.Core.Tests/ApprovalFiles/MeterTests.Verify_MeterAPI.approved.txt @@ -5,7 +5,6 @@ "error.type", "execution.result", "nservicebus.discriminator", - "nservicebus.failure_type", "nservicebus.message_handler_type", "nservicebus.message_handler_types", "nservicebus.message_type", diff --git a/src/NServiceBus.Core/OpenTelemetry/Metrics/MeterTags.cs b/src/NServiceBus.Core/OpenTelemetry/Metrics/MeterTags.cs index aad3c33b8da..5db35d1fe52 100644 --- a/src/NServiceBus.Core/OpenTelemetry/Metrics/MeterTags.cs +++ b/src/NServiceBus.Core/OpenTelemetry/Metrics/MeterTags.cs @@ -5,7 +5,6 @@ static class MeterTags public const string EndpointDiscriminator = "nservicebus.discriminator"; public const string QueueName = "nservicebus.queue"; public const string MessageType = "nservicebus.message_type"; - public const string FailureType = "nservicebus.failure_type"; public const string MessageHandlerTypes = "nservicebus.message_handler_types"; public const string MessageHandlerType = "nservicebus.message_handler_type"; public const string ExecutionResult = "execution.result"; diff --git a/src/NServiceBus.Core/Pipeline/Incoming/IncomingPipelineMetrics.cs b/src/NServiceBus.Core/Pipeline/Incoming/IncomingPipelineMetrics.cs index 6db8c09f64b..5f676e2ada4 100644 --- a/src/NServiceBus.Core/Pipeline/Incoming/IncomingPipelineMetrics.cs +++ b/src/NServiceBus.Core/Pipeline/Incoming/IncomingPipelineMetrics.cs @@ -55,6 +55,7 @@ public void RecordMessageSuccessfullyProcessed(ITransportReceiveContext context, } TagList tags; + tags.Add(new(MeterTags.ExecutionResult, "success")); incomingPipelineMetricTags.ApplyTags(ref tags, [ MeterTags.QueueName, MeterTags.EndpointDiscriminator, @@ -86,13 +87,16 @@ public void RecordMessageProcessingFailure(IncomingPipelineMetricTags incomingPi } TagList tags; - tags.Add(new(MeterTags.FailureType, error.GetType().FullName)); + tags.Add(new(MeterTags.ErrorType, error.GetType().FullName)); + tags.Add(new(MeterTags.ExecutionResult, "failure")); incomingPipelineMetricTags.ApplyTags(ref tags, [ MeterTags.QueueName, MeterTags.EndpointDiscriminator, MeterTags.MessageType, MeterTags.MessageHandlerTypes]); totalFailures.Add(1, tags); + + // the critical time is intentionally not recorded in case of failure } public void RecordFetchedMessage(IncomingPipelineMetricTags incomingPipelineMetricTags) diff --git a/src/NServiceBus.Core/Recoverability/RecoverabilityRoutingConnector.cs b/src/NServiceBus.Core/Recoverability/RecoverabilityRoutingConnector.cs index b6ef6c5de41..a383541a84d 100644 --- a/src/NServiceBus.Core/Recoverability/RecoverabilityRoutingConnector.cs +++ b/src/NServiceBus.Core/Recoverability/RecoverabilityRoutingConnector.cs @@ -1,7 +1,6 @@ namespace NServiceBus; using System; -using System.Diagnostics; using System.Threading.Tasks; using Pipeline; @@ -22,7 +21,6 @@ public RecoverabilityRoutingConnector( public override async Task Invoke(IRecoverabilityContext context, Func stage) { - var availableMetricTags = context.Extensions.Get(); var recoverabilityActionContext = context.PreventChanges(); var recoverabilityAction = context.RecoverabilityAction; var routingContexts = recoverabilityAction @@ -33,11 +31,6 @@ public override async Task Invoke(IRecoverabilityContext context, Func