Skip to content

Commit

Permalink
Add Standard metrics for messaging (#37940)
Browse files Browse the repository at this point in the history
* standard metrics for messaging

* revert change
  • Loading branch information
vishweshbankwar authored Jul 31, 2023
1 parent 5ed6767 commit b7feaa4
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using Azure.Monitor.OpenTelemetry.Exporter.Models;

Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,25 +42,22 @@ 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)
{
activity.SetTag("_MS.ProcessedByMetricExtractors", "(Name: X,Ver:'1.1')");
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)
{
activity.SetTag("_MS.ProcessedByMetricExtractors", "(Name: X,Ver:'1.1')");
ReportDependencyDurationMetric(activity);
}
}

// TODO: other activity kinds
// (2023-07) fix before GA
}

private void ReportRequestDurationMetric(Activity activity)
Expand Down Expand Up @@ -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<string, object?>(StandardMetricConstants.DependencyTargetKey, dependencyTarget));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<TelemetryItem>();
var metricTelemetryItems = new List<TelemetryItem>();

var standardMetricCustomProcessor = new StandardMetricsExtractionProcessor(new AzureMonitorMetricExporter(new MockTransmitter(metricTelemetryItems)));

var traceServiceName = new KeyValuePair<string, object>("service.name", "trace.service");
var resourceAttributes = new KeyValuePair<string, object>[] { 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<TelemetryItem>();
Expand All @@ -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");
Expand Down Expand Up @@ -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<TelemetryItem>();
var metricTelemetryItems = new List<TelemetryItem>();

var standardMetricCustomProcessor = new StandardMetricsExtractionProcessor(new AzureMonitorMetricExporter(new MockTransmitter(metricTelemetryItems)));

var traceServiceName = new KeyValuePair<string, object>("service.name", "trace.service");
var resourceAttributes = new KeyValuePair<string, object>[] { 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<TelemetryItem>();
Expand All @@ -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");
Expand Down Expand Up @@ -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);
}
Expand Down

0 comments on commit b7feaa4

Please sign in to comment.