Skip to content

Commit cc5ec07

Browse files
declarative config: support Inferred spans (#2030)
Co-authored-by: otelbot <[email protected]>
1 parent 32971ab commit cc5ec07

File tree

12 files changed

+364
-147
lines changed

12 files changed

+364
-147
lines changed

inferred-spans/README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,31 @@ So if you are using an autoconfigured OpenTelemetry SDK, you'll only need to add
4343
| otel.inferred.spans.lib.directory <br/> OTEL_INFERRED_SPANS_LIB_DIRECTORY | Defaults to the value of `java.io.tmpdir` | Profiling requires that the [async-profiler](https://github.com/async-profiler/async-profiler) shared library is exported to a temporary location and loaded by the JVM. The partition backing this location must be executable, however in some server-hardened environments, `noexec` may be set on the standard `/tmp` partition, leading to `java.lang.UnsatisfiedLinkError` errors. Set this property to an alternative directory (e.g. `/var/tmp`) to resolve this. |
4444
| otel.inferred.spans.parent.override.handler <br/> OTEL_INFERRED_SPANS_PARENT_OVERRIDE_HANDLER | Defaults to a handler adding span-links to the inferred span | Inferred spans sometimes need to be inserted as the new parent of a normal span, which is not directly possible because that span has already been sent. For this reason, this relationship needs to be represented differently, which normally is done by adding a span-link to the inferred span. This configuration can be used to override that behaviour by providing the fully qualified name of a class implementing `BiConsumer<SpanBuilder, SpanContext>`: The biconsumer will be invoked with the inferred span as first argument and the span for which the inferred one was detected as new parent as second argument |
4545

46+
### Usage with declarative configuration
47+
48+
You can configure the inferred spans processor using declarative YAML configuration with the
49+
OpenTelemetry SDK. For example:
50+
51+
```yaml
52+
file_format: 1.0-rc.1
53+
tracer_provider:
54+
processors:
55+
- inferred_spans/development:
56+
enabled: true # true by default unlike autoconfiguration described above
57+
sampling_interval: 25ms
58+
included_classes: "org.example.myapp.*"
59+
excluded_classes: "java.*"
60+
min_duration: 10ms
61+
interval: 5s
62+
duration: 5s
63+
lib_directory: "/var/tmp"
64+
parent_override_handler: "com.example.MyParentOverrideHandler"
65+
```
66+
67+
All the same settings as for [autoconfiguration](#autoconfiguration) can be used here,
68+
just with the `otel.inferred.spans.` prefix stripped.
69+
For example, `otel.inferred.spans.sampling.interval` becomes `sampling_interval` in YAML.
70+
4671
### Manual SDK setup
4772

4873
If you manually set-up your `OpenTelemetrySDK`, you need to create and register an `InferredSpansProcessor` with your `TracerProvider`:

inferred-spans/build.gradle.kts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ dependencies {
1212
annotationProcessor("com.google.auto.service:auto-service")
1313
compileOnly("com.google.auto.service:auto-service-annotations")
1414
compileOnly("io.opentelemetry:opentelemetry-sdk")
15-
compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi")
15+
compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure")
16+
compileOnly("io.opentelemetry:opentelemetry-sdk-extension-incubator")
17+
compileOnly("io.opentelemetry.instrumentation:opentelemetry-declarative-config-bridge")
1618
compileOnly("io.opentelemetry.semconv:opentelemetry-semconv")
1719
implementation("com.lmax:disruptor")
1820
implementation("org.jctools:jctools-core")
@@ -25,9 +27,11 @@ dependencies {
2527
testImplementation("io.opentelemetry.semconv:opentelemetry-semconv")
2628
testImplementation("io.opentelemetry:opentelemetry-sdk")
2729
testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure")
30+
testImplementation("io.opentelemetry:opentelemetry-sdk-extension-incubator")
2831
testImplementation("io.opentelemetry:opentelemetry-sdk-testing")
2932
testImplementation("io.opentelemetry:opentelemetry-api-incubator")
3033
testImplementation("io.opentelemetry:opentelemetry-exporter-logging")
34+
testImplementation("io.opentelemetry.instrumentation:opentelemetry-declarative-config-bridge")
3135
}
3236

3337
tasks {

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

Lines changed: 2 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -5,130 +5,29 @@
55

66
package io.opentelemetry.contrib.inferredspans;
77

8-
import static java.util.stream.Collectors.toList;
8+
import static io.opentelemetry.contrib.inferredspans.InferredSpansConfig.ENABLED_OPTION;
99

1010
import com.google.auto.service.AutoService;
11-
import io.opentelemetry.api.trace.SpanBuilder;
12-
import io.opentelemetry.api.trace.SpanContext;
1311
import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer;
1412
import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider;
15-
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
16-
import java.time.Duration;
17-
import java.util.Arrays;
18-
import java.util.List;
19-
import java.util.function.BiConsumer;
20-
import java.util.function.Consumer;
2113
import java.util.logging.Logger;
22-
import javax.annotation.Nullable;
2314

2415
@AutoService(AutoConfigurationCustomizerProvider.class)
2516
public class InferredSpansAutoConfig implements AutoConfigurationCustomizerProvider {
2617

2718
private static final Logger log = Logger.getLogger(InferredSpansAutoConfig.class.getName());
2819

29-
static final String ENABLED_OPTION = "otel.inferred.spans.enabled";
30-
static final String LOGGING_OPTION = "otel.inferred.spans.logging.enabled";
31-
static final String DIAGNOSTIC_FILES_OPTION = "otel.inferred.spans.backup.diagnostic.files";
32-
static final String SAFEMODE_OPTION = "otel.inferred.spans.safe.mode";
33-
static final String POSTPROCESSING_OPTION = "otel.inferred.spans.post.processing.enabled";
34-
static final String SAMPLING_INTERVAL_OPTION = "otel.inferred.spans.sampling.interval";
35-
static final String MIN_DURATION_OPTION = "otel.inferred.spans.min.duration";
36-
static final String INCLUDED_CLASSES_OPTION = "otel.inferred.spans.included.classes";
37-
static final String EXCLUDED_CLASSES_OPTION = "otel.inferred.spans.excluded.classes";
38-
static final String INTERVAL_OPTION = "otel.inferred.spans.interval";
39-
static final String DURATION_OPTION = "otel.inferred.spans.duration";
40-
static final String LIB_DIRECTORY_OPTION = "otel.inferred.spans.lib.directory";
41-
static final String PARENT_OVERRIDE_HANDLER_OPTION =
42-
"otel.inferred.spans.parent.override.handler";
43-
4420
@Override
4521
public void customize(AutoConfigurationCustomizer config) {
4622
config.addTracerProviderCustomizer(
4723
(providerBuilder, properties) -> {
4824
if (properties.getBoolean(ENABLED_OPTION, false)) {
49-
InferredSpansProcessorBuilder builder = InferredSpansProcessor.builder();
50-
51-
PropertiesApplier applier = new PropertiesApplier(properties);
52-
53-
applier.applyBool(LOGGING_OPTION, builder::profilerLoggingEnabled);
54-
applier.applyBool(DIAGNOSTIC_FILES_OPTION, builder::backupDiagnosticFiles);
55-
applier.applyInt(SAFEMODE_OPTION, builder::asyncProfilerSafeMode);
56-
applier.applyBool(POSTPROCESSING_OPTION, builder::postProcessingEnabled);
57-
applier.applyDuration(SAMPLING_INTERVAL_OPTION, builder::samplingInterval);
58-
applier.applyDuration(MIN_DURATION_OPTION, builder::inferredSpansMinDuration);
59-
applier.applyWildcards(INCLUDED_CLASSES_OPTION, builder::includedClasses);
60-
applier.applyWildcards(EXCLUDED_CLASSES_OPTION, builder::excludedClasses);
61-
applier.applyDuration(INTERVAL_OPTION, builder::profilerInterval);
62-
applier.applyDuration(DURATION_OPTION, builder::profilingDuration);
63-
applier.applyString(LIB_DIRECTORY_OPTION, builder::profilerLibDirectory);
64-
65-
String parentOverrideHandlerName = properties.getString(PARENT_OVERRIDE_HANDLER_OPTION);
66-
if (parentOverrideHandlerName != null && !parentOverrideHandlerName.isEmpty()) {
67-
builder.parentOverrideHandler(
68-
constructParentOverrideHandler(parentOverrideHandlerName));
69-
}
70-
71-
providerBuilder.addSpanProcessor(builder.build());
25+
providerBuilder.addSpanProcessor(InferredSpansConfig.createSpanProcessor(properties));
7226
} else {
7327
log.finest(
7428
"Not enabling inferred spans processor because " + ENABLED_OPTION + " is not set");
7529
}
7630
return providerBuilder;
7731
});
7832
}
79-
80-
@SuppressWarnings("unchecked")
81-
private static BiConsumer<SpanBuilder, SpanContext> constructParentOverrideHandler(String name) {
82-
try {
83-
Class<?> clazz = Class.forName(name);
84-
return (BiConsumer<SpanBuilder, SpanContext>) clazz.getConstructor().newInstance();
85-
} catch (Exception e) {
86-
throw new IllegalArgumentException("Could not construct parent override handler", e);
87-
}
88-
}
89-
90-
private static class PropertiesApplier {
91-
92-
private final ConfigProperties properties;
93-
94-
PropertiesApplier(ConfigProperties properties) {
95-
this.properties = properties;
96-
}
97-
98-
void applyBool(String configKey, Consumer<Boolean> funcToApply) {
99-
applyValue(properties.getBoolean(configKey), funcToApply);
100-
}
101-
102-
void applyInt(String configKey, Consumer<Integer> funcToApply) {
103-
applyValue(properties.getInt(configKey), funcToApply);
104-
}
105-
106-
void applyDuration(String configKey, Consumer<Duration> funcToApply) {
107-
applyValue(properties.getDuration(configKey), funcToApply);
108-
}
109-
110-
void applyString(String configKey, Consumer<String> funcToApply) {
111-
applyValue(properties.getString(configKey), funcToApply);
112-
}
113-
114-
void applyWildcards(String configKey, Consumer<? super List<WildcardMatcher>> funcToApply) {
115-
String wildcardListString = properties.getString(configKey);
116-
if (wildcardListString != null && !wildcardListString.isEmpty()) {
117-
List<WildcardMatcher> values =
118-
Arrays.stream(wildcardListString.split(","))
119-
.filter(str -> !str.isEmpty())
120-
.map(WildcardMatcher::valueOf)
121-
.collect(toList());
122-
if (!values.isEmpty()) {
123-
funcToApply.accept(values);
124-
}
125-
}
126-
}
127-
128-
private static <T> void applyValue(@Nullable T value, Consumer<T> funcToApply) {
129-
if (value != null) {
130-
funcToApply.accept(value);
131-
}
132-
}
133-
}
13433
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.contrib.inferredspans;
7+
8+
import static java.util.Collections.unmodifiableList;
9+
import static java.util.stream.Collectors.toList;
10+
11+
import io.opentelemetry.api.trace.SpanBuilder;
12+
import io.opentelemetry.api.trace.SpanContext;
13+
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
14+
import io.opentelemetry.sdk.trace.SpanProcessor;
15+
import java.time.Duration;
16+
import java.util.Arrays;
17+
import java.util.List;
18+
import java.util.function.BiConsumer;
19+
import java.util.function.Consumer;
20+
import javax.annotation.Nullable;
21+
22+
class InferredSpansConfig {
23+
24+
private InferredSpansConfig() {}
25+
26+
static final String ENABLED_OPTION = "otel.inferred.spans.enabled";
27+
static final String LOGGING_OPTION = "otel.inferred.spans.logging.enabled";
28+
static final String DIAGNOSTIC_FILES_OPTION = "otel.inferred.spans.backup.diagnostic.files";
29+
static final String SAFEMODE_OPTION = "otel.inferred.spans.safe.mode";
30+
static final String POSTPROCESSING_OPTION = "otel.inferred.spans.post.processing.enabled";
31+
static final String SAMPLING_INTERVAL_OPTION = "otel.inferred.spans.sampling.interval";
32+
static final String MIN_DURATION_OPTION = "otel.inferred.spans.min.duration";
33+
static final String INCLUDED_CLASSES_OPTION = "otel.inferred.spans.included.classes";
34+
static final String EXCLUDED_CLASSES_OPTION = "otel.inferred.spans.excluded.classes";
35+
static final String INTERVAL_OPTION = "otel.inferred.spans.interval";
36+
static final String DURATION_OPTION = "otel.inferred.spans.duration";
37+
static final String LIB_DIRECTORY_OPTION = "otel.inferred.spans.lib.directory";
38+
static final String PARENT_OVERRIDE_HANDLER_OPTION =
39+
"otel.inferred.spans.parent.override.handler";
40+
41+
static final List<String> ALL_PROPERTIES =
42+
unmodifiableList(
43+
Arrays.asList(
44+
ENABLED_OPTION,
45+
LOGGING_OPTION,
46+
DIAGNOSTIC_FILES_OPTION,
47+
SAFEMODE_OPTION,
48+
POSTPROCESSING_OPTION,
49+
SAMPLING_INTERVAL_OPTION,
50+
MIN_DURATION_OPTION,
51+
INCLUDED_CLASSES_OPTION,
52+
EXCLUDED_CLASSES_OPTION,
53+
INTERVAL_OPTION,
54+
DURATION_OPTION,
55+
LIB_DIRECTORY_OPTION,
56+
PARENT_OVERRIDE_HANDLER_OPTION));
57+
58+
static SpanProcessor createSpanProcessor(ConfigProperties properties) {
59+
InferredSpansProcessorBuilder builder = InferredSpansProcessor.builder().profilerEnabled(true);
60+
61+
PropertiesApplier applier = new PropertiesApplier(properties);
62+
63+
applier.applyBool(LOGGING_OPTION, builder::profilerLoggingEnabled);
64+
applier.applyBool(DIAGNOSTIC_FILES_OPTION, builder::backupDiagnosticFiles);
65+
applier.applyInt(SAFEMODE_OPTION, builder::asyncProfilerSafeMode);
66+
applier.applyBool(POSTPROCESSING_OPTION, builder::postProcessingEnabled);
67+
applier.applyDuration(SAMPLING_INTERVAL_OPTION, builder::samplingInterval);
68+
applier.applyDuration(MIN_DURATION_OPTION, builder::inferredSpansMinDuration);
69+
applier.applyWildcards(INCLUDED_CLASSES_OPTION, builder::includedClasses);
70+
applier.applyWildcards(EXCLUDED_CLASSES_OPTION, builder::excludedClasses);
71+
applier.applyDuration(INTERVAL_OPTION, builder::profilerInterval);
72+
applier.applyDuration(DURATION_OPTION, builder::profilingDuration);
73+
applier.applyString(LIB_DIRECTORY_OPTION, builder::profilerLibDirectory);
74+
75+
String parentOverrideHandlerName = properties.getString(PARENT_OVERRIDE_HANDLER_OPTION);
76+
if (parentOverrideHandlerName != null && !parentOverrideHandlerName.isEmpty()) {
77+
builder.parentOverrideHandler(constructParentOverrideHandler(parentOverrideHandlerName));
78+
}
79+
80+
return builder.build();
81+
}
82+
83+
@SuppressWarnings("unchecked")
84+
private static BiConsumer<SpanBuilder, SpanContext> constructParentOverrideHandler(String name) {
85+
try {
86+
Class<?> clazz = Class.forName(name);
87+
return (BiConsumer<SpanBuilder, SpanContext>) clazz.getConstructor().newInstance();
88+
} catch (Exception e) {
89+
throw new IllegalArgumentException("Could not construct parent override handler", e);
90+
}
91+
}
92+
93+
private static class PropertiesApplier {
94+
95+
private final ConfigProperties properties;
96+
97+
PropertiesApplier(ConfigProperties properties) {
98+
this.properties = properties;
99+
}
100+
101+
void applyBool(String configKey, Consumer<Boolean> funcToApply) {
102+
applyValue(properties.getBoolean(configKey), funcToApply);
103+
}
104+
105+
void applyInt(String configKey, Consumer<Integer> funcToApply) {
106+
applyValue(properties.getInt(configKey), funcToApply);
107+
}
108+
109+
void applyDuration(String configKey, Consumer<Duration> funcToApply) {
110+
applyValue(properties.getDuration(configKey), funcToApply);
111+
}
112+
113+
void applyString(String configKey, Consumer<String> funcToApply) {
114+
applyValue(properties.getString(configKey), funcToApply);
115+
}
116+
117+
void applyWildcards(String configKey, Consumer<? super List<WildcardMatcher>> funcToApply) {
118+
String wildcardListString = properties.getString(configKey);
119+
if (wildcardListString != null && !wildcardListString.isEmpty()) {
120+
List<WildcardMatcher> values =
121+
Arrays.stream(wildcardListString.split(","))
122+
.filter(str -> !str.isEmpty())
123+
.map(WildcardMatcher::valueOf)
124+
.collect(toList());
125+
if (!values.isEmpty()) {
126+
funcToApply.accept(values);
127+
}
128+
}
129+
}
130+
131+
private static <T> void applyValue(@Nullable T value, Consumer<T> funcToApply) {
132+
if (value != null) {
133+
funcToApply.accept(value);
134+
}
135+
}
136+
}
137+
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ public class InferredSpansProcessor implements SpanProcessor {
5151
@Nullable File activationEventsFile,
5252
@Nullable File jfrFile) {
5353
this.config = config;
54-
profiler = new SamplingProfiler(config, clock, this::getTracer, activationEventsFile, jfrFile);
54+
profiler =
55+
new SamplingProfiler(config, clock, this::getTracer, activationEventsFile, jfrFile, null);
5556
if (startScheduledProfiling) {
5657
profiler.start();
5758
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
@SuppressWarnings("CanIgnoreReturnValueSuggester")
2222
public class InferredSpansProcessorBuilder {
23+
private boolean enabled = true;
2324
private boolean profilerLoggingEnabled = true;
2425
private boolean backupDiagnosticFiles = false;
2526
private int asyncProfilerSafeMode = 0;
@@ -59,6 +60,7 @@ public class InferredSpansProcessorBuilder {
5960
public InferredSpansProcessor build() {
6061
InferredSpansConfiguration config =
6162
new InferredSpansConfiguration(
63+
enabled,
6264
profilerLoggingEnabled,
6365
backupDiagnosticFiles,
6466
asyncProfilerSafeMode,
@@ -78,6 +80,11 @@ public InferredSpansProcessor build() {
7880
return processor;
7981
}
8082

83+
public InferredSpansProcessorBuilder profilerEnabled(boolean profilerEnabled) {
84+
this.enabled = profilerEnabled;
85+
return this;
86+
}
87+
8188
/**
8289
* By default, async profiler prints warning messages about missing JVM symbols to standard
8390
* output. Set this option to {@code false} to suppress such messages.

0 commit comments

Comments
 (0)