Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
bf44697
Snapshot Profiler Configuration refactoring
robsunday Jul 7, 2026
5c93e2b
Fix for missing nodes in profiler exporter config
robsunday Jul 7, 2026
efcacf6
Fix for missing nodes in profiler exporter config
robsunday Jul 7, 2026
eff6544
Code review followup
robsunday Jul 8, 2026
b8e36cf
Merge branch 'main' into opamp-snapshot-profiler-config-refactor
robsunday Jul 10, 2026
57276b0
Merge branch 'main' into opamp-snapshot-profiler-config-refactor
robsunday Jul 14, 2026
898d25e
Initial commit
robsunday Jul 14, 2026
73b5c6d
Test fixes and cleanup of comments
robsunday Jul 15, 2026
dbdaeaa
Merge branch 'main' into opamp-call-graphs
robsunday Jul 15, 2026
576149f
Tests cleanup
robsunday Jul 15, 2026
6f66ce4
SpanTracker support
robsunday Jul 15, 2026
cce4a7d
Merge branch 'main' into opamp-call-graphs
robsunday Jul 17, 2026
656daea
TraceThreadChangeDetector support
robsunday Jul 17, 2026
900e1f8
Tests updated after TraceThreadChangeDetector support
robsunday Jul 17, 2026
a7aa8c6
Tests improved
robsunday Jul 17, 2026
0538bb5
Declarative config customizer improvements
robsunday Jul 17, 2026
b23d3d1
SnapshotProfilingSpanProcessor support
robsunday Jul 20, 2026
e39a53f
spotless
robsunday Jul 20, 2026
a1c515e
Merge branch 'main' into opamp-call-graphs
robsunday Jul 21, 2026
d8bd09c
Merge branch 'main' into opamp-call-graphs
robsunday Jul 22, 2026
c084413
code review followup
robsunday Jul 24, 2026
c603258
Merge branch 'main' into opamp-call-graphs
robsunday Jul 24, 2026
4ac828d
code review followup
robsunday Jul 24, 2026
0cac85b
Merge branch 'main' into opamp-call-graphs
robsunday Jul 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -157,17 +157,19 @@ private void setJfrContextStorageEnabled(boolean enabled) {
* request.
*/
private void tryStart() {
updateJvmMemoryMetrics();
if (isJfrRecordingActive()) {
logger.fine("JFR is already running, not starting again.");
return;
}

if (!jfr.isAvailable()) {
logger.warning(
"JDK Flight Recorder (JFR) is not available in this JVM. Profiling will not start.");
return;
}

configSupplier.get().log();
updateJvmMemoryMetrics();
setJfrContextStorageEnabled(true);
activateJfrRecording(getResource(sdk));
logger.info("Profiler is active.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ class ActiveSpanTracker implements ContextStorage, SpanTracker {
private final ContextStorage delegate;
private final TraceRegistry registry;

private volatile boolean enabled;

ActiveSpanTracker(ContextStorage delegate, TraceRegistry registry) {
this.delegate = delegate;
this.registry = registry;
Expand All @@ -39,6 +41,10 @@ class ActiveSpanTracker implements ContextStorage, SpanTracker {
@Override
public Scope attach(Context toAttach) {
Scope scope = delegate.attach(toAttach);
if (!isEnabled()) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[for reviewer] ActiveSpanTracker is always created during SDK initialization and can be turned on and off by SnapshotProfilingSupervisor

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the unfortunate world we will live in forever now. 💣

return scope;
}

SpanContext newSpanContext = Span.fromContext(toAttach).getSpanContext();
if (doNotTrack(newSpanContext)) {
return scope;
Expand All @@ -61,6 +67,16 @@ public Scope attach(Context toAttach) {
};
}

@Override
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}

@Override
public boolean isEnabled() {
return enabled;
}

private boolean doNotTrack(SpanContext spanContext) {
return !spanContext.isSampled() || !registry.isRegistered(spanContext);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ private ContextStorage trackActiveSpans(ContextStorage storage, TraceRegistry re
}

private ContextStorage detectThreadChanges(ContextStorage storage, TraceRegistry registry) {
return new TraceThreadChangeDetector(storage, registry, StackTraceSampler.SUPPLIER);
TraceThreadChangeDetector detector =
new TraceThreadChangeDetector(storage, registry, StackTraceSampler.SUPPLIER);
TraceThreadChangeDetector.SUPPLIER.configure(detector);
return detector;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright Splunk Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.splunk.opentelemetry.profiler.snapshot;

import com.google.auto.service.AutoService;
import com.google.common.annotations.VisibleForTesting;
import io.opentelemetry.javaagent.extension.AgentListener;
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
import java.util.function.Function;

@AutoService(AgentListener.class)
public class SnapshotProfilingAgentListener implements AgentListener {
private final Function<AutoConfiguredOpenTelemetrySdk, SnapshotProfilingSupervisor>
snapshotProfilingSupervisorMaker;

public SnapshotProfilingAgentListener() {
this(SnapshotProfilingSupervisor::initialize);
}

@VisibleForTesting
SnapshotProfilingAgentListener(
Function<AutoConfiguredOpenTelemetrySdk, SnapshotProfilingSupervisor>
snapshotProfilingSupervisorMaker) {
this.snapshotProfilingSupervisorMaker = snapshotProfilingSupervisorMaker;
}

@Override
public void afterAgent(AutoConfiguredOpenTelemetrySdk sdk) {
// Must be always executed to initialize supervisor
SnapshotProfilingSupervisor supervisor = snapshotProfilingSupervisorMaker.apply(sdk);

SnapshotProfilingConfiguration configuration = SnapshotProfilingConfiguration.SUPPLIER.get();
if (configuration.isEnabled()) {
supervisor.startProfiling();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,19 +49,12 @@ OpenTelemetryConfigurationModel customizeModel(OpenTelemetryConfigurationModel m
SnapshotProfilingConfiguration snapshotProfiling = getSnapshotProfilingConfig(model);
SnapshotProfilingConfiguration.SUPPLIER.configure(snapshotProfiling);

if (snapshotProfiling.isEnabled()) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[for reviewer] We always setup some components that must be created during sdk initialization, and later on they are enabled or disabled by SnapshotProfilingSupervisor.

initActiveSpansTracking();
initStackTraceSampler(snapshotProfiling);

@robsunday robsunday Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[for reviewer] StackTraceSampler is now initiated in StackTraceSamplerInitializer called by SnapshotProfilingSupervisor

addSpanProcessors(model);
}
initActiveSpansTracking();
addSpanProcessors(model);

return model;
}

private void initStackTraceSampler(SnapshotProfilingConfiguration snapshotProfilingConfig) {
StackTraceSamplerInitializer.setupStackTraceSampler(snapshotProfilingConfig);
}

private void addSpanProcessors(OpenTelemetryConfigurationModel model) {
TracerProviderModel tracerProviderModel = model.getTracerProvider();
if (tracerProviderModel == null) {
Expand Down

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[for reviewer] Like in SnapshotProfilingConfigurationCustomizerProvider, we always preconfigure some components that later can be enabled or disabled by SnapshotProfilingSupervisor

Original file line number Diff line number Diff line change
Expand Up @@ -22,56 +22,24 @@
import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder;
import java.time.Duration;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function;

@AutoService(AutoConfigurationCustomizerProvider.class)
public class SnapshotProfilingSdkCustomizer implements AutoConfigurationCustomizerProvider {
private final TraceRegistry registry;
private final Function<ConfigProperties, StackTraceSampler> samplerProvider;
private final ContextStorageWrapper contextStorageWrapper;

public SnapshotProfilingSdkCustomizer() {
this(
TraceRegistryHolder.getTraceRegistry(),
stackTraceSamplerProvider(),
new ContextStorageWrapper());
}

private static Function<ConfigProperties, StackTraceSampler> stackTraceSamplerProvider() {
return properties -> {
SnapshotProfilingConfiguration configuration = SnapshotProfilingConfiguration.SUPPLIER.get();
Duration samplingPeriod = configuration.getSamplingInterval();
StagingArea.SUPPLIER.configure(createStagingArea(configuration));
return new PeriodicStackTraceSampler(
StagingArea.SUPPLIER, SpanTracker.SUPPLIER, samplingPeriod);
};
}

private static StagingArea createStagingArea(SnapshotProfilingConfiguration configuration) {
Duration interval = configuration.getExportInterval();
int capacity = configuration.getStagingCapacity();
return new PeriodicallyExportingStagingArea(StackTraceExporter.SUPPLIER, interval, capacity);
this(TraceRegistryHolder.getTraceRegistry(), new ContextStorageWrapper());
}

@VisibleForTesting
SnapshotProfilingSdkCustomizer(
TraceRegistry registry,
StackTraceSampler sampler,
ContextStorageWrapper contextStorageWrapper) {
this(registry, properties -> sampler, contextStorageWrapper);
}

private SnapshotProfilingSdkCustomizer(
TraceRegistry registry,
Function<ConfigProperties, StackTraceSampler> samplerProvider,
ContextStorageWrapper contextStorageWrapper) {
TraceRegistry registry, ContextStorageWrapper contextStorageWrapper) {
this.registry = registry;
this.samplerProvider = samplerProvider;
this.contextStorageWrapper = contextStorageWrapper;
}

Expand All @@ -82,7 +50,6 @@ public void customize(AutoConfigurationCustomizer autoConfigurationCustomizer) {

autoConfigurationCustomizer
.addTracerProviderCustomizer(snapshotProfilingSpanProcessor(registry))
.addPropertiesCustomizer(setupStackTraceSampler())

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[for reviewer] StackTraceSampler is now initiated in StackTraceSamplerInitializer called by SnapshotProfilingSupervisor

.addPropertiesCustomizer(startTrackingActiveSpans(registry))
.addTracerProviderCustomizer(addShutdownHook());
}
Expand All @@ -99,54 +66,31 @@ public void customize(AutoConfigurationCustomizer autoConfigurationCustomizer) {
private BiFunction<SdkTracerProviderBuilder, ConfigProperties, SdkTracerProviderBuilder>
addShutdownHook() {
return (builder, properties) -> {
if (snapshotProfilingEnabled()) {
builder.addSpanProcessor(new SdkShutdownHook());
}
builder.addSpanProcessor(new SdkShutdownHook());

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[for reviewer] It is safe to call SdkShutdownHook regardless if snapshot profiling is on or off

return builder;
};
}

private BiFunction<SdkTracerProviderBuilder, ConfigProperties, SdkTracerProviderBuilder>
snapshotProfilingSpanProcessor(TraceRegistry registry) {
return (builder, properties) -> {
if (snapshotProfilingEnabled()) {
double selectionProbability =
SnapshotProfilingConfiguration.SUPPLIER.get().getSnapshotSelectionProbability();
double selectionProbability =
SnapshotProfilingConfiguration.SUPPLIER.get().getSnapshotSelectionProbability();

return builder.addSpanProcessor(
new SnapshotProfilingSpanProcessor(
registry, new TraceIdBasedSnapshotSelector(selectionProbability)));
}
return builder;
};
}

private boolean includeTraceContextPropagator(Set<String> configuredPropagators) {
return configuredPropagators.isEmpty();
}
SnapshotProfilingSpanProcessor spanProcessor =

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[for reviewer] SnapshotProfilingSpanProcessor is now enabled and disabled by SnapshotProfilingSupervisor

new SnapshotProfilingSpanProcessor(
registry, new TraceIdBasedSnapshotSelector(selectionProbability));
SnapshotProfilingSpanProcessor.SUPPLIER.configure(spanProcessor);

private Function<ConfigProperties, Map<String, String>> setupStackTraceSampler() {
return properties -> {
if (snapshotProfilingEnabled()) {
StackTraceSampler sampler = samplerProvider.apply(properties);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[for reviewer] It is now created by StackTraceSamplerInitializer called by SnapshotProfilingSupervisor when profiling is enabled

ConfigurableSupplier<StackTraceSampler> supplier = StackTraceSampler.SUPPLIER;
supplier.configure(sampler);
}
return Collections.emptyMap();
return builder.addSpanProcessor(spanProcessor);
};
}

private Function<ConfigProperties, Map<String, String>> startTrackingActiveSpans(
TraceRegistry registry) {
return properties -> {
if (snapshotProfilingEnabled()) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[for reviewer] This check is replaced by enabled property in ActiveSpanTracker and TraceThreadChangeDetector that are created by ContextStorageWrapper

contextStorageWrapper.wrapContextStorage(registry);
}
contextStorageWrapper.wrapContextStorage(registry);
return Collections.emptyMap();
};
}

private boolean snapshotProfilingEnabled() {
return SnapshotProfilingConfiguration.SUPPLIER.get().isEnabled();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import static com.splunk.opentelemetry.profiler.ProfilingSemanticAttributes.SNAPSHOT_PROFILING;

import com.splunk.opentelemetry.profiler.util.OptionalConfigurableSupplier;
import io.opentelemetry.api.trace.SpanContext;
import io.opentelemetry.context.Context;
import io.opentelemetry.sdk.common.CompletableResultCode;
Expand All @@ -29,9 +30,12 @@
* Custom {@link SpanProcessor} implementation that will register traces for snapshot profiling<br>
*/
public class SnapshotProfilingSpanProcessor implements SpanProcessor {
public static final OptionalConfigurableSupplier<SnapshotProfilingSpanProcessor> SUPPLIER =
new OptionalConfigurableSupplier<>();
private final TraceRegistry registry;
private final SnapshotSelector selector;
private final OrphanedTraceCleaner orphanedTraceCleaner;
private volatile boolean enabled;

SnapshotProfilingSpanProcessor(TraceRegistry registry, SnapshotSelector selector) {
this.registry = registry;
Expand All @@ -41,6 +45,9 @@ public class SnapshotProfilingSpanProcessor implements SpanProcessor {

@Override
public void onStart(Context context, ReadWriteSpan span) {
if (!isEnabled()) {
return;
}
if (isEntry(span)) {
SpanContext spanContext = span.getSpanContext();
boolean selected = selector.select(spanContext);
Expand Down Expand Up @@ -70,6 +77,8 @@ public boolean isStartRequired() {
@Override
public void onEnd(ReadableSpan span) {
if (isEntry(span)) {
// To prevent memory leaks do the cleanup even if this processor is disabled.
// Some spans may have been started when processor was enabled.
registry.unregister(span.getSpanContext());
orphanedTraceCleaner.unregister(span.getSpanContext());
}
Expand All @@ -89,4 +98,12 @@ public CompletableResultCode shutdown() {
orphanedTraceCleaner.close();
return SpanProcessor.super.shutdown();
}

public void setEnabled(boolean enabled) {
this.enabled = enabled;
}

public boolean isEnabled() {
return enabled;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,11 @@ public SnapshotProfilingSpanProcessor create(
DeclarativeConfigProperties declarativeConfigProperties) {
double selectionProbability =
SnapshotProfilingConfiguration.SUPPLIER.get().getSnapshotSelectionProbability();
return new SnapshotProfilingSpanProcessor(
traceRegistry, new TraceIdBasedSnapshotSelector(selectionProbability));

SnapshotProfilingSpanProcessor spanProcessor =
new SnapshotProfilingSpanProcessor(
traceRegistry, new TraceIdBasedSnapshotSelector(selectionProbability));
SnapshotProfilingSpanProcessor.SUPPLIER.configure(spanProcessor);
return spanProcessor;
}
}
Loading
Loading