Skip to content

Commit 5aae224

Browse files
jackshiraziCopilot
andauthored
[dynamic control] Add delegating sampler implementation (open-telemetry#2607)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 8409096 commit 5aae224

2 files changed

Lines changed: 147 additions & 0 deletions

File tree

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.contrib.dynamic.sampler;
7+
8+
import io.opentelemetry.api.common.Attributes;
9+
import io.opentelemetry.api.trace.SpanKind;
10+
import io.opentelemetry.context.Context;
11+
import io.opentelemetry.sdk.trace.data.LinkData;
12+
import io.opentelemetry.sdk.trace.samplers.Sampler;
13+
import io.opentelemetry.sdk.trace.samplers.SamplingResult;
14+
import java.util.List;
15+
import javax.annotation.Nullable;
16+
17+
/**
18+
* A {@link Sampler} implementation that delegates sampling decisions to another {@link Sampler}
19+
* instance held in a volatile field. This allows the effective sampling strategy to be reconfigured
20+
* at runtime without rebuilding the {@code TracerSdkProvider} or recreating instrumented
21+
* components.
22+
*
23+
* <p>This class is thread-safe. All access to the current delegate sampler is performed through a
24+
* volatile reference, so sampling decisions and delegate updates may occur concurrently without
25+
* additional synchronization.
26+
*
27+
* <p>The delegate sampler can be updated dynamically via {@link #setDelegate(Sampler)}. Passing
28+
* {@code null} to {@code setDelegate} or the constructor will cause {@link Sampler#alwaysOn()} to
29+
* be used as the fallback delegate.
30+
*/
31+
public class DelegatingSampler implements Sampler {
32+
33+
private volatile Sampler delegate;
34+
35+
/**
36+
* Creates a new {@link DelegatingSampler} with the given initial delegate.
37+
*
38+
* <p>If {@code initialDelegate} is {@code null}, {@link Sampler#alwaysOn()} will be used as the
39+
* initial delegate.
40+
*
41+
* @param initialDelegate the initial {@link Sampler} to delegate to, or {@code null} to use
42+
* {@link Sampler#alwaysOn()} by default
43+
*/
44+
public DelegatingSampler(@Nullable Sampler initialDelegate) {
45+
this.delegate = initialDelegate != null ? initialDelegate : Sampler.alwaysOn();
46+
}
47+
48+
public DelegatingSampler() {
49+
this(Sampler.alwaysOn());
50+
}
51+
52+
/**
53+
* Updates the delegate {@link Sampler} used by this {@code DelegatingSampler} at runtime.
54+
*
55+
* <p>If {@code sampler} is {@code null}, this method will instead use {@link Sampler#alwaysOn()}
56+
* as the delegate.
57+
*
58+
* @param sampler the new delegate sampler to use, or {@code null} to fall back to {@link
59+
* Sampler#alwaysOn()}
60+
*/
61+
public void setDelegate(@Nullable Sampler sampler) {
62+
delegate = sampler != null ? sampler : Sampler.alwaysOn();
63+
}
64+
65+
@Override
66+
public SamplingResult shouldSample(
67+
Context ctx,
68+
String traceId,
69+
String name,
70+
SpanKind kind,
71+
Attributes attrs,
72+
List<LinkData> links) {
73+
return delegate.shouldSample(ctx, traceId, name, kind, attrs, links);
74+
}
75+
76+
@Override
77+
public String getDescription() {
78+
return "delegating/" + delegate.getDescription();
79+
}
80+
81+
@Override
82+
public String toString() {
83+
return getDescription();
84+
}
85+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.contrib.dynamic.sampler;
7+
8+
import static org.assertj.core.api.Assertions.assertThat;
9+
10+
import io.opentelemetry.api.common.Attributes;
11+
import io.opentelemetry.api.trace.SpanKind;
12+
import io.opentelemetry.context.Context;
13+
import io.opentelemetry.sdk.trace.samplers.Sampler;
14+
import io.opentelemetry.sdk.trace.samplers.SamplingDecision;
15+
import io.opentelemetry.sdk.trace.samplers.SamplingResult;
16+
import java.util.Collections;
17+
import org.junit.jupiter.api.Test;
18+
19+
class DelegatingSamplerTest {
20+
21+
@Test
22+
void defaultDelegateIsAlwaysOn() {
23+
DelegatingSampler sampler = new DelegatingSampler();
24+
25+
SamplingDecision decision = doSample(sampler);
26+
27+
assertThat(decision).isEqualTo(SamplingDecision.RECORD_AND_SAMPLE);
28+
}
29+
30+
@Test
31+
void setDelegateNullFallsBackToAlwaysOn() {
32+
DelegatingSampler sampler = new DelegatingSampler(Sampler.alwaysOff());
33+
34+
sampler.setDelegate(null);
35+
36+
SamplingDecision decision = doSample(sampler);
37+
assertThat(decision).isEqualTo(SamplingDecision.RECORD_AND_SAMPLE);
38+
}
39+
40+
@Test
41+
void shouldSampleDelegatesToCurrentSamplerAfterUpdate() {
42+
DelegatingSampler sampler = new DelegatingSampler(Sampler.alwaysOff());
43+
44+
assertThat(doSample(sampler)).isEqualTo(SamplingDecision.DROP);
45+
46+
sampler.setDelegate(Sampler.alwaysOn());
47+
48+
assertThat(doSample(sampler)).isEqualTo(SamplingDecision.RECORD_AND_SAMPLE);
49+
}
50+
51+
private static SamplingDecision doSample(DelegatingSampler sampler) {
52+
SamplingResult result =
53+
sampler.shouldSample(
54+
Context.root(),
55+
"00000000000000000000000000000001",
56+
"test-span",
57+
SpanKind.INTERNAL,
58+
Attributes.empty(),
59+
Collections.emptyList());
60+
return result.getDecision();
61+
}
62+
}

0 commit comments

Comments
 (0)