Skip to content

Commit 752f77d

Browse files
authored
[dynamic control] Move away from JSON requirement (#2652)
1 parent 0834da3 commit 752f77d

File tree

7 files changed

+67
-114
lines changed

7 files changed

+67
-114
lines changed

dynamic-control/src/main/java/io/opentelemetry/contrib/dynamic/policy/TelemetryPolicy.java

Lines changed: 12 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -5,43 +5,38 @@
55

66
package io.opentelemetry.contrib.dynamic.policy;
77

8-
import com.fasterxml.jackson.databind.JsonNode;
98
import java.util.Objects;
10-
import javax.annotation.Nullable;
119

1210
/**
13-
* Represents a single Telemetry Policy, comprising a type and a specification.
11+
* Represents a single Telemetry Policy identified by type.
1412
*
1513
* <p>A {@code TelemetryPolicy} instance encapsulates a specific rule or configuration intent
16-
* identified by its {@link #getType() type}. The behavior of the policy is defined by its {@link
17-
* #getSpec() specification}, which is a JSON object.
14+
* identified by its {@link #getType() type}.
1815
*
19-
* <p>Policies are typically immutable data carriers. The {@code spec} can be {@code null}, which
20-
* conventionally signifies the removal or absence of a policy for the given type in certain
21-
* contexts (e.g., when calculating diffs or updates).
16+
* <p>Policies are immutable data carriers.
2217
*
23-
* <p>As an example take the JSON structure `{"trace-sampling": {"probability" : 0.5}}` This is of
24-
* type "trace-sampling", with spec `{"probability" : 0.5}`, indicating the intent that the trace
25-
* sampling-probability be set to 50%
18+
* <p>As an example, policy type {@code trace-sampling} indicates that trace sampling behavior
19+
* should be configured.
20+
*
21+
* <p>Direct instantiation of this base class is intentionally supported for type-only policy
22+
* signals (for example, to indicate policy removal/reset without policy-specific values).
2623
*
2724
* @see io.opentelemetry.contrib.dynamic.policy
2825
*/
2926
public class TelemetryPolicy {
3027
private final String type; // e.g. "trace-sampling"
31-
// JSON content after schema validation
32-
// null means removed, which is relevant for merging policies
33-
@Nullable private final JsonNode spec;
3428

3529
/**
3630
* Constructs a new TelemetryPolicy.
3731
*
32+
* <p>This constructor is used by type-specific subclasses and also directly for type-only policy
33+
* signals such as policy removal/reset.
34+
*
3835
* @param type the type of the policy (e.g., "trace-sampling"), must not be null.
39-
* @param spec the JSON specification of the policy, or {@code null} to indicate removal.
4036
*/
41-
public TelemetryPolicy(String type, @Nullable JsonNode spec) {
37+
public TelemetryPolicy(String type) {
4238
Objects.requireNonNull(type, "type cannot be null");
4339
this.type = type;
44-
this.spec = spec;
4540
}
4641

4742
/**
@@ -55,34 +50,4 @@ public TelemetryPolicy(String type, @Nullable JsonNode spec) {
5550
public String getType() {
5651
return type;
5752
}
58-
59-
/**
60-
* Returns the specification of this policy.
61-
*
62-
* <p>The specification is a JSON structure defining the parameters of the policy. If {@code
63-
* null}, it may indicate that the policy is being removed or is empty.
64-
*
65-
* @return the policy specification, or {@code null}.
66-
*/
67-
@Nullable
68-
public JsonNode getSpec() {
69-
return spec;
70-
}
71-
72-
@Override
73-
public boolean equals(Object o) {
74-
if (this == o) {
75-
return true;
76-
}
77-
if (!(o instanceof TelemetryPolicy)) {
78-
return false;
79-
}
80-
TelemetryPolicy that = (TelemetryPolicy) o;
81-
return Objects.equals(type, that.type) && Objects.equals(spec, that.spec);
82-
}
83-
84-
@Override
85-
public int hashCode() {
86-
return Objects.hash(type, spec);
87-
}
8853
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.contrib.dynamic.policy;
7+
8+
public final class TraceSamplingRatePolicy extends TelemetryPolicy {
9+
public static final String TYPE = "trace-sampling";
10+
11+
private final double probability;
12+
13+
public TraceSamplingRatePolicy(double probability) {
14+
super(TYPE);
15+
if (Double.isNaN(probability) || probability < 0.0 || probability > 1.0) {
16+
throw new IllegalArgumentException("probability must be within [0.0, 1.0]");
17+
}
18+
this.probability = probability;
19+
}
20+
21+
public double getProbability() {
22+
return probability;
23+
}
24+
}

dynamic-control/src/main/java/io/opentelemetry/contrib/dynamic/policy/TraceSamplingRatePolicyImplementer.java

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
package io.opentelemetry.contrib.dynamic.policy;
77

8-
import com.fasterxml.jackson.databind.JsonNode;
98
import io.opentelemetry.contrib.dynamic.sampler.DelegatingSampler;
109
import io.opentelemetry.sdk.trace.samplers.Sampler;
1110
import java.util.Collections;
@@ -16,25 +15,21 @@
1615
* Implements the {@code trace-sampling} policy by updating a {@link DelegatingSampler}.
1716
*
1817
* <p>This implementer listens for validated {@link TelemetryPolicy} updates of type {@code
19-
* "trace-sampling"} and applies the {@code probability} field to the delegate sampler using {@link
20-
* Sampler#traceIdRatioBased(double)} wrapped by {@link Sampler#parentBased(Sampler)}.
18+
* "trace-sampling"} and applies {@link TraceSamplingRatePolicy#getProbability()} to the delegate
19+
* sampler using {@link Sampler#traceIdRatioBased(double)} wrapped by {@link
20+
* Sampler#parentBased(Sampler)}.
2121
*
22-
* <p>If the policy spec is {@code null} (policy removal), the delegate falls back to {@link
23-
* Sampler#alwaysOn()}.
22+
* <p>If a type-only {@link TelemetryPolicy} of type {@code "trace-sampling"} is received, it is
23+
* treated as policy removal and the delegate falls back to {@link Sampler#alwaysOn()}.
2424
*
2525
* <p>Validation is performed by {@link TraceSamplingValidator}; this implementer only consumes
2626
* policies produced by that validator.
2727
*
28-
* <p>Policies with a non-null spec that omit {@code probability} are ignored, which should not
29-
* occur if validation is functioning correctly.
30-
*
3128
* <p>This class is thread-safe. Calls to {@link #onPoliciesChanged(List)} can occur concurrently
3229
* with sampling operations on the associated {@link DelegatingSampler}.
3330
*/
3431
public final class TraceSamplingRatePolicyImplementer implements PolicyImplementer {
3532

36-
private static final String TRACE_SAMPLING_TYPE = "trace-sampling";
37-
private static final String PROBABILITY_FIELD = "probability";
3833
private static final List<PolicyValidator> VALIDATORS =
3934
Collections.<PolicyValidator>singletonList(new TraceSamplingValidator());
4035

@@ -58,19 +53,17 @@ public List<PolicyValidator> getValidators() {
5853
@Override
5954
public void onPoliciesChanged(List<TelemetryPolicy> policies) {
6055
for (TelemetryPolicy policy : policies) {
61-
if (!TRACE_SAMPLING_TYPE.equals(policy.getType())) {
56+
if (!TraceSamplingRatePolicy.TYPE.equals(policy.getType())) {
6257
continue;
6358
}
64-
JsonNode spec = policy.getSpec();
65-
if (spec == null) {
59+
if (!(policy instanceof TraceSamplingRatePolicy)) {
60+
// Type-only policy represents removing trace-sampling config.
6661
delegatingSampler.setDelegate(Sampler.alwaysOn());
6762
continue;
6863
}
69-
if (spec.has(PROBABILITY_FIELD)) {
70-
double ratio = spec.get(PROBABILITY_FIELD).asDouble(1.0);
71-
Sampler sampler = Sampler.parentBased(Sampler.traceIdRatioBased(ratio));
72-
delegatingSampler.setDelegate(sampler);
73-
}
64+
double ratio = ((TraceSamplingRatePolicy) policy).getProbability();
65+
Sampler sampler = Sampler.parentBased(Sampler.traceIdRatioBased(ratio));
66+
delegatingSampler.setDelegate(sampler);
7467
}
7568
}
7669
}

dynamic-control/src/main/java/io/opentelemetry/contrib/dynamic/policy/TraceSamplingValidator.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public final class TraceSamplingValidator implements PolicyValidator {
2323

2424
@Override
2525
public String getPolicyType() {
26-
return "trace-sampling";
26+
return TraceSamplingRatePolicy.TYPE;
2727
}
2828

2929
@Override
@@ -43,7 +43,7 @@ public TelemetryPolicy validate(String json) {
4343
if (probNode.isNumber()) {
4444
double d = probNode.asDouble();
4545
if (d >= 0.0 && d <= 1.0) {
46-
return new TelemetryPolicy(getPolicyType(), spec);
46+
return new TraceSamplingRatePolicy(d);
4747
}
4848
}
4949
}
@@ -62,8 +62,7 @@ public TelemetryPolicy validateAlias(String key, String value) {
6262
try {
6363
double d = Double.parseDouble(value);
6464
if (d >= 0.0 && d <= 1.0) {
65-
JsonNode spec = MAPPER.createObjectNode().put("probability", d);
66-
return new TelemetryPolicy(getPolicyType(), spec);
65+
return new TraceSamplingRatePolicy(d);
6766
}
6867
} catch (NumberFormatException e) {
6968
// invalid

dynamic-control/src/test/java/io/opentelemetry/contrib/dynamic/policy/LinePerPolicyFileProviderTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ public TelemetryPolicy validate(String json) {
111111
if (!acceptJson) {
112112
return null;
113113
}
114-
return new TelemetryPolicy(TRACE_SAMPLING_TYPE, null);
114+
return new TelemetryPolicy(TRACE_SAMPLING_TYPE);
115115
}
116116

117117
@Override
@@ -124,7 +124,7 @@ public TelemetryPolicy validateAlias(String key, String value) {
124124
if (!acceptAlias) {
125125
return null;
126126
}
127-
return new TelemetryPolicy(TRACE_SAMPLING_TYPE, null);
127+
return new TelemetryPolicy(TRACE_SAMPLING_TYPE);
128128
}
129129

130130
@Override

dynamic-control/src/test/java/io/opentelemetry/contrib/dynamic/policy/TraceSamplingRatePolicyImplementerTest.java

Lines changed: 5 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88
import static java.util.Collections.singletonList;
99
import static org.assertj.core.api.Assertions.assertThat;
1010

11-
import com.fasterxml.jackson.databind.JsonNode;
12-
import com.fasterxml.jackson.databind.ObjectMapper;
1311
import io.opentelemetry.api.common.Attributes;
1412
import io.opentelemetry.api.trace.SpanKind;
1513
import io.opentelemetry.context.Context;
@@ -24,15 +22,13 @@
2422

2523
class TraceSamplingRatePolicyImplementerTest {
2624

27-
private static final ObjectMapper MAPPER = new ObjectMapper();
28-
2925
@Test
30-
void nullSpecFallsBackToAlwaysOn() {
26+
void typeOnlyTraceSamplingPolicyFallsBackToAlwaysOn() {
3127
DelegatingSampler delegatingSampler = new DelegatingSampler(Sampler.alwaysOff());
3228
TraceSamplingRatePolicyImplementer implementer =
3329
new TraceSamplingRatePolicyImplementer(delegatingSampler);
3430

35-
implementer.onPoliciesChanged(singletonList(new TelemetryPolicy("trace-sampling", null)));
31+
implementer.onPoliciesChanged(singletonList(new TelemetryPolicy("trace-sampling")));
3632

3733
assertThat(decisionFor(delegatingSampler)).isEqualTo(SamplingDecision.RECORD_AND_SAMPLE);
3834
}
@@ -43,8 +39,7 @@ void appliesProbabilityToDelegate() {
4339
TraceSamplingRatePolicyImplementer implementer =
4440
new TraceSamplingRatePolicyImplementer(delegatingSampler);
4541

46-
implementer.onPoliciesChanged(
47-
singletonList(new TelemetryPolicy("trace-sampling", spec("probability", 1.0))));
42+
implementer.onPoliciesChanged(singletonList(new TraceSamplingRatePolicy(1.0)));
4843

4944
assertThat(decisionFor(delegatingSampler)).isEqualTo(SamplingDecision.RECORD_AND_SAMPLE);
5045
}
@@ -55,20 +50,7 @@ void ignoresUnrelatedPolicyTypes() {
5550
TraceSamplingRatePolicyImplementer implementer =
5651
new TraceSamplingRatePolicyImplementer(delegatingSampler);
5752

58-
implementer.onPoliciesChanged(
59-
singletonList(new TelemetryPolicy("other-policy", spec("value", 1.0))));
60-
61-
assertThat(decisionFor(delegatingSampler)).isEqualTo(SamplingDecision.DROP);
62-
}
63-
64-
@Test
65-
void ignoresTraceSamplingPolicyWithoutProbability() {
66-
DelegatingSampler delegatingSampler = new DelegatingSampler(Sampler.alwaysOff());
67-
TraceSamplingRatePolicyImplementer implementer =
68-
new TraceSamplingRatePolicyImplementer(delegatingSampler);
69-
70-
implementer.onPoliciesChanged(
71-
singletonList(new TelemetryPolicy("trace-sampling", spec("other-field", 1.0))));
53+
implementer.onPoliciesChanged(singletonList(new TelemetryPolicy("other-policy")));
7254

7355
assertThat(decisionFor(delegatingSampler)).isEqualTo(SamplingDecision.DROP);
7456
}
@@ -80,9 +62,7 @@ void lastTraceSamplingPolicyWins() {
8062
new TraceSamplingRatePolicyImplementer(delegatingSampler);
8163

8264
List<TelemetryPolicy> policies =
83-
Arrays.asList(
84-
new TelemetryPolicy("trace-sampling", spec("probability", 0.0)),
85-
new TelemetryPolicy("trace-sampling", spec("probability", 1.0)));
65+
Arrays.asList(new TraceSamplingRatePolicy(0.0), new TraceSamplingRatePolicy(1.0));
8666

8767
implementer.onPoliciesChanged(policies);
8868

@@ -100,8 +80,4 @@ private static SamplingDecision decisionFor(DelegatingSampler sampler) {
10080
Collections.emptyList());
10181
return result.getDecision();
10282
}
103-
104-
private static JsonNode spec(String field, double value) {
105-
return MAPPER.createObjectNode().put(field, value);
106-
}
10783
}

dynamic-control/src/test/java/io/opentelemetry/contrib/dynamic/policy/TraceSamplingValidatorTest.java

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
class TraceSamplingValidatorTest {
1616

1717
private static final String TRACE_SAMPLING_POLICY_TYPE = "trace-sampling";
18-
private static final String PROBABILITY_FIELD = "probability";
1918
private static final String TRACE_SAMPLING_ALIAS = "trace-sampling.probability";
2019

2120
private final TraceSamplingValidator validator = new TraceSamplingValidator();
@@ -36,7 +35,8 @@ void testValidate_ValidJson() {
3635
TelemetryPolicy policy = validator.validate(json);
3736
assertThat(policy).isNotNull();
3837
assertThat(policy.getType()).isEqualTo(TRACE_SAMPLING_POLICY_TYPE);
39-
assertThat(policy.getSpec().get(PROBABILITY_FIELD).asDouble()).isCloseTo(0.5, within(1e-9));
38+
assertThat(policy).isInstanceOf(TraceSamplingRatePolicy.class);
39+
assertThat(((TraceSamplingRatePolicy) policy).getProbability()).isCloseTo(0.5, within(1e-9));
4040
}
4141

4242
@ParameterizedTest
@@ -46,7 +46,8 @@ void testValidate_ValidJson_BoundaryValues(double probability) {
4646
TelemetryPolicy policy = validator.validate(json);
4747
assertThat(policy).isNotNull();
4848
assertThat(policy.getType()).isEqualTo(TRACE_SAMPLING_POLICY_TYPE);
49-
assertThat(policy.getSpec().get(PROBABILITY_FIELD).asDouble())
49+
assertThat(policy).isInstanceOf(TraceSamplingRatePolicy.class);
50+
assertThat(((TraceSamplingRatePolicy) policy).getProbability())
5051
.isCloseTo(probability, within(1e-9));
5152
}
5253

@@ -70,8 +71,7 @@ void testValidate_InvalidJson_MissingProbability() {
7071

7172
@Test
7273
void testValidate_InvalidJson_ProbabilityNotNumber() {
73-
String json =
74-
"{\"" + TRACE_SAMPLING_POLICY_TYPE + "\": {\"" + PROBABILITY_FIELD + "\": \"high\"}}";
74+
String json = "{\"" + TRACE_SAMPLING_POLICY_TYPE + "\": {\"probability\": \"high\"}}";
7575
assertThat(validator.validate(json)).isNull();
7676
}
7777

@@ -87,7 +87,8 @@ void testValidateAlias_Valid() {
8787
TelemetryPolicy policy = validator.validateAlias(TRACE_SAMPLING_ALIAS, "0.5");
8888
assertThat(policy).isNotNull();
8989
assertThat(policy.getType()).isEqualTo(TRACE_SAMPLING_POLICY_TYPE);
90-
assertThat(policy.getSpec().get(PROBABILITY_FIELD).asDouble()).isCloseTo(0.5, within(1e-9));
90+
assertThat(policy).isInstanceOf(TraceSamplingRatePolicy.class);
91+
assertThat(((TraceSamplingRatePolicy) policy).getProbability()).isCloseTo(0.5, within(1e-9));
9192
}
9293

9394
@ParameterizedTest
@@ -96,7 +97,8 @@ void testValidateAlias_Valid_BoundaryValues(String probability) {
9697
TelemetryPolicy policy = validator.validateAlias(TRACE_SAMPLING_ALIAS, probability);
9798
assertThat(policy).isNotNull();
9899
assertThat(policy.getType()).isEqualTo(TRACE_SAMPLING_POLICY_TYPE);
99-
assertThat(policy.getSpec().get(PROBABILITY_FIELD).asDouble())
100+
assertThat(policy).isInstanceOf(TraceSamplingRatePolicy.class);
101+
assertThat(((TraceSamplingRatePolicy) policy).getProbability())
100102
.isCloseTo(Double.parseDouble(probability), within(1e-9));
101103
}
102104

@@ -117,12 +119,6 @@ void testValidateAlias_InvalidValue_OutOfRange(String probability) {
117119
}
118120

119121
private static String jsonForProbability(double probability) {
120-
return "{\""
121-
+ TRACE_SAMPLING_POLICY_TYPE
122-
+ "\": {\""
123-
+ PROBABILITY_FIELD
124-
+ "\": "
125-
+ probability
126-
+ "}}";
122+
return "{\"" + TRACE_SAMPLING_POLICY_TYPE + "\": {\"probability\": " + probability + "}}";
127123
}
128124
}

0 commit comments

Comments
 (0)