Skip to content

Commit

Permalink
[AzureMonitorExporter] Fix Duplicate Timestamps in OTELRESOURCE Metri…
Browse files Browse the repository at this point in the history
…cs (Azure#41761)

* Fix Duplicate Timestamps in OTELRESOURCE Metrics

* Update changelog

* Update tests
  • Loading branch information
rajkumar-rangaraj authored Feb 2, 2024
1 parent f6b57c9 commit e0ebb9e
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 24 deletions.
5 changes: 5 additions & 0 deletions sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@

### Bugs Fixed

* Fixed an issue where `_OTELRESOURCE_` metrics were emitted with duplicated
timestamps. This fix ensures accurate and distinct timestamping for all
`_OTELRESOURCE_` metrics.
([#41761](https://github.com/Azure/azure-sdk-for-net/pull/41761))

### Other Changes

## 1.2.0 (2024-01-24)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,11 @@ public TelemetryItem(DateTime time, AzureMonitorResource? resource, string instr
SetResourceSdkVersionAndIkey(resource, instrumentationKey);
}

public TelemetryItem(DateTime time, AzureMonitorResource? resource, string instrumentationKey, MonitorBase monitorBaseData) : this(time, resource, instrumentationKey)
{
Data = monitorBaseData;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SetResourceSdkVersionAndIkey(AzureMonitorResource? resource, string instrumentationKey)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ internal sealed class AzureMonitorResource

internal string? RoleInstance { get; set; }

internal TelemetryItem? MetricTelemetry { get; set; }
internal MonitorBase? MonitorBaseData { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -135,13 +135,10 @@ internal static class ResourceExtensions

if (shouldReportMetricTelemetry && metricsData != null)
{
azureMonitorResource.MetricTelemetry = new TelemetryItem(DateTime.UtcNow, azureMonitorResource, instrumentationKey!)
azureMonitorResource.MonitorBaseData = new MonitorBase
{
Data = new MonitorBase
{
BaseType = "MetricData",
BaseData = metricsData
}
BaseType = "MetricData",
BaseData = metricsData
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@ internal static List<TelemetryItem> OtelToAzureMonitorTrace(Batch<Activity> batc
List<TelemetryItem> telemetryItems = new List<TelemetryItem>();
TelemetryItem telemetryItem;

if (batchActivity.Count > 0 && azureMonitorResource?.MetricTelemetry != null)
if (batchActivity.Count > 0 && azureMonitorResource?.MonitorBaseData != null)
{
telemetryItems.Add(azureMonitorResource.MetricTelemetry);
var otelResourceMetricTelemetry = new TelemetryItem(DateTime.UtcNow, azureMonitorResource, instrumentationKey!, azureMonitorResource.MonitorBaseData);
telemetryItems.Add(otelResourceMetricTelemetry);
}

foreach (var activity in batchActivity)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ public void DefaultResource(string? instrumentationKey, string envVarValue)
Assert.Equal(Dns.GetHostName(), azMonResource?.RoleInstance);
if (envVarValue == "true")
{
Assert.Equal(instrumentationKey != null, azMonResource?.MetricTelemetry != null);
Assert.Equal(instrumentationKey != null, azMonResource?.MonitorBaseData != null);
}
else
{
Assert.Null(azMonResource?.MetricTelemetry);
Assert.Null(azMonResource?.MonitorBaseData);
}
}
finally
Expand Down Expand Up @@ -221,10 +221,8 @@ public void SdkPrefixIsNotInResourceMetrics()
var resource = ResourceBuilder.CreateDefault().AddAttributes(testAttributes).Build();
var azMonResource = resource.CreateAzureMonitorResource(InstrumentationKey);

Assert.Equal("Metric", azMonResource!.MetricTelemetry!.Name);

var monitorBase = azMonResource.MetricTelemetry.Data;
var metricsData = monitorBase.BaseData as MetricsData;
var monitorBase = azMonResource?.MonitorBaseData;
var metricsData = monitorBase?.BaseData as MetricsData;

var metricDataPoint = metricsData?.Metrics[0];
Assert.Equal("bar", metricsData?.Properties["foo"]);
Expand All @@ -242,7 +240,7 @@ public void SdkPrefixIsNotInResourceMetrics()
[Theory]
[InlineData(null)]
[InlineData(InstrumentationKey)]
public void MetricTelemetryHasAllResourceAttributes(string? instrumentationKey)
public void MonitorBaseDataHasAllResourceAttributes(string? instrumentationKey)
{
try
{
Expand All @@ -261,15 +259,13 @@ public void MetricTelemetryHasAllResourceAttributes(string? instrumentationKey)
var resource = ResourceBuilder.CreateEmpty().AddAttributes(testAttributes).Build();
var azMonResource = resource.CreateAzureMonitorResource(instrumentationKey);

Assert.Equal(instrumentationKey != null, azMonResource?.MetricTelemetry != null);
Assert.Equal(instrumentationKey != null, azMonResource?.MonitorBaseData != null);

if (instrumentationKey != null)
{
Assert.Equal("Metric", azMonResource!.MetricTelemetry!.Name);
Assert.Equal(3, azMonResource.MetricTelemetry.Tags.Count);
Assert.NotNull(azMonResource.MetricTelemetry.Data);
Assert.NotNull(azMonResource?.MonitorBaseData);

var monitorBase = azMonResource.MetricTelemetry.Data;
var monitorBase = azMonResource.MonitorBaseData;
var metricsData = monitorBase.BaseData as MetricsData;

Assert.NotNull(metricsData?.Metrics);
Expand Down Expand Up @@ -366,11 +362,11 @@ public void MetricTelemetryIsAddedToResourceBasedOnEnvVar(string envVarValue)

if (envVarValue == "true")
{
Assert.NotNull(azMonResource?.MetricTelemetry);
Assert.NotNull(azMonResource?.MonitorBaseData);
}
else
{
Assert.Null(azMonResource?.MetricTelemetry);
Assert.Null(azMonResource?.MonitorBaseData);
}
}
finally
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Linq;
using System.Net;
using Azure.Monitor.OpenTelemetry.Exporter.Internals;
using Azure.Monitor.OpenTelemetry.Exporter.Internals.Platform;
using Azure.Monitor.OpenTelemetry.Exporter.Models;
using OpenTelemetry;
using OpenTelemetry.Resources;
Expand Down Expand Up @@ -271,7 +272,7 @@ public void AiLocationIpIsNotSetByDefault()
var telemetryItems = TraceHelper.OtelToAzureMonitorTrace(new Batch<Activity>(new Activity[] { activity }, 1), null, "instrumentationKey", 1.0f);
var telemetryItem = telemetryItems.FirstOrDefault();

Assert.False(telemetryItem?.Tags.TryGetValue(ContextTagKeys.AiLocationIp.ToString(),out _));
Assert.False(telemetryItem?.Tags.TryGetValue(ContextTagKeys.AiLocationIp.ToString(), out _));
}

[Fact]
Expand Down Expand Up @@ -408,6 +409,120 @@ public void RequestNameMatchesOperationNameForConsumerSpans()
Assert.Equal(requestData.Name, telemetryItem?.Tags[ContextTagKeys.AiOperationName.ToString()]);
}

[Fact]
public void OTelResourceMetricTelemetryHasAllResourceAttributes()
{
try
{
Environment.SetEnvironmentVariable(EnvironmentVariableConstants.EXPORT_RESOURCE_METRIC, "true");
var instrumentationKey = "00000000-0000-0000-0000-000000000000";

var testAttributes = new Dictionary<string, object>
{
{SemanticConventions.AttributeServiceName, "my-service" },
{SemanticConventions.AttributeServiceNamespace, "my-namespace" },
{SemanticConventions.AttributeServiceInstance, "my-instance" },
{SemanticConventions.AttributeK8sDeployment, "my-deployment" },
{SemanticConventions.AttributeK8sPod, "my-pod" },
{ "foo", "bar" }
};

using ActivitySource activitySource = new ActivitySource(ActivitySourceName);
using var activity = activitySource.StartActivity(
ActivityName,
ActivityKind.Server,
null,
startTime: DateTime.UtcNow);

Assert.NotNull(activity);

var resource = ResourceBuilder.CreateEmpty().AddAttributes(testAttributes).Build();
var azMonResource = resource.CreateAzureMonitorResource(instrumentationKey);

var telemetryItems = TraceHelper.OtelToAzureMonitorTrace(new Batch<Activity>(new Activity[] { activity }, 1), null, instrumentationKey, 1.0f);
var telemetryItem = telemetryItems.FirstOrDefault();

var monitorBase = telemetryItem?.Data;
var metricsData = monitorBase?.BaseData as MetricsData;

Assert.NotNull(metricsData?.Metrics);

var metricDataPoint = metricsData?.Metrics[0];
Assert.Equal("_OTELRESOURCE_", metricDataPoint?.Name);
Assert.Equal(0, metricDataPoint?.Value);

Assert.Equal(6, metricsData?.Properties.Count);

Assert.Equal("my-service", metricsData?.Properties[SemanticConventions.AttributeServiceName]);
Assert.Equal("my-namespace", metricsData?.Properties[SemanticConventions.AttributeServiceNamespace]);
Assert.Equal("my-instance", metricsData?.Properties[SemanticConventions.AttributeServiceInstance]);
Assert.Equal("my-deployment", metricsData?.Properties[SemanticConventions.AttributeK8sDeployment]);
Assert.Equal("my-pod", metricsData?.Properties[SemanticConventions.AttributeK8sPod]);
Assert.Equal("bar", metricsData?.Properties["foo"]);
}
catch (Exception)
{
Environment.SetEnvironmentVariable(EnvironmentVariableConstants.EXPORT_RESOURCE_METRIC, null);
}
}

[Fact]
public void OTelResourceMetricTimesAreDifferent()
{
try
{
Environment.SetEnvironmentVariable(EnvironmentVariableConstants.EXPORT_RESOURCE_METRIC, "true");
var instrumentationKey = "00000000-0000-0000-0000-000000000000";

var testAttributes = new Dictionary<string, object>
{
{ "foo", "bar" }
};

using ActivitySource activitySource = new ActivitySource(ActivitySourceName);
using var activity1 = activitySource.StartActivity(
ActivityName,
ActivityKind.Server,
null,
startTime: DateTime.UtcNow);

Assert.NotNull(activity1);

var resource = ResourceBuilder.CreateEmpty().AddAttributes(testAttributes).Build();
var azMonResource = resource.CreateAzureMonitorResource(instrumentationKey);

var telemetryItems = TraceHelper.OtelToAzureMonitorTrace(new Batch<Activity>(new Activity[] { activity1 }, 1), null, instrumentationKey, 1.0f);
var telemetryItem1 = telemetryItems.FirstOrDefault();

var monitorBase = telemetryItem1?.Data;
var metricsData = monitorBase?.BaseData as MetricsData;

Assert.NotNull(metricsData?.Metrics);

var metricDataPoint = metricsData?.Metrics[0];
Assert.Equal("_OTELRESOURCE_", metricDataPoint?.Name);
Assert.Equal(1, metricsData?.Properties.Count);
Assert.Equal("bar", metricsData?.Properties["foo"]);

using var activity2 = activitySource.StartActivity(
ActivityName,
ActivityKind.Server,
null,
startTime: DateTime.UtcNow);

Assert.NotNull(activity2);

telemetryItems = TraceHelper.OtelToAzureMonitorTrace(new Batch<Activity>(new Activity[] { activity2 }, 1), null, instrumentationKey, 1.0f);
var telemetryItem2 = telemetryItems.FirstOrDefault();

Assert.NotEqual(telemetryItem1?.Time, telemetryItem2?.Time);
}
catch (Exception)
{
Environment.SetEnvironmentVariable(EnvironmentVariableConstants.EXPORT_RESOURCE_METRIC, null);
}
}

/// <summary>
/// If SERVICE.NAME is not defined, it will fall-back to "unknown_service".
/// (https://github.com/open-telemetry/opentelemetry-specification/tree/main/specification/resource/semantic_conventions#semantic-attributes-with-sdk-provided-default-value).
Expand Down

0 comments on commit e0ebb9e

Please sign in to comment.