Skip to content

Commit fa6a9c7

Browse files
jackshiraziotelbot[bot]trask
authored
Add dynamically changing the inferred span interval (#2153)
Co-authored-by: otelbot <[email protected]> Co-authored-by: Trask Stalnaker <[email protected]>
1 parent 2a04aab commit fa6a9c7

File tree

6 files changed

+173
-5
lines changed

6 files changed

+173
-5
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.contrib.inferredspans;
7+
8+
import java.time.Duration;
9+
import javax.annotation.Nullable;
10+
11+
/**
12+
* A global accessor for the {@link InferredSpansProcessor} instance.
13+
*
14+
* <p>This class is for internal use only and may be removed in a future release.
15+
*/
16+
public final class InferredSpans {
17+
18+
@Nullable private static volatile InferredSpansProcessor instance;
19+
20+
private InferredSpans() {}
21+
22+
/**
23+
* Sets the {@link InferredSpansProcessor} instance.
24+
*
25+
* @param processor the processor instance
26+
*/
27+
public static void setInstance(@Nullable InferredSpansProcessor processor) {
28+
instance = processor;
29+
}
30+
31+
/**
32+
* Returns whether inferred spans are enabled.
33+
*
34+
* @return whether inferred spans are enabled
35+
*/
36+
public static boolean isEnabled() {
37+
return instance != null;
38+
}
39+
40+
/**
41+
* Sets the profiler interval.
42+
*
43+
* @param interval the new profiler interval
44+
*/
45+
public static void setProfilerInterval(Duration interval) {
46+
InferredSpansProcessor p = instance;
47+
if (p != null) {
48+
p.setProfilerInterval(interval);
49+
}
50+
}
51+
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.io.File;
2222
import java.io.IOException;
2323
import java.io.InputStream;
24+
import java.time.Duration;
2425
import java.util.Properties;
2526
import java.util.concurrent.Executors;
2627
import java.util.concurrent.ThreadFactory;
@@ -38,6 +39,7 @@ public class InferredSpansProcessor implements SpanProcessor {
3839

3940
// Visible for testing
4041
final SamplingProfiler profiler;
42+
private final InferredSpansConfiguration config;
4143

4244
private Supplier<TracerProvider> tracerProvider = GlobalOpenTelemetry::getTracerProvider;
4345
@Nullable private volatile Tracer tracer;
@@ -48,12 +50,18 @@ public class InferredSpansProcessor implements SpanProcessor {
4850
boolean startScheduledProfiling,
4951
@Nullable File activationEventsFile,
5052
@Nullable File jfrFile) {
53+
this.config = config;
5154
profiler = new SamplingProfiler(config, clock, this::getTracer, activationEventsFile, jfrFile);
5255
if (startScheduledProfiling) {
5356
profiler.start();
5457
}
5558
}
5659

60+
public void setProfilerInterval(Duration interval) {
61+
config.setProfilerInterval(interval);
62+
profiler.reschedule();
63+
}
64+
5765
public static InferredSpansProcessorBuilder builder() {
5866
return new InferredSpansProcessorBuilder();
5967
}

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,11 @@ public InferredSpansProcessor build() {
7171
profilingDuration,
7272
profilerLibDirectory,
7373
parentOverrideHandler);
74-
return new InferredSpansProcessor(
75-
config, clock, startScheduledProfiling, activationEventsFile, jfrFile);
74+
InferredSpansProcessor processor =
75+
new InferredSpansProcessor(
76+
config, clock, startScheduledProfiling, activationEventsFile, jfrFile);
77+
InferredSpans.setInstance(processor);
78+
return processor;
7679
}
7780

7881
/**

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public class InferredSpansConfiguration {
2323
private final Duration inferredSpansMinDuration;
2424
private final List<WildcardMatcher> includedClasses;
2525
private final List<WildcardMatcher> excludedClasses;
26-
private final Duration profilerInterval;
26+
private volatile Duration profilerInterval;
2727
private final Duration profilingDuration;
2828
@Nullable private final String profilerLibDirectory;
2929
private final BiConsumer<SpanBuilder, SpanContext> parentOverrideHandler;
@@ -84,6 +84,10 @@ public Duration getProfilingInterval() {
8484
return profilerInterval;
8585
}
8686

87+
public void setProfilerInterval(Duration profilerInterval) {
88+
this.profilerInterval = profilerInterval;
89+
}
90+
8791
public Duration getProfilingDuration() {
8892
return profilingDuration;
8993
}

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

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import java.util.Locale;
4040
import java.util.Objects;
4141
import java.util.concurrent.Executors;
42+
import java.util.concurrent.Future;
4243
import java.util.concurrent.ScheduledExecutorService;
4344
import java.util.concurrent.TimeUnit;
4445
import java.util.concurrent.locks.LockSupport;
@@ -151,6 +152,7 @@ public class SamplingProfiler implements Runnable {
151152
private final Supplier<Tracer> tracerProvider;
152153

153154
private final AsyncProfiler profiler;
155+
@Nullable private volatile Future<?> profilingTask;
154156

155157
/**
156158
* Creates a sampling profiler, optionally relying on existing files.
@@ -385,7 +387,7 @@ public void run() {
385387

386388
if (!interrupted && !scheduler.isShutdown()) {
387389
long delay = config.getProfilingInterval().toMillis() - profilingDuration.toMillis();
388-
scheduler.schedule(this, delay, TimeUnit.MILLISECONDS);
390+
profilingTask = scheduler.schedule(this, delay, TimeUnit.MILLISECONDS);
389391
}
390392
}
391393

@@ -723,7 +725,19 @@ public void copyFromFiles(Path activationEvents, Path traces) throws IOException
723725

724726
@SuppressWarnings("FutureReturnValueIgnored")
725727
public void start() {
726-
scheduler.submit(this);
728+
profilingTask = scheduler.submit(this);
729+
}
730+
731+
@SuppressWarnings({"FutureReturnValueIgnored", "Interruption"})
732+
public void reschedule() {
733+
Future<?> future = this.profilingTask;
734+
if (future != null) {
735+
if (future.cancel(true)) {
736+
Duration profilingDuration = config.getProfilingDuration();
737+
long delay = config.getProfilingInterval().toMillis() - profilingDuration.toMillis();
738+
profilingTask = scheduler.schedule(this, delay, TimeUnit.MILLISECONDS);
739+
}
740+
}
727741
}
728742

729743
public void stop() throws InterruptedException, IOException {
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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 org.assertj.core.api.Assertions.assertThat;
9+
import static org.awaitility.Awaitility.await;
10+
11+
import io.opentelemetry.contrib.inferredspans.internal.SamplingProfiler;
12+
import io.opentelemetry.contrib.inferredspans.internal.util.DisabledOnOpenJ9;
13+
import java.time.Duration;
14+
import org.junit.jupiter.api.AfterEach;
15+
import org.junit.jupiter.api.BeforeEach;
16+
import org.junit.jupiter.api.Test;
17+
import org.junit.jupiter.api.condition.DisabledOnOs;
18+
import org.junit.jupiter.api.condition.OS;
19+
20+
@DisabledOnOs(OS.WINDOWS)
21+
@DisabledOnOpenJ9
22+
class InferredSpansTest {
23+
24+
private ProfilerTestSetup setup;
25+
26+
@BeforeEach
27+
void setUp() {
28+
InferredSpans.setInstance(null);
29+
}
30+
31+
@AfterEach
32+
void tearDown() {
33+
if (setup != null) {
34+
setup.close();
35+
}
36+
InferredSpans.setInstance(null);
37+
}
38+
39+
@Test
40+
void testIsEnabled() {
41+
assertThat(InferredSpans.isEnabled()).isFalse();
42+
43+
setup = ProfilerTestSetup.create(c -> {});
44+
45+
assertThat(InferredSpans.isEnabled()).isTrue();
46+
47+
setup.close();
48+
setup = null;
49+
50+
// In a real-world scenario, the close() method would lead to the processor being garbage
51+
// collected, but to make it deterministic, we manually set the instance to null
52+
InferredSpans.setInstance(null);
53+
assertThat(InferredSpans.isEnabled()).isFalse();
54+
}
55+
56+
@Test
57+
void testSetProfilerIntervalWhenDisabled() {
58+
InferredSpans.setProfilerInterval(Duration.ofMillis(10));
59+
60+
setup =
61+
ProfilerTestSetup.create(
62+
c ->
63+
c.profilerInterval(Duration.ofSeconds(10))
64+
.profilingDuration(Duration.ofMillis(500)));
65+
66+
// assert that the interval set before the profiler was initialized is ignored
67+
assertThat(setup.profiler.getConfig().getProfilingInterval()).isEqualTo(Duration.ofSeconds(10));
68+
}
69+
70+
@Test
71+
void testSetProfilerInterval() {
72+
setup =
73+
ProfilerTestSetup.create(
74+
c ->
75+
c.profilerInterval(Duration.ofSeconds(10))
76+
.profilingDuration(Duration.ofMillis(500)));
77+
78+
SamplingProfiler profiler = setup.profiler;
79+
await()
80+
.untilAsserted(() -> assertThat(profiler.getProfilingSessions()).isGreaterThanOrEqualTo(1));
81+
82+
InferredSpans.setProfilerInterval(Duration.ofMillis(100));
83+
84+
await()
85+
.timeout(Duration.ofSeconds(2))
86+
.untilAsserted(() -> assertThat(profiler.getProfilingSessions()).isGreaterThanOrEqualTo(2));
87+
}
88+
}

0 commit comments

Comments
 (0)