Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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,16 +157,19 @@ private void setJfrContextStorageEnabled(boolean enabled) {
* request.
*/
private void tryStart() {
updateJvmMemoryMetrics();
if (isJfrRecordingActive()) {
logger.fine("JFR is already running, not starting again.");
return;
}

updateJvmMemoryMetrics();

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

configSupplier.get().log();
setJfrContextStorageEnabled(true);
activateJfrRecording(getResource(sdk));
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 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

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,53 @@
/*
* 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()) {
return;
}

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);

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 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())
.addPropertiesCustomizer(startTrackingActiveSpans(registry))
.addTracerProviderCustomizer(addShutdownHook());
}
Expand All @@ -99,54 +66,28 @@ 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();

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

private boolean includeTraceContextPropagator(Set<String> configuredPropagators) {
return configuredPropagators.isEmpty();
}
double selectionProbability =
SnapshotProfilingConfiguration.SUPPLIER.get().getSnapshotSelectionProbability();

private Function<ConfigProperties, Map<String, String>> setupStackTraceSampler() {
return properties -> {
if (snapshotProfilingEnabled()) {
StackTraceSampler sampler = samplerProvider.apply(properties);
ConfigurableSupplier<StackTraceSampler> supplier = StackTraceSampler.SUPPLIER;
supplier.configure(sampler);
}
return Collections.emptyMap();
return builder.addSpanProcessor(
new SnapshotProfilingSpanProcessor(
registry, new TraceIdBasedSnapshotSelector(selectionProbability)));
};
}

private Function<ConfigProperties, Map<String, String>> startTrackingActiveSpans(
TraceRegistry registry) {
return properties -> {
if (snapshotProfilingEnabled()) {
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
@@ -0,0 +1,116 @@
/*
* 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.common.annotations.VisibleForTesting;
import com.splunk.opentelemetry.profiler.OtelLoggerFactory;
import com.splunk.opentelemetry.profiler.util.OptionalConfigurableSupplier;
import io.opentelemetry.sdk.autoconfigure.AutoConfigureUtil;
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
import java.util.logging.Logger;

public class SnapshotProfilingSupervisor {
public static final OptionalConfigurableSupplier<SnapshotProfilingSupervisor> SUPPLIER =
new OptionalConfigurableSupplier<>();
private static final Logger logger =
Logger.getLogger(SnapshotProfilingSupervisor.class.getName());

private final OptionalConfigurableSupplier<SnapshotProfilingConfiguration> configurationSupplier;
private final ConfigurableSupplier<SpanTracker> spanTrackerSupplier;
private final OptionalConfigurableSupplier<TraceThreadChangeDetector>
traceThreadChangeDetectorSupplier;
private final AutoConfiguredOpenTelemetrySdk sdk;
private final OtelLoggerFactory otelLoggerFactory;
private boolean running;

@VisibleForTesting
SnapshotProfilingSupervisor(
OptionalConfigurableSupplier<SnapshotProfilingConfiguration> configurationSupplier,
ConfigurableSupplier<SpanTracker> spanTrackerSupplier,
OptionalConfigurableSupplier<TraceThreadChangeDetector> traceThreadChangeDetectorSupplier,
AutoConfiguredOpenTelemetrySdk sdk,
OtelLoggerFactory otelLoggerFactory) {
this.configurationSupplier = configurationSupplier;
this.spanTrackerSupplier = spanTrackerSupplier;
this.traceThreadChangeDetectorSupplier = traceThreadChangeDetectorSupplier;
this.sdk = sdk;
this.otelLoggerFactory = otelLoggerFactory;
}

public static SnapshotProfilingSupervisor initialize(AutoConfiguredOpenTelemetrySdk sdk) {
if (SUPPLIER.isConfigured()) {
throw new IllegalStateException("Snapshot profiling already initialized");
}

SnapshotProfilingSupervisor supervisor =
new SnapshotProfilingSupervisor(
SnapshotProfilingConfiguration.SUPPLIER,
SpanTracker.SUPPLIER,
TraceThreadChangeDetector.SUPPLIER,
sdk,
new OtelLoggerFactory());
SUPPLIER.configure(supervisor);

return supervisor;
}

public synchronized void startProfiling() {
if (running) {
return;
}

configurationSupplier.get().log();

// StagingArea.SUPPLIER
// StackTraceSampler.SUPPLIER
// StackTraceExporter.SUPPLIER = AsyncStackTraceExporter
StackTraceSamplerInitializer.setupStackTraceSampler(configurationSupplier.get());
StackTraceSamplerInitializer.setupStackTraceExporter(
configurationSupplier.get(), AutoConfigureUtil.getResource(sdk), otelLoggerFactory);

// SpanTracker.SUPPLIER
spanTrackerSupplier.get().setEnabled(true);
traceThreadChangeDetectorSupplier.get().setEnabled(true);

// SnapshotProfilingSdkCustomizer/SnapshotProfilingSpanProcessorComponentProvider ->
// SnapshotProfilingSpanProcessor
// SdkShutdownHookComponentProvider -> SdkShutdownHook

running = true;
logger.info("Snapshot profiling is active.");
}

public synchronized void stopProfiling() {
if (!running) {
return;
}

StackTraceSampler.SUPPLIER.get().close();
StackTraceSampler.SUPPLIER.reset();

StagingArea.SUPPLIER.get().close();
StagingArea.SUPPLIER.reset();

StackTraceExporter.SUPPLIER.get().close();
StackTraceExporter.SUPPLIER.reset();

SpanTracker.SUPPLIER.get().setEnabled(false);
TraceThreadChangeDetector.SUPPLIER.get().setEnabled(false);

running = false;
}
}
Loading
Loading