Skip to content

Commit 404f79c

Browse files
authored
Add span kind to sampling overrides (#1960)
* Option 1 * Revert "Option 1" This reverts commit cf5b048. * Add span kind to sampling overrides * Support key-only matches * Fix * Bump version * Fix sporadic test failure * Fix test * Add todo
1 parent da46934 commit 404f79c

File tree

7 files changed

+185
-41
lines changed

7 files changed

+185
-41
lines changed

agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/configuration/Configuration.java

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import java.util.Map;
3838
import java.util.regex.Pattern;
3939
import java.util.regex.PatternSyntaxException;
40+
import org.checkerframework.checker.nullness.qual.Nullable;
4041

4142
// an assumption is made throughout this file that user will not explicitly use `null` value in json
4243
// file
@@ -62,6 +63,26 @@ private static boolean isEmpty(String str) {
6263
return str == null || str.trim().isEmpty();
6364
}
6465

66+
// TODO (trask) investigate options for mapping lowercase values to otel enum directly
67+
public enum SpanKind {
68+
@JsonProperty("server")
69+
SERVER(io.opentelemetry.api.trace.SpanKind.SERVER),
70+
@JsonProperty("client")
71+
CLIENT(io.opentelemetry.api.trace.SpanKind.CLIENT),
72+
@JsonProperty("consumer")
73+
CONSUMER(io.opentelemetry.api.trace.SpanKind.CONSUMER),
74+
@JsonProperty("producer")
75+
PRODUCER(io.opentelemetry.api.trace.SpanKind.PRODUCER),
76+
@JsonProperty("internal")
77+
INTERNAL(io.opentelemetry.api.trace.SpanKind.INTERNAL);
78+
79+
public final io.opentelemetry.api.trace.SpanKind otelSpanKind;
80+
81+
SpanKind(io.opentelemetry.api.trace.SpanKind otelSpanKind) {
82+
this.otelSpanKind = otelSpanKind;
83+
}
84+
}
85+
6586
public enum MatchType {
6687
@JsonProperty("strict")
6788
STRICT,
@@ -373,6 +394,8 @@ private static String getDefaultPath() {
373394
}
374395

375396
public static class SamplingOverride {
397+
// TODO (trask) consider making this required when moving out of preview
398+
@Nullable public SpanKind spanKind;
376399
// not using include/exclude, because you can still get exclude with this by adding a second
377400
// (exclude) override above it
378401
// (since only the first matching override is used)
@@ -381,11 +404,11 @@ public static class SamplingOverride {
381404
public String id; // optional, used for debugging purposes only
382405

383406
public void validate() {
384-
if (attributes.isEmpty()) {
407+
if (spanKind == null && attributes.isEmpty()) {
385408
// TODO add doc and go link, similar to telemetry processors
386409
throw new FriendlyException(
387-
"A sampling override configuration has no attributes.",
388-
"Please provide one or more attributes for the sampling override configuration.");
410+
"A sampling override configuration is missing \"spanKind\" and has no attributes.",
411+
"Please provide at least one of \"spanKind\" or \"attributes\" for the sampling override configuration.");
389412
}
390413
if (percentage == null) {
391414
// TODO add doc and go link, similar to telemetry processors
@@ -407,8 +430,8 @@ public void validate() {
407430

408431
public static class SamplingOverrideAttribute {
409432
public String key;
410-
public String value;
411-
public MatchType matchType;
433+
@Nullable public String value;
434+
@Nullable public MatchType matchType;
412435

413436
private void validate() {
414437
if (isEmpty(key)) {
@@ -417,9 +440,9 @@ private void validate() {
417440
"A sampling override configuration has an attribute section that is missing a \"key\".",
418441
"Please provide a \"key\" under the attribute section of the sampling override configuration.");
419442
}
420-
if (matchType == null) {
443+
if (matchType == null && value != null) {
421444
throw new FriendlyException(
422-
"A sampling override configuration has an attribute section that is missing a \"matchType\".",
445+
"A sampling override configuration has an attribute section with a \"value\" that is missing a \"matchType\".",
423446
"Please provide a \"matchType\" under the attribute section of the sampling override configuration.");
424447
}
425448
if (matchType == MatchType.REGEXP) {

agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/sampling/AiSampler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ public SamplingResult shouldSample(
8787
Attributes attributes,
8888
List<LinkData> parentLinks) {
8989

90-
MatcherGroup override = samplingOverrides.getOverride(attributes);
90+
MatcherGroup override = samplingOverrides.getOverride(spanKind, attributes);
9191

9292
if (override != null) {
9393
return getSamplingResult(

agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/sampling/SamplingOverrides.java

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import com.microsoft.applicationinsights.agent.internal.telemetry.TelemetryUtil;
2929
import io.opentelemetry.api.common.AttributeKey;
3030
import io.opentelemetry.api.common.Attributes;
31+
import io.opentelemetry.api.trace.SpanKind;
3132
import io.opentelemetry.api.trace.TraceState;
3233
import io.opentelemetry.sdk.trace.samplers.SamplingDecision;
3334
import io.opentelemetry.sdk.trace.samplers.SamplingResult;
@@ -37,7 +38,7 @@
3738
import java.util.ArrayList;
3839
import java.util.List;
3940
import java.util.regex.Pattern;
40-
import org.checkerframework.checker.nullness.qual.Nullable;
41+
import javax.annotation.Nullable;
4142

4243
// TODO find a better name for this class (and MatcherGroup too)
4344
class SamplingOverrides {
@@ -52,10 +53,10 @@ class SamplingOverrides {
5253
}
5354

5455
@Nullable
55-
MatcherGroup getOverride(Attributes attributes) {
56+
MatcherGroup getOverride(SpanKind spanKind, Attributes attributes) {
5657
LazyHttpUrl lazyHttpUrl = new LazyHttpUrl(attributes);
5758
for (MatcherGroup matcherGroups : matcherGroups) {
58-
if (matcherGroups.matches(attributes, lazyHttpUrl)) {
59+
if (matcherGroups.matches(spanKind, attributes, lazyHttpUrl)) {
5960
return matcherGroups;
6061
}
6162
}
@@ -143,11 +144,13 @@ public TraceState getUpdatedTraceState(TraceState parentTraceState) {
143144
}
144145

145146
static class MatcherGroup {
147+
@Nullable private final SpanKind spanKind;
146148
private final List<TempPredicate> predicates;
147149
private final double percentage;
148150
private final SamplingResult recordAndSampleAndOverwriteTraceState;
149151

150152
private MatcherGroup(SamplingOverride override) {
153+
spanKind = override.spanKind != null ? override.spanKind.otelSpanKind : null;
151154
predicates = new ArrayList<>();
152155
for (SamplingOverrideAttribute attribute : override.attributes) {
153156
predicates.add(toPredicate(attribute));
@@ -165,7 +168,10 @@ SamplingResult getRecordAndSampleAndOverwriteTraceState() {
165168
return recordAndSampleAndOverwriteTraceState;
166169
}
167170

168-
private boolean matches(Attributes attributes, LazyHttpUrl lazyHttpUrl) {
171+
private boolean matches(SpanKind spanKind, Attributes attributes, LazyHttpUrl lazyHttpUrl) {
172+
if (this.spanKind != null && !this.spanKind.equals(spanKind)) {
173+
return false;
174+
}
169175
for (TempPredicate predicate : predicates) {
170176
if (!predicate.test(attributes, lazyHttpUrl)) {
171177
return false;
@@ -179,6 +185,8 @@ private static TempPredicate toPredicate(SamplingOverrideAttribute attribute) {
179185
return new StrictMatcher(attribute.key, attribute.value);
180186
} else if (attribute.matchType == MatchType.REGEXP) {
181187
return new RegexpMatcher(attribute.key, attribute.value);
188+
} else if (attribute.matchType == null) {
189+
return new KeyOnlyMatcher(attribute.key);
182190
} else {
183191
throw new IllegalStateException("Unexpected match type: " + attribute.matchType);
184192
}
@@ -223,6 +231,23 @@ public boolean test(Attributes attributes, LazyHttpUrl lazyHttpUrl) {
223231
}
224232
}
225233

234+
private static class KeyOnlyMatcher implements TempPredicate {
235+
private final AttributeKey<String> key;
236+
237+
private KeyOnlyMatcher(String key) {
238+
this.key = AttributeKey.stringKey(key);
239+
}
240+
241+
@Override
242+
public boolean test(Attributes attributes, LazyHttpUrl lazyHttpUrl) {
243+
String val = attributes.get(key);
244+
if (val == null && key.getKey().equals(SemanticAttributes.HTTP_URL.getKey())) {
245+
val = lazyHttpUrl.get();
246+
}
247+
return val != null;
248+
}
249+
}
250+
226251
// this is temporary until semantic attributes stabilize and we make breaking change
227252
private static class LazyHttpUrl {
228253
private final Attributes attributes;

agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/heartbeat/HeartbeatTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ void heartBeatPayloadContainsDataByDefault() throws InterruptedException {
8686
provider.initialize(TelemetryClient.createForTest());
8787

8888
// some of the initialization above happens in a separate thread
89-
Thread.sleep(100);
89+
Thread.sleep(500);
9090

9191
// then
9292
MetricsData t = (MetricsData) provider.gatherData().getData().getBaseData();

0 commit comments

Comments
 (0)