diff --git a/profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilingSupervisor.java b/profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilingSupervisor.java index bcf70483e..05a0547ac 100644 --- a/profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilingSupervisor.java +++ b/profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilingSupervisor.java @@ -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."); diff --git a/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/ActiveSpanTracker.java b/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/ActiveSpanTracker.java index ecbaadfa7..2a7f98d7e 100644 --- a/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/ActiveSpanTracker.java +++ b/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/ActiveSpanTracker.java @@ -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; @@ -39,6 +41,10 @@ class ActiveSpanTracker implements ContextStorage, SpanTracker { @Override public Scope attach(Context toAttach) { Scope scope = delegate.attach(toAttach); + if (!isEnabled()) { + return scope; + } + SpanContext newSpanContext = Span.fromContext(toAttach).getSpanContext(); if (doNotTrack(newSpanContext)) { return scope; @@ -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); } diff --git a/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/ContextStorageWrapper.java b/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/ContextStorageWrapper.java index a712d1c9f..96aee14a1 100644 --- a/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/ContextStorageWrapper.java +++ b/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/ContextStorageWrapper.java @@ -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; } } diff --git a/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotProfilingAgentListener.java b/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotProfilingAgentListener.java new file mode 100644 index 000000000..f289ca945 --- /dev/null +++ b/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotProfilingAgentListener.java @@ -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 + snapshotProfilingSupervisorMaker; + + public SnapshotProfilingAgentListener() { + this(SnapshotProfilingSupervisor::initialize); + } + + @VisibleForTesting + SnapshotProfilingAgentListener( + Function + 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(); + } + } +} diff --git a/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotProfilingConfigurationCustomizerProvider.java b/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotProfilingConfigurationCustomizerProvider.java index 84d6d737b..96f2c853d 100644 --- a/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotProfilingConfigurationCustomizerProvider.java +++ b/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotProfilingConfigurationCustomizerProvider.java @@ -49,19 +49,12 @@ OpenTelemetryConfigurationModel customizeModel(OpenTelemetryConfigurationModel m SnapshotProfilingConfiguration snapshotProfiling = getSnapshotProfilingConfig(model); SnapshotProfilingConfiguration.SUPPLIER.configure(snapshotProfiling); - if (snapshotProfiling.isEnabled()) { - initActiveSpansTracking(); - initStackTraceSampler(snapshotProfiling); - 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) { diff --git a/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotProfilingSdkCustomizer.java b/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotProfilingSdkCustomizer.java index 8bfcd76fa..3ba530b46 100644 --- a/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotProfilingSdkCustomizer.java +++ b/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotProfilingSdkCustomizer.java @@ -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 samplerProvider; private final ContextStorageWrapper contextStorageWrapper; public SnapshotProfilingSdkCustomizer() { - this( - TraceRegistryHolder.getTraceRegistry(), - stackTraceSamplerProvider(), - new ContextStorageWrapper()); - } - - private static Function 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 samplerProvider, - ContextStorageWrapper contextStorageWrapper) { + TraceRegistry registry, ContextStorageWrapper contextStorageWrapper) { this.registry = registry; - this.samplerProvider = samplerProvider; this.contextStorageWrapper = contextStorageWrapper; } @@ -82,7 +50,6 @@ public void customize(AutoConfigurationCustomizer autoConfigurationCustomizer) { autoConfigurationCustomizer .addTracerProviderCustomizer(snapshotProfilingSpanProcessor(registry)) - .addPropertiesCustomizer(setupStackTraceSampler()) .addPropertiesCustomizer(startTrackingActiveSpans(registry)) .addTracerProviderCustomizer(addShutdownHook()); } @@ -99,9 +66,7 @@ public void customize(AutoConfigurationCustomizer autoConfigurationCustomizer) { private BiFunction addShutdownHook() { return (builder, properties) -> { - if (snapshotProfilingEnabled()) { - builder.addSpanProcessor(new SdkShutdownHook()); - } + builder.addSpanProcessor(new SdkShutdownHook()); return builder; }; } @@ -109,44 +74,23 @@ public void customize(AutoConfigurationCustomizer autoConfigurationCustomizer) { private BiFunction 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 configuredPropagators) { - return configuredPropagators.isEmpty(); - } + SnapshotProfilingSpanProcessor spanProcessor = + new SnapshotProfilingSpanProcessor( + registry, new TraceIdBasedSnapshotSelector(selectionProbability)); + SnapshotProfilingSpanProcessor.SUPPLIER.configure(spanProcessor); - private Function> setupStackTraceSampler() { - return properties -> { - if (snapshotProfilingEnabled()) { - StackTraceSampler sampler = samplerProvider.apply(properties); - ConfigurableSupplier supplier = StackTraceSampler.SUPPLIER; - supplier.configure(sampler); - } - return Collections.emptyMap(); + return builder.addSpanProcessor(spanProcessor); }; } private Function> startTrackingActiveSpans( TraceRegistry registry) { return properties -> { - if (snapshotProfilingEnabled()) { - contextStorageWrapper.wrapContextStorage(registry); - } + contextStorageWrapper.wrapContextStorage(registry); return Collections.emptyMap(); }; } - - private boolean snapshotProfilingEnabled() { - return SnapshotProfilingConfiguration.SUPPLIER.get().isEnabled(); - } } diff --git a/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotProfilingSpanProcessor.java b/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotProfilingSpanProcessor.java index 5f2d49fd8..0777fc6a9 100644 --- a/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotProfilingSpanProcessor.java +++ b/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotProfilingSpanProcessor.java @@ -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; @@ -29,9 +30,12 @@ * Custom {@link SpanProcessor} implementation that will register traces for snapshot profiling
*/ public class SnapshotProfilingSpanProcessor implements SpanProcessor { + public static final OptionalConfigurableSupplier 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; @@ -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); @@ -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()); } @@ -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; + } } diff --git a/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotProfilingSpanProcessorComponentProvider.java b/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotProfilingSpanProcessorComponentProvider.java index f76757f71..3fb351605 100644 --- a/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotProfilingSpanProcessorComponentProvider.java +++ b/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotProfilingSpanProcessorComponentProvider.java @@ -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; } } diff --git a/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotProfilingSupervisor.java b/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotProfilingSupervisor.java new file mode 100644 index 000000000..cf8fb95ec --- /dev/null +++ b/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotProfilingSupervisor.java @@ -0,0 +1,117 @@ +/* + * 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 SUPPLIER = + new OptionalConfigurableSupplier<>(); + private static final Logger logger = + Logger.getLogger(SnapshotProfilingSupervisor.class.getName()); + + private final OptionalConfigurableSupplier configurationSupplier; + private final ConfigurableSupplier spanTrackerSupplier; + private final OptionalConfigurableSupplier + traceThreadChangeDetectorSupplier; + private final OptionalConfigurableSupplier + profilingSpanProcessorSupplier; + private final AutoConfiguredOpenTelemetrySdk sdk; + private final OtelLoggerFactory otelLoggerFactory; + private boolean running; + + @VisibleForTesting + SnapshotProfilingSupervisor( + OptionalConfigurableSupplier configurationSupplier, + ConfigurableSupplier spanTrackerSupplier, + OptionalConfigurableSupplier traceThreadChangeDetectorSupplier, + OptionalConfigurableSupplier profilingSpanProcessorSupplier, + AutoConfiguredOpenTelemetrySdk sdk, + OtelLoggerFactory otelLoggerFactory) { + this.configurationSupplier = configurationSupplier; + this.spanTrackerSupplier = spanTrackerSupplier; + this.traceThreadChangeDetectorSupplier = traceThreadChangeDetectorSupplier; + this.profilingSpanProcessorSupplier = profilingSpanProcessorSupplier; + 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, + SnapshotProfilingSpanProcessor.SUPPLIER, + sdk, + new OtelLoggerFactory()); + SUPPLIER.configure(supervisor); + + return supervisor; + } + + public synchronized void startProfiling() { + if (running) { + return; + } + + configurationSupplier.get().log(); + + StackTraceSamplerInitializer.setupStackTraceSampler(configurationSupplier.get()); + StackTraceSamplerInitializer.setupStackTraceExporter( + configurationSupplier.get(), AutoConfigureUtil.getResource(sdk), otelLoggerFactory); + + spanTrackerSupplier.get().setEnabled(true); + traceThreadChangeDetectorSupplier.get().setEnabled(true); + + profilingSpanProcessorSupplier.get().setEnabled(true); + + 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(); + + spanTrackerSupplier.get().setEnabled(false); + traceThreadChangeDetectorSupplier.get().setEnabled(false); + + profilingSpanProcessorSupplier.get().setEnabled(false); + + running = false; + } +} diff --git a/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/SpanTracker.java b/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/SpanTracker.java index 6c5f370d5..31ed12b7f 100644 --- a/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/SpanTracker.java +++ b/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/SpanTracker.java @@ -20,8 +20,27 @@ import java.util.Optional; interface SpanTracker { - SpanTracker NOOP = thread -> Optional.empty(); + SpanTracker NOOP = + new SpanTracker() { + @Override + public Optional getActiveSpan(Thread thread) { + return Optional.empty(); + } + + @Override + public void setEnabled(boolean enabled) {} + + @Override + public boolean isEnabled() { + return false; + } + }; + ConfigurableSupplier SUPPLIER = new ConfigurableSupplier<>(SpanTracker.NOOP); Optional getActiveSpan(Thread thread); + + void setEnabled(boolean enabled); + + boolean isEnabled(); } diff --git a/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/StackTraceExporterActivator.java b/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/StackTraceExporterActivator.java deleted file mode 100644 index 1f474d529..000000000 --- a/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/StackTraceExporterActivator.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * 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 com.splunk.opentelemetry.profiler.OtelLoggerFactory; -import com.splunk.opentelemetry.profiler.util.DeclarativeConfigPropertiesUtil; -import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; -import io.opentelemetry.api.logs.Logger; -import io.opentelemetry.javaagent.extension.AgentListener; -import io.opentelemetry.sdk.autoconfigure.AutoConfigureUtil; -import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; -import io.opentelemetry.sdk.resources.Resource; - -@AutoService(AgentListener.class) -public class StackTraceExporterActivator implements AgentListener { - private static final java.util.logging.Logger logger = - java.util.logging.Logger.getLogger(StackTraceExporterActivator.class.getName()); - - private final OtelLoggerFactory otelLoggerFactory; - - public StackTraceExporterActivator() { - this(new OtelLoggerFactory()); - } - - @VisibleForTesting - StackTraceExporterActivator(OtelLoggerFactory otelLoggerFactory) { - this.otelLoggerFactory = otelLoggerFactory; - } - - @Override - public void afterAgent(AutoConfiguredOpenTelemetrySdk sdk) { - SnapshotProfilingConfiguration configuration = SnapshotProfilingConfiguration.SUPPLIER.get(); - if (!configuration.isEnabled()) { - return; - } - - configuration.log(); - - int maxDepth = configuration.getStackDepth(); - Logger otelLogger = buildLogger(sdk, configuration.getConfigProperties()); - AsyncStackTraceExporter exporter = new AsyncStackTraceExporter(otelLogger, maxDepth); - StackTraceExporter.SUPPLIER.configure(exporter); - - logger.info("Snapshot profiling is active."); - } - - private Logger buildLogger(AutoConfiguredOpenTelemetrySdk sdk, Object configProperties) { - Resource resource = AutoConfigureUtil.getResource(sdk); - if (configProperties instanceof DeclarativeConfigProperties) { - DeclarativeConfigProperties exporterConfig = - DeclarativeConfigPropertiesUtil.getStructuredOrEmpty( - (DeclarativeConfigProperties) configProperties, "exporter"); - return otelLoggerFactory.build(exporterConfig, resource); - } - if (configProperties instanceof ConfigProperties) { - return otelLoggerFactory.build((ConfigProperties) configProperties, resource); - } - throw new IllegalArgumentException( - "Unsupported config properties type: " + configProperties.getClass().getName()); - } -} diff --git a/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/StackTraceSamplerInitializer.java b/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/StackTraceSamplerInitializer.java index 6593de656..d6ba287a1 100644 --- a/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/StackTraceSamplerInitializer.java +++ b/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/StackTraceSamplerInitializer.java @@ -16,10 +16,16 @@ package com.splunk.opentelemetry.profiler.snapshot; +import com.splunk.opentelemetry.profiler.OtelLoggerFactory; +import com.splunk.opentelemetry.profiler.util.DeclarativeConfigPropertiesUtil; +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.api.logs.Logger; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.resources.Resource; import java.time.Duration; class StackTraceSamplerInitializer { - public StackTraceSamplerInitializer() {} + private StackTraceSamplerInitializer() {} static void setupStackTraceSampler(SnapshotProfilingConfiguration configuration) { Duration samplingPeriod = configuration.getSamplingInterval(); @@ -31,9 +37,35 @@ static void setupStackTraceSampler(SnapshotProfilingConfiguration configuration) StackTraceSampler.SUPPLIER.configure(sampler); } + static void setupStackTraceExporter( + SnapshotProfilingConfiguration configuration, + Resource resource, + OtelLoggerFactory otelLoggerFactory) { + int maxDepth = configuration.getStackDepth(); + Logger otelLogger = + buildLogger(otelLoggerFactory, resource, configuration.getConfigProperties()); + AsyncStackTraceExporter exporter = new AsyncStackTraceExporter(otelLogger, maxDepth); + StackTraceExporter.SUPPLIER.configure(exporter); + } + private static StagingArea createStagingArea(SnapshotProfilingConfiguration configuration) { Duration interval = configuration.getExportInterval(); int capacity = configuration.getStagingCapacity(); return new PeriodicallyExportingStagingArea(StackTraceExporter.SUPPLIER, interval, capacity); } + + private static Logger buildLogger( + OtelLoggerFactory otelLoggerFactory, Resource resource, Object configProperties) { + if (configProperties instanceof DeclarativeConfigProperties) { + DeclarativeConfigProperties exporterConfig = + DeclarativeConfigPropertiesUtil.getStructuredOrEmpty( + (DeclarativeConfigProperties) configProperties, "exporter"); + return otelLoggerFactory.build(exporterConfig, resource); + } + if (configProperties instanceof ConfigProperties) { + return otelLoggerFactory.build((ConfigProperties) configProperties, resource); + } + throw new IllegalArgumentException( + "Unsupported config properties type: " + configProperties.getClass().getName()); + } } diff --git a/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/TraceThreadChangeDetector.java b/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/TraceThreadChangeDetector.java index 5ab79f00f..2ca75a0cf 100644 --- a/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/TraceThreadChangeDetector.java +++ b/profiler/src/main/java/com/splunk/opentelemetry/profiler/snapshot/TraceThreadChangeDetector.java @@ -16,6 +16,7 @@ package com.splunk.opentelemetry.profiler.snapshot; +import com.splunk.opentelemetry.profiler.util.OptionalConfigurableSupplier; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.context.Context; @@ -25,10 +26,15 @@ import javax.annotation.Nullable; class TraceThreadChangeDetector implements ContextStorage { + static OptionalConfigurableSupplier SUPPLIER = + new OptionalConfigurableSupplier<>(); + private final ContextStorage delegate; private final TraceRegistry registry; private final Supplier sampler; + private volatile boolean enabled; + TraceThreadChangeDetector( ContextStorage delegate, TraceRegistry registry, Supplier sampler) { this.delegate = delegate; @@ -36,9 +42,21 @@ class TraceThreadChangeDetector implements ContextStorage { this.sampler = sampler; } + void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + boolean isEnabled() { + return enabled; + } + @Override public Scope attach(Context toAttach) { Scope scope = delegate.attach(toAttach); + if (!isEnabled()) { + return scope; + } + SpanContext newSpanContext = Span.fromContext(toAttach).getSpanContext(); if (doNotTrack(newSpanContext)) { return scope; diff --git a/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/ActiveSpanTrackerTest.java b/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/ActiveSpanTrackerTest.java index dc498418f..5f0417804 100644 --- a/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/ActiveSpanTrackerTest.java +++ b/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/ActiveSpanTrackerTest.java @@ -35,6 +35,7 @@ import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executors; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; class ActiveSpanTrackerTest { @@ -42,6 +43,11 @@ class ActiveSpanTrackerTest { private final TogglableTraceRegistry registry = new TogglableTraceRegistry(); private final ActiveSpanTracker spanTracker = new ActiveSpanTracker(storage, registry); + @BeforeEach + void setUp() { + spanTracker.setEnabled(true); + } + @Test void currentContextComesFromOpenTelemetryContextStorage() { var context = Context.root().with(ContextKey.named("test-key"), "value"); @@ -209,6 +215,19 @@ void doNotTrackSpanWhenNoSpanPresentInContext() { } } + @Test + void doNotTrackSpanWhenTrackerIsDisabled() { + spanTracker.setEnabled(false); + + var span = Span.wrap(Snapshotting.spanContext().build()); + var context = Context.root().with(span); + registry.register(span.getSpanContext()); + + try (var ignored = spanTracker.attach(context)) { + assertEquals(Optional.empty(), spanTracker.getActiveSpan(Thread.currentThread())); + } + } + @Test void doNotTrackSpanWhenSpanIsNotSampled() { var span = Span.wrap(Snapshotting.spanContext().unsampled().build()); diff --git a/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/ConcurrentServiceEntrySamplingTest.java b/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/ConcurrentServiceEntrySamplingTest.java index 70f6a8934..a42e7c18b 100644 --- a/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/ConcurrentServiceEntrySamplingTest.java +++ b/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/ConcurrentServiceEntrySamplingTest.java @@ -19,7 +19,6 @@ import static org.awaitility.Awaitility.await; import static org.junit.jupiter.api.Assertions.assertEquals; -import com.splunk.opentelemetry.profiler.OtelLoggerFactory; import com.splunk.opentelemetry.profiler.snapshot.simulation.Delay; import com.splunk.opentelemetry.profiler.snapshot.simulation.ExitCall; import com.splunk.opentelemetry.profiler.snapshot.simulation.Message; @@ -28,13 +27,14 @@ import io.opentelemetry.api.trace.SpanId; import io.opentelemetry.context.Context; import io.opentelemetry.sdk.autoconfigure.OpenTelemetrySdkExtension; -import io.opentelemetry.sdk.testing.exporter.InMemoryLogRecordExporter; import java.time.Duration; import java.util.ArrayList; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.function.UnaryOperator; import java.util.stream.Collectors; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -43,9 +43,9 @@ class ConcurrentServiceEntrySamplingTest { private final ResettingContextStorageWrapper spanTrackingActivator = new ResettingContextStorageWrapper(); - private final InMemoryLogRecordExporter logExporter = InMemoryLogRecordExporter.create(); private final InMemoryStagingArea staging = new InMemoryStagingArea(); private final StackTraceSampler sampler = newSampler(staging); + private final SnapshotProfilingAgentListener agentListener = Snapshotting.agentListener(); private StackTraceSampler newSampler(StagingArea staging) { var stagingAreaSupplier = StagingArea.SUPPLIER; @@ -55,7 +55,7 @@ private StackTraceSampler newSampler(StagingArea staging) { } private final SnapshotProfilingSdkCustomizer downstreamCustomizer = - Snapshotting.customizer().with(sampler).with(spanTrackingActivator).build(); + Snapshotting.customizer().with(spanTrackingActivator).build(); @RegisterExtension public final OpenTelemetrySdkExtension downstreamSdk = @@ -63,12 +63,12 @@ private StackTraceSampler newSampler(StagingArea staging) { .withProperty("splunk.snapshot.profiler.enabled", "true") .withProperty("splunk.snapshot.selection.probability", "1.0") .with(downstreamCustomizer) - .with( - new StackTraceExporterActivator( - new OtelLoggerFactory( - () -> logExporter, declarativeConfigProperties -> logExporter))) + .with(agentListener) .build(); + private final SnapshotProfilingSpanProcessor downstreamSpanProcessor = + SnapshotProfilingSpanProcessor.SUPPLIER.get(); + @RegisterExtension public final Server downstream = Server.builder(downstreamSdk) @@ -86,8 +86,12 @@ private StackTraceSampler newSampler(StagingArea staging) { .withProperty("splunk.snapshot.profiler.enabled", "true") .withProperty("splunk.snapshot.selection.probability", "1.0") .with(upstreamCustomizer) + .with(agentListener) .build(); + private final SnapshotProfilingSpanProcessor upstreamSpanProcessor = + SnapshotProfilingSpanProcessor.SUPPLIER.get(); + @RegisterExtension public final Server upstream = Server.builder(upstreamSdk) @@ -95,6 +99,19 @@ private StackTraceSampler newSampler(StagingArea staging) { .performing(concurrentExitCallsTo(2, downstream, upstreamSdk)) .build(); + @BeforeEach + void enableContextTracking() { + Snapshotting.enable(downstreamSpanProcessor, upstreamSpanProcessor); + Snapshotting.customizer().with(sampler).with(staging); + SpanTracker.SUPPLIER.get().setEnabled(true); + TraceThreadChangeDetector.SUPPLIER.get().setEnabled(true); + } + + @AfterEach + void tearDown() { + Snapshotting.resetProfiling(); + } + /** * The test is attempting to model the scenario where an upstream service makes two concurrent * requests into the same downstream service within the same trace. Real scenarios may differ, but diff --git a/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/ContextStorageWrapperTest.java b/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/ContextStorageWrapperTest.java index 475a69dc6..81a72569f 100644 --- a/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/ContextStorageWrapperTest.java +++ b/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/ContextStorageWrapperTest.java @@ -43,6 +43,7 @@ void installActiveSpanTracker() { registry.register(spanContext); wrapper.wrapContextStorage(registry); + SpanTracker.SUPPLIER.get().setEnabled(true); try (var ignored = Context.current().with(span).makeCurrent()) { var activeSpan = SpanTracker.SUPPLIER.get().getActiveSpan(Thread.currentThread()); assertEquals(Optional.of(spanContext), activeSpan); @@ -61,6 +62,7 @@ void installThreadChangeDetector() { StackTraceSampler.SUPPLIER.configure(sampler); wrapper.wrapContextStorage(registry); + TraceThreadChangeDetector.SUPPLIER.get().setEnabled(true); try (var ignored = Context.current().with(span).makeCurrent()) { assertThat(sampler.isBeingSampled(Thread.currentThread())).isTrue(); } @@ -88,6 +90,8 @@ void spanTrackingRunsBeforeThreadChangeDetector() { StackTraceSampler.SUPPLIER.configure(sampler); wrapper.wrapContextStorage(registry); + SpanTracker.SUPPLIER.get().setEnabled(true); + TraceThreadChangeDetector.SUPPLIER.get().setEnabled(true); try (var ignored = Context.current().with(span).makeCurrent()) { var activeSpan = SpanTracker.SUPPLIER.get().getActiveSpan(Thread.currentThread()).orElseThrow(); diff --git a/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/DistributedProfilingSignalTest.java b/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/DistributedProfilingSignalTest.java index 1ee1c13ab..db5ee015d 100644 --- a/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/DistributedProfilingSignalTest.java +++ b/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/DistributedProfilingSignalTest.java @@ -26,10 +26,13 @@ import com.splunk.opentelemetry.profiler.snapshot.simulation.Server; import io.opentelemetry.sdk.autoconfigure.OpenTelemetrySdkExtension; 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.extension.RegisterExtension; class DistributedProfilingSignalTest { + private final SnapshotProfilingAgentListener agentListener = Snapshotting.agentListener(); private final RecordingTraceRegistry downstreamRegistry = new RecordingTraceRegistry(); private final SnapshotProfilingSdkCustomizer downstreamCustomizer = Snapshotting.customizer().with(downstreamRegistry).build(); @@ -40,8 +43,12 @@ class DistributedProfilingSignalTest { .withProperty("splunk.snapshot.profiler.enabled", "true") .withProperty("splunk.snapshot.selection.probability", "1.0") .with(downstreamCustomizer) + .with(agentListener) .build(); + private final SnapshotProfilingSpanProcessor downstreamSpanProcessor = + SnapshotProfilingSpanProcessor.SUPPLIER.get(); + @RegisterExtension public final Server downstream = Server.builder(downstreamSdk) @@ -66,12 +73,26 @@ class DistributedProfilingSignalTest { .withProperty("splunk.snapshot.profiler.enabled", "true") .withProperty("splunk.snapshot.selection.probability", "1.0") .with(upstreamCustomizer) + .with(agentListener) .build(); + private final SnapshotProfilingSpanProcessor upstreamSpanProcessor = + SnapshotProfilingSpanProcessor.SUPPLIER.get(); + @RegisterExtension public final Server upstream = Server.builder(upstreamSdk).named("upstream").performing(ExitCall.to(middle)).build(); + @BeforeEach + void enableSpanProcessors() { + Snapshotting.enable(downstreamSpanProcessor, upstreamSpanProcessor); + } + + @AfterEach + void tearDown() { + Snapshotting.resetProfiling(); + } + /** * The test below is asserting a few things are happening. First, consider the following * distributed system. diff --git a/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/GracefulShutdownTest.java b/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/GracefulShutdownTest.java index 5b80afa7a..46df1a52a 100644 --- a/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/GracefulShutdownTest.java +++ b/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/GracefulShutdownTest.java @@ -27,6 +27,7 @@ import io.opentelemetry.sdk.testing.exporter.InMemoryLogRecordExporter; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.slf4j.LoggerFactory; @@ -41,11 +42,17 @@ class GracefulShutdownTest { .withProperty("splunk.snapshot.selection.probability", "1.0") .with(Snapshotting.customizer().withRealStackTraceSampler().withRealStagingArea().build()) .with( - new StackTraceExporterActivator( + Snapshotting.agentListener( new OtelLoggerFactory( () -> logExporter, declarativeConfigProperties -> logExporter))) .build(); + @AfterEach + void tearDown() { + sdk.close(); + Snapshotting.resetProfiling(); + } + @Test void stopSnapshotProfilingExtensionWhenOpenTelemetrySdkIsShutdown(Tracer tracer) throws Exception { diff --git a/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/InMemorySpanTracker.java b/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/InMemorySpanTracker.java index 829390d45..7478596ee 100644 --- a/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/InMemorySpanTracker.java +++ b/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/InMemorySpanTracker.java @@ -23,6 +23,7 @@ class InMemorySpanTracker implements SpanTracker { private final Map activeSpans = new HashMap<>(); + private boolean enabled = true; void store(Thread thread, SpanContext spanContext) { activeSpans.put(thread.getId(), spanContext); @@ -32,4 +33,14 @@ void store(Thread thread, SpanContext spanContext) { public Optional getActiveSpan(Thread thread) { return Optional.ofNullable(activeSpans.get(thread.getId())); } + + @Override + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + @Override + public boolean isEnabled() { + return enabled; + } } diff --git a/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/LongRunningBackgroundTaskTest.java b/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/LongRunningBackgroundTaskTest.java index 791c125c7..e8ed17761 100644 --- a/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/LongRunningBackgroundTaskTest.java +++ b/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/LongRunningBackgroundTaskTest.java @@ -26,11 +26,13 @@ import java.time.Duration; import java.util.concurrent.CountDownLatch; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; class LongRunningBackgroundTaskTest { private final InMemoryStagingArea staging = new InMemoryStagingArea(); + private final SnapshotProfilingAgentListener agentListener = Snapshotting.agentListener(); private final SnapshotProfilingSdkCustomizer customizer = Snapshotting.customizer().withRealStackTraceSampler().with(staging).build(); @@ -40,6 +42,7 @@ class LongRunningBackgroundTaskTest { .withProperty("splunk.snapshot.profiler.enabled", "true") .withProperty("splunk.snapshot.selection.probability", "1.0") .with(customizer) + .with(agentListener) .build(); private CountDownLatch slowTaskLatch = new CountDownLatch(1); @@ -48,6 +51,12 @@ class LongRunningBackgroundTaskTest { public final Server server = Server.builder(sdk).named("server").performing(Background.task(slowTask())).build(); + @BeforeEach + void enableThreadChangeDetection() { + Snapshotting.customizer().withRealStackTraceSampler().with(staging); + TraceThreadChangeDetector.SUPPLIER.get().setEnabled(true); + } + private Runnable slowTask() { return () -> { try { @@ -62,6 +71,7 @@ private Runnable slowTask() { void reset() { slowTaskLatch.countDown(); slowTaskLatch = new CountDownLatch(1); + Snapshotting.resetProfiling(); } @Test diff --git a/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/MultiThreadedTraceProfilingTest.java b/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/MultiThreadedTraceProfilingTest.java index a5afc32e3..093b27252 100644 --- a/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/MultiThreadedTraceProfilingTest.java +++ b/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/MultiThreadedTraceProfilingTest.java @@ -26,11 +26,14 @@ import java.time.Duration; import java.util.UUID; import java.util.concurrent.Callable; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; public class MultiThreadedTraceProfilingTest { private final InMemoryStagingArea staging = new InMemoryStagingArea(); + private final SnapshotProfilingAgentListener agentListener = Snapshotting.agentListener(); private final SnapshotProfilingSdkCustomizer customizer = Snapshotting.customizer().withRealStackTraceSampler().with(staging).build(); @@ -40,12 +43,24 @@ public class MultiThreadedTraceProfilingTest { .withProperty("splunk.snapshot.profiler.enabled", "true") .withProperty("splunk.snapshot.selection.probability", "1.0") .with(customizer) + .with(agentListener) .build(); @RegisterExtension public final Server server = Server.builder(sdk).named("server").performing(Background.task(slowTask())).build(); + @BeforeEach + void enableThreadChangeDetection() { + Snapshotting.customizer().withRealStackTraceSampler().with(staging); + TraceThreadChangeDetector.SUPPLIER.get().setEnabled(true); + } + + @AfterEach + void tearDown() { + Snapshotting.resetProfiling(); + } + private Callable slowTask() { return () -> { Thread.sleep(250); diff --git a/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/StackTraceExporterActivatorTest.java b/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotProfilingAgentListenerTest.java similarity index 86% rename from profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/StackTraceExporterActivatorTest.java rename to profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotProfilingAgentListenerTest.java index f5d3d153b..6b5398dd9 100644 --- a/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/StackTraceExporterActivatorTest.java +++ b/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotProfilingAgentListenerTest.java @@ -20,6 +20,7 @@ import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; import com.splunk.opentelemetry.profiler.OtelLoggerFactory; import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; @@ -32,20 +33,23 @@ import java.util.Collections; import java.util.List; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -class StackTraceExporterActivatorTest { +class SnapshotProfilingAgentListenerTest { @RegisterExtension public final OpenTelemetrySdkExtension sdk = OpenTelemetrySdkExtension.configure().build(); + @BeforeEach + void setUp() { + TraceThreadChangeDetector.SUPPLIER.configure(mock(TraceThreadChangeDetector.class)); + SnapshotProfilingSpanProcessor.SUPPLIER.configure(mock(SnapshotProfilingSpanProcessor.class)); + } + @AfterEach void tearDown() { - SpanTracker.SUPPLIER.reset(); - StackTraceSampler.SUPPLIER.reset(); - StagingArea.SUPPLIER.reset(); - SnapshotProfilingConfiguration.SUPPLIER.reset(); - StackTraceExporter.SUPPLIER.reset(); + Snapshotting.resetProfiling(); } @Test @@ -58,7 +62,7 @@ void configureStackTraceExporterProvider() { .build(); SnapshotProfilingConfiguration.SUPPLIER.configure(configuration); - new StackTraceExporterActivator( + Snapshotting.agentListener( new OtelLoggerFactory(() -> logExporter, declarativeConfigProperties -> logExporter)) .afterAgent(sdk); @@ -79,7 +83,7 @@ void declarativeConfigWithoutExporterPreservesComponentLoader() { .build(); SnapshotProfilingConfiguration.SUPPLIER.configure(configuration); - new StackTraceExporterActivator().afterAgent(sdk); + new SnapshotProfilingAgentListener().afterAgent(sdk); var exporter = StackTraceExporter.SUPPLIER.get(); assertNotSame(StackTraceExporter.NOOP, exporter); @@ -93,7 +97,7 @@ void doNotConfigureStackTraceExporterProvider() { SnapshotProfilingConfiguration.builder().setEnabled(false).build(); SnapshotProfilingConfiguration.SUPPLIER.configure(configuration); - new StackTraceExporterActivator().afterAgent(sdk); + new SnapshotProfilingAgentListener().afterAgent(sdk); var exporter = StackTraceExporter.SUPPLIER.get(); assertSame(StackTraceExporter.NOOP, exporter); @@ -101,7 +105,7 @@ void doNotConfigureStackTraceExporterProvider() { private static class RecordingComponentLoader implements ComponentLoader { private final ComponentLoader delegate = - ComponentLoader.forClassLoader(StackTraceExporterActivatorTest.class.getClassLoader()); + ComponentLoader.forClassLoader(SnapshotProfilingAgentListenerTest.class.getClassLoader()); private final List> loadedSpiClasses = new ArrayList<>(); @Override diff --git a/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotProfilingConfigurationCustomizerProviderTest.java b/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotProfilingConfigurationCustomizerProviderTest.java index ecb07ab81..3408bd296 100644 --- a/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotProfilingConfigurationCustomizerProviderTest.java +++ b/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotProfilingConfigurationCustomizerProviderTest.java @@ -26,14 +26,11 @@ import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.OpenTelemetryConfigurationModel; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.SimpleSpanProcessorModel; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.SpanProcessorModel; -import java.io.IOException; -import java.nio.file.Path; import java.util.List; import java.util.Set; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import org.junit.jupiter.api.io.TempDir; class SnapshotProfilingConfigurationCustomizerProviderTest { @RegisterExtension static final AutoCleanupExtension autoCleanup = AutoCleanupExtension.create(); @@ -44,10 +41,11 @@ void resetSuppliers() { StackTraceSampler.SUPPLIER.reset(); StagingArea.SUPPLIER.reset(); SpanTracker.SUPPLIER.reset(); + TraceThreadChangeDetector.SUPPLIER.reset(); } @Test - void shouldDoNothingIfProfilerIsNotEnabled(@TempDir Path tempDir) throws IOException { + void shouldAddComponentsNeededToEnableProfilingIfProfilerIsNotEnabledInitially() { // given String yaml = """ @@ -58,9 +56,18 @@ void shouldDoNothingIfProfilerIsNotEnabled(@TempDir Path tempDir) throws IOExcep OpenTelemetryConfigurationModel model = getCustomizedModel(yaml); // then + List expectedProcessors = + List.of( + new SpanProcessorModel() + .withAdditionalProperty(SnapshotProfilingSpanProcessorComponentProvider.NAME, null), + new SpanProcessorModel() + .withAdditionalProperty(SdkShutdownHookComponentProvider.NAME, null)); + assertThat(model).isNotNull(); assertThat(model.getPropagator()).isNull(); - assertThat(model.getTracerProvider()).isNull(); + assertThat(model.getTracerProvider()).isNotNull(); + assertThat(model.getTracerProvider().getProcessors()).hasSize(2); + assertThat(model.getTracerProvider().getProcessors()).containsAll(expectedProcessors); } @Test diff --git a/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotProfilingFeatureFlagTest.java b/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotProfilingFeatureFlagTest.java index 1946fb4d5..80cbf7e20 100644 --- a/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotProfilingFeatureFlagTest.java +++ b/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotProfilingFeatureFlagTest.java @@ -21,6 +21,7 @@ import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.Context; import io.opentelemetry.sdk.autoconfigure.OpenTelemetrySdkExtension; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -29,12 +30,18 @@ class SnapshotProfilingFeatureFlagTest { private final TraceRegistry registry = new TraceRegistry(); private final SnapshotProfilingSdkCustomizer customizer = Snapshotting.customizer().with(registry).build(); + private final SnapshotProfilingAgentListener agentListener = Snapshotting.agentListener(); + + @AfterEach + void tearDown() { + Snapshotting.resetProfiling(); + } @Nested class SnapshotProfilingDisabledByDefaultTest { @RegisterExtension public final OpenTelemetrySdkExtension s = - OpenTelemetrySdkExtension.configure().with(customizer).build(); + OpenTelemetrySdkExtension.configure().with(customizer).with(agentListener).build(); @Test void snapshotProfilingIsDisabledByDefault(Tracer tracer) { @@ -53,6 +60,7 @@ class SnapshotProfilingEnabledTest { .with(customizer) .withProperty("splunk.snapshot.profiler.enabled", "true") .withProperty("splunk.snapshot.selection.probability", "1.0") + .with(agentListener) .build(); @Test @@ -71,6 +79,7 @@ class SnapshotProfilingDisabledTest { OpenTelemetrySdkExtension.configure() .with(customizer) .withProperty("splunk.snapshot.profiler.enabled", "false") + .with(agentListener) .build(); @Test diff --git a/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotProfilingLogExportingTest.java b/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotProfilingLogExportingTest.java index db71b2917..6d3900130 100644 --- a/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotProfilingLogExportingTest.java +++ b/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotProfilingLogExportingTest.java @@ -61,7 +61,7 @@ class SnapshotProfilingLogExportingTest { .withProperty("splunk.snapshot.selection.probability", "1.0") .with(customizer) .with( - new StackTraceExporterActivator( + Snapshotting.agentListener( new OtelLoggerFactory( () -> logExporter, declarativeConfigProperties -> logExporter))) .build(); diff --git a/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotProfilingSdkCustomizerBuilder.java b/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotProfilingSdkCustomizerBuilder.java index f6cab1f2c..b6a899717 100644 --- a/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotProfilingSdkCustomizerBuilder.java +++ b/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotProfilingSdkCustomizerBuilder.java @@ -20,7 +20,6 @@ class SnapshotProfilingSdkCustomizerBuilder { private TraceRegistry registry = new TraceRegistry(); - private StackTraceSampler sampler = new ObservableStackTraceSampler(); private ContextStorageWrapper contextStorageWrapper = new ResettingContextStorageWrapper(); SnapshotProfilingSdkCustomizerBuilder with(TraceRegistry registry) { @@ -28,10 +27,6 @@ SnapshotProfilingSdkCustomizerBuilder with(TraceRegistry registry) { return this; } - SnapshotProfilingSdkCustomizer real() { - return new SnapshotProfilingSdkCustomizer(); - } - SnapshotProfilingSdkCustomizerBuilder withRealStackTraceSampler() { return withRealStackTraceSampler(Duration.ofMillis(20)); } @@ -43,7 +38,6 @@ SnapshotProfilingSdkCustomizerBuilder withRealStackTraceSampler(Duration samplin SnapshotProfilingSdkCustomizerBuilder with(StackTraceSampler sampler) { StackTraceSampler.SUPPLIER.configure(sampler); - this.sampler = sampler; return this; } @@ -67,6 +61,6 @@ SnapshotProfilingSdkCustomizerBuilder with(ContextStorageWrapper contextStorageW } SnapshotProfilingSdkCustomizer build() { - return new SnapshotProfilingSdkCustomizer(registry, sampler, contextStorageWrapper); + return new SnapshotProfilingSdkCustomizer(registry, contextStorageWrapper); } } diff --git a/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotSpanAttributeTest.java b/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotSpanAttributeTest.java index 865be857f..9eef7d3b8 100644 --- a/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotSpanAttributeTest.java +++ b/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/SnapshotSpanAttributeTest.java @@ -25,11 +25,13 @@ import io.opentelemetry.context.Context; import io.opentelemetry.sdk.autoconfigure.OpenTelemetrySdkExtension; import io.opentelemetry.sdk.trace.ReadWriteSpan; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; class SnapshotSpanAttributeTest { private final TogglableTraceRegistry registry = new TogglableTraceRegistry(); + private final SnapshotProfilingAgentListener agentListener = Snapshotting.agentListener(); private final SnapshotProfilingSdkCustomizer customizer = Snapshotting.customizer().with(registry).build(); @@ -39,8 +41,14 @@ class SnapshotSpanAttributeTest { .with(customizer) .withProperty("splunk.snapshot.profiler.enabled", "true") .withProperty("splunk.snapshot.selection.probability", "1.0") + .with(agentListener) .build(); + @AfterEach + void tearDown() { + Snapshotting.resetProfiling(); + } + @Test void addSnapshotSpanAttributeToEntrySpans(Tracer tracer) { try (var ignored = Context.root().makeCurrent()) { diff --git a/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/Snapshotting.java b/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/Snapshotting.java index ef1324c17..fa0c2fe2f 100644 --- a/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/Snapshotting.java +++ b/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/Snapshotting.java @@ -16,10 +16,12 @@ package com.splunk.opentelemetry.profiler.snapshot; +import com.splunk.opentelemetry.profiler.OtelLoggerFactory; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.api.trace.TraceFlags; import io.opentelemetry.api.trace.TraceState; +import io.opentelemetry.sdk.testing.exporter.InMemoryLogRecordExporter; import io.opentelemetry.sdk.trace.IdGenerator; import java.time.Duration; import java.time.Instant; @@ -32,6 +34,48 @@ static SnapshotProfilingSdkCustomizerBuilder customizer() { return new SnapshotProfilingSdkCustomizerBuilder(); } + static SnapshotProfilingAgentListener agentListener(OtelLoggerFactory otelLoggerFactory) { + return new SnapshotProfilingAgentListener( + sdk -> + new SnapshotProfilingSupervisor( + SnapshotProfilingConfiguration.SUPPLIER, + SpanTracker.SUPPLIER, + TraceThreadChangeDetector.SUPPLIER, + SnapshotProfilingSpanProcessor.SUPPLIER, + sdk, + otelLoggerFactory)); + } + + static SnapshotProfilingAgentListener agentListener() { + var logExporter = InMemoryLogRecordExporter.create(); + return agentListener( + new OtelLoggerFactory(() -> logExporter, declarativeConfigProperties -> logExporter)); + } + + static void enable(SnapshotProfilingSpanProcessor... spanProcessors) { + for (SnapshotProfilingSpanProcessor spanProcessor : spanProcessors) { + spanProcessor.setEnabled(true); + } + } + + static void resetProfiling() { + SnapshotProfilingConfiguration.SUPPLIER.reset(); + + if (SnapshotProfilingSupervisor.SUPPLIER.isConfigured()) { + SnapshotProfilingSupervisor.SUPPLIER.get().stopProfiling(); + SnapshotProfilingSupervisor.SUPPLIER.reset(); + } + + StackTraceSampler.SUPPLIER.reset(); + StagingArea.SUPPLIER.reset(); + StackTraceExporter.SUPPLIER.reset(); + + SpanTracker.SUPPLIER.reset(); + TraceThreadChangeDetector.SUPPLIER.reset(); + + SnapshotProfilingSpanProcessor.SUPPLIER.reset(); + } + static StackTraceBuilder stackTrace() { var threadId = RANDOM.nextLong(10_000); return new StackTraceBuilder() diff --git a/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/SpanSamplingTest.java b/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/SpanSamplingTest.java index ff5a28281..a2be47794 100644 --- a/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/SpanSamplingTest.java +++ b/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/SpanSamplingTest.java @@ -22,15 +22,22 @@ import io.opentelemetry.context.Context; import io.opentelemetry.sdk.autoconfigure.OpenTelemetrySdkExtension; import io.opentelemetry.sdk.trace.samplers.Sampler; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; class SpanSamplingTest { private final TraceRegistry registry = new TraceRegistry(); + private final SnapshotProfilingAgentListener agentListener = Snapshotting.agentListener(); private final SnapshotProfilingSdkCustomizer customizer = Snapshotting.customizer().with(registry).build(); + @AfterEach + void tearDown() { + Snapshotting.resetProfiling(); + } + @Nested class SpanSamplingDisabled { @RegisterExtension @@ -40,6 +47,7 @@ class SpanSamplingDisabled { .withProperty("splunk.snapshot.selection.probability", "1.0") .withSampler(Sampler.alwaysOff()) .with(customizer) + .with(agentListener) .build(); @Test @@ -60,6 +68,7 @@ class SpanSamplingEnabled { .withProperty("splunk.snapshot.selection.probability", "1.0") .withSampler(Sampler.alwaysOn()) .with(customizer) + .with(agentListener) .build(); @Test diff --git a/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/TraceProfilingTest.java b/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/TraceProfilingTest.java index 4022c6fe2..4a9cbf444 100644 --- a/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/TraceProfilingTest.java +++ b/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/TraceProfilingTest.java @@ -24,12 +24,15 @@ import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.Context; import io.opentelemetry.sdk.autoconfigure.OpenTelemetrySdkExtension; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; class TraceProfilingTest { private final TogglableTraceRegistry registry = new TogglableTraceRegistry(); private final ObservableStackTraceSampler sampler = new ObservableStackTraceSampler(); + private final SnapshotProfilingAgentListener agentListener = Snapshotting.agentListener(); private final SnapshotProfilingSdkCustomizer customizer = Snapshotting.customizer().with(registry).with(sampler).build(); @@ -39,8 +42,20 @@ class TraceProfilingTest { .withProperty("splunk.snapshot.profiler.enabled", "true") .withProperty("splunk.snapshot.selection.probability", "1.0") .with(customizer) + .with(agentListener) .build(); + @BeforeEach + void enableThreadChangeDetection() { + Snapshotting.customizer().with(sampler); + TraceThreadChangeDetector.SUPPLIER.get().setEnabled(true); + } + + @AfterEach + void tearDown() { + Snapshotting.resetProfiling(); + } + @Test void startTraceProfilingWhenRootSpanContextBegins(Tracer tracer) { try (var ignored = Context.root().makeCurrent()) { diff --git a/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/TraceRegistrationTest.java b/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/TraceRegistrationTest.java index f2ca277f7..74051dc43 100644 --- a/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/TraceRegistrationTest.java +++ b/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/TraceRegistrationTest.java @@ -22,11 +22,13 @@ import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.Context; import io.opentelemetry.sdk.autoconfigure.OpenTelemetrySdkExtension; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; class TraceRegistrationTest { private final TraceRegistry registry = new TraceRegistry(); + private final SnapshotProfilingAgentListener agentListener = Snapshotting.agentListener(); private final SnapshotProfilingSdkCustomizer customizer = Snapshotting.customizer().with(registry).build(); @@ -36,8 +38,14 @@ class TraceRegistrationTest { .withProperty("splunk.snapshot.profiler.enabled", "true") .withProperty("splunk.snapshot.selection.probability", "1.0") .with(customizer) + .with(agentListener) .build(); + @AfterEach + void tearDown() { + Snapshotting.resetProfiling(); + } + @Test void registerTraceForProfilingWhenRootSpanStarts(Tracer tracer) { try (var ignored = Context.current().makeCurrent()) { diff --git a/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/TraceThreadChangeDetectorTest.java b/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/TraceThreadChangeDetectorTest.java index 1ccbe582e..958aa3396 100644 --- a/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/TraceThreadChangeDetectorTest.java +++ b/profiler/src/test/java/com/splunk/opentelemetry/profiler/snapshot/TraceThreadChangeDetectorTest.java @@ -26,6 +26,7 @@ import io.opentelemetry.context.Scope; import java.util.concurrent.Callable; import java.util.concurrent.Executors; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; class TraceThreadChangeDetectorTest { @@ -35,6 +36,11 @@ class TraceThreadChangeDetectorTest { private final TraceThreadChangeDetector detector = new TraceThreadChangeDetector(storage, registry, () -> sampler); + @BeforeEach + void setUp() { + detector.setEnabled(true); + } + @Test void currentContextComesFromOpenTelemetryContextStorage() { var context = Context.root().with(ContextKey.named("test-key"), "value"); @@ -110,6 +116,25 @@ void doNotStartSamplingThreadWhenSpanIsNotSampled() throws Exception { } } + @Test + void doNotPerformSamplingWhenNotEnabled() throws Exception { + var spanContext = Snapshotting.spanContext().build(); + registry.register(spanContext); + sampler.start(Thread.currentThread(), spanContext); + + var span = Span.wrap(spanContext); + var context = Context.root().with(span); + + var executor = Executors.newSingleThreadExecutor(); + detector.setEnabled(false); + + try (var ts = executor.submit(captureThread(() -> detector.attach(context))).get()) { + assertThat(sampler.isBeingSampled(ts.thread)).isFalse(); + } finally { + executor.shutdownNow(); + } + } + private Callable captureThread(Callable callable) { return () -> new ThreadScope(Thread.currentThread(), callable.call()); }