Skip to content

Commit 612a75d

Browse files
committed
Allow customization of parent-override behaviour for inferred-spans
1 parent b1f46bb commit 612a75d

File tree

9 files changed

+132
-31
lines changed

9 files changed

+132
-31
lines changed

inferred-spans/README.md

Lines changed: 15 additions & 14 deletions
Large diffs are not rendered by default.

inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpansAutoConfig.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,15 @@
66
package io.opentelemetry.contrib.inferredspans;
77

88
import com.google.auto.service.AutoService;
9+
import io.opentelemetry.api.trace.SpanBuilder;
10+
import io.opentelemetry.api.trace.SpanContext;
911
import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer;
1012
import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider;
1113
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
1214
import java.time.Duration;
1315
import java.util.Arrays;
1416
import java.util.List;
17+
import java.util.function.BiConsumer;
1518
import java.util.function.Consumer;
1619
import java.util.logging.Logger;
1720
import java.util.stream.Collectors;
@@ -34,6 +37,8 @@ public class InferredSpansAutoConfig implements AutoConfigurationCustomizerProvi
3437
static final String INTERVAL_OPTION = "otel.inferred.spans.interval";
3538
static final String DURATION_OPTION = "otel.inferred.spans.duration";
3639
static final String LIB_DIRECTORY_OPTION = "otel.inferred.spans.lib.directory";
40+
static final String PARENT_OVERRIDE_HANDLER_OPTION =
41+
"otel.inferred.spans.parent.override.handler";
3742

3843
@Override
3944
public void customize(AutoConfigurationCustomizer config) {
@@ -56,6 +61,12 @@ public void customize(AutoConfigurationCustomizer config) {
5661
applier.applyDuration(DURATION_OPTION, builder::profilingDuration);
5762
applier.applyString(LIB_DIRECTORY_OPTION, builder::profilerLibDirectory);
5863

64+
String parentOverrideHandlerName = properties.getString(PARENT_OVERRIDE_HANDLER_OPTION);
65+
if (parentOverrideHandlerName != null && !parentOverrideHandlerName.isEmpty()) {
66+
builder.parentOverrideHandler(
67+
constructParentOverrideHandler(parentOverrideHandlerName));
68+
}
69+
5970
providerBuilder.addSpanProcessor(builder.build());
6071
} else {
6172
log.finest(
@@ -65,6 +76,16 @@ public void customize(AutoConfigurationCustomizer config) {
6576
});
6677
}
6778

79+
@SuppressWarnings("unchecked")
80+
private static BiConsumer<SpanBuilder, SpanContext> constructParentOverrideHandler(String name) {
81+
try {
82+
Class<?> clazz = Class.forName(name);
83+
return (BiConsumer<SpanBuilder, SpanContext>) clazz.getConstructor().newInstance();
84+
} catch (Exception e) {
85+
throw new IllegalArgumentException("Could not construct parent override handler", e);
86+
}
87+
}
88+
6889
private static class PropertiesApplier {
6990

7091
private final ConfigProperties properties;

inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpansProcessorBuilder.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@
55

66
package io.opentelemetry.contrib.inferredspans;
77

8+
import io.opentelemetry.api.trace.SpanBuilder;
9+
import io.opentelemetry.api.trace.SpanContext;
10+
import io.opentelemetry.contrib.inferredspans.internal.CallTree;
811
import io.opentelemetry.contrib.inferredspans.internal.InferredSpansConfiguration;
912
import io.opentelemetry.contrib.inferredspans.internal.SpanAnchoredClock;
1013
import java.io.File;
1114
import java.time.Duration;
1215
import java.util.Arrays;
1316
import java.util.List;
17+
import java.util.function.BiConsumer;
1418
import javax.annotation.Nullable;
1519

1620
@SuppressWarnings("CanIgnoreReturnValueSuggester")
@@ -48,6 +52,8 @@ public class InferredSpansProcessorBuilder {
4852

4953
@Nullable private File activationEventsFile = null;
5054
@Nullable private File jfrFile = null;
55+
private BiConsumer<SpanBuilder, SpanContext> parentOverrideHandler =
56+
CallTree.DEFAULT_PARENT_OVERRIDE;
5157

5258
InferredSpansProcessorBuilder() {}
5359

@@ -64,7 +70,8 @@ public InferredSpansProcessor build() {
6470
excludedClasses,
6571
profilerInterval,
6672
profilingDuration,
67-
profilerLibDirectory);
73+
profilerLibDirectory,
74+
parentOverrideHandler);
6875
return new InferredSpansProcessor(
6976
config, clock, startScheduledProfiling, activationEventsFile, jfrFile);
7077
}
@@ -188,4 +195,17 @@ InferredSpansProcessorBuilder jfrFile(@Nullable File jfrFile) {
188195
this.jfrFile = jfrFile;
189196
return this;
190197
}
198+
199+
/**
200+
* Defines the action to perform when a inferred span is discovered to actually be the parent of a
201+
* normal span. The first argument of the handler is the modifiable inferred span, the second
202+
* argument the span context of the normal span which should be somehow marked as child of the
203+
* inferred one. By default, a span link is added to the inferred span to represent this
204+
* relationship.
205+
*/
206+
InferredSpansProcessorBuilder parentOverrideHandler(
207+
BiConsumer<SpanBuilder, SpanContext> handler) {
208+
this.parentOverrideHandler = handler;
209+
return this;
210+
}
191211
}

inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/internal/CallTree.java

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.util.Arrays;
2626
import java.util.List;
2727
import java.util.concurrent.TimeUnit;
28+
import java.util.function.BiConsumer;
2829
import java.util.logging.Logger;
2930
import javax.annotation.Nullable;
3031
import org.agrona.collections.LongHashSet;
@@ -50,9 +51,12 @@ public class CallTree implements Recyclable {
5051

5152
private static final int INITIAL_CHILD_SIZE = 2;
5253

53-
private static final Attributes CHILD_LINK_ATTRIBUTES =
54+
public static final Attributes CHILD_LINK_ATTRIBUTES =
5455
Attributes.builder().put(LINK_IS_CHILD, true).build();
5556

57+
public static final BiConsumer<SpanBuilder, SpanContext> DEFAULT_PARENT_OVERRIDE =
58+
(inferredSpan, child) -> inferredSpan.addLink(child, CHILD_LINK_ATTRIBUTES);
59+
5660
@Nullable private CallTree parent;
5761
protected int count;
5862
private List<CallTree> children = new ArrayList<>(INITIAL_CHILD_SIZE);
@@ -427,6 +431,7 @@ int spanify(
427431
@Nullable Span parentSpan,
428432
TraceContext parentContext,
429433
SpanAnchoredClock clock,
434+
BiConsumer<SpanBuilder, SpanContext> normalSpanParentOverride,
430435
StringBuilder tempBuilder,
431436
Tracer tracer) {
432437
int createdSpans = 0;
@@ -437,7 +442,15 @@ int spanify(
437442
Span span = null;
438443
if (!isPillar() || isLeaf()) {
439444
createdSpans++;
440-
span = asSpan(root, parentSpan, parentContext, tracer, clock, tempBuilder);
445+
span =
446+
asSpan(
447+
root,
448+
parentSpan,
449+
parentContext,
450+
tracer,
451+
clock,
452+
normalSpanParentOverride,
453+
tempBuilder);
441454
this.isSpan = true;
442455
}
443456
List<CallTree> children = getChildren();
@@ -450,6 +463,7 @@ int spanify(
450463
span != null ? span : parentSpan,
451464
parentContext,
452465
clock,
466+
normalSpanParentOverride,
453467
tempBuilder,
454468
tracer);
455469
}
@@ -462,6 +476,7 @@ protected Span asSpan(
462476
TraceContext parentContext,
463477
Tracer tracer,
464478
SpanAnchoredClock clock,
479+
BiConsumer<SpanBuilder, SpanContext> normalSpanParentOverride,
465480
StringBuilder tempBuilder) {
466481

467482
Context parentOtelCtx;
@@ -494,7 +509,11 @@ protected Span asSpan(
494509
clock.toEpochNanos(parentContext.getClockAnchor(), this.start),
495510
TimeUnit.NANOSECONDS);
496511
insertChildIdLinks(
497-
spanBuilder, Span.fromContext(parentOtelCtx).getSpanContext(), parentContext, tempBuilder);
512+
spanBuilder,
513+
Span.fromContext(parentOtelCtx).getSpanContext(),
514+
parentContext,
515+
normalSpanParentOverride,
516+
tempBuilder);
498517

499518
// we're not interested in the very bottom of the stack which contains things like accepting and
500519
// handling connections
@@ -517,6 +536,7 @@ private void insertChildIdLinks(
517536
SpanBuilder span,
518537
SpanContext parentContext,
519538
TraceContext nonInferredParent,
539+
BiConsumer<SpanBuilder, SpanContext> normalSpanParentOverride,
520540
StringBuilder tempBuilder) {
521541
if (childIds == null || childIds.isEmpty()) {
522542
return;
@@ -527,13 +547,13 @@ private void insertChildIdLinks(
527547
if (nonInferredParent.getSpanId() == childIds.getParentId(i)) {
528548
tempBuilder.setLength(0);
529549
HexUtils.appendLongAsHex(childIds.getId(i), tempBuilder);
530-
SpanContext spanContext =
550+
SpanContext childSpanContext =
531551
SpanContext.create(
532552
parentContext.getTraceId(),
533553
tempBuilder.toString(),
534554
parentContext.getTraceFlags(),
535555
parentContext.getTraceState());
536-
span.addLink(spanContext, CHILD_LINK_ATTRIBUTES);
556+
normalSpanParentOverride.accept(span, childSpanContext);
537557
}
538558
}
539559
}
@@ -863,13 +883,18 @@ private static CallTree findCommonAncestor(CallTree previousTopOfStack, CallTree
863883
* possible to update the parent ID of a regular span so that it correctly reflects being a
864884
* child of an inferred span.
865885
*/
866-
public int spanify(SpanAnchoredClock clock, Tracer tracer) {
886+
public int spanify(
887+
SpanAnchoredClock clock,
888+
Tracer tracer,
889+
BiConsumer<SpanBuilder, SpanContext> normalSpanOverride) {
867890
StringBuilder tempBuilder = new StringBuilder();
868891
int createdSpans = 0;
869892
List<CallTree> callTrees = getChildren();
870893
for (int i = 0, size = callTrees.size(); i < size; i++) {
871894
createdSpans +=
872-
callTrees.get(i).spanify(this, null, rootContext, clock, tempBuilder, tracer);
895+
callTrees
896+
.get(i)
897+
.spanify(this, null, rootContext, clock, normalSpanOverride, tempBuilder, tracer);
873898
}
874899
return createdSpans;
875900
}

inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/internal/InferredSpansConfiguration.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@
55

66
package io.opentelemetry.contrib.inferredspans.internal;
77

8+
import io.opentelemetry.api.trace.SpanBuilder;
9+
import io.opentelemetry.api.trace.SpanContext;
810
import io.opentelemetry.contrib.inferredspans.WildcardMatcher;
911
import java.time.Duration;
1012
import java.util.List;
13+
import java.util.function.BiConsumer;
1114
import javax.annotation.Nullable;
1215

1316
public class InferredSpansConfiguration {
@@ -22,8 +25,8 @@ public class InferredSpansConfiguration {
2225
private final List<WildcardMatcher> excludedClasses;
2326
private final Duration profilerInterval;
2427
private final Duration profilingDuration;
25-
2628
@Nullable private final String profilerLibDirectory;
29+
private final BiConsumer<SpanBuilder, SpanContext> parentOverrideHandler;
2730

2831
@SuppressWarnings("TooManyParameters")
2932
public InferredSpansConfiguration(
@@ -37,7 +40,8 @@ public InferredSpansConfiguration(
3740
List<WildcardMatcher> excludedClasses,
3841
Duration profilerInterval,
3942
Duration profilingDuration,
40-
@Nullable String profilerLibDirectory) {
43+
@Nullable String profilerLibDirectory,
44+
BiConsumer<SpanBuilder, SpanContext> parentOverrideHandler) {
4145
this.profilerLoggingEnabled = profilerLoggingEnabled;
4246
this.backupDiagnosticFiles = backupDiagnosticFiles;
4347
this.asyncProfilerSafeMode = asyncProfilerSafeMode;
@@ -49,6 +53,7 @@ public InferredSpansConfiguration(
4953
this.profilerInterval = profilerInterval;
5054
this.profilingDuration = profilingDuration;
5155
this.profilerLibDirectory = profilerLibDirectory;
56+
this.parentOverrideHandler = parentOverrideHandler;
5257
}
5358

5459
public boolean isProfilingLoggingEnabled() {
@@ -100,4 +105,8 @@ public String getProfilerLibDirectory() {
100105
public boolean isPostProcessingEnabled() {
101106
return postProcessingEnabled;
102107
}
108+
109+
public BiConsumer<SpanBuilder, SpanContext> getParentOverrideHandler() {
110+
return parentOverrideHandler;
111+
}
103112
}

inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/internal/SamplingProfiler.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -952,7 +952,10 @@ private void stopProfiling(SamplingProfiler samplingProfiler) {
952952
callTree.end(
953953
samplingProfiler.callTreePool, samplingProfiler.getInferredSpansMinDurationNs());
954954
int createdSpans =
955-
callTree.spanify(samplingProfiler.getClock(), samplingProfiler.tracerProvider.get());
955+
callTree.spanify(
956+
samplingProfiler.getClock(),
957+
samplingProfiler.tracerProvider.get(),
958+
samplingProfiler.config.getParentOverrideHandler());
956959
if (logger.isLoggable(Level.FINE)) {
957960
if (createdSpans > 0) {
958961
logger.log(

inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/InferredSpansAutoConfigTest.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
import io.opentelemetry.api.GlobalOpenTelemetry;
1212
import io.opentelemetry.api.OpenTelemetry;
1313
import io.opentelemetry.api.trace.Span;
14+
import io.opentelemetry.api.trace.SpanBuilder;
15+
import io.opentelemetry.api.trace.SpanContext;
1416
import io.opentelemetry.api.trace.Tracer;
1517
import io.opentelemetry.context.Scope;
1618
import io.opentelemetry.contrib.inferredspans.internal.InferredSpansConfiguration;
@@ -23,6 +25,7 @@
2325
import java.nio.file.Path;
2426
import java.time.Duration;
2527
import java.util.List;
28+
import java.util.function.BiConsumer;
2629
import java.util.stream.Collectors;
2730
import org.junit.jupiter.api.AfterEach;
2831
import org.junit.jupiter.api.BeforeEach;
@@ -40,6 +43,12 @@ public void resetGlobalOtel() {
4043
OtelReflectionUtils.shutdownAndResetGlobalOtel();
4144
}
4245

46+
public static class NoOpParentOverrideHandler implements BiConsumer<SpanBuilder, SpanContext> {
47+
48+
@Override
49+
public void accept(SpanBuilder spanBuilder, SpanContext spanContext) {}
50+
}
51+
4352
@Test
4453
@DisabledOnOpenJ9
4554
public void checkAllOptions(@TempDir Path tmpDir) {
@@ -57,7 +66,10 @@ public void checkAllOptions(@TempDir Path tmpDir) {
5766
.put(InferredSpansAutoConfig.EXCLUDED_CLASSES_OPTION, "blub,test*.test2")
5867
.put(InferredSpansAutoConfig.INTERVAL_OPTION, "2s")
5968
.put(InferredSpansAutoConfig.DURATION_OPTION, "3s")
60-
.put(InferredSpansAutoConfig.LIB_DIRECTORY_OPTION, libDir)) {
69+
.put(InferredSpansAutoConfig.LIB_DIRECTORY_OPTION, libDir)
70+
.put(
71+
InferredSpansAutoConfig.PARENT_OVERRIDE_HANDLER_OPTION,
72+
NoOpParentOverrideHandler.class.getName())) {
6173

6274
OpenTelemetry otel = GlobalOpenTelemetry.get();
6375
List<SpanProcessor> processors = OtelReflectionUtils.getSpanProcessors(otel);
@@ -81,6 +93,7 @@ public void checkAllOptions(@TempDir Path tmpDir) {
8193
assertThat(config.getProfilingInterval()).isEqualTo(Duration.ofSeconds(2));
8294
assertThat(config.getProfilingDuration()).isEqualTo(Duration.ofSeconds(3));
8395
assertThat(config.getProfilerLibDirectory()).isEqualTo(libDir);
96+
assertThat(config.getParentOverrideHandler()).isInstanceOf(NoOpParentOverrideHandler.class);
8497
}
8598
}
8699

inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/internal/CallTreeSpanifyTest.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,10 @@ void testSpanification() throws Exception {
4848
setup.profiler.setProfilingSessionOngoing(true);
4949
CallTree.Root callTree =
5050
CallTreeTest.getCallTree(setup, new String[] {" dd ", " cc ", " bbb ", "aaaaee"});
51-
assertThat(callTree.spanify(nanoClock, setup.sdk.getTracer("dummy-tracer"))).isEqualTo(4);
51+
assertThat(
52+
callTree.spanify(
53+
nanoClock, setup.sdk.getTracer("dummy-tracer"), CallTree.DEFAULT_PARENT_OVERRIDE))
54+
.isEqualTo(4);
5255
assertThat(setup.getSpans()).hasSize(5);
5356
assertThat(setup.getSpans().stream().map(SpanData::getName))
5457
.containsExactly(
@@ -158,7 +161,8 @@ void testCallTreeWithActiveSpan() {
158161
.build());
159162

160163
try (OpenTelemetrySdk outputSdk = sdkBuilder.build()) {
161-
root.spanify(nanoClock, outputSdk.getTracer("dummy-tracer"));
164+
root.spanify(
165+
nanoClock, outputSdk.getTracer("dummy-tracer"), CallTree.DEFAULT_PARENT_OVERRIDE);
162166

163167
List<SpanData> spans = exporter.getFinishedSpanItems();
164168
assertThat(spans).hasSize(2);
@@ -206,7 +210,8 @@ void testSpanWithInvertedActivation() {
206210
.addSpanProcessor(SimpleSpanProcessor.create(exporter))
207211
.build());
208212
try (OpenTelemetrySdk outputSdk = sdkBuilder.build()) {
209-
root.spanify(nanoClock, outputSdk.getTracer("dummy-tracer"));
213+
root.spanify(
214+
nanoClock, outputSdk.getTracer("dummy-tracer"), CallTree.DEFAULT_PARENT_OVERRIDE);
210215

211216
List<SpanData> spans = exporter.getFinishedSpanItems();
212217
assertThat(spans).hasSize(1);
@@ -249,7 +254,8 @@ void testSpanWithNestedActivation() {
249254
.addSpanProcessor(SimpleSpanProcessor.create(exporter))
250255
.build());
251256
try (OpenTelemetrySdk outputSdk = sdkBuilder.build()) {
252-
root.spanify(nanoClock, outputSdk.getTracer("dummy-tracer"));
257+
root.spanify(
258+
nanoClock, outputSdk.getTracer("dummy-tracer"), CallTree.DEFAULT_PARENT_OVERRIDE);
253259

254260
List<SpanData> spans = exporter.getFinishedSpanItems();
255261
assertThat(spans).hasSize(1);

inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/internal/CallTreeTest.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -869,7 +869,10 @@ private Map<String, SpanData> assertCallTree(
869869
assertThat(actualResult).isEqualTo(expectedResult.toString());
870870

871871
if (expectedSpans != null) {
872-
root.spanify(nanoClock, profilerSetup.sdk.getTracer("dummy-inferred-spans-tracer"));
872+
root.spanify(
873+
nanoClock,
874+
profilerSetup.sdk.getTracer("dummy-inferred-spans-tracer"),
875+
CallTree.DEFAULT_PARENT_OVERRIDE);
873876
Map<String, SpanData> spans =
874877
profilerSetup.getSpans().stream()
875878
.collect(toMap(s -> s.getName().replaceAll(".*#", ""), Function.identity()));

0 commit comments

Comments
 (0)