diff --git a/src/AWS.OpenTelemetry.AutoInstrumentation/AWS.OpenTelemetry.AutoInstrumentation.csproj b/src/AWS.OpenTelemetry.AutoInstrumentation/AWS.OpenTelemetry.AutoInstrumentation.csproj
index 5b53171c..737673bc 100644
--- a/src/AWS.OpenTelemetry.AutoInstrumentation/AWS.OpenTelemetry.AutoInstrumentation.csproj
+++ b/src/AWS.OpenTelemetry.AutoInstrumentation/AWS.OpenTelemetry.AutoInstrumentation.csproj
@@ -24,6 +24,9 @@
<_Parameter1>AWS.OpenTelemetry.AutoInstrumentation.Tests
+
+ <_Parameter1>DynamicProxyGenAssembly2
+
diff --git a/src/AWS.OpenTelemetry.AutoInstrumentation/AwsMetricAttributeGenerator.cs b/src/AWS.OpenTelemetry.AutoInstrumentation/AwsMetricAttributeGenerator.cs
index a705530c..dc31bb05 100644
--- a/src/AWS.OpenTelemetry.AutoInstrumentation/AwsMetricAttributeGenerator.cs
+++ b/src/AWS.OpenTelemetry.AutoInstrumentation/AwsMetricAttributeGenerator.cs
@@ -21,7 +21,7 @@ namespace AWS.OpenTelemetry.AutoInstrumentation;
/// represent "incoming" traffic, { and spans
/// represent "outgoing" traffic, and spans are ignored.
///
-internal sealed class AwsMetricAttributeGenerator : IMetricAttributeGenerator
+internal class AwsMetricAttributeGenerator : IMetricAttributeGenerator
{
private static readonly ILoggerFactory Factory = LoggerFactory.Create(builder => builder.AddConsole());
private static readonly ILogger Logger = Factory.CreateLogger();
@@ -46,7 +46,7 @@ internal sealed class AwsMetricAttributeGenerator : IMetricAttributeGenerator
private static readonly string AttributeServiceName = "service.name";
///
- public Dictionary GenerateMetricAttributeMapFromSpan(Activity span, Resource resource)
+ public virtual Dictionary GenerateMetricAttributeMapFromSpan(Activity span, Resource resource)
{
Dictionary attributesMap = new Dictionary();
if (ShouldGenerateServiceMetricAttributes(span))
@@ -89,7 +89,7 @@ private ActivityTagsCollection GenerateDependencyMetricAttributes(Activity span,
private static void SetService(Resource resource, Activity span, ActivityTagsCollection attributes)
#pragma warning restore SA1204 // Static elements should appear before instance elements
{
- string? service = (string?)resource.Attributes.First(attribute => attribute.Key == AttributeServiceName).Value;
+ string? service = (string?)resource.Attributes.FirstOrDefault(attribute => attribute.Key == AttributeServiceName).Value;
// In practice the service name is never null, but we can be defensive here.
if (service == null || service.StartsWith(OtelUnknownServicePrefix))
diff --git a/test/AWS.OpenTelemetry.AutoInstrumentation.Tests/AWS.OpenTelemetry.AutoInstrumentation.Tests.csproj b/test/AWS.OpenTelemetry.AutoInstrumentation.Tests/AWS.OpenTelemetry.AutoInstrumentation.Tests.csproj
index e3884120..4b2f2729 100644
--- a/test/AWS.OpenTelemetry.AutoInstrumentation.Tests/AWS.OpenTelemetry.AutoInstrumentation.Tests.csproj
+++ b/test/AWS.OpenTelemetry.AutoInstrumentation.Tests/AWS.OpenTelemetry.AutoInstrumentation.Tests.csproj
@@ -11,6 +11,7 @@
+
diff --git a/test/AWS.OpenTelemetry.AutoInstrumentation.Tests/AwsSpanMetricsProcessorTest.cs b/test/AWS.OpenTelemetry.AutoInstrumentation.Tests/AwsSpanMetricsProcessorTest.cs
index 6e1943a1..bd1ea744 100644
--- a/test/AWS.OpenTelemetry.AutoInstrumentation.Tests/AwsSpanMetricsProcessorTest.cs
+++ b/test/AWS.OpenTelemetry.AutoInstrumentation.Tests/AwsSpanMetricsProcessorTest.cs
@@ -1,18 +1,545 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
+using System.Diagnostics;
+using System.Diagnostics.Metrics;
+using System.Reflection;
+using HarmonyLib;
+using Moq;
+using OpenTelemetry.Resources;
+using OpenTelemetry.Trace;
+using static AWS.OpenTelemetry.AutoInstrumentation.AwsAttributeKeys;
+
+
+
namespace AWS.OpenTelemetry.AutoInstrumentation.Tests;
-///
-/// TODO: Add documentation here
-///
-public class AwsSpanMetricsProcessorTest
+// There is two test that is not implemented in this Class, comparing with Java:
+
+// 1. testIsRequired()
+// Implementation of AwsSpanMetricsProcessor.isStartRequired() and isEndRequired() do not exist
+
+// 2. testsOnEndMetricsGenerationLocalRootServerSpan()
+// This test cannot be done here because there is no difference (or cannot set difference) in dotnet for
+// a null parent information and a invalid parent information
+// Found no way to setup a Activity.Parent to a default/invalid value,
+// It either valid (set by passing a parent ID and automatically matching Activity.Parent field)
+// or just Null
+
+
+
+
+public class AwsSpanMetricsProcessorTest: IDisposable
{
- ///
- /// Test documentation
- ///
+ public static int count;
+ private AwsSpanMetricsProcessor awsSpanMetricsProcessor;
+ private Mock Generator = new Mock();
+ private Resource resource = Resource.Empty;
+ private Meter meter = new Meter("test");
+ private Histogram errorHistogram;
+ private Histogram faultHistogram;
+ private Histogram latencyHistogram;
+ private ActivitySource activitySource = new ActivitySource("test");
+ private Activity spanDataMock;
+ private readonly double testLatencyMillis = 150;
+
+ public void Dispose()
+ {
+ GlobalCallbackData.Clear();
+ }
+
+ public AwsSpanMetricsProcessorTest()
+ {
+ var listener = new ActivityListener
+ {
+ ShouldListenTo = (activitySource) => true,
+ Sample = ((ref ActivityCreationOptions options) => ActivitySamplingResult.AllData)
+ };
+ ActivitySource.AddActivityListener(listener);
+
+ errorHistogram = meter.CreateHistogram("error");
+ faultHistogram = meter.CreateHistogram("fault");
+ latencyHistogram = meter.CreateHistogram("latency");
+ var meterListener = new MeterListener();
+ meterListener.EnableMeasurementEvents(errorHistogram);
+ meterListener.EnableMeasurementEvents(faultHistogram);
+ meterListener.EnableMeasurementEvents(latencyHistogram);
+ meter.Tags.AddItem(new KeyValuePair("test", "test"));
+ meterListener.SetMeasurementEventCallback(((instrument, measurement, tags, state) =>
+ {
+ var list = GlobalCallbackData.CallList is null ? [] : GlobalCallbackData.CallList;
+ list.Add(new KeyValuePair(instrument.Name, new KeyValuePair(measurement, tags[0])));
+ GlobalCallbackData.CallList = list;
+ }
+ ));
+ meterListener.SetMeasurementEventCallback(((instrument, measurement, tags, state) =>
+ {
+ var list = GlobalCallbackData.CallList is null ? [] : GlobalCallbackData.CallList;
+ list.Add(new KeyValuePair(instrument.Name, new KeyValuePair(measurement, tags[0])));
+ GlobalCallbackData.CallList = list;
+ }
+ ));
+ awsSpanMetricsProcessor = AwsSpanMetricsProcessor.Create(errorHistogram, faultHistogram, latencyHistogram, Generator.Object, resource);
+ }
+
+ [Fact]
+ public void testStartDoesNothingToSpan()
+ {
+ spanDataMock = activitySource.StartActivity("test");
+ var parentInfo = spanDataMock.ParentSpanId;
+ awsSpanMetricsProcessor.OnStart(spanDataMock);
+ Assert.Equal(parentInfo, spanDataMock.ParentSpanId);
+ }
+
+ [Fact]
+ public void testTearDown()
+ {
+ Assert.True(awsSpanMetricsProcessor.Shutdown());
+ Assert.True(awsSpanMetricsProcessor.ForceFlush());
+ }
+
+ /**
+ * Tests starting with testOnEndMetricsGeneration are testing the logic in
+ * AwsSpanMetricsProcessor's onEnd method pertaining to metrics generation.
+ */
+ [Fact]
+ public void testOnEndMetricsGenerationWithoutSpanAttributes()
+ {
+ spanDataMock = activitySource.StartActivity("test", ActivityKind.Server);
+ setLatency();
+ Dictionary expectAttributes = buildMetricAttributes(true, spanDataMock);
+ Generator.Setup(g => g.GenerateMetricAttributeMapFromSpan(spanDataMock, resource))
+ .Returns(expectAttributes);
+ spanDataMock.SetEndTime(DateTime.Now);
+ awsSpanMetricsProcessor.OnEnd(spanDataMock);
+ verifyHistogramRecords(expectAttributes, 1,0);
+ }
+
[Fact]
- public void Test1()
+ public void testOnEndMetricsGenerationWithoutMetricAttributes()
{
+ spanDataMock = activitySource.StartActivity("test", ActivityKind.Server);
+ setLatency();
+ spanDataMock.SetTag(AwsAttributeKeys.AttributeHttpStatusCode, (long)500);
+ Dictionary expectAttributes = buildMetricAttributes(false, spanDataMock);
+ Generator.Setup(g => g.GenerateMetricAttributeMapFromSpan(spanDataMock, resource))
+ .Returns(expectAttributes);
+ awsSpanMetricsProcessor.OnEnd(spanDataMock);
+ verifyHistogramRecords(expectAttributes, 0,0);
}
+
+ [Fact]
+ public void testsOnEndMetricsGenerationLocalRootConsumerSpan()
+ {
+ spanDataMock = activitySource.StartActivity("test", ActivityKind.Consumer);
+ setLatency();
+ Dictionary expectAttributes = buildMetricAttributes(true, spanDataMock);
+ Generator.Setup(g => g.GenerateMetricAttributeMapFromSpan(spanDataMock, resource))
+ .Returns(expectAttributes);
+ awsSpanMetricsProcessor.OnEnd(spanDataMock);
+ verifyHistogramRecords(expectAttributes, 1,1);
+ }
+
+ [Fact]
+ public void testsOnEndMetricsGenerationLocalRootClientSpan()
+ {
+ spanDataMock = activitySource.StartActivity("test", ActivityKind.Client);
+ setLatency();
+ Dictionary expectAttributes = buildMetricAttributes(true, spanDataMock);
+ Generator.Setup(g => g.GenerateMetricAttributeMapFromSpan(spanDataMock, resource))
+ .Returns(expectAttributes);
+ awsSpanMetricsProcessor.OnEnd(spanDataMock);
+ verifyHistogramRecords(expectAttributes, 1,1);
+ }
+
+ [Fact]
+ public void testsOnEndMetricsGenerationLocalRootProducerSpan()
+ {
+ spanDataMock = activitySource.StartActivity("test", ActivityKind.Producer);
+ setLatency();
+ Dictionary expectAttributes = buildMetricAttributes(true, spanDataMock);
+ Generator.Setup(g => g.GenerateMetricAttributeMapFromSpan(spanDataMock, resource))
+ .Returns(expectAttributes);
+ awsSpanMetricsProcessor.OnEnd(spanDataMock);
+ verifyHistogramRecords(expectAttributes, 1,1);
+ }
+
+ [Fact]
+ public void testsOnEndMetricsGenerationLocalRootInternalSpan()
+ {
+ spanDataMock = activitySource.StartActivity("test", ActivityKind.Internal);
+ setLatency();
+ Dictionary expectAttributes = buildMetricAttributes(true, spanDataMock);
+ Generator.Setup(g => g.GenerateMetricAttributeMapFromSpan(spanDataMock, resource))
+ .Returns(expectAttributes);
+ awsSpanMetricsProcessor.OnEnd(spanDataMock);
+ verifyHistogramRecords(expectAttributes, 1,0);
+ }
+
+ [Fact]
+ public void testsOnEndMetricsGenerationLocalRootProducerSpanWithoutMetricAttributes()
+ {
+ spanDataMock = activitySource.StartActivity("test", ActivityKind.Producer);
+ setLatency();
+ Dictionary expectAttributes = buildMetricAttributes(false, spanDataMock);
+ Generator.Setup(g => g.GenerateMetricAttributeMapFromSpan(spanDataMock, resource))
+ .Returns(expectAttributes);
+ awsSpanMetricsProcessor.OnEnd(spanDataMock);
+ verifyHistogramRecords(expectAttributes, 0,0);
+ }
+
+ [Fact]
+ public void testsOnEndMetricsGenerationClientSpan()
+ {
+ Activity parentSpan = activitySource.StartActivity("test parent");
+ using (spanDataMock = activitySource.StartActivity("test Child", ActivityKind.Client))
+ {
+ spanDataMock.SetParentId(parentSpan.TraceId, parentSpan.SpanId);
+ spanDataMock.Start();
+ setLatency();
+ Dictionary expectAttributes = buildMetricAttributes(true, spanDataMock);
+ Generator.Setup(g => g.GenerateMetricAttributeMapFromSpan(spanDataMock, resource))
+ .Returns(expectAttributes);
+ awsSpanMetricsProcessor.OnEnd(spanDataMock);
+ verifyHistogramRecords(expectAttributes, 0,1);
+ }
+ }
+
+ [Fact]
+ public void testsOnEndMetricsGenerationProducerSpan()
+ {
+ Activity parentSpan = activitySource.StartActivity("test parent");
+ using (spanDataMock = activitySource.StartActivity("test Child", ActivityKind.Producer))
+ {
+ spanDataMock.SetParentId(parentSpan.TraceId, parentSpan.SpanId);
+ spanDataMock.Start();
+ setLatency();
+ Dictionary expectAttributes = buildMetricAttributes(true, spanDataMock);
+ Generator.Setup(g => g.GenerateMetricAttributeMapFromSpan(spanDataMock, resource))
+ .Returns(expectAttributes);
+ awsSpanMetricsProcessor.OnEnd(spanDataMock);
+ verifyHistogramRecords(expectAttributes, 0,1);
+ }
+ }
+
+ [Fact]
+ public void testOnEndMetricsGenerationWithoutEndRequired()
+ {
+ spanDataMock = activitySource.StartActivity("test", ActivityKind.Server);
+ spanDataMock.SetTag(AttributeHttpStatusCode, (long)500);
+ setLatency();
+ Dictionary expectAttributes = buildMetricAttributes(true, spanDataMock);
+ Generator.Setup(g => g.GenerateMetricAttributeMapFromSpan(spanDataMock, resource))
+ .Returns(expectAttributes);
+ awsSpanMetricsProcessor.OnEnd(spanDataMock);
+ var serviceMetrics = expectAttributes[IMetricAttributeGenerator.ServiceMetric];
+ var serviceKVP = new KeyValuePair(serviceMetrics.Keys.FirstOrDefault(),
+ serviceMetrics.Values.FirstOrDefault());
+ List> expectedService = [];
+ expectedService.Add(new KeyValuePair("error", new KeyValuePair(0,serviceKVP)));
+ expectedService.Add(new KeyValuePair("fault", new KeyValuePair(1,serviceKVP)));
+ expectedService.Add(new KeyValuePair("latency", new KeyValuePair(testLatencyMillis,serviceKVP)));
+
+ Assert.True(GlobalCallbackData.CallList.OrderBy(kvp => kvp.Key).SequenceEqual(expectedService.OrderBy(kvp => kvp.Key)));
+ }
+
+ [Fact]
+ public void testOnEndMetricsGenerationWithLatency()
+ {
+ spanDataMock = activitySource.StartActivity("test", ActivityKind.Server);
+ spanDataMock.SetTag(AttributeHttpStatusCode, (long)200);
+ setLatency(5.5);
+ Dictionary expectAttributes = buildMetricAttributes(true, spanDataMock);
+ Generator.Setup(g => g.GenerateMetricAttributeMapFromSpan(spanDataMock, resource))
+ .Returns(expectAttributes);
+ awsSpanMetricsProcessor.OnEnd(spanDataMock);
+ var serviceMetrics = expectAttributes[IMetricAttributeGenerator.ServiceMetric];
+ var serviceKVP = new KeyValuePair(serviceMetrics.Keys.FirstOrDefault(),
+ serviceMetrics.Values.FirstOrDefault());
+ List> expectedService = [];
+ expectedService.Add(new KeyValuePair("error", new KeyValuePair(0,serviceKVP)));
+ expectedService.Add(new KeyValuePair("fault", new KeyValuePair(0,serviceKVP)));
+ expectedService.Add(new KeyValuePair("latency", new KeyValuePair(5.5,serviceKVP)));
+
+ Assert.True(GlobalCallbackData.CallList.OrderBy(kvp => kvp.Key).SequenceEqual(expectedService.OrderBy(kvp => kvp.Key)));
+ }
+
+ [Fact]
+ public void testOnEndMetricsGenerationWithStatusCodes()
+ {
+ validateMetricsGeneratedForHttpStatusCode(null, ExpectedStatusMetric.NEITHER);
+
+ // Valid HTTP status codes
+ validateMetricsGeneratedForHttpStatusCode(200L, ExpectedStatusMetric.NEITHER);
+ validateMetricsGeneratedForHttpStatusCode(399L, ExpectedStatusMetric.NEITHER);
+ validateMetricsGeneratedForHttpStatusCode(400L, ExpectedStatusMetric.ERROR);
+ validateMetricsGeneratedForHttpStatusCode(499L, ExpectedStatusMetric.ERROR);
+ validateMetricsGeneratedForHttpStatusCode(500L, ExpectedStatusMetric.FAULT);
+ validateMetricsGeneratedForHttpStatusCode(599L, ExpectedStatusMetric.FAULT);
+ validateMetricsGeneratedForHttpStatusCode(600L, ExpectedStatusMetric.NEITHER);
+ }
+
+ [Fact]
+ public void testOnEndMetricsGenerationWithStatusDataError() {
+ // Empty Status and HTTP with Error Status
+ validateMetricsGeneratedForStatusDataError(null, ExpectedStatusMetric.FAULT);
+
+ // Valid HTTP with Error Status
+ validateMetricsGeneratedForStatusDataError(200L, ExpectedStatusMetric.FAULT);
+ validateMetricsGeneratedForStatusDataError(399L, ExpectedStatusMetric.FAULT);
+ validateMetricsGeneratedForStatusDataError(400L, ExpectedStatusMetric.ERROR);
+ validateMetricsGeneratedForStatusDataError(499L, ExpectedStatusMetric.ERROR);
+ validateMetricsGeneratedForStatusDataError(500L, ExpectedStatusMetric.FAULT);
+ validateMetricsGeneratedForStatusDataError(599L, ExpectedStatusMetric.FAULT);
+ validateMetricsGeneratedForStatusDataError(600L, ExpectedStatusMetric.FAULT);
+ }
+
+ [Fact]
+ public void testOnEndMetricsGenerationWithAwsStatusCodes() {
+ // Invalid HTTP status codes
+ validateMetricsGeneratedForAttributeStatusCode(null, ExpectedStatusMetric.NEITHER);
+
+ // Valid HTTP status codes
+ validateMetricsGeneratedForAttributeStatusCode(399L, ExpectedStatusMetric.NEITHER);
+ validateMetricsGeneratedForAttributeStatusCode(400L, ExpectedStatusMetric.ERROR);
+ validateMetricsGeneratedForAttributeStatusCode(499L, ExpectedStatusMetric.ERROR);
+ validateMetricsGeneratedForAttributeStatusCode(500L, ExpectedStatusMetric.FAULT);
+ validateMetricsGeneratedForAttributeStatusCode(599L, ExpectedStatusMetric.FAULT);
+ validateMetricsGeneratedForAttributeStatusCode(600L, ExpectedStatusMetric.NEITHER);
+ }
+
+ [Fact]
+ public void testOnEndMetricsGenerationWithStatusDataOk() {
+ // Empty Status and HTTP with Ok Status
+ validateMetricsGeneratedForStatusDataOk(null, ExpectedStatusMetric.NEITHER);
+
+ // Valid HTTP with Ok Status
+ validateMetricsGeneratedForStatusDataOk(200L, ExpectedStatusMetric.NEITHER);
+ validateMetricsGeneratedForStatusDataOk(399L, ExpectedStatusMetric.NEITHER);
+ validateMetricsGeneratedForStatusDataOk(400L, ExpectedStatusMetric.ERROR);
+ validateMetricsGeneratedForStatusDataOk(499L, ExpectedStatusMetric.ERROR);
+ validateMetricsGeneratedForStatusDataOk(500L, ExpectedStatusMetric.FAULT);
+ validateMetricsGeneratedForStatusDataOk(599L, ExpectedStatusMetric.FAULT);
+ validateMetricsGeneratedForStatusDataOk(600L, ExpectedStatusMetric.NEITHER);
+ }
+
+ private void validateMetricsGeneratedForAttributeStatusCode(
+ long? awsStatusCode, ExpectedStatusMetric expectedStatusMetric)
+ {
+ spanDataMock = activitySource.StartActivity("test", ActivityKind.Producer);
+ spanDataMock.SetTag("new key", "new value");
+ setLatency();
+ Dictionary expectAttributes = buildMetricAttributes(true, spanDataMock);
+ if (awsStatusCode != null)
+ {
+ expectAttributes[IMetricAttributeGenerator.ServiceMetric]["new service key"] = "new service value";
+
+ expectAttributes[IMetricAttributeGenerator.ServiceMetric]
+ .Add(new KeyValuePair(AttributeHttpStatusCode, awsStatusCode));
+
+ expectAttributes[IMetricAttributeGenerator.DependencyMetric]["new dependency key"] = "new dependency value";
+
+ expectAttributes[IMetricAttributeGenerator.DependencyMetric]
+ .Add(new KeyValuePair(AttributeHttpStatusCode, awsStatusCode));
+ }
+
+ Generator.Setup(g => g.GenerateMetricAttributeMapFromSpan(spanDataMock, resource))
+ .Returns(expectAttributes);
+
+ awsSpanMetricsProcessor.OnEnd(spanDataMock);
+ validMetrics(expectAttributes, expectedStatusMetric);
+ spanDataMock.Dispose();
+ }
+
+ private void validateMetricsGeneratedForStatusDataOk(
+ long? httpStatusCode, ExpectedStatusMetric expectedStatusMetric) {
+ spanDataMock = activitySource.StartActivity("test", ActivityKind.Producer);
+ spanDataMock.SetTag(AttributeHttpStatusCode, httpStatusCode);
+ spanDataMock.SetStatus(ActivityStatusCode.Ok);
+ setLatency();
+ Dictionary expectAttributes = buildMetricAttributes(true, spanDataMock);
+ Generator.Setup(g => g.GenerateMetricAttributeMapFromSpan(spanDataMock, resource))
+ .Returns(expectAttributes);
+ awsSpanMetricsProcessor.OnEnd(spanDataMock);
+ validMetrics(expectAttributes, expectedStatusMetric);
+ spanDataMock.Dispose();
+ }
+
+
+ private void validateMetricsGeneratedForStatusDataError(
+ long? httpStatusCode, ExpectedStatusMetric expectedStatusMetric) {
+ spanDataMock = activitySource.StartActivity("test", ActivityKind.Producer);
+ spanDataMock.SetTag(AttributeHttpStatusCode, httpStatusCode);
+ spanDataMock.SetStatus(ActivityStatusCode.Error);
+ setLatency();
+ Dictionary expectAttributes = buildMetricAttributes(true, spanDataMock);
+ Generator.Setup(g => g.GenerateMetricAttributeMapFromSpan(spanDataMock, resource))
+ .Returns(expectAttributes);
+ awsSpanMetricsProcessor.OnEnd(spanDataMock);
+ validMetrics(expectAttributes, expectedStatusMetric);
+ spanDataMock.Dispose();
+ }
+
+ private void validateMetricsGeneratedForHttpStatusCode(
+ long? httpStatusCode, ExpectedStatusMetric expectedStatusMetric)
+ {
+ spanDataMock = activitySource.StartActivity("test", ActivityKind.Producer);
+ spanDataMock.SetTag(AttributeHttpStatusCode, httpStatusCode);
+ setLatency();
+ Dictionary expectAttributes = buildMetricAttributes(true, spanDataMock);
+ Generator.Setup(g => g.GenerateMetricAttributeMapFromSpan(spanDataMock, resource))
+ .Returns(expectAttributes);
+ awsSpanMetricsProcessor.OnEnd(spanDataMock);
+ validMetrics(expectAttributes, expectedStatusMetric);
+ spanDataMock.Dispose();
+ }
+
+ private void validMetrics(Dictionary metricAttributesMap,
+ ExpectedStatusMetric expectedStatusMetric)
+ {
+ var expectedList = new List>();
+ var serviceMetrics = metricAttributesMap[IMetricAttributeGenerator.ServiceMetric];
+ var serviceKVP = new KeyValuePair(serviceMetrics.Keys.FirstOrDefault(),
+ serviceMetrics.Values.FirstOrDefault());
+ List> expectedService = [];
+ var dependencyMetrics = metricAttributesMap[IMetricAttributeGenerator.DependencyMetric];
+ var dependencyKVP = new KeyValuePair(dependencyMetrics.Keys.FirstOrDefault(),
+ dependencyMetrics.Values.FirstOrDefault());
+ List> expectedDependency = [];
+
+ switch (expectedStatusMetric)
+ {
+ case ExpectedStatusMetric.ERROR:
+ expectedService.Add(new KeyValuePair("error", new KeyValuePair(1,serviceKVP)));
+ expectedService.Add(new KeyValuePair("fault", new KeyValuePair(0,serviceKVP)));
+ expectedDependency.Add(new KeyValuePair("error", new KeyValuePair(1,dependencyKVP)));
+ expectedDependency.Add(new KeyValuePair("fault", new KeyValuePair(0,dependencyKVP)));
+ break;
+ case ExpectedStatusMetric.FAULT:
+ expectedService.Add(new KeyValuePair("error", new KeyValuePair(0,serviceKVP)));
+ expectedService.Add(new KeyValuePair("fault", new KeyValuePair(1,serviceKVP)));
+ expectedDependency.Add(new KeyValuePair("error", new KeyValuePair(0,dependencyKVP)));
+ expectedDependency.Add(new KeyValuePair("fault", new KeyValuePair(1,dependencyKVP)));
+ break;
+ case ExpectedStatusMetric.NEITHER:
+ expectedService.Add(new KeyValuePair("error", new KeyValuePair(0,serviceKVP)));
+ expectedService.Add(new KeyValuePair("fault", new KeyValuePair(0,serviceKVP)));
+ expectedDependency.Add(new KeyValuePair("error", new KeyValuePair(0,dependencyKVP)));
+ expectedDependency.Add(new KeyValuePair("fault", new KeyValuePair(0,dependencyKVP)));
+ break;
+ }
+
+ expectedList.AddRange(expectedService);
+ expectedList.AddRange(expectedDependency);
+
+ var expectDict = new Dictionary, int>();
+ var actualDict = new Dictionary, int>();
+ GlobalCallbackData.CallList.RemoveAll(kvp => kvp.Key == "latency");
+ if (expectedList.Count > 0)
+ {
+ expectDict = expectedList.GroupBy(kvp => kvp).ToDictionary(g => g.Key, g => g.Count());
+ }
+ if (GlobalCallbackData.CallList is not null)
+ {
+ actualDict = GlobalCallbackData.CallList.GroupBy(kvp => kvp).ToDictionary(g => g.Key, g => g.Count());
+ }
+ Assert.Equal(expectDict, actualDict);
+
+ GlobalCallbackData.Clear();
+
+ }
+
+ private void verifyHistogramRecords(Dictionary metricAttributesMap,
+ int wantedServiceMetricInvocation,
+ int wantedDependencyMetricInvocation)
+ {
+ var expectedList = new List>();
+ if (wantedServiceMetricInvocation > 0)
+ {
+ var serviceMetrics = metricAttributesMap[IMetricAttributeGenerator.ServiceMetric];
+ var serviceKVP = new KeyValuePair(serviceMetrics.Keys.FirstOrDefault(),
+ serviceMetrics.Values.FirstOrDefault());
+ List> expectedService = [];
+ expectedService.Add(new KeyValuePair("error", new KeyValuePair(0,serviceKVP)));
+ expectedService.Add(new KeyValuePair("fault", new KeyValuePair(0,serviceKVP)));
+ expectedService.Add(new KeyValuePair("latency", new KeyValuePair(testLatencyMillis,serviceKVP)));
+ expectedService = expectedService.SelectMany(item => Enumerable.Repeat(item, wantedServiceMetricInvocation)).ToList();
+ expectedList.AddRange(expectedService);
+ }
+
+ if (wantedDependencyMetricInvocation > 0)
+ {
+ var dependencyMetrics = metricAttributesMap[IMetricAttributeGenerator.DependencyMetric];
+ var dependencyKVP = new KeyValuePair(dependencyMetrics.Keys.FirstOrDefault(),
+ dependencyMetrics.Values.FirstOrDefault());
+ List> expectedDependency = [];
+ expectedDependency.Add(new KeyValuePair("error", new KeyValuePair(0,dependencyKVP)));
+ expectedDependency.Add(new KeyValuePair("fault", new KeyValuePair(0,dependencyKVP)));
+ expectedDependency.Add(new KeyValuePair("latency", new KeyValuePair(testLatencyMillis,dependencyKVP)));
+ expectedDependency = expectedDependency.SelectMany(item => Enumerable.Repeat(item, wantedDependencyMetricInvocation)).ToList();
+ expectedList.AddRange(expectedDependency);
+ }
+
+ var expectDict = new Dictionary, int>();
+ var actualDict = new Dictionary, int>();
+ if (expectedList.Count > 0)
+ {
+ expectDict = expectedList.GroupBy(kvp => kvp).ToDictionary(g => g.Key, g => g.Count());
+ }
+ if (GlobalCallbackData.CallList is not null)
+ {
+ actualDict = GlobalCallbackData.CallList.GroupBy(kvp => kvp).ToDictionary(g => g.Key, g => g.Count());
+ }
+
+ Assert.Equal(expectDict, actualDict);
+
+ }
+
+ private Dictionary buildMetricAttributes(bool containAttributes, Activity span)
+ {
+ Dictionary attributes = new Dictionary();
+ if (containAttributes)
+ {
+ if (AwsSpanProcessingUtil.ShouldGenerateDependencyMetricAttributes(span))
+ {
+ attributes.Add(IMetricAttributeGenerator.DependencyMetric, new ActivityTagsCollection([new KeyValuePair("new dependency key", "new dependency value")]));
+ }
+
+ if (AwsSpanProcessingUtil.ShouldGenerateServiceMetricAttributes(span))
+ {
+ attributes.Add(IMetricAttributeGenerator.ServiceMetric, new ActivityTagsCollection([new KeyValuePair("new service key", "new service value")]));
+ }
+ }
+ return attributes;
+ }
+
+ // Configure latency
+ private void setLatency(double latency = -1)
+ {
+ if (latency == -1)
+ {
+ latency = testLatencyMillis;
+ }
+ PropertyInfo spanDuration = typeof(Activity).GetProperty("Duration");
+ TimeSpan timeSpan = TimeSpan.FromMilliseconds(latency);
+ MethodInfo durationSetMethod = spanDuration?.GetSetMethod(nonPublic: true);
+ durationSetMethod.Invoke(spanDataMock, new object?[] { timeSpan });
+ }
+
}
+
+public static class GlobalCallbackData
+{
+ public static List> CallList { get; set; }
+
+ public static void Clear()
+ {
+ CallList = null;
+ }
+}
+public enum ExpectedStatusMetric {
+ ERROR = 0,
+ FAULT = 1,
+ NEITHER = 2
+}
+