Skip to content

Commit 5e03532

Browse files
Introduce AWS X-Ray Adaptive Sampling support (#1170)
1 parent a62f47f commit 5e03532

File tree

14 files changed

+3379
-17
lines changed

14 files changed

+3379
-17
lines changed

.github/patches/opentelemetry-java-contrib.patch

Lines changed: 3116 additions & 0 deletions
Large diffs are not rendered by default.

.github/patches/versions

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
OTEL_JAVA_INSTRUMENTATION_VERSION=v2.18.1
2+
OTEL_JAVA_CONTRIB_VERSION=v1.48.0

awsagentprovider/build.gradle.kts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,10 @@ dependencies {
3838
implementation("io.opentelemetry.contrib:opentelemetry-aws-xray")
3939
// AWS Resource Detectors
4040
implementation("io.opentelemetry.contrib:opentelemetry-aws-resources")
41-
// Json file reader
41+
// JSON file reader
4242
implementation("com.fasterxml.jackson.core:jackson-databind:2.16.1")
43+
// YAML file reader
44+
implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.16.1")
4345
// Import AWS SDK v1 core for ARN parsing utilities
4446
implementation("com.amazonaws:aws-java-sdk-core:1.12.773")
4547
// Export configuration

awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AttributePropagatingSpanProcessor.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ public void onStart(Context parentContext, ReadWriteSpan span) {
112112
if (propagationData != null) {
113113
span.setAttribute(propagationDataKey, propagationData);
114114
}
115+
span.setAttribute(AwsAttributeKeys.AWS_TRACE_FLAG_SAMPLED, span.getSpanContext().isSampled());
115116
}
116117

117118
private boolean isConsumerKind(ReadableSpan span) {

awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsAgentPropertiesCustomizerProvider.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public void customize(AutoConfigurationCustomizer autoConfiguration) {
2626
() ->
2727
new HashMap<String, String>() {
2828
{
29-
put("otel.propagators", "baggage,xray,tracecontext,b3,b3multi");
29+
put("otel.propagators", "baggage,xray,tracecontext");
3030
put("otel.instrumentation.aws-sdk.experimental-span-attributes", "true");
3131
put(
3232
"otel.instrumentation.aws-sdk.experimental-record-individual-http-error",

awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsApplicationSignalsCustomizerProvider.java

Lines changed: 87 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,14 @@
1515

1616
package software.amazon.opentelemetry.javaagent.providers;
1717

18+
import com.fasterxml.jackson.core.JsonProcessingException;
19+
import com.fasterxml.jackson.core.type.TypeReference;
20+
import com.fasterxml.jackson.databind.ObjectMapper;
21+
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
1822
import io.opentelemetry.api.common.Attributes;
1923
import io.opentelemetry.api.common.AttributesBuilder;
20-
import io.opentelemetry.contrib.awsxray.AlwaysRecordSampler;
24+
import io.opentelemetry.contrib.awsxray.AwsXrayAdaptiveSamplingConfig;
25+
import io.opentelemetry.contrib.awsxray.AwsXrayRemoteSampler;
2126
import io.opentelemetry.contrib.awsxray.ResourceHolder;
2227
import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter;
2328
import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter;
@@ -42,6 +47,11 @@
4247
import io.opentelemetry.sdk.trace.SpanProcessor;
4348
import io.opentelemetry.sdk.trace.export.SpanExporter;
4449
import io.opentelemetry.sdk.trace.samplers.Sampler;
50+
import java.io.IOException;
51+
import java.nio.charset.StandardCharsets;
52+
import java.nio.file.Files;
53+
import java.nio.file.Path;
54+
import java.nio.file.Paths;
4555
import java.time.Duration;
4656
import java.util.ArrayList;
4757
import java.util.Arrays;
@@ -142,11 +152,16 @@ public final class AwsApplicationSignalsCustomizerProvider
142152
private static final String OTEL_EXPORTER_OTLP_LOGS_COMPRESSION_CONFIG =
143153
"otel.exporter.otlp.logs.compression";
144154

155+
private static final String AWS_XRAY_ADAPTIVE_SAMPLING_CONFIG =
156+
"aws.xray.adaptive.sampling.config";
157+
145158
// UDP packet can be upto 64KB. To limit the packet size, we limit the exported batch size.
146159
// This is a bit of a magic number, as there is no simple way to tell how many spans can make a
147160
// 64KB batch since spans can vary in size.
148161
private static final int LAMBDA_SPAN_EXPORT_BATCH_SIZE = 10;
149162

163+
private Sampler sampler;
164+
150165
public void customize(AutoConfigurationCustomizer autoConfiguration) {
151166
autoConfiguration.addPropertiesCustomizer(this::customizeProperties);
152167
autoConfiguration.addPropertiesCustomizer(this::customizeLambdaEnvProperties);
@@ -281,6 +296,27 @@ private Resource customizeResource(Resource resource, ConfigProperties configPro
281296
}
282297

283298
private Sampler customizeSampler(Sampler sampler, ConfigProperties configProps) {
299+
if (sampler instanceof AwsXrayRemoteSampler) {
300+
String config = configProps.getString(AWS_XRAY_ADAPTIVE_SAMPLING_CONFIG);
301+
AwsXrayAdaptiveSamplingConfig parsedConfig = null;
302+
303+
try {
304+
parsedConfig = parseConfigString(config);
305+
} catch (Exception e) {
306+
logger.log(
307+
Level.WARNING, "Failed to parse adaptive sampling configuration: {0}", e.getMessage());
308+
}
309+
310+
if (parsedConfig != null) {
311+
try {
312+
((AwsXrayRemoteSampler) sampler).setAdaptiveSamplingConfig(parsedConfig);
313+
} catch (Exception e) {
314+
logger.log(
315+
Level.WARNING, "Error processing adaptive sampling config: {0}", e.getMessage());
316+
}
317+
}
318+
this.sampler = sampler;
319+
}
284320
if (isApplicationSignalsEnabled(configProps)) {
285321
return AlwaysRecordSampler.create(sampler);
286322
}
@@ -344,10 +380,13 @@ private SdkTracerProviderBuilder customizeTracerProviderBuilder(
344380
.build();
345381

346382
// Construct and set application signals metrics processor
347-
SpanProcessor spanMetricsProcessor =
383+
AwsSpanMetricsProcessorBuilder awsSpanMetricsProcessorBuilder =
348384
AwsSpanMetricsProcessorBuilder.create(
349-
meterProvider, ResourceHolder.getResource(), meterProvider::forceFlush)
350-
.build();
385+
meterProvider, ResourceHolder.getResource(), meterProvider::forceFlush);
386+
if (this.sampler != null) {
387+
awsSpanMetricsProcessorBuilder.setSampler(this.sampler);
388+
}
389+
SpanProcessor spanMetricsProcessor = awsSpanMetricsProcessorBuilder.build();
351390
tracerProviderBuilder.addSpanProcessor(spanMetricsProcessor);
352391
}
353392
return tracerProviderBuilder;
@@ -423,11 +462,14 @@ SpanExporter customizeSpanExporter(SpanExporter spanExporter, ConfigProperties c
423462
}
424463

425464
if (isApplicationSignalsEnabled(configProps)) {
426-
return AwsMetricAttributesSpanExporterBuilder.create(
427-
spanExporter, ResourceHolder.getResource())
428-
.build();
465+
spanExporter =
466+
AwsMetricAttributesSpanExporterBuilder.create(spanExporter, ResourceHolder.getResource())
467+
.build();
429468
}
430469

470+
if (this.sampler instanceof AwsXrayRemoteSampler) {
471+
((AwsXrayRemoteSampler) this.sampler).setSpanExporter(spanExporter);
472+
}
431473
return spanExporter;
432474
}
433475

@@ -467,6 +509,44 @@ LogRecordExporter customizeLogsExporter(
467509
return logsExporter;
468510
}
469511

512+
static AwsXrayAdaptiveSamplingConfig parseConfigString(String config)
513+
throws JsonProcessingException {
514+
if (config == null) {
515+
return null;
516+
}
517+
518+
// Check if the config is a file path and the file exists
519+
Path path = Paths.get(config);
520+
if (Files.exists(path)) {
521+
try {
522+
config = String.join("\n", Files.readAllLines(path, StandardCharsets.UTF_8));
523+
} catch (IOException e) {
524+
throw new IllegalArgumentException(
525+
"Failed to read adaptive sampling configuration file: " + e.getMessage(), e);
526+
}
527+
}
528+
529+
ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory());
530+
Map<String, Object> configMap =
531+
yamlMapper.readValue(config, new TypeReference<Map<String, Object>>() {});
532+
533+
Object versionObj = configMap.get("version");
534+
if (versionObj == null) {
535+
throw new IllegalArgumentException(
536+
"Missing required 'version' field in adaptive sampling configuration");
537+
}
538+
539+
double version = ((Number) versionObj).doubleValue();
540+
if (version >= 2L) {
541+
throw new IllegalArgumentException(
542+
"Incompatible adaptive sampling config version: "
543+
+ version
544+
+ ". This version of the AWS X-Ray remote sampler only supports versions strictly below 2.0.");
545+
}
546+
547+
return yamlMapper.readValue(config, AwsXrayAdaptiveSamplingConfig.class);
548+
}
549+
470550
private enum ApplicationSignalsExporterProvider {
471551
INSTANCE;
472552

awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsSpanMetricsProcessor.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,14 @@
2525
import io.opentelemetry.api.metrics.LongHistogram;
2626
import io.opentelemetry.api.trace.StatusCode;
2727
import io.opentelemetry.context.Context;
28+
import io.opentelemetry.contrib.awsxray.AwsXrayRemoteSampler;
2829
import io.opentelemetry.sdk.common.CompletableResultCode;
2930
import io.opentelemetry.sdk.resources.Resource;
3031
import io.opentelemetry.sdk.trace.ReadWriteSpan;
3132
import io.opentelemetry.sdk.trace.ReadableSpan;
3233
import io.opentelemetry.sdk.trace.SpanProcessor;
3334
import io.opentelemetry.sdk.trace.data.SpanData;
35+
import io.opentelemetry.sdk.trace.samplers.Sampler;
3436
import java.util.Map;
3537
import java.util.function.Supplier;
3638
import javax.annotation.concurrent.Immutable;
@@ -75,16 +77,25 @@ public final class AwsSpanMetricsProcessor implements SpanProcessor {
7577
private final Resource resource;
7678
private final Supplier<CompletableResultCode> forceFlushAction;
7779

80+
private Sampler sampler;
81+
7882
/** Use {@link AwsSpanMetricsProcessorBuilder} to construct this processor. */
7983
static AwsSpanMetricsProcessor create(
8084
LongHistogram errorHistogram,
8185
LongHistogram faultHistogram,
8286
DoubleHistogram latencyHistogram,
8387
MetricAttributeGenerator generator,
8488
Resource resource,
89+
Sampler sampler,
8590
Supplier<CompletableResultCode> forceFlushAction) {
8691
return new AwsSpanMetricsProcessor(
87-
errorHistogram, faultHistogram, latencyHistogram, generator, resource, forceFlushAction);
92+
errorHistogram,
93+
faultHistogram,
94+
latencyHistogram,
95+
generator,
96+
resource,
97+
sampler,
98+
forceFlushAction);
8899
}
89100

90101
private AwsSpanMetricsProcessor(
@@ -93,12 +104,14 @@ private AwsSpanMetricsProcessor(
93104
DoubleHistogram latencyHistogram,
94105
MetricAttributeGenerator generator,
95106
Resource resource,
107+
Sampler sampler,
96108
Supplier<CompletableResultCode> forceFlushAction) {
97109
this.errorHistogram = errorHistogram;
98110
this.faultHistogram = faultHistogram;
99111
this.latencyHistogram = latencyHistogram;
100112
this.generator = generator;
101113
this.resource = resource;
114+
this.sampler = sampler;
102115
this.forceFlushAction = forceFlushAction;
103116
}
104117

@@ -125,6 +138,9 @@ public void onEnd(ReadableSpan span) {
125138
for (Map.Entry<String, Attributes> attribute : attributeMap.entrySet()) {
126139
recordMetrics(span, spanData, attribute.getValue());
127140
}
141+
if (sampler != null) {
142+
((AwsXrayRemoteSampler) sampler).adaptSampling(span, spanData);
143+
}
128144
}
129145

130146
@Override

awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsSpanMetricsProcessorBuilder.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import io.opentelemetry.api.metrics.MeterProvider;
2525
import io.opentelemetry.sdk.common.CompletableResultCode;
2626
import io.opentelemetry.sdk.resources.Resource;
27+
import io.opentelemetry.sdk.trace.samplers.Sampler;
2728
import java.util.function.Supplier;
2829

2930
/** A builder for {@link AwsSpanMetricsProcessor} */
@@ -51,6 +52,7 @@ public final class AwsSpanMetricsProcessorBuilder {
5152

5253
// Optional builder elements
5354
private MetricAttributeGenerator generator = DEFAULT_GENERATOR;
55+
private Sampler sampler;
5456
private String scopeName = DEFAULT_SCOPE_NAME;
5557

5658
public static AwsSpanMetricsProcessorBuilder create(
@@ -80,6 +82,17 @@ public AwsSpanMetricsProcessorBuilder setGenerator(MetricAttributeGenerator gene
8082
return this;
8183
}
8284

85+
/**
86+
* Sets the sampler used to determine if the spans should be sampled This will be used to increase
87+
* sampling rate in the case of errors
88+
*/
89+
@CanIgnoreReturnValue
90+
public AwsSpanMetricsProcessorBuilder setSampler(Sampler sampler) {
91+
requireNonNull(sampler, "sampler");
92+
this.sampler = sampler;
93+
return this;
94+
}
95+
8396
/**
8497
* Sets the scope name used in the creation of metrics by the span metrics processor. If unset,
8598
* defaults to {@link #DEFAULT_SCOPE_NAME}. Must not be null.
@@ -99,6 +112,12 @@ public AwsSpanMetricsProcessor build() {
99112
meter.histogramBuilder(LATENCY).setUnit(LATENCY_UNITS).build();
100113

101114
return AwsSpanMetricsProcessor.create(
102-
errorHistogram, faultHistogram, latencyHistogram, generator, resource, forceFlushAction);
115+
errorHistogram,
116+
faultHistogram,
117+
latencyHistogram,
118+
generator,
119+
resource,
120+
sampler,
121+
forceFlushAction);
103122
}
104123
}

0 commit comments

Comments
 (0)