Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.contrib.dynamic.sampler;

import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.context.Context;
import io.opentelemetry.sdk.trace.data.LinkData;
import io.opentelemetry.sdk.trace.samplers.Sampler;
import io.opentelemetry.sdk.trace.samplers.SamplingResult;
import java.util.List;
import javax.annotation.Nullable;

/**
* A {@link Sampler} implementation that delegates sampling decisions to another {@link Sampler}
* instance held in a volatile field. This allows the effective sampling strategy to be reconfigured
* at runtime without rebuilding the {@code TracerSdkProvider} or recreating instrumented
* components.
*
* <p>This class is thread-safe. All access to the current delegate sampler is performed through a
* volatile reference, so sampling decisions and delegate updates may occur concurrently without
* additional synchronization.
*
* <p>The delegate sampler can be updated dynamically via {@link #setDelegate(Sampler)}. Passing
* {@code null} to {@code setDelegate} or the constructor will cause {@link Sampler#alwaysOn()} to
* be used as the fallback delegate.
*/
public class DelegatingSampler implements Sampler {

private volatile Sampler delegate;

/**
* Creates a new {@link DelegatingSampler} with the given initial delegate.
*
* <p>If {@code initialDelegate} is {@code null}, {@link Sampler#alwaysOn()} will be used as the
* initial delegate.
*
* @param initialDelegate the initial {@link Sampler} to delegate to, or {@code null} to use
* {@link Sampler#alwaysOn()} by default
*/
public DelegatingSampler(@Nullable Sampler initialDelegate) {
this.delegate = initialDelegate != null ? initialDelegate : Sampler.alwaysOn();
}

public DelegatingSampler() {
this(Sampler.alwaysOn());
}

/**
* Updates the delegate {@link Sampler} used by this {@code DelegatingSampler} at runtime.
*
* <p>If {@code sampler} is {@code null}, this method will instead use {@link Sampler#alwaysOn()}
* as the delegate.
*
* @param sampler the new delegate sampler to use, or {@code null} to fall back to {@link
* Sampler#alwaysOn()}
*/
public void setDelegate(@Nullable Sampler sampler) {
delegate = sampler != null ? sampler : Sampler.alwaysOn();
}

@Override
public SamplingResult shouldSample(
Context ctx,
String traceId,
String name,
SpanKind kind,
Attributes attrs,
List<LinkData> links) {
return delegate.shouldSample(ctx, traceId, name, kind, attrs, links);
}

@Override
public String getDescription() {
return "delegating/" + delegate.getDescription();
}

@Override
public String toString() {
return getDescription();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.contrib.dynamic.sampler;

import static org.assertj.core.api.Assertions.assertThat;

import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.context.Context;
import io.opentelemetry.sdk.trace.samplers.Sampler;
import io.opentelemetry.sdk.trace.samplers.SamplingDecision;
import io.opentelemetry.sdk.trace.samplers.SamplingResult;
import java.util.Collections;
import org.junit.jupiter.api.Test;

class DelegatingSamplerTest {

@Test
void defaultDelegateIsAlwaysOn() {
DelegatingSampler sampler = new DelegatingSampler();

SamplingDecision decision = doSample(sampler);

assertThat(decision).isEqualTo(SamplingDecision.RECORD_AND_SAMPLE);
}

@Test
void setDelegateNullFallsBackToAlwaysOn() {
DelegatingSampler sampler = new DelegatingSampler(Sampler.alwaysOff());

sampler.setDelegate(null);

SamplingDecision decision = doSample(sampler);
assertThat(decision).isEqualTo(SamplingDecision.RECORD_AND_SAMPLE);
}

@Test
void shouldSampleDelegatesToCurrentSamplerAfterUpdate() {
DelegatingSampler sampler = new DelegatingSampler(Sampler.alwaysOff());

assertThat(doSample(sampler)).isEqualTo(SamplingDecision.DROP);

sampler.setDelegate(Sampler.alwaysOn());

assertThat(doSample(sampler)).isEqualTo(SamplingDecision.RECORD_AND_SAMPLE);
}

private static SamplingDecision doSample(DelegatingSampler sampler) {
SamplingResult result =
sampler.shouldSample(
Context.root(),
"00000000000000000000000000000001",
"test-span",
SpanKind.INTERNAL,
Attributes.empty(),
Collections.emptyList());
return result.getDecision();
}
}
Loading