From f6abc515194a16a6b00d6c98756eae8fcf7b4cd3 Mon Sep 17 00:00:00 2001 From: robsunday Date: Wed, 24 Jun 2026 13:35:30 +0200 Subject: [PATCH 01/13] Profiler configuration refactoring --- .../jvmmetrics/JvmMetricsInstaller.java | 13 +- .../jvmmetrics/JvmMetricsInstallerTest.java | 10 + ...DeclarativeEffectiveConfigFileFactory.java | 3 +- .../EnvVarsEffectiveConfigFileFactory.java | 5 +- .../opamp/RemoteConfigProcessor.java | 7 +- ...arativeEffectiveConfigFileFactoryTest.java | 8 +- .../profiler/JfrAgentListener.java | 25 +- .../profiler/LogExporterBuilder.java | 16 +- .../profiler/OtelLoggerFactory.java | 13 +- .../PeriodicRecordingFlusherBuilder.java | 9 +- .../profiler/ProfilerConfiguration.java | 279 ++++++++++++++++-- ... => ProfilerConfigurationInitializer.java} | 40 ++- .../ProfilerDeclarativeConfiguration.java | 171 ----------- ...ofilerDeclarativeConfigurationFactory.java | 83 ++++++ .../ProfilerEnvVarsConfiguration.java | 215 -------------- .../ProfilerEnvVarsConfigurationFactory.java | 138 +++++++++ .../profiler/ProfilingSupervisor.java | 19 +- .../profiler/JfrAgentListenerTest.java | 11 +- .../profiler/LogExporterBuilderTest.java | 18 +- .../profiler/OtelLoggerFactoryTest.java | 2 +- .../PeriodicRecordingFlusherBuilderTest.java | 53 ++-- .../profiler/ProfilerConfigurationTest.java | 73 +++++ ...rDeclarativeConfigurationFactoryTest.java} | 34 ++- ...ofilerEnvVarsConfigurationFactoryTest.java | 182 ++++++++++++ .../ProfilerEnvVarsConfigurationTest.java | 180 ----------- .../profiler/ProfilingSupervisorTest.java | 30 +- .../profiler/TestProfilingConfig.java | 130 -------- .../ConcurrentServiceEntrySamplingTest.java | 2 +- .../snapshot/GracefulShutdownTest.java | 2 +- .../SnapshotProfilingLogExportingTest.java | 2 +- 30 files changed, 918 insertions(+), 855 deletions(-) rename profiler/src/main/java/com/splunk/opentelemetry/profiler/{ProfilerDeclarativeConfigurationInitializer.java => ProfilerConfigurationInitializer.java} (56%) delete mode 100644 profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilerDeclarativeConfiguration.java create mode 100644 profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilerDeclarativeConfigurationFactory.java delete mode 100644 profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilerEnvVarsConfiguration.java create mode 100644 profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilerEnvVarsConfigurationFactory.java create mode 100644 profiler/src/test/java/com/splunk/opentelemetry/profiler/ProfilerConfigurationTest.java rename profiler/src/test/java/com/splunk/opentelemetry/profiler/{ProfilerDeclarativeConfigurationTest.java => ProfilerDeclarativeConfigurationFactoryTest.java} (71%) create mode 100644 profiler/src/test/java/com/splunk/opentelemetry/profiler/ProfilerEnvVarsConfigurationFactoryTest.java delete mode 100644 profiler/src/test/java/com/splunk/opentelemetry/profiler/ProfilerEnvVarsConfigurationTest.java delete mode 100644 profiler/src/test/java/com/splunk/opentelemetry/profiler/TestProfilingConfig.java diff --git a/instrumentation/jvm-metrics/src/main/java/com/splunk/opentelemetry/instrumentation/jvmmetrics/JvmMetricsInstaller.java b/instrumentation/jvm-metrics/src/main/java/com/splunk/opentelemetry/instrumentation/jvmmetrics/JvmMetricsInstaller.java index fed727687..c0e0980ec 100644 --- a/instrumentation/jvm-metrics/src/main/java/com/splunk/opentelemetry/instrumentation/jvmmetrics/JvmMetricsInstaller.java +++ b/instrumentation/jvm-metrics/src/main/java/com/splunk/opentelemetry/instrumentation/jvmmetrics/JvmMetricsInstaller.java @@ -22,8 +22,6 @@ import com.splunk.opentelemetry.instrumentation.jvmmetrics.otel.OtelAllocatedMemoryMetrics; import com.splunk.opentelemetry.instrumentation.jvmmetrics.otel.OtelGcMemoryMetrics; import com.splunk.opentelemetry.profiler.ProfilerConfiguration; -import com.splunk.opentelemetry.profiler.ProfilerDeclarativeConfiguration; -import com.splunk.opentelemetry.profiler.ProfilerEnvVarsConfiguration; import io.opentelemetry.javaagent.extension.AgentListener; import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; @@ -33,7 +31,7 @@ public class JvmMetricsInstaller implements AgentListener { @Override public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk) { ConfigProperties config = getConfig(autoConfiguredOpenTelemetrySdk); - boolean metricsEnabled = isProfilerMemoryEnabled(config); + boolean metricsEnabled = isProfilerMemoryEnabled(); if (!config.getBoolean("otel.instrumentation.jvm-metrics-splunk.enabled", metricsEnabled)) { return; @@ -43,12 +41,7 @@ public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetr new OtelGcMemoryMetrics().install(); } - private boolean isProfilerMemoryEnabled(ConfigProperties config) { - ProfilerConfiguration profilerConfiguration = - ProfilerDeclarativeConfiguration.SUPPLIER.isConfigured() - ? ProfilerDeclarativeConfiguration.SUPPLIER.get() - : new ProfilerEnvVarsConfiguration(config); - - return profilerConfiguration.getMemoryEnabled(); + private boolean isProfilerMemoryEnabled() { + return ProfilerConfiguration.SUPPLIER.get().getMemoryEnabled(); } } diff --git a/instrumentation/jvm-metrics/src/test/java/com/splunk/opentelemetry/instrumentation/jvmmetrics/JvmMetricsInstallerTest.java b/instrumentation/jvm-metrics/src/test/java/com/splunk/opentelemetry/instrumentation/jvmmetrics/JvmMetricsInstallerTest.java index 7aa2c2e95..81c48d29d 100644 --- a/instrumentation/jvm-metrics/src/test/java/com/splunk/opentelemetry/instrumentation/jvmmetrics/JvmMetricsInstallerTest.java +++ b/instrumentation/jvm-metrics/src/test/java/com/splunk/opentelemetry/instrumentation/jvmmetrics/JvmMetricsInstallerTest.java @@ -23,6 +23,8 @@ import static org.mockito.Mockito.verify; import com.splunk.opentelemetry.instrumentation.jvmmetrics.otel.OtelAllocatedMemoryMetrics; +import com.splunk.opentelemetry.profiler.ProfilerConfiguration; +import com.splunk.opentelemetry.profiler.ProfilerEnvVarsConfigurationFactory; import com.splunk.opentelemetry.testing.declarativeconfig.DeclarativeConfigTestUtil; import io.opentelemetry.instrumentation.config.bridge.DeclarativeConfigPropertiesBridgeBuilder; import io.opentelemetry.sdk.autoconfigure.AutoConfigureUtil; @@ -30,12 +32,18 @@ import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; import java.util.Map; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.mockito.MockedConstruction; import org.mockito.MockedStatic; class JvmMetricsInstallerTest { + @AfterEach + void tearDown() { + ProfilerConfiguration.SUPPLIER.reset(); + } + @Test void shouldInstallJvmMetrics_declarativeConfig() { // given @@ -53,6 +61,7 @@ void shouldInstallJvmMetrics_declarativeConfig() { new DeclarativeConfigPropertiesBridgeBuilder() .build( AutoConfigureUtil.getInstrumentationConfig(DeclarativeConfigTestUtil.parse(yaml))); + ProfilerConfiguration.SUPPLIER.configure(ProfilerEnvVarsConfigurationFactory.create(config)); try (MockedStatic autoConfigureUtil = mockStatic(AutoConfigureUtil.class); MockedConstruction allocatedMetrics = @@ -78,6 +87,7 @@ void shouldInstallJvmMetrics_envVarsConfig() { ConfigProperties config = DefaultConfigProperties.createFromMap( Map.of("otel.instrumentation.jvm-metrics-splunk.enabled", "true")); + ProfilerConfiguration.SUPPLIER.configure(ProfilerEnvVarsConfigurationFactory.create(config)); try (MockedStatic autoConfigureUtil = mockStatic(AutoConfigureUtil.class); MockedConstruction allocatedMetrics = diff --git a/opamp/src/main/java/com/splunk/opentelemetry/opamp/DeclarativeEffectiveConfigFileFactory.java b/opamp/src/main/java/com/splunk/opentelemetry/opamp/DeclarativeEffectiveConfigFileFactory.java index a3410f895..7ab53d8af 100644 --- a/opamp/src/main/java/com/splunk/opentelemetry/opamp/DeclarativeEffectiveConfigFileFactory.java +++ b/opamp/src/main/java/com/splunk/opentelemetry/opamp/DeclarativeEffectiveConfigFileFactory.java @@ -18,7 +18,6 @@ import com.google.common.annotations.VisibleForTesting; import com.splunk.opentelemetry.profiler.ProfilerConfiguration; -import com.splunk.opentelemetry.profiler.ProfilerDeclarativeConfiguration; import com.splunk.opentelemetry.profiler.snapshot.SnapshotProfilingConfiguration; import com.splunk.opentelemetry.profiler.snapshot.SnapshotProfilingDeclarativeConfiguration; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.LogRecordExporterModel; @@ -69,7 +68,7 @@ public String createEffectiveConfigContent() { if (model == null) { return ""; } - ProfilerConfiguration profilerConfiguration = ProfilerDeclarativeConfiguration.SUPPLIER.get(); + ProfilerConfiguration profilerConfiguration = ProfilerConfiguration.SUPPLIER.get(); SnapshotProfilingConfiguration snapshotConfiguration = SnapshotProfilingDeclarativeConfiguration.SUPPLIER.get(); diff --git a/opamp/src/main/java/com/splunk/opentelemetry/opamp/EnvVarsEffectiveConfigFileFactory.java b/opamp/src/main/java/com/splunk/opentelemetry/opamp/EnvVarsEffectiveConfigFileFactory.java index 62186f822..021a5ae1a 100644 --- a/opamp/src/main/java/com/splunk/opentelemetry/opamp/EnvVarsEffectiveConfigFileFactory.java +++ b/opamp/src/main/java/com/splunk/opentelemetry/opamp/EnvVarsEffectiveConfigFileFactory.java @@ -17,7 +17,7 @@ package com.splunk.opentelemetry.opamp; import com.splunk.opentelemetry.profiler.ProfilerConfiguration; -import com.splunk.opentelemetry.profiler.ProfilerEnvVarsConfiguration; +import com.splunk.opentelemetry.profiler.ProfilerEnvVarsConfigurationFactory; import com.splunk.opentelemetry.profiler.snapshot.SnapshotProfilingConfiguration; import com.splunk.opentelemetry.profiler.snapshot.SnapshotProfilingEnvVarsConfiguration; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; @@ -49,7 +49,8 @@ public String createEffectiveConfigContent() { } private void addSplunkEnvVars(EffectiveConfigBuilder builder) { - ProfilerConfiguration profilerConfiguration = new ProfilerEnvVarsConfiguration(config); + ProfilerConfiguration profilerConfiguration = + ProfilerEnvVarsConfigurationFactory.create(config); SnapshotProfilingConfiguration snapshotConfiguration = new SnapshotProfilingEnvVarsConfiguration(config); diff --git a/opamp/src/main/java/com/splunk/opentelemetry/opamp/RemoteConfigProcessor.java b/opamp/src/main/java/com/splunk/opentelemetry/opamp/RemoteConfigProcessor.java index d25adfff0..92881a41e 100644 --- a/opamp/src/main/java/com/splunk/opentelemetry/opamp/RemoteConfigProcessor.java +++ b/opamp/src/main/java/com/splunk/opentelemetry/opamp/RemoteConfigProcessor.java @@ -19,7 +19,8 @@ import static io.opentelemetry.api.incubator.config.DeclarativeConfigProperties.empty; import com.google.common.annotations.VisibleForTesting; -import com.splunk.opentelemetry.profiler.ProfilerDeclarativeConfiguration; +import com.splunk.opentelemetry.profiler.ProfilerConfiguration; +import com.splunk.opentelemetry.profiler.ProfilerDeclarativeConfigurationFactory; import com.splunk.opentelemetry.profiler.ProfilingSupervisor; import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; import io.opentelemetry.opamp.client.OpampClient; @@ -69,8 +70,8 @@ public void applyConfig(AgentRemoteConfig remoteConfig, OpampClient opampClient) if (splunkDistributionConfigProperties.getPropertyKeys().contains(PROFILING_NODE_NAME)) { DeclarativeConfigProperties profilingConfigProperties = splunkDistributionConfigProperties.getStructured(PROFILING_NODE_NAME, empty()); - ProfilerDeclarativeConfiguration profilingConfig = - new ProfilerDeclarativeConfiguration(profilingConfigProperties); + ProfilerConfiguration profilingConfig = + ProfilerDeclarativeConfigurationFactory.create(profilingConfigProperties); // TODO: should be merged with current profiling config. Probably we will need profiler // configuration refactoring and some listeners implemented for profiler configuration // changes. For POC use this temporary solution diff --git a/opamp/src/test/java/com/splunk/opentelemetry/opamp/DeclarativeEffectiveConfigFileFactoryTest.java b/opamp/src/test/java/com/splunk/opentelemetry/opamp/DeclarativeEffectiveConfigFileFactoryTest.java index 0bbf444ff..6e0c44e88 100644 --- a/opamp/src/test/java/com/splunk/opentelemetry/opamp/DeclarativeEffectiveConfigFileFactoryTest.java +++ b/opamp/src/test/java/com/splunk/opentelemetry/opamp/DeclarativeEffectiveConfigFileFactoryTest.java @@ -23,7 +23,7 @@ import static org.mockito.Mockito.when; import com.splunk.opentelemetry.profiler.ProfilerConfiguration; -import com.splunk.opentelemetry.profiler.ProfilerDeclarativeConfiguration; +import com.splunk.opentelemetry.profiler.ProfilerDeclarativeConfigurationFactory; import com.splunk.opentelemetry.profiler.snapshot.SnapshotProfilingConfiguration; import com.splunk.opentelemetry.profiler.snapshot.SnapshotProfilingDeclarativeConfiguration; import com.splunk.opentelemetry.testing.declarativeconfig.DeclarativeConfigTestUtil; @@ -56,7 +56,7 @@ class DeclarativeEffectiveConfigFileFactoryTest { @AfterEach void afterEach() { DeclarativeConfigurationInterceptor.reset(); - ProfilerDeclarativeConfiguration.SUPPLIER.reset(); + ProfilerConfiguration.SUPPLIER.reset(); SnapshotProfilingDeclarativeConfiguration.SUPPLIER.reset(); } @@ -396,8 +396,8 @@ public static void main(String[] args) throws Exception { DeclarativeConfigProperties profilingConfig = getDistributionConfig(model).getStructured("profiling", empty()); - ProfilerDeclarativeConfiguration.SUPPLIER.configure( - new ProfilerDeclarativeConfiguration(profilingConfig)); + ProfilerConfiguration.SUPPLIER.configure( + ProfilerDeclarativeConfigurationFactory.create(profilingConfig)); SnapshotProfilingDeclarativeConfiguration.SUPPLIER.configure( new SnapshotProfilingDeclarativeConfiguration(profilingConfig)); diff --git a/profiler/src/main/java/com/splunk/opentelemetry/profiler/JfrAgentListener.java b/profiler/src/main/java/com/splunk/opentelemetry/profiler/JfrAgentListener.java index fc978ef53..cf8ea81e7 100644 --- a/profiler/src/main/java/com/splunk/opentelemetry/profiler/JfrAgentListener.java +++ b/profiler/src/main/java/com/splunk/opentelemetry/profiler/JfrAgentListener.java @@ -19,9 +19,7 @@ import com.google.auto.service.AutoService; import com.google.common.annotations.VisibleForTesting; 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 java.util.logging.Logger; @AutoService(AgentListener.class) @@ -40,12 +38,14 @@ public JfrAgentListener() { @Override public void afterAgent(AutoConfiguredOpenTelemetrySdk sdk) { - ProfilingSupervisor.setupJfrContextStorage(); + if (jfr.isAvailable()) { + ProfilingSupervisor.setupJfrContextStorage(); + } - ProfilerConfiguration config = getProfilerConfiguration(sdk); // Always start the supervisor, so it can start profiling later elsewhere. - ProfilingSupervisor supervisor = makeProfilingSupervisor(sdk, config); + ProfilingSupervisor supervisor = makeProfilingSupervisor(sdk); + ProfilerConfiguration config = ProfilerConfiguration.SUPPLIER.get(); if (notClearForTakeoff(config)) { return; } @@ -60,19 +60,8 @@ public int order() { } // Exists for testing - ProfilingSupervisor makeProfilingSupervisor( - AutoConfiguredOpenTelemetrySdk sdk, ProfilerConfiguration config) { - return ProfilingSupervisor.createAndStart(sdk, config); - } - - private static ProfilerConfiguration getProfilerConfiguration( - AutoConfiguredOpenTelemetrySdk sdk) { - if (ProfilerDeclarativeConfiguration.SUPPLIER.isConfigured()) { - return ProfilerDeclarativeConfiguration.SUPPLIER.get(); - } else { - ConfigProperties configProperties = AutoConfigureUtil.getConfig(sdk); - return new ProfilerEnvVarsConfiguration(configProperties); - } + ProfilingSupervisor makeProfilingSupervisor(AutoConfiguredOpenTelemetrySdk sdk) { + return ProfilingSupervisor.createAndStart(sdk); } private boolean notClearForTakeoff(ProfilerConfiguration config) { diff --git a/profiler/src/main/java/com/splunk/opentelemetry/profiler/LogExporterBuilder.java b/profiler/src/main/java/com/splunk/opentelemetry/profiler/LogExporterBuilder.java index ea7c781c6..ec4e5f364 100644 --- a/profiler/src/main/java/com/splunk/opentelemetry/profiler/LogExporterBuilder.java +++ b/profiler/src/main/java/com/splunk/opentelemetry/profiler/LogExporterBuilder.java @@ -44,7 +44,8 @@ class LogExporterBuilder { static final String EXTRA_CONTENT_TYPE = "Extra-Content-Type"; static final String STACKTRACES_HEADER_VALUE = "otel-profiling-stacktraces"; - static LogRecordExporter fromConfig(DeclarativeConfigProperties exporterConfigProperties) { + static LogRecordExporter fromDeclarativeConfig( + DeclarativeConfigProperties exporterConfigProperties) { if (exporterConfigProperties != null) { Set propertyKeys = exporterConfigProperties.getPropertyKeys(); @@ -68,11 +69,8 @@ static LogRecordExporter fromConfig(DeclarativeConfigProperties exporterConfigPr throw new ConfigurationException("Profiler exporter configuration is invalid"); } - static LogRecordExporter fromConfig(ConfigProperties config) { - return fromConfig(new ProfilerEnvVarsConfiguration(config)); - } - - static LogRecordExporter fromConfig(ProfilerEnvVarsConfiguration config) { + static LogRecordExporter fromEnvironmentConfig() { + ProfilerConfiguration config = ProfilerConfiguration.SUPPLIER.get(); String protocol = config.getOtlpProtocol(); if ("http/protobuf".equals(protocol)) { return buildHttpExporter(config, OtlpHttpLogRecordExporter::builder); @@ -84,7 +82,7 @@ static LogRecordExporter fromConfig(ProfilerEnvVarsConfiguration config) { @VisibleForTesting static LogRecordExporter buildGrpcExporter( - ProfilerEnvVarsConfiguration config, Supplier makeBuilder) { + ProfilerConfiguration config, Supplier makeBuilder) { OtlpGrpcLogRecordExporterBuilder builder = makeBuilder.get(); String ingestUrl = config.getIngestUrl(); builder.setEndpoint(ingestUrl); @@ -94,13 +92,13 @@ static LogRecordExporter buildGrpcExporter( @VisibleForTesting static LogRecordExporter buildHttpExporter( - ProfilerEnvVarsConfiguration config, Supplier makeBuilder) { + ProfilerConfiguration config, Supplier makeBuilder) { OtlpHttpLogRecordExporterBuilder builder = makeBuilder.get(); String ingestUrl = config.getIngestUrl(); OtlpConfigUtil.configureOtlpExporterBuilder( DATA_TYPE_LOGS, - config.getConfigProperties(), + (ConfigProperties) config.getConfigProperties(), builder::setComponentLoader, builder::setEndpoint, builder::addHeader, diff --git a/profiler/src/main/java/com/splunk/opentelemetry/profiler/OtelLoggerFactory.java b/profiler/src/main/java/com/splunk/opentelemetry/profiler/OtelLoggerFactory.java index f164fc902..2bddc274a 100644 --- a/profiler/src/main/java/com/splunk/opentelemetry/profiler/OtelLoggerFactory.java +++ b/profiler/src/main/java/com/splunk/opentelemetry/profiler/OtelLoggerFactory.java @@ -26,26 +26,27 @@ import io.opentelemetry.sdk.logs.export.SimpleLogRecordProcessor; import io.opentelemetry.sdk.resources.Resource; import java.util.function.Function; +import java.util.function.Supplier; public class OtelLoggerFactory { - private final Function logRecordExporter; + private final Supplier logRecordExporter; private final Function declarativeLogRecordExporter; public OtelLoggerFactory() { - this(LogExporterBuilder::fromConfig, LogExporterBuilder::fromConfig); + this(LogExporterBuilder::fromEnvironmentConfig, LogExporterBuilder::fromDeclarativeConfig); } @VisibleForTesting public OtelLoggerFactory( - Function logRecordExporter, + Supplier logRecordExporter, Function declarativeLogRecordExporter) { this.logRecordExporter = logRecordExporter; this.declarativeLogRecordExporter = declarativeLogRecordExporter; } public Logger build(ConfigProperties properties, Resource resource) { - LogRecordExporter exporter = createLogRecordExporter(properties); + LogRecordExporter exporter = createLogRecordExporter(); LogRecordProcessor processor = SimpleLogRecordProcessor.create(exporter); return buildOtelLogger(processor, resource); } @@ -56,8 +57,8 @@ public Logger build(DeclarativeConfigProperties properties, Resource resource) { return buildOtelLogger(processor, resource); } - private LogRecordExporter createLogRecordExporter(ConfigProperties properties) { - return logRecordExporter.apply(properties); + private LogRecordExporter createLogRecordExporter() { + return logRecordExporter.get(); } private LogRecordExporter createLogRecordExporter(DeclarativeConfigProperties properties) { diff --git a/profiler/src/main/java/com/splunk/opentelemetry/profiler/PeriodicRecordingFlusherBuilder.java b/profiler/src/main/java/com/splunk/opentelemetry/profiler/PeriodicRecordingFlusherBuilder.java index 00efd8a62..54297452a 100644 --- a/profiler/src/main/java/com/splunk/opentelemetry/profiler/PeriodicRecordingFlusherBuilder.java +++ b/profiler/src/main/java/com/splunk/opentelemetry/profiler/PeriodicRecordingFlusherBuilder.java @@ -25,7 +25,6 @@ import com.splunk.opentelemetry.profiler.exporter.CpuEventExporter; import com.splunk.opentelemetry.profiler.exporter.PprofCpuEventExporter; import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.logs.LogRecordProcessor; import io.opentelemetry.sdk.logs.SdkLoggerProvider; import io.opentelemetry.sdk.logs.export.LogRecordExporter; @@ -162,13 +161,9 @@ private static LogRecordExporter createLogRecordExporter(Object configProperties if (configProperties instanceof DeclarativeConfigProperties) { DeclarativeConfigProperties exporterConfig = ((DeclarativeConfigProperties) configProperties).getStructured("exporter", empty()); - return LogExporterBuilder.fromConfig(exporterConfig); + return LogExporterBuilder.fromDeclarativeConfig(exporterConfig); } - if (configProperties instanceof ConfigProperties) { - return LogExporterBuilder.fromConfig((ConfigProperties) configProperties); - } - throw new IllegalArgumentException( - "Unsupported config properties type: " + configProperties.getClass().getName()); + return LogExporterBuilder.fromEnvironmentConfig(); } private boolean checkOutputDir(Path outputDir) { diff --git a/profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilerConfiguration.java b/profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilerConfiguration.java index 6e2aaf69f..32a0f152c 100644 --- a/profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilerConfiguration.java +++ b/profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilerConfiguration.java @@ -16,50 +16,289 @@ package com.splunk.opentelemetry.profiler; +import com.splunk.opentelemetry.profiler.util.OptionalConfigurableSupplier; import java.time.Duration; +import java.util.Objects; +import java.util.logging.Logger; +import javax.annotation.Nullable; -public interface ProfilerConfiguration { - boolean HAS_OBJECT_ALLOCATION_SAMPLE_EVENT = getJavaVersion() >= 16; +public class ProfilerConfiguration { + public static final OptionalConfigurableSupplier SUPPLIER = + new OptionalConfigurableSupplier<>(); - boolean isEnabled(); + public static final boolean HAS_OBJECT_ALLOCATION_SAMPLE_EVENT = getJavaVersion() >= 16; - void log(); + private static final Logger logger = Logger.getLogger(ProfilerConfiguration.class.getName()); + private static final String DEFAULT_PROFILER_DIRECTORY = System.getProperty("java.io.tmpdir"); + private static final Duration DEFAULT_RECORDING_DURATION = Duration.ofSeconds(20); + private static final Duration DEFAULT_CALL_STACK_INTERVAL = Duration.ofSeconds(10); - String getIngestUrl(); + private final boolean enabled; + @Nullable private final String ingestUrl; + @Nullable private final String otlpProtocol; + private final boolean memoryEnabled; + private final boolean memoryEventRateLimitEnabled; + private final String memoryEventRate; + private final boolean useAllocationSampleEvent; + private final Duration callStackInterval; + private final boolean includeAgentInternalStacks; + private final boolean includeJvmInternalStacks; + private final boolean tracingStacksOnly; + private final int stackDepth; + private final boolean keepFiles; + private final String profilerDirectory; + private final Duration recordingDuration; + @Nullable private final Object configProperties; - String getOtlpProtocol(); + private ProfilerConfiguration(Builder builder) { + enabled = builder.enabled; + ingestUrl = builder.ingestUrl; + otlpProtocol = builder.otlpProtocol; + memoryEnabled = builder.memoryEnabled; + memoryEventRateLimitEnabled = builder.memoryEventRateLimitEnabled; + memoryEventRate = builder.memoryEventRate; + useAllocationSampleEvent = builder.useAllocationSampleEvent; + callStackInterval = builder.callStackInterval; + includeAgentInternalStacks = builder.includeAgentInternalStacks; + includeJvmInternalStacks = builder.includeJvmInternalStacks; + tracingStacksOnly = builder.tracingStacksOnly; + stackDepth = builder.stackDepth; + keepFiles = builder.keepFiles; + profilerDirectory = builder.profilerDirectory; + recordingDuration = builder.recordingDuration; + configProperties = builder.configProperties; + } + + public static Builder builder() { + return new Builder(); + } + + public Builder newBuilder() { + return new Builder(this); + } - boolean getMemoryEnabled(); + public boolean isEnabled() { + return enabled; + } + + public void log() { + logger.info("-----------------------"); + logger.info("Profiler configuration:"); + log("Enabled", isEnabled()); + log("ProfilerDirectory", getProfilerDirectory()); + log("RecordingDuration", getRecordingDuration().toMillis() + "ms"); + log("KeepFiles", getKeepFiles()); + log("OtlpProtocol", getOtlpProtocol()); + log("IngestUrl", getIngestUrl()); + log("MemoryEnabled", getMemoryEnabled()); + if (getMemoryEventRateLimitEnabled()) { + log("MemoryEventRate", getMemoryEventRate()); + } + log("UseAllocationSampleEvent", getUseAllocationSampleEvent()); + log("CallStackInterval", getCallStackInterval().toMillis() + "ms"); + log("IncludeAgentInternalStacks", getIncludeAgentInternalStacks()); + log("IncludeJvmInternalStacks", getIncludeJvmInternalStacks()); + log("TracingStacksOnly", getTracingStacksOnly()); + log("StackDepth", getStackDepth()); + logger.info("-----------------------"); + } + + private static void log(String key, @Nullable Object value) { + logger.info(String.format("%30s : %s", key, value)); + } + + @Nullable + public String getIngestUrl() { + return ingestUrl; + } + + @Nullable + public String getOtlpProtocol() { + return otlpProtocol; + } + + public boolean getMemoryEnabled() { + return memoryEnabled; + } - boolean getMemoryEventRateLimitEnabled(); + public boolean getMemoryEventRateLimitEnabled() { + return memoryEventRateLimitEnabled; + } - String getMemoryEventRate(); + public String getMemoryEventRate() { + return memoryEventRate; + } - boolean getUseAllocationSampleEvent(); + public boolean getUseAllocationSampleEvent() { + return useAllocationSampleEvent; + } - Duration getCallStackInterval(); + public Duration getCallStackInterval() { + return callStackInterval; + } - boolean getIncludeAgentInternalStacks(); + public boolean getIncludeAgentInternalStacks() { + return includeAgentInternalStacks; + } - boolean getIncludeJvmInternalStacks(); + public boolean getIncludeJvmInternalStacks() { + return includeJvmInternalStacks; + } - boolean getTracingStacksOnly(); + public boolean getTracingStacksOnly() { + return tracingStacksOnly; + } - int getStackDepth(); + public int getStackDepth() { + return stackDepth; + } - boolean getKeepFiles(); + public boolean getKeepFiles() { + return keepFiles; + } - String getProfilerDirectory(); + public String getProfilerDirectory() { + return profilerDirectory; + } - Duration getRecordingDuration(); + public Duration getRecordingDuration() { + return recordingDuration; + } - Object getConfigProperties(); + @Nullable + public Object getConfigProperties() { + return configProperties; + } - static int getJavaVersion() { + public static int getJavaVersion() { String javaSpecVersion = System.getProperty("java.specification.version"); if ("1.8".equals(javaSpecVersion)) { return 8; } return Integer.parseInt(javaSpecVersion); } + + public static class Builder { + private boolean enabled; + @Nullable private String ingestUrl; + @Nullable private String otlpProtocol; + private boolean memoryEnabled; + private boolean memoryEventRateLimitEnabled = true; + private String memoryEventRate = "150/s"; + private boolean useAllocationSampleEvent; + private Duration callStackInterval = DEFAULT_CALL_STACK_INTERVAL; + private boolean includeAgentInternalStacks; + private boolean includeJvmInternalStacks; + private boolean tracingStacksOnly; + private int stackDepth = 1024; + private boolean keepFiles; + private String profilerDirectory = DEFAULT_PROFILER_DIRECTORY; + private Duration recordingDuration = DEFAULT_RECORDING_DURATION; + @Nullable private Object configProperties; + + private Builder() {} + + private Builder(ProfilerConfiguration config) { + enabled = config.enabled; + ingestUrl = config.ingestUrl; + otlpProtocol = config.otlpProtocol; + memoryEnabled = config.memoryEnabled; + memoryEventRateLimitEnabled = config.memoryEventRateLimitEnabled; + memoryEventRate = config.memoryEventRate; + useAllocationSampleEvent = config.useAllocationSampleEvent; + callStackInterval = config.callStackInterval; + includeAgentInternalStacks = config.includeAgentInternalStacks; + includeJvmInternalStacks = config.includeJvmInternalStacks; + tracingStacksOnly = config.tracingStacksOnly; + stackDepth = config.stackDepth; + keepFiles = config.keepFiles; + profilerDirectory = config.profilerDirectory; + recordingDuration = config.recordingDuration; + configProperties = config.configProperties; + } + + public ProfilerConfiguration build() { + return new ProfilerConfiguration(this); + } + + public Builder setEnabled(boolean enabled) { + this.enabled = enabled; + return this; + } + + public Builder setIngestUrl(@Nullable String ingestUrl) { + this.ingestUrl = ingestUrl; + return this; + } + + public Builder setOtlpProtocol(@Nullable String otlpProtocol) { + this.otlpProtocol = otlpProtocol; + return this; + } + + public Builder setMemoryEnabled(boolean memoryEnabled) { + this.memoryEnabled = memoryEnabled; + return this; + } + + public Builder setMemoryEventRateLimitEnabled(boolean memoryEventRateLimitEnabled) { + this.memoryEventRateLimitEnabled = memoryEventRateLimitEnabled; + return this; + } + + public Builder setMemoryEventRate(String memoryEventRate) { + this.memoryEventRate = Objects.requireNonNull(memoryEventRate); + return this; + } + + public Builder setUseAllocationSampleEvent(boolean useAllocationSampleEvent) { + this.useAllocationSampleEvent = useAllocationSampleEvent; + return this; + } + + public Builder setCallStackInterval(Duration callStackInterval) { + this.callStackInterval = Objects.requireNonNull(callStackInterval); + return this; + } + + public Builder setIncludeAgentInternalStacks(boolean includeAgentInternalStacks) { + this.includeAgentInternalStacks = includeAgentInternalStacks; + return this; + } + + public Builder setIncludeJvmInternalStacks(boolean includeJvmInternalStacks) { + this.includeJvmInternalStacks = includeJvmInternalStacks; + return this; + } + + public Builder setTracingStacksOnly(boolean tracingStacksOnly) { + this.tracingStacksOnly = tracingStacksOnly; + return this; + } + + public Builder setStackDepth(int stackDepth) { + this.stackDepth = stackDepth; + return this; + } + + public Builder setKeepFiles(boolean keepFiles) { + this.keepFiles = keepFiles; + return this; + } + + public Builder setProfilerDirectory(String profilerDirectory) { + this.profilerDirectory = Objects.requireNonNull(profilerDirectory); + return this; + } + + public Builder setRecordingDuration(Duration recordingDuration) { + this.recordingDuration = Objects.requireNonNull(recordingDuration); + return this; + } + + public Builder setConfigProperties(@Nullable Object configProperties) { + this.configProperties = configProperties; + return this; + } + } } diff --git a/profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilerDeclarativeConfigurationInitializer.java b/profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilerConfigurationInitializer.java similarity index 56% rename from profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilerDeclarativeConfigurationInitializer.java rename to profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilerConfigurationInitializer.java index 72fba6659..830655de7 100644 --- a/profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilerDeclarativeConfigurationInitializer.java +++ b/profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilerConfigurationInitializer.java @@ -23,16 +23,24 @@ import io.opentelemetry.sdk.autoconfigure.AutoConfigureUtil; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfigurationCustomizer; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.DeclarativeConfigurationCustomizerProvider; +import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer; +import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider; +import java.util.Collections; /** - * Purpose of this class is to configure the supplier of ProfilerDeclarativeConfiguration. - * ProfilerDeclarativeConfiguration class object can then be used in code executed after SDK is - * created, such as AgentListeners. + * Purpose of this class is to configure the supplier of ProfilerConfiguration. The configured + * object can then be used in code executed after SDK is created, such as AgentListeners. */ -@AutoService(DeclarativeConfigurationCustomizerProvider.class) -public class ProfilerDeclarativeConfigurationInitializer - implements DeclarativeConfigurationCustomizerProvider { +@AutoService({ + DeclarativeConfigurationCustomizerProvider.class, + AutoConfigurationCustomizerProvider.class +}) +public class ProfilerConfigurationInitializer + implements DeclarativeConfigurationCustomizerProvider, AutoConfigurationCustomizerProvider { + + @Override public void customize(DeclarativeConfigurationCustomizer configurationCustomizer) { + // Initialize profiler configuration from declarative config configurationCustomizer.addModelCustomizer( (model) -> { DeclarativeConfigProperties distributionConfig = @@ -40,10 +48,26 @@ public void customize(DeclarativeConfigurationCustomizer configurationCustomizer DeclarativeConfigProperties profilingConfig = distributionConfig.getStructured("profiling", empty()); - ProfilerDeclarativeConfiguration.SUPPLIER.configure( - new ProfilerDeclarativeConfiguration(profilingConfig)); + ProfilerConfiguration.SUPPLIER.configure( + ProfilerDeclarativeConfigurationFactory.create(profilingConfig)); return model; }); } + + @Override + public void customize(AutoConfigurationCustomizer autoConfiguration) { + // Initialize profiler configuration from environment config + autoConfiguration.addPropertiesCustomizer( + configProperties -> { + ProfilerConfiguration.SUPPLIER.configure( + ProfilerEnvVarsConfigurationFactory.create(configProperties)); + return Collections.emptyMap(); + }); + } + + @Override + public int order() { + return Integer.MAX_VALUE; + } } diff --git a/profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilerDeclarativeConfiguration.java b/profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilerDeclarativeConfiguration.java deleted file mode 100644 index 1da14893f..000000000 --- a/profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilerDeclarativeConfiguration.java +++ /dev/null @@ -1,171 +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; - -import static io.opentelemetry.api.incubator.config.DeclarativeConfigProperties.empty; - -import com.splunk.opentelemetry.profiler.util.OptionalConfigurableSupplier; -import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; -import java.time.Duration; -import java.util.logging.Logger; -import javax.annotation.Nullable; - -public class ProfilerDeclarativeConfiguration implements ProfilerConfiguration { - public static final OptionalConfigurableSupplier SUPPLIER = - new OptionalConfigurableSupplier<>(); - - private static final Logger logger = - Logger.getLogger(ProfilerDeclarativeConfiguration.class.getName()); - - private static final String ROOT_NODE_NAME = "always_on"; - - private static final String DEFAULT_PROFILER_DIRECTORY = System.getProperty("java.io.tmpdir"); - private static final long DEFAULT_RECORDING_DURATION = Duration.ofSeconds(20).toMillis(); - private static final long DEFAULT_SAMPLING_INTERVAL = Duration.ofSeconds(10).toMillis(); - - private static final String MEMORY_PROFILER = "memory_profiler"; - private static final String MEMORY_EVENT_RATE = "event_rate"; - - private final DeclarativeConfigProperties profilingConfig; - - public ProfilerDeclarativeConfiguration(DeclarativeConfigProperties profilingConfig) { - this.profilingConfig = profilingConfig; - } - - @Override - public boolean isEnabled() { - return (profilingConfig != null) && profilingConfig.getPropertyKeys().contains(ROOT_NODE_NAME); - } - - @Override - public void log() { - logger.info("-----------------------"); - logger.info("Profiler declarative configuration:"); - log("Enabled", isEnabled()); - log("ProfilerDirectory", getProfilerDirectory()); - log("RecordingDuration", getRecordingDuration().toMillis() + "ms"); - log("KeepFiles", getKeepFiles()); - log("MemoryEnabled", getMemoryEnabled()); - if (getMemoryEventRateLimitEnabled()) { - log("MemoryEventRate", getMemoryEventRate()); - } - log("UseAllocationSampleEvent", getUseAllocationSampleEvent()); - log("CallStackInterval", getCallStackInterval().toMillis() + "ms"); - log("IncludeAgentInternalStacks", getIncludeAgentInternalStacks()); - log("IncludeJvmInternalStacks", getIncludeJvmInternalStacks()); - log("TracingStacksOnly", getTracingStacksOnly()); - log("StackDepth", getStackDepth()); - logger.info("-----------------------"); - } - - private static void log(String key, @Nullable Object value) { - logger.info(String.format("%30s : %s", key, value)); - } - - @Override - public String getIngestUrl() { - throw new UnsupportedOperationException("Not supported for declarative configuration"); - } - - @Override - public String getOtlpProtocol() { - throw new UnsupportedOperationException("Not supported for declarative configuration"); - } - - @Override - public boolean getMemoryEnabled() { - return getConfigRoot().getPropertyKeys().contains(MEMORY_PROFILER); - } - - @Override - public boolean getMemoryEventRateLimitEnabled() { - return getMemoryProfilerConfig().getString(MEMORY_EVENT_RATE) != null; - } - - @Override - public String getMemoryEventRate() { - return getMemoryProfilerConfig().getString(MEMORY_EVENT_RATE, "150/s"); - } - - @Override - public boolean getUseAllocationSampleEvent() { - // Using jdk16+ ObjectAllocationSample event is disabled by default - return HAS_OBJECT_ALLOCATION_SAMPLE_EVENT - && getMemoryProfilerConfig().getBoolean("native_sampling", false); - } - - @Override - public Duration getCallStackInterval() { - return getDuration( - getConfigRoot().getStructured("cpu_profiler", empty()), - "sampling_interval", - DEFAULT_SAMPLING_INTERVAL); - } - - @Override - public boolean getIncludeAgentInternalStacks() { - return getConfigRoot().getBoolean("include_agent_internals", false); - } - - @Override - public boolean getIncludeJvmInternalStacks() { - return getConfigRoot().getBoolean("include_jvm_internals", false); - } - - @Override - public boolean getTracingStacksOnly() { - return getConfigRoot().getBoolean("tracing_stacks_only", false); - } - - @Override - public int getStackDepth() { - return getConfigRoot().getInt("stack_depth", 1024); - } - - @Override - public boolean getKeepFiles() { - return getConfigRoot().getBoolean("keep_recording_files", false); - } - - @Override - public String getProfilerDirectory() { - return getConfigRoot().getString("recording_directory", DEFAULT_PROFILER_DIRECTORY); - } - - @Override - public Duration getRecordingDuration() { - return getDuration(getConfigRoot(), "recording_duration", DEFAULT_RECORDING_DURATION); - } - - @Override - public DeclarativeConfigProperties getConfigProperties() { - return profilingConfig; - } - - private DeclarativeConfigProperties getConfigRoot() { - return profilingConfig.getStructured(ROOT_NODE_NAME, empty()); - } - - private DeclarativeConfigProperties getMemoryProfilerConfig() { - return getConfigRoot().getStructured(MEMORY_PROFILER, empty()); - } - - private static Duration getDuration( - DeclarativeConfigProperties config, String key, long defaultValue) { - return Duration.ofMillis(config.getLong(key, defaultValue)); - } -} diff --git a/profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilerDeclarativeConfigurationFactory.java b/profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilerDeclarativeConfigurationFactory.java new file mode 100644 index 000000000..ee03d6664 --- /dev/null +++ b/profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilerDeclarativeConfigurationFactory.java @@ -0,0 +1,83 @@ +/* + * 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; + +import static io.opentelemetry.api.incubator.config.DeclarativeConfigProperties.empty; + +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import java.time.Duration; + +public final class ProfilerDeclarativeConfigurationFactory { + private static final String ROOT_NODE_NAME = "always_on"; + + private static final String DEFAULT_PROFILER_DIRECTORY = System.getProperty("java.io.tmpdir"); + private static final long DEFAULT_RECORDING_DURATION = Duration.ofSeconds(20).toMillis(); + private static final long DEFAULT_SAMPLING_INTERVAL = Duration.ofSeconds(10).toMillis(); + + private static final String MEMORY_PROFILER = "memory_profiler"; + private static final String MEMORY_EVENT_RATE = "event_rate"; + + private ProfilerDeclarativeConfigurationFactory() {} + + public static ProfilerConfiguration create(DeclarativeConfigProperties profilingConfig) { + DeclarativeConfigProperties config = profilingConfig == null ? empty() : profilingConfig; + DeclarativeConfigProperties configRoot = getConfigRoot(config); + DeclarativeConfigProperties memoryProfilerConfig = getMemoryProfilerConfig(configRoot); + + boolean useAllocationSampleEvent = + ProfilerConfiguration.HAS_OBJECT_ALLOCATION_SAMPLE_EVENT + && memoryProfilerConfig.getBoolean("native_sampling", false); + Duration callStackInterval = + getDuration( + configRoot.getStructured("cpu_profiler", empty()), + "sampling_interval", + DEFAULT_SAMPLING_INTERVAL); + + return ProfilerConfiguration.builder() + .setEnabled(config.getPropertyKeys().contains(ROOT_NODE_NAME)) + .setMemoryEnabled(configRoot.getPropertyKeys().contains(MEMORY_PROFILER)) + .setMemoryEventRateLimitEnabled(memoryProfilerConfig.getString(MEMORY_EVENT_RATE) != null) + .setMemoryEventRate(memoryProfilerConfig.getString(MEMORY_EVENT_RATE, "150/s")) + .setUseAllocationSampleEvent(useAllocationSampleEvent) + .setCallStackInterval(callStackInterval) + .setIncludeAgentInternalStacks(configRoot.getBoolean("include_agent_internals", false)) + .setIncludeJvmInternalStacks(configRoot.getBoolean("include_jvm_internals", false)) + .setTracingStacksOnly(configRoot.getBoolean("tracing_stacks_only", false)) + .setStackDepth(configRoot.getInt("stack_depth", 1024)) + .setKeepFiles(configRoot.getBoolean("keep_recording_files", false)) + .setProfilerDirectory( + configRoot.getString("recording_directory", DEFAULT_PROFILER_DIRECTORY)) + .setRecordingDuration( + getDuration(configRoot, "recording_duration", DEFAULT_RECORDING_DURATION)) + .setConfigProperties(config) + .build(); + } + + private static DeclarativeConfigProperties getConfigRoot(DeclarativeConfigProperties config) { + return config.getStructured(ROOT_NODE_NAME, empty()); + } + + private static DeclarativeConfigProperties getMemoryProfilerConfig( + DeclarativeConfigProperties configRoot) { + return configRoot.getStructured(MEMORY_PROFILER, empty()); + } + + private static Duration getDuration( + DeclarativeConfigProperties config, String key, long defaultValue) { + return Duration.ofMillis(config.getLong(key, defaultValue)); + } +} diff --git a/profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilerEnvVarsConfiguration.java b/profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilerEnvVarsConfiguration.java deleted file mode 100644 index 2f11adb70..000000000 --- a/profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilerEnvVarsConfiguration.java +++ /dev/null @@ -1,215 +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; - -import static java.util.logging.Level.WARNING; - -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; -import java.time.Duration; -import java.util.logging.Logger; -import javax.annotation.Nullable; - -public class ProfilerEnvVarsConfiguration implements ProfilerConfiguration { - private static final Logger logger = - Logger.getLogger(ProfilerEnvVarsConfiguration.class.getName()); - - static final String CONFIG_KEY_OTLP_PROTOCOL = "otel.exporter.otlp.protocol"; - static final String CONFIG_KEY_OTEL_OTLP_URL = "otel.exporter.otlp.endpoint"; - - /* Keys visible for testing */ - public static final String CONFIG_KEY_PROFILER_ENABLED = "splunk.profiler.enabled"; - public static final String CONFIG_KEY_PROFILER_DIRECTORY = "splunk.profiler.directory"; - public static final String CONFIG_KEY_RECORDING_DURATION = "splunk.profiler.recording.duration"; - public static final String CONFIG_KEY_KEEP_FILES = "splunk.profiler.keep-files"; - public static final String CONFIG_KEY_INGEST_URL = "splunk.profiler.logs-endpoint"; - public static final String CONFIG_KEY_PROFILER_OTLP_PROTOCOL = "splunk.profiler.otlp.protocol"; - public static final String CONFIG_KEY_MEMORY_ENABLED = "splunk.profiler.memory.enabled"; - public static final String CONFIG_KEY_MEMORY_EVENT_RATE_LIMIT_ENABLED = - "splunk.profiler.memory.event.rate-limit.enabled"; - public static final String CONFIG_KEY_MEMORY_EVENT_RATE = "splunk.profiler.memory.event.rate"; - public static final String CONFIG_KEY_MEMORY_NATIVE_SAMPLING = - "splunk.profiler.memory.native.sampling"; - public static final String CONFIG_KEY_CALL_STACK_INTERVAL = "splunk.profiler.call.stack.interval"; - public static final String CONFIG_KEY_INCLUDE_AGENT_INTERNALS = - "splunk.profiler.include.agent.internals"; - // Include stacks where every frame starts with jvm/sun/jdk - public static final String CONFIG_KEY_INCLUDE_JVM_INTERNALS = - "splunk.profiler.include.jvm.internals"; - public static final String CONFIG_KEY_INCLUDE_INTERNAL_STACKS = - "splunk.profiler.include.internal.stacks"; - public static final String CONFIG_KEY_TRACING_STACKS_ONLY = "splunk.profiler.tracing.stacks.only"; - public static final String CONFIG_KEY_STACK_DEPTH = "splunk.profiler.max.stack.depth"; - - private static final String DEFAULT_PROFILER_DIRECTORY = System.getProperty("java.io.tmpdir"); - private static final Duration DEFAULT_RECORDING_DURATION = Duration.ofSeconds(20); - private static final Duration DEFAULT_CALL_STACK_INTERVAL = Duration.ofSeconds(10); - - private final ConfigProperties config; - - public ProfilerEnvVarsConfiguration(ConfigProperties config) { - this.config = config; - } - - @Override - public void log() { - logger.info("-----------------------"); - logger.info("Profiler env vars based configuration:"); - log(CONFIG_KEY_PROFILER_ENABLED, isEnabled()); - log(CONFIG_KEY_PROFILER_DIRECTORY, getProfilerDirectory()); - log(CONFIG_KEY_RECORDING_DURATION, getRecordingDuration().toMillis() + "ms"); - log(CONFIG_KEY_KEEP_FILES, getKeepFiles()); - log(CONFIG_KEY_PROFILER_OTLP_PROTOCOL, getOtlpProtocol()); - log(CONFIG_KEY_INGEST_URL, getIngestUrl()); - log(CONFIG_KEY_OTEL_OTLP_URL, config.getString(CONFIG_KEY_OTEL_OTLP_URL)); - log(CONFIG_KEY_MEMORY_ENABLED, getMemoryEnabled()); - if (getMemoryEventRateLimitEnabled()) { - log(CONFIG_KEY_MEMORY_EVENT_RATE, getMemoryEventRate()); - } - log(CONFIG_KEY_MEMORY_NATIVE_SAMPLING, getUseAllocationSampleEvent()); - log(CONFIG_KEY_CALL_STACK_INTERVAL, getCallStackInterval().toMillis() + "ms"); - log(CONFIG_KEY_INCLUDE_AGENT_INTERNALS, getIncludeAgentInternalStacks()); - log(CONFIG_KEY_INCLUDE_JVM_INTERNALS, getIncludeJvmInternalStacks()); - log(CONFIG_KEY_TRACING_STACKS_ONLY, getTracingStacksOnly()); - log(CONFIG_KEY_STACK_DEPTH, getStackDepth()); - logger.info("-----------------------"); - } - - private void log(String key, @Nullable Object value) { - logger.info(String.format("%39s : %s", key, value)); - } - - @Override - public boolean isEnabled() { - return config.getBoolean(CONFIG_KEY_PROFILER_ENABLED, false); - } - - @Override - public String getIngestUrl() { - String ingestUrl = config.getString(CONFIG_KEY_INGEST_URL); - if (ingestUrl == null) { - String defaultIngestUrl = getDefaultLogsEndpoint(); - ingestUrl = config.getString(CONFIG_KEY_OTEL_OTLP_URL, defaultIngestUrl); - - if (ingestUrl.startsWith("https://ingest.") - && ingestUrl.endsWith(".observability.splunkcloud.com")) { - logger.log( - WARNING, - "Profiling data can not be sent to {0}, using {1} instead. " - + "You can override it by setting " - + CONFIG_KEY_INGEST_URL, - new Object[] {ingestUrl, defaultIngestUrl}); - return defaultIngestUrl; - } - - if ("http/protobuf".equals(getOtlpProtocol())) { - ingestUrl = maybeAppendHttpPath(ingestUrl); - } - } - return ingestUrl; - } - - private String maybeAppendHttpPath(String ingestUrl) { - if (!ingestUrl.endsWith("v1/logs")) { - if (!ingestUrl.endsWith("/")) { - ingestUrl += "/"; - } - ingestUrl += "v1/logs"; - } - return ingestUrl; - } - - private String getDefaultLogsEndpoint() { - return "http/protobuf".equals(getOtlpProtocol()) - ? "http://localhost:4318/v1/logs" - : "http://localhost:4317"; - } - - @Override - public String getOtlpProtocol() { - return config.getString( - CONFIG_KEY_PROFILER_OTLP_PROTOCOL, - config.getString(CONFIG_KEY_OTLP_PROTOCOL, "http/protobuf")); - } - - @Override - public boolean getMemoryEnabled() { - return config.getBoolean(CONFIG_KEY_MEMORY_ENABLED, false); - } - - @Override - public boolean getMemoryEventRateLimitEnabled() { - return config.getBoolean(CONFIG_KEY_MEMORY_EVENT_RATE_LIMIT_ENABLED, true); - } - - @Override - public String getMemoryEventRate() { - return config.getString(CONFIG_KEY_MEMORY_EVENT_RATE, "150/s"); - } - - @Override - public boolean getUseAllocationSampleEvent() { - return HAS_OBJECT_ALLOCATION_SAMPLE_EVENT - && config.getBoolean(CONFIG_KEY_MEMORY_NATIVE_SAMPLING, false); - } - - @Override - public Duration getCallStackInterval() { - return config.getDuration(CONFIG_KEY_CALL_STACK_INTERVAL, DEFAULT_CALL_STACK_INTERVAL); - } - - @Override - public boolean getIncludeAgentInternalStacks() { - boolean includeInternals = config.getBoolean(CONFIG_KEY_INCLUDE_INTERNAL_STACKS, false); - return config.getBoolean(CONFIG_KEY_INCLUDE_AGENT_INTERNALS, includeInternals); - } - - @Override - public boolean getIncludeJvmInternalStacks() { - boolean includeInternals = config.getBoolean(CONFIG_KEY_INCLUDE_INTERNAL_STACKS, false); - return config.getBoolean(CONFIG_KEY_INCLUDE_JVM_INTERNALS, includeInternals); - } - - @Override - public boolean getTracingStacksOnly() { - return config.getBoolean(CONFIG_KEY_TRACING_STACKS_ONLY, false); - } - - @Override - public int getStackDepth() { - return config.getInt(CONFIG_KEY_STACK_DEPTH, 1024); - } - - @Override - public boolean getKeepFiles() { - return config.getBoolean(CONFIG_KEY_KEEP_FILES, false); - } - - @Override - public String getProfilerDirectory() { - return config.getString(CONFIG_KEY_PROFILER_DIRECTORY, DEFAULT_PROFILER_DIRECTORY); - } - - @Override - public Duration getRecordingDuration() { - return config.getDuration(CONFIG_KEY_RECORDING_DURATION, DEFAULT_RECORDING_DURATION); - } - - @Override - public ConfigProperties getConfigProperties() { - return config; - } -} diff --git a/profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilerEnvVarsConfigurationFactory.java b/profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilerEnvVarsConfigurationFactory.java new file mode 100644 index 000000000..f4392e751 --- /dev/null +++ b/profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilerEnvVarsConfigurationFactory.java @@ -0,0 +1,138 @@ +/* + * 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; + +import static java.util.logging.Level.WARNING; + +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import java.time.Duration; +import java.util.logging.Logger; + +public final class ProfilerEnvVarsConfigurationFactory { + private static final Logger logger = + Logger.getLogger(ProfilerEnvVarsConfigurationFactory.class.getName()); + + static final String CONFIG_KEY_OTLP_PROTOCOL = "otel.exporter.otlp.protocol"; + static final String CONFIG_KEY_OTEL_OTLP_URL = "otel.exporter.otlp.endpoint"; + + /* Keys visible for testing */ + static final String CONFIG_KEY_PROFILER_ENABLED = "splunk.profiler.enabled"; + static final String CONFIG_KEY_PROFILER_DIRECTORY = "splunk.profiler.directory"; + static final String CONFIG_KEY_RECORDING_DURATION = "splunk.profiler.recording.duration"; + static final String CONFIG_KEY_KEEP_FILES = "splunk.profiler.keep-files"; + static final String CONFIG_KEY_INGEST_URL = "splunk.profiler.logs-endpoint"; + static final String CONFIG_KEY_PROFILER_OTLP_PROTOCOL = "splunk.profiler.otlp.protocol"; + static final String CONFIG_KEY_MEMORY_ENABLED = "splunk.profiler.memory.enabled"; + static final String CONFIG_KEY_MEMORY_EVENT_RATE_LIMIT_ENABLED = + "splunk.profiler.memory.event.rate-limit.enabled"; + static final String CONFIG_KEY_MEMORY_EVENT_RATE = "splunk.profiler.memory.event.rate"; + static final String CONFIG_KEY_MEMORY_NATIVE_SAMPLING = "splunk.profiler.memory.native.sampling"; + static final String CONFIG_KEY_CALL_STACK_INTERVAL = "splunk.profiler.call.stack.interval"; + static final String CONFIG_KEY_INCLUDE_AGENT_INTERNALS = + "splunk.profiler.include.agent.internals"; + // Include stacks where every frame starts with jvm/sun/jdk + static final String CONFIG_KEY_INCLUDE_JVM_INTERNALS = "splunk.profiler.include.jvm.internals"; + static final String CONFIG_KEY_INCLUDE_INTERNAL_STACKS = + "splunk.profiler.include.internal.stacks"; + static final String CONFIG_KEY_TRACING_STACKS_ONLY = "splunk.profiler.tracing.stacks.only"; + static final String CONFIG_KEY_STACK_DEPTH = "splunk.profiler.max.stack.depth"; + + private static final String DEFAULT_PROFILER_DIRECTORY = System.getProperty("java.io.tmpdir"); + private static final Duration DEFAULT_RECORDING_DURATION = Duration.ofSeconds(20); + private static final Duration DEFAULT_CALL_STACK_INTERVAL = Duration.ofSeconds(10); + + private ProfilerEnvVarsConfigurationFactory() {} + + public static ProfilerConfiguration create(ConfigProperties config) { + String otlpProtocol = getOtlpProtocol(config); + boolean includeInternals = config.getBoolean(CONFIG_KEY_INCLUDE_INTERNAL_STACKS, false); + + boolean useAllocationSampleEvent = + ProfilerConfiguration.HAS_OBJECT_ALLOCATION_SAMPLE_EVENT + && config.getBoolean(CONFIG_KEY_MEMORY_NATIVE_SAMPLING, false); + return ProfilerConfiguration.builder() + .setEnabled(config.getBoolean(CONFIG_KEY_PROFILER_ENABLED, false)) + .setIngestUrl(getIngestUrl(config, otlpProtocol)) + .setOtlpProtocol(otlpProtocol) + .setMemoryEnabled(config.getBoolean(CONFIG_KEY_MEMORY_ENABLED, false)) + .setMemoryEventRateLimitEnabled( + config.getBoolean(CONFIG_KEY_MEMORY_EVENT_RATE_LIMIT_ENABLED, true)) + .setMemoryEventRate(config.getString(CONFIG_KEY_MEMORY_EVENT_RATE, "150/s")) + .setUseAllocationSampleEvent(useAllocationSampleEvent) + .setCallStackInterval( + config.getDuration(CONFIG_KEY_CALL_STACK_INTERVAL, DEFAULT_CALL_STACK_INTERVAL)) + .setIncludeAgentInternalStacks( + config.getBoolean(CONFIG_KEY_INCLUDE_AGENT_INTERNALS, includeInternals)) + .setIncludeJvmInternalStacks( + config.getBoolean(CONFIG_KEY_INCLUDE_JVM_INTERNALS, includeInternals)) + .setTracingStacksOnly(config.getBoolean(CONFIG_KEY_TRACING_STACKS_ONLY, false)) + .setStackDepth(config.getInt(CONFIG_KEY_STACK_DEPTH, 1024)) + .setKeepFiles(config.getBoolean(CONFIG_KEY_KEEP_FILES, false)) + .setProfilerDirectory( + config.getString(CONFIG_KEY_PROFILER_DIRECTORY, DEFAULT_PROFILER_DIRECTORY)) + .setRecordingDuration( + config.getDuration(CONFIG_KEY_RECORDING_DURATION, DEFAULT_RECORDING_DURATION)) + .setConfigProperties(config) + .build(); + } + + private static String getIngestUrl(ConfigProperties config, String otlpProtocol) { + String ingestUrl = config.getString(CONFIG_KEY_INGEST_URL); + if (ingestUrl == null) { + String defaultIngestUrl = getDefaultLogsEndpoint(otlpProtocol); + ingestUrl = config.getString(CONFIG_KEY_OTEL_OTLP_URL, defaultIngestUrl); + + if (ingestUrl.startsWith("https://ingest.") + && ingestUrl.endsWith(".observability.splunkcloud.com")) { + logger.log( + WARNING, + "Profiling data can not be sent to {0}, using {1} instead. " + + "You can override it by setting " + + CONFIG_KEY_INGEST_URL, + new Object[] {ingestUrl, defaultIngestUrl}); + return defaultIngestUrl; + } + + if ("http/protobuf".equals(otlpProtocol)) { + ingestUrl = maybeAppendHttpPath(ingestUrl); + } + } + return ingestUrl; + } + + private static String maybeAppendHttpPath(String ingestUrl) { + if (!ingestUrl.endsWith("v1/logs")) { + if (!ingestUrl.endsWith("/")) { + ingestUrl += "/"; + } + ingestUrl += "v1/logs"; + } + return ingestUrl; + } + + private static String getDefaultLogsEndpoint(String otlpProtocol) { + return "http/protobuf".equals(otlpProtocol) + ? "http://localhost:4318/v1/logs" + : "http://localhost:4317"; + } + + private static String getOtlpProtocol(ConfigProperties config) { + return config.getString( + CONFIG_KEY_PROFILER_OTLP_PROTOCOL, + config.getString(CONFIG_KEY_OTLP_PROTOCOL, "http/protobuf")); + } +} 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 c1575c40c..2b9269ffd 100644 --- a/profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilingSupervisor.java +++ b/profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilingSupervisor.java @@ -41,7 +41,7 @@ public class ProfilingSupervisor { private static final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(ProfilingSupervisor.class.getName()); - private final ProfilerConfiguration config; + private final OptionalConfigurableSupplier configSupplier; private final JFR jfr; private final AutoConfiguredOpenTelemetrySdk sdk; private final BlockingQueue commandQueue; @@ -53,24 +53,24 @@ public class ProfilingSupervisor { @VisibleForTesting ProfilingSupervisor( - ProfilerConfiguration config, + OptionalConfigurableSupplier configSupplier, JFR jfr, AutoConfiguredOpenTelemetrySdk sdk, BlockingQueue commandQueue) { - this.config = config; + this.configSupplier = configSupplier; this.jfr = jfr; this.sdk = sdk; this.commandQueue = commandQueue; } - static ProfilingSupervisor createAndStart( - AutoConfiguredOpenTelemetrySdk sdk, ProfilerConfiguration config) { + static ProfilingSupervisor createAndStart(AutoConfiguredOpenTelemetrySdk sdk) { if (SUPPLIER.isConfigured()) { throw new IllegalStateException("Already started"); } ExecutorService executor = HelpfulExecutors.newSingleThreadExecutor("JFR Profiler"); BlockingQueue queue = new LinkedBlockingQueue<>(); - ProfilingSupervisor supervisor = new ProfilingSupervisor(config, JFR.getInstance(), sdk, queue); + ProfilingSupervisor supervisor = + new ProfilingSupervisor(ProfilerConfiguration.SUPPLIER, JFR.getInstance(), sdk, queue); SUPPLIER.configure(supervisor); supervisor.start(executor); @@ -137,10 +137,10 @@ private void tryStart() { "JDK Flight Recorder (JFR) is not available in this JVM. Profiling will not start."); return; } - config.log(); - logger.info("Profiler is active."); + configSupplier.get().log(); setJfrContextStorageEnabled(true); activateJfrRecording(getResource(sdk)); + logger.info("Profiler is active."); } private void tryStop() { @@ -150,6 +150,7 @@ private void tryStop() { } setJfrContextStorageEnabled(false); deactivateJfrRecording(); + logger.info("Profiler is deactivated."); } private boolean isJfrRecordingActive() { @@ -173,7 +174,7 @@ private void deactivateJfrRecording() { // Exists for testing PeriodicRecordingFlusherBuilder makeRecordingFlusherBuilder(Resource resource) { - return PeriodicRecordingFlusher.builder(config, resource); + return PeriodicRecordingFlusher.builder(configSupplier.get(), resource); } static void setupJfrContextStorage() { diff --git a/profiler/src/test/java/com/splunk/opentelemetry/profiler/JfrAgentListenerTest.java b/profiler/src/test/java/com/splunk/opentelemetry/profiler/JfrAgentListenerTest.java index 9f87f91d0..3d7d28fe2 100644 --- a/profiler/src/test/java/com/splunk/opentelemetry/profiler/JfrAgentListenerTest.java +++ b/profiler/src/test/java/com/splunk/opentelemetry/profiler/JfrAgentListenerTest.java @@ -43,7 +43,7 @@ class JfrAgentListenerTest { @AfterEach void resetDeclarativeConfigSuppliers() { - ProfilerDeclarativeConfiguration.SUPPLIER.reset(); + ProfilerConfiguration.SUPPLIER.reset(); SnapshotProfilingDeclarativeConfiguration.SUPPLIER.reset(); } @@ -68,8 +68,7 @@ void shouldActivateJfrRecording(@TempDir Path tempDir) throws IOException { JfrAgentListener listener = new JfrAgentListener(jfr) { @Override - ProfilingSupervisor makeProfilingSupervisor( - AutoConfiguredOpenTelemetrySdk sdk, ProfilerConfiguration config) { + ProfilingSupervisor makeProfilingSupervisor(AutoConfiguredOpenTelemetrySdk sdk) { return supervisor; } }; @@ -102,8 +101,7 @@ void shouldNotActivateJfrRecording_JfrNotAvailable(@TempDir Path tempDir) throws JfrAgentListener listener = new JfrAgentListener(jfr) { @Override - ProfilingSupervisor makeProfilingSupervisor( - AutoConfiguredOpenTelemetrySdk sdk, ProfilerConfiguration config) { + ProfilingSupervisor makeProfilingSupervisor(AutoConfiguredOpenTelemetrySdk sdk) { return supervisor; } }; @@ -130,8 +128,7 @@ void shouldNotActivateJfrRecording_profilerDisabled(String yaml, @TempDir Path t JfrAgentListener listener = new JfrAgentListener(jfr) { @Override - ProfilingSupervisor makeProfilingSupervisor( - AutoConfiguredOpenTelemetrySdk sdk, ProfilerConfiguration config) { + ProfilingSupervisor makeProfilingSupervisor(AutoConfiguredOpenTelemetrySdk sdk) { return supervisor; } }; diff --git a/profiler/src/test/java/com/splunk/opentelemetry/profiler/LogExporterBuilderTest.java b/profiler/src/test/java/com/splunk/opentelemetry/profiler/LogExporterBuilderTest.java index 973be8856..b95ec00cd 100644 --- a/profiler/src/test/java/com/splunk/opentelemetry/profiler/LogExporterBuilderTest.java +++ b/profiler/src/test/java/com/splunk/opentelemetry/profiler/LogExporterBuilderTest.java @@ -53,7 +53,7 @@ class EnvVarBasedConfig { @Test void testBuildSimpleGrpc() { // given - ProfilerEnvVarsConfiguration config = mock(ProfilerEnvVarsConfiguration.class); + ProfilerConfiguration config = mock(ProfilerConfiguration.class); OtlpGrpcLogRecordExporterBuilder builder = mock(OtlpGrpcLogRecordExporterBuilder.class); OtlpGrpcLogRecordExporter expected = mock(OtlpGrpcLogRecordExporter.class); @@ -72,7 +72,7 @@ void testBuildSimpleGrpc() { @Test void testBuildSimpleHttp() { // given - ProfilerEnvVarsConfiguration config = mock(ProfilerEnvVarsConfiguration.class); + ProfilerConfiguration config = mock(ProfilerConfiguration.class); ConfigProperties configProperties = mock(ConfigProperties.class); OtlpHttpLogRecordExporterBuilder builder = mock(OtlpHttpLogRecordExporterBuilder.class); OtlpHttpLogRecordExporter expected = mock(OtlpHttpLogRecordExporter.class); @@ -95,7 +95,7 @@ void testBuildSimpleHttp() { @Test void extraOtlpHeaders() { // given - ProfilerEnvVarsConfiguration config = mock(ProfilerEnvVarsConfiguration.class); + ProfilerConfiguration config = mock(ProfilerConfiguration.class); ConfigProperties configProperties = mock(ConfigProperties.class); when(config.getConfigProperties()).thenReturn(configProperties); when(config.getOtlpProtocol()).thenReturn("http/protobuf"); @@ -127,7 +127,7 @@ void extraOtlpHeaders() { @Test void extraOtlpLogSpecificHeaders() { // given - ProfilerEnvVarsConfiguration config = mock(ProfilerEnvVarsConfiguration.class); + ProfilerConfiguration config = mock(ProfilerConfiguration.class); ConfigProperties configProperties = mock(ConfigProperties.class); when(config.getConfigProperties()).thenReturn(configProperties); when(config.getOtlpProtocol()).thenReturn("http/protobuf"); @@ -173,7 +173,7 @@ void shouldCreateHttpExporter() { DeclarativeConfigProperties exporterConfig = getExporterConfig(model); // when - LogRecordExporter exporter = LogExporterBuilder.fromConfig(exporterConfig); + LogRecordExporter exporter = LogExporterBuilder.fromDeclarativeConfig(exporterConfig); // then assertThat(exporter).isNotNull(); @@ -197,7 +197,7 @@ void shouldCreateGrpcExporter() { DeclarativeConfigProperties exporterConfig = getExporterConfig(model); // when - LogRecordExporter exporter = LogExporterBuilder.fromConfig(exporterConfig); + LogRecordExporter exporter = LogExporterBuilder.fromDeclarativeConfig(exporterConfig); // then assertThat(exporter).isNotNull(); @@ -220,7 +220,7 @@ void shouldCreateHttpExporter_defaultEndpoint() { DeclarativeConfigProperties exporterConfig = getExporterConfig(model); // when - LogRecordExporter exporter = LogExporterBuilder.fromConfig(exporterConfig); + LogRecordExporter exporter = LogExporterBuilder.fromDeclarativeConfig(exporterConfig); // then assertThat(exporter).isNotNull(); @@ -243,7 +243,7 @@ void shouldCreateGrpcExporter_defaultEndpoint() { DeclarativeConfigProperties exporterConfig = getExporterConfig(model); // when - LogRecordExporter exporter = LogExporterBuilder.fromConfig(exporterConfig); + LogRecordExporter exporter = LogExporterBuilder.fromDeclarativeConfig(exporterConfig); // then assertThat(exporter).isNotNull(); @@ -266,7 +266,7 @@ void shouldThrowExceptionForInvalidProtocol() { DeclarativeConfigProperties exporterConfig = getExporterConfig(model); // when, then - assertThatThrownBy(() -> LogExporterBuilder.fromConfig(exporterConfig)) + assertThatThrownBy(() -> LogExporterBuilder.fromDeclarativeConfig(exporterConfig)) .isInstanceOf(ConfigurationException.class); } diff --git a/profiler/src/test/java/com/splunk/opentelemetry/profiler/OtelLoggerFactoryTest.java b/profiler/src/test/java/com/splunk/opentelemetry/profiler/OtelLoggerFactoryTest.java index 67ceaa988..a75309a9c 100644 --- a/profiler/src/test/java/com/splunk/opentelemetry/profiler/OtelLoggerFactoryTest.java +++ b/profiler/src/test/java/com/splunk/opentelemetry/profiler/OtelLoggerFactoryTest.java @@ -33,7 +33,7 @@ class OtelLoggerFactoryTest { private final InMemoryLogRecordExporter exporter = InMemoryLogRecordExporter.create(); private final OtelLoggerFactory factory = - new OtelLoggerFactory(properties -> exporter, declarativeConfigProperties -> exporter); + new OtelLoggerFactory(() -> exporter, declarativeConfigProperties -> exporter); @Test void configureLoggerWithProfilingInstrumentationScopeName() { diff --git a/profiler/src/test/java/com/splunk/opentelemetry/profiler/PeriodicRecordingFlusherBuilderTest.java b/profiler/src/test/java/com/splunk/opentelemetry/profiler/PeriodicRecordingFlusherBuilderTest.java index d309ac1da..0ab6b23f0 100644 --- a/profiler/src/test/java/com/splunk/opentelemetry/profiler/PeriodicRecordingFlusherBuilderTest.java +++ b/profiler/src/test/java/com/splunk/opentelemetry/profiler/PeriodicRecordingFlusherBuilderTest.java @@ -17,16 +17,18 @@ package com.splunk.opentelemetry.profiler; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockConstruction; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; import io.opentelemetry.sdk.resources.Resource; import java.nio.file.Files; import java.nio.file.Path; import java.time.Duration; +import java.util.Map; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.mockito.MockedConstruction; @@ -35,12 +37,17 @@ class PeriodicRecordingFlusherBuilderTest { @TempDir Path tempDir; + @AfterEach + void tearDown() { + ProfilerConfiguration.SUPPLIER.reset(); + } + @Test void buildConfiguresJfrAndWiresRecorderIntoSequencer() { JFR jfr = mock(JFR.class); - TestProfilingConfig config = config(tempDir); - config.stackDepth = 73; - config.recordingDuration = Duration.ofMillis(100); + ProfilerConfiguration config = + config(tempDir).setStackDepth(73).setRecordingDuration(Duration.ofMillis(100)).build(); + ProfilerConfiguration.SUPPLIER.configure(config); try (MockedConstruction recorderConstruction = mockConstruction(JfrRecorder.class)) { @@ -64,8 +71,8 @@ void buildConfiguresJfrAndWiresRecorderIntoSequencer() { void buildCreatesMissingOutputDirectoryWhenKeepingFiles() { Path outputDir = tempDir.resolve("profiler-output"); JFR jfr = mock(JFR.class); - TestProfilingConfig config = config(outputDir); - config.keepFiles = true; + ProfilerConfiguration config = config(outputDir).setKeepFiles(true).build(); + ProfilerConfiguration.SUPPLIER.configure(config); try (MockedConstruction recorderConstruction = mockConstruction(JfrRecorder.class)) { @@ -83,8 +90,8 @@ void buildContinuesWhenKeepFilesPathIsNotADirectory() throws Exception { Path outputFile = tempDir.resolve("profiler-output"); Files.createFile(outputFile); JFR jfr = mock(JFR.class); - TestProfilingConfig config = config(outputFile); - config.keepFiles = true; + ProfilerConfiguration config = config(outputFile).setKeepFiles(true).build(); + ProfilerConfiguration.SUPPLIER.configure(config); try (MockedConstruction recorderConstruction = mockConstruction(JfrRecorder.class)) { @@ -96,21 +103,19 @@ void buildContinuesWhenKeepFilesPathIsNotADirectory() throws Exception { } } - @Test - void buildRejectsUnsupportedConfigProperties() { - JFR jfr = mock(JFR.class); - TestProfilingConfig config = config(tempDir); - config.configProperties = new Object(); - - assertThatThrownBy( - () -> PeriodicRecordingFlusher.builder(config, Resource.empty()).jfr(jfr).build()) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageStartingWith("Unsupported config properties type:"); - } - - private TestProfilingConfig config(Path outputDir) { - TestProfilingConfig config = new TestProfilingConfig(); - config.profilerDirectory = outputDir.toString(); - return config; + private ProfilerConfiguration.Builder config(Path outputDir) { + return ProfilerConfiguration.builder() + .setEnabled(true) + .setIngestUrl("http://localhost:4318/v1/logs") + .setOtlpProtocol("http/protobuf") + .setProfilerDirectory(outputDir.toString()) + .setRecordingDuration(Duration.ofDays(1)) + .setConfigProperties( + DefaultConfigProperties.createFromMap( + Map.of( + "otel.exporter.otlp.protocol", + "http/protobuf", + "otel.exporter.otlp.endpoint", + "http://localhost:4318"))); } } diff --git a/profiler/src/test/java/com/splunk/opentelemetry/profiler/ProfilerConfigurationTest.java b/profiler/src/test/java/com/splunk/opentelemetry/profiler/ProfilerConfigurationTest.java new file mode 100644 index 000000000..397a207d3 --- /dev/null +++ b/profiler/src/test/java/com/splunk/opentelemetry/profiler/ProfilerConfigurationTest.java @@ -0,0 +1,73 @@ +/* + * 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; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.Duration; +import org.junit.jupiter.api.Test; + +class ProfilerConfigurationTest { + + @Test + void newBuilderCopiesExistingConfigurationAndAllowsMutation() { + Object configProperties = new Object(); + ProfilerConfiguration original = + ProfilerConfiguration.builder() + .setEnabled(false) + .setIngestUrl("https://logs.example.com") + .setOtlpProtocol("grpc") + .setMemoryEnabled(true) + .setMemoryEventRateLimitEnabled(false) + .setMemoryEventRate("250/s") + .setUseAllocationSampleEvent(true) + .setCallStackInterval(Duration.ofMillis(1410)) + .setIncludeAgentInternalStacks(true) + .setIncludeJvmInternalStacks(true) + .setTracingStacksOnly(true) + .setStackDepth(73) + .setKeepFiles(true) + .setProfilerDirectory("/tmp/profiler") + .setRecordingDuration(Duration.ofSeconds(30)) + .setConfigProperties(configProperties) + .build(); + + ProfilerConfiguration copy = original.newBuilder().setEnabled(true).build(); + + assertThat(original.isEnabled()).isFalse(); + assertThat(copy.isEnabled()).isTrue(); + assertThat(copy.getIngestUrl()).isEqualTo(original.getIngestUrl()); + assertThat(copy.getOtlpProtocol()).isEqualTo(original.getOtlpProtocol()); + assertThat(copy.getMemoryEnabled()).isEqualTo(original.getMemoryEnabled()); + assertThat(copy.getMemoryEventRateLimitEnabled()) + .isEqualTo(original.getMemoryEventRateLimitEnabled()); + assertThat(copy.getMemoryEventRate()).isEqualTo(original.getMemoryEventRate()); + assertThat(copy.getUseAllocationSampleEvent()) + .isEqualTo(original.getUseAllocationSampleEvent()); + assertThat(copy.getCallStackInterval()).isEqualTo(original.getCallStackInterval()); + assertThat(copy.getIncludeAgentInternalStacks()) + .isEqualTo(original.getIncludeAgentInternalStacks()); + assertThat(copy.getIncludeJvmInternalStacks()) + .isEqualTo(original.getIncludeJvmInternalStacks()); + assertThat(copy.getTracingStacksOnly()).isEqualTo(original.getTracingStacksOnly()); + assertThat(copy.getStackDepth()).isEqualTo(original.getStackDepth()); + assertThat(copy.getKeepFiles()).isEqualTo(original.getKeepFiles()); + assertThat(copy.getProfilerDirectory()).isEqualTo(original.getProfilerDirectory()); + assertThat(copy.getRecordingDuration()).isEqualTo(original.getRecordingDuration()); + assertThat(copy.getConfigProperties()).isSameAs(configProperties); + } +} diff --git a/profiler/src/test/java/com/splunk/opentelemetry/profiler/ProfilerDeclarativeConfigurationTest.java b/profiler/src/test/java/com/splunk/opentelemetry/profiler/ProfilerDeclarativeConfigurationFactoryTest.java similarity index 71% rename from profiler/src/test/java/com/splunk/opentelemetry/profiler/ProfilerDeclarativeConfigurationTest.java rename to profiler/src/test/java/com/splunk/opentelemetry/profiler/ProfilerDeclarativeConfigurationFactoryTest.java index 156052a18..a80b4a767 100644 --- a/profiler/src/test/java/com/splunk/opentelemetry/profiler/ProfilerDeclarativeConfigurationTest.java +++ b/profiler/src/test/java/com/splunk/opentelemetry/profiler/ProfilerDeclarativeConfigurationFactoryTest.java @@ -18,7 +18,6 @@ import static com.splunk.opentelemetry.testing.declarativeconfig.DeclarativeConfigTestUtil.getProfilingConfig; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; import com.splunk.opentelemetry.testing.declarativeconfig.DeclarativeConfigTestUtil; import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; @@ -26,11 +25,10 @@ import java.time.Duration; import org.junit.jupiter.api.Test; -class ProfilerDeclarativeConfigurationTest { +class ProfilerDeclarativeConfigurationFactoryTest { @Test void shouldMapYamlToConfiguration() { - // given OpenTelemetryConfigurationModel model = DeclarativeConfigTestUtil.parse( """ @@ -56,10 +54,8 @@ void shouldMapYamlToConfiguration() { DeclarativeConfigProperties profilingConfig = getProfilingConfig(model); - // when - ProfilerDeclarativeConfiguration config = new ProfilerDeclarativeConfiguration(profilingConfig); + ProfilerConfiguration config = ProfilerDeclarativeConfigurationFactory.create(profilingConfig); - // then assertThat(config.isEnabled()).isTrue(); assertThat(config.getIncludeAgentInternalStacks()).isTrue(); assertThat(config.getIncludeJvmInternalStacks()).isTrue(); @@ -72,9 +68,29 @@ void shouldMapYamlToConfiguration() { assertThat(config.getMemoryEnabled()).isTrue(); assertThat(config.getMemoryEventRateLimitEnabled()).isTrue(); assertThat(config.getMemoryEventRate()).isEqualTo("250/s"); - assertThat(config.getUseAllocationSampleEvent()).isTrue(); + assertThat(config.getUseAllocationSampleEvent()) + .isEqualTo(ProfilerConfiguration.HAS_OBJECT_ALLOCATION_SAMPLE_EVENT); + assertThat(config.getConfigProperties()).isSameAs(profilingConfig); + } + + @Test + void shouldDisableProfilerWhenAlwaysOnIsMissing() { + OpenTelemetryConfigurationModel model = + DeclarativeConfigTestUtil.parse( + """ + file_format: "1.0" + distribution: + splunk: + profiling: + """); + + ProfilerConfiguration config = + ProfilerDeclarativeConfigurationFactory.create(getProfilingConfig(model)); - assertThrows(UnsupportedOperationException.class, config::getIngestUrl); - assertThrows(UnsupportedOperationException.class, config::getOtlpProtocol); + assertThat(config.isEnabled()).isFalse(); + assertThat(config.getMemoryEnabled()).isFalse(); + assertThat(config.getMemoryEventRateLimitEnabled()).isFalse(); + assertThat(config.getCallStackInterval()).isEqualTo(Duration.ofSeconds(10)); + assertThat(config.getRecordingDuration()).isEqualTo(Duration.ofSeconds(20)); } } diff --git a/profiler/src/test/java/com/splunk/opentelemetry/profiler/ProfilerEnvVarsConfigurationFactoryTest.java b/profiler/src/test/java/com/splunk/opentelemetry/profiler/ProfilerEnvVarsConfigurationFactoryTest.java new file mode 100644 index 000000000..c65c2731f --- /dev/null +++ b/profiler/src/test/java/com/splunk/opentelemetry/profiler/ProfilerEnvVarsConfigurationFactoryTest.java @@ -0,0 +1,182 @@ +/* + * 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; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import io.opentelemetry.common.ComponentLoader; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; +import java.time.Duration; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class ProfilerEnvVarsConfigurationFactoryTest { + private static final ComponentLoader COMPONENT_LOADER = + ComponentLoader.forClassLoader( + ProfilerEnvVarsConfigurationFactoryTest.class.getClassLoader()); + + String logsEndpoint = "http://logs.example.com"; + String otelEndpoint = "http://otel.example.com"; + String defaultLogsEndpoint = "http://localhost:4318/v1/logs"; + + @Test + void shouldMapPropertiesToConfiguration() { + ConfigProperties configProperties = + config( + Map.ofEntries( + Map.entry("splunk.profiler.enabled", "true"), + Map.entry("splunk.profiler.directory", "/tmp/prof"), + Map.entry("splunk.profiler.recording.duration", "12345ms"), + Map.entry("splunk.profiler.keep-files", "true"), + Map.entry("splunk.profiler.logs-endpoint", logsEndpoint), + Map.entry("splunk.profiler.otlp.protocol", "grpc"), + Map.entry("splunk.profiler.memory.enabled", "true"), + Map.entry("splunk.profiler.memory.event.rate-limit.enabled", "true"), + Map.entry("splunk.profiler.memory.event.rate", "250/s"), + Map.entry("splunk.profiler.memory.native.sampling", "true"), + Map.entry("splunk.profiler.call.stack.interval", "1410ms"), + Map.entry("splunk.profiler.include.agent.internals", "true"), + Map.entry("splunk.profiler.include.jvm.internals", "true"), + Map.entry("splunk.profiler.tracing.stacks.only", "true"), + Map.entry("splunk.profiler.max.stack.depth", "73"))); + + ProfilerConfiguration profilerConfiguration = + ProfilerEnvVarsConfigurationFactory.create(configProperties); + + assertThat(profilerConfiguration.isEnabled()).isTrue(); + assertThat(profilerConfiguration.getIngestUrl()).isEqualTo(logsEndpoint); + assertThat(profilerConfiguration.getOtlpProtocol()).isEqualTo("grpc"); + assertThat(profilerConfiguration.getMemoryEnabled()).isTrue(); + assertThat(profilerConfiguration.getMemoryEventRateLimitEnabled()).isTrue(); + assertThat(profilerConfiguration.getMemoryEventRate()).isEqualTo("250/s"); + assertThat(profilerConfiguration.getUseAllocationSampleEvent()) + .isEqualTo(ProfilerConfiguration.HAS_OBJECT_ALLOCATION_SAMPLE_EVENT); + assertThat(profilerConfiguration.getCallStackInterval()).isEqualTo(Duration.ofMillis(1410)); + assertThat(profilerConfiguration.getIncludeAgentInternalStacks()).isTrue(); + assertThat(profilerConfiguration.getIncludeJvmInternalStacks()).isTrue(); + assertThat(profilerConfiguration.getTracingStacksOnly()).isTrue(); + assertThat(profilerConfiguration.getStackDepth()).isEqualTo(73); + assertThat(profilerConfiguration.getKeepFiles()).isTrue(); + assertThat(profilerConfiguration.getProfilerDirectory()).isEqualTo("/tmp/prof"); + assertThat(profilerConfiguration.getRecordingDuration()).isEqualTo(Duration.ofMillis(12345)); + assertThat(profilerConfiguration.getConfigProperties()).isSameAs(configProperties); + } + + @Test + void getIngestUrl_endpointDefined() { + ProfilerConfiguration profilerConfiguration = + ProfilerEnvVarsConfigurationFactory.create( + config( + Map.of(ProfilerEnvVarsConfigurationFactory.CONFIG_KEY_INGEST_URL, logsEndpoint))); + + assertThat(profilerConfiguration.getIngestUrl()).isEqualTo(logsEndpoint); + } + + @Test + void getIngestUrl_endpointNotDefined_usedOtelGrpc() { + ProfilerConfiguration profilerConfiguration = + ProfilerEnvVarsConfigurationFactory.create( + config( + Map.of( + ProfilerEnvVarsConfigurationFactory.CONFIG_KEY_OTEL_OTLP_URL, + otelEndpoint, + ProfilerEnvVarsConfigurationFactory.CONFIG_KEY_PROFILER_OTLP_PROTOCOL, + "grpc"))); + + assertThat(profilerConfiguration.getIngestUrl()).isEqualTo(otelEndpoint); + } + + @Test + void getIngestUrl_endpointNotDefined_usedOtelHttpProtobuf() { + ProfilerConfiguration profilerConfiguration = + ProfilerEnvVarsConfigurationFactory.create( + config( + Map.of( + ProfilerEnvVarsConfigurationFactory.CONFIG_KEY_OTEL_OTLP_URL, + otelEndpoint, + ProfilerEnvVarsConfigurationFactory.CONFIG_KEY_PROFILER_OTLP_PROTOCOL, + "http/protobuf"))); + + assertThat(profilerConfiguration.getIngestUrl()).isEqualTo(otelEndpoint + "/v1/logs"); + } + + @Test + void getIngestUrl_endpointNotDefined_usedOtelHttpProtobufWithPath() { + String endpoint = otelEndpoint + "/v1/logs"; + + ProfilerConfiguration profilerConfiguration = + ProfilerEnvVarsConfigurationFactory.create( + config( + Map.of( + ProfilerEnvVarsConfigurationFactory.CONFIG_KEY_OTEL_OTLP_URL, + endpoint, + ProfilerEnvVarsConfigurationFactory.CONFIG_KEY_PROFILER_OTLP_PROTOCOL, + "http/protobuf"))); + + assertThat(profilerConfiguration.getIngestUrl()).isEqualTo(endpoint); + } + + @Test + void getIngestUrlSplunkRealm() { + ProfilerConfiguration profilerConfiguration = + ProfilerEnvVarsConfigurationFactory.create( + config( + Map.of( + ProfilerEnvVarsConfigurationFactory.CONFIG_KEY_OTEL_OTLP_URL, + "https://ingest.us0.observability.splunkcloud.com", + ProfilerEnvVarsConfigurationFactory.CONFIG_KEY_PROFILER_OTLP_PROTOCOL, + "http/protobuf"))); + + assertThat(profilerConfiguration.getIngestUrl()).isEqualTo(defaultLogsEndpoint); + } + + @Test + void getOtlpProtocolDefault() { + ProfilerConfiguration profilerConfiguration = + ProfilerEnvVarsConfigurationFactory.create(config(Collections.emptyMap())); + + assertThat(profilerConfiguration.getOtlpProtocol()).isEqualTo("http/protobuf"); + } + + @Test + void getOtlpProtocolOtelPropertySet() { + ProfilerConfiguration profilerConfiguration = + ProfilerEnvVarsConfigurationFactory.create( + config(Collections.singletonMap("otel.exporter.otlp.protocol", "test"))); + + assertThat(profilerConfiguration.getOtlpProtocol()).isEqualTo("test"); + } + + @Test + void getOtlpProtocol() { + Map map = new HashMap<>(); + map.put("otel.exporter.otlp.protocol", "test1"); + map.put("splunk.profiler.otlp.protocol", "test2"); + + ProfilerConfiguration profilerConfiguration = + ProfilerEnvVarsConfigurationFactory.create(config(map)); + + assertThat(profilerConfiguration.getOtlpProtocol()).isEqualTo("test2"); + } + + private static ConfigProperties config(Map map) { + return DefaultConfigProperties.create(map, COMPONENT_LOADER); + } +} diff --git a/profiler/src/test/java/com/splunk/opentelemetry/profiler/ProfilerEnvVarsConfigurationTest.java b/profiler/src/test/java/com/splunk/opentelemetry/profiler/ProfilerEnvVarsConfigurationTest.java deleted file mode 100644 index ff397b48b..000000000 --- a/profiler/src/test/java/com/splunk/opentelemetry/profiler/ProfilerEnvVarsConfigurationTest.java +++ /dev/null @@ -1,180 +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; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import io.opentelemetry.common.ComponentLoader; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; -import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import org.junit.jupiter.api.Test; - -class ProfilerEnvVarsConfigurationTest { - private static final ComponentLoader COMPONENT_LOADER = - ComponentLoader.forClassLoader(ProfilerEnvVarsConfigurationTest.class.getClassLoader()); - - String logsEndpoint = "http://logs.example.com"; - String otelEndpoint = "http://otel.example.com"; - String defaultLogsEndpoint = "http://localhost:4318/v1/logs"; - - @Test - void getIngestUrl_endpointDefined() { - // given - ConfigProperties config = mock(ConfigProperties.class); - ProfilerEnvVarsConfiguration profilerConfiguration = new ProfilerEnvVarsConfiguration(config); - when(config.getString(ProfilerEnvVarsConfiguration.CONFIG_KEY_INGEST_URL)) - .thenReturn(logsEndpoint); - - // when - String result = profilerConfiguration.getIngestUrl(); - - // then - assertThat(result).isEqualTo(logsEndpoint); - } - - @Test - void getIngestUrl_endpointNotDefined_usedOtelGrpc() { - // given - ConfigProperties config = mock(ConfigProperties.class); - ProfilerEnvVarsConfiguration profilerConfiguration = new ProfilerEnvVarsConfiguration(config); - when(config.getString(ProfilerEnvVarsConfiguration.CONFIG_KEY_INGEST_URL)).thenReturn(null); - when(config.getString(eq(ProfilerEnvVarsConfiguration.CONFIG_KEY_OTEL_OTLP_URL), anyString())) - .thenReturn(otelEndpoint); - when(config.getString( - eq(ProfilerEnvVarsConfiguration.CONFIG_KEY_PROFILER_OTLP_PROTOCOL), any())) - .thenReturn("grpc"); - - // when - String result = profilerConfiguration.getIngestUrl(); - - // then - assertThat(result).isEqualTo(otelEndpoint); - } - - @Test - void getIngestUrl_endpointNotDefined_usedOtelHttpProtobuf() { - // given - ConfigProperties config = mock(ConfigProperties.class); - ProfilerEnvVarsConfiguration profilerConfiguration = new ProfilerEnvVarsConfiguration(config); - when(config.getString(ProfilerEnvVarsConfiguration.CONFIG_KEY_INGEST_URL)).thenReturn(null); - when(config.getString(eq(ProfilerEnvVarsConfiguration.CONFIG_KEY_OTEL_OTLP_URL), anyString())) - .thenReturn(otelEndpoint); - when(config.getString( - eq(ProfilerEnvVarsConfiguration.CONFIG_KEY_PROFILER_OTLP_PROTOCOL), any())) - .thenReturn("http/protobuf"); - - // when - String result = profilerConfiguration.getIngestUrl(); - - // then - assertThat(result).isEqualTo(otelEndpoint + "/v1/logs"); - } - - @Test - void getIngestUrl_endpointNotDefined_usedOtelHttpProtobufWithPath() { - // given - String endpoint = otelEndpoint + "/v1/logs"; - - ConfigProperties config = mock(ConfigProperties.class); - ProfilerEnvVarsConfiguration profilerConfiguration = new ProfilerEnvVarsConfiguration(config); - when(config.getString(ProfilerEnvVarsConfiguration.CONFIG_KEY_INGEST_URL)).thenReturn(null); - when(config.getString(eq(ProfilerEnvVarsConfiguration.CONFIG_KEY_OTEL_OTLP_URL), anyString())) - .thenReturn(endpoint); - when(config.getString( - eq(ProfilerEnvVarsConfiguration.CONFIG_KEY_PROFILER_OTLP_PROTOCOL), any())) - .thenReturn("http/protobuf"); - - // when - String result = profilerConfiguration.getIngestUrl(); - - // then - assertThat(result).isEqualTo(endpoint); - } - - @Test - void getIngestUrlSplunkRealm() { - // given - ConfigProperties config = mock(ConfigProperties.class); - ProfilerEnvVarsConfiguration profilerConfiguration = new ProfilerEnvVarsConfiguration(config); - when(config.getString(ProfilerEnvVarsConfiguration.CONFIG_KEY_INGEST_URL)).thenReturn(null); - when(config.getString(eq(ProfilerEnvVarsConfiguration.CONFIG_KEY_OTEL_OTLP_URL), anyString())) - .thenReturn("https://ingest.us0.observability.splunkcloud.com"); - when(config.getString( - eq(ProfilerEnvVarsConfiguration.CONFIG_KEY_PROFILER_OTLP_PROTOCOL), any())) - .thenReturn("http/protobuf"); - - // when - String result = profilerConfiguration.getIngestUrl(); - - // then - assertThat(result).isEqualTo(defaultLogsEndpoint); - } - - @Test - void getOtlpProtocolDefault() { - // given - ProfilerEnvVarsConfiguration profilerConfiguration = - new ProfilerEnvVarsConfiguration( - DefaultConfigProperties.create(Collections.emptyMap(), COMPONENT_LOADER)); - - // when - String result = profilerConfiguration.getOtlpProtocol(); - - // then - assertThat(result).isEqualTo("http/protobuf"); - } - - @Test - void getOtlpProtocolOtelPropertySet() { - // given - ProfilerEnvVarsConfiguration profilerConfiguration = - new ProfilerEnvVarsConfiguration( - DefaultConfigProperties.create( - Collections.singletonMap("otel.exporter.otlp.protocol", "test"), COMPONENT_LOADER)); - - // when - String result = profilerConfiguration.getOtlpProtocol(); - - // then - assertThat(result).isEqualTo("test"); - } - - @Test - void getOtlpProtocol() { - // given - Map map = new HashMap<>(); - map.put("otel.exporter.otlp.protocol", "test1"); - map.put("splunk.profiler.otlp.protocol", "test2"); - - ProfilerEnvVarsConfiguration profilerConfiguration = - new ProfilerEnvVarsConfiguration(DefaultConfigProperties.create(map, COMPONENT_LOADER)); - - // when - String result = profilerConfiguration.getOtlpProtocol(); - - // then - assertThat(result).isEqualTo("test2"); - } -} diff --git a/profiler/src/test/java/com/splunk/opentelemetry/profiler/ProfilingSupervisorTest.java b/profiler/src/test/java/com/splunk/opentelemetry/profiler/ProfilingSupervisorTest.java index 3d2e133e5..1d4a1d0b1 100644 --- a/profiler/src/test/java/com/splunk/opentelemetry/profiler/ProfilingSupervisorTest.java +++ b/profiler/src/test/java/com/splunk/opentelemetry/profiler/ProfilingSupervisorTest.java @@ -21,10 +21,12 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import com.splunk.opentelemetry.profiler.util.OptionalConfigurableSupplier; import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; import io.opentelemetry.sdk.resources.Resource; import java.nio.file.Path; @@ -45,7 +47,7 @@ class ProfilingSupervisorTest { @Mock JFR jfr; - TestProfilingConfig config; + ProfilerConfiguration config; TestPeriodicRecordingFlusherBuilder builder; AutoConfiguredOpenTelemetrySdk sdk; ExecutorService executor; @@ -53,8 +55,14 @@ class ProfilingSupervisorTest { @BeforeEach void setUp(@TempDir Path tempDir) { - config = new TestProfilingConfig(); - config.profilerDirectory = tempDir.toString(); + config = + spy( + ProfilerConfiguration.builder() + .setEnabled(true) + .setProfilerDirectory(tempDir.toString()) + .setStackDepth(4321) + .setRecordingDuration(Duration.ofMinutes(1)) + .build()); builder = new TestPeriodicRecordingFlusherBuilder(config, mock(Resource.class)); executor = Executors.newSingleThreadExecutor(); sdk = @@ -88,21 +96,19 @@ void requestStartDoesNotStartProfilerWhenJfrIsUnavailable() { supervisor.requestStartProfiling(); await().untilAsserted(() -> verify(jfr).isAvailable()); - assertThat(config.logCalled).isFalse(); + verify(config, never()).log(); verify(jfr, never()).setStackDepth(anyInt()); assertThat(builder.buildCalled).isFalse(); } @Test void requestStartProfilingBuildsAndStartsRecordingSequencer() { - config.stackDepth = 4321; - config.recordingDuration = Duration.ofMinutes(1); when(jfr.isAvailable()).thenReturn(true); supervisor.requestStartProfiling(); await().untilAsserted(() -> assertThat(builder.buildCalled).isTrue()); - assertThat(config.logCalled).isTrue(); + verify(config).log(); assertThat(builder.jfr).isSameAs(jfr); assertThat(builder.config).isSameAs(config); assertThat(builder.resource).isNotNull(); @@ -156,7 +162,7 @@ private static class TestProfilingSupervisor extends ProfilingSupervisor { JFR jfr, AutoConfiguredOpenTelemetrySdk sdk, PeriodicRecordingFlusherBuilder builder) { - super(config, jfr, sdk, new LinkedBlockingQueue<>()); + super(configSupplier(config), jfr, sdk, new LinkedBlockingQueue<>()); this.builder = builder; } @@ -164,6 +170,14 @@ private static class TestProfilingSupervisor extends ProfilingSupervisor { PeriodicRecordingFlusherBuilder makeRecordingFlusherBuilder(Resource resource) { return builder; } + + private static OptionalConfigurableSupplier configSupplier( + ProfilerConfiguration config) { + OptionalConfigurableSupplier supplier = + new OptionalConfigurableSupplier<>(); + supplier.configure(config); + return supplier; + } } private static class TestPeriodicRecordingFlusherBuilder extends PeriodicRecordingFlusherBuilder { diff --git a/profiler/src/test/java/com/splunk/opentelemetry/profiler/TestProfilingConfig.java b/profiler/src/test/java/com/splunk/opentelemetry/profiler/TestProfilingConfig.java deleted file mode 100644 index 20cd32291..000000000 --- a/profiler/src/test/java/com/splunk/opentelemetry/profiler/TestProfilingConfig.java +++ /dev/null @@ -1,130 +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; - -import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; -import java.time.Duration; -import java.util.Map; - -class TestProfilingConfig implements ProfilerConfiguration { - - boolean logCalled; - String profilerDirectory; - boolean keepFiles; - int stackDepth = 1024; - Duration recordingDuration = Duration.ofDays(1); - Duration callStackInterval = Duration.ofSeconds(10); - boolean includeAgentInternalStacks; - boolean includeJvmInternalStacks; - boolean tracingStacksOnly; - boolean memoryEnabled; - boolean memoryEventRateLimitEnabled = true; - String memoryEventRate = "150/s"; - boolean useAllocationSampleEvent; - Object configProperties = - DefaultConfigProperties.createFromMap( - Map.of( - "otel.exporter.otlp.protocol", - "http/protobuf", - "otel.exporter.otlp.endpoint", - "http://localhost:4318")); - - @Override - public boolean isEnabled() { - return true; - } - - @Override - public void log() { - logCalled = true; - } - - @Override - public String getIngestUrl() { - return "http://localhost:4318/v1/logs"; - } - - @Override - public String getOtlpProtocol() { - return "http/protobuf"; - } - - @Override - public boolean getMemoryEnabled() { - return memoryEnabled; - } - - @Override - public boolean getMemoryEventRateLimitEnabled() { - return memoryEventRateLimitEnabled; - } - - @Override - public String getMemoryEventRate() { - return memoryEventRate; - } - - @Override - public boolean getUseAllocationSampleEvent() { - return useAllocationSampleEvent; - } - - @Override - public Duration getCallStackInterval() { - return callStackInterval; - } - - @Override - public boolean getIncludeAgentInternalStacks() { - return includeAgentInternalStacks; - } - - @Override - public boolean getIncludeJvmInternalStacks() { - return includeJvmInternalStacks; - } - - @Override - public boolean getTracingStacksOnly() { - return tracingStacksOnly; - } - - @Override - public int getStackDepth() { - return stackDepth; - } - - @Override - public boolean getKeepFiles() { - return keepFiles; - } - - @Override - public String getProfilerDirectory() { - return profilerDirectory; - } - - @Override - public Duration getRecordingDuration() { - return recordingDuration; - } - - @Override - public Object getConfigProperties() { - return configProperties; - } -} 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 327e9bc68..70f6a8934 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 @@ -66,7 +66,7 @@ private StackTraceSampler newSampler(StagingArea staging) { .with( new StackTraceExporterActivator( new OtelLoggerFactory( - properties -> logExporter, declarativeConfigProperties -> logExporter))) + () -> logExporter, declarativeConfigProperties -> logExporter))) .build(); @RegisterExtension 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 c54ad34dc..5b80afa7a 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 @@ -43,7 +43,7 @@ class GracefulShutdownTest { .with( new StackTraceExporterActivator( new OtelLoggerFactory( - properties -> logExporter, declarativeConfigProperties -> logExporter))) + () -> logExporter, declarativeConfigProperties -> logExporter))) .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 ee1b18bab..db71b2917 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 @@ -63,7 +63,7 @@ class SnapshotProfilingLogExportingTest { .with( new StackTraceExporterActivator( new OtelLoggerFactory( - properties -> logExporter, declarativeConfigProperties -> logExporter))) + () -> logExporter, declarativeConfigProperties -> logExporter))) .build(); @AfterEach From 7af278ecfda63262b5ebe6bc00a4563769da53ef Mon Sep 17 00:00:00 2001 From: robsunday Date: Wed, 24 Jun 2026 14:18:47 +0200 Subject: [PATCH 02/13] Licenses refreshed --- .../META-INF/LICENSE.txt | 0 .../META-INF/NOTICE.txt | 0 licenses/licenses.md | 8 ++++---- 3 files changed, 4 insertions(+), 4 deletions(-) rename licenses/{commons-jexl3-3.6.2.jar => commons-jexl3-3.6.3.jar}/META-INF/LICENSE.txt (100%) rename licenses/{commons-jexl3-3.6.2.jar => commons-jexl3-3.6.3.jar}/META-INF/NOTICE.txt (100%) diff --git a/licenses/commons-jexl3-3.6.2.jar/META-INF/LICENSE.txt b/licenses/commons-jexl3-3.6.3.jar/META-INF/LICENSE.txt similarity index 100% rename from licenses/commons-jexl3-3.6.2.jar/META-INF/LICENSE.txt rename to licenses/commons-jexl3-3.6.3.jar/META-INF/LICENSE.txt diff --git a/licenses/commons-jexl3-3.6.2.jar/META-INF/NOTICE.txt b/licenses/commons-jexl3-3.6.3.jar/META-INF/NOTICE.txt similarity index 100% rename from licenses/commons-jexl3-3.6.2.jar/META-INF/NOTICE.txt rename to licenses/commons-jexl3-3.6.3.jar/META-INF/NOTICE.txt diff --git a/licenses/licenses.md b/licenses/licenses.md index 91bf2d05f..a79680819 100644 --- a/licenses/licenses.md +++ b/licenses/licenses.md @@ -1,7 +1,7 @@ # splunk-otel-javaagent ## Dependency License Report -_2026-06-20 10:34:26 EEST_ +_2026-06-24 14:18:04 CEST_ ## Apache License, Version 2.0 **1** **Group:** `com.squareup.okhttp3` **Name:** `okhttp` **Version:** `5.4.0` @@ -92,12 +92,12 @@ _2026-06-20 10:34:26 EEST_ > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java-instrumentation](https://github.com/open-telemetry/opentelemetry-java-instrumentation) > - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) -**23** **Group:** `org.apache.commons` **Name:** `commons-jexl3` **Version:** `3.6.2` +**23** **Group:** `org.apache.commons` **Name:** `commons-jexl3` **Version:** `3.6.3` > - **Project URL**: [https://commons.apache.org/proper/commons-jexl/](https://commons.apache.org/proper/commons-jexl/) > - **Manifest License**: [https://www.apache.org/licenses/LICENSE-2.0](Apache License, Version 2.0) > - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) -> - **Embedded license files**: [commons-jexl3-3.6.2.jar/META-INF/LICENSE.txt](commons-jexl3-3.6.2.jar/META-INF/LICENSE.txt) - - [commons-jexl3-3.6.2.jar/META-INF/NOTICE.txt](commons-jexl3-3.6.2.jar/META-INF/NOTICE.txt) +> - **Embedded license files**: [commons-jexl3-3.6.3.jar/META-INF/LICENSE.txt](commons-jexl3-3.6.3.jar/META-INF/LICENSE.txt) + - [commons-jexl3-3.6.3.jar/META-INF/NOTICE.txt](commons-jexl3-3.6.3.jar/META-INF/NOTICE.txt) **24** **Group:** `org.jetbrains` **Name:** `annotations` **Version:** `13.0` > - **POM Project URL**: [http://www.jetbrains.org](http://www.jetbrains.org) From b4a88a32a2ecd7da3ed3a1a3e8336d6b86105b8d Mon Sep 17 00:00:00 2001 From: robsunday Date: Wed, 24 Jun 2026 15:31:36 +0200 Subject: [PATCH 03/13] Feedback after remote config sent. --- .../DeclarativeConfigurationInterceptor.java | 2 +- .../opentelemetry/opamp/OpampActivator.java | 32 ++--- .../opamp/RemoteConfigProcessor.java | 91 +++++++++----- .../opamp/ServerToAgentMessageHandler.java | 6 +- ...DeclarativeEffectiveConfigFileFactory.java | 3 +- .../EffectiveConfigBuilder.java | 2 +- .../EffectiveConfigFactory.java | 2 +- .../EffectiveConfigReporter.java | 75 ++++++++++++ .../EnvVarsEffectiveConfigFileFactory.java | 6 +- .../UpdatableEffectiveConfigState.java | 34 ++++++ .../YamlNodeBuilder.java | 2 +- .../opamp/OpampActivatorTest.java | 10 +- .../opamp/RemoteConfigProcessorTest.java | 112 ++++++++++++++++-- ...arativeEffectiveConfigFileFactoryTest.java | 3 +- ...EnvVarsEffectiveConfigFileFactoryTest.java | 11 +- .../YamlNodeBuilderTest.java | 2 +- 16 files changed, 313 insertions(+), 80 deletions(-) rename opamp/src/main/java/com/splunk/opentelemetry/opamp/{ => effectiveconfig}/DeclarativeEffectiveConfigFileFactory.java (98%) rename opamp/src/main/java/com/splunk/opentelemetry/opamp/{ => effectiveconfig}/EffectiveConfigBuilder.java (95%) rename opamp/src/main/java/com/splunk/opentelemetry/opamp/{ => effectiveconfig}/EffectiveConfigFactory.java (97%) create mode 100644 opamp/src/main/java/com/splunk/opentelemetry/opamp/effectiveconfig/EffectiveConfigReporter.java rename opamp/src/main/java/com/splunk/opentelemetry/opamp/{ => effectiveconfig}/EnvVarsEffectiveConfigFileFactory.java (95%) create mode 100644 opamp/src/main/java/com/splunk/opentelemetry/opamp/effectiveconfig/UpdatableEffectiveConfigState.java rename opamp/src/main/java/com/splunk/opentelemetry/opamp/{ => effectiveconfig}/YamlNodeBuilder.java (99%) rename opamp/src/test/java/com/splunk/opentelemetry/opamp/{ => effectiveconfig}/DeclarativeEffectiveConfigFileFactoryTest.java (99%) rename opamp/src/test/java/com/splunk/opentelemetry/opamp/{ => effectiveconfig}/EnvVarsEffectiveConfigFileFactoryTest.java (94%) rename opamp/src/test/java/com/splunk/opentelemetry/opamp/{ => effectiveconfig}/YamlNodeBuilderTest.java (98%) diff --git a/opamp/src/main/java/com/splunk/opentelemetry/opamp/DeclarativeConfigurationInterceptor.java b/opamp/src/main/java/com/splunk/opentelemetry/opamp/DeclarativeConfigurationInterceptor.java index b3a46a6e1..ffb3e452b 100644 --- a/opamp/src/main/java/com/splunk/opentelemetry/opamp/DeclarativeConfigurationInterceptor.java +++ b/opamp/src/main/java/com/splunk/opentelemetry/opamp/DeclarativeConfigurationInterceptor.java @@ -35,7 +35,7 @@ public static OpenTelemetryConfigurationModel getConfigurationModel() { } @VisibleForTesting - static void reset() { + public static void reset() { configurationModel = null; } diff --git a/opamp/src/main/java/com/splunk/opentelemetry/opamp/OpampActivator.java b/opamp/src/main/java/com/splunk/opentelemetry/opamp/OpampActivator.java index b7d4452ef..d98bd2a60 100644 --- a/opamp/src/main/java/com/splunk/opentelemetry/opamp/OpampActivator.java +++ b/opamp/src/main/java/com/splunk/opentelemetry/opamp/OpampActivator.java @@ -17,11 +17,12 @@ package com.splunk.opentelemetry.opamp; import static io.opentelemetry.opamp.client.internal.request.service.HttpRequestService.DEFAULT_DELAY_BETWEEN_RETRIES; -import static io.opentelemetry.sdk.autoconfigure.AutoConfigureUtil.getConfig; import static io.opentelemetry.sdk.autoconfigure.AutoConfigureUtil.getResource; import static java.util.logging.Level.WARNING; import com.google.auto.service.AutoService; +import com.splunk.opentelemetry.opamp.effectiveconfig.EffectiveConfigReporter; +import com.splunk.opentelemetry.opamp.effectiveconfig.UpdatableEffectiveConfigState; import com.splunk.opentelemetry.profiler.ProfilingSupervisor; import io.opentelemetry.javaagent.extension.AgentListener; import io.opentelemetry.opamp.client.OpampClient; @@ -31,7 +32,6 @@ import io.opentelemetry.opamp.client.internal.request.service.HttpRequestService; import io.opentelemetry.opamp.client.internal.response.MessageData; import io.opentelemetry.opamp.client.internal.state.State; -import io.opentelemetry.sdk.autoconfigure.AutoConfigureUtil; import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; import io.opentelemetry.sdk.resources.Resource; import java.io.IOException; @@ -55,18 +55,20 @@ public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetr } Resource resource = getResource(autoConfiguredOpenTelemetrySdk); - EffectiveConfigFactory effectiveConfigFactory = - createEffectiveConfigFactory(autoConfiguredOpenTelemetrySdk); - State.EffectiveConfig effectiveConfig = buildEffectiveConfig(effectiveConfigFactory); + UpdatableEffectiveConfigState effectiveConfigState = new UpdatableEffectiveConfigState(); + EffectiveConfigReporter effectiveConfigReporter = + EffectiveConfigReporter.create(autoConfiguredOpenTelemetrySdk, effectiveConfigState); + effectiveConfigReporter.reportEffectiveConfigIfChanged(); ServerToAgentMessageHandler serverToAgentMessageHandler = - new ServerToAgentMessageHandler(ProfilingSupervisor.SUPPLIER.get()); + new ServerToAgentMessageHandler( + ProfilingSupervisor.SUPPLIER.get(), effectiveConfigReporter); OpampClient client = startOpampClient( opampClientConfiguration, resource, - effectiveConfig, + effectiveConfigState, new OpampClient.Callbacks() { @Override public void onConnect(OpampClient opampClient) { @@ -108,13 +110,6 @@ public int order() { return Integer.MAX_VALUE; } - private EffectiveConfigFactory createEffectiveConfigFactory(AutoConfiguredOpenTelemetrySdk sdk) { - if (AutoConfigureUtil.isDeclarativeConfig(sdk)) { - return new DeclarativeEffectiveConfigFileFactory(); - } - return new EnvVarsEffectiveConfigFileFactory(getConfig(sdk)); - } - static OpampClient startOpampClient( OpampClientConfiguration opampClientConfiguration, Resource resource, @@ -146,15 +141,6 @@ static OpampClient startOpampClient( return builder.build(callbacks); } - static State.EffectiveConfig buildEffectiveConfig(EffectiveConfigFactory effectiveConfigFactory) { - return new State.EffectiveConfig() { - @Override - public opamp.proto.EffectiveConfig get() { - return new opamp.proto.EffectiveConfig(effectiveConfigFactory.createEffectiveConfigMap()); - } - }; - } - private static ComponentHealth createInitialHealthReport() { Instant now = Instant.now(); long nowNanos = now.getEpochSecond() * 1_000_000_000L + now.getNano(); diff --git a/opamp/src/main/java/com/splunk/opentelemetry/opamp/RemoteConfigProcessor.java b/opamp/src/main/java/com/splunk/opentelemetry/opamp/RemoteConfigProcessor.java index 92881a41e..adbfcff32 100644 --- a/opamp/src/main/java/com/splunk/opentelemetry/opamp/RemoteConfigProcessor.java +++ b/opamp/src/main/java/com/splunk/opentelemetry/opamp/RemoteConfigProcessor.java @@ -19,6 +19,7 @@ import static io.opentelemetry.api.incubator.config.DeclarativeConfigProperties.empty; import com.google.common.annotations.VisibleForTesting; +import com.splunk.opentelemetry.opamp.effectiveconfig.EffectiveConfigReporter; import com.splunk.opentelemetry.profiler.ProfilerConfiguration; import com.splunk.opentelemetry.profiler.ProfilerDeclarativeConfigurationFactory; import com.splunk.opentelemetry.profiler.ProfilingSupervisor; @@ -29,6 +30,7 @@ import java.util.Map; import java.util.Objects; import java.util.logging.Logger; +import okio.ByteString; import opamp.proto.AgentConfigFile; import opamp.proto.AgentRemoteConfig; import opamp.proto.RemoteConfigStatus; @@ -40,10 +42,13 @@ public class RemoteConfigProcessor { private static final String REMOTE_CONFIG_FILE_NAME = "splunk.remote.config"; private static final String PROFILING_NODE_NAME = "profiling"; - public ProfilingSupervisor profilingSupervisor; + private final ProfilingSupervisor profilingSupervisor; + private final EffectiveConfigReporter effectiveConfigReporter; - public RemoteConfigProcessor(ProfilingSupervisor profilingSupervisor) { + public RemoteConfigProcessor( + ProfilingSupervisor profilingSupervisor, EffectiveConfigReporter effectiveConfigReporter) { this.profilingSupervisor = Objects.requireNonNull(profilingSupervisor); + this.effectiveConfigReporter = Objects.requireNonNull(effectiveConfigReporter); } public void applyConfig(AgentRemoteConfig remoteConfig, OpampClient opampClient) { @@ -60,34 +65,53 @@ public void applyConfig(AgentRemoteConfig remoteConfig, OpampClient opampClient) return; } - DeclarativeConfigProperties remoteConfigProperties = toDeclarativeConfigProperties(configFile); - DeclarativeConfigProperties splunkDistributionConfigProperties = - remoteConfigProperties - .getStructured("distribution", empty()) - .getStructured("splunk", empty()); - - // Update profiler configuration only when profiling node exists - if (splunkDistributionConfigProperties.getPropertyKeys().contains(PROFILING_NODE_NAME)) { - DeclarativeConfigProperties profilingConfigProperties = - splunkDistributionConfigProperties.getStructured(PROFILING_NODE_NAME, empty()); - ProfilerConfiguration profilingConfig = - ProfilerDeclarativeConfigurationFactory.create(profilingConfigProperties); - // TODO: should be merged with current profiling config. Probably we will need profiler - // configuration refactoring and some listeners implemented for profiler configuration - // changes. For POC use this temporary solution - if (profilingConfig.isEnabled()) { - profilingSupervisor.requestStartProfiling(); - } else { - profilingSupervisor.requestStopProfiling(); + try { + DeclarativeConfigProperties remoteConfigProperties = + toDeclarativeConfigProperties(configFile); + DeclarativeConfigProperties distributionRemoteConfigProperties = + remoteConfigProperties + .getStructured("distribution", empty()) + .getStructured("splunk", empty()); + + // Update profiler configuration only when profiling node exists + if (distributionRemoteConfigProperties.getPropertyKeys().contains(PROFILING_NODE_NAME)) { + ProfilerConfiguration receivedProfilerConfig = + ProfilerDeclarativeConfigurationFactory.create( + distributionRemoteConfigProperties.getStructured(PROFILING_NODE_NAME, empty())); + + ProfilerConfiguration updatedProfilerConfig = + ProfilerConfiguration.SUPPLIER + .get() + .newBuilder() + .setEnabled(receivedProfilerConfig.isEnabled()) + .build(); + ProfilerConfiguration.SUPPLIER.configure(updatedProfilerConfig); + + if (updatedProfilerConfig.isEnabled()) { + profilingSupervisor.requestStartProfiling(); + } else { + profilingSupervisor.requestStopProfiling(); + } } + + // Confirm to the OpAMP Server that remote config has been applied. + reportRemoteConfigStatus( + remoteConfig.config_hash, + RemoteConfigStatuses.RemoteConfigStatuses_APPLIED, + "", + opampClient); + + } catch (Exception e) { + reportRemoteConfigStatus( + remoteConfig.config_hash, + RemoteConfigStatuses.RemoteConfigStatuses_FAILED, + "Exception occurred: " + e.getMessage(), + opampClient); + throw e; } - // Confirm to the OpAMP Server that remote config has been applied. - opampClient.setRemoteConfigStatus( - new RemoteConfigStatus.Builder() - .last_remote_config_hash(remoteConfig.config_hash) - .status(RemoteConfigStatuses.RemoteConfigStatuses_APPLIED) - .build()); + // TODO: Maybe should be postponed after profiler is enabled/disabled? + effectiveConfigReporter.reportEffectiveConfigIfChanged(); } @VisibleForTesting @@ -95,4 +119,17 @@ static DeclarativeConfigProperties toDeclarativeConfigProperties(AgentConfigFile return DeclarativeConfiguration.toConfigProperties( new ByteArrayInputStream(configFile.body.toByteArray())); } + + private void reportRemoteConfigStatus( + ByteString configHash, + RemoteConfigStatuses status, + String errorMessage, + OpampClient opampClient) { + opampClient.setRemoteConfigStatus( + new RemoteConfigStatus.Builder() + .last_remote_config_hash(configHash) + .error_message(errorMessage) + .status(status) + .build()); + } } diff --git a/opamp/src/main/java/com/splunk/opentelemetry/opamp/ServerToAgentMessageHandler.java b/opamp/src/main/java/com/splunk/opentelemetry/opamp/ServerToAgentMessageHandler.java index f0ab02d36..30d938135 100644 --- a/opamp/src/main/java/com/splunk/opentelemetry/opamp/ServerToAgentMessageHandler.java +++ b/opamp/src/main/java/com/splunk/opentelemetry/opamp/ServerToAgentMessageHandler.java @@ -17,6 +17,7 @@ package com.splunk.opentelemetry.opamp; import com.google.common.annotations.VisibleForTesting; +import com.splunk.opentelemetry.opamp.effectiveconfig.EffectiveConfigReporter; import com.splunk.opentelemetry.profiler.ProfilingSupervisor; import io.opentelemetry.opamp.client.OpampClient; import io.opentelemetry.opamp.client.internal.response.MessageData; @@ -24,8 +25,9 @@ public class ServerToAgentMessageHandler { private final RemoteConfigProcessor remoteConfigProcessor; - public ServerToAgentMessageHandler(ProfilingSupervisor profilingSupervisor) { - this(new RemoteConfigProcessor(profilingSupervisor)); + public ServerToAgentMessageHandler( + ProfilingSupervisor profilingSupervisor, EffectiveConfigReporter effectiveConfigReporter) { + this(new RemoteConfigProcessor(profilingSupervisor, effectiveConfigReporter)); } @VisibleForTesting diff --git a/opamp/src/main/java/com/splunk/opentelemetry/opamp/DeclarativeEffectiveConfigFileFactory.java b/opamp/src/main/java/com/splunk/opentelemetry/opamp/effectiveconfig/DeclarativeEffectiveConfigFileFactory.java similarity index 98% rename from opamp/src/main/java/com/splunk/opentelemetry/opamp/DeclarativeEffectiveConfigFileFactory.java rename to opamp/src/main/java/com/splunk/opentelemetry/opamp/effectiveconfig/DeclarativeEffectiveConfigFileFactory.java index 7ab53d8af..7fe5d6ebe 100644 --- a/opamp/src/main/java/com/splunk/opentelemetry/opamp/DeclarativeEffectiveConfigFileFactory.java +++ b/opamp/src/main/java/com/splunk/opentelemetry/opamp/effectiveconfig/DeclarativeEffectiveConfigFileFactory.java @@ -14,9 +14,10 @@ * limitations under the License. */ -package com.splunk.opentelemetry.opamp; +package com.splunk.opentelemetry.opamp.effectiveconfig; import com.google.common.annotations.VisibleForTesting; +import com.splunk.opentelemetry.opamp.DeclarativeConfigurationInterceptor; import com.splunk.opentelemetry.profiler.ProfilerConfiguration; import com.splunk.opentelemetry.profiler.snapshot.SnapshotProfilingConfiguration; import com.splunk.opentelemetry.profiler.snapshot.SnapshotProfilingDeclarativeConfiguration; diff --git a/opamp/src/main/java/com/splunk/opentelemetry/opamp/EffectiveConfigBuilder.java b/opamp/src/main/java/com/splunk/opentelemetry/opamp/effectiveconfig/EffectiveConfigBuilder.java similarity index 95% rename from opamp/src/main/java/com/splunk/opentelemetry/opamp/EffectiveConfigBuilder.java rename to opamp/src/main/java/com/splunk/opentelemetry/opamp/effectiveconfig/EffectiveConfigBuilder.java index 08ab87bbb..8bfa48a54 100644 --- a/opamp/src/main/java/com/splunk/opentelemetry/opamp/EffectiveConfigBuilder.java +++ b/opamp/src/main/java/com/splunk/opentelemetry/opamp/effectiveconfig/EffectiveConfigBuilder.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.splunk.opentelemetry.opamp; +package com.splunk.opentelemetry.opamp.effectiveconfig; import java.time.Duration; diff --git a/opamp/src/main/java/com/splunk/opentelemetry/opamp/EffectiveConfigFactory.java b/opamp/src/main/java/com/splunk/opentelemetry/opamp/effectiveconfig/EffectiveConfigFactory.java similarity index 97% rename from opamp/src/main/java/com/splunk/opentelemetry/opamp/EffectiveConfigFactory.java rename to opamp/src/main/java/com/splunk/opentelemetry/opamp/effectiveconfig/EffectiveConfigFactory.java index 13c454783..a68a7466f 100644 --- a/opamp/src/main/java/com/splunk/opentelemetry/opamp/EffectiveConfigFactory.java +++ b/opamp/src/main/java/com/splunk/opentelemetry/opamp/effectiveconfig/EffectiveConfigFactory.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.splunk.opentelemetry.opamp; +package com.splunk.opentelemetry.opamp.effectiveconfig; import static java.nio.charset.StandardCharsets.UTF_8; diff --git a/opamp/src/main/java/com/splunk/opentelemetry/opamp/effectiveconfig/EffectiveConfigReporter.java b/opamp/src/main/java/com/splunk/opentelemetry/opamp/effectiveconfig/EffectiveConfigReporter.java new file mode 100644 index 000000000..587021bd9 --- /dev/null +++ b/opamp/src/main/java/com/splunk/opentelemetry/opamp/effectiveconfig/EffectiveConfigReporter.java @@ -0,0 +1,75 @@ +/* + * 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.opamp.effectiveconfig; + +import static io.opentelemetry.sdk.autoconfigure.AutoConfigureUtil.getConfig; +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.google.common.annotations.VisibleForTesting; +import io.opentelemetry.sdk.autoconfigure.AutoConfigureUtil; +import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; +import java.util.HashMap; +import java.util.Map; +import okio.ByteString; +import opamp.proto.AgentConfigFile; +import opamp.proto.AgentConfigMap; + +public class EffectiveConfigReporter { + private final UpdatableEffectiveConfigState effectiveConfigState; + private final EffectiveConfigFactory effectiveConfigFactory; + private String lastReportedConfigContent; + + @VisibleForTesting + EffectiveConfigReporter( + EffectiveConfigFactory effectiveConfigFactory, + UpdatableEffectiveConfigState effectiveConfigState) { + this.effectiveConfigFactory = effectiveConfigFactory; + this.effectiveConfigState = effectiveConfigState; + } + + public static EffectiveConfigReporter create( + AutoConfiguredOpenTelemetrySdk sdk, UpdatableEffectiveConfigState effectiveConfigState) { + return new EffectiveConfigReporter(createEffectiveConfigFactory(sdk), effectiveConfigState); + } + + public boolean reportEffectiveConfigIfChanged() { + // Detect if effectiveConfig changed and needs to be reported + String configContent = effectiveConfigFactory.createEffectiveConfigContent(); + if (configContent.equals(lastReportedConfigContent)) { + return false; + } + + Map configMap = new HashMap<>(); + AgentConfigFile configFile = + new AgentConfigFile( + new ByteString(configContent.getBytes(UTF_8)), effectiveConfigFactory.getContentType()); + configMap.put(effectiveConfigFactory.getFileName(), configFile); + + effectiveConfigState.set(new AgentConfigMap(configMap)); + lastReportedConfigContent = configContent; + + return true; + } + + private static EffectiveConfigFactory createEffectiveConfigFactory( + AutoConfiguredOpenTelemetrySdk sdk) { + if (AutoConfigureUtil.isDeclarativeConfig(sdk)) { + return new DeclarativeEffectiveConfigFileFactory(); + } + return new EnvVarsEffectiveConfigFileFactory(getConfig(sdk)); + } +} diff --git a/opamp/src/main/java/com/splunk/opentelemetry/opamp/EnvVarsEffectiveConfigFileFactory.java b/opamp/src/main/java/com/splunk/opentelemetry/opamp/effectiveconfig/EnvVarsEffectiveConfigFileFactory.java similarity index 95% rename from opamp/src/main/java/com/splunk/opentelemetry/opamp/EnvVarsEffectiveConfigFileFactory.java rename to opamp/src/main/java/com/splunk/opentelemetry/opamp/effectiveconfig/EnvVarsEffectiveConfigFileFactory.java index 021a5ae1a..687fda85a 100644 --- a/opamp/src/main/java/com/splunk/opentelemetry/opamp/EnvVarsEffectiveConfigFileFactory.java +++ b/opamp/src/main/java/com/splunk/opentelemetry/opamp/effectiveconfig/EnvVarsEffectiveConfigFileFactory.java @@ -14,10 +14,9 @@ * limitations under the License. */ -package com.splunk.opentelemetry.opamp; +package com.splunk.opentelemetry.opamp.effectiveconfig; import com.splunk.opentelemetry.profiler.ProfilerConfiguration; -import com.splunk.opentelemetry.profiler.ProfilerEnvVarsConfigurationFactory; import com.splunk.opentelemetry.profiler.snapshot.SnapshotProfilingConfiguration; import com.splunk.opentelemetry.profiler.snapshot.SnapshotProfilingEnvVarsConfiguration; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; @@ -49,8 +48,7 @@ public String createEffectiveConfigContent() { } private void addSplunkEnvVars(EffectiveConfigBuilder builder) { - ProfilerConfiguration profilerConfiguration = - ProfilerEnvVarsConfigurationFactory.create(config); + ProfilerConfiguration profilerConfiguration = ProfilerConfiguration.SUPPLIER.get(); SnapshotProfilingConfiguration snapshotConfiguration = new SnapshotProfilingEnvVarsConfiguration(config); diff --git a/opamp/src/main/java/com/splunk/opentelemetry/opamp/effectiveconfig/UpdatableEffectiveConfigState.java b/opamp/src/main/java/com/splunk/opentelemetry/opamp/effectiveconfig/UpdatableEffectiveConfigState.java new file mode 100644 index 000000000..242109ab5 --- /dev/null +++ b/opamp/src/main/java/com/splunk/opentelemetry/opamp/effectiveconfig/UpdatableEffectiveConfigState.java @@ -0,0 +1,34 @@ +/* + * 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.opamp.effectiveconfig; + +import io.opentelemetry.opamp.client.internal.state.State; +import opamp.proto.AgentConfigMap; + +public class UpdatableEffectiveConfigState extends State.EffectiveConfig { + private AgentConfigMap agentConfigMap; + + public void set(AgentConfigMap configMap) { + agentConfigMap = configMap; + notifyUpdate(); + } + + @Override + public opamp.proto.EffectiveConfig get() { + return new opamp.proto.EffectiveConfig(agentConfigMap); + } +} diff --git a/opamp/src/main/java/com/splunk/opentelemetry/opamp/YamlNodeBuilder.java b/opamp/src/main/java/com/splunk/opentelemetry/opamp/effectiveconfig/YamlNodeBuilder.java similarity index 99% rename from opamp/src/main/java/com/splunk/opentelemetry/opamp/YamlNodeBuilder.java rename to opamp/src/main/java/com/splunk/opentelemetry/opamp/effectiveconfig/YamlNodeBuilder.java index 1bd57418d..e38bfe946 100644 --- a/opamp/src/main/java/com/splunk/opentelemetry/opamp/YamlNodeBuilder.java +++ b/opamp/src/main/java/com/splunk/opentelemetry/opamp/effectiveconfig/YamlNodeBuilder.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.splunk.opentelemetry.opamp; +package com.splunk.opentelemetry.opamp.effectiveconfig; import java.util.LinkedHashMap; import java.util.Map; diff --git a/opamp/src/test/java/com/splunk/opentelemetry/opamp/OpampActivatorTest.java b/opamp/src/test/java/com/splunk/opentelemetry/opamp/OpampActivatorTest.java index ca85b97da..689590a19 100644 --- a/opamp/src/test/java/com/splunk/opentelemetry/opamp/OpampActivatorTest.java +++ b/opamp/src/test/java/com/splunk/opentelemetry/opamp/OpampActivatorTest.java @@ -16,7 +16,6 @@ package com.splunk.opentelemetry.opamp; -import static com.splunk.opentelemetry.opamp.OpampActivator.buildEffectiveConfig; import static io.opentelemetry.api.common.AttributeKey.booleanKey; import static io.opentelemetry.api.common.AttributeKey.doubleKey; import static io.opentelemetry.api.common.AttributeKey.longKey; @@ -29,16 +28,15 @@ import static io.opentelemetry.semconv.incubating.OsIncubatingAttributes.OS_TYPE; import static io.opentelemetry.semconv.incubating.OsIncubatingAttributes.OS_VERSION; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import com.splunk.opentelemetry.opamp.effectiveconfig.UpdatableEffectiveConfigState; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.Value; import io.opentelemetry.instrumentation.testing.internal.AutoCleanupExtension; import io.opentelemetry.opamp.client.OpampClient; import io.opentelemetry.opamp.client.internal.response.MessageData; -import io.opentelemetry.opamp.client.internal.state.State; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; -import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.testing.internal.armeria.common.HttpResponse; import io.opentelemetry.testing.internal.armeria.common.HttpStatus; @@ -133,9 +131,7 @@ void testOpamp() throws Exception { .build(); server.enqueue(HttpResponse.of(HttpStatus.OK, MediaType.X_PROTOBUF, response.encode())); - ConfigProperties config = DefaultConfigProperties.createFromMap(Map.of()); - State.EffectiveConfig effectiveConfig = - buildEffectiveConfig(new EnvVarsEffectiveConfigFileFactory(config)); + UpdatableEffectiveConfigState effectiveConfig = mock(); CompletableFuture result = new CompletableFuture<>(); OpampClientConfiguration opampClientConfiguration = diff --git a/opamp/src/test/java/com/splunk/opentelemetry/opamp/RemoteConfigProcessorTest.java b/opamp/src/test/java/com/splunk/opentelemetry/opamp/RemoteConfigProcessorTest.java index 5f134fd19..6bc7518db 100644 --- a/opamp/src/test/java/com/splunk/opentelemetry/opamp/RemoteConfigProcessorTest.java +++ b/opamp/src/test/java/com/splunk/opentelemetry/opamp/RemoteConfigProcessorTest.java @@ -17,10 +17,13 @@ package com.splunk.opentelemetry.opamp; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import com.splunk.opentelemetry.opamp.effectiveconfig.EffectiveConfigReporter; +import com.splunk.opentelemetry.profiler.ProfilerConfiguration; +import com.splunk.opentelemetry.profiler.ProfilingSupervisor; import io.opentelemetry.opamp.client.OpampClient; import java.util.Map; import okio.ByteString; @@ -29,15 +32,34 @@ import opamp.proto.AgentRemoteConfig; import opamp.proto.RemoteConfigStatus; import opamp.proto.RemoteConfigStatuses; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +@ExtendWith(MockitoExtension.class) class RemoteConfigProcessorTest { - private final RemoteConfigProcessor handler = new RemoteConfigProcessor(mock()); - private final OpampClient opampClient = mock(OpampClient.class); + @Mock ProfilingSupervisor profilingSupervisor; + @Mock EffectiveConfigReporter effectiveConfigReporter; + @Mock OpampClient opampClient; + private RemoteConfigProcessor handler; + + @BeforeEach + void setUp() { + ProfilerConfiguration.SUPPLIER.configure(ProfilerConfiguration.builder().build()); + handler = new RemoteConfigProcessor(profilingSupervisor, effectiveConfigReporter); + } + + @AfterEach + void tearDown() { + ProfilerConfiguration.SUPPLIER.reset(); + } @Test - void shouldMarkRemoteConfigAsApplied() { + void shouldMarkRemoteConfigAsAppliedWhenProfilingConfigIsNotProvided() { // given String remoteConfigYaml = "test-config:"; ByteString configHash = ByteString.encodeUtf8("test-config-hash"); @@ -54,12 +76,67 @@ void shouldMarkRemoteConfigAsApplied() { handler.applyConfig(remoteConfig, opampClient); // then - ArgumentCaptor statusCaptor = - ArgumentCaptor.forClass(RemoteConfigStatus.class); - verify(opampClient).setRemoteConfigStatus(statusCaptor.capture()); - RemoteConfigStatus status = statusCaptor.getValue(); + RemoteConfigStatus status = getReportedRemoteConfigStatus(); + assertThat(status.last_remote_config_hash).isEqualTo(configHash); + assertThat(status.status).isEqualTo(RemoteConfigStatuses.RemoteConfigStatuses_APPLIED); + assertThat(status.error_message).isEmpty(); + assertThat(ProfilerConfiguration.SUPPLIER.get().isEnabled()).isFalse(); + verify(effectiveConfigReporter).reportEffectiveConfigIfChanged(); + verifyNoInteractions(profilingSupervisor); + } + + @Test + void shouldStartProfilingWhenRemoteConfigEnablesProfiler() { + // given + String remoteConfigYaml = + """ + distribution: + splunk: + profiling: + always_on: + cpu_profiler: + """; + ByteString configHash = ByteString.encodeUtf8("test-config-hash"); + AgentRemoteConfig remoteConfig = createRemoteConfig(configHash, remoteConfigYaml); + + // when + handler.applyConfig(remoteConfig, opampClient); + + // then + RemoteConfigStatus status = getReportedRemoteConfigStatus(); assertThat(status.last_remote_config_hash).isEqualTo(configHash); assertThat(status.status).isEqualTo(RemoteConfigStatuses.RemoteConfigStatuses_APPLIED); + assertThat(status.error_message).isEmpty(); + assertThat(ProfilerConfiguration.SUPPLIER.get().isEnabled()).isTrue(); + verify(profilingSupervisor).requestStartProfiling(); + verify(profilingSupervisor, never()).requestStopProfiling(); + verify(effectiveConfigReporter).reportEffectiveConfigIfChanged(); + } + + @Test + void shouldStopProfilingWhenRemoteConfigDisablesProfiler() { + // given + String remoteConfigYaml = + """ + distribution: + splunk: + profiling: + """; + ByteString configHash = ByteString.encodeUtf8("test-config-hash"); + AgentRemoteConfig remoteConfig = createRemoteConfig(configHash, remoteConfigYaml); + + // when + handler.applyConfig(remoteConfig, opampClient); + + // then + RemoteConfigStatus status = getReportedRemoteConfigStatus(); + assertThat(status.last_remote_config_hash).isEqualTo(configHash); + assertThat(status.status).isEqualTo(RemoteConfigStatuses.RemoteConfigStatuses_APPLIED); + assertThat(status.error_message).isEmpty(); + assertThat(ProfilerConfiguration.SUPPLIER.get().isEnabled()).isFalse(); + verify(profilingSupervisor).requestStopProfiling(); + verify(profilingSupervisor, never()).requestStartProfiling(); + verify(effectiveConfigReporter).reportEffectiveConfigIfChanged(); } @Test @@ -76,7 +153,24 @@ void shouldIgnoreRemoteConfigWithoutExpectedConfigFile() { handler.applyConfig(remoteConfig, opampClient); // then - verify(opampClient, never()).setRemoteConfigStatus(org.mockito.ArgumentMatchers.any()); + assertThat(ProfilerConfiguration.SUPPLIER.get().isEnabled()).isFalse(); + verifyNoInteractions(opampClient, profilingSupervisor); + verify(effectiveConfigReporter, never()).reportEffectiveConfigIfChanged(); + } + + private RemoteConfigStatus getReportedRemoteConfigStatus() { + ArgumentCaptor statusCaptor = + ArgumentCaptor.forClass(RemoteConfigStatus.class); + verify(opampClient).setRemoteConfigStatus(statusCaptor.capture()); + return statusCaptor.getValue(); + } + + private static AgentRemoteConfig createRemoteConfig(ByteString configHash, String config) { + return createRemoteConfig( + configHash, + Map.of( + "splunk.remote.config", + new AgentConfigFile.Builder().body(ByteString.encodeUtf8(config)).build())); } private static AgentRemoteConfig createRemoteConfig( diff --git a/opamp/src/test/java/com/splunk/opentelemetry/opamp/DeclarativeEffectiveConfigFileFactoryTest.java b/opamp/src/test/java/com/splunk/opentelemetry/opamp/effectiveconfig/DeclarativeEffectiveConfigFileFactoryTest.java similarity index 99% rename from opamp/src/test/java/com/splunk/opentelemetry/opamp/DeclarativeEffectiveConfigFileFactoryTest.java rename to opamp/src/test/java/com/splunk/opentelemetry/opamp/effectiveconfig/DeclarativeEffectiveConfigFileFactoryTest.java index 6e0c44e88..091c6bf7a 100644 --- a/opamp/src/test/java/com/splunk/opentelemetry/opamp/DeclarativeEffectiveConfigFileFactoryTest.java +++ b/opamp/src/test/java/com/splunk/opentelemetry/opamp/effectiveconfig/DeclarativeEffectiveConfigFileFactoryTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.splunk.opentelemetry.opamp; +package com.splunk.opentelemetry.opamp.effectiveconfig; import static io.opentelemetry.api.incubator.config.DeclarativeConfigProperties.empty; import static io.opentelemetry.sdk.autoconfigure.AutoConfigureUtil.getDistributionConfig; @@ -22,6 +22,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.when; +import com.splunk.opentelemetry.opamp.DeclarativeConfigurationInterceptor; import com.splunk.opentelemetry.profiler.ProfilerConfiguration; import com.splunk.opentelemetry.profiler.ProfilerDeclarativeConfigurationFactory; import com.splunk.opentelemetry.profiler.snapshot.SnapshotProfilingConfiguration; diff --git a/opamp/src/test/java/com/splunk/opentelemetry/opamp/EnvVarsEffectiveConfigFileFactoryTest.java b/opamp/src/test/java/com/splunk/opentelemetry/opamp/effectiveconfig/EnvVarsEffectiveConfigFileFactoryTest.java similarity index 94% rename from opamp/src/test/java/com/splunk/opentelemetry/opamp/EnvVarsEffectiveConfigFileFactoryTest.java rename to opamp/src/test/java/com/splunk/opentelemetry/opamp/effectiveconfig/EnvVarsEffectiveConfigFileFactoryTest.java index f48165485..5e2c4d578 100644 --- a/opamp/src/test/java/com/splunk/opentelemetry/opamp/EnvVarsEffectiveConfigFileFactoryTest.java +++ b/opamp/src/test/java/com/splunk/opentelemetry/opamp/effectiveconfig/EnvVarsEffectiveConfigFileFactoryTest.java @@ -14,19 +14,27 @@ * limitations under the License. */ -package com.splunk.opentelemetry.opamp; +package com.splunk.opentelemetry.opamp.effectiveconfig; import static org.assertj.core.api.Assertions.assertThat; +import com.splunk.opentelemetry.profiler.ProfilerConfiguration; +import com.splunk.opentelemetry.profiler.ProfilerEnvVarsConfigurationFactory; import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; import java.io.IOException; import java.io.StringReader; import java.util.Map; import java.util.Properties; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; class EnvVarsEffectiveConfigFileFactoryTest { + @AfterEach + void tearDown() { + ProfilerConfiguration.SUPPLIER.reset(); + } + @Test void createFile_reportsCorrectContentType() { DefaultConfigProperties config = DefaultConfigProperties.createFromMap(Map.of()); @@ -154,6 +162,7 @@ void buildFileContent_usesSignalSpecificProtocolWhenResolvingEndpoints() throws private static Properties createFileContent(Map configMap) throws IOException { DefaultConfigProperties config = DefaultConfigProperties.createFromMap(configMap); + ProfilerConfiguration.SUPPLIER.configure(ProfilerEnvVarsConfigurationFactory.create(config)); String fileContent = new EnvVarsEffectiveConfigFileFactory(config).createEffectiveConfigContent(); diff --git a/opamp/src/test/java/com/splunk/opentelemetry/opamp/YamlNodeBuilderTest.java b/opamp/src/test/java/com/splunk/opentelemetry/opamp/effectiveconfig/YamlNodeBuilderTest.java similarity index 98% rename from opamp/src/test/java/com/splunk/opentelemetry/opamp/YamlNodeBuilderTest.java rename to opamp/src/test/java/com/splunk/opentelemetry/opamp/effectiveconfig/YamlNodeBuilderTest.java index fd9d7e751..319902ea2 100644 --- a/opamp/src/test/java/com/splunk/opentelemetry/opamp/YamlNodeBuilderTest.java +++ b/opamp/src/test/java/com/splunk/opentelemetry/opamp/effectiveconfig/YamlNodeBuilderTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.splunk.opentelemetry.opamp; +package com.splunk.opentelemetry.opamp.effectiveconfig; import static org.assertj.core.api.Assertions.assertThat; From 846d29869a25ba6c57d5925f5d8c10b4fba85456 Mon Sep 17 00:00:00 2001 From: robsunday Date: Wed, 24 Jun 2026 18:12:55 +0200 Subject: [PATCH 04/13] Naming improved. Test improved --- .../profiler/ProfilerConfiguration.java | 2 +- .../profiler/ProfilerConfigurationTest.java | 88 ++++++++++++++----- 2 files changed, 67 insertions(+), 23 deletions(-) diff --git a/profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilerConfiguration.java b/profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilerConfiguration.java index 32a0f152c..12593b2a2 100644 --- a/profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilerConfiguration.java +++ b/profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilerConfiguration.java @@ -73,7 +73,7 @@ public static Builder builder() { return new Builder(); } - public Builder newBuilder() { + public Builder toBuilder() { return new Builder(this); } diff --git a/profiler/src/test/java/com/splunk/opentelemetry/profiler/ProfilerConfigurationTest.java b/profiler/src/test/java/com/splunk/opentelemetry/profiler/ProfilerConfigurationTest.java index 397a207d3..449b0ba24 100644 --- a/profiler/src/test/java/com/splunk/opentelemetry/profiler/ProfilerConfigurationTest.java +++ b/profiler/src/test/java/com/splunk/opentelemetry/profiler/ProfilerConfigurationTest.java @@ -24,8 +24,39 @@ class ProfilerConfigurationTest { @Test - void newBuilderCopiesExistingConfigurationAndAllowsMutation() { + void toBuilder_shouldCopyExistingConfigurationWithoutMutation() { Object configProperties = new Object(); + ProfilerConfiguration original = + ProfilerConfiguration.builder() + .setEnabled(true) + .setIngestUrl("https://logs.example.com") + .setOtlpProtocol("grpc") + .setMemoryEnabled(true) + .setMemoryEventRateLimitEnabled(false) + .setMemoryEventRate("250/s") + .setUseAllocationSampleEvent(true) + .setCallStackInterval(Duration.ofMillis(1410)) + .setIncludeAgentInternalStacks(true) + .setIncludeJvmInternalStacks(true) + .setTracingStacksOnly(true) + .setStackDepth(73) + .setKeepFiles(true) + .setProfilerDirectory("/tmp/profiler") + .setRecordingDuration(Duration.ofSeconds(30)) + .setConfigProperties(configProperties) + .build(); + + ProfilerConfiguration copy = original.toBuilder().build(); + + assertThat(copy).isNotSameAs(original); + assertThat(copy).usingRecursiveComparison().isEqualTo(original); + assertThat(copy.getConfigProperties()).isSameAs(configProperties); + } + + @Test + void toBuilder_shouldCopyExistingConfigurationAndAllowMutation() { + Object configProperties = new Object(); + Object mutatedConfigProperties = new Object(); ProfilerConfiguration original = ProfilerConfiguration.builder() .setEnabled(false) @@ -46,28 +77,41 @@ void newBuilderCopiesExistingConfigurationAndAllowsMutation() { .setConfigProperties(configProperties) .build(); - ProfilerConfiguration copy = original.newBuilder().setEnabled(true).build(); + ProfilerConfiguration copy = + original.toBuilder() + .setEnabled(true) + .setIngestUrl("https://mutated-logs.example.com") + .setOtlpProtocol("http/protobuf") + .setMemoryEnabled(false) + .setMemoryEventRateLimitEnabled(true) + .setMemoryEventRate("333/s") + .setUseAllocationSampleEvent(false) + .setCallStackInterval(Duration.ofMillis(2500)) + .setIncludeAgentInternalStacks(false) + .setIncludeJvmInternalStacks(false) + .setTracingStacksOnly(false) + .setStackDepth(142) + .setKeepFiles(false) + .setProfilerDirectory("/tmp/mutated-profiler") + .setRecordingDuration(Duration.ofSeconds(60)) + .setConfigProperties(mutatedConfigProperties) + .build(); - assertThat(original.isEnabled()).isFalse(); assertThat(copy.isEnabled()).isTrue(); - assertThat(copy.getIngestUrl()).isEqualTo(original.getIngestUrl()); - assertThat(copy.getOtlpProtocol()).isEqualTo(original.getOtlpProtocol()); - assertThat(copy.getMemoryEnabled()).isEqualTo(original.getMemoryEnabled()); - assertThat(copy.getMemoryEventRateLimitEnabled()) - .isEqualTo(original.getMemoryEventRateLimitEnabled()); - assertThat(copy.getMemoryEventRate()).isEqualTo(original.getMemoryEventRate()); - assertThat(copy.getUseAllocationSampleEvent()) - .isEqualTo(original.getUseAllocationSampleEvent()); - assertThat(copy.getCallStackInterval()).isEqualTo(original.getCallStackInterval()); - assertThat(copy.getIncludeAgentInternalStacks()) - .isEqualTo(original.getIncludeAgentInternalStacks()); - assertThat(copy.getIncludeJvmInternalStacks()) - .isEqualTo(original.getIncludeJvmInternalStacks()); - assertThat(copy.getTracingStacksOnly()).isEqualTo(original.getTracingStacksOnly()); - assertThat(copy.getStackDepth()).isEqualTo(original.getStackDepth()); - assertThat(copy.getKeepFiles()).isEqualTo(original.getKeepFiles()); - assertThat(copy.getProfilerDirectory()).isEqualTo(original.getProfilerDirectory()); - assertThat(copy.getRecordingDuration()).isEqualTo(original.getRecordingDuration()); - assertThat(copy.getConfigProperties()).isSameAs(configProperties); + assertThat(copy.getIngestUrl()).isEqualTo("https://mutated-logs.example.com"); + assertThat(copy.getOtlpProtocol()).isEqualTo("http/protobuf"); + assertThat(copy.getMemoryEnabled()).isFalse(); + assertThat(copy.getMemoryEventRateLimitEnabled()).isTrue(); + assertThat(copy.getMemoryEventRate()).isEqualTo("333/s"); + assertThat(copy.getUseAllocationSampleEvent()).isFalse(); + assertThat(copy.getCallStackInterval()).isEqualTo(Duration.ofMillis(2500)); + assertThat(copy.getIncludeAgentInternalStacks()).isFalse(); + assertThat(copy.getIncludeJvmInternalStacks()).isFalse(); + assertThat(copy.getTracingStacksOnly()).isFalse(); + assertThat(copy.getStackDepth()).isEqualTo(142); + assertThat(copy.getKeepFiles()).isFalse(); + assertThat(copy.getProfilerDirectory()).isEqualTo("/tmp/mutated-profiler"); + assertThat(copy.getRecordingDuration()).isEqualTo(Duration.ofSeconds(60)); + assertThat(copy.getConfigProperties()).isSameAs(mutatedConfigProperties); } } From f3ae2683626d366be28a2469e29a8a9db452f678 Mon Sep 17 00:00:00 2001 From: robsunday Date: Wed, 24 Jun 2026 18:21:30 +0200 Subject: [PATCH 05/13] Fix after merge --- .../com/splunk/opentelemetry/opamp/RemoteConfigProcessor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opamp/src/main/java/com/splunk/opentelemetry/opamp/RemoteConfigProcessor.java b/opamp/src/main/java/com/splunk/opentelemetry/opamp/RemoteConfigProcessor.java index adbfcff32..c28bd7f33 100644 --- a/opamp/src/main/java/com/splunk/opentelemetry/opamp/RemoteConfigProcessor.java +++ b/opamp/src/main/java/com/splunk/opentelemetry/opamp/RemoteConfigProcessor.java @@ -82,7 +82,7 @@ public void applyConfig(AgentRemoteConfig remoteConfig, OpampClient opampClient) ProfilerConfiguration updatedProfilerConfig = ProfilerConfiguration.SUPPLIER .get() - .newBuilder() + .toBuilder() .setEnabled(receivedProfilerConfig.isEnabled()) .build(); ProfilerConfiguration.SUPPLIER.configure(updatedProfilerConfig); From ad3ef7e9dcbdfe905d7d23a752f4cf0cfac8ad69 Mon Sep 17 00:00:00 2001 From: robsunday Date: Thu, 25 Jun 2026 10:31:53 +0200 Subject: [PATCH 06/13] spotless --- .../com/splunk/opentelemetry/opamp/RemoteConfigProcessor.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/opamp/src/main/java/com/splunk/opentelemetry/opamp/RemoteConfigProcessor.java b/opamp/src/main/java/com/splunk/opentelemetry/opamp/RemoteConfigProcessor.java index c28bd7f33..98136e35a 100644 --- a/opamp/src/main/java/com/splunk/opentelemetry/opamp/RemoteConfigProcessor.java +++ b/opamp/src/main/java/com/splunk/opentelemetry/opamp/RemoteConfigProcessor.java @@ -80,9 +80,7 @@ public void applyConfig(AgentRemoteConfig remoteConfig, OpampClient opampClient) distributionRemoteConfigProperties.getStructured(PROFILING_NODE_NAME, empty())); ProfilerConfiguration updatedProfilerConfig = - ProfilerConfiguration.SUPPLIER - .get() - .toBuilder() + ProfilerConfiguration.SUPPLIER.get().toBuilder() .setEnabled(receivedProfilerConfig.isEnabled()) .build(); ProfilerConfiguration.SUPPLIER.configure(updatedProfilerConfig); From 8b4c420a11fb7a14d052a044797fe757ecde1d6b Mon Sep 17 00:00:00 2001 From: robsunday Date: Thu, 25 Jun 2026 15:45:50 +0200 Subject: [PATCH 07/13] Profiler reinitialization implemented --- .../opamp/RemoteConfigProcessor.java | 7 ++---- .../profiler/ProfilingSupervisor.java | 23 ++++++++++++++++++- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/opamp/src/main/java/com/splunk/opentelemetry/opamp/RemoteConfigProcessor.java b/opamp/src/main/java/com/splunk/opentelemetry/opamp/RemoteConfigProcessor.java index 98136e35a..3bf199260 100644 --- a/opamp/src/main/java/com/splunk/opentelemetry/opamp/RemoteConfigProcessor.java +++ b/opamp/src/main/java/com/splunk/opentelemetry/opamp/RemoteConfigProcessor.java @@ -82,14 +82,11 @@ public void applyConfig(AgentRemoteConfig remoteConfig, OpampClient opampClient) ProfilerConfiguration updatedProfilerConfig = ProfilerConfiguration.SUPPLIER.get().toBuilder() .setEnabled(receivedProfilerConfig.isEnabled()) + .setCallStackInterval(receivedProfilerConfig.getCallStackInterval()) .build(); ProfilerConfiguration.SUPPLIER.configure(updatedProfilerConfig); - if (updatedProfilerConfig.isEnabled()) { - profilingSupervisor.requestStartProfiling(); - } else { - profilingSupervisor.requestStopProfiling(); - } + profilingSupervisor.requestReinitializeProfiling(); } // Confirm to the OpAMP Server that remote config has been applied. 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 2b9269ffd..8825cfb02 100644 --- a/profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilingSupervisor.java +++ b/profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilingSupervisor.java @@ -105,6 +105,10 @@ public void requestStopProfiling() { commandQueue.add(ProfilingCommand.STOP); } + public void requestReinitializeProfiling() { + commandQueue.add(ProfilingCommand.REINITIALIZE); + } + private void handleCommand(ProfilingCommand command) { switch (command) { case START: @@ -113,6 +117,9 @@ private void handleCommand(ProfilingCommand command) { case STOP: tryStop(); break; + case REINITIALIZE: + tryReinitialize(); + break; } } @@ -153,6 +160,19 @@ private void tryStop() { logger.info("Profiler is deactivated."); } + private void tryReinitialize() { + logger.info("Reinitializing profiler."); + // Stop if currently running + if (isJfrRecordingActive()) { + tryStop(); + } + // Start with current setting if profiling is enabled. If settings changed since last start they + // will be applied. + if (configSupplier.get().isEnabled()) { + tryStart(); + } + } + private boolean isJfrRecordingActive() { return recordingFlusher.get() != null; } @@ -192,6 +212,7 @@ static void setupJfrContextStorage() { enum ProfilingCommand { START, - STOP + STOP, + REINITIALIZE } } From 507a86f610d34ca4edebd5905358a2de492d132f Mon Sep 17 00:00:00 2001 From: robsunday Date: Thu, 25 Jun 2026 16:37:43 +0200 Subject: [PATCH 08/13] Unit test added --- .../EffectiveConfigReporterTest.java | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 opamp/src/test/java/com/splunk/opentelemetry/opamp/effectiveconfig/EffectiveConfigReporterTest.java diff --git a/opamp/src/test/java/com/splunk/opentelemetry/opamp/effectiveconfig/EffectiveConfigReporterTest.java b/opamp/src/test/java/com/splunk/opentelemetry/opamp/effectiveconfig/EffectiveConfigReporterTest.java new file mode 100644 index 000000000..411134a57 --- /dev/null +++ b/opamp/src/test/java/com/splunk/opentelemetry/opamp/effectiveconfig/EffectiveConfigReporterTest.java @@ -0,0 +1,100 @@ +/* + * 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.opamp.effectiveconfig; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import opamp.proto.AgentConfigFile; +import opamp.proto.AgentConfigMap; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class EffectiveConfigReporterTest { + private static final String CONFIG_FILE_NAME = "splunk-effective-config.properties"; + private static final String CONTENT_TYPE = "text/plain; format=properties; vendor=splunk"; + + @Mock private EffectiveConfigFactory effectiveConfigFactory; + @Mock private UpdatableEffectiveConfigState effectiveConfigState; + + private EffectiveConfigReporter reporter; + + @BeforeEach + void setUp() { + when(effectiveConfigFactory.getFileName()).thenReturn(CONFIG_FILE_NAME); + when(effectiveConfigFactory.getContentType()).thenReturn(CONTENT_TYPE); + reporter = new EffectiveConfigReporter(effectiveConfigFactory, effectiveConfigState); + } + + @Test + void reportEffectiveConfigIfChanged_reportsGeneratedConfig() { + when(effectiveConfigFactory.createEffectiveConfigContent()).thenReturn("first-config"); + + boolean reported = reporter.reportEffectiveConfigIfChanged(); + + assertThat(reported).isTrue(); + AgentConfigFile configFile = captureReportedConfigFile(); + assertThat(configFile.body.utf8()).isEqualTo("first-config"); + assertThat(configFile.content_type).isEqualTo(CONTENT_TYPE); + } + + @Test + void reportEffectiveConfigIfChanged_skipsUnchangedConfig() { + when(effectiveConfigFactory.createEffectiveConfigContent()).thenReturn("same-config"); + + boolean firstReport = reporter.reportEffectiveConfigIfChanged(); + boolean secondReport = reporter.reportEffectiveConfigIfChanged(); + + assertThat(firstReport).isTrue(); + assertThat(secondReport).isFalse(); + verify(effectiveConfigState, times(1)).set(any()); + } + + @Test + void reportEffectiveConfigIfChanged_reportsUpdatedConfig() { + when(effectiveConfigFactory.createEffectiveConfigContent()) + .thenReturn("first-config", "second-config"); + + boolean firstReport = reporter.reportEffectiveConfigIfChanged(); + boolean secondReport = reporter.reportEffectiveConfigIfChanged(); + + assertThat(firstReport).isTrue(); + assertThat(secondReport).isTrue(); + + ArgumentCaptor configMapCaptor = ArgumentCaptor.forClass(AgentConfigMap.class); + verify(effectiveConfigState, times(2)).set(configMapCaptor.capture()); + assertThat(configMapCaptor.getAllValues()) + .extracting(configMap -> configMap.config_map.get(CONFIG_FILE_NAME).body.utf8()) + .containsExactly("first-config", "second-config"); + } + + private AgentConfigFile captureReportedConfigFile() { + ArgumentCaptor configMapCaptor = ArgumentCaptor.forClass(AgentConfigMap.class); + verify(effectiveConfigState).set(configMapCaptor.capture()); + AgentConfigMap configMap = configMapCaptor.getValue(); + assertThat(configMap.config_map).containsOnlyKeys(CONFIG_FILE_NAME); + return configMap.config_map.get(CONFIG_FILE_NAME); + } +} From 613c493f008f9b79c2b305415c6d520e1ab534c2 Mon Sep 17 00:00:00 2001 From: robsunday Date: Thu, 25 Jun 2026 18:56:40 +0200 Subject: [PATCH 09/13] Optimized profiler restart --- .../opamp/RemoteConfigProcessor.java | 9 ++-- .../profiler/ProfilerConfiguration.java | 48 +++++++++++++++++++ .../profiler/ProfilerConfigurationTest.java | 4 +- 3 files changed, 57 insertions(+), 4 deletions(-) diff --git a/opamp/src/main/java/com/splunk/opentelemetry/opamp/RemoteConfigProcessor.java b/opamp/src/main/java/com/splunk/opentelemetry/opamp/RemoteConfigProcessor.java index 3bf199260..63459c876 100644 --- a/opamp/src/main/java/com/splunk/opentelemetry/opamp/RemoteConfigProcessor.java +++ b/opamp/src/main/java/com/splunk/opentelemetry/opamp/RemoteConfigProcessor.java @@ -79,14 +79,17 @@ public void applyConfig(AgentRemoteConfig remoteConfig, OpampClient opampClient) ProfilerDeclarativeConfigurationFactory.create( distributionRemoteConfigProperties.getStructured(PROFILING_NODE_NAME, empty())); + ProfilerConfiguration currentProfilerConfiguration = ProfilerConfiguration.SUPPLIER.get(); ProfilerConfiguration updatedProfilerConfig = - ProfilerConfiguration.SUPPLIER.get().toBuilder() + currentProfilerConfiguration.toBuilder() .setEnabled(receivedProfilerConfig.isEnabled()) .setCallStackInterval(receivedProfilerConfig.getCallStackInterval()) .build(); - ProfilerConfiguration.SUPPLIER.configure(updatedProfilerConfig); - profilingSupervisor.requestReinitializeProfiling(); + if (!currentProfilerConfiguration.equals(updatedProfilerConfig)) { + ProfilerConfiguration.SUPPLIER.configure(updatedProfilerConfig); + profilingSupervisor.requestReinitializeProfiling(); + } } // Confirm to the OpAMP Server that remote config has been applied. diff --git a/profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilerConfiguration.java b/profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilerConfiguration.java index 12593b2a2..9bf4a7ed7 100644 --- a/profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilerConfiguration.java +++ b/profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilerConfiguration.java @@ -170,6 +170,54 @@ public Object getConfigProperties() { return configProperties; } + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof ProfilerConfiguration)) { + return false; + } + ProfilerConfiguration that = (ProfilerConfiguration) other; + return enabled == that.enabled + && memoryEnabled == that.memoryEnabled + && memoryEventRateLimitEnabled == that.memoryEventRateLimitEnabled + && useAllocationSampleEvent == that.useAllocationSampleEvent + && includeAgentInternalStacks == that.includeAgentInternalStacks + && includeJvmInternalStacks == that.includeJvmInternalStacks + && tracingStacksOnly == that.tracingStacksOnly + && stackDepth == that.stackDepth + && keepFiles == that.keepFiles + && Objects.equals(ingestUrl, that.ingestUrl) + && Objects.equals(otlpProtocol, that.otlpProtocol) + && Objects.equals(memoryEventRate, that.memoryEventRate) + && Objects.equals(callStackInterval, that.callStackInterval) + && Objects.equals(profilerDirectory, that.profilerDirectory) + && Objects.equals(recordingDuration, that.recordingDuration) + && Objects.equals(configProperties, that.configProperties); + } + + @Override + public int hashCode() { + return Objects.hash( + enabled, + ingestUrl, + otlpProtocol, + memoryEnabled, + memoryEventRateLimitEnabled, + memoryEventRate, + useAllocationSampleEvent, + callStackInterval, + includeAgentInternalStacks, + includeJvmInternalStacks, + tracingStacksOnly, + stackDepth, + keepFiles, + profilerDirectory, + recordingDuration, + configProperties); + } + public static int getJavaVersion() { String javaSpecVersion = System.getProperty("java.specification.version"); if ("1.8".equals(javaSpecVersion)) { diff --git a/profiler/src/test/java/com/splunk/opentelemetry/profiler/ProfilerConfigurationTest.java b/profiler/src/test/java/com/splunk/opentelemetry/profiler/ProfilerConfigurationTest.java index 449b0ba24..daf2c95cd 100644 --- a/profiler/src/test/java/com/splunk/opentelemetry/profiler/ProfilerConfigurationTest.java +++ b/profiler/src/test/java/com/splunk/opentelemetry/profiler/ProfilerConfigurationTest.java @@ -49,7 +49,8 @@ void toBuilder_shouldCopyExistingConfigurationWithoutMutation() { ProfilerConfiguration copy = original.toBuilder().build(); assertThat(copy).isNotSameAs(original); - assertThat(copy).usingRecursiveComparison().isEqualTo(original); + assertThat(copy).isEqualTo(original); + assertThat(copy.hashCode()).isEqualTo(original.hashCode()); assertThat(copy.getConfigProperties()).isSameAs(configProperties); } @@ -97,6 +98,7 @@ void toBuilder_shouldCopyExistingConfigurationAndAllowMutation() { .setConfigProperties(mutatedConfigProperties) .build(); + assertThat(copy).isNotEqualTo(original); assertThat(copy.isEnabled()).isTrue(); assertThat(copy.getIngestUrl()).isEqualTo("https://mutated-logs.example.com"); assertThat(copy.getOtlpProtocol()).isEqualTo("http/protobuf"); From 1b8ccdc9413358b24b601ca9227c5d9f019fadb9 Mon Sep 17 00:00:00 2001 From: robsunday Date: Fri, 26 Jun 2026 10:11:24 +0200 Subject: [PATCH 10/13] Test fixed --- .../opentelemetry/opamp/RemoteConfigProcessorTest.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/opamp/src/test/java/com/splunk/opentelemetry/opamp/RemoteConfigProcessorTest.java b/opamp/src/test/java/com/splunk/opentelemetry/opamp/RemoteConfigProcessorTest.java index 6bc7518db..1ee2fb8aa 100644 --- a/opamp/src/test/java/com/splunk/opentelemetry/opamp/RemoteConfigProcessorTest.java +++ b/opamp/src/test/java/com/splunk/opentelemetry/opamp/RemoteConfigProcessorTest.java @@ -108,7 +108,7 @@ void shouldStartProfilingWhenRemoteConfigEnablesProfiler() { assertThat(status.status).isEqualTo(RemoteConfigStatuses.RemoteConfigStatuses_APPLIED); assertThat(status.error_message).isEmpty(); assertThat(ProfilerConfiguration.SUPPLIER.get().isEnabled()).isTrue(); - verify(profilingSupervisor).requestStartProfiling(); + verify(profilingSupervisor).requestReinitializeProfiling(); verify(profilingSupervisor, never()).requestStopProfiling(); verify(effectiveConfigReporter).reportEffectiveConfigIfChanged(); } @@ -116,6 +116,8 @@ void shouldStartProfilingWhenRemoteConfigEnablesProfiler() { @Test void shouldStopProfilingWhenRemoteConfigDisablesProfiler() { // given + ProfilerConfiguration.SUPPLIER.configure( + ProfilerConfiguration.builder().setEnabled(true).build()); String remoteConfigYaml = """ distribution: @@ -134,7 +136,7 @@ void shouldStopProfilingWhenRemoteConfigDisablesProfiler() { assertThat(status.status).isEqualTo(RemoteConfigStatuses.RemoteConfigStatuses_APPLIED); assertThat(status.error_message).isEmpty(); assertThat(ProfilerConfiguration.SUPPLIER.get().isEnabled()).isFalse(); - verify(profilingSupervisor).requestStopProfiling(); + verify(profilingSupervisor).requestReinitializeProfiling(); verify(profilingSupervisor, never()).requestStartProfiling(); verify(effectiveConfigReporter).reportEffectiveConfigIfChanged(); } From ece581ae797f1835fcccb0242376ff45f3a6c2e4 Mon Sep 17 00:00:00 2001 From: robsunday Date: Fri, 26 Jun 2026 14:04:29 +0200 Subject: [PATCH 11/13] Tests improved --- .../opamp/RemoteConfigProcessor.java | 3 +- .../opamp/RemoteConfigProcessorTest.java | 61 +++++++- .../profiler/ProfilingSupervisorTest.java | 137 ++++++++++++++++-- 3 files changed, 185 insertions(+), 16 deletions(-) diff --git a/opamp/src/main/java/com/splunk/opentelemetry/opamp/RemoteConfigProcessor.java b/opamp/src/main/java/com/splunk/opentelemetry/opamp/RemoteConfigProcessor.java index 63459c876..96fa3bc82 100644 --- a/opamp/src/main/java/com/splunk/opentelemetry/opamp/RemoteConfigProcessor.java +++ b/opamp/src/main/java/com/splunk/opentelemetry/opamp/RemoteConfigProcessor.java @@ -29,6 +29,7 @@ import java.io.ByteArrayInputStream; import java.util.Map; import java.util.Objects; +import java.util.logging.Level; import java.util.logging.Logger; import okio.ByteString; import opamp.proto.AgentConfigFile; @@ -100,12 +101,12 @@ public void applyConfig(AgentRemoteConfig remoteConfig, OpampClient opampClient) opampClient); } catch (Exception e) { + logger.log(Level.WARNING, "Remote config not applied due to exception", e); reportRemoteConfigStatus( remoteConfig.config_hash, RemoteConfigStatuses.RemoteConfigStatuses_FAILED, "Exception occurred: " + e.getMessage(), opampClient); - throw e; } // TODO: Maybe should be postponed after profiler is enabled/disabled? diff --git a/opamp/src/test/java/com/splunk/opentelemetry/opamp/RemoteConfigProcessorTest.java b/opamp/src/test/java/com/splunk/opentelemetry/opamp/RemoteConfigProcessorTest.java index 1ee2fb8aa..5ad66018c 100644 --- a/opamp/src/test/java/com/splunk/opentelemetry/opamp/RemoteConfigProcessorTest.java +++ b/opamp/src/test/java/com/splunk/opentelemetry/opamp/RemoteConfigProcessorTest.java @@ -20,6 +20,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import com.splunk.opentelemetry.opamp.effectiveconfig.EffectiveConfigReporter; import com.splunk.opentelemetry.profiler.ProfilerConfiguration; @@ -109,7 +110,65 @@ void shouldStartProfilingWhenRemoteConfigEnablesProfiler() { assertThat(status.error_message).isEmpty(); assertThat(ProfilerConfiguration.SUPPLIER.get().isEnabled()).isTrue(); verify(profilingSupervisor).requestReinitializeProfiling(); - verify(profilingSupervisor, never()).requestStopProfiling(); + verifyNoMoreInteractions(profilingSupervisor); + verify(effectiveConfigReporter).reportEffectiveConfigIfChanged(); + } + + @Test + void shouldReportErrorWhenRemoteConfigProcessingFailed() { + // given + String remoteConfigYaml = + """ + distribution: + splunk: + profiling: + INVALID YAML HERE + """; + ByteString configHash = ByteString.encodeUtf8("test-config-hash"); + AgentRemoteConfig remoteConfig = createRemoteConfig(configHash, remoteConfigYaml); + + // when + handler.applyConfig(remoteConfig, opampClient); + + // then + RemoteConfigStatus status = getReportedRemoteConfigStatus(); + assertThat(status.last_remote_config_hash).isEqualTo(configHash); + assertThat(status.status).isEqualTo(RemoteConfigStatuses.RemoteConfigStatuses_FAILED); + assertThat(status.error_message).startsWith("Exception occurred:"); + assertThat(ProfilerConfiguration.SUPPLIER.get().isEnabled()).isFalse(); + assertThat(ProfilerConfiguration.SUPPLIER.get().getCallStackInterval().toMillis()) + .isEqualTo(10000); + verifyNoInteractions(profilingSupervisor); + verify(effectiveConfigReporter).reportEffectiveConfigIfChanged(); + } + + @Test + void shouldUpdateProfilerConfigWhileRunning() { + // given + ProfilerConfiguration.SUPPLIER.configure( + ProfilerConfiguration.builder().setEnabled(true).build()); + + String remoteConfigYaml = + """ + distribution: + splunk: + profiling: + always_on: + cpu_profiler: + sampling_interval: 123 + """; + ByteString configHash = ByteString.encodeUtf8("test-config-hash"); + AgentRemoteConfig remoteConfig = createRemoteConfig(configHash, remoteConfigYaml); + + // when + handler.applyConfig(remoteConfig, opampClient); + + // then + assertThat(ProfilerConfiguration.SUPPLIER.get().isEnabled()).isTrue(); + assertThat(ProfilerConfiguration.SUPPLIER.get().getCallStackInterval().toMillis()) + .isEqualTo(123); + verify(profilingSupervisor).requestReinitializeProfiling(); + verifyNoMoreInteractions(profilingSupervisor); verify(effectiveConfigReporter).reportEffectiveConfigIfChanged(); } diff --git a/profiler/src/test/java/com/splunk/opentelemetry/profiler/ProfilingSupervisorTest.java b/profiler/src/test/java/com/splunk/opentelemetry/profiler/ProfilingSupervisorTest.java index 1d4a1d0b1..5f0cadc13 100644 --- a/profiler/src/test/java/com/splunk/opentelemetry/profiler/ProfilingSupervisorTest.java +++ b/profiler/src/test/java/com/splunk/opentelemetry/profiler/ProfilingSupervisorTest.java @@ -51,7 +51,7 @@ class ProfilingSupervisorTest { TestPeriodicRecordingFlusherBuilder builder; AutoConfiguredOpenTelemetrySdk sdk; ExecutorService executor; - ProfilingSupervisor supervisor; + TestProfilingSupervisor supervisor; @BeforeEach void setUp(@TempDir Path tempDir) { @@ -90,11 +90,14 @@ void tearDown() { } @Test - void requestStartDoesNotStartProfilerWhenJfrIsUnavailable() { + void requestStartProfiling_doesNotStartProfilerWhenJfrIsUnavailable() { + // given when(jfr.isAvailable()).thenReturn(false); + // when supervisor.requestStartProfiling(); + // then await().untilAsserted(() -> verify(jfr).isAvailable()); verify(config, never()).log(); verify(jfr, never()).setStackDepth(anyInt()); @@ -102,11 +105,14 @@ void requestStartDoesNotStartProfilerWhenJfrIsUnavailable() { } @Test - void requestStartProfilingBuildsAndStartsRecordingSequencer() { + void requestStartProfiling_buildsAndStartsRecordingFlusher() { + // given when(jfr.isAvailable()).thenReturn(true); + // when supervisor.requestStartProfiling(); + // then await().untilAsserted(() -> assertThat(builder.buildCalled).isTrue()); verify(config).log(); assertThat(builder.jfr).isSameAs(jfr); @@ -116,59 +122,155 @@ void requestStartProfilingBuildsAndStartsRecordingSequencer() { } @Test - void requestStartProfilingOnlyStartsOnce() { + void requestStartProfiling_startsOnlyOnce() { + // given when(jfr.isAvailable()).thenReturn(true); + // when supervisor.requestStartProfiling(); await().untilAsserted(() -> assertThat(builder.buildCalled).isTrue()); supervisor.requestStartProfiling(); + // then await().during(Duration.ofMillis(200)).untilAsserted(() -> verify(builder.flusher).start()); assertThat(builder.buildCount).isEqualTo(1); } @Test - void requestStopProfilingStopsActiveRecordingFlusher() { + void requestStopProfiling_stopsActiveRecordingFlusher() { + // given when(jfr.isAvailable()).thenReturn(true); - supervisor.requestStartProfiling(); await().untilAsserted(() -> verify(builder.flusher).start()); + // when supervisor.requestStopProfiling(); + // then await().untilAsserted(() -> verify(builder.flusher).stop()); } @Test - void requestStartProfilingCanStartAgainAfterStop() { + void requestStartProfiling_startsAfterStop() { + // given when(jfr.isAvailable()).thenReturn(true); - supervisor.requestStartProfiling(); await().untilAsserted(() -> verify(builder.flusher).start()); supervisor.requestStopProfiling(); await().untilAsserted(() -> verify(builder.flusher).stop()); + // when + supervisor.requestStartProfiling(); + + // then + await().untilAsserted(() -> verify(builder.flusher, times(2)).start()); + assertThat(builder.buildCount).isEqualTo(2); + } + + @Test + void requestReinitializeProfiling_restartsActiveProfilerWhenEnabled() { + // given + when(jfr.isAvailable()).thenReturn(true); supervisor.requestStartProfiling(); + await().untilAsserted(() -> verify(builder.flusher).start()); + + ProfilerConfiguration updatedConfig = spy(config.toBuilder().setStackDepth(1234).build()); + supervisor.configureProfilerConfiguration(updatedConfig); + // when + supervisor.requestReinitializeProfiling(); + + // then await().untilAsserted(() -> verify(builder.flusher, times(2)).start()); + verify(builder.flusher).stop(); + verify(updatedConfig).log(); assertThat(builder.buildCount).isEqualTo(2); + assertThat(builder.config).isSameAs(updatedConfig); + } + + @Test + void requestReinitializeProfiling_stopsActiveProfilerWhenDisabled() { + // given + when(jfr.isAvailable()).thenReturn(true); + supervisor.requestStartProfiling(); + await().untilAsserted(() -> verify(builder.flusher).start()); + + ProfilerConfiguration disabledConfig = spy(config.toBuilder().setEnabled(false).build()); + supervisor.configureProfilerConfiguration(disabledConfig); + + // when + supervisor.requestReinitializeProfiling(); + + // then + await().untilAsserted(() -> verify(builder.flusher).stop()); + verify(disabledConfig).isEnabled(); + await().during(Duration.ofMillis(200)).untilAsserted(() -> verify(builder.flusher).start()); + assertThat(builder.buildCount).isEqualTo(1); + } + + @Test + void requestReinitializeProfiling_startsInactiveProfilerWhenEnabled() { + // given + when(jfr.isAvailable()).thenReturn(true); + ProfilerConfiguration updatedConfig = spy(config.toBuilder().setStackDepth(1234).build()); + supervisor.configureProfilerConfiguration(updatedConfig); + + // when + supervisor.requestReinitializeProfiling(); + + // then + await().untilAsserted(() -> verify(builder.flusher).start()); + verify(builder.flusher, never()).stop(); + verify(updatedConfig).log(); + assertThat(builder.config).isSameAs(updatedConfig); + } + + @Test + void requestReinitializeProfiling_keepsInactiveProfilerStoppedWhenDisabled() { + // given + ProfilerConfiguration disabledConfig = spy(config.toBuilder().setEnabled(false).build()); + supervisor.configureProfilerConfiguration(disabledConfig); + + // when + supervisor.requestReinitializeProfiling(); + + // then + await().untilAsserted(() -> verify(disabledConfig).isEnabled()); + verify(jfr, never()).isAvailable(); + verify(builder.flusher, never()).start(); + verify(builder.flusher, never()).stop(); + assertThat(builder.buildCalled).isFalse(); } private static class TestProfilingSupervisor extends ProfilingSupervisor { - private final PeriodicRecordingFlusherBuilder builder; + private final TestPeriodicRecordingFlusherBuilder builder; + private final OptionalConfigurableSupplier configSupplier; TestProfilingSupervisor( ProfilerConfiguration config, JFR jfr, AutoConfiguredOpenTelemetrySdk sdk, - PeriodicRecordingFlusherBuilder builder) { - super(configSupplier(config), jfr, sdk, new LinkedBlockingQueue<>()); + TestPeriodicRecordingFlusherBuilder builder) { + this(configSupplier(config), jfr, sdk, builder); + } + + private TestProfilingSupervisor( + OptionalConfigurableSupplier configSupplier, + JFR jfr, + AutoConfiguredOpenTelemetrySdk sdk, + TestPeriodicRecordingFlusherBuilder builder) { + super(configSupplier, jfr, sdk, new LinkedBlockingQueue<>()); this.builder = builder; + this.configSupplier = configSupplier; + } + + void configureProfilerConfiguration(ProfilerConfiguration config) { + configSupplier.configure(config); } @Override PeriodicRecordingFlusherBuilder makeRecordingFlusherBuilder(Resource resource) { - return builder; + return builder.withConfigAndResource(configSupplier.get(), resource); } private static OptionalConfigurableSupplier configSupplier( @@ -182,8 +284,8 @@ private static OptionalConfigurableSupplier configSupplie private static class TestPeriodicRecordingFlusherBuilder extends PeriodicRecordingFlusherBuilder { final PeriodicRecordingFlusher flusher = mock(PeriodicRecordingFlusher.class); - private final ProfilerConfiguration config; - private final Resource resource; + private ProfilerConfiguration config; + private Resource resource; JFR jfr; boolean buildCalled; int buildCount; @@ -194,6 +296,13 @@ public TestPeriodicRecordingFlusherBuilder(ProfilerConfiguration config, Resourc this.resource = resource; } + TestPeriodicRecordingFlusherBuilder withConfigAndResource( + ProfilerConfiguration config, Resource resource) { + this.config = config; + this.resource = resource; + return this; + } + @Override PeriodicRecordingFlusherBuilder jfr(JFR jfr) { this.jfr = jfr; From 53c29765502994ae91b4aa25497a194b043c129c Mon Sep 17 00:00:00 2001 From: robsunday Date: Fri, 26 Jun 2026 14:36:39 +0200 Subject: [PATCH 12/13] PeriodicRecordingFlusher and tests improved --- .../profiler/PeriodicRecordingFlusher.java | 6 - ...a => PeriodicRecordingFlusherFactory.java} | 20 +-- .../profiler/ProfilingSupervisor.java | 19 ++- ... PeriodicRecordingFlusherFactoryTest.java} | 28 ++-- .../profiler/ProfilingSupervisorTest.java | 155 ++++++------------ 5 files changed, 84 insertions(+), 144 deletions(-) rename profiler/src/main/java/com/splunk/opentelemetry/profiler/{PeriodicRecordingFlusherBuilder.java => PeriodicRecordingFlusherFactory.java} (94%) rename profiler/src/test/java/com/splunk/opentelemetry/profiler/{PeriodicRecordingFlusherBuilderTest.java => PeriodicRecordingFlusherFactoryTest.java} (79%) diff --git a/profiler/src/main/java/com/splunk/opentelemetry/profiler/PeriodicRecordingFlusher.java b/profiler/src/main/java/com/splunk/opentelemetry/profiler/PeriodicRecordingFlusher.java index ae6c2f16e..3ecfc58e1 100644 --- a/profiler/src/main/java/com/splunk/opentelemetry/profiler/PeriodicRecordingFlusher.java +++ b/profiler/src/main/java/com/splunk/opentelemetry/profiler/PeriodicRecordingFlusher.java @@ -20,7 +20,6 @@ import com.google.common.annotations.VisibleForTesting; import com.splunk.opentelemetry.profiler.util.HelpfulExecutors; -import io.opentelemetry.sdk.resources.Resource; import java.time.Duration; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; @@ -78,9 +77,4 @@ void handleInterval() { logger.log(SEVERE, "Profiler periodic task failed.", throwable); } } - - public static PeriodicRecordingFlusherBuilder builder( - ProfilerConfiguration config, Resource resource) { - return new PeriodicRecordingFlusherBuilder(config, resource); - } } diff --git a/profiler/src/main/java/com/splunk/opentelemetry/profiler/PeriodicRecordingFlusherBuilder.java b/profiler/src/main/java/com/splunk/opentelemetry/profiler/PeriodicRecordingFlusherFactory.java similarity index 94% rename from profiler/src/main/java/com/splunk/opentelemetry/profiler/PeriodicRecordingFlusherBuilder.java rename to profiler/src/main/java/com/splunk/opentelemetry/profiler/PeriodicRecordingFlusherFactory.java index 54297452a..332490caa 100644 --- a/profiler/src/main/java/com/splunk/opentelemetry/profiler/PeriodicRecordingFlusherBuilder.java +++ b/profiler/src/main/java/com/splunk/opentelemetry/profiler/PeriodicRecordingFlusherFactory.java @@ -37,25 +37,11 @@ import java.time.Duration; import java.util.Map; -class PeriodicRecordingFlusherBuilder { +class PeriodicRecordingFlusherFactory { private static final java.util.logging.Logger logger = - java.util.logging.Logger.getLogger(PeriodicRecordingFlusherBuilder.class.getName()); - private final ProfilerConfiguration config; - private final Resource resource; + java.util.logging.Logger.getLogger(PeriodicRecordingFlusherFactory.class.getName()); - private JFR jfr; - - public PeriodicRecordingFlusherBuilder(ProfilerConfiguration config, Resource resource) { - this.config = config; - this.resource = resource; - } - - PeriodicRecordingFlusherBuilder jfr(JFR jfr) { - this.jfr = jfr; - return this; - } - - PeriodicRecordingFlusher build() { + PeriodicRecordingFlusher create(ProfilerConfiguration config, Resource resource, JFR jfr) { if (jfr == null) { jfr = JFR.getInstance(); } 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 8825cfb02..dbb362338 100644 --- a/profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilingSupervisor.java +++ b/profiler/src/main/java/com/splunk/opentelemetry/profiler/ProfilingSupervisor.java @@ -45,6 +45,7 @@ public class ProfilingSupervisor { private final JFR jfr; private final AutoConfiguredOpenTelemetrySdk sdk; private final BlockingQueue commandQueue; + private final PeriodicRecordingFlusherFactory recordingFlusherFactory; private final AtomicReference recordingFlusher = new AtomicReference<>(); private static final AtomicReference jfrContextStorage = @@ -57,10 +58,21 @@ public class ProfilingSupervisor { JFR jfr, AutoConfiguredOpenTelemetrySdk sdk, BlockingQueue commandQueue) { + this(configSupplier, jfr, sdk, commandQueue, new PeriodicRecordingFlusherFactory()); + } + + @VisibleForTesting + ProfilingSupervisor( + OptionalConfigurableSupplier configSupplier, + JFR jfr, + AutoConfiguredOpenTelemetrySdk sdk, + BlockingQueue commandQueue, + PeriodicRecordingFlusherFactory recordingFlusherFactory) { this.configSupplier = configSupplier; this.jfr = jfr; this.sdk = sdk; this.commandQueue = commandQueue; + this.recordingFlusherFactory = recordingFlusherFactory; } static ProfilingSupervisor createAndStart(AutoConfiguredOpenTelemetrySdk sdk) { @@ -179,7 +191,7 @@ private boolean isJfrRecordingActive() { private void activateJfrRecording(Resource resource) { PeriodicRecordingFlusher recordingFlusher = - makeRecordingFlusherBuilder(resource).jfr(jfr).build(); + recordingFlusherFactory.create(configSupplier.get(), resource, jfr); if (this.recordingFlusher.compareAndSet(null, recordingFlusher)) { recordingFlusher.start(); } @@ -192,11 +204,6 @@ private void deactivateJfrRecording() { } } - // Exists for testing - PeriodicRecordingFlusherBuilder makeRecordingFlusherBuilder(Resource resource) { - return PeriodicRecordingFlusher.builder(configSupplier.get(), resource); - } - static void setupJfrContextStorage() { if (!contextStorageSetup.compareAndSet(false, true)) { return; diff --git a/profiler/src/test/java/com/splunk/opentelemetry/profiler/PeriodicRecordingFlusherBuilderTest.java b/profiler/src/test/java/com/splunk/opentelemetry/profiler/PeriodicRecordingFlusherFactoryTest.java similarity index 79% rename from profiler/src/test/java/com/splunk/opentelemetry/profiler/PeriodicRecordingFlusherBuilderTest.java rename to profiler/src/test/java/com/splunk/opentelemetry/profiler/PeriodicRecordingFlusherFactoryTest.java index 0ab6b23f0..e3c414a85 100644 --- a/profiler/src/test/java/com/splunk/opentelemetry/profiler/PeriodicRecordingFlusherBuilderTest.java +++ b/profiler/src/test/java/com/splunk/opentelemetry/profiler/PeriodicRecordingFlusherFactoryTest.java @@ -33,7 +33,7 @@ import org.junit.jupiter.api.io.TempDir; import org.mockito.MockedConstruction; -class PeriodicRecordingFlusherBuilderTest { +class PeriodicRecordingFlusherFactoryTest { @TempDir Path tempDir; @@ -43,62 +43,62 @@ void tearDown() { } @Test - void buildConfiguresJfrAndWiresRecorderIntoSequencer() { + void createConfiguresJfrAndWiresRecorderIntoSequencer() { JFR jfr = mock(JFR.class); ProfilerConfiguration config = config(tempDir).setStackDepth(73).setRecordingDuration(Duration.ofMillis(100)).build(); ProfilerConfiguration.SUPPLIER.configure(config); + PeriodicRecordingFlusherFactory factory = new PeriodicRecordingFlusherFactory(); try (MockedConstruction recorderConstruction = mockConstruction(JfrRecorder.class)) { - PeriodicRecordingFlusher sequencer = - PeriodicRecordingFlusher.builder(config, Resource.empty()).jfr(jfr).build(); + PeriodicRecordingFlusher flusher = factory.create(config, Resource.empty(), jfr); - assertThat(sequencer).isNotNull(); + assertThat(flusher).isNotNull(); assertThat(recorderConstruction.constructed()).hasSize(1); verify(jfr).setStackDepth(73); JfrRecorder recorder = recorderConstruction.constructed().get(0); when(recorder.isStarted()).thenReturn(true); - sequencer.handleInterval(); + flusher.handleInterval(); verify(recorder).flushSnapshot(); } } @Test - void buildCreatesMissingOutputDirectoryWhenKeepingFiles() { + void createCreatesMissingOutputDirectoryWhenKeepingFiles() { Path outputDir = tempDir.resolve("profiler-output"); JFR jfr = mock(JFR.class); ProfilerConfiguration config = config(outputDir).setKeepFiles(true).build(); ProfilerConfiguration.SUPPLIER.configure(config); + PeriodicRecordingFlusherFactory factory = new PeriodicRecordingFlusherFactory(); try (MockedConstruction recorderConstruction = mockConstruction(JfrRecorder.class)) { - PeriodicRecordingFlusher sequencer = - PeriodicRecordingFlusher.builder(config, Resource.empty()).jfr(jfr).build(); + PeriodicRecordingFlusher flusher = factory.create(config, Resource.empty(), jfr); - assertThat(sequencer).isNotNull(); + assertThat(flusher).isNotNull(); assertThat(outputDir).isDirectory(); assertThat(recorderConstruction.constructed()).hasSize(1); } } @Test - void buildContinuesWhenKeepFilesPathIsNotADirectory() throws Exception { + void createContinuesWhenKeepFilesPathIsNotADirectory() throws Exception { Path outputFile = tempDir.resolve("profiler-output"); Files.createFile(outputFile); JFR jfr = mock(JFR.class); ProfilerConfiguration config = config(outputFile).setKeepFiles(true).build(); ProfilerConfiguration.SUPPLIER.configure(config); + PeriodicRecordingFlusherFactory factory = new PeriodicRecordingFlusherFactory(); try (MockedConstruction recorderConstruction = mockConstruction(JfrRecorder.class)) { - PeriodicRecordingFlusher sequencer = - PeriodicRecordingFlusher.builder(config, Resource.empty()).jfr(jfr).build(); + PeriodicRecordingFlusher flusher = factory.create(config, Resource.empty(), jfr); - assertThat(sequencer).isNotNull(); + assertThat(flusher).isNotNull(); assertThat(recorderConstruction.constructed()).hasSize(1); } } diff --git a/profiler/src/test/java/com/splunk/opentelemetry/profiler/ProfilingSupervisorTest.java b/profiler/src/test/java/com/splunk/opentelemetry/profiler/ProfilingSupervisorTest.java index 5f0cadc13..61f65a8f5 100644 --- a/profiler/src/test/java/com/splunk/opentelemetry/profiler/ProfilingSupervisorTest.java +++ b/profiler/src/test/java/com/splunk/opentelemetry/profiler/ProfilingSupervisorTest.java @@ -48,10 +48,11 @@ class ProfilingSupervisorTest { @Mock JFR jfr; ProfilerConfiguration config; - TestPeriodicRecordingFlusherBuilder builder; + OptionalConfigurableSupplier configSupplier; + TestPeriodicRecordingFlusherFactory recordingFlusherFactory; AutoConfiguredOpenTelemetrySdk sdk; ExecutorService executor; - TestProfilingSupervisor supervisor; + ProfilingSupervisor supervisor; @BeforeEach void setUp(@TempDir Path tempDir) { @@ -63,7 +64,9 @@ void setUp(@TempDir Path tempDir) { .setStackDepth(4321) .setRecordingDuration(Duration.ofMinutes(1)) .build()); - builder = new TestPeriodicRecordingFlusherBuilder(config, mock(Resource.class)); + configSupplier = new OptionalConfigurableSupplier<>(); + configSupplier.configure(config); + recordingFlusherFactory = new TestPeriodicRecordingFlusherFactory(); executor = Executors.newSingleThreadExecutor(); sdk = AutoConfiguredOpenTelemetrySdk.builder() @@ -80,7 +83,9 @@ void setUp(@TempDir Path tempDir) { "otel.service.name", "profiling-supervisor-test")) .build(); - supervisor = new TestProfilingSupervisor(config, jfr, sdk, builder); + supervisor = + new ProfilingSupervisor( + configSupplier, jfr, sdk, new LinkedBlockingQueue<>(), recordingFlusherFactory); supervisor.start(executor); } @@ -101,7 +106,7 @@ void requestStartProfiling_doesNotStartProfilerWhenJfrIsUnavailable() { await().untilAsserted(() -> verify(jfr).isAvailable()); verify(config, never()).log(); verify(jfr, never()).setStackDepth(anyInt()); - assertThat(builder.buildCalled).isFalse(); + assertThat(recordingFlusherFactory.createCalled).isFalse(); } @Test @@ -113,12 +118,12 @@ void requestStartProfiling_buildsAndStartsRecordingFlusher() { supervisor.requestStartProfiling(); // then - await().untilAsserted(() -> assertThat(builder.buildCalled).isTrue()); + await().untilAsserted(() -> assertThat(recordingFlusherFactory.createCalled).isTrue()); verify(config).log(); - assertThat(builder.jfr).isSameAs(jfr); - assertThat(builder.config).isSameAs(config); - assertThat(builder.resource).isNotNull(); - verify(builder.flusher).start(); + assertThat(recordingFlusherFactory.jfr).isSameAs(jfr); + assertThat(recordingFlusherFactory.config).isSameAs(config); + assertThat(recordingFlusherFactory.resource).isNotNull(); + verify(recordingFlusherFactory.flusher).start(); } @Test @@ -128,12 +133,14 @@ void requestStartProfiling_startsOnlyOnce() { // when supervisor.requestStartProfiling(); - await().untilAsserted(() -> assertThat(builder.buildCalled).isTrue()); + await().untilAsserted(() -> assertThat(recordingFlusherFactory.createCalled).isTrue()); supervisor.requestStartProfiling(); // then - await().during(Duration.ofMillis(200)).untilAsserted(() -> verify(builder.flusher).start()); - assertThat(builder.buildCount).isEqualTo(1); + await() + .during(Duration.ofMillis(200)) + .untilAsserted(() -> verify(recordingFlusherFactory.flusher).start()); + assertThat(recordingFlusherFactory.createCount).isEqualTo(1); } @Test @@ -141,13 +148,13 @@ void requestStopProfiling_stopsActiveRecordingFlusher() { // given when(jfr.isAvailable()).thenReturn(true); supervisor.requestStartProfiling(); - await().untilAsserted(() -> verify(builder.flusher).start()); + await().untilAsserted(() -> verify(recordingFlusherFactory.flusher).start()); // when supervisor.requestStopProfiling(); // then - await().untilAsserted(() -> verify(builder.flusher).stop()); + await().untilAsserted(() -> verify(recordingFlusherFactory.flusher).stop()); } @Test @@ -155,16 +162,16 @@ void requestStartProfiling_startsAfterStop() { // given when(jfr.isAvailable()).thenReturn(true); supervisor.requestStartProfiling(); - await().untilAsserted(() -> verify(builder.flusher).start()); + await().untilAsserted(() -> verify(recordingFlusherFactory.flusher).start()); supervisor.requestStopProfiling(); - await().untilAsserted(() -> verify(builder.flusher).stop()); + await().untilAsserted(() -> verify(recordingFlusherFactory.flusher).stop()); // when supervisor.requestStartProfiling(); // then - await().untilAsserted(() -> verify(builder.flusher, times(2)).start()); - assertThat(builder.buildCount).isEqualTo(2); + await().untilAsserted(() -> verify(recordingFlusherFactory.flusher, times(2)).start()); + assertThat(recordingFlusherFactory.createCount).isEqualTo(2); } @Test @@ -172,20 +179,20 @@ void requestReinitializeProfiling_restartsActiveProfilerWhenEnabled() { // given when(jfr.isAvailable()).thenReturn(true); supervisor.requestStartProfiling(); - await().untilAsserted(() -> verify(builder.flusher).start()); + await().untilAsserted(() -> verify(recordingFlusherFactory.flusher).start()); ProfilerConfiguration updatedConfig = spy(config.toBuilder().setStackDepth(1234).build()); - supervisor.configureProfilerConfiguration(updatedConfig); + configSupplier.configure(updatedConfig); // when supervisor.requestReinitializeProfiling(); // then - await().untilAsserted(() -> verify(builder.flusher, times(2)).start()); - verify(builder.flusher).stop(); + await().untilAsserted(() -> verify(recordingFlusherFactory.flusher, times(2)).start()); + verify(recordingFlusherFactory.flusher).stop(); verify(updatedConfig).log(); - assertThat(builder.buildCount).isEqualTo(2); - assertThat(builder.config).isSameAs(updatedConfig); + assertThat(recordingFlusherFactory.createCount).isEqualTo(2); + assertThat(recordingFlusherFactory.config).isSameAs(updatedConfig); } @Test @@ -193,19 +200,21 @@ void requestReinitializeProfiling_stopsActiveProfilerWhenDisabled() { // given when(jfr.isAvailable()).thenReturn(true); supervisor.requestStartProfiling(); - await().untilAsserted(() -> verify(builder.flusher).start()); + await().untilAsserted(() -> verify(recordingFlusherFactory.flusher).start()); ProfilerConfiguration disabledConfig = spy(config.toBuilder().setEnabled(false).build()); - supervisor.configureProfilerConfiguration(disabledConfig); + configSupplier.configure(disabledConfig); // when supervisor.requestReinitializeProfiling(); // then - await().untilAsserted(() -> verify(builder.flusher).stop()); + await().untilAsserted(() -> verify(recordingFlusherFactory.flusher).stop()); verify(disabledConfig).isEnabled(); - await().during(Duration.ofMillis(200)).untilAsserted(() -> verify(builder.flusher).start()); - assertThat(builder.buildCount).isEqualTo(1); + await() + .during(Duration.ofMillis(200)) + .untilAsserted(() -> verify(recordingFlusherFactory.flusher).start()); + assertThat(recordingFlusherFactory.createCount).isEqualTo(1); } @Test @@ -213,23 +222,23 @@ void requestReinitializeProfiling_startsInactiveProfilerWhenEnabled() { // given when(jfr.isAvailable()).thenReturn(true); ProfilerConfiguration updatedConfig = spy(config.toBuilder().setStackDepth(1234).build()); - supervisor.configureProfilerConfiguration(updatedConfig); + configSupplier.configure(updatedConfig); // when supervisor.requestReinitializeProfiling(); // then - await().untilAsserted(() -> verify(builder.flusher).start()); - verify(builder.flusher, never()).stop(); + await().untilAsserted(() -> verify(recordingFlusherFactory.flusher).start()); + verify(recordingFlusherFactory.flusher, never()).stop(); verify(updatedConfig).log(); - assertThat(builder.config).isSameAs(updatedConfig); + assertThat(recordingFlusherFactory.config).isSameAs(updatedConfig); } @Test void requestReinitializeProfiling_keepsInactiveProfilerStoppedWhenDisabled() { // given ProfilerConfiguration disabledConfig = spy(config.toBuilder().setEnabled(false).build()); - supervisor.configureProfilerConfiguration(disabledConfig); + configSupplier.configure(disabledConfig); // when supervisor.requestReinitializeProfiling(); @@ -237,82 +246,26 @@ void requestReinitializeProfiling_keepsInactiveProfilerStoppedWhenDisabled() { // then await().untilAsserted(() -> verify(disabledConfig).isEnabled()); verify(jfr, never()).isAvailable(); - verify(builder.flusher, never()).start(); - verify(builder.flusher, never()).stop(); - assertThat(builder.buildCalled).isFalse(); + verify(recordingFlusherFactory.flusher, never()).start(); + verify(recordingFlusherFactory.flusher, never()).stop(); + assertThat(recordingFlusherFactory.createCalled).isFalse(); } - private static class TestProfilingSupervisor extends ProfilingSupervisor { - private final TestPeriodicRecordingFlusherBuilder builder; - private final OptionalConfigurableSupplier configSupplier; - - TestProfilingSupervisor( - ProfilerConfiguration config, - JFR jfr, - AutoConfiguredOpenTelemetrySdk sdk, - TestPeriodicRecordingFlusherBuilder builder) { - this(configSupplier(config), jfr, sdk, builder); - } - - private TestProfilingSupervisor( - OptionalConfigurableSupplier configSupplier, - JFR jfr, - AutoConfiguredOpenTelemetrySdk sdk, - TestPeriodicRecordingFlusherBuilder builder) { - super(configSupplier, jfr, sdk, new LinkedBlockingQueue<>()); - this.builder = builder; - this.configSupplier = configSupplier; - } - - void configureProfilerConfiguration(ProfilerConfiguration config) { - configSupplier.configure(config); - } - - @Override - PeriodicRecordingFlusherBuilder makeRecordingFlusherBuilder(Resource resource) { - return builder.withConfigAndResource(configSupplier.get(), resource); - } - - private static OptionalConfigurableSupplier configSupplier( - ProfilerConfiguration config) { - OptionalConfigurableSupplier supplier = - new OptionalConfigurableSupplier<>(); - supplier.configure(config); - return supplier; - } - } - - private static class TestPeriodicRecordingFlusherBuilder extends PeriodicRecordingFlusherBuilder { + private static class TestPeriodicRecordingFlusherFactory extends PeriodicRecordingFlusherFactory { final PeriodicRecordingFlusher flusher = mock(PeriodicRecordingFlusher.class); private ProfilerConfiguration config; private Resource resource; JFR jfr; - boolean buildCalled; - int buildCount; - - public TestPeriodicRecordingFlusherBuilder(ProfilerConfiguration config, Resource resource) { - super(config, resource); - this.config = config; - this.resource = resource; - } + boolean createCalled; + int createCount; - TestPeriodicRecordingFlusherBuilder withConfigAndResource( - ProfilerConfiguration config, Resource resource) { + @Override + PeriodicRecordingFlusher create(ProfilerConfiguration config, Resource resource, JFR jfr) { this.config = config; this.resource = resource; - return this; - } - - @Override - PeriodicRecordingFlusherBuilder jfr(JFR jfr) { this.jfr = jfr; - return this; - } - - @Override - PeriodicRecordingFlusher build() { - buildCalled = true; - buildCount++; + createCalled = true; + createCount++; return flusher; } } From cdb1e5a4536ee2905a5a269d18447b5dc83ba35e Mon Sep 17 00:00:00 2001 From: robsunday Date: Fri, 26 Jun 2026 14:53:07 +0200 Subject: [PATCH 13/13] Further tests improvements --- .../profiler/ProfilingSupervisorTest.java | 100 +++++++++--------- 1 file changed, 49 insertions(+), 51 deletions(-) diff --git a/profiler/src/test/java/com/splunk/opentelemetry/profiler/ProfilingSupervisorTest.java b/profiler/src/test/java/com/splunk/opentelemetry/profiler/ProfilingSupervisorTest.java index 61f65a8f5..724744a77 100644 --- a/profiler/src/test/java/com/splunk/opentelemetry/profiler/ProfilingSupervisorTest.java +++ b/profiler/src/test/java/com/splunk/opentelemetry/profiler/ProfilingSupervisorTest.java @@ -16,10 +16,11 @@ package com.splunk.opentelemetry.profiler; -import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.mock; +import static org.mockito.ArgumentMatchers.same; +import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; @@ -46,10 +47,11 @@ class ProfilingSupervisorTest { @Mock JFR jfr; + @Mock PeriodicRecordingFlusherFactory recordingFlusherFactory; + @Mock PeriodicRecordingFlusher recordingFlusher; ProfilerConfiguration config; OptionalConfigurableSupplier configSupplier; - TestPeriodicRecordingFlusherFactory recordingFlusherFactory; AutoConfiguredOpenTelemetrySdk sdk; ExecutorService executor; ProfilingSupervisor supervisor; @@ -66,7 +68,11 @@ void setUp(@TempDir Path tempDir) { .build()); configSupplier = new OptionalConfigurableSupplier<>(); configSupplier.configure(config); - recordingFlusherFactory = new TestPeriodicRecordingFlusherFactory(); + lenient() + .when( + recordingFlusherFactory.create( + any(ProfilerConfiguration.class), any(Resource.class), any(JFR.class))) + .thenReturn(recordingFlusher); executor = Executors.newSingleThreadExecutor(); sdk = AutoConfiguredOpenTelemetrySdk.builder() @@ -106,7 +112,7 @@ void requestStartProfiling_doesNotStartProfilerWhenJfrIsUnavailable() { await().untilAsserted(() -> verify(jfr).isAvailable()); verify(config, never()).log(); verify(jfr, never()).setStackDepth(anyInt()); - assertThat(recordingFlusherFactory.createCalled).isFalse(); + verify(recordingFlusherFactory, never()).create(any(), any(), any()); } @Test @@ -118,12 +124,9 @@ void requestStartProfiling_buildsAndStartsRecordingFlusher() { supervisor.requestStartProfiling(); // then - await().untilAsserted(() -> assertThat(recordingFlusherFactory.createCalled).isTrue()); + await().untilAsserted(() -> verify(recordingFlusher).start()); verify(config).log(); - assertThat(recordingFlusherFactory.jfr).isSameAs(jfr); - assertThat(recordingFlusherFactory.config).isSameAs(config); - assertThat(recordingFlusherFactory.resource).isNotNull(); - verify(recordingFlusherFactory.flusher).start(); + verifyRecordingFlusherCreatedWith(config); } @Test @@ -133,14 +136,17 @@ void requestStartProfiling_startsOnlyOnce() { // when supervisor.requestStartProfiling(); - await().untilAsserted(() -> assertThat(recordingFlusherFactory.createCalled).isTrue()); + await().untilAsserted(() -> verify(recordingFlusherFactory).create(any(), any(), any())); supervisor.requestStartProfiling(); // then await() .during(Duration.ofMillis(200)) - .untilAsserted(() -> verify(recordingFlusherFactory.flusher).start()); - assertThat(recordingFlusherFactory.createCount).isEqualTo(1); + .untilAsserted( + () -> { + verify(recordingFlusher).start(); + verifyRecordingFlusherCreated(1); + }); } @Test @@ -148,13 +154,13 @@ void requestStopProfiling_stopsActiveRecordingFlusher() { // given when(jfr.isAvailable()).thenReturn(true); supervisor.requestStartProfiling(); - await().untilAsserted(() -> verify(recordingFlusherFactory.flusher).start()); + await().untilAsserted(() -> verify(recordingFlusher).start()); // when supervisor.requestStopProfiling(); // then - await().untilAsserted(() -> verify(recordingFlusherFactory.flusher).stop()); + await().untilAsserted(() -> verify(recordingFlusher).stop()); } @Test @@ -162,16 +168,16 @@ void requestStartProfiling_startsAfterStop() { // given when(jfr.isAvailable()).thenReturn(true); supervisor.requestStartProfiling(); - await().untilAsserted(() -> verify(recordingFlusherFactory.flusher).start()); + await().untilAsserted(() -> verify(recordingFlusher).start()); supervisor.requestStopProfiling(); - await().untilAsserted(() -> verify(recordingFlusherFactory.flusher).stop()); + await().untilAsserted(() -> verify(recordingFlusher).stop()); // when supervisor.requestStartProfiling(); // then - await().untilAsserted(() -> verify(recordingFlusherFactory.flusher, times(2)).start()); - assertThat(recordingFlusherFactory.createCount).isEqualTo(2); + await().untilAsserted(() -> verify(recordingFlusher, times(2)).start()); + verifyRecordingFlusherCreated(2); } @Test @@ -179,7 +185,7 @@ void requestReinitializeProfiling_restartsActiveProfilerWhenEnabled() { // given when(jfr.isAvailable()).thenReturn(true); supervisor.requestStartProfiling(); - await().untilAsserted(() -> verify(recordingFlusherFactory.flusher).start()); + await().untilAsserted(() -> verify(recordingFlusher).start()); ProfilerConfiguration updatedConfig = spy(config.toBuilder().setStackDepth(1234).build()); configSupplier.configure(updatedConfig); @@ -188,11 +194,11 @@ void requestReinitializeProfiling_restartsActiveProfilerWhenEnabled() { supervisor.requestReinitializeProfiling(); // then - await().untilAsserted(() -> verify(recordingFlusherFactory.flusher, times(2)).start()); - verify(recordingFlusherFactory.flusher).stop(); + await().untilAsserted(() -> verify(recordingFlusher, times(2)).start()); + verify(recordingFlusher).stop(); verify(updatedConfig).log(); - assertThat(recordingFlusherFactory.createCount).isEqualTo(2); - assertThat(recordingFlusherFactory.config).isSameAs(updatedConfig); + verifyRecordingFlusherCreated(2); + verifyRecordingFlusherCreatedWith(updatedConfig); } @Test @@ -200,7 +206,7 @@ void requestReinitializeProfiling_stopsActiveProfilerWhenDisabled() { // given when(jfr.isAvailable()).thenReturn(true); supervisor.requestStartProfiling(); - await().untilAsserted(() -> verify(recordingFlusherFactory.flusher).start()); + await().untilAsserted(() -> verify(recordingFlusher).start()); ProfilerConfiguration disabledConfig = spy(config.toBuilder().setEnabled(false).build()); configSupplier.configure(disabledConfig); @@ -209,12 +215,15 @@ void requestReinitializeProfiling_stopsActiveProfilerWhenDisabled() { supervisor.requestReinitializeProfiling(); // then - await().untilAsserted(() -> verify(recordingFlusherFactory.flusher).stop()); + await().untilAsserted(() -> verify(recordingFlusher).stop()); verify(disabledConfig).isEnabled(); await() .during(Duration.ofMillis(200)) - .untilAsserted(() -> verify(recordingFlusherFactory.flusher).start()); - assertThat(recordingFlusherFactory.createCount).isEqualTo(1); + .untilAsserted( + () -> { + verify(recordingFlusher).start(); + verifyRecordingFlusherCreated(1); + }); } @Test @@ -228,10 +237,10 @@ void requestReinitializeProfiling_startsInactiveProfilerWhenEnabled() { supervisor.requestReinitializeProfiling(); // then - await().untilAsserted(() -> verify(recordingFlusherFactory.flusher).start()); - verify(recordingFlusherFactory.flusher, never()).stop(); + await().untilAsserted(() -> verify(recordingFlusher).start()); + verify(recordingFlusher, never()).stop(); verify(updatedConfig).log(); - assertThat(recordingFlusherFactory.config).isSameAs(updatedConfig); + verifyRecordingFlusherCreatedWith(updatedConfig); } @Test @@ -246,27 +255,16 @@ void requestReinitializeProfiling_keepsInactiveProfilerStoppedWhenDisabled() { // then await().untilAsserted(() -> verify(disabledConfig).isEnabled()); verify(jfr, never()).isAvailable(); - verify(recordingFlusherFactory.flusher, never()).start(); - verify(recordingFlusherFactory.flusher, never()).stop(); - assertThat(recordingFlusherFactory.createCalled).isFalse(); + verify(recordingFlusher, never()).start(); + verify(recordingFlusher, never()).stop(); + verify(recordingFlusherFactory, never()).create(any(), any(), any()); } - private static class TestPeriodicRecordingFlusherFactory extends PeriodicRecordingFlusherFactory { - final PeriodicRecordingFlusher flusher = mock(PeriodicRecordingFlusher.class); - private ProfilerConfiguration config; - private Resource resource; - JFR jfr; - boolean createCalled; - int createCount; - - @Override - PeriodicRecordingFlusher create(ProfilerConfiguration config, Resource resource, JFR jfr) { - this.config = config; - this.resource = resource; - this.jfr = jfr; - createCalled = true; - createCount++; - return flusher; - } + private void verifyRecordingFlusherCreated(int count) { + verify(recordingFlusherFactory, times(count)).create(any(), any(), any()); + } + + private void verifyRecordingFlusherCreatedWith(ProfilerConfiguration config) { + verify(recordingFlusherFactory).create(same(config), any(Resource.class), same(jfr)); } }