-
Notifications
You must be signed in to change notification settings - Fork 4.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add standard metrics custom processor and request duration metric (#3…
…3955) * draft * compositeprocessor * add test * revert * formatting * rmv using * rmv ? * resolve PR comments * add meter * fix test * rmv using * format * rmv using
- Loading branch information
1 parent
95de9f7
commit 6615941
Showing
7 changed files
with
238 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
23 changes: 23 additions & 0 deletions
23
sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/StandardMetricConstants.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
namespace Azure.Monitor.OpenTelemetry.Exporter.Internals | ||
{ | ||
internal class StandardMetricConstants | ||
{ | ||
internal const string StandardMetricMeterName = "StandardMetricMeter"; | ||
internal const string RequestDurationInstrumentName = "RequestDurationStandardMetric"; | ||
|
||
// request duration keys and values | ||
internal const string RequestDurationMetricIdValue = "requests/duration"; | ||
internal const string RequestSuccessKey = "Request.Success"; | ||
internal const string RequestResultCodeKey = "request/resultCode"; | ||
|
||
//common keys | ||
internal const string IsSyntheticKey = "operation/synthetic"; | ||
internal const string IsAutoCollectedKey = "_MS.IsAutocollected"; | ||
internal const string CloudRoleNameKey = "cloud/roleName"; | ||
internal const string CloudRoleInstanceKey = "cloud/roleInstance"; | ||
internal const string MetricIdKey = "_MS.MetricId"; | ||
} | ||
} |
95 changes: 95 additions & 0 deletions
95
.../Azure.Monitor.OpenTelemetry.Exporter/src/Internals/StandardMetricsExtractionProcessor.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
#nullable disable // TODO: remove and fix errors | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Diagnostics; | ||
using System.Diagnostics.Metrics; | ||
using Azure.Monitor.OpenTelemetry.Exporter.Models; | ||
using OpenTelemetry; | ||
|
||
namespace Azure.Monitor.OpenTelemetry.Exporter.Internals | ||
{ | ||
internal class StandardMetricsExtractionProcessor : BaseProcessor<Activity> | ||
{ | ||
private bool _disposed; | ||
private AzureMonitorResource _resource; | ||
private readonly Meter _meter; | ||
private readonly Histogram<double> _requestDuration; | ||
|
||
internal static readonly IReadOnlyDictionary<string, string> s_standardMetricNameMapping = new Dictionary<string, string>() | ||
{ | ||
[StandardMetricConstants.RequestDurationInstrumentName] = StandardMetricConstants.RequestDurationMetricIdValue, | ||
}; | ||
|
||
internal AzureMonitorResource StandardMetricResource => _resource ??= ParentProvider.GetResource().UpdateRoleNameAndInstance(); | ||
|
||
internal StandardMetricsExtractionProcessor() | ||
{ | ||
_meter = new Meter(StandardMetricConstants.StandardMetricMeterName); | ||
_requestDuration = _meter.CreateHistogram<double>(StandardMetricConstants.RequestDurationInstrumentName); | ||
} | ||
|
||
public override void OnEnd(Activity activity) | ||
{ | ||
if (activity.Kind == ActivityKind.Server) | ||
{ | ||
if (_requestDuration.Enabled) | ||
{ | ||
activity.SetTag("_MS.ProcessedByMetricExtractors", "(Name: X,Ver:'1.1')"); | ||
ReportRequestDurationMetric(activity, SemanticConventions.AttributeHttpStatusCode); | ||
} | ||
} | ||
|
||
// TODO: other activity kinds | ||
} | ||
|
||
private void ReportRequestDurationMetric(Activity activity, string statusCodeAttribute) | ||
{ | ||
string statusCodeAttributeValue = null; | ||
foreach (var tag in activity.EnumerateTagObjects()) | ||
{ | ||
if (tag.Key == statusCodeAttribute) | ||
{ | ||
statusCodeAttributeValue = tag.Value.ToString(); | ||
break; | ||
} | ||
} | ||
|
||
TagList tags = default; | ||
tags.Add(new KeyValuePair<string, object>(StandardMetricConstants.RequestResultCodeKey, statusCodeAttributeValue)); | ||
tags.Add(new KeyValuePair<string, object>(StandardMetricConstants.MetricIdKey, StandardMetricConstants.RequestDurationMetricIdValue)); | ||
tags.Add(new KeyValuePair<string, object>(StandardMetricConstants.IsAutoCollectedKey, "True")); | ||
tags.Add(new KeyValuePair<string, object>(StandardMetricConstants.IsSyntheticKey, "False")); | ||
tags.Add(new KeyValuePair<string, object>(StandardMetricConstants.CloudRoleInstanceKey, StandardMetricResource.RoleInstance)); | ||
tags.Add(new KeyValuePair<string, object>(StandardMetricConstants.CloudRoleNameKey, StandardMetricResource.RoleName)); | ||
tags.Add(new KeyValuePair<string, object>(StandardMetricConstants.RequestSuccessKey, RequestData.isSuccess(activity, statusCodeAttributeValue, OperationType.Http))); | ||
|
||
// Report metric | ||
_requestDuration.Record(activity.Duration.TotalMilliseconds, tags); | ||
} | ||
|
||
protected override void Dispose(bool disposing) | ||
{ | ||
if (!_disposed) | ||
{ | ||
if (disposing) | ||
{ | ||
try | ||
{ | ||
_meter?.Dispose(); | ||
} | ||
catch (Exception) | ||
{ | ||
} | ||
} | ||
|
||
_disposed = true; | ||
} | ||
|
||
base.Dispose(disposing); | ||
} | ||
} | ||
} |
87 changes: 87 additions & 0 deletions
87
...elemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/StandardMetricTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
using Azure.Monitor.OpenTelemetry.Exporter.Internals; | ||
using Azure.Monitor.OpenTelemetry.Exporter.Models; | ||
using OpenTelemetry.Metrics; | ||
using OpenTelemetry; | ||
using Xunit; | ||
using Azure.Monitor.OpenTelemetry.Exporter.Tests.CommonTestFramework; | ||
using OpenTelemetry.Trace; | ||
using System.Collections.Concurrent; | ||
using System.Diagnostics; | ||
using System.Linq; | ||
using Microsoft.Identity.Client.Platforms.Features.DesktopOs.Kerberos; | ||
using System; | ||
using System.Threading; | ||
|
||
namespace Azure.Monitor.OpenTelemetry.Exporter.Tests | ||
{ | ||
public class StandardMetricTests | ||
{ | ||
[Fact] | ||
public void ValidateRequestDurationMetric() | ||
{ | ||
var activitySource = new ActivitySource(nameof(StandardMetricTests.ValidateRequestDurationMetric)); | ||
var traceTelemetryItems = new ConcurrentBag<TelemetryItem>(); | ||
var metricTelemetryItems = new ConcurrentBag<TelemetryItem>(); | ||
|
||
var standardMetricCustomProcessor = new StandardMetricsExtractionProcessor(); | ||
|
||
using var tracerProvider = Sdk.CreateTracerProviderBuilder() | ||
.SetSampler(new AlwaysOnSampler()) | ||
.AddSource(nameof(StandardMetricTests.ValidateRequestDurationMetric)) | ||
.AddProcessor(standardMetricCustomProcessor) | ||
.AddProcessor(new BatchActivityExportProcessor(new AzureMonitorTraceExporter(new MockTransmitter(traceTelemetryItems)))) | ||
.Build(); | ||
|
||
using var meterProvider = Sdk.CreateMeterProviderBuilder() | ||
.AddMeter(StandardMetricConstants.StandardMetricMeterName) | ||
.AddReader(new PeriodicExportingMetricReader(new AzureMonitorMetricExporter(new MockTransmitter(metricTelemetryItems))) | ||
{ TemporalityPreference = MetricReaderTemporalityPreference.Delta }) | ||
.Build(); | ||
|
||
using (var activity = activitySource.StartActivity("Test", ActivityKind.Server)) | ||
{ | ||
activity?.SetTag(SemanticConventions.AttributeHttpStatusCode, 200); | ||
} | ||
|
||
tracerProvider?.ForceFlush(); | ||
|
||
WaitForActivityExport(traceTelemetryItems); | ||
|
||
meterProvider?.ForceFlush(); | ||
|
||
Assert.Single(metricTelemetryItems); | ||
|
||
var metricTelemetry = metricTelemetryItems.Single(); | ||
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("200", resultCode); | ||
Assert.True(metricData.Properties.TryGetValue(StandardMetricConstants.IsAutoCollectedKey, out var isAutoCollectedFlag)); | ||
Assert.Equal("True", isAutoCollectedFlag); | ||
Assert.True(metricData.Properties.TryGetValue(StandardMetricConstants.IsSyntheticKey, out var isSynthetic)); | ||
Assert.Equal("False", isSynthetic); | ||
Assert.True(metricData.Properties.TryGetValue(StandardMetricConstants.CloudRoleInstanceKey, out _)); | ||
Assert.True(metricData.Properties.TryGetValue(StandardMetricConstants.CloudRoleNameKey, out _)); | ||
Assert.True(metricData.Properties.TryGetValue(StandardMetricConstants.MetricIdKey, out var metricId)); | ||
Assert.Equal(StandardMetricConstants.RequestDurationMetricIdValue, metricId); | ||
} | ||
|
||
private void WaitForActivityExport(ConcurrentBag<TelemetryItem> traceTelemetryItems) | ||
{ | ||
var result = SpinWait.SpinUntil( | ||
condition: () => | ||
{ | ||
Thread.Sleep(10); | ||
return traceTelemetryItems.Any(); | ||
}, | ||
timeout: TimeSpan.FromSeconds(10)); | ||
|
||
Assert.True(result, $"{nameof(WaitForActivityExport)} failed."); | ||
} | ||
} | ||
} |