diff --git a/.github/actions/patch-dependencies/action.yml b/.github/actions/patch-dependencies/action.yml index 9281534275..529d956221 100644 --- a/.github/actions/patch-dependencies/action.yml +++ b/.github/actions/patch-dependencies/action.yml @@ -49,6 +49,9 @@ runs: if [[ -f .github/patches/opentelemetry-java.patch ]]; then echo 'patch_otel_java=true' >> $GITHUB_ENV fi + if [[ -f .github/patches/opentelemetry-java-instrumentation.patch ]]; then + echo 'patch_otel_java_instrumentation=true' >> $GITHUB_ENV + fi if [[ -f .github/patches/opentelemetry-java-contrib.patch ]]; then echo 'patch_otel_java_contrib=true' >> $GITHUB_ENV fi @@ -57,21 +60,22 @@ runs: - name: Clone and patch repositories run: .github/scripts/patch.sh if: ${{ env.patch_otel_java == 'true' || - env.patch_otel_java_contrib == 'true' }} + env.patch_otel_java_contrib == 'true' || + env.patch_otel_java_instrumentation == 'true' }} shell: bash - name: Build opentelemetry-java with tests uses: gradle/gradle-build-action@v2 if: ${{ env.patch_otel_java == 'true' && inputs.run_tests != 'false' }} with: - arguments: build publishToMavenLocal + arguments: build publishToMavenLocal --scan --no-daemon build-root-directory: opentelemetry-java - name: Build opentelemetry-java uses: gradle/gradle-build-action@v2 if: ${{ env.patch_otel_java == 'true' && inputs.run_tests == 'false' }} with: - arguments: publishToMavenLocal + arguments: publishToMavenLocal --scan --no-daemon build-root-directory: opentelemetry-java - name: cleanup opentelemetry-java @@ -83,17 +87,36 @@ runs: uses: gradle/gradle-build-action@v2 if: ${{ env.patch_otel_java_contrib == 'true' && inputs.run_tests != 'false' }} with: - arguments: build publishToMavenLocal + arguments: build publishToMavenLocal --scan --no-daemon build-root-directory: opentelemetry-java-contrib - name: Build opentelemetry-java-contrib uses: gradle/gradle-build-action@v2 if: ${{ env.patch_otel_java_contrib == 'true' && inputs.run_tests == 'false' }} with: - arguments: publishToMavenLocal + arguments: publishToMavenLocal --scan --no-daemon build-root-directory: opentelemetry-java-contrib - name: cleanup opentelemetry-java-contrib run: rm -rf opentelemetry-java-contrib if: ${{ env.patch_otel_java_contrib == 'true' }} shell: bash + + - name: Build opentelemetry-java-instrumentation with tests + uses: gradle/gradle-build-action@v2 + if: ${{ env.patch_otel_java_instrumentation == 'true' && inputs.run_tests != 'false' }} + with: + arguments: check -x spotlessCheck publishToMavenLocal --scan --no-daemon + build-root-directory: opentelemetry-java-instrumentation + + - name: Build opentelemetry java instrumentation + uses: gradle/gradle-build-action@v2 + if: ${{ env.patch_otel_java_instrumentation == 'true' && inputs.run_tests == 'false' }} + with: + arguments: publishToMavenLocal --scan --no-daemon + build-root-directory: opentelemetry-java-instrumentation + + - name: cleanup opentelmetry-java-instrumentation + run: rm -rf opentelemetry-java-instrumentation + if: ${{ env.patch_otel_java_instrumentation == 'true' }} + shell: bash \ No newline at end of file diff --git a/.github/patches/opentelemetry-java-contrib.patch b/.github/patches/opentelemetry-java-contrib.patch index 718fa85de4..6db6a272db 100644 --- a/.github/patches/opentelemetry-java-contrib.patch +++ b/.github/patches/opentelemetry-java-contrib.patch @@ -30,32 +30,98 @@ index 4f7743a3..9e2082ed 100644 + }, ], } +diff --git a/aws-xray-propagator/src/main/java/io/opentelemetry/contrib/awsxray/propagator/AwsXrayPropagator.java b/aws-xray-propagator/src/main/java/io/opentelemetry/contrib/awsxray/propagator/AwsXrayPropagator.java +index 721d0144..dce2d2a3 100644 +--- a/aws-xray-propagator/src/main/java/io/opentelemetry/contrib/awsxray/propagator/AwsXrayPropagator.java ++++ b/aws-xray-propagator/src/main/java/io/opentelemetry/contrib/awsxray/propagator/AwsXrayPropagator.java +@@ -9,6 +9,7 @@ import static io.opentelemetry.api.internal.OtelEncodingUtils.isValidBase16Strin + + import io.opentelemetry.api.baggage.Baggage; + import io.opentelemetry.api.baggage.BaggageBuilder; ++import io.opentelemetry.api.baggage.propagation.W3CBaggagePropagator; + import io.opentelemetry.api.internal.StringUtils; + import io.opentelemetry.api.trace.Span; + import io.opentelemetry.api.trace.SpanContext; +@@ -80,6 +81,9 @@ public final class AwsXrayPropagator implements TextMapPropagator { + private static final String INVALID_LINEAGE = "-1:11111111:0"; + private static final int NUM_OF_LINEAGE_DELIMITERS = 2; + ++ // Copied from AwsSamplingResult in aws-xray extension ++ private static final String AWS_XRAY_SAMPLING_RULE_TRACE_STATE_KEY = "xrsr"; ++ + private static final List FIELDS = Collections.singletonList(TRACE_HEADER_KEY); + + private static final AwsXrayPropagator INSTANCE = new AwsXrayPropagator(); +@@ -140,6 +144,16 @@ public final class AwsXrayPropagator implements TextMapPropagator { + + Baggage baggage = Baggage.fromContext(context); + String lineageHeader = baggage.getEntryValue(LINEAGE_KEY); ++ // Get sampling rule from trace state and inject into baggage ++ // This is a back up in case the next service does not have trace state propagation ++ String ruleFromTraceState = ++ spanContext.getTraceState().get(AWS_XRAY_SAMPLING_RULE_TRACE_STATE_KEY); ++ if (ruleFromTraceState != null) { ++ baggage = ++ baggage.toBuilder() ++ .put(AWS_XRAY_SAMPLING_RULE_TRACE_STATE_KEY, ruleFromTraceState) ++ .build(); ++ } + + if (lineageHeader != null) { + traceHeader +@@ -152,6 +166,9 @@ public final class AwsXrayPropagator implements TextMapPropagator { + // add 256 character truncation + String truncatedTraceHeader = traceHeader.substring(0, Math.min(traceHeader.length(), 256)); + setter.set(carrier, TRACE_HEADER_KEY, truncatedTraceHeader); ++ ++ // Ensure baggage is propagated with any modifications ++ W3CBaggagePropagator.getInstance().inject(context.with(baggage), carrier, setter); + } + + @Override +@@ -245,12 +262,15 @@ public final class AwsXrayPropagator implements TextMapPropagator { + logger.finest("Both traceId and spanId are required to extract a valid span context. "); + } + ++ SpanContext upstreamSpanContext = Span.fromContext(context).getSpanContext(); + SpanContext spanContext = + SpanContext.createFromRemoteParent( + StringUtils.padLeft(traceId, TraceId.getLength()), + spanId, + isSampled ? TraceFlags.getSampled() : TraceFlags.getDefault(), +- TraceState.getDefault()); ++ upstreamSpanContext.isValid() ++ ? upstreamSpanContext.getTraceState() ++ : TraceState.getDefault()); + + if (spanContext.isValid()) { + context = context.with(Span.wrap(spanContext)); diff --git a/aws-xray/build.gradle.kts b/aws-xray/build.gradle.kts index 54dabba7..d56b12bd 100644 --- a/aws-xray/build.gradle.kts +++ b/aws-xray/build.gradle.kts @@ -11,6 +11,7 @@ dependencies { api("io.opentelemetry:opentelemetry-sdk-trace") - + compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") + implementation("io.opentelemetry.semconv:opentelemetry-semconv:1.32.0-alpha") - + implementation("com.squareup.okhttp3:okhttp") implementation("io.opentelemetry.semconv:opentelemetry-semconv") @@ -25,6 +26,7 @@ dependencies { - + implementation("com.fasterxml.jackson.core:jackson-core") implementation("com.fasterxml.jackson.core:jackson-databind") + implementation("com.github.ben-manes.caffeine:caffeine:2.9.3") - + testImplementation("com.linecorp.armeria:armeria-junit5") testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") diff --git a/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsSamplingResult.java b/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsSamplingResult.java new file mode 100644 -index 00000000..41f22f90 +index 00000000..4aed8959 --- /dev/null +++ b/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsSamplingResult.java -@@ -0,0 +1,54 @@ +@@ -0,0 +1,56 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 @@ -67,6 +133,7 @@ index 00000000..41f22f90 +import io.opentelemetry.api.trace.TraceState; +import io.opentelemetry.sdk.trace.samplers.SamplingDecision; +import io.opentelemetry.sdk.trace.samplers.SamplingResult; ++import javax.annotation.Nullable; + +final class AwsSamplingResult implements SamplingResult { + @@ -76,17 +143,17 @@ index 00000000..41f22f90 + + private final SamplingDecision decision; + private final Attributes attributes; -+ private final String samplingRuleName; ++ @Nullable private final String samplingRuleName; + + private AwsSamplingResult( -+ SamplingDecision decision, Attributes attributes, String samplingRuleName) { ++ SamplingDecision decision, Attributes attributes, @Nullable String samplingRuleName) { + this.decision = decision; + this.attributes = attributes; + this.samplingRuleName = samplingRuleName; + } + + static AwsSamplingResult create( -+ SamplingDecision decision, Attributes attributes, String samplingRuleName) { ++ SamplingDecision decision, Attributes attributes, @Nullable String samplingRuleName) { + return new AwsSamplingResult(decision, attributes, samplingRuleName); + } + @@ -102,7 +169,8 @@ index 00000000..41f22f90 + + @Override + public TraceState getUpdatedTraceState(TraceState parentTraceState) { -+ if (parentTraceState.get(AWS_XRAY_SAMPLING_RULE_TRACE_STATE_KEY) == null) { ++ if (parentTraceState.get(AWS_XRAY_SAMPLING_RULE_TRACE_STATE_KEY) == null ++ && this.samplingRuleName != null) { + return parentTraceState.toBuilder() + .put(AWS_XRAY_SAMPLING_RULE_TRACE_STATE_KEY, samplingRuleName) + .build(); @@ -265,7 +333,7 @@ index 00000000..dc5b7a01 + } +} diff --git a/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsXrayRemoteSampler.java b/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsXrayRemoteSampler.java -index ad9b72a2..7864f358 100644 +index ad9b72a2..31d5a293 100644 --- a/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsXrayRemoteSampler.java +++ b/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsXrayRemoteSampler.java @@ -9,16 +9,22 @@ import io.opentelemetry.api.common.Attributes; @@ -301,8 +369,11 @@ index ad9b72a2..7864f358 100644 private final Resource resource; private final Clock clock; private final Sampler initialSampler; -@@ -59,6 +68,9 @@ public final class AwsXrayRemoteSampler implements Sampler, Closeable { - @Nullable private volatile XrayRulesSampler internalXrayRulesSampler; +@@ -56,9 +65,11 @@ public final class AwsXrayRemoteSampler implements Sampler, Closeable { + @Nullable private volatile ScheduledFuture pollFuture; + @Nullable private volatile ScheduledFuture fetchTargetsFuture; + @Nullable private volatile GetSamplingRulesResponse previousRulesResponse; +- @Nullable private volatile XrayRulesSampler internalXrayRulesSampler; private volatile Sampler sampler; + @Nullable private AwsXrayAdaptiveSamplingConfig adaptiveSamplingConfig; @@ -311,7 +382,7 @@ index ad9b72a2..7864f358 100644 /** * Returns a {@link AwsXrayRemoteSamplerBuilder} with the given {@link Resource}. This {@link * Resource} should be the same as what the OpenTelemetry SDK is configured with. -@@ -120,6 +132,40 @@ public final class AwsXrayRemoteSampler implements Sampler, Closeable { +@@ -120,13 +131,47 @@ public final class AwsXrayRemoteSampler implements Sampler, Closeable { return "AwsXrayRemoteSampler{" + sampler.getDescription() + "}"; } @@ -321,8 +392,8 @@ index ad9b72a2..7864f358 100644 + } else if (config != null && this.adaptiveSamplingConfig == null) { + // Save here and also pass to XrayRulesSampler directly as it already exists + this.adaptiveSamplingConfig = config; -+ if (internalXrayRulesSampler != null) { -+ internalXrayRulesSampler.setAdaptiveSamplingConfig(config); ++ if (sampler instanceof XrayRulesSampler) { ++ ((XrayRulesSampler) sampler).setAdaptiveSamplingConfig(config); + } + } + } @@ -344,27 +415,44 @@ index ad9b72a2..7864f358 100644 + throw new IllegalStateException( + "Programming bug - BatchSpanProcessor is null while trying to adapt sampling"); + } -+ if (internalXrayRulesSampler != null) { -+ internalXrayRulesSampler.adaptSampling(span, spanData, this.bsp::onEnd); ++ if (sampler instanceof XrayRulesSampler) { ++ ((XrayRulesSampler) sampler).adaptSampling(span, spanData, this.bsp::onEnd); + } + } + private void getAndUpdateSampler() { try { // No pagination support yet, or possibly ever. -@@ -134,8 +180,8 @@ public final class AwsXrayRemoteSampler implements Sampler, Closeable { + GetSamplingRulesResponse response = + client.getSamplingRules(GetSamplingRulesRequest.create(null)); + if (!response.equals(previousRulesResponse)) { +- updateInternalSamplers( ++ sampler = + new XrayRulesSampler( + clientId, + resource, +@@ -134,8 +179,8 @@ public final class AwsXrayRemoteSampler implements Sampler, Closeable { initialSampler, response.getSamplingRules().stream() .map(SamplingRuleRecord::getRule) - .collect(Collectors.toList()))); - + .collect(Collectors.toList()), -+ adaptiveSamplingConfig)); ++ adaptiveSamplingConfig); previousRulesResponse = response; ScheduledFuture existingFetchTargetsFuture = fetchTargetsFuture; if (existingFetchTargetsFuture != null) { -@@ -179,14 +225,29 @@ public final class AwsXrayRemoteSampler implements Sampler, Closeable { - XrayRulesSampler xrayRulesSampler = this.internalXrayRulesSampler; +@@ -172,25 +217,41 @@ public final class AwsXrayRemoteSampler implements Sampler, Closeable { + } + + private void fetchTargets() { +- if (this.internalXrayRulesSampler == null) { ++ if (!(sampler instanceof XrayRulesSampler)) { + throw new IllegalStateException("Programming bug."); + } + +- XrayRulesSampler xrayRulesSampler = this.internalXrayRulesSampler; ++ XrayRulesSampler xrayRulesSampler = (XrayRulesSampler) sampler; try { Date now = Date.from(Instant.ofEpochSecond(0, clock.now())); - List statistics = xrayRulesSampler.snapshot(now); @@ -396,6 +484,24 @@ index ad9b72a2..7864f358 100644 Map targets = response.getDocuments().stream() .collect(Collectors.toMap(SamplingTargetDocument::getRuleName, Function.identity())); +- updateInternalSamplers(xrayRulesSampler.withTargets(targets, requestedTargetRuleNames, now)); ++ sampler = ++ xrayRulesSampler = xrayRulesSampler.withTargets(targets, requestedTargetRuleNames, now); + } catch (Throwable t) { + // Might be a transient API failure, try again after a default interval. + fetchTargetsFuture = +@@ -226,11 +287,6 @@ public final class AwsXrayRemoteSampler implements Sampler, Closeable { + return new String(clientIdChars); + } + +- private void updateInternalSamplers(XrayRulesSampler xrayRulesSampler) { +- this.internalXrayRulesSampler = xrayRulesSampler; +- this.sampler = Sampler.parentBased(internalXrayRulesSampler); +- } +- + // Visible for testing + XraySamplerClient getClient() { + return client; diff --git a/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/GetSamplingRulesResponse.java b/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/GetSamplingRulesResponse.java index dca930d5..01835dc2 100644 --- a/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/GetSamplingRulesResponse.java @@ -590,11 +696,14 @@ index c1e178f5..406f07e2 100644 + } } diff --git a/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/SamplingRuleApplier.java b/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/SamplingRuleApplier.java -index 1d97c4ae..6462c7f3 100644 +index 1d97c4ae..dd369f5f 100644 --- a/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/SamplingRuleApplier.java +++ b/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/SamplingRuleApplier.java -@@ -11,10 +11,13 @@ import io.opentelemetry.api.common.AttributeKey; +@@ -9,12 +9,16 @@ import static io.opentelemetry.semconv.ServiceAttributes.SERVICE_NAME; + + import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; ++import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.context.Context; +import io.opentelemetry.contrib.awsxray.GetSamplingTargetsRequest.SamplingBoostStatisticsDocument; @@ -607,7 +716,7 @@ index 1d97c4ae..6462c7f3 100644 import io.opentelemetry.sdk.trace.data.LinkData; import io.opentelemetry.sdk.trace.samplers.Sampler; import io.opentelemetry.sdk.trace.samplers.SamplingDecision; -@@ -76,12 +79,20 @@ final class SamplingRuleApplier { +@@ -76,12 +80,20 @@ final class SamplingRuleApplier { private final String clientId; private final String ruleName; @@ -628,7 +737,7 @@ index 1d97c4ae..6462c7f3 100644 private final Map attributeMatchers; private final Matcher urlPathMatcher; private final Matcher serviceNameMatcher; -@@ -94,7 +105,11 @@ final class SamplingRuleApplier { +@@ -94,7 +106,11 @@ final class SamplingRuleApplier { private final long nextSnapshotTimeNanos; @@ -641,7 +750,7 @@ index 1d97c4ae..6462c7f3 100644 this.clientId = clientId; this.clock = clock; String ruleName = rule.getRuleName(); -@@ -108,6 +123,8 @@ final class SamplingRuleApplier { +@@ -108,6 +124,8 @@ final class SamplingRuleApplier { } this.ruleName = ruleName; @@ -650,7 +759,7 @@ index 1d97c4ae..6462c7f3 100644 // We don't have a SamplingTarget so are ready to report a snapshot right away. nextSnapshotTimeNanos = clock.nanoTime(); -@@ -124,7 +141,15 @@ final class SamplingRuleApplier { +@@ -124,7 +142,15 @@ final class SamplingRuleApplier { reservoirSampler = Sampler.alwaysOff(); borrowing = false; } @@ -667,7 +776,7 @@ index 1d97c4ae..6462c7f3 100644 if (rule.getAttributes().isEmpty()) { attributeMatchers = Collections.emptyMap(); -@@ -147,11 +172,16 @@ final class SamplingRuleApplier { +@@ -147,11 +173,16 @@ final class SamplingRuleApplier { private SamplingRuleApplier( String clientId, String ruleName, @@ -684,7 +793,7 @@ index 1d97c4ae..6462c7f3 100644 Map attributeMatchers, Matcher urlPathMatcher, Matcher serviceNameMatcher, -@@ -163,11 +193,16 @@ final class SamplingRuleApplier { +@@ -163,11 +194,16 @@ final class SamplingRuleApplier { long nextSnapshotTimeNanos) { this.clientId = clientId; this.ruleName = ruleName; @@ -701,7 +810,7 @@ index 1d97c4ae..6462c7f3 100644 this.attributeMatchers = attributeMatchers; this.urlPathMatcher = urlPathMatcher; this.serviceNameMatcher = serviceNameMatcher; -@@ -177,6 +212,7 @@ final class SamplingRuleApplier { +@@ -177,6 +213,7 @@ final class SamplingRuleApplier { this.resourceArnMatcher = resourceArnMatcher; this.statistics = statistics; this.nextSnapshotTimeNanos = nextSnapshotTimeNanos; @@ -709,13 +818,40 @@ index 1d97c4ae..6462c7f3 100644 } @SuppressWarnings("deprecation") // TODO -@@ -273,45 +309,84 @@ final class SamplingRuleApplier { - statistics.sampled.increment(); +@@ -257,8 +294,13 @@ final class SamplingRuleApplier { + SpanKind spanKind, + Attributes attributes, + List parentLinks) { ++ // Only emit statistics for spans for which a sampling decision is being made actively ++ // i.e. The root span in a call chain ++ boolean shouldCount = !Span.fromContext(parentContext).getSpanContext().isValid(); + // Incrementing requests first ensures sample / borrow rate are positive. +- statistics.requests.increment(); ++ if (shouldCount) { ++ statistics.requests.increment(); ++ } + boolean reservoirExpired = clock.nanoTime() >= reservoirEndTimeNanos; + SamplingResult result = + !reservoirExpired +@@ -267,51 +309,92 @@ final class SamplingRuleApplier { + : SamplingResult.create(SamplingDecision.DROP); + if (result.getDecision() != SamplingDecision.DROP) { + // We use the result from the reservoir sampler if it worked. +- if (borrowing) { +- statistics.borrowed.increment(); ++ if (shouldCount) { ++ if (borrowing) { ++ statistics.borrowed.increment(); ++ } ++ statistics.sampled.increment(); + } +- statistics.sampled.increment(); return result; } - result = - fixedRateSampler.shouldSample( - parentContext, traceId, name, spanKind, attributes, parentLinks); +- if (result.getDecision() != SamplingDecision.DROP) { + + if (clock.nanoTime() < boostEndTimeNanos) { + result = @@ -726,7 +862,7 @@ index 1d97c4ae..6462c7f3 100644 + fixedRateSampler.shouldSample( + parentContext, traceId, name, spanKind, attributes, parentLinks); + } - if (result.getDecision() != SamplingDecision.DROP) { ++ if (shouldCount && result.getDecision() != SamplingDecision.DROP) { statistics.sampled.increment(); } return result; @@ -811,7 +947,7 @@ index 1d97c4ae..6462c7f3 100644 + Duration.between(now.toInstant(), target.getReservoirQuotaTtl().toInstant()) .toNanos(); } -@@ -319,16 +394,36 @@ final class SamplingRuleApplier { +@@ -319,16 +402,36 @@ final class SamplingRuleApplier { target.getIntervalSecs() != null ? TimeUnit.SECONDS.toNanos(target.getIntervalSecs()) : AwsXrayRemoteSampler.DEFAULT_TARGET_INTERVAL_NANOS; @@ -849,7 +985,7 @@ index 1d97c4ae..6462c7f3 100644 attributeMatchers, urlPathMatcher, serviceNameMatcher, -@@ -344,11 +439,16 @@ final class SamplingRuleApplier { +@@ -344,11 +447,16 @@ final class SamplingRuleApplier { return new SamplingRuleApplier( clientId, ruleName, @@ -866,7 +1002,7 @@ index 1d97c4ae..6462c7f3 100644 attributeMatchers, urlPathMatcher, serviceNameMatcher, -@@ -364,6 +464,15 @@ final class SamplingRuleApplier { +@@ -364,6 +472,15 @@ final class SamplingRuleApplier { return ruleName; } @@ -882,7 +1018,21 @@ index 1d97c4ae..6462c7f3 100644 @Nullable private static String getArn(Attributes attributes, Resource resource) { String arn = resource.getAttributes().get(AWS_ECS_CONTAINER_ARN); -@@ -515,5 +624,30 @@ final class SamplingRuleApplier { +@@ -500,11 +617,11 @@ final class SamplingRuleApplier { + } + + private Sampler createRateLimited(int numPerSecond) { +- return new RateLimitingSampler(numPerSecond, clock); ++ return Sampler.parentBased(new RateLimitingSampler(numPerSecond, clock)); + } + + private static Sampler createFixedRate(double rate) { +- return Sampler.traceIdRatioBased(rate); ++ return Sampler.parentBased(Sampler.traceIdRatioBased(rate)); + } + + // We keep track of sampling requests and decisions to report to X-Ray to allow it to allocate +@@ -515,5 +632,30 @@ final class SamplingRuleApplier { final LongAdder requests = new LongAdder(); final LongAdder sampled = new LongAdder(); final LongAdder borrowed = new LongAdder(); @@ -914,10 +1064,10 @@ index 1d97c4ae..6462c7f3 100644 } } diff --git a/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/XrayRulesSampler.java b/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/XrayRulesSampler.java -index 75977dc0..9620ba2b 100644 +index 75977dc0..48bdeb0f 100644 --- a/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/XrayRulesSampler.java +++ b/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/XrayRulesSampler.java -@@ -5,42 +5,79 @@ +@@ -5,42 +5,81 @@ package io.opentelemetry.contrib.awsxray; @@ -926,9 +1076,11 @@ index 75977dc0..9620ba2b 100644 + +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; ++import io.opentelemetry.api.baggage.Baggage; +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.StatusCode; import io.opentelemetry.context.Context; @@ -998,7 +1150,7 @@ index 75977dc0..9620ba2b 100644 this( clientId, resource, -@@ -49,8 +86,19 @@ final class XrayRulesSampler implements Sampler { +@@ -49,8 +88,19 @@ final class XrayRulesSampler implements Sampler { rules.stream() // Lower priority value takes precedence so normal ascending sort. .sorted(Comparator.comparingInt(GetSamplingRulesResponse.SamplingRule::getPriority)) @@ -1020,7 +1172,7 @@ index 75977dc0..9620ba2b 100644 } private XrayRulesSampler( -@@ -58,12 +106,36 @@ final class XrayRulesSampler implements Sampler { +@@ -58,12 +108,36 @@ final class XrayRulesSampler implements Sampler { Resource resource, Clock clock, Sampler fallbackSampler, @@ -1058,15 +1210,22 @@ index 75977dc0..9620ba2b 100644 } @Override -@@ -74,10 +146,36 @@ final class XrayRulesSampler implements Sampler { +@@ -74,10 +148,43 @@ final class XrayRulesSampler implements Sampler { SpanKind spanKind, Attributes attributes, List parentLinks) { ++ SpanContext parentSpanContext = Span.fromContext(parentContext).getSpanContext(); + String upstreamMatchedRule = -+ Span.fromContext(parentContext) -+ .getSpanContext() ++ parentSpanContext + .getTraceState() + .get(AwsSamplingResult.AWS_XRAY_SAMPLING_RULE_TRACE_STATE_KEY); ++ if (upstreamMatchedRule == null) { ++ Baggage b = Baggage.fromContext(parentContext); ++ upstreamMatchedRule = ++ b != null ++ ? b.getEntryValue(AwsSamplingResult.AWS_XRAY_SAMPLING_RULE_TRACE_STATE_KEY) ++ : null; ++ } for (SamplingRuleApplier applier : ruleAppliers) { if (applier.matches(attributes, resource)) { - return applier.shouldSample( @@ -1078,26 +1237,26 @@ index 75977dc0..9620ba2b 100644 + // Otherwise, encode and propagate the matched sampling rule using AwsSamplingResult + String ruleToPropagate; + if (upstreamMatchedRule != null) { -+ ruleToPropagate = hashToRuleMap.getOrDefault(upstreamMatchedRule, applier.getRuleName()); ++ ruleToPropagate = hashToRuleMap.getOrDefault(upstreamMatchedRule, null); ++ } else if (parentSpanContext.isValid()) { ++ ruleToPropagate = null; + } else { + ruleToPropagate = applier.getRuleName(); + } -+ String hashedRule = ruleToHashMap.getOrDefault(ruleToPropagate, ruleToPropagate); -+ if (this.adaptiveSamplingConfig != null -+ && this.adaptiveSamplingConfig.getAnomalyCaptureLimit() != null) { -+ // If the span is capturable based on local SDK config, add sampling rule attribute -+ return AwsSamplingResult.create( -+ result.getDecision(), -+ result.getAttributes().toBuilder() -+ .put(AWS_XRAY_SAMPLING_RULE.getKey(), ruleToPropagate) -+ .build(), -+ hashedRule); -+ } -+ return AwsSamplingResult.create(result.getDecision(), result.getAttributes(), hashedRule); ++ String hashedRule = ruleToHashMap.getOrDefault(ruleToPropagate, upstreamMatchedRule); ++ ++ return AwsSamplingResult.create( ++ result.getDecision(), ++ result.getAttributes().toBuilder() ++ .put( ++ AWS_XRAY_SAMPLING_RULE.getKey(), ++ ruleToPropagate != null ? ruleToPropagate : "UNKNOWN") ++ .build(), ++ hashedRule); } } -@@ -96,7 +194,184 @@ final class XrayRulesSampler implements Sampler { +@@ -96,7 +203,97 @@ final class XrayRulesSampler implements Sampler { return "XrayRulesSampler{" + Arrays.toString(ruleAppliers) + "}"; } @@ -1113,6 +1272,8 @@ index 75977dc0..9620ba2b 100644 + int anomalyTracesPerSecond = config.getAnomalyCaptureLimit().getAnomalyTracesPerSecond(); + this.anomalyCaptureRateLimiter = + new RateLimiter(anomalyTracesPerSecond, anomalyTracesPerSecond, clock); ++ } else { ++ this.anomalyCaptureRateLimiter = new RateLimiter(1, 1, clock); + } + } + } @@ -1121,81 +1282,10 @@ index 75977dc0..9620ba2b 100644 + if (!adaptiveSamplingRuleExists && this.adaptiveSamplingConfig == null) { + return; + } -+ Long statusCode = spanData.getAttributes().get(HTTP_RESPONSE_STATUS_CODE); -+ -+ boolean shouldBoostSampling = false; -+ boolean shouldCaptureAnomalySpan = false; + -+ List anomalyConditions = -+ adaptiveSamplingConfig != null ? adaptiveSamplingConfig.getAnomalyConditions() : null; -+ // Empty list -> no conditions will apply and we will not do anything -+ if (anomalyConditions != null && !anomalyConditions.isEmpty()) { -+ String operation = spanData.getAttributes().get(AwsAttributeKeys.AWS_LOCAL_OPERATION); -+ if (operation == null) { -+ operation = generateIngressOperation(spanData); -+ } -+ for (AwsXrayAdaptiveSamplingConfig.AnomalyConditions condition : anomalyConditions) { -+ // Skip condition if it would only re-apply action already being taken -+ if ((shouldBoostSampling -+ && AwsXrayAdaptiveSamplingConfig.UsageType.SAMPLING_BOOST.equals( -+ condition.getUsage())) -+ || (shouldCaptureAnomalySpan -+ && AwsXrayAdaptiveSamplingConfig.UsageType.ANOMALY_TRACE_CAPTURE.equals( -+ condition.getUsage()))) { -+ continue; -+ } -+ // Check if the operation matches any in the list or if operations list is null (match all) -+ List operations = condition.getOperations(); -+ if (!(operations == null || operations.isEmpty() || operations.contains(operation))) { -+ continue; -+ } -+ // Check if any anomalyConditions detect an anomaly either through error code or latency -+ boolean isAnomaly = false; -+ -+ String errorCodeRegex = condition.getErrorCodeRegex(); -+ if (statusCode != null && errorCodeRegex != null) { -+ isAnomaly = statusCode.toString().matches(errorCodeRegex); -+ } -+ -+ Long highLatencyMs = condition.getHighLatencyMs(); -+ if (highLatencyMs != null) { -+ isAnomaly = -+ (errorCodeRegex == null || isAnomaly) -+ && (span.getLatencyNanos() / 1_000_000.0) >= highLatencyMs; -+ } -+ -+ if (isAnomaly) { -+ AwsXrayAdaptiveSamplingConfig.UsageType usage = condition.getUsage(); -+ if (usage != null) { -+ switch (usage) { -+ case BOTH: -+ shouldBoostSampling = true; -+ shouldCaptureAnomalySpan = true; -+ break; -+ case SAMPLING_BOOST: -+ shouldBoostSampling = true; -+ break; -+ case ANOMALY_TRACE_CAPTURE: -+ shouldCaptureAnomalySpan = true; -+ break; -+ default: // do nothing -+ } -+ } else { -+ shouldBoostSampling = true; -+ shouldCaptureAnomalySpan = true; -+ } -+ } -+ if (shouldBoostSampling && shouldCaptureAnomalySpan) { -+ break; -+ } -+ } -+ } else if ((statusCode != null && statusCode > 499) -+ || (statusCode == null -+ && spanData.getStatus() != null -+ && StatusCode.ERROR.equals(spanData.getStatus().getStatusCode()))) { -+ shouldBoostSampling = true; -+ shouldCaptureAnomalySpan = true; -+ } ++ AnomalyDetectionResult result = isAnomaly(span, spanData); ++ boolean shouldBoostSampling = result.shouldBoostSampling(); ++ boolean shouldCaptureAnomalySpan = result.shouldCaptureAnomalySpan(); + + String traceId = spanData.getTraceId(); + AwsXrayAdaptiveSamplingConfig.UsageType existingUsage = traceUsageCache.getIfPresent(traceId); @@ -1219,7 +1309,7 @@ index 75977dc0..9620ba2b 100644 + span.getSpanContext() + .getTraceState() + .get(AwsSamplingResult.AWS_XRAY_SAMPLING_RULE_TRACE_STATE_KEY); -+ String ruleNameForBoostStats = ++ String upstreamRuleName = + traceStateValue != null + ? hashToRuleMap.getOrDefault(traceStateValue, traceStateValue) + : traceStateValue; @@ -1227,7 +1317,7 @@ index 75977dc0..9620ba2b 100644 + SamplingRuleApplier matchedRule = null; + for (SamplingRuleApplier applier : ruleAppliers) { + // Rule propagated from when sampling decision was made, otherwise the matched rule -+ if (applier.getRuleName().equals(ruleNameForBoostStats)) { ++ if (applier.getRuleName().equals(upstreamRuleName)) { + ruleToReportTo = applier; + break; + } @@ -1240,10 +1330,12 @@ index 75977dc0..9620ba2b 100644 + logger.log( + Level.FINE, + "No sampling rule matched the request. This is a bug in either the OpenTelemetry SDK or X-Ray."); -+ } else { ++ } else if (!span.getParentSpanContext().isValid()) { ++ // Span is not from an upstream service, so we should boost the matched rule + ruleToReportTo = matchedRule; + } + } ++ + if (shouldBoostSampling + && ruleToReportTo != null + && ruleToReportTo.hasBoost() @@ -1256,34 +1348,14 @@ index 75977dc0..9620ba2b 100644 + } + } + -+ // Any interaction with a cache entry will reset the expiration timer of that entry -+ if (isSpanCaptured && isCountedAsAnomalyForBoost) { -+ this.traceUsageCache.put(traceId, AwsXrayAdaptiveSamplingConfig.UsageType.BOTH); -+ } else if (isSpanCaptured) { -+ if (AwsXrayAdaptiveSamplingConfig.UsageType.isUsedForBoost(existingUsage)) { -+ this.traceUsageCache.put(traceId, AwsXrayAdaptiveSamplingConfig.UsageType.BOTH); -+ } else { -+ this.traceUsageCache.put( -+ traceId, AwsXrayAdaptiveSamplingConfig.UsageType.ANOMALY_TRACE_CAPTURE); -+ } -+ } else if (isCountedAsAnomalyForBoost) { -+ if (AwsXrayAdaptiveSamplingConfig.UsageType.isUsedForAnomalyTraceCapture(existingUsage)) { -+ this.traceUsageCache.put(traceId, AwsXrayAdaptiveSamplingConfig.UsageType.BOTH); -+ } else { -+ this.traceUsageCache.put(traceId, AwsXrayAdaptiveSamplingConfig.UsageType.SAMPLING_BOOST); -+ } -+ } else if (existingUsage != null) { -+ this.traceUsageCache.put(traceId, existingUsage); -+ } else { -+ this.traceUsageCache.put(traceId, AwsXrayAdaptiveSamplingConfig.UsageType.NEITHER); -+ } ++ updateTraceUsageCache(traceId, isSpanCaptured, isCountedAsAnomalyForBoost); + } + + List snapshot(Date now) { return Arrays.stream(ruleAppliers) .map(rule -> rule.snapshot(now)) .filter(Objects::nonNull) -@@ -115,15 +390,16 @@ final class XrayRulesSampler implements Sampler { +@@ -115,15 +312,16 @@ final class XrayRulesSampler implements Sampler { Map ruleTargets, Set requestedTargetRuleNames, Date now) { @@ -1302,7 +1374,7 @@ index 75977dc0..9620ba2b 100644 } if (requestedTargetRuleNames.contains(rule.getRuleName())) { // In practice X-Ray should return a target for any rule we requested but -@@ -135,6 +411,92 @@ final class XrayRulesSampler implements Sampler { +@@ -135,6 +333,216 @@ final class XrayRulesSampler implements Sampler { return rule; }) .toArray(SamplingRuleApplier[]::new); @@ -1319,6 +1391,85 @@ index 75977dc0..9620ba2b 100644 + traceUsageCache); + } + ++ private AnomalyDetectionResult isAnomaly(ReadableSpan span, SpanData spanData) { ++ boolean shouldBoostSampling = false; ++ boolean shouldCaptureAnomalySpan = false; ++ Long statusCode = spanData.getAttributes().get(HTTP_RESPONSE_STATUS_CODE); ++ ++ List anomalyConditions = ++ adaptiveSamplingConfig != null ? adaptiveSamplingConfig.getAnomalyConditions() : null; ++ // Empty list -> no conditions will apply and we will not do anything ++ if (anomalyConditions != null) { ++ String operation = spanData.getAttributes().get(AwsAttributeKeys.AWS_LOCAL_OPERATION); ++ if (operation == null) { ++ operation = generateIngressOperation(spanData); ++ } ++ for (AwsXrayAdaptiveSamplingConfig.AnomalyConditions condition : anomalyConditions) { ++ // Skip condition if it would only re-apply action already being taken ++ if ((shouldBoostSampling ++ && AwsXrayAdaptiveSamplingConfig.UsageType.SAMPLING_BOOST.equals( ++ condition.getUsage())) ++ || (shouldCaptureAnomalySpan ++ && AwsXrayAdaptiveSamplingConfig.UsageType.ANOMALY_TRACE_CAPTURE.equals( ++ condition.getUsage()))) { ++ continue; ++ } ++ // Check if the operation matches any in the list or if operations list is null (match all) ++ List operations = condition.getOperations(); ++ if (!(operations == null || operations.isEmpty() || operations.contains(operation))) { ++ continue; ++ } ++ // Check if any anomalyConditions detect an anomaly either through error code or latency ++ boolean isAnomaly = false; ++ ++ String errorCodeRegex = condition.getErrorCodeRegex(); ++ if (statusCode != null && errorCodeRegex != null) { ++ isAnomaly = statusCode.toString().matches(errorCodeRegex); ++ } ++ ++ Long highLatencyMs = condition.getHighLatencyMs(); ++ if (highLatencyMs != null) { ++ isAnomaly = ++ (errorCodeRegex == null || isAnomaly) ++ && (span.getLatencyNanos() / 1_000_000.0) >= highLatencyMs; ++ } ++ ++ if (isAnomaly) { ++ AwsXrayAdaptiveSamplingConfig.UsageType usage = condition.getUsage(); ++ if (usage != null) { ++ switch (usage) { ++ case BOTH: ++ shouldBoostSampling = true; ++ shouldCaptureAnomalySpan = true; ++ break; ++ case SAMPLING_BOOST: ++ shouldBoostSampling = true; ++ break; ++ case ANOMALY_TRACE_CAPTURE: ++ shouldCaptureAnomalySpan = true; ++ break; ++ default: // do nothing ++ } ++ } else { ++ shouldBoostSampling = true; ++ shouldCaptureAnomalySpan = true; ++ } ++ } ++ if (shouldBoostSampling && shouldCaptureAnomalySpan) { ++ break; ++ } ++ } ++ } else if ((statusCode != null && statusCode > 499) ++ || (statusCode == null ++ && spanData.getStatus() != null ++ && StatusCode.ERROR.equals(spanData.getStatus().getStatusCode()))) { ++ shouldBoostSampling = true; ++ shouldCaptureAnomalySpan = true; ++ } ++ ++ return new AnomalyDetectionResult(shouldBoostSampling, shouldCaptureAnomalySpan); ++ } ++ + static boolean isKeyPresent(SpanData span, AttributeKey key) { + return span.getAttributes().get(key) != null; + } @@ -1360,6 +1511,33 @@ index 75977dc0..9620ba2b 100644 + return "/"; + } + ++ private void updateTraceUsageCache( ++ String traceId, boolean isSpanCaptured, boolean isCountedAsAnomalyForBoost) { ++ AwsXrayAdaptiveSamplingConfig.UsageType existingUsage = traceUsageCache.getIfPresent(traceId); ++ ++ // Any interaction with a cache entry will reset the expiration timer of that entry ++ if (isSpanCaptured && isCountedAsAnomalyForBoost) { ++ this.traceUsageCache.put(traceId, AwsXrayAdaptiveSamplingConfig.UsageType.BOTH); ++ } else if (isSpanCaptured) { ++ if (AwsXrayAdaptiveSamplingConfig.UsageType.isUsedForBoost(existingUsage)) { ++ this.traceUsageCache.put(traceId, AwsXrayAdaptiveSamplingConfig.UsageType.BOTH); ++ } else { ++ this.traceUsageCache.put( ++ traceId, AwsXrayAdaptiveSamplingConfig.UsageType.ANOMALY_TRACE_CAPTURE); ++ } ++ } else if (isCountedAsAnomalyForBoost) { ++ if (AwsXrayAdaptiveSamplingConfig.UsageType.isUsedForAnomalyTraceCapture(existingUsage)) { ++ this.traceUsageCache.put(traceId, AwsXrayAdaptiveSamplingConfig.UsageType.BOTH); ++ } else { ++ this.traceUsageCache.put(traceId, AwsXrayAdaptiveSamplingConfig.UsageType.SAMPLING_BOOST); ++ } ++ } else if (existingUsage != null) { ++ this.traceUsageCache.put(traceId, existingUsage); ++ } else { ++ this.traceUsageCache.put(traceId, AwsXrayAdaptiveSamplingConfig.UsageType.NEITHER); ++ } ++ } ++ + private static Map createRuleHashMaps( + List rules) { + Map ruleToHashMap = new HashMap<>(); @@ -1394,21 +1572,39 @@ index 75977dc0..9620ba2b 100644 + Cache getTraceUsageCache() { + traceUsageCache.cleanUp(); + return traceUsageCache; ++ } ++ ++ private static class AnomalyDetectionResult { ++ private final boolean shouldBoostSampling; ++ private final boolean shouldCaptureAnomalySpan; ++ ++ public AnomalyDetectionResult(boolean shouldBoostSampling, boolean shouldCaptureAnomalySpan) { ++ this.shouldBoostSampling = shouldBoostSampling; ++ this.shouldCaptureAnomalySpan = shouldCaptureAnomalySpan; ++ } ++ ++ boolean shouldBoostSampling() { ++ return shouldBoostSampling; ++ } ++ ++ boolean shouldCaptureAnomalySpan() { ++ return shouldCaptureAnomalySpan; ++ } } } diff --git a/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/AwsXrayRemoteSamplerTest.java b/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/AwsXrayRemoteSamplerTest.java -index 4e5cd13b..ec256fe0 100644 +index 4e5cd13b..5af11a25 100644 --- a/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/AwsXrayRemoteSamplerTest.java +++ b/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/AwsXrayRemoteSamplerTest.java @@ -7,7 +7,10 @@ package io.opentelemetry.contrib.awsxray; - + import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; import static org.awaitility.Awaitility.await; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; - + import com.google.common.io.ByteStreams; import com.linecorp.armeria.common.HttpResponse; @@ -21,6 +24,9 @@ import io.opentelemetry.api.trace.SpanKind; @@ -1421,10 +1617,25 @@ index 4e5cd13b..ec256fe0 100644 import io.opentelemetry.sdk.trace.samplers.Sampler; import io.opentelemetry.sdk.trace.samplers.SamplingDecision; import java.io.IOException; -@@ -187,6 +193,31 @@ class AwsXrayRemoteSamplerTest { - } +@@ -169,21 +175,28 @@ class AwsXrayRemoteSamplerTest { } - + + @Test +- void parentBasedXraySamplerAfterDefaultSampler() { +- rulesResponse.set(RULE_RESPONSE_1); +- try (AwsXrayRemoteSampler samplerWithLongerPollingInterval = +- AwsXrayRemoteSampler.newBuilder(Resource.empty()) +- .setInitialSampler(Sampler.alwaysOn()) +- .setEndpoint(server.httpUri().toString()) +- .setPollingInterval(Duration.ofMillis(5)) +- .build()) { +- await() +- .pollDelay(Duration.ofMillis(10)) +- .untilAsserted( +- () -> { +- assertThat(sampler.getDescription()) +- .startsWith("AwsXrayRemoteSampler{ParentBased{root:XrayRulesSampler{["); +- }); + void setAndResetSpanExporter() { + try (AwsXrayRemoteSampler sampler = AwsXrayRemoteSampler.newBuilder(Resource.empty()).build()) { + // Setting span exporter should only work once @@ -1447,16 +1658,13 @@ index 4e5cd13b..ec256fe0 100644 + sampler.setSpanExporter(mock(SpanExporter.class)); + assertThatCode(() -> sampler.adaptSampling(mock(ReadableSpan.class), mock(SpanData.class))) + .doesNotThrowAnyException(); -+ } -+ } -+ - // https://github.com/open-telemetry/opentelemetry-java-contrib/issues/376 - @Test - void testJitterTruncation() { -@@ -206,6 +237,16 @@ class AwsXrayRemoteSamplerTest { } } - + +@@ -206,6 +219,16 @@ class AwsXrayRemoteSamplerTest { + } + } + + @Test + void setAdaptiveSamplingConfig() { + try (AwsXrayRemoteSampler sampler = AwsXrayRemoteSampler.newBuilder(Resource.empty()).build()) { @@ -1471,21 +1679,23 @@ index 4e5cd13b..ec256fe0 100644 return sampler .shouldSample( diff --git a/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/SamplingRuleApplierTest.java b/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/SamplingRuleApplierTest.java -index 920a5ffd..dcc7118a 100644 +index 920a5ffd..b7c21aa0 100644 --- a/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/SamplingRuleApplierTest.java +++ b/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/SamplingRuleApplierTest.java -@@ -15,18 +15,25 @@ import static io.opentelemetry.semconv.incubating.HttpIncubatingAttributes.HTTP_ +@@ -15,18 +15,27 @@ import static io.opentelemetry.semconv.incubating.HttpIncubatingAttributes.HTTP_ import static io.opentelemetry.semconv.incubating.NetIncubatingAttributes.NET_HOST_NAME; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; - + import com.fasterxml.jackson.databind.ObjectMapper; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; ++import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanContext; ++import io.opentelemetry.api.trace.SpanId; import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.TraceFlags; import io.opentelemetry.api.trace.TraceId; @@ -1500,7 +1710,7 @@ index 920a5ffd..dcc7118a 100644 import io.opentelemetry.sdk.trace.samplers.SamplingDecision; import io.opentelemetry.sdk.trace.samplers.SamplingResult; import io.opentelemetry.semconv.HttpAttributes; -@@ -37,6 +44,7 @@ import java.io.IOException; +@@ -37,6 +46,7 @@ import java.io.IOException; import java.io.UncheckedIOException; import java.time.Duration; import java.time.Instant; @@ -1508,16 +1718,16 @@ index 920a5ffd..dcc7118a 100644 import java.util.Collections; import java.util.Date; import java.util.concurrent.TimeUnit; -@@ -50,6 +58,7 @@ class SamplingRuleApplierTest { +@@ -50,6 +60,7 @@ class SamplingRuleApplierTest { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - + private static final String CLIENT_ID = "test-client-id"; + private static final String TEST_SERVICE_NAME = "test-service-name"; - + @Nested @SuppressWarnings("ClassCanBeStatic") -@@ -57,7 +66,10 @@ class SamplingRuleApplierTest { - +@@ -57,7 +68,10 @@ class SamplingRuleApplierTest { + private final SamplingRuleApplier applier = new SamplingRuleApplier( - CLIENT_ID, readSamplingRule("/sampling-rule-exactmatch.json"), Clock.getDefault()); @@ -1525,12 +1735,12 @@ index 920a5ffd..dcc7118a 100644 + readSamplingRule("/sampling-rule-exactmatch.json"), + TEST_SERVICE_NAME, + Clock.getDefault()); - + private final Resource resource = Resource.builder() -@@ -91,7 +103,8 @@ class SamplingRuleApplierTest { +@@ -91,7 +105,8 @@ class SamplingRuleApplierTest { .isEqualTo(SamplingResult.create(SamplingDecision.RECORD_AND_SAMPLE)); - + Date now = new Date(); - GetSamplingTargetsRequest.SamplingStatisticsDocument statistics = applier.snapshot(now); + GetSamplingTargetsRequest.SamplingStatisticsDocument statistics = @@ -1538,16 +1748,16 @@ index 920a5ffd..dcc7118a 100644 assertThat(statistics.getClientId()).isEqualTo(CLIENT_ID); assertThat(statistics.getRuleName()).isEqualTo("Test"); assertThat(statistics.getTimestamp()).isEqualTo(now); -@@ -100,7 +113,7 @@ class SamplingRuleApplierTest { +@@ -100,7 +115,7 @@ class SamplingRuleApplierTest { assertThat(statistics.getBorrowCount()).isEqualTo(0); - + // Reset - statistics = applier.snapshot(now); + statistics = applier.snapshot(now).getStatisticsDocument(); assertThat(statistics.getRequestCount()).isEqualTo(0); assertThat(statistics.getSampledCount()).isEqualTo(0); assertThat(statistics.getBorrowCount()).isEqualTo(0); -@@ -108,7 +121,7 @@ class SamplingRuleApplierTest { +@@ -108,7 +123,7 @@ class SamplingRuleApplierTest { doSample(applier); doSample(applier); now = new Date(); @@ -1556,8 +1766,8 @@ index 920a5ffd..dcc7118a 100644 assertThat(statistics.getClientId()).isEqualTo(CLIENT_ID); assertThat(statistics.getRuleName()).isEqualTo("Test"); assertThat(statistics.getTimestamp()).isEqualTo(now); -@@ -283,7 +296,10 @@ class SamplingRuleApplierTest { - +@@ -283,7 +298,10 @@ class SamplingRuleApplierTest { + private final SamplingRuleApplier applier = new SamplingRuleApplier( - CLIENT_ID, readSamplingRule("/sampling-rule-wildcards.json"), Clock.getDefault()); @@ -1565,12 +1775,12 @@ index 920a5ffd..dcc7118a 100644 + readSamplingRule("/sampling-rule-wildcards.json"), + TEST_SERVICE_NAME, + Clock.getDefault()); - + private final Resource resource = Resource.builder() -@@ -316,7 +332,8 @@ class SamplingRuleApplierTest { +@@ -316,7 +334,8 @@ class SamplingRuleApplierTest { assertThat(doSample(applier)).isEqualTo(SamplingResult.create(SamplingDecision.DROP)); - + Date now = new Date(); - GetSamplingTargetsRequest.SamplingStatisticsDocument statistics = applier.snapshot(now); + GetSamplingTargetsRequest.SamplingStatisticsDocument statistics = @@ -1578,16 +1788,16 @@ index 920a5ffd..dcc7118a 100644 assertThat(statistics.getClientId()).isEqualTo(CLIENT_ID); assertThat(statistics.getRuleName()).isEqualTo("Test"); assertThat(statistics.getTimestamp()).isEqualTo(now); -@@ -325,7 +342,7 @@ class SamplingRuleApplierTest { +@@ -325,7 +344,7 @@ class SamplingRuleApplierTest { assertThat(statistics.getBorrowCount()).isEqualTo(0); - + // Reset - statistics = applier.snapshot(now); + statistics = applier.snapshot(now).getStatisticsDocument(); assertThat(statistics.getRequestCount()).isEqualTo(0); assertThat(statistics.getSampledCount()).isEqualTo(0); assertThat(statistics.getBorrowCount()).isEqualTo(0); -@@ -333,7 +350,7 @@ class SamplingRuleApplierTest { +@@ -333,7 +352,7 @@ class SamplingRuleApplierTest { doSample(applier); doSample(applier); now = new Date(); @@ -1596,8 +1806,8 @@ index 920a5ffd..dcc7118a 100644 assertThat(statistics.getClientId()).isEqualTo(CLIENT_ID); assertThat(statistics.getRuleName()).isEqualTo("Test"); assertThat(statistics.getTimestamp()).isEqualTo(now); -@@ -626,7 +643,10 @@ class SamplingRuleApplierTest { - +@@ -626,7 +645,10 @@ class SamplingRuleApplierTest { + private final SamplingRuleApplier applier = new SamplingRuleApplier( - CLIENT_ID, readSamplingRule("/sampling-rule-awslambda.json"), Clock.getDefault()); @@ -1605,10 +1815,10 @@ index 920a5ffd..dcc7118a 100644 + readSamplingRule("/sampling-rule-awslambda.json"), + TEST_SERVICE_NAME, + Clock.getDefault()); - + private final Resource resource = Resource.builder() -@@ -677,7 +697,10 @@ class SamplingRuleApplierTest { +@@ -677,7 +699,10 @@ class SamplingRuleApplierTest { void borrowing() { SamplingRuleApplier applier = new SamplingRuleApplier( @@ -1617,12 +1827,12 @@ index 920a5ffd..dcc7118a 100644 + readSamplingRule("/sampling-rule-reservoir.json"), + TEST_SERVICE_NAME, + Clock.getDefault()); - + // Borrow assertThat(doSample(applier)) -@@ -688,7 +711,8 @@ class SamplingRuleApplierTest { +@@ -688,7 +713,8 @@ class SamplingRuleApplierTest { assertThat(doSample(applier)).isEqualTo(SamplingResult.create(SamplingDecision.DROP)); - + Date now = new Date(); - GetSamplingTargetsRequest.SamplingStatisticsDocument statistics = applier.snapshot(now); + GetSamplingTargetsRequest.SamplingStatisticsDocument statistics = @@ -1630,25 +1840,68 @@ index 920a5ffd..dcc7118a 100644 assertThat(statistics.getClientId()).isEqualTo(CLIENT_ID); assertThat(statistics.getRuleName()).isEqualTo("Test"); assertThat(statistics.getTimestamp()).isEqualTo(now); -@@ -697,7 +721,7 @@ class SamplingRuleApplierTest { +@@ -697,7 +723,7 @@ class SamplingRuleApplierTest { assertThat(statistics.getBorrowCount()).isEqualTo(1); - + // Reset - statistics = applier.snapshot(now); + statistics = applier.snapshot(now).getStatisticsDocument(); assertThat(statistics.getRequestCount()).isEqualTo(0); assertThat(statistics.getSampledCount()).isEqualTo(0); assertThat(statistics.getBorrowCount()).isEqualTo(0); -@@ -713,7 +737,7 @@ class SamplingRuleApplierTest { +@@ -713,7 +739,7 @@ class SamplingRuleApplierTest { }); - + now = new Date(); - statistics = applier.snapshot(now); + statistics = applier.snapshot(now).getStatisticsDocument(); assertThat(statistics.getClientId()).isEqualTo(CLIENT_ID); assertThat(statistics.getRuleName()).isEqualTo("Test"); assertThat(statistics.getTimestamp()).isEqualTo(now); -@@ -727,7 +751,7 @@ class SamplingRuleApplierTest { +@@ -722,12 +748,50 @@ class SamplingRuleApplierTest { + assertThat(statistics.getBorrowCount()).isEqualTo(1); + } + ++ @Test ++ void generateStatistics() { ++ SamplingRuleApplier applier = ++ new SamplingRuleApplier( ++ CLIENT_ID, ++ readSamplingRule("/sampling-rule-sample-all.json"), ++ TEST_SERVICE_NAME, ++ Clock.getDefault()); ++ ++ // Send a span for which the sampling decision hasn't been made yet ++ assertThat(doSample(applier)) ++ .isEqualTo(SamplingResult.create(SamplingDecision.RECORD_AND_SAMPLE)); ++ ++ // Send spans for which the sampling decision has already been made ++ // Send in different amounts to ensure statistics are generated for correct calls ++ assertThat(doSampleSpanWithValidContext(applier, /* isSampled= */ true)) ++ .isEqualTo(SamplingResult.create(SamplingDecision.RECORD_AND_SAMPLE)); ++ assertThat(doSampleSpanWithValidContext(applier, /* isSampled= */ true)) ++ .isEqualTo(SamplingResult.create(SamplingDecision.RECORD_AND_SAMPLE)); ++ assertThat(doSampleSpanWithValidContext(applier, /* isSampled= */ false)) ++ .isEqualTo(SamplingResult.create(SamplingDecision.DROP)); ++ assertThat(doSampleSpanWithValidContext(applier, /* isSampled= */ false)) ++ .isEqualTo(SamplingResult.create(SamplingDecision.DROP)); ++ assertThat(doSampleSpanWithValidContext(applier, /* isSampled= */ false)) ++ .isEqualTo(SamplingResult.create(SamplingDecision.DROP)); ++ ++ // Verify outgoing statistics ++ Date now = new Date(); ++ GetSamplingTargetsRequest.SamplingStatisticsDocument statistics = ++ applier.snapshot(now).getStatisticsDocument(); ++ assertThat(statistics.getClientId()).isEqualTo(CLIENT_ID); ++ assertThat(statistics.getRuleName()).isEqualTo("Test"); ++ assertThat(statistics.getTimestamp()).isEqualTo(now); ++ assertThat(statistics.getRequestCount()).isEqualTo(1); ++ assertThat(statistics.getSampledCount()).isEqualTo(1); ++ assertThat(statistics.getBorrowCount()).isEqualTo(0); ++ } ++ + @Test + void ruleWithTarget() { TestClock clock = TestClock.create(); SamplingRuleApplier applier = new SamplingRuleApplier( @@ -1657,8 +1910,8 @@ index 920a5ffd..dcc7118a 100644 // No target yet, borrows from reservoir every second. assertThat(doSample(applier)) .isEqualTo(SamplingResult.create(SamplingDecision.RECORD_AND_SAMPLE)); -@@ -746,8 +770,8 @@ class SamplingRuleApplierTest { - +@@ -746,8 +810,8 @@ class SamplingRuleApplierTest { + // Got a target! SamplingTargetDocument target = - SamplingTargetDocument.create(0.0, 5, 2, Date.from(now.plusSeconds(10)), "test"); @@ -1667,8 +1920,8 @@ index 920a5ffd..dcc7118a 100644 + applier = applier.withTarget(target, Date.from(now), clock.nanoTime()); // Statistics not expired yet assertThat(applier.snapshot(Date.from(now))).isNull(); - -@@ -786,7 +810,7 @@ class SamplingRuleApplierTest { + +@@ -786,7 +850,7 @@ class SamplingRuleApplierTest { TestClock clock = TestClock.create(); SamplingRuleApplier applier = new SamplingRuleApplier( @@ -1677,9 +1930,9 @@ index 920a5ffd..dcc7118a 100644 // No target yet, borrows from reservoir every second. assertThat(doSample(applier)) .isEqualTo(SamplingResult.create(SamplingDecision.RECORD_AND_SAMPLE)); -@@ -804,8 +828,8 @@ class SamplingRuleApplierTest { +@@ -804,8 +868,8 @@ class SamplingRuleApplierTest { assertThat(applier.snapshot(Date.from(now.plus(Duration.ofMinutes(30))))).isNotNull(); - + // Got a target! - SamplingTargetDocument target = SamplingTargetDocument.create(0.0, 5, null, null, "test"); - applier = applier.withTarget(target, Date.from(now)); @@ -1688,10 +1941,10 @@ index 920a5ffd..dcc7118a 100644 // No reservoir, always use fixed rate (drop) assertThat(doSample(applier)).isEqualTo(SamplingResult.create(SamplingDecision.DROP)); assertThat(doSample(applier)).isEqualTo(SamplingResult.create(SamplingDecision.DROP)); -@@ -815,12 +839,105 @@ class SamplingRuleApplierTest { +@@ -815,12 +879,105 @@ class SamplingRuleApplierTest { assertThat(applier.snapshot(Date.from(now))).isNotNull(); } - + + @Test + void ruleWithBoost() { + TestClock clock = TestClock.create(); @@ -1792,13 +2045,13 @@ index 920a5ffd..dcc7118a 100644 new SamplingRuleApplier( - CLIENT_ID, readSamplingRule("/sampling-rule-reservoir.json"), clock); + CLIENT_ID, readSamplingRule("/sampling-rule-reservoir.json"), TEST_SERVICE_NAME, clock); - + Instant now = Instant.ofEpochSecond(0, clock.now()); assertThat(applier.snapshot(Date.from(now))).isNotNull(); -@@ -839,6 +956,71 @@ class SamplingRuleApplierTest { +@@ -839,6 +996,71 @@ class SamplingRuleApplierTest { assertThat(doSample(applier)).isEqualTo(SamplingResult.create(SamplingDecision.DROP)); } - + + @Test + void hasBoostMethod() { + SamplingRuleApplier applierWithBoost = @@ -1867,21 +2120,50 @@ index 920a5ffd..dcc7118a 100644 private static SamplingResult doSample(SamplingRuleApplier applier) { return applier.shouldSample( Context.current(), +@@ -849,6 +1071,28 @@ class SamplingRuleApplierTest { + Collections.emptyList()); + } + ++ private static SamplingResult doSampleSpanWithValidContext( ++ SamplingRuleApplier applier, boolean isSampled) { ++ String traceId = TraceId.fromLongs(1, 2); ++ Context parentContext = ++ Context.root() ++ .with( ++ Span.wrap( ++ SpanContext.create( ++ traceId, ++ SpanId.fromLong(1L), ++ isSampled ? TraceFlags.getSampled() : TraceFlags.getDefault(), ++ TraceState.getDefault()))); ++ ++ return applier.shouldSample( ++ parentContext, ++ traceId, ++ SpanId.fromLong(2L), ++ SpanKind.CLIENT, ++ Attributes.empty(), ++ Collections.emptyList()); ++ } ++ + private static GetSamplingRulesResponse.SamplingRule readSamplingRule(String resourcePath) { + try { + return OBJECT_MAPPER.readValue( diff --git a/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/XrayRulesSamplerTest.java b/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/XrayRulesSamplerTest.java -index 1ca8df34..72ec524b 100644 +index 1ca8df34..14ebdbda 100644 --- a/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/XrayRulesSamplerTest.java +++ b/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/XrayRulesSamplerTest.java @@ -5,17 +5,28 @@ - + package io.opentelemetry.contrib.awsxray; - + +import static io.opentelemetry.semconv.HttpAttributes.HTTP_RESPONSE_STATUS_CODE; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; - + import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.SpanContext; @@ -1912,9 +2194,9 @@ index 1ca8df34..72ec524b 100644 import java.util.stream.Collectors; import java.util.stream.Stream; import org.junit.jupiter.api.Test; - + class XrayRulesSamplerTest { - + + private static final AttributeKey URL_PATH = AttributeKey.stringKey("url.path"); + private static final AttributeKey HTTP_METHOD = AttributeKey.stringKey("http.method"); + @@ -1958,24 +2240,26 @@ index 1ca8df34..72ec524b 100644 - 1); + 1, + null); - + TestClock clock = TestClock.create(); XrayRulesSampler sampler = -@@ -103,22 +124,58 @@ class XrayRulesSamplerTest { +@@ -103,22 +124,72 @@ class XrayRulesSamplerTest { Resource.getDefault(), clock, Sampler.alwaysOn(), - Arrays.asList(rule1, rule4, rule3, rule2)); + Arrays.asList(rule1, rule4, rule3, rule2), + null); - + assertThat(doSample(sampler, "cat-service")) - .isEqualTo(SamplingResult.create(SamplingDecision.RECORD_AND_SAMPLE)); + .usingRecursiveComparison() + .isEqualTo( + AwsSamplingResult.create( + SamplingDecision.RECORD_AND_SAMPLE, -+ Attributes.empty(), ++ Attributes.builder() ++ .put(XrayRulesSampler.AWS_XRAY_SAMPLING_RULE, "cat-rule") ++ .build(), + XrayRulesSampler.hashRuleName("cat-rule"))); assertThat(doSample(sampler, "cat-service")) - .isEqualTo(SamplingResult.create(SamplingDecision.RECORD_AND_SAMPLE)); @@ -1983,7 +2267,9 @@ index 1ca8df34..72ec524b 100644 + .isEqualTo( + AwsSamplingResult.create( + SamplingDecision.RECORD_AND_SAMPLE, -+ Attributes.empty(), ++ Attributes.builder() ++ .put(XrayRulesSampler.AWS_XRAY_SAMPLING_RULE, "cat-rule") ++ .build(), + XrayRulesSampler.hashRuleName("cat-rule"))); assertThat(doSample(sampler, "dog-service")) - .isEqualTo(SamplingResult.create(SamplingDecision.RECORD_AND_SAMPLE)); @@ -1991,7 +2277,9 @@ index 1ca8df34..72ec524b 100644 + .isEqualTo( + AwsSamplingResult.create( + SamplingDecision.RECORD_AND_SAMPLE, -+ Attributes.empty(), ++ Attributes.builder() ++ .put(XrayRulesSampler.AWS_XRAY_SAMPLING_RULE, "dog-rule") ++ .build(), + XrayRulesSampler.hashRuleName("dog-rule"))); assertThat(doSample(sampler, "dog-service")) - .isEqualTo(SamplingResult.create(SamplingDecision.DROP)); @@ -1999,7 +2287,9 @@ index 1ca8df34..72ec524b 100644 + .isEqualTo( + AwsSamplingResult.create( + SamplingDecision.DROP, -+ Attributes.empty(), ++ Attributes.builder() ++ .put(XrayRulesSampler.AWS_XRAY_SAMPLING_RULE, "dog-rule") ++ .build(), + XrayRulesSampler.hashRuleName("dog-rule"))); assertThat(doSample(sampler, "bat-service")) - .isEqualTo(SamplingResult.create(SamplingDecision.RECORD_AND_SAMPLE)); @@ -2007,7 +2297,9 @@ index 1ca8df34..72ec524b 100644 + .isEqualTo( + AwsSamplingResult.create( + SamplingDecision.RECORD_AND_SAMPLE, -+ Attributes.empty(), ++ Attributes.builder() ++ .put(XrayRulesSampler.AWS_XRAY_SAMPLING_RULE, "bat-rule") ++ .build(), + XrayRulesSampler.hashRuleName("bat-rule"))); assertThat(doSample(sampler, "bat-service")) - .isEqualTo(SamplingResult.create(SamplingDecision.RECORD_AND_SAMPLE)); @@ -2015,7 +2307,9 @@ index 1ca8df34..72ec524b 100644 + .isEqualTo( + AwsSamplingResult.create( + SamplingDecision.RECORD_AND_SAMPLE, -+ Attributes.empty(), ++ Attributes.builder() ++ .put(XrayRulesSampler.AWS_XRAY_SAMPLING_RULE, "bat-rule") ++ .build(), + XrayRulesSampler.hashRuleName("bat-rule"))); assertThat(doSample(sampler, "unknown")) - .isEqualTo(SamplingResult.create(SamplingDecision.DROP)); @@ -2023,25 +2317,27 @@ index 1ca8df34..72ec524b 100644 + .isEqualTo( + AwsSamplingResult.create( + SamplingDecision.DROP, -+ Attributes.empty(), ++ Attributes.builder() ++ .put(XrayRulesSampler.AWS_XRAY_SAMPLING_RULE, "default-rule") ++ .build(), + XrayRulesSampler.hashRuleName("default-rule"))); - + Instant now = Instant.ofEpochSecond(0, clock.now()); assertThat(sampler.snapshot(Date.from(now))).hasSize(4); -@@ -128,10 +185,10 @@ class XrayRulesSamplerTest { +@@ -128,10 +199,10 @@ class XrayRulesSamplerTest { assertThat(sampler.snapshot(Date.from(now))).hasSize(4); - + SamplingTargetDocument catTarget = - SamplingTargetDocument.create(0.0, 10, null, null, "cat-rule"); + SamplingTargetDocument.create(0.0, 10, null, null, null, "cat-rule"); - + SamplingTargetDocument batTarget = - SamplingTargetDocument.create(0.0, 5, null, null, "bat-rule"); + SamplingTargetDocument.create(0.0, 5, null, null, null, "bat-rule"); - + clock.advance(Duration.ofSeconds(10)); now = Instant.ofEpochSecond(0, clock.now()); -@@ -145,16 +202,41 @@ class XrayRulesSamplerTest { +@@ -145,16 +216,51 @@ class XrayRulesSamplerTest { .collect(Collectors.toSet()), Date.from(now)); assertThat(doSample(sampler, "dog-service")) @@ -2050,7 +2346,9 @@ index 1ca8df34..72ec524b 100644 + .isEqualTo( + AwsSamplingResult.create( + SamplingDecision.RECORD_AND_SAMPLE, -+ Attributes.empty(), ++ Attributes.builder() ++ .put(XrayRulesSampler.AWS_XRAY_SAMPLING_RULE, "dog-rule") ++ .build(), + XrayRulesSampler.hashRuleName("dog-rule"))); assertThat(doSample(sampler, "dog-service")) - .isEqualTo(SamplingResult.create(SamplingDecision.DROP)); @@ -2058,7 +2356,9 @@ index 1ca8df34..72ec524b 100644 + .isEqualTo( + AwsSamplingResult.create( + SamplingDecision.DROP, -+ Attributes.empty(), ++ Attributes.builder() ++ .put(XrayRulesSampler.AWS_XRAY_SAMPLING_RULE, "dog-rule") ++ .build(), + XrayRulesSampler.hashRuleName("dog-rule"))); assertThat(doSample(sampler, "unknown")) - .isEqualTo(SamplingResult.create(SamplingDecision.DROP)); @@ -2066,7 +2366,9 @@ index 1ca8df34..72ec524b 100644 + .isEqualTo( + AwsSamplingResult.create( + SamplingDecision.DROP, -+ Attributes.empty(), ++ Attributes.builder() ++ .put(XrayRulesSampler.AWS_XRAY_SAMPLING_RULE, "default-rule") ++ .build(), + XrayRulesSampler.hashRuleName("default-rule"))); // Targets overridden to always drop. assertThat(doSample(sampler, "cat-service")) @@ -2075,7 +2377,9 @@ index 1ca8df34..72ec524b 100644 + .isEqualTo( + AwsSamplingResult.create( + SamplingDecision.DROP, -+ Attributes.empty(), ++ Attributes.builder() ++ .put(XrayRulesSampler.AWS_XRAY_SAMPLING_RULE, "cat-rule") ++ .build(), + XrayRulesSampler.hashRuleName("cat-rule"))); assertThat(doSample(sampler, "bat-service")) - .isEqualTo(SamplingResult.create(SamplingDecision.DROP)); @@ -2083,15 +2387,17 @@ index 1ca8df34..72ec524b 100644 + .isEqualTo( + AwsSamplingResult.create( + SamplingDecision.DROP, -+ Attributes.empty(), ++ Attributes.builder() ++ .put(XrayRulesSampler.AWS_XRAY_SAMPLING_RULE, "bat-rule") ++ .build(), + XrayRulesSampler.hashRuleName("bat-rule"))); - + // Minimum is batTarget, 5s from now assertThat(sampler.nextTargetFetchTimeNanos()) -@@ -169,6 +251,867 @@ class XrayRulesSamplerTest { +@@ -169,6 +275,891 @@ class XrayRulesSamplerTest { assertThat(sampler.snapshot(Date.from(now))).hasSize(4); } - + + @Test + void updateTargetsWithLocalAdaptiveSamplingConfig() { + SamplingRule rule1 = @@ -2430,6 +2736,10 @@ index 1ca8df34..72ec524b 100644 + .thenReturn( + SpanContext.create( + "TRACE_ID", "SPAN_ID", TraceFlags.getDefault(), TraceState.getDefault())); ++ when(readableSpanMock.getParentSpanContext()) ++ .thenReturn( ++ SpanContext.create( ++ "TRACE_ID", "SPAN_ID", TraceFlags.getDefault(), TraceState.getDefault())); + SpanData spanDataMock = mock(SpanData.class); + Attributes attributesMock = mock(Attributes.class); + when(spanDataMock.getAttributes()).thenReturn(attributesMock); @@ -2574,6 +2884,10 @@ index 1ca8df34..72ec524b 100644 + .thenReturn( + SpanContext.create( + "TRACE_ID", "SPAN_ID", TraceFlags.getDefault(), TraceState.getDefault())); ++ when(readableSpanMock.getParentSpanContext()) ++ .thenReturn( ++ SpanContext.create( ++ "TRACE_ID", "SPAN_ID", TraceFlags.getDefault(), TraceState.getDefault())); + when(readableSpanMock.getAttribute(any())).thenReturn("test-operation"); + when(readableSpanMock.getLatencyNanos()).thenReturn(1L); + @@ -2642,6 +2956,10 @@ index 1ca8df34..72ec524b 100644 + .thenReturn( + SpanContext.create( + "TRACE_ID", "SPAN_ID", TraceFlags.getDefault(), TraceState.getDefault())); ++ when(readableSpanMock.getParentSpanContext()) ++ .thenReturn( ++ SpanContext.create( ++ "TRACE_ID", "SPAN_ID", TraceFlags.getDefault(), TraceState.getDefault())); + when(readableSpanMock.getAttribute(any())).thenReturn("test-operation"); + when(readableSpanMock.getLatencyNanos()).thenReturn(300_000_000L); // 300 ms + @@ -2711,6 +3029,10 @@ index 1ca8df34..72ec524b 100644 + .thenReturn( + SpanContext.create( + "TRACE_ID", "SPAN_ID", TraceFlags.getDefault(), TraceState.getDefault())); ++ when(readableSpanMock.getParentSpanContext()) ++ .thenReturn( ++ SpanContext.create( ++ "TRACE_ID", "SPAN_ID", TraceFlags.getDefault(), TraceState.getDefault())); + when(readableSpanMock.getAttribute(any())).thenReturn("test-operation"); + when(readableSpanMock.getLatencyNanos()).thenReturn(300_000_000L); // 300 ms + @@ -2796,6 +3118,10 @@ index 1ca8df34..72ec524b 100644 + .thenReturn( + SpanContext.create( + "TRACE_ID", "SPAN_ID", TraceFlags.getDefault(), TraceState.getDefault())); ++ when(readableSpanMock.getParentSpanContext()) ++ .thenReturn( ++ SpanContext.create( ++ "TRACE_ID", "SPAN_ID", TraceFlags.getDefault(), TraceState.getDefault())); + + SpanData spanDataMock = mock(SpanData.class); + Attributes attributesMock = mock(Attributes.class); @@ -2879,6 +3205,10 @@ index 1ca8df34..72ec524b 100644 + .thenReturn( + SpanContext.create( + "TRACE_ID", "SPAN_ID", TraceFlags.getDefault(), TraceState.getDefault())); ++ when(readableSpanMock.getParentSpanContext()) ++ .thenReturn( ++ SpanContext.create( ++ "TRACE_ID", "SPAN_ID", TraceFlags.getDefault(), TraceState.getDefault())); + when(readableSpanMock.getLatencyNanos()).thenReturn(1L); + + SpanData spanDataMock = mock(SpanData.class); @@ -2968,7 +3298,7 @@ index 283e3b3c..cf0cb072 100644 + .build()), + Collections.emptyList()); GetSamplingTargetsResponse response = client.getSamplingTargets(samplingTargetsRequest); - + AggregatedHttpRequest request = server.takeRequest().request(); @@ -174,7 +175,8 @@ class XraySamplerClientTest { assertThatThrownBy( @@ -3008,6 +3338,27 @@ index 00000000..32752d5e + "speed": "0" + } +} +diff --git a/aws-xray/src/test/resources/sampling-rule-sample-all.json b/aws-xray/src/test/resources/sampling-rule-sample-all.json +new file mode 100644 +index 00000000..4ba3013a +--- /dev/null ++++ b/aws-xray/src/test/resources/sampling-rule-sample-all.json +@@ -0,0 +1,15 @@ ++{ ++ "RuleName": "Test", ++ "RuleARN": "arn:aws:xray:us-east-1:595986152929:sampling-rule/Test", ++ "ResourceARN": "arn:aws:xray:us-east-1:595986152929:my-service", ++ "Priority": 1, ++ "FixedRate": 1.0, ++ "ReservoirSize": 0, ++ "ServiceName": "*", ++ "ServiceType": "*", ++ "Host": "*", ++ "HTTPMethod": "*", ++ "URLPath": "*", ++ "Version": 1, ++ "Attributes": {} ++} diff --git a/disk-buffering/build.gradle.kts b/disk-buffering/build.gradle.kts index 8250c1bd..74a1a24c 100644 --- a/disk-buffering/build.gradle.kts diff --git a/.github/patches/opentelemetry-java-instrumentation.patch b/.github/patches/opentelemetry-java-instrumentation.patch new file mode 100644 index 0000000000..988a048b1d --- /dev/null +++ b/.github/patches/opentelemetry-java-instrumentation.patch @@ -0,0 +1,28 @@ +diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts +index 98def282f8..65fd6a8a13 100644 +--- a/dependencyManagement/build.gradle.kts ++++ b/dependencyManagement/build.gradle.kts +@@ -104,7 +104,7 @@ val DEPENDENCIES = listOf( + "io.netty:netty:3.10.6.Final", + "io.opentelemetry.contrib:opentelemetry-azure-resources:${otelContribVersion}", + "io.opentelemetry.contrib:opentelemetry-aws-resources:${otelContribVersion}", +- "io.opentelemetry.contrib:opentelemetry-aws-xray-propagator:${otelContribVersion}", ++ "io.opentelemetry.contrib:opentelemetry-aws-xray-propagator:1.48.0-alpha-adot1", + "io.opentelemetry.contrib:opentelemetry-gcp-resources:${otelContribVersion}", + "io.opentelemetry.contrib:opentelemetry-cloudfoundry-resources:${otelContribVersion}", + "io.opentelemetry.contrib:opentelemetry-baggage-processor:${otelContribVersion}", +diff --git a/version.gradle.kts b/version.gradle.kts +index 023d04703c..ec9690086c 100644 +--- a/version.gradle.kts ++++ b/version.gradle.kts +@@ -1,5 +1,5 @@ +-val stableVersion = "2.18.1" +-val alphaVersion = "2.18.1-alpha" ++val stableVersion = "2.18.1-adot1" ++val alphaVersion = "2.18.1-adot1-alpha" + + allprojects { + if (findProperty("otel.stable") != "true") { +-- +2.45.1 + diff --git a/.github/scripts/patch.sh b/.github/scripts/patch.sh index b6a6bba94e..9d2c902a61 100755 --- a/.github/scripts/patch.sh +++ b/.github/scripts/patch.sh @@ -44,3 +44,16 @@ if [[ -f "$OTEL_JAVA_CONTRIB_PATCH" ]]; then else echo "Skipping patching opentelemetry-java-contrib" fi + + +OTEL_JAVA_INSTRUMENTATION_PATCH=".github/patches/opentelemetry-java-instrumentation.patch" +if [[ -f "$OTEL_JAVA_INSTRUMENTATION_PATCH" ]]; then + git clone https://github.com/open-telemetry/opentelemetry-java-instrumentation.git + cd opentelemetry-java-instrumentation + git checkout ${OTEL_JAVA_INSTRUMENTATION_VERSION} -b tag-${OTEL_JAVA_INSTRUMENTATION_VERSION} + patch -p1 < "../${OTEL_JAVA_INSTRUMENTATION_PATCH}" + git commit -a -m "ADOT Patch release" + cd - +else + echo "Skipping patching opentelemetry-java-instrumentation" +fi diff --git a/CHANGELOG.md b/CHANGELOG.md index dcfaa0543d..6750e1dc6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,4 +16,6 @@ If your change does not need a CHANGELOG entry, add the "skip changelog" label t ### Enhancements - Support X-Ray Trace Id extraction from Lambda Context object, and respect user-configured OTEL_PROPAGATORS in AWS Lamdba instrumentation - ([#1191](https://github.com/aws-observability/aws-otel-java-instrumentation/pull/1191)) \ No newline at end of file + ([#1191](https://github.com/aws-observability/aws-otel-java-instrumentation/pull/1191)) +- Adaptive Sampling improvements: Ensure propagation of sampling rule across services and AWS accounts. Remove unnecessary B3 propagator. + ([#1201](https://github.com/aws-observability/aws-otel-java-instrumentation/pull/1201)) \ No newline at end of file diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index d6218a08b2..cf5f3c2f26 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -27,7 +27,7 @@ data class DependencySet(val group: String, val version: String, val modules: Li val testSnapshots = rootProject.findProperty("testUpstreamSnapshots") == "true" // This is the version of the upstream instrumentation BOM -val otelVersion = "2.18.1" +val otelVersion = "2.18.1-adot1" val otelSnapshotVersion = "2.19.0" val otelAlphaVersion = if (!testSnapshots) "$otelVersion-alpha" else "$otelSnapshotVersion-alpha-SNAPSHOT" val otelJavaAgentVersion = if (!testSnapshots) otelVersion else "$otelSnapshotVersion-SNAPSHOT" diff --git a/lambda-layer/build-layer.sh b/lambda-layer/build-layer.sh index 791ad59152..473d83317d 100755 --- a/lambda-layer/build-layer.sh +++ b/lambda-layer/build-layer.sh @@ -2,22 +2,31 @@ set -e SOURCEDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +file="$SOURCEDIR/../.github/patches/versions" +contrib_version=$(awk -F'=v' '/OTEL_JAVA_CONTRIB_VERSION/ {print $2}' "$file") +if [[ -n "$contrib_version" ]]; then + echo "Found OTEL Contrib Version: ${contrib_version}" + ## Clone and Patch the OpenTelemetry Java contrib Repository + echo "Info: Cloning and Patching OpenTelemetry Java contrib Repository" + git clone https://github.com/open-telemetry/opentelemetry-java-contrib.git + pushd opentelemetry-java-contrib + git checkout v${contrib_version} -b tag-v${contrib_version} -## Get ADOT version -echo "Info: Getting ADOT Version" -pushd "$SOURCEDIR"/.. -version=$(./gradlew -q printVersion) -echo "Found ADOT Version: ${version}" -popd + # There is another patch in the .github/patches directory for other changes. We should apply them too for consistency. + patch -p1 < "$SOURCEDIR"/../.github/patches/opentelemetry-java-contrib.patch + + ./gradlew publishToMavenLocal + popd + rm -rf opentelemetry-java-contrib +fi ## Get OTel version echo "Info: Getting OTEL Version" -file="$SOURCEDIR/../.github/patches/versions" -otel_instrumentation_version=$(awk -F'=v' '/OTEL_JAVA_INSTRUMENTATION_VERSION/ {print $2}' "$file") -echo "Found OTEL Version: ${otel_instrumentation_version}" +version=$(awk -F'=v' '/OTEL_JAVA_INSTRUMENTATION_VERSION/ {print $2}' "$file") +echo "Found OTEL Version: ${version}" # Exit if the version is empty or null -if [[ -z "$otel_instrumentation_version" ]]; then +if [[ -z "$version" ]]; then echo "Error: Version could not be found in ${file}." exit 1 fi @@ -27,7 +36,10 @@ fi echo "Info: Cloning and Patching OpenTelemetry Java Instrumentation Repository" git clone https://github.com/open-telemetry/opentelemetry-java-instrumentation.git pushd opentelemetry-java-instrumentation -git checkout v${otel_instrumentation_version} -b tag-v${otel_instrumentation_version} +git checkout v${version} -b tag-v${version} + +# There is another patch in the .github/patches directory for other changes. We should apply them too for consistency. +patch -p1 < "$SOURCEDIR"/../.github/patches/opentelemetry-java-instrumentation.patch # This patch is for Lambda related context propagation patch -p1 < "$SOURCEDIR"/patches/opentelemetry-java-instrumentation.patch @@ -36,23 +48,6 @@ patch -p1 < "$SOURCEDIR"/patches/opentelemetry-java-instrumentation.patch popd rm -rf opentelemetry-java-instrumentation -contrib_version=$(awk -F'=v' '/OTEL_JAVA_CONTRIB_VERSION/ {print $2}' "$file") -if [[ -n "$contrib_version" ]]; then - echo "Found OTEL Contrib Version: ${contrib_version}" - ## Clone and Patch the OpenTelemetry Java contrib Repository - echo "Info: Cloning and Patching OpenTelemetry Java contrib Repository" - git clone https://github.com/open-telemetry/opentelemetry-java-contrib.git - pushd opentelemetry-java-contrib - git checkout v${contrib_version} -b tag-v${contrib_version} - - # There is another patch in the .github/patches directory for other changes. We should apply them too for consistency. - patch -p1 < "$SOURCEDIR"/../.github/patches/opentelemetry-java-contrib.patch - - ./gradlew publishToMavenLocal - popd - rm -rf opentelemetry-java-contrib -fi - ## Build the ADOT Java from current source echo "Info: Building ADOT Java from current source" pushd "$SOURCEDIR"/.. diff --git a/lambda-layer/patches/aws-otel-java-instrumentation.patch b/lambda-layer/patches/aws-otel-java-instrumentation.patch index bbd66b64c1..f95c364151 100644 --- a/lambda-layer/patches/aws-otel-java-instrumentation.patch +++ b/lambda-layer/patches/aws-otel-java-instrumentation.patch @@ -6,7 +6,7 @@ index d186406..91b9386 100644 val testSnapshots = rootProject.findProperty("testUpstreamSnapshots") == "true" // This is the version of the upstream instrumentation BOM --val otelVersion = "2.18.1" +-val otelVersion = "2.18.1-adot1" +val otelVersion = "2.18.1-adot-lambda1" val otelSnapshotVersion = "2.19.0" val otelAlphaVersion = if (!testSnapshots) "$otelVersion-alpha" else "$otelSnapshotVersion-alpha-SNAPSHOT" diff --git a/lambda-layer/patches/opentelemetry-java-instrumentation.patch b/lambda-layer/patches/opentelemetry-java-instrumentation.patch index c90c3bb0fa..f82cfd273f 100644 --- a/lambda-layer/patches/opentelemetry-java-instrumentation.patch +++ b/lambda-layer/patches/opentelemetry-java-instrumentation.patch @@ -618,8 +618,8 @@ index 023d04703c..b267166804 100644 --- a/version.gradle.kts +++ b/version.gradle.kts @@ -1,5 +1,5 @@ --val stableVersion = "2.18.1" --val alphaVersion = "2.18.1-alpha" +-val stableVersion = "2.18.1-adot1" +-val alphaVersion = "2.18.1-adot1-alpha" +val stableVersion = "2.18.1-adot-lambda1" +val alphaVersion = "2.18.1-adot-lambda1-alpha" diff --git a/scripts/local_patch.sh b/scripts/local_patch.sh index 079d4516b9..d1c01c5d8b 100755 --- a/scripts/local_patch.sh +++ b/scripts/local_patch.sh @@ -56,4 +56,28 @@ if [[ -f "$OTEL_JAVA_CONTRIB_PATCH" ]]; then rm -rf opentelemetry-java-contrib else echo "Skipping patching opentelemetry-java-contrib" +fi + + +# Patching opentelemetry-java-instrumentation +OTEL_JAVA_INSTRUMENTATION_PATCH=".github/patches/opentelemetry-java-instrumentation.patch" +if [[ -f "$OTEL_JAVA_INSTRUMENTATION_PATCH" ]]; then + echo "Patching opentelemetry-java-instrumentation" + git clone https://github.com/open-telemetry/opentelemetry-java-instrumentation.git + cd opentelemetry-java-instrumentation + + echo "Checking out tag ${OTEL_JAVA_INSTRUMENTATION_VERSION}" + git checkout ${OTEL_JAVA_INSTRUMENTATION_VERSION} -b tag-${OTEL_JAVA_INSTRUMENTATION_VERSION} + patch -p1 < "../${OTEL_JAVA_INSTRUMENTATION_PATCH}" + git commit -a -m "ADOT Patch release" + + echo "Building patched opentelemetry-java-instrumentation" + ./gradlew clean assemble + ./gradlew publishToMavenLocal + cd - + + echo "Cleaning up opentelemetry-java-instrumentation" + rm -rf opentelemetry-java-instrumentation +else + echo "Skipping patching opentelemetry-java-instrumentation" fi \ No newline at end of file