diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Customizations/Models/RemoteDependencyData.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Customizations/Models/RemoteDependencyData.cs index 5d08c465e60e0..2de3e51bdbf8d 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Customizations/Models/RemoteDependencyData.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Customizations/Models/RemoteDependencyData.cs @@ -55,20 +55,7 @@ public RemoteDependencyData(int version, Activity activity, ref ActivityTagsProc if (activityTagsProcessor.AzureNamespace != null) { - if (activity.Kind == ActivityKind.Internal) - { - Type = $"InProc | {activityTagsProcessor.AzureNamespace}"; - } - else if (activity.Kind == ActivityKind.Producer) - { - Type = $"Queue Message | {activityTagsProcessor.AzureNamespace}"; - } - else - { - // The Azure SDK sets az.namespace with its resource provider information. - // When ActivityKind is not internal and az.namespace is present, set the value of Type to az.namespace. - Type = activityTagsProcessor.AzureNamespace ?? Type; - } + Type = TraceHelper.GetAzureSDKDependencyType(activity.Kind, activityTagsProcessor.AzureNamespace); } else if (activity.Kind == ActivityKind.Internal) { diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/AzMonListExtensions.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/AzMonListExtensions.cs index b6d546b2a7b16..77c86cb76ee14 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/AzMonListExtensions.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/AzMonListExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Diagnostics; using System.Runtime.CompilerServices; using Azure.Monitor.OpenTelemetry.Exporter.Models; @@ -344,6 +345,8 @@ internal static (string? DbName, string? DbTarget) GetDbDependencyTargetAndName( return tagObjects.GetHttpDependencyTarget(); case OperationType.Db: return tagObjects.GetDbDependencyTargetAndName().DbTarget; + case OperationType.Messaging: + return tagObjects.GetMessagingUrlAndSourceOrTarget(ActivityKind.Producer).SourceOrTarget; default: return null; } diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/StandardMetricsExtractionProcessor.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/StandardMetricsExtractionProcessor.cs index e30c78cfdf4fb..8866e12026952 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/StandardMetricsExtractionProcessor.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/StandardMetricsExtractionProcessor.cs @@ -42,7 +42,7 @@ internal StandardMetricsExtractionProcessor(AzureMonitorMetricExporter metricExp public override void OnEnd(Activity activity) { - if (activity.Kind == ActivityKind.Server) + if (activity.Kind == ActivityKind.Server || activity.Kind == ActivityKind.Consumer) { if (_requestDuration.Enabled) { @@ -50,7 +50,7 @@ public override void OnEnd(Activity activity) ReportRequestDurationMetric(activity); } } - if (activity.Kind == ActivityKind.Client || activity.Kind == ActivityKind.Internal) + if (activity.Kind == ActivityKind.Client || activity.Kind == ActivityKind.Internal || activity.Kind == ActivityKind.Producer) { if (_dependencyDuration.Enabled) { @@ -58,9 +58,6 @@ public override void OnEnd(Activity activity) ReportDependencyDurationMetric(activity); } } - - // TODO: other activity kinds - // (2023-07) fix before GA } private void ReportRequestDurationMetric(Activity activity) @@ -110,7 +107,15 @@ private void ReportDependencyDurationMetric(Activity activity) statusCode = AzMonList.GetTagValue(ref activityTagsProcessor.MappedTags, SemanticConventions.AttributeHttpStatusCode)?.ToString(); } - var dependencyType = activityTagsProcessor.MappedTags.GetDependencyType(activityTagsProcessor.activityType); + string? dependencyType; + if (activityTagsProcessor.AzureNamespace != null) + { + dependencyType = TraceHelper.GetAzureSDKDependencyType(activity.Kind, activityTagsProcessor.AzureNamespace); + } + else + { + dependencyType = activity.Kind == ActivityKind.Internal ? "InProc" : activityTagsProcessor.MappedTags.GetDependencyType(activityTagsProcessor.activityType); + } TagList tags = default; tags.Add(new KeyValuePair(StandardMetricConstants.DependencyTargetKey, dependencyTarget)); diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/TraceHelper.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/TraceHelper.cs index 63e92667a6ccc..59d3569905c45 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/TraceHelper.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/TraceHelper.cs @@ -404,5 +404,24 @@ private static void AddContextToMSLinks(StringBuilder linksJson, ActivityLink li linksJson .Append("},"); } + + internal static string GetAzureSDKDependencyType(ActivityKind kind, string azureNamespace) + { + // TODO: see if the values can be cached to avoid allocation. + if (kind == ActivityKind.Internal) + { + return $"InProc | {azureNamespace}"; + } + else if (kind == ActivityKind.Producer) + { + return $"Queue Message | {azureNamespace}"; + } + else + { + // The Azure SDK sets az.namespace with its resource provider information. + // When ActivityKind is not internal and az.namespace is present, set the value of Type to az.namespace. + return azureNamespace; + } + } } } diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/StandardMetricTests.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/StandardMetricTests.cs index 9d4a76d77d988..8bbb12c9d5cf7 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/StandardMetricTests.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/StandardMetricTests.cs @@ -118,7 +118,61 @@ public void ValidateRequestDurationMetricNew() } [Fact] - public void ValidateDependencyDurationMetric() + public void ValidateRequestDurationMetricConsumerKind() + { + var activitySource = new ActivitySource(nameof(StandardMetricTests.ValidateRequestDurationMetricConsumerKind)); + var traceTelemetryItems = new List(); + var metricTelemetryItems = new List(); + + var standardMetricCustomProcessor = new StandardMetricsExtractionProcessor(new AzureMonitorMetricExporter(new MockTransmitter(metricTelemetryItems))); + + var traceServiceName = new KeyValuePair("service.name", "trace.service"); + var resourceAttributes = new KeyValuePair[] { traceServiceName }; + + using var tracerProvider = Sdk.CreateTracerProviderBuilder() + .SetSampler(new AlwaysOnSampler()) + .SetResourceBuilder(ResourceBuilder.CreateDefault().AddAttributes(resourceAttributes)) + .AddSource(nameof(StandardMetricTests.ValidateRequestDurationMetricConsumerKind)) + .AddProcessor(standardMetricCustomProcessor) + .AddProcessor(new BatchActivityExportProcessor(new AzureMonitorTraceExporter(new AzureMonitorExporterOptions(), new MockTransmitter(traceTelemetryItems)))) + .Build(); + + using (var activity = activitySource.StartActivity("Test", ActivityKind.Consumer)) + { + activity?.SetTag(SemanticConventions.AttributeMessagingSystem, "messagingsystem"); + activity?.SetTag(SemanticConventions.AttributeServerAddress, "localhost"); + activity?.SetTag(SemanticConventions.AttributeMessagingDestinationName, "destination"); + activity?.SetStatus(ActivityStatusCode.Ok); + } + + tracerProvider?.ForceFlush(); + + WaitForActivityExport(traceTelemetryItems); + + standardMetricCustomProcessor._meterProvider?.ForceFlush(); + + Assert.Single(metricTelemetryItems); + + var metricTelemetry = metricTelemetryItems.Last()!; + Assert.Equal("MetricData", metricTelemetry.Data.BaseType); + var metricData = (MetricsData)metricTelemetry.Data.BaseData; + Assert.True(metricData.Properties.TryGetValue(StandardMetricConstants.RequestSuccessKey, out var isSuccess)); + Assert.Equal("True", isSuccess); + Assert.True(metricData.Properties.TryGetValue(StandardMetricConstants.RequestResultCodeKey, out var resultCode)); + Assert.Equal("0", resultCode); + Assert.True(metricData.Properties.TryGetValue(StandardMetricConstants.IsAutoCollectedKey, out var isAutoCollectedFlag)); + Assert.Equal("True", isAutoCollectedFlag); + Assert.True(metricData.Properties.TryGetValue(StandardMetricConstants.CloudRoleInstanceKey, out _)); + Assert.True(metricData.Properties.TryGetValue(StandardMetricConstants.CloudRoleNameKey, out var cloudRoleName)); + Assert.Equal("trace.service", cloudRoleName); + Assert.True(metricData.Properties.TryGetValue(StandardMetricConstants.MetricIdKey, out var metricId)); + Assert.Equal(StandardMetricConstants.RequestDurationMetricIdValue, metricId); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void ValidateDependencyDurationMetric(bool isAzureSDK) { var activitySource = new ActivitySource(nameof(StandardMetricTests.ValidateDependencyDurationMetric)); var traceTelemetryItems = new List(); @@ -139,6 +193,10 @@ public void ValidateDependencyDurationMetric() using (var activity = activitySource.StartActivity("Test", ActivityKind.Client)) { + if (isAzureSDK) + { + activity?.SetTag(SemanticConventions.AttributeAzureNameSpace, "aznamespace"); + } activity?.SetTag(SemanticConventions.AttributeHttpStatusCode, 200); activity?.SetTag(SemanticConventions.AttributeHttpMethod, "Get"); activity?.SetTag(SemanticConventions.AttributeHttpUrl, "https://www.foo.com"); @@ -167,13 +225,91 @@ public void ValidateDependencyDurationMetric() Assert.True(metricData.Properties.TryGetValue(StandardMetricConstants.MetricIdKey, out var metricId)); Assert.Equal(StandardMetricConstants.DependencyDurationMetricIdValue, metricId); Assert.True(metricData.Properties.TryGetValue(StandardMetricConstants.DependencyTypeKey, out var dependencyType)); - Assert.Equal("Http", dependencyType); + if (isAzureSDK) + { + Assert.Equal("aznamespace", dependencyType); + } + else + { + Assert.Equal("Http", dependencyType); + } + Assert.True(metricData.Properties.TryGetValue(StandardMetricConstants.DependencyTargetKey, out var dependencyTarget)); Assert.Equal("www.foo.com", dependencyTarget); } - [Fact] - public void ValidateDependencyDurationMetricNew() + [Theory] + [InlineData(true)] + [InlineData(false)] + public void ValidateDependencyDurationMetricForProducerKind(bool isAzureSDKSpan) + { + var activitySource = new ActivitySource(nameof(StandardMetricTests.ValidateDependencyDurationMetricForProducerKind)); + var traceTelemetryItems = new List(); + var metricTelemetryItems = new List(); + + var standardMetricCustomProcessor = new StandardMetricsExtractionProcessor(new AzureMonitorMetricExporter(new MockTransmitter(metricTelemetryItems))); + + var traceServiceName = new KeyValuePair("service.name", "trace.service"); + var resourceAttributes = new KeyValuePair[] { traceServiceName }; + + using var tracerProvider = Sdk.CreateTracerProviderBuilder() + .SetSampler(new AlwaysOnSampler()) + .SetResourceBuilder(ResourceBuilder.CreateDefault().AddAttributes(resourceAttributes)) + .AddSource(nameof(StandardMetricTests.ValidateDependencyDurationMetricForProducerKind)) + .AddProcessor(standardMetricCustomProcessor) + .AddProcessor(new BatchActivityExportProcessor(new AzureMonitorTraceExporter(new AzureMonitorExporterOptions(), new MockTransmitter(traceTelemetryItems)))) + .Build(); + + using (var activity = activitySource.StartActivity("Test", ActivityKind.Producer)) + { + if (isAzureSDKSpan) + { + activity?.SetTag(SemanticConventions.AttributeAzureNameSpace, "aznamespace"); + } + activity?.SetTag(SemanticConventions.AttributeMessagingSystem, "messagingsystem"); + activity?.SetTag(SemanticConventions.AttributeServerAddress, "localhost"); + activity?.SetTag(SemanticConventions.AttributeMessagingDestinationName, "destination"); + } + + tracerProvider?.ForceFlush(); + + WaitForActivityExport(traceTelemetryItems); + + standardMetricCustomProcessor._meterProvider?.ForceFlush(); + + Assert.Single(metricTelemetryItems); + + var metricTelemetry = metricTelemetryItems.Last()!; + Assert.Equal("MetricData", metricTelemetry.Data.BaseType); + var metricData = (MetricsData)metricTelemetry.Data.BaseData; + Assert.True(metricData.Properties.TryGetValue(StandardMetricConstants.DependencySuccessKey, out var isSuccess)); + Assert.Equal("True", isSuccess); + Assert.True(metricData.Properties.TryGetValue(StandardMetricConstants.DependencyResultCodeKey, out var resultCode)); + Assert.Equal("0", resultCode); + Assert.True(metricData.Properties.TryGetValue(StandardMetricConstants.IsAutoCollectedKey, out var isAutoCollectedFlag)); + Assert.Equal("True", isAutoCollectedFlag); + Assert.True(metricData.Properties.TryGetValue(StandardMetricConstants.CloudRoleInstanceKey, out _)); + Assert.True(metricData.Properties.TryGetValue(StandardMetricConstants.CloudRoleNameKey, out var cloudRoleName)); + Assert.Equal("trace.service", cloudRoleName); + Assert.True(metricData.Properties.TryGetValue(StandardMetricConstants.MetricIdKey, out var metricId)); + Assert.Equal(StandardMetricConstants.DependencyDurationMetricIdValue, metricId); + Assert.True(metricData.Properties.TryGetValue(StandardMetricConstants.DependencyTypeKey, out var dependencyType)); + if (isAzureSDKSpan) + { + Assert.Equal("Queue Message | aznamespace", dependencyType); + } + else + { + Assert.Equal("messagingsystem", dependencyType); + } + Assert.True(metricData.Properties.TryGetValue(StandardMetricConstants.DependencyTargetKey, out var dependencyTarget)); + Assert.Equal("localhost/destination", dependencyTarget); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void ValidateDependencyDurationMetricNew(bool isAzureSDK) { var activitySource = new ActivitySource(nameof(StandardMetricTests.ValidateDependencyDurationMetric)); var traceTelemetryItems = new List(); @@ -194,6 +330,10 @@ public void ValidateDependencyDurationMetricNew() using (var activity = activitySource.StartActivity("Test", ActivityKind.Client)) { + if (isAzureSDK) + { + activity?.SetTag(SemanticConventions.AttributeAzureNameSpace, "aznamespace"); + } activity?.SetTag(SemanticConventions.AttributeHttpResponseStatusCode, 200); activity?.SetTag(SemanticConventions.AttributeHttpRequestMethod, "Get"); activity?.SetTag(SemanticConventions.AttributeServerAddress, "foo.com"); @@ -222,7 +362,15 @@ public void ValidateDependencyDurationMetricNew() Assert.True(metricData.Properties.TryGetValue(StandardMetricConstants.MetricIdKey, out var metricId)); Assert.Equal(StandardMetricConstants.DependencyDurationMetricIdValue, metricId); Assert.True(metricData.Properties.TryGetValue(StandardMetricConstants.DependencyTypeKey, out var dependencyType)); - Assert.Equal("Http", dependencyType); + if (isAzureSDK) + { + Assert.Equal("aznamespace", dependencyType); + } + else + { + Assert.Equal("Http", dependencyType); + } + Assert.True(metricData.Properties.TryGetValue(StandardMetricConstants.DependencyTargetKey, out var dependencyTarget)); Assert.Equal("foo.com", dependencyTarget); }