diff --git a/CHANGELOG.md b/CHANGELOG.md
index b5b3250443..0a5f527175 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -21,3 +21,7 @@ If your change does not need a CHANGELOG entry, add the "skip changelog" label t
([#1201](https://github.com/aws-observability/aws-otel-java-instrumentation/pull/1201))
- Add support for new formal database semantic convention keys.
([#1162](https://github.com/aws-observability/aws-otel-java-instrumentation/pull/1162))
+
+### Refactors
+- Refactor AwsMetricAttributesSpanExporter to an onEnding processor
+ ([#1250](https://github.com/aws-observability/aws-otel-java-instrumentation/pull/1250))
diff --git a/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/httpservers/base/BaseHttpServerTest.java b/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/httpservers/base/BaseHttpServerTest.java
index ce55c23fe5..3fdba0e3a4 100644
--- a/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/httpservers/base/BaseHttpServerTest.java
+++ b/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/httpservers/base/BaseHttpServerTest.java
@@ -52,7 +52,7 @@
public abstract class BaseHttpServerTest extends ContractTestBase {
/**
- * Assert span attributes inserted by the AwsMetricAttributesSpanExporter
+ * Assert span attributes inserted by the AwsAttributeGeneratingSpanProcessor
*
* @param resourceScopeSpans list of spans that were exported by the application
* @param method the http method that was used (GET, PUT, DELETE...)
diff --git a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsApplicationSignalsCustomizerProvider.java b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsApplicationSignalsCustomizerProvider.java
index 1073b52bc3..a066954e40 100644
--- a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsApplicationSignalsCustomizerProvider.java
+++ b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsApplicationSignalsCustomizerProvider.java
@@ -76,7 +76,7 @@
*
Add AwsAttributeGeneratingSpanProcessor to add more attributes to all spans.
*
*
* You can control when these customizations are applied using the property
@@ -337,6 +337,9 @@ private SdkTracerProviderBuilder customizeTracerProviderBuilder(
// Construct and set local and remote attributes span processor
tracerProviderBuilder.addSpanProcessor(
AttributePropagatingSpanProcessorBuilder.create().build());
+ // Construct and set AWS Attribute Generating Span Processor
+ tracerProviderBuilder.addSpanProcessor(
+ AwsAttributeGeneratingSpanProcessorBuilder.create(ResourceHolder.getResource()).build());
// If running on Lambda, we just need to export 100% spans and skip generating any Application
// Signals metrics.
@@ -351,16 +354,9 @@ private SdkTracerProviderBuilder customizeTracerProviderBuilder(
.setEndpoint(tracesEndpoint)
.build();
- // Wrap the udp exporter with the AwsMetricsAttributesSpanExporter to add Application
- // Signals attributes to unsampled spans too
- SpanExporter appSignalsSpanExporter =
- AwsMetricAttributesSpanExporterBuilder.create(
- spanExporter, ResourceHolder.getResource())
- .build();
-
tracerProviderBuilder.addSpanProcessor(
AwsUnsampledOnlySpanProcessorBuilder.create()
- .setSpanExporter(appSignalsSpanExporter)
+ .setSpanExporter(spanExporter)
.setMaxExportBatchSize(LAMBDA_SPAN_EXPORT_BATCH_SIZE)
.build());
return tracerProviderBuilder;
@@ -461,12 +457,6 @@ SpanExporter customizeSpanExporter(SpanExporter spanExporter, ConfigProperties c
}
}
- if (isApplicationSignalsEnabled(configProps)) {
- spanExporter =
- AwsMetricAttributesSpanExporterBuilder.create(spanExporter, ResourceHolder.getResource())
- .build();
- }
-
if (this.sampler instanceof AwsXrayRemoteSampler) {
((AwsXrayRemoteSampler) this.sampler).setSpanExporter(spanExporter);
}
diff --git a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsAttributeGeneratingSpanProcessor.java b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsAttributeGeneratingSpanProcessor.java
new file mode 100644
index 0000000000..2c817a6267
--- /dev/null
+++ b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsAttributeGeneratingSpanProcessor.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.opentelemetry.javaagent.providers;
+
+import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_SPAN_KIND;
+
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.context.Context;
+import io.opentelemetry.sdk.resources.Resource;
+import io.opentelemetry.sdk.trace.ReadWriteSpan;
+import io.opentelemetry.sdk.trace.ReadableSpan;
+import io.opentelemetry.sdk.trace.data.SpanData;
+import io.opentelemetry.sdk.trace.internal.ExtendedSpanProcessor;
+import java.util.Map;
+
+public class AwsAttributeGeneratingSpanProcessor implements ExtendedSpanProcessor {
+
+ private final MetricAttributeGenerator generator;
+ private final Resource resource;
+
+ public static AwsAttributeGeneratingSpanProcessor create(
+ MetricAttributeGenerator generator, Resource resource) {
+ return new AwsAttributeGeneratingSpanProcessor(generator, resource);
+ }
+
+ private AwsAttributeGeneratingSpanProcessor(
+ MetricAttributeGenerator generator, Resource resource) {
+ this.generator = generator;
+ this.resource = resource;
+ }
+
+ @Override
+ public void onStart(Context parentContext, ReadWriteSpan span) {}
+
+ @Override
+ public boolean isStartRequired() {
+ return false;
+ }
+
+ @Override
+ public void onEnding(ReadWriteSpan span) {
+ // If the map has no items, no modifications are required. If there is one item, it means the
+ // span either produces Service or Dependency metric attributes, and in either case we want to
+ // modify the span with them. If there are two items, the span produces both Service and
+ // Dependency metric attributes indicating the span is a local dependency root. The Service
+ // Attributes must be a subset of the Dependency, with the exception of AWS_SPAN_KIND. The
+ // knowledge that the span is a local root is more important than knowing that it is a
+ // Dependency metric, so we take all the Dependency metrics but replace AWS_SPAN_KIND with
+ // LOCAL_ROOT.
+ SpanData spanData = span.toSpanData();
+ Map attributeMap =
+ generator.generateMetricAttributeMapFromSpan(span.toSpanData(), resource);
+
+ boolean generatesServiceMetrics =
+ AwsSpanProcessingUtil.shouldGenerateServiceMetricAttributes(spanData);
+ boolean generatesDependencyMetrics =
+ AwsSpanProcessingUtil.shouldGenerateDependencyMetricAttributes(spanData);
+
+ if (generatesServiceMetrics && generatesDependencyMetrics) {
+ // Order matters: dependency metric attributes include AWS_SPAN_KIND key
+ span.setAllAttributes(attributeMap.get(MetricAttributeGenerator.DEPENDENCY_METRIC));
+ span.setAttribute(AWS_SPAN_KIND, AwsSpanProcessingUtil.LOCAL_ROOT);
+ } else if (generatesServiceMetrics) {
+ span.setAllAttributes(attributeMap.get(MetricAttributeGenerator.SERVICE_METRIC));
+ } else if (generatesDependencyMetrics) {
+ span.setAllAttributes(attributeMap.get(MetricAttributeGenerator.DEPENDENCY_METRIC));
+ }
+ }
+
+ @Override
+ public boolean isOnEndingRequired() {
+ return true;
+ }
+
+ @Override
+ public void onEnd(ReadableSpan span) {}
+
+ @Override
+ public boolean isEndRequired() {
+ return false;
+ }
+}
diff --git a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsMetricAttributesSpanExporterBuilder.java b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsAttributeGeneratingSpanProcessorBuilder.java
similarity index 62%
rename from awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsMetricAttributesSpanExporterBuilder.java
rename to awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsAttributeGeneratingSpanProcessorBuilder.java
index 7de8b00f2a..c5670a2b7e 100644
--- a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsMetricAttributesSpanExporterBuilder.java
+++ b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsAttributeGeneratingSpanProcessorBuilder.java
@@ -19,43 +19,40 @@
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import io.opentelemetry.sdk.resources.Resource;
-import io.opentelemetry.sdk.trace.export.SpanExporter;
-public class AwsMetricAttributesSpanExporterBuilder {
+public class AwsAttributeGeneratingSpanProcessorBuilder {
// Defaults
private static final MetricAttributeGenerator DEFAULT_GENERATOR =
new AwsMetricAttributeGenerator();
// Required builder elements
- private final SpanExporter delegate;
private final Resource resource;
// Optional builder elements
private MetricAttributeGenerator generator = DEFAULT_GENERATOR;
- public static AwsMetricAttributesSpanExporterBuilder create(
- SpanExporter delegate, Resource resource) {
- return new AwsMetricAttributesSpanExporterBuilder(delegate, resource);
+ public static AwsAttributeGeneratingSpanProcessorBuilder create(Resource resource) {
+ return new AwsAttributeGeneratingSpanProcessorBuilder(resource);
}
- private AwsMetricAttributesSpanExporterBuilder(SpanExporter delegate, Resource resource) {
- this.delegate = delegate;
+ private AwsAttributeGeneratingSpanProcessorBuilder(Resource resource) {
this.resource = resource;
}
/**
- * Sets the generator used to generate attributes used spancs exported by the exporter. If unset,
+ * Sets the generator used to generate attributes added to spans in the processor. If unset,
* defaults to {@link #DEFAULT_GENERATOR}. Must not be null.
*/
@CanIgnoreReturnValue
- public AwsMetricAttributesSpanExporterBuilder setGenerator(MetricAttributeGenerator generator) {
+ public AwsAttributeGeneratingSpanProcessorBuilder setGenerator(
+ MetricAttributeGenerator generator) {
requireNonNull(generator, "generator");
this.generator = generator;
return this;
}
- public AwsMetricAttributesSpanExporter build() {
- return AwsMetricAttributesSpanExporter.create(delegate, generator, resource);
+ public AwsAttributeGeneratingSpanProcessor build() {
+ return AwsAttributeGeneratingSpanProcessor.create(generator, resource);
}
}
diff --git a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsMetricAttributesSpanExporter.java b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsMetricAttributesSpanExporter.java
deleted file mode 100644
index 0ff3e93c34..0000000000
--- a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsMetricAttributesSpanExporter.java
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright Amazon.com, Inc. or its affiliates.
- *
- * Licensed under the Apache License, Version 2.0 (the "License").
- * You may not use this file except in compliance with the License.
- * A copy of the License is located at
- *
- * http://aws.amazon.com/apache2.0
- *
- * or in the "license" file accompanying this file. This file is distributed
- * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
- * express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package software.amazon.opentelemetry.javaagent.providers;
-
-import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_SPAN_KIND;
-
-import io.opentelemetry.api.common.AttributeKey;
-import io.opentelemetry.api.common.Attributes;
-import io.opentelemetry.api.common.AttributesBuilder;
-import io.opentelemetry.sdk.common.CompletableResultCode;
-import io.opentelemetry.sdk.resources.Resource;
-import io.opentelemetry.sdk.trace.data.DelegatingSpanData;
-import io.opentelemetry.sdk.trace.data.SpanData;
-import io.opentelemetry.sdk.trace.export.SpanExporter;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import javax.annotation.concurrent.Immutable;
-
-/**
- * This exporter will update a span with metric attributes before exporting. It depends on a {@link
- * SpanExporter} being provided on instantiation, which the AwsSpanMetricsExporter will delegate
- * export to. Also, a {@link MetricAttributeGenerator} must be provided, which will provide a means
- * to determine attributes which should be applied to the span. Finally, a {@link Resource} must be
- * provided, which is used to generate metric attributes.
- *
- * This exporter should be coupled with the {@link AwsSpanMetricsProcessor} using the same {@link
- * MetricAttributeGenerator}. This will result in metrics and spans being produced with common
- * attributes.
- */
-@Immutable
-public class AwsMetricAttributesSpanExporter implements SpanExporter {
-
- private final SpanExporter delegate;
- private final MetricAttributeGenerator generator;
- private final Resource resource;
-
- /** Use {@link AwsMetricAttributesSpanExporterBuilder} to construct this exporter. */
- static AwsMetricAttributesSpanExporter create(
- SpanExporter delegate, MetricAttributeGenerator generator, Resource resource) {
- return new AwsMetricAttributesSpanExporter(delegate, generator, resource);
- }
-
- private AwsMetricAttributesSpanExporter(
- SpanExporter delegate, MetricAttributeGenerator generator, Resource resource) {
- this.delegate = delegate;
- this.generator = generator;
- this.resource = resource;
- }
-
- @Override
- public CompletableResultCode export(Collection spans) {
- List modifiedSpans = addMetricAttributes(spans);
- return delegate.export(modifiedSpans);
- }
-
- @Override
- public CompletableResultCode flush() {
- return delegate.flush();
- }
-
- @Override
- public CompletableResultCode shutdown() {
- return delegate.shutdown();
- }
-
- @Override
- public void close() {
- delegate.close();
- }
-
- private List addMetricAttributes(Collection spans) {
- List modifiedSpans = new ArrayList<>();
-
- for (SpanData span : spans) {
- // If the map has no items, no modifications are required. If there is one item, it means the
- // span either produces Service or Dependency metric attributes, and in either case we want to
- // modify the span with them. If there are two items, the span produces both Service and
- // Dependency metric attributes indicating the span is a local dependency root. The Service
- // Attributes must be a subset of the Dependency, with the exception of AWS_SPAN_KIND. The
- // knowledge that the span is a local root is more important that knowing that it is a
- // Dependency metric, so we take all the Dependency metrics but replace AWS_SPAN_KIND with
- // LOCAL_ROOT.
- Map attributeMap =
- generator.generateMetricAttributeMapFromSpan(span, resource);
- Attributes attributes = Attributes.empty();
-
- boolean generatesServiceMetrics =
- AwsSpanProcessingUtil.shouldGenerateServiceMetricAttributes(span);
- boolean generatesDependencyMetrics =
- AwsSpanProcessingUtil.shouldGenerateDependencyMetricAttributes(span);
-
- if (generatesServiceMetrics && generatesDependencyMetrics) {
- attributes =
- copyAttributesWithLocalRoot(
- attributeMap.get(MetricAttributeGenerator.DEPENDENCY_METRIC));
- } else if (generatesServiceMetrics) {
- attributes = attributeMap.get(MetricAttributeGenerator.SERVICE_METRIC);
- } else if (generatesDependencyMetrics) {
- attributes = attributeMap.get(MetricAttributeGenerator.DEPENDENCY_METRIC);
- }
-
- if (!attributes.isEmpty()) {
- span = wrapSpanWithAttributes(span, attributes);
- }
- modifiedSpans.add(span);
- }
-
- return modifiedSpans;
- }
-
- private Attributes copyAttributesWithLocalRoot(Attributes attributes) {
- AttributesBuilder builder = attributes.toBuilder();
- builder.remove(AWS_SPAN_KIND);
- builder.put(AWS_SPAN_KIND, AwsSpanProcessingUtil.LOCAL_ROOT);
- return builder.build();
- }
-
- /**
- * {@link #export} works with a {@link SpanData}, which does not permit modification. However, we
- * need to add derived metric attributes to the span. To work around this, we will wrap the
- * SpanData with a {@link DelegatingSpanData} that simply passes through all API calls, except for
- * those pertaining to Attributes, i.e. {@link SpanData#getAttributes()} and {@link
- * SpanData#getTotalAttributeCount} APIs.
- *
- * See https://github.com/open-telemetry/opentelemetry-specification/issues/1089 for more
- * context on this approach.
- */
- private static SpanData wrapSpanWithAttributes(SpanData span, Attributes attributes) {
- Attributes originalAttributes = span.getAttributes();
- Attributes replacementAttributes = originalAttributes.toBuilder().putAll(attributes).build();
-
- int newAttributeKeyCount = 0;
- for (Entry, Object> entry : attributes.asMap().entrySet()) {
- if (originalAttributes.get(entry.getKey()) == null) {
- newAttributeKeyCount++;
- }
- }
- int originalTotalAttributeCount = span.getTotalAttributeCount();
- int replacementTotalAttributeCount = originalTotalAttributeCount + newAttributeKeyCount;
-
- return new DelegatingSpanData(span) {
- @Override
- public Attributes getAttributes() {
- return replacementAttributes;
- }
-
- @Override
- public int getTotalAttributeCount() {
- return replacementTotalAttributeCount;
- }
- };
- }
-}
diff --git a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/MetricAttributeGenerator.java b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/MetricAttributeGenerator.java
index cb3a5f7cae..d94d73c9de 100644
--- a/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/MetricAttributeGenerator.java
+++ b/awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/MetricAttributeGenerator.java
@@ -23,7 +23,7 @@
/**
* Metric attribute generator defines an interface for classes that can generate specific attributes
* to be used by an {@link AwsSpanMetricsProcessor} to produce metrics and by {@link
- * AwsMetricAttributesSpanExporter} to wrap the original span.
+ * AwsAttributeGeneratingSpanProcessor} to update the original span.
*/
public interface MetricAttributeGenerator {
static final String SERVICE_METRIC = "Service";
diff --git a/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/AwsApplicationSignalsCustomizerTest.java b/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/AwsApplicationSignalsCustomizerTest.java
index 1732c95627..0f1d9663b8 100644
--- a/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/AwsApplicationSignalsCustomizerTest.java
+++ b/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/AwsApplicationSignalsCustomizerTest.java
@@ -195,7 +195,7 @@ void testEnableApplicationSignalsSpanExporter() {
"otlp"),
defaultHttpSpanExporter,
this.provider::customizeSpanExporter,
- AwsMetricAttributesSpanExporter.class);
+ OtlpHttpSpanExporter.class);
}
@Test
@@ -212,7 +212,7 @@ void testSigv4ShouldNotDisableApplicationSignalsSpanExporter() {
"otlp"),
defaultHttpSpanExporter,
this.provider::customizeSpanExporter,
- AwsMetricAttributesSpanExporter.class);
+ OtlpAwsSpanExporter.class);
}
private static void customizeExporterTest(
diff --git a/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/AwsAttributeGeneratingSpanProcessorTest.java b/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/AwsAttributeGeneratingSpanProcessorTest.java
new file mode 100644
index 0000000000..29d2f918ae
--- /dev/null
+++ b/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/AwsAttributeGeneratingSpanProcessorTest.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.opentelemetry.javaagent.providers;
+
+import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION;
+import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MessagingOperationTypeIncubatingValues.PROCESS;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.*;
+import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LOCAL_SERVICE;
+import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_SPAN_KIND;
+import static software.amazon.opentelemetry.javaagent.providers.MetricAttributeGenerator.DEPENDENCY_METRIC;
+import static software.amazon.opentelemetry.javaagent.providers.MetricAttributeGenerator.SERVICE_METRIC;
+
+import io.opentelemetry.api.common.AttributeKey;
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.api.trace.SpanContext;
+import io.opentelemetry.api.trace.SpanKind;
+import io.opentelemetry.api.trace.Tracer;
+import io.opentelemetry.context.Context;
+import io.opentelemetry.sdk.resources.Resource;
+import io.opentelemetry.sdk.trace.ReadWriteSpan;
+import io.opentelemetry.sdk.trace.ReadableSpan;
+import io.opentelemetry.sdk.trace.SdkTracerProvider;
+import io.opentelemetry.sdk.trace.data.SpanData;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+/** Unit tests for {@link AwsAttributeGeneratingSpanProcessor}. */
+class AwsAttributeGeneratingSpanProcessorTest {
+
+ private static final Resource testResource = Resource.empty();
+
+ private Tracer tracer;
+ private MetricAttributeGenerator generatorMock;
+
+ @BeforeEach
+ public void setup() {
+ MockitoAnnotations.openMocks(this);
+ generatorMock = mock(MetricAttributeGenerator.class);
+
+ tracer =
+ SdkTracerProvider.builder()
+ .addSpanProcessor(
+ AwsAttributeGeneratingSpanProcessor.create(generatorMock, testResource))
+ .build()
+ .get("awsxray");
+ }
+
+ @Test
+ public void testOnEndingGeneratesBothWithoutOverride() {
+ Span span = tracer.spanBuilder("test").setSpanKind(SpanKind.CLIENT).startSpan();
+
+ // Mock attributes returned by generator
+ Attributes metricAttributes =
+ Attributes.of(
+ AWS_LOCAL_SERVICE,
+ "SERVICE",
+ AWS_SPAN_KIND,
+ SpanKind.CLIENT.name(),
+ AttributeKey.stringKey("key"),
+ "val");
+ Map attributeMap = new HashMap<>();
+ attributeMap.put(DEPENDENCY_METRIC, metricAttributes);
+ when(generatorMock.generateMetricAttributeMapFromSpan(any(SpanData.class), any(Resource.class)))
+ .thenReturn(attributeMap);
+
+ // End span and verify it was modified appropriately
+ span.end();
+ ReadableSpan readableSpan = (ReadableSpan) span;
+ assertThat(readableSpan.getAttribute(AWS_SPAN_KIND))
+ .isEqualTo(AwsSpanProcessingUtil.LOCAL_ROOT);
+ assertThat(readableSpan.getAttribute(AWS_LOCAL_SERVICE)).isEqualTo("SERVICE");
+ assertThat(readableSpan.getAttributes().size()).isEqualTo(metricAttributes.size());
+ }
+
+ @Test
+ public void testOnEndingGeneratesBothWithOverride() {
+ Span span =
+ tracer
+ .spanBuilder("test")
+ .setSpanKind(SpanKind.CLIENT)
+ .setAttribute("start_key", "start_val")
+ .setAttribute("was_changed", false)
+ .startSpan();
+
+ // Mock attributes returned by generator
+ Attributes metricAttributes =
+ Attributes.builder()
+ .put(AWS_LOCAL_SERVICE, "SERVICE")
+ .put(AWS_SPAN_KIND, SpanKind.CLIENT.name())
+ .put("ending_key", "ending_val")
+ .put("was_changed", true)
+ .build();
+ Map attributeMap = new HashMap<>();
+ attributeMap.put(DEPENDENCY_METRIC, metricAttributes);
+ when(generatorMock.generateMetricAttributeMapFromSpan(any(SpanData.class), any(Resource.class)))
+ .thenReturn(attributeMap);
+
+ // End span and verify it was modified appropriately
+ span.end();
+ ReadableSpan readableSpan = (ReadableSpan) span;
+ assertThat(readableSpan.getAttribute(AWS_SPAN_KIND))
+ .isEqualTo(AwsSpanProcessingUtil.LOCAL_ROOT);
+ assertThat(readableSpan.getAttribute(AWS_LOCAL_SERVICE)).isEqualTo("SERVICE");
+ assertThat(readableSpan.getAttribute(AttributeKey.booleanKey("was_changed"))).isEqualTo(true);
+ // 2 start attributes + 4 ending attributes - 1 overlap
+ assertThat(readableSpan.getAttributes().size()).isEqualTo(5);
+ }
+
+ @Test
+ public void testOnEndingGeneratesService() {
+ Span span = tracer.spanBuilder("test").setSpanKind(SpanKind.SERVER).startSpan();
+
+ // Mock attributes returned by generator
+ Attributes metricAttributes =
+ Attributes.of(AWS_LOCAL_SERVICE, "SERVICE", AWS_SPAN_KIND, SpanKind.SERVER.name());
+ Map attributeMap = new HashMap<>();
+ attributeMap.put(SERVICE_METRIC, metricAttributes);
+ when(generatorMock.generateMetricAttributeMapFromSpan(any(SpanData.class), any(Resource.class)))
+ .thenReturn(attributeMap);
+
+ // End span and verify it was modified appropriately
+ span.end();
+ ReadableSpan readableSpan = (ReadableSpan) span;
+ assertThat(readableSpan.getAttribute(AWS_LOCAL_SERVICE)).isEqualTo("SERVICE");
+ assertThat(readableSpan.getAttribute(AWS_SPAN_KIND)).isEqualTo(SpanKind.SERVER.name());
+ assertThat(readableSpan.getAttributes().size()).isEqualTo(2);
+ }
+
+ @Test
+ public void testOnEndingGeneratesDependency() {
+ Span span =
+ tracer
+ .spanBuilder("test")
+ .setSpanKind(SpanKind.CONSUMER)
+ .setParent(mock(Context.class))
+ .startSpan();
+
+ // Mock attributes returned by generator
+ Attributes metricAttributes =
+ Attributes.of(AWS_LOCAL_SERVICE, "SERVICE", AWS_SPAN_KIND, SpanKind.CONSUMER.name());
+ Map attributeMap = new HashMap<>();
+ attributeMap.put(DEPENDENCY_METRIC, metricAttributes);
+ when(generatorMock.generateMetricAttributeMapFromSpan(any(SpanData.class), any(Resource.class)))
+ .thenReturn(attributeMap);
+
+ // Ensure span is treated as only having dependency metric information
+ try (MockedStatic mockUtil =
+ Mockito.mockStatic(AwsSpanProcessingUtil.class)) {
+ mockUtil
+ .when(
+ () ->
+ AwsSpanProcessingUtil.shouldGenerateServiceMetricAttributes(any(SpanData.class)))
+ .thenReturn(false);
+ mockUtil
+ .when(
+ () ->
+ AwsSpanProcessingUtil.shouldGenerateDependencyMetricAttributes(
+ any(SpanData.class)))
+ .thenReturn(true);
+
+ // End span and verify it was modified appropriately
+ span.end();
+ ReadableSpan readableSpan = (ReadableSpan) span;
+ assertThat(readableSpan.getAttribute(AWS_LOCAL_SERVICE)).isEqualTo("SERVICE");
+ assertThat(readableSpan.getAttribute(AWS_SPAN_KIND)).isEqualTo(SpanKind.CONSUMER.name());
+ assertThat(readableSpan.getAttributes().size()).isEqualTo(2);
+ }
+ }
+
+ @Test
+ public void testOnEndingGeneratesBothWithTwoMetrics() {
+ Span span = tracer.spanBuilder("test").setSpanKind(SpanKind.CLIENT).startSpan();
+
+ // Mock attributes returned by generator
+ Map attributeMap = new HashMap<>();
+ Attributes serviceMetricAttributes =
+ Attributes.of(AttributeKey.stringKey("new service key"), "new service value");
+ attributeMap.put(SERVICE_METRIC, serviceMetricAttributes);
+ Attributes dependencyMetricAttributes =
+ Attributes.of(
+ AttributeKey.stringKey("new dependency key"),
+ "new dependency value",
+ AWS_SPAN_KIND,
+ SpanKind.PRODUCER.name());
+ attributeMap.put(DEPENDENCY_METRIC, dependencyMetricAttributes);
+ when(generatorMock.generateMetricAttributeMapFromSpan(any(SpanData.class), any(Resource.class)))
+ .thenReturn(attributeMap);
+
+ // End span and verify it was modified appropriately
+ span.end();
+ ReadableSpan readableSpan = (ReadableSpan) span;
+ assertThat(readableSpan.getAttribute(AWS_SPAN_KIND))
+ .isEqualTo(AwsSpanProcessingUtil.LOCAL_ROOT);
+ assertThat(readableSpan.getAttribute(AttributeKey.stringKey("new dependency key")))
+ .isEqualTo("new dependency value");
+ assertThat(readableSpan.getAttributes().size()).isEqualTo(dependencyMetricAttributes.size());
+ }
+
+ @Test
+ public void testConsumerSpanHasEmptyAttributes() {
+ AwsAttributeGeneratingSpanProcessor processor =
+ AwsAttributeGeneratingSpanProcessorBuilder.create(Resource.create(Attributes.empty()))
+ .build();
+
+ Attributes attributesMock = mock(Attributes.class);
+ SpanData spanDataMock = mock(SpanData.class);
+ SpanContext parentSpanContextMock = mock(SpanContext.class);
+ ReadWriteSpan readWriteSpanMock = mock(ReadWriteSpan.class);
+
+ when(attributesMock.get(AwsAttributeKeys.AWS_CONSUMER_PARENT_SPAN_KIND))
+ .thenReturn(SpanKind.CONSUMER.name());
+ when(attributesMock.get(MESSAGING_OPERATION)).thenReturn(PROCESS);
+ when(spanDataMock.getKind()).thenReturn(SpanKind.CONSUMER);
+ when(spanDataMock.getAttributes()).thenReturn(attributesMock);
+ when(spanDataMock.getParentSpanContext()).thenReturn(parentSpanContextMock);
+ when(parentSpanContextMock.isValid()).thenReturn(true);
+ when(parentSpanContextMock.isRemote()).thenReturn(false);
+ when(readWriteSpanMock.toSpanData()).thenReturn(spanDataMock);
+
+ // The dependencyAttributesMock will only be used if
+ // AwsSpanProcessingUtil.shouldGenerateDependencyMetricAttributes(span) is true.
+ // It shouldn't have any interaction since the spanData is a consumer process with parent span
+ // of consumer
+ Map attributeMap = new HashMap<>();
+ Attributes dependencyAttributesMock = mock(Attributes.class);
+ attributeMap.put(DEPENDENCY_METRIC, dependencyAttributesMock);
+ // Configure generated attributes
+ when(generatorMock.generateMetricAttributeMapFromSpan(eq(spanDataMock), eq(testResource)))
+ .thenReturn(attributeMap);
+
+ // End span and verify it was modified appropriately
+ processor.onEnding(readWriteSpanMock);
+ verify(readWriteSpanMock, times(0)).setAttribute(eq(AWS_SPAN_KIND), any());
+ verify(readWriteSpanMock, times(0)).setAllAttributes(any());
+ }
+}
diff --git a/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/AwsMetricAttributesSpanExporterTest.java b/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/AwsMetricAttributesSpanExporterTest.java
deleted file mode 100644
index f2c0a9d01b..0000000000
--- a/awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/AwsMetricAttributesSpanExporterTest.java
+++ /dev/null
@@ -1,487 +0,0 @@
-/*
- * Copyright Amazon.com, Inc. or its affiliates.
- *
- * Licensed under the Apache License, Version 2.0 (the "License").
- * You may not use this file except in compliance with the License.
- * A copy of the License is located at
- *
- * http://aws.amazon.com/apache2.0
- *
- * or in the "license" file accompanying this file. This file is distributed
- * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
- * express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package software.amazon.opentelemetry.javaagent.providers;
-
-import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MESSAGING_OPERATION;
-import static io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes.MessagingOperationTypeIncubatingValues.PROCESS;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoInteractions;
-import static org.mockito.Mockito.when;
-import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_SPAN_KIND;
-import static software.amazon.opentelemetry.javaagent.providers.MetricAttributeGenerator.DEPENDENCY_METRIC;
-import static software.amazon.opentelemetry.javaagent.providers.MetricAttributeGenerator.SERVICE_METRIC;
-
-import io.opentelemetry.api.common.AttributeKey;
-import io.opentelemetry.api.common.Attributes;
-import io.opentelemetry.api.trace.SpanContext;
-import io.opentelemetry.api.trace.SpanKind;
-import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
-import io.opentelemetry.sdk.resources.Resource;
-import io.opentelemetry.sdk.trace.data.EventData;
-import io.opentelemetry.sdk.trace.data.LinkData;
-import io.opentelemetry.sdk.trace.data.SpanData;
-import io.opentelemetry.sdk.trace.data.StatusData;
-import io.opentelemetry.sdk.trace.export.SpanExporter;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.MockitoAnnotations;
-
-/** Unit tests for {@link AwsSpanMetricsProcessor}. */
-class AwsMetricAttributesSpanExporterTest {
-
- @Captor private static ArgumentCaptor> delegateExportCaptor;
-
- // Test constants
- private static final boolean CONTAINS_ATTRIBUTES = true;
- private static final boolean CONTAINS_NO_ATTRIBUTES = false;
-
- // Resource is not mockable, but tests can safely rely on an empty resource.
- private static final Resource testResource = Resource.empty();
-
- // Mocks required for tests.
- private MetricAttributeGenerator generatorMock;
- private SpanExporter delegateMock;
-
- private AwsMetricAttributesSpanExporter awsMetricAttributesSpanExporter;
-
- @BeforeEach
- public void setUpMocks() {
- MockitoAnnotations.openMocks(this);
- generatorMock = mock(MetricAttributeGenerator.class);
- delegateMock = mock(SpanExporter.class);
-
- awsMetricAttributesSpanExporter =
- AwsMetricAttributesSpanExporter.create(delegateMock, generatorMock, testResource);
- }
-
- @Test
- public void testPassthroughDelegations() {
- awsMetricAttributesSpanExporter.flush();
- awsMetricAttributesSpanExporter.shutdown();
- awsMetricAttributesSpanExporter.close();
- verify(delegateMock, times(1)).flush();
- verify(delegateMock, times(1)).shutdown();
- verify(delegateMock, times(1)).close();
- }
-
- @Test
- public void testExportDelegationWithoutAttributeOrModification() {
- Attributes spanAttributes = buildSpanAttributes(CONTAINS_NO_ATTRIBUTES);
- SpanData spanDataMock = buildSpanDataMock(spanAttributes);
- Attributes metricAttributes = buildMetricAttributes(CONTAINS_NO_ATTRIBUTES);
- configureMocksForExport(spanDataMock, metricAttributes);
-
- awsMetricAttributesSpanExporter.export(Collections.singletonList(spanDataMock));
- verify(delegateMock, times(1)).export(delegateExportCaptor.capture());
- Collection exportedSpans = delegateExportCaptor.getValue();
- assertThat(exportedSpans.size()).isEqualTo(1);
-
- SpanData exportedSpan = (SpanData) exportedSpans.toArray()[0];
- assertThat(exportedSpan).isEqualTo(spanDataMock);
- }
-
- @Test
- public void testExportDelegationWithAttributeButWithoutModification() {
- Attributes spanAttributes = buildSpanAttributes(CONTAINS_ATTRIBUTES);
- SpanData spanDataMock = buildSpanDataMock(spanAttributes);
- Attributes metricAttributes = buildMetricAttributes(CONTAINS_NO_ATTRIBUTES);
- configureMocksForExport(spanDataMock, metricAttributes);
-
- awsMetricAttributesSpanExporter.export(Collections.singletonList(spanDataMock));
- verify(delegateMock, times(1)).export(delegateExportCaptor.capture());
- Collection exportedSpans = delegateExportCaptor.getValue();
- assertThat(exportedSpans.size()).isEqualTo(1);
-
- SpanData exportedSpan = (SpanData) exportedSpans.toArray()[0];
- assertThat(exportedSpan).isEqualTo(spanDataMock);
- }
-
- @Test
- public void testExportDelegationWithoutAttributeButWithModification() {
- Attributes spanAttributes = buildSpanAttributes(CONTAINS_NO_ATTRIBUTES);
- SpanData spanDataMock = buildSpanDataMock(spanAttributes);
- Attributes metricAttributes = buildMetricAttributes(CONTAINS_ATTRIBUTES);
- configureMocksForExport(spanDataMock, metricAttributes);
-
- awsMetricAttributesSpanExporter.export(Collections.singletonList(spanDataMock));
- verify(delegateMock, times(1)).export(delegateExportCaptor.capture());
- List exportedSpans = (List) delegateExportCaptor.getValue();
- assertThat(exportedSpans.size()).isEqualTo(1);
-
- SpanData exportedSpan = exportedSpans.get(0);
- assertThat(exportedSpan.getClass()).isNotEqualTo(spanDataMock.getClass());
- assertThat(exportedSpan.getTotalAttributeCount()).isEqualTo(metricAttributes.size());
- Attributes exportedAttributes = exportedSpan.getAttributes();
- assertThat(exportedAttributes.size()).isEqualTo(metricAttributes.size());
- metricAttributes.forEach((k, v) -> assertThat(exportedAttributes.get(k)).isEqualTo(v));
- }
-
- @Test
- public void testExportDelegationWithAttributeAndModification() {
- Attributes spanAttributes = buildSpanAttributes(CONTAINS_ATTRIBUTES);
- SpanData spanDataMock = buildSpanDataMock(spanAttributes);
- Attributes metricAttributes = buildMetricAttributes(CONTAINS_ATTRIBUTES);
- configureMocksForExport(spanDataMock, metricAttributes);
-
- awsMetricAttributesSpanExporter.export(Collections.singletonList(spanDataMock));
- verify(delegateMock, times(1)).export(delegateExportCaptor.capture());
- List exportedSpans = (List) delegateExportCaptor.getValue();
- assertThat(exportedSpans.size()).isEqualTo(1);
-
- SpanData exportedSpan = exportedSpans.get(0);
- assertThat(exportedSpan.getClass()).isNotEqualTo(spanDataMock.getClass());
- int expectedAttributeCount = metricAttributes.size() + spanAttributes.size();
- assertThat(exportedSpan.getTotalAttributeCount()).isEqualTo(expectedAttributeCount);
- Attributes exportedAttributes = exportedSpan.getAttributes();
- assertThat(exportedAttributes.size()).isEqualTo(expectedAttributeCount);
- spanAttributes.forEach((k, v) -> assertThat(exportedAttributes.get(k)).isEqualTo(v));
- metricAttributes.forEach((k, v) -> assertThat(exportedAttributes.get(k)).isEqualTo(v));
- }
-
- @Test
- public void testExportDelegationWithMultipleSpans() {
- Attributes spanAttributes1 = buildSpanAttributes(CONTAINS_NO_ATTRIBUTES);
- SpanData spanDataMock1 = buildSpanDataMock(spanAttributes1);
- Attributes metricAttributes1 = buildMetricAttributes(CONTAINS_NO_ATTRIBUTES);
- configureMocksForExport(spanDataMock1, metricAttributes1);
-
- Attributes spanAttributes2 = buildSpanAttributes(CONTAINS_ATTRIBUTES);
- SpanData spanDataMock2 = buildSpanDataMock(spanAttributes2);
- Attributes metricAttributes2 = buildMetricAttributes(CONTAINS_ATTRIBUTES);
- configureMocksForExport(spanDataMock2, metricAttributes2);
-
- Attributes spanAttributes3 = buildSpanAttributes(CONTAINS_ATTRIBUTES);
- SpanData spanDataMock3 = buildSpanDataMock(spanAttributes3);
- Attributes metricAttributes3 = buildMetricAttributes(CONTAINS_NO_ATTRIBUTES);
- configureMocksForExport(spanDataMock3, metricAttributes3);
-
- awsMetricAttributesSpanExporter.export(
- Arrays.asList(spanDataMock1, spanDataMock2, spanDataMock3));
- verify(delegateMock, times(1)).export(delegateExportCaptor.capture());
- List exportedSpans = (List) delegateExportCaptor.getValue();
- assertThat(exportedSpans.size()).isEqualTo(3);
-
- SpanData exportedSpan1 = exportedSpans.get(0);
- SpanData exportedSpan2 = exportedSpans.get(1);
- SpanData exportedSpan3 = exportedSpans.get(2);
-
- assertThat(exportedSpan1).isEqualTo(spanDataMock1);
- assertThat(exportedSpan3).isEqualTo(spanDataMock3);
-
- assertThat(exportedSpan2.getClass()).isNotEqualTo(spanDataMock2.getClass());
- int expectedAttributeCount = metricAttributes2.size() + spanAttributes2.size();
- assertThat(exportedSpan2.getTotalAttributeCount()).isEqualTo(expectedAttributeCount);
- Attributes exportedAttributes = exportedSpan2.getAttributes();
- assertThat(exportedAttributes.size()).isEqualTo(expectedAttributeCount);
- spanAttributes2.forEach((k, v) -> assertThat(exportedAttributes.get(k)).isEqualTo(v));
- metricAttributes2.forEach((k, v) -> assertThat(exportedAttributes.get(k)).isEqualTo(v));
- }
-
- @Test
- public void testOverridenAttributes() {
- Attributes spanAttributes =
- Attributes.of(
- AttributeKey.stringKey("key1"),
- "old value1",
- AttributeKey.stringKey("key2"),
- "old value2");
- SpanData spanDataMock = buildSpanDataMock(spanAttributes);
- Attributes metricAttributes =
- Attributes.of(
- AttributeKey.stringKey("key1"),
- "new value1",
- AttributeKey.stringKey("key3"),
- "new value3");
- configureMocksForExport(spanDataMock, metricAttributes);
-
- awsMetricAttributesSpanExporter.export(Collections.singletonList(spanDataMock));
- verify(delegateMock, times(1)).export(delegateExportCaptor.capture());
- List exportedSpans = (List) delegateExportCaptor.getValue();
- assertThat(exportedSpans.size()).isEqualTo(1);
-
- SpanData exportedSpan = exportedSpans.get(0);
- assertThat(exportedSpan.getClass()).isNotEqualTo(spanDataMock.getClass());
- assertThat(exportedSpan.getTotalAttributeCount()).isEqualTo(3);
- Attributes exportedAttributes = exportedSpan.getAttributes();
- assertThat(exportedAttributes.size()).isEqualTo(3);
- assertThat(exportedAttributes.get(AttributeKey.stringKey("key1"))).isEqualTo("new value1");
- assertThat(exportedAttributes.get(AttributeKey.stringKey("key2"))).isEqualTo("old value2");
- assertThat(exportedAttributes.get(AttributeKey.stringKey("key3"))).isEqualTo("new value3");
- }
-
- @Test
- public void testExportDelegatingSpanDataBehaviour() {
- Attributes spanAttributes = buildSpanAttributes(CONTAINS_ATTRIBUTES);
- SpanData spanDataMock = buildSpanDataMock(spanAttributes);
- Attributes metricAttributes = buildMetricAttributes(CONTAINS_ATTRIBUTES);
- configureMocksForExport(spanDataMock, metricAttributes);
-
- awsMetricAttributesSpanExporter.export(Collections.singletonList(spanDataMock));
- verify(delegateMock, times(1)).export(delegateExportCaptor.capture());
- List exportedSpans = (List) delegateExportCaptor.getValue();
- assertThat(exportedSpans.size()).isEqualTo(1);
-
- SpanData exportedSpan = exportedSpans.get(0);
-
- SpanContext spanContextMock = mock(SpanContext.class);
- when(spanDataMock.getSpanContext()).thenReturn(spanContextMock);
- assertThat(exportedSpan.getSpanContext()).isEqualTo(spanContextMock);
-
- SpanContext parentSpanContextMock = mock(SpanContext.class);
- when(spanDataMock.getParentSpanContext()).thenReturn(parentSpanContextMock);
- assertThat(exportedSpan.getParentSpanContext()).isEqualTo(parentSpanContextMock);
-
- when(spanDataMock.getResource()).thenReturn(testResource);
- assertThat(exportedSpan.getResource()).isEqualTo(testResource);
-
- // InstrumentationLibraryInfo is deprecated, so actually invoking it causes build failures.
- // Excluding from this test.
-
- InstrumentationScopeInfo testInstrumentationScopeInfo = InstrumentationScopeInfo.empty();
- when(spanDataMock.getInstrumentationScopeInfo()).thenReturn(testInstrumentationScopeInfo);
- assertThat(exportedSpan.getInstrumentationScopeInfo()).isEqualTo(testInstrumentationScopeInfo);
-
- String testName = "name";
- when(spanDataMock.getName()).thenReturn(testName);
- assertThat(exportedSpan.getName()).isEqualTo(testName);
-
- SpanKind kindMock = mock(SpanKind.class);
- when(spanDataMock.getKind()).thenReturn(kindMock);
- assertThat(exportedSpan.getKind()).isEqualTo(kindMock);
-
- long testStartEpochNanos = 1L;
- when(spanDataMock.getStartEpochNanos()).thenReturn(testStartEpochNanos);
- assertThat(exportedSpan.getStartEpochNanos()).isEqualTo(testStartEpochNanos);
-
- List eventsMock = Collections.singletonList(mock(EventData.class));
- when(spanDataMock.getEvents()).thenReturn(eventsMock);
- assertThat(exportedSpan.getEvents()).isEqualTo(eventsMock);
-
- List linksMock = Collections.singletonList(mock(LinkData.class));
- when(spanDataMock.getLinks()).thenReturn(linksMock);
- assertThat(exportedSpan.getLinks()).isEqualTo(linksMock);
-
- StatusData statusMock = mock(StatusData.class);
- when(spanDataMock.getStatus()).thenReturn(statusMock);
- assertThat(exportedSpan.getStatus()).isEqualTo(statusMock);
-
- long testEndEpochNanosMock = 2L;
- when(spanDataMock.getEndEpochNanos()).thenReturn(testEndEpochNanosMock);
- assertThat(exportedSpan.getEndEpochNanos()).isEqualTo(testEndEpochNanosMock);
-
- when(spanDataMock.hasEnded()).thenReturn(true);
- assertThat(exportedSpan.hasEnded()).isEqualTo(true);
-
- int testTotalRecordedEventsMock = 3;
- when(spanDataMock.getTotalRecordedEvents()).thenReturn(testTotalRecordedEventsMock);
- assertThat(exportedSpan.getTotalRecordedEvents()).isEqualTo(testTotalRecordedEventsMock);
-
- int testTotalRecordedLinksMock = 4;
- when(spanDataMock.getTotalRecordedLinks()).thenReturn(testTotalRecordedLinksMock);
- assertThat(exportedSpan.getTotalRecordedLinks()).isEqualTo(testTotalRecordedLinksMock);
- }
-
- @Test
- public void testExportDelegationWithTwoMetrics() {
- // Original Span Attribute
- Attributes spanAttributes = buildSpanAttributes(CONTAINS_ATTRIBUTES);
-
- // Create new span data mock
- SpanData spanDataMock = mock(SpanData.class);
- when(spanDataMock.getAttributes()).thenReturn(spanAttributes);
- when(spanDataMock.getTotalAttributeCount()).thenReturn(spanAttributes.size());
- when(spanDataMock.getKind()).thenReturn(SpanKind.PRODUCER);
- when(spanDataMock.getParentSpanContext()).thenReturn(null);
-
- // Create mock for the generateMetricAttributeMapFromSpan. Returns both dependency and service
- // metric
- Map attributeMap = new HashMap<>();
- Attributes serviceMtricAttributes =
- Attributes.of(AttributeKey.stringKey("new service key"), "new service value");
- attributeMap.put(SERVICE_METRIC, serviceMtricAttributes);
-
- Attributes dependencyMetricAttributes =
- Attributes.of(
- AttributeKey.stringKey("new dependency key"),
- "new dependency value",
- AWS_SPAN_KIND,
- SpanKind.PRODUCER.name());
- attributeMap.put(DEPENDENCY_METRIC, dependencyMetricAttributes);
-
- when(generatorMock.generateMetricAttributeMapFromSpan(eq(spanDataMock), eq(testResource)))
- .thenReturn(attributeMap);
-
- awsMetricAttributesSpanExporter.export(Collections.singletonList(spanDataMock));
- verify(delegateMock, times(1)).export(delegateExportCaptor.capture());
- List exportedSpans = (List) delegateExportCaptor.getValue();
- assertThat(exportedSpans.size()).isEqualTo(1);
-
- // Retrieve the returned span
- SpanData exportedSpan = exportedSpans.get(0);
-
- // Check the number of attributes
- int expectedAttributeCount = dependencyMetricAttributes.size() + spanAttributes.size();
- assertThat(exportedSpan.getTotalAttributeCount()).isEqualTo(expectedAttributeCount);
- Attributes exportedAttributes = exportedSpan.getAttributes();
- assertThat(exportedAttributes.size()).isEqualTo(expectedAttributeCount);
-
- // Check that all expected attributes are present
- spanAttributes.forEach((k, v) -> assertThat(exportedAttributes.get(k)).isEqualTo(v));
- dependencyMetricAttributes.forEach(
- (k, v) -> {
- if (k.equals(AWS_SPAN_KIND)) {
- assertThat(exportedAttributes.get(k)).isNotEqualTo(v);
- } else {
- assertThat(exportedAttributes.get(k)).isEqualTo(v);
- }
- });
- assertThat(exportedAttributes.get(AwsAttributeKeys.AWS_SPAN_KIND))
- .isEqualTo(AwsSpanProcessingUtil.LOCAL_ROOT);
- }
-
- @Test
- public void testConsumerProcessSpanHasEmptyAttribute() {
- Attributes attributesMock = mock(Attributes.class);
- SpanData spanDataMock = mock(SpanData.class);
- SpanContext parentSpanContextMock = mock(SpanContext.class);
-
- when(attributesMock.get(AwsAttributeKeys.AWS_CONSUMER_PARENT_SPAN_KIND))
- .thenReturn(SpanKind.CONSUMER.name());
- when(attributesMock.get(MESSAGING_OPERATION)).thenReturn(PROCESS);
- when(spanDataMock.getKind()).thenReturn(SpanKind.CONSUMER);
- when(spanDataMock.getAttributes()).thenReturn(attributesMock);
- when(spanDataMock.getParentSpanContext()).thenReturn(parentSpanContextMock);
- when(parentSpanContextMock.isValid()).thenReturn(true);
- when(parentSpanContextMock.isRemote()).thenReturn(false);
-
- // The dependencyAttributesMock will only be used if
- // AwsSpanProcessingUtil.shouldGenerateDependencyMetricAttributes(span) is true.
- // It shouldn't have any interaction since the spanData is a consumer process with parent span
- // of consumer
- Map attributeMap = new HashMap<>();
- Attributes dependencyAttributesMock = mock(Attributes.class);
- attributeMap.put(DEPENDENCY_METRIC, dependencyAttributesMock);
- // Configure generated attributes
- when(generatorMock.generateMetricAttributeMapFromSpan(eq(spanDataMock), eq(testResource)))
- .thenReturn(attributeMap);
-
- awsMetricAttributesSpanExporter.export(Collections.singletonList(spanDataMock));
- verify(delegateMock, times(1)).export(delegateExportCaptor.capture());
- Collection exportedSpans = delegateExportCaptor.getValue();
- assertThat(exportedSpans.size()).isEqualTo(1);
-
- verifyNoInteractions(dependencyAttributesMock);
-
- SpanData exportedSpan = (SpanData) exportedSpans.toArray()[0];
- assertThat(exportedSpan).isEqualTo(spanDataMock);
- }
-
- @Test
- public void testExportDelegationWithDependencyMetrics() {
- // Original Span Attribute
- Attributes spanAttributes = buildSpanAttributes(CONTAINS_ATTRIBUTES);
-
- // Create new span data mock
- SpanData spanDataMock = mock(SpanData.class);
- SpanContext spanContextMock = mock(SpanContext.class);
- when(spanContextMock.isRemote()).thenReturn(false);
- when(spanContextMock.isValid()).thenReturn(true);
- when(spanDataMock.getAttributes()).thenReturn(spanAttributes);
- when(spanDataMock.getTotalAttributeCount()).thenReturn(spanAttributes.size());
- when(spanDataMock.getKind()).thenReturn(SpanKind.PRODUCER);
- when(spanDataMock.getParentSpanContext()).thenReturn(spanContextMock);
-
- // Create mock for the generateMetricAttributeMapFromSpan. Returns dependency metric
- Map attributeMap = new HashMap<>();
- Attributes metricAttributes =
- Attributes.of(AttributeKey.stringKey("new service key"), "new dependency value");
- attributeMap.put(DEPENDENCY_METRIC, metricAttributes);
-
- when(generatorMock.generateMetricAttributeMapFromSpan(eq(spanDataMock), eq(testResource)))
- .thenReturn(attributeMap);
-
- awsMetricAttributesSpanExporter.export(Collections.singletonList(spanDataMock));
- verify(delegateMock, times(1)).export(delegateExportCaptor.capture());
- List exportedSpans = (List) delegateExportCaptor.getValue();
- assertThat(exportedSpans.size()).isEqualTo(1);
-
- // Retrieve the returned span
- SpanData exportedSpan = exportedSpans.get(0);
-
- // Check the number of attributes
- int expectedAttributeCount = metricAttributes.size() + spanAttributes.size();
- assertThat(exportedSpan.getTotalAttributeCount()).isEqualTo(expectedAttributeCount);
- Attributes exportedAttributes = exportedSpan.getAttributes();
- assertThat(exportedAttributes.size()).isEqualTo(expectedAttributeCount);
-
- // Check that all expected attributes are present
- spanAttributes.forEach((k, v) -> assertThat(exportedAttributes.get(k)).isEqualTo(v));
- metricAttributes.forEach((k, v) -> assertThat(exportedAttributes.get(k)).isEqualTo(v));
- }
-
- private static Attributes buildSpanAttributes(boolean containsAttribute) {
- if (containsAttribute) {
- return Attributes.of(AttributeKey.stringKey("original key"), "original value");
- } else {
- return Attributes.empty();
- }
- }
-
- private static Attributes buildMetricAttributes(boolean containsAttribute) {
- if (containsAttribute) {
- return Attributes.of(AttributeKey.stringKey("new key"), "new value");
- } else {
- return Attributes.empty();
- }
- }
-
- private static SpanData buildSpanDataMock(Attributes spanAttributes) {
- // Configure spanData
- SpanData mockSpanData = mock(SpanData.class);
- when(mockSpanData.getAttributes()).thenReturn(spanAttributes);
- when(mockSpanData.getTotalAttributeCount()).thenReturn(spanAttributes.size());
- when(mockSpanData.getKind()).thenReturn(SpanKind.SERVER);
- when(mockSpanData.getParentSpanContext()).thenReturn(null);
- return mockSpanData;
- }
-
- private void configureMocksForExport(SpanData spanDataMock, Attributes metricAttributes) {
- Map attributeMap = new HashMap<>();
- if (AwsSpanProcessingUtil.shouldGenerateServiceMetricAttributes(spanDataMock)) {
- attributeMap.put(SERVICE_METRIC, metricAttributes);
- }
-
- if (AwsSpanProcessingUtil.shouldGenerateDependencyMetricAttributes(spanDataMock)) {
- attributeMap.put(DEPENDENCY_METRIC, metricAttributes);
- }
-
- // Configure generated attributes
- when(generatorMock.generateMetricAttributeMapFromSpan(eq(spanDataMock), eq(testResource)))
- .thenReturn(attributeMap);
- }
-}