diff --git a/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpans.java b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpans.java
new file mode 100644
index 000000000..76f55db83
--- /dev/null
+++ b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpans.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.contrib.inferredspans;
+
+import java.time.Duration;
+import javax.annotation.Nullable;
+
+/**
+ * A global accessor for the {@link InferredSpansProcessor} instance.
+ *
+ *
This class is for internal use only and may be removed in a future release.
+ */
+public final class InferredSpans {
+
+ @Nullable private static volatile InferredSpansProcessor instance;
+
+ private InferredSpans() {}
+
+ /**
+ * Sets the {@link InferredSpansProcessor} instance.
+ *
+ * @param processor the processor instance
+ */
+ public static void setInstance(@Nullable InferredSpansProcessor processor) {
+ instance = processor;
+ }
+
+ /**
+ * Returns whether inferred spans are enabled.
+ *
+ * @return whether inferred spans are enabled
+ */
+ public static boolean isEnabled() {
+ return instance != null;
+ }
+
+ /**
+ * Sets the profiler interval.
+ *
+ * @param interval the new profiler interval
+ */
+ public static void setProfilerInterval(Duration interval) {
+ InferredSpansProcessor p = instance;
+ if (p != null) {
+ p.setProfilerInterval(interval);
+ }
+ }
+}
diff --git a/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpansProcessor.java b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpansProcessor.java
index a9288bac2..19baf3174 100644
--- a/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpansProcessor.java
+++ b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpansProcessor.java
@@ -21,6 +21,7 @@
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
+import java.time.Duration;
import java.util.Properties;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
@@ -38,6 +39,7 @@ public class InferredSpansProcessor implements SpanProcessor {
// Visible for testing
final SamplingProfiler profiler;
+ private final InferredSpansConfiguration config;
private Supplier tracerProvider = GlobalOpenTelemetry::getTracerProvider;
@Nullable private volatile Tracer tracer;
@@ -48,12 +50,18 @@ public class InferredSpansProcessor implements SpanProcessor {
boolean startScheduledProfiling,
@Nullable File activationEventsFile,
@Nullable File jfrFile) {
+ this.config = config;
profiler = new SamplingProfiler(config, clock, this::getTracer, activationEventsFile, jfrFile);
if (startScheduledProfiling) {
profiler.start();
}
}
+ public void setProfilerInterval(Duration interval) {
+ config.setProfilerInterval(interval);
+ profiler.reschedule();
+ }
+
public static InferredSpansProcessorBuilder builder() {
return new InferredSpansProcessorBuilder();
}
diff --git a/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpansProcessorBuilder.java b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpansProcessorBuilder.java
index 3124f3341..e8f52ed68 100644
--- a/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpansProcessorBuilder.java
+++ b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpansProcessorBuilder.java
@@ -71,8 +71,11 @@ public InferredSpansProcessor build() {
profilingDuration,
profilerLibDirectory,
parentOverrideHandler);
- return new InferredSpansProcessor(
- config, clock, startScheduledProfiling, activationEventsFile, jfrFile);
+ InferredSpansProcessor processor =
+ new InferredSpansProcessor(
+ config, clock, startScheduledProfiling, activationEventsFile, jfrFile);
+ InferredSpans.setInstance(processor);
+ return processor;
}
/**
diff --git a/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/internal/InferredSpansConfiguration.java b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/internal/InferredSpansConfiguration.java
index 5091a36a5..819a5b8cf 100644
--- a/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/internal/InferredSpansConfiguration.java
+++ b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/internal/InferredSpansConfiguration.java
@@ -23,7 +23,7 @@ public class InferredSpansConfiguration {
private final Duration inferredSpansMinDuration;
private final List includedClasses;
private final List excludedClasses;
- private final Duration profilerInterval;
+ private volatile Duration profilerInterval;
private final Duration profilingDuration;
@Nullable private final String profilerLibDirectory;
private final BiConsumer parentOverrideHandler;
@@ -84,6 +84,10 @@ public Duration getProfilingInterval() {
return profilerInterval;
}
+ public void setProfilerInterval(Duration profilerInterval) {
+ this.profilerInterval = profilerInterval;
+ }
+
public Duration getProfilingDuration() {
return profilingDuration;
}
diff --git a/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/internal/SamplingProfiler.java b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/internal/SamplingProfiler.java
index 61e5f75a7..27d0e5305 100644
--- a/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/internal/SamplingProfiler.java
+++ b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/internal/SamplingProfiler.java
@@ -39,6 +39,7 @@
import java.util.Locale;
import java.util.Objects;
import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
@@ -151,6 +152,7 @@ public class SamplingProfiler implements Runnable {
private final Supplier tracerProvider;
private final AsyncProfiler profiler;
+ @Nullable private volatile Future> profilingTask;
/**
* Creates a sampling profiler, optionally relying on existing files.
@@ -385,7 +387,7 @@ public void run() {
if (!interrupted && !scheduler.isShutdown()) {
long delay = config.getProfilingInterval().toMillis() - profilingDuration.toMillis();
- scheduler.schedule(this, delay, TimeUnit.MILLISECONDS);
+ profilingTask = scheduler.schedule(this, delay, TimeUnit.MILLISECONDS);
}
}
@@ -723,7 +725,19 @@ public void copyFromFiles(Path activationEvents, Path traces) throws IOException
@SuppressWarnings("FutureReturnValueIgnored")
public void start() {
- scheduler.submit(this);
+ profilingTask = scheduler.submit(this);
+ }
+
+ @SuppressWarnings({"FutureReturnValueIgnored", "Interruption"})
+ public void reschedule() {
+ Future> future = this.profilingTask;
+ if (future != null) {
+ if (future.cancel(true)) {
+ Duration profilingDuration = config.getProfilingDuration();
+ long delay = config.getProfilingInterval().toMillis() - profilingDuration.toMillis();
+ profilingTask = scheduler.schedule(this, delay, TimeUnit.MILLISECONDS);
+ }
+ }
}
public void stop() throws InterruptedException, IOException {
diff --git a/inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/InferredSpansTest.java b/inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/InferredSpansTest.java
new file mode 100644
index 000000000..e0de5997e
--- /dev/null
+++ b/inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/InferredSpansTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.contrib.inferredspans;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Awaitility.await;
+
+import io.opentelemetry.contrib.inferredspans.internal.SamplingProfiler;
+import io.opentelemetry.contrib.inferredspans.internal.util.DisabledOnOpenJ9;
+import java.time.Duration;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.DisabledOnOs;
+import org.junit.jupiter.api.condition.OS;
+
+@DisabledOnOs(OS.WINDOWS)
+@DisabledOnOpenJ9
+class InferredSpansTest {
+
+ private ProfilerTestSetup setup;
+
+ @BeforeEach
+ void setUp() {
+ InferredSpans.setInstance(null);
+ }
+
+ @AfterEach
+ void tearDown() {
+ if (setup != null) {
+ setup.close();
+ }
+ InferredSpans.setInstance(null);
+ }
+
+ @Test
+ void testIsEnabled() {
+ assertThat(InferredSpans.isEnabled()).isFalse();
+
+ setup = ProfilerTestSetup.create(c -> {});
+
+ assertThat(InferredSpans.isEnabled()).isTrue();
+
+ setup.close();
+ setup = null;
+
+ // In a real-world scenario, the close() method would lead to the processor being garbage
+ // collected, but to make it deterministic, we manually set the instance to null
+ InferredSpans.setInstance(null);
+ assertThat(InferredSpans.isEnabled()).isFalse();
+ }
+
+ @Test
+ void testSetProfilerIntervalWhenDisabled() {
+ InferredSpans.setProfilerInterval(Duration.ofMillis(10));
+
+ setup =
+ ProfilerTestSetup.create(
+ c ->
+ c.profilerInterval(Duration.ofSeconds(10))
+ .profilingDuration(Duration.ofMillis(500)));
+
+ // assert that the interval set before the profiler was initialized is ignored
+ assertThat(setup.profiler.getConfig().getProfilingInterval()).isEqualTo(Duration.ofSeconds(10));
+ }
+
+ @Test
+ void testSetProfilerInterval() {
+ setup =
+ ProfilerTestSetup.create(
+ c ->
+ c.profilerInterval(Duration.ofSeconds(10))
+ .profilingDuration(Duration.ofMillis(500)));
+
+ SamplingProfiler profiler = setup.profiler;
+ await()
+ .untilAsserted(() -> assertThat(profiler.getProfilingSessions()).isGreaterThanOrEqualTo(1));
+
+ InferredSpans.setProfilerInterval(Duration.ofMillis(100));
+
+ await()
+ .timeout(Duration.ofSeconds(2))
+ .untilAsserted(() -> assertThat(profiler.getProfilingSessions()).isGreaterThanOrEqualTo(2));
+ }
+}