Skip to content

Commit debaab3

Browse files
committed
consistent-sampling: rebase on upstream ComposableSampler API
Rewrites the consistent-sampling module on top of the composable-samplers API in opentelemetry-sdk-extension-incubator. The legacy OTEP 4673 probability samplers are removed, the consistent56 package is promoted into io.opentelemetry.contrib.sampler.consistent, and classes that duplicated upstream functionality are dropped. The remaining ConsistentRateLimitingSampler, ConsistentVariableThresholdSampler, and ConsistentAnyOf now implement upstream ComposableSampler. ConsistentReservoirSamplingSpanProcessor and the parentbased_consistent_probability autoconfigure provider are retained; the provider now delegates to CompositeSampler.wrap(ComposableSampler.parentThreshold(ComposableSampler.probability(p))).
1 parent 66d414e commit debaab3

58 files changed

Lines changed: 1053 additions & 5568 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,40 @@
22

33
## Unreleased
44

5+
### Consistent sampling
6+
7+
- **Breaking change**: the legacy OTEP 4673 probability samplers in the
8+
`io.opentelemetry.contrib.sampler.consistent` package
9+
(`ConsistentProbabilityBasedSampler`, `ConsistentComposedAnd/OrSampler`,
10+
and the legacy `ConsistentAlwaysOn/OffSampler` / `ConsistentParentBasedSampler`
11+
factories on `ConsistentSampler`) have been removed.
12+
- `ConsistentReservoirSamplingSpanProcessor` and the
13+
`parentbased_consistent_probability` autoconfigure sampler name are retained.
14+
The autoconfigure provider now builds its sampler on top of the upstream
15+
`ComposableSampler` / `CompositeSampler` API; the resulting behavior is
16+
equivalent to `CompositeSampler.wrap(ComposableSampler.parentThreshold(ComposableSampler.probability(p)))`.
17+
- **Breaking change**: the `consistent56` package has been rewritten on top of
18+
the upstream `opentelemetry-sdk-extension-incubator` composable-samplers API
19+
and promoted to the `io.opentelemetry.contrib.sampler.consistent` package
20+
name. Classes that duplicated upstream functionality (`Composable`,
21+
`SamplingIntent`, `ConsistentAlwaysOn/OffSampler`,
22+
`ConsistentFixedThresholdSampler`, `ConsistentParentBasedSampler`,
23+
`ConsistentRuleBasedSampler`, `RandomValueGenerator(s)`,
24+
`Predicate`/`PredicatedSampler`) have been removed. The remaining
25+
`ConsistentRateLimitingSampler`, `ConsistentVariableThresholdSampler`, and
26+
`ConsistentAnyOf` now implement the upstream
27+
`io.opentelemetry.sdk.extension.incubator.trace.samplers.ComposableSampler`
28+
interface. Use `CompositeSampler.wrap(composable)` to turn a composable
29+
sampler into a `Sampler`, and use `ComposableSampler`'s static factories
30+
(`alwaysOn`, `alwaysOff`, `probability`, `parentThreshold`, …) directly
31+
instead of the removed `ConsistentSampler.alwaysOn/alwaysOff/probabilityBased/parentBased/asSampler`.
32+
Migration mapping:
33+
- `ConsistentSampler.alwaysOn()``ComposableSampler.alwaysOn()`
34+
- `ConsistentSampler.alwaysOff()``ComposableSampler.alwaysOff()`
35+
- `ConsistentSampler.probabilityBased(p)``ComposableSampler.probability(p)`
36+
- `ConsistentSampler.parentBased(root)``ComposableSampler.parentThreshold(root)`
37+
- `ConsistentSampler.asSampler(c)``CompositeSampler.wrap(c)`
38+
539
## Version 1.55.0 (2026-03-31)
640

741
### Disk buffering
@@ -220,7 +254,6 @@ an issue if this causes problems.
220254
- Add declarative configuration support
221255
([#2262](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/2262))
222256

223-
224257
## Version 1.49.0 (2025-08-25)
225258

226259
### Consistent sampling
@@ -870,7 +903,7 @@ which can be sent later on demand.
870903

871904
- JFR connection is a library to allow configuration and control of JFR
872905
without depending on jdk.jfr.
873-
This is a contribution of https://github.com/microsoft/jfr-streaming.
906+
This is a contribution of <https://github.com/microsoft/jfr-streaming>.
874907

875908
## Version 1.24.0 (2023-04-03)
876909

@@ -1052,8 +1085,8 @@ All components updated to target OpenTelemetry SDK 1.13.0.
10521085
### Consistent sampling - New 🌟
10531086

10541087
This component adds various Sampler implementations for consistent sampling as defined by
1055-
https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/tracestate-probability-sampling.md
1056-
and https://github.com/open-telemetry/opentelemetry-specification/pull/2047.
1088+
<https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/tracestate-probability-sampling.md>
1089+
and <https://github.com/open-telemetry/opentelemetry-specification/pull/2047>.
10571090

10581091
## Version 1.12.0 (2022-03-14)
10591092

consistent-sampling/README.md

Lines changed: 42 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,51 @@
11
# Consistent sampling
22

3-
There are two major components included here.
4-
5-
## Original proposal implementation
6-
7-
The original specification for consistent probability sampling is defined by
8-
<https://github.com/open-telemetry/opentelemetry-specification/blob/main/oteps/trace/4673-experimental-probability-sampling.md>
9-
and <https://github.com/open-telemetry/opentelemetry-specification/pull/2047>.
10-
It supports sampling probabilities that are power of 2 (1, 1/2, 1/4, ...), and uses 8-bit `r-value` and 8-bit `p-value` in tracestate.
11-
12-
The implementation of this proposal is contained by the package `io/opentelemetry/contrib/sampler/consistent` in this repository and provides various Sampler implementations.
13-
14-
* **ConsistentSampler**:
15-
abstract base class of all consistent sampler implementations below
16-
* **ConsistentAlwaysOffSampler**:
17-
see <https://github.com/open-telemetry/opentelemetry-specification/blob/main/oteps/trace/4673-experimental-probability-sampling.md#always-off-sampler>
18-
* **ConsistentAlwaysOnSampler**:
19-
see <https://github.com/open-telemetry/opentelemetry-specification/blob/main/oteps/trace/4673-experimental-probability-sampling.md#always-on-consistent-probability-sampler>
20-
* **ConsistentComposedAndSampler**:
21-
allows combining two consistent samplers and samples when both samplers would sample
22-
* **ConsistentComposedOrSampler**:
23-
allows combining two consistent sampler and samples when at least one of both samplers would sample,
24-
see <https://github.com/open-telemetry/opentelemetry-specification/blob/main/oteps/trace/4673-experimental-probability-sampling.md#requirement-combine-multiple-consistent-probability-samplers-using-the-minimum-p-value>
25-
* **ConsistentParentBasedSampler**:
26-
see <https://github.com/open-telemetry/opentelemetry-specification/blob/main/oteps/trace/4673-experimental-probability-sampling.md#parentconsistentprobabilitybased-sampler>
27-
* **ConsistentProbabilityBasedSampler**:
28-
see <https://github.com/open-telemetry/opentelemetry-specification/blob/main/oteps/trace/4673-experimental-probability-sampling.md#consistentprobabilitybased-sampler>
29-
* **ConsistentRateLimitingSampler**:
30-
a rate limiting sampler based on exponential smoothing that dynamically adjusts the sampling
31-
probability based on the estimated rate of spans occurring to satisfy a given rate of sampled spans
32-
33-
## Current proposal implementation
34-
35-
The current version of the specification for consistent probability sampling is described by
3+
This module implements the current specification for consistent probability
4+
sampling described by
365
<https://github.com/open-telemetry/oteps/blob/main/text/trace/0235-sampling-threshold-in-trace-state.md>.
37-
It uses **56** bits for representing _rejection threshold_, which corresponds to a much wider range of sampling probabilities than the original proposal.
38-
39-
The implementation of the current proposal is contained by the package `io/opentelemetry/contrib/sampler/consistent56` in this repository and provides implementation for a number of different Samplers.
40-
41-
* **ConsistentSampler**
42-
abstract base class for all consistent sampler implementations
43-
* **ComposableSampler**:
44-
interface used to build hierarchies of Samplers, see [Composite Samplers](https://github.com/open-telemetry/oteps/pull/250)
45-
* **ConsistentAlwaysOffSampler**:
46-
* **ConsistentAlwaysOnSampler**:
47-
* **ConsistentAnyOfSampler**:
48-
allows combining several consistent samplers; it samples when at least one of them would sample,
49-
* **ConsistentParentBasedSampler**:
50-
* **ConsistentFixedThresholdSampler**:
51-
consistent probability sampler that uses a predefined sampling probability
6+
It uses **56** bits for representing the _rejection threshold_, which
7+
corresponds to a much wider range of sampling probabilities than the original
8+
OTEP&nbsp;4673 proposal.
9+
10+
The reference implementation of composable samplers now lives upstream in
11+
[`opentelemetry-java`](https://github.com/open-telemetry/opentelemetry-java)
12+
under the `opentelemetry-sdk-extension-incubator` module, in the package
13+
`io.opentelemetry.sdk.extension.incubator.trace.samplers`. That package provides
14+
`ComposableSampler` (including the static factories `alwaysOn()`, `alwaysOff()`,
15+
`probability(ratio)`, `parentThreshold(rootSampler)`, `ruleBasedBuilder()`,
16+
`annotating(sampler, attributes)`) and `CompositeSampler.wrap(ComposableSampler)`
17+
for turning a composable sampler into a `Sampler`.
18+
19+
The package `io.opentelemetry.contrib.sampler.consistent` in this repository
20+
adds the following samplers on top of the upstream incubator:
21+
5222
* **ConsistentRateLimitingSampler**:
5323
a rate limiting sampler based on exponential smoothing that dynamically adjusts the sampling
5424
probability based on the estimated rate of spans occurring to satisfy a given rate of sampled spans
55-
* **ConsistentRuleBasedSampler**
56-
a sampler that performs stratified sampling by evaluating qualifying conditions and propagating the sampling decision from one of its delegate samplers
25+
* **ConsistentVariableThresholdSampler**:
26+
a consistent probability sampler whose sampling probability can be updated at runtime
27+
* **ConsistentAnyOf**:
28+
allows combining several composable samplers; it samples when at least one of them would sample
29+
* **ConsistentReservoirSamplingSpanProcessor**:
30+
a `SpanProcessor` that buffers ended spans and, if more spans arrive in a
31+
period than the configured reservoir size, consistently down-samples the
32+
buffer before export
33+
34+
The `ConsistentSampler` class is a convenience factory for the contrib-only
35+
samplers above. For the common samplers use `ComposableSampler`'s static
36+
factories directly.
37+
38+
## Autoconfigure
39+
40+
This module registers the `parentbased_consistent_probability` sampler name for
41+
the OpenTelemetry autoconfigure SDK, equivalent to
42+
`CompositeSampler.wrap(ComposableSampler.parentThreshold(ComposableSampler.probability(arg)))`.
43+
Use it via:
44+
45+
```
46+
OTEL_TRACES_SAMPLER=parentbased_consistent_probability
47+
OTEL_TRACES_SAMPLER_ARG=0.1
48+
```
5749

5850
## Component owners
5951

consistent-sampling/build.gradle.kts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ otelJava.moduleName.set("io.opentelemetry.contrib.sampler")
99
dependencies {
1010
api("io.opentelemetry:opentelemetry-sdk-trace")
1111
api("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi")
12-
testImplementation("org.hipparchus:hipparchus-core:4.0.3")
13-
testImplementation("org.hipparchus:hipparchus-stat:4.0.3")
12+
api("io.opentelemetry:opentelemetry-sdk-extension-incubator")
13+
14+
testImplementation("io.opentelemetry:opentelemetry-sdk-testing")
1415
}
1516

1617
tasks {

consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent/ConsistentAlwaysOffSampler.java

Lines changed: 0 additions & 26 deletions
This file was deleted.

consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent/ConsistentAlwaysOnSampler.java

Lines changed: 0 additions & 26 deletions
This file was deleted.

consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentAnyOf.java renamed to consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent/ConsistentAnyOf.java

Lines changed: 35 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,21 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
package io.opentelemetry.contrib.sampler.consistent56;
6+
package io.opentelemetry.contrib.sampler.consistent;
77

8-
import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.getInvalidThreshold;
9-
import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.isValidThreshold;
8+
import static io.opentelemetry.contrib.sampler.consistent.ConsistentSamplingUtil.INVALID_THRESHOLD;
9+
import static io.opentelemetry.contrib.sampler.consistent.ConsistentSamplingUtil.isValidThreshold;
1010

1111
import io.opentelemetry.api.common.Attributes;
1212
import io.opentelemetry.api.common.AttributesBuilder;
1313
import io.opentelemetry.api.trace.SpanKind;
1414
import io.opentelemetry.api.trace.TraceState;
1515
import io.opentelemetry.context.Context;
16+
import io.opentelemetry.sdk.extension.incubator.trace.samplers.ComposableSampler;
17+
import io.opentelemetry.sdk.extension.incubator.trace.samplers.SamplingIntent;
1618
import io.opentelemetry.sdk.trace.data.LinkData;
1719
import java.util.List;
20+
import java.util.function.Function;
1821
import java.util.stream.Collectors;
1922
import java.util.stream.Stream;
2023
import javax.annotation.Nullable;
@@ -25,18 +28,12 @@
2528
* uses the minimum threshold value received.
2629
*/
2730
@Immutable
28-
final class ConsistentAnyOf extends ConsistentSampler {
29-
30-
private final Composable[] delegates;
31+
final class ConsistentAnyOf implements ComposableSampler {
3132

33+
private final ComposableSampler[] delegates;
3234
private final String description;
3335

34-
/**
35-
* Constructs a new consistent AnyOf sampler using the provided delegate samplers.
36-
*
37-
* @param delegates the delegate samplers
38-
*/
39-
ConsistentAnyOf(@Nullable Composable... delegates) {
36+
ConsistentAnyOf(@Nullable ComposableSampler... delegates) {
4037
if (delegates == null || delegates.length == 0) {
4138
throw new IllegalArgumentException(
4239
"At least one delegate must be specified for ConsistentAnyOf");
@@ -46,13 +43,14 @@ final class ConsistentAnyOf extends ConsistentSampler {
4643

4744
this.description =
4845
Stream.of(delegates)
49-
.map(Object::toString)
46+
.map(ComposableSampler::getDescription)
5047
.collect(Collectors.joining(",", "ConsistentAnyOf{", "}"));
5148
}
5249

5350
@Override
5451
public SamplingIntent getSamplingIntent(
5552
Context parentContext,
53+
String traceId,
5654
String name,
5755
SpanKind spanKind,
5856
Attributes attributes,
@@ -62,67 +60,53 @@ public SamplingIntent getSamplingIntent(
6260

6361
// If any of the delegates provides a valid threshold, the resulting threshold is the minimum
6462
// value T from the set of those valid threshold values, otherwise it is invalid threshold.
65-
long minimumThreshold = getInvalidThreshold();
63+
long minimumThreshold = INVALID_THRESHOLD;
6664

6765
// If any of the delegates returning the threshold value equal to T returns true upon calling
68-
// its IsAdjustedCountReliable() method, the resulting isAdjustedCountReliable is true,
66+
// its isThresholdReliable() method, the resulting thresholdReliable is true,
6967
// otherwise it is false.
70-
boolean isAdjustedCountCorrect = false;
68+
boolean thresholdReliable = false;
7169

7270
int k = 0;
73-
for (Composable delegate : delegates) {
71+
for (ComposableSampler delegate : delegates) {
7472
SamplingIntent delegateIntent =
75-
delegate.getSamplingIntent(parentContext, name, spanKind, attributes, parentLinks);
73+
delegate.getSamplingIntent(
74+
parentContext, traceId, name, spanKind, attributes, parentLinks);
7675
long delegateThreshold = delegateIntent.getThreshold();
7776
if (isValidThreshold(delegateThreshold)) {
7877
if (isValidThreshold(minimumThreshold)) {
7978
if (delegateThreshold == minimumThreshold) {
80-
if (delegateIntent.isAdjustedCountReliable()) {
81-
isAdjustedCountCorrect = true;
79+
if (delegateIntent.isThresholdReliable()) {
80+
thresholdReliable = true;
8281
}
8382
} else if (delegateThreshold < minimumThreshold) {
8483
minimumThreshold = delegateThreshold;
85-
isAdjustedCountCorrect = delegateIntent.isAdjustedCountReliable();
84+
thresholdReliable = delegateIntent.isThresholdReliable();
8685
}
8786
} else {
8887
minimumThreshold = delegateThreshold;
89-
isAdjustedCountCorrect = delegateIntent.isAdjustedCountReliable();
88+
thresholdReliable = delegateIntent.isThresholdReliable();
9089
}
9190
}
9291
intents[k++] = delegateIntent;
9392
}
9493

95-
long resultingThreshold = minimumThreshold;
96-
boolean isResultingAdjustedCountCorrect = isAdjustedCountCorrect;
97-
98-
return new SamplingIntent() {
99-
@Override
100-
public long getThreshold() {
101-
return resultingThreshold;
102-
}
103-
104-
@Override
105-
public boolean isAdjustedCountReliable() {
106-
return isResultingAdjustedCountCorrect;
107-
}
94+
AttributesBuilder builder = Attributes.builder();
95+
for (SamplingIntent intent : intents) {
96+
builder = builder.putAll(intent.getAttributes());
97+
}
98+
Attributes mergedAttributes = builder.build();
10899

109-
@Override
110-
public Attributes getAttributes() {
111-
AttributesBuilder builder = Attributes.builder();
112-
for (SamplingIntent intent : intents) {
113-
builder = builder.putAll(intent.getAttributes());
114-
}
115-
return builder.build();
116-
}
100+
Function<TraceState, TraceState> composed =
101+
s -> {
102+
TraceState state = s;
103+
for (SamplingIntent intent : intents) {
104+
state = intent.getTraceStateUpdater().apply(state);
105+
}
106+
return state;
107+
};
117108

118-
@Override
119-
public TraceState updateTraceState(TraceState previousState) {
120-
for (SamplingIntent intent : intents) {
121-
previousState = intent.updateTraceState(previousState);
122-
}
123-
return previousState;
124-
}
125-
};
109+
return SamplingIntent.create(minimumThreshold, thresholdReliable, mergedAttributes, composed);
126110
}
127111

128112
@Override

0 commit comments

Comments
 (0)