From 4df78fcb54c8c28c831ada16b39508c4856a0163 Mon Sep 17 00:00:00 2001 From: Jack Shirazi Date: Thu, 21 Aug 2025 15:39:09 +0100 Subject: [PATCH 1/3] Add dynamically changing the interval --- .../contrib/inferredspans/InferredSpans.java | 51 +++++++++++ .../inferredspans/InferredSpansProcessor.java | 8 ++ .../InferredSpansProcessorBuilder.java | 7 +- .../internal/InferredSpansConfiguration.java | 6 +- .../internal/SamplingProfiler.java | 18 +++- .../inferredspans/InferredSpansTest.java | 91 +++++++++++++++++++ 6 files changed, 176 insertions(+), 5 deletions(-) create mode 100644 inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpans.java create mode 100644 inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/InferredSpansTest.java 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 22f59ba53..9170835b5 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 @@ -19,6 +19,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.time.Duration; import java.util.Objects; import java.util.Properties; import java.util.concurrent.Executors; @@ -38,6 +39,7 @@ public class InferredSpansProcessor implements SpanProcessor { // Visible for testing final SamplingProfiler profiler; + private final InferredSpansConfiguration config; private Supplier tracerProvider = GlobalOpenTelemetry::getTracerProvider; @@ -49,12 +51,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 b464f0f42..6efc8d331 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 @@ -72,8 +72,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..7fc93ee4d --- /dev/null +++ b/inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/InferredSpansTest.java @@ -0,0 +1,91 @@ +/* + * 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)); + } +} From 15196844daccd01f1ad7233a51ed8113c74dd3e7 Mon Sep 17 00:00:00 2001 From: otelbot <197425009+otelbot@users.noreply.github.com> Date: Thu, 21 Aug 2025 14:53:01 +0000 Subject: [PATCH 2/3] ./gradlew spotlessApply --- .../contrib/inferredspans/InferredSpansTest.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) 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 index 7fc93ee4d..e0de5997e 100644 --- a/inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/InferredSpansTest.java +++ b/inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/InferredSpansTest.java @@ -64,8 +64,7 @@ void testSetProfilerIntervalWhenDisabled() { .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)); + assertThat(setup.profiler.getConfig().getProfilingInterval()).isEqualTo(Duration.ofSeconds(10)); } @Test @@ -78,14 +77,12 @@ void testSetProfilerInterval() { SamplingProfiler profiler = setup.profiler; await() - .untilAsserted( - () -> assertThat(profiler.getProfilingSessions()).isGreaterThanOrEqualTo(1)); + .untilAsserted(() -> assertThat(profiler.getProfilingSessions()).isGreaterThanOrEqualTo(1)); InferredSpans.setProfilerInterval(Duration.ofMillis(100)); await() .timeout(Duration.ofSeconds(2)) - .untilAsserted( - () -> assertThat(profiler.getProfilingSessions()).isGreaterThanOrEqualTo(2)); + .untilAsserted(() -> assertThat(profiler.getProfilingSessions()).isGreaterThanOrEqualTo(2)); } } From f22b9721718c888580145fd6b1823cebdd34265c Mon Sep 17 00:00:00 2001 From: otelbot <197425009+otelbot@users.noreply.github.com> Date: Tue, 2 Sep 2025 20:51:25 +0000 Subject: [PATCH 3/3] ./gradlew spotlessApply --- .../contrib/inferredspans/InferredSpansProcessor.java | 1 - 1 file changed, 1 deletion(-) 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 7bc96b074..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 @@ -22,7 +22,6 @@ import java.io.IOException; import java.io.InputStream; import java.time.Duration; -import java.util.Objects; import java.util.Properties; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory;