Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,17 @@
import io.opentelemetry.api.incubator.ExtendedOpenTelemetry;
import io.opentelemetry.api.incubator.config.ConfigProvider;
import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties;
import io.opentelemetry.common.ComponentLoader;
import io.opentelemetry.instrumentation.config.bridge.ConfigPropertiesBackedConfigProvider;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.extension.incubator.fileconfig.YamlDeclarativeConfigProperties;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalInstrumentationModel;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalLanguageSpecificInstrumentationModel;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalLanguageSpecificInstrumentationPropertyModel;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OpenTelemetryConfigurationModel;
import io.opentelemetry.sdk.resources.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import javax.annotation.Nullable;

Expand Down Expand Up @@ -57,6 +65,8 @@ public static boolean isDeclarativeConfig(AutoConfiguredOpenTelemetrySdk sdk) {
return false;
}

// TODO: This is temporary solution. For now assume that distribution node is located under
// .instrumentation/development.java.distribution
Copy link
Contributor Author

@robsunday robsunday Jan 12, 2026

Choose a reason for hiding this comment

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

[for reviewer] distribution node will be moved to the root in subsequent PR

@Nullable
public static DeclarativeConfigProperties getDistributionConfig(
AutoConfiguredOpenTelemetrySdk sdk) {
Expand All @@ -69,18 +79,38 @@ public static DeclarativeConfigProperties getDistributionConfig(
return null;
}

// TODO: This is temporary solution until distribution config support is implemented in the
// upstream. For now assume that distribution node is located under
// .instrumentation/development.java.distribution
// Replace this code with `return sdk.getConfigProvider().getDistributionConfig()` once is
// implemented
DeclarativeConfigProperties instrumentationConfig = configProvider.getInstrumentationConfig();
if (instrumentationConfig == null) {
return null;
}
return instrumentationConfig
.getStructured("java", empty())
.getStructured("distribution", empty());
return instrumentationConfig.getStructured("java", empty()).getStructured("distribution");
}

public static DeclarativeConfigProperties getDistributionConfig(
OpenTelemetryConfigurationModel model) {
ExperimentalInstrumentationModel instrumentationModel = model.getInstrumentationDevelopment();
if (instrumentationModel == null) {
return empty();
}

ExperimentalLanguageSpecificInstrumentationModel javaModel = instrumentationModel.getJava();
if (javaModel == null) {
return empty();
}

ComponentLoader componentLoader =
ComponentLoader.forClassLoader(DeclarativeConfigProperties.class.getClassLoader());
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not really sure what the ComponentLoader is used for here but I think that the one that sdk uses itself would use the extension class loader when running with agent. I guess the usage here is weird, usually you probably don't need to create DeclarativeConfigProperties from the model and won't need to create a ComponentLoader. This is just a note.

Map<String, ExperimentalLanguageSpecificInstrumentationPropertyModel> original =
javaModel.getAdditionalProperties();
Map<String, Object> properties = new HashMap<>();
ExperimentalLanguageSpecificInstrumentationPropertyModel distribution =
original.get("distribution");
properties.put(
"distribution", distribution != null ? distribution.getAdditionalProperties() : null);
DeclarativeConfigProperties config =
YamlDeclarativeConfigProperties.create(properties, componentLoader);

return config.getStructured("distribution", empty()); // Should this empty() be there?
}

public static Resource getResource(AutoConfiguredOpenTelemetrySdk sdk) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,7 @@ private static ProfilerConfiguration getProfilerConfiguration(
DeclarativeConfigProperties distributionConfig = AutoConfigureUtil.getDistributionConfig(sdk);
distributionConfig = Optional.ofNullable(distributionConfig).orElse(empty());
return new ProfilerDeclarativeConfiguration(
distributionConfig
.getStructured("splunk", empty())
.getStructured("profiling", empty())
.getStructured("always_on", empty()));
distributionConfig.getStructured("splunk", empty()).getStructured("profiling", empty()));
} else {
ConfigProperties configProperties = AutoConfigureUtil.getConfig(sdk);
return new ProfilerEnvVarsConfiguration(configProperties);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter;
import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporterBuilder;
import io.opentelemetry.exporter.otlp.internal.OtlpConfigUtil;
import io.opentelemetry.exporter.otlp.internal.OtlpGrpcLogRecordExporterComponentProvider;
import io.opentelemetry.exporter.otlp.internal.OtlpHttpLogRecordExporterComponentProvider;
import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter;
import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporterBuilder;
Expand All @@ -39,17 +40,19 @@ class LogExporterBuilder {
static LogRecordExporter fromConfig(DeclarativeConfigProperties exporterConfigProperties) {
if (exporterConfigProperties != null) {

DeclarativeConfigProperties otlpHttp = exporterConfigProperties.getStructured("otlp_http");
DeclarativeConfigProperties otlpHttp =
exporterConfigProperties.getStructured("otlp_log_http");
Copy link
Contributor Author

Choose a reason for hiding this comment

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

[for reviewer] Logger name updated due to GDI spec

if (otlpHttp != null) {
OtlpHttpLogRecordExporterComponentProvider provider =
new OtlpHttpLogRecordExporterComponentProvider();
return provider.create(otlpHttp);
}

DeclarativeConfigProperties otlpGrpc = exporterConfigProperties.getStructured("otlp_grpc");
DeclarativeConfigProperties otlpGrpc =
exporterConfigProperties.getStructured("otlp_log_grpc");
if (otlpGrpc != null) {
OtlpHttpLogRecordExporterComponentProvider provider =
new OtlpHttpLogRecordExporterComponentProvider();
OtlpGrpcLogRecordExporterComponentProvider provider =
new OtlpGrpcLogRecordExporterComponentProvider();
return provider.create(otlpGrpc);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.splunk.opentelemetry.profiler;

import com.google.common.annotations.VisibleForTesting;
import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties;
import io.opentelemetry.api.logs.Logger;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.logs.LogRecordProcessor;
Expand All @@ -28,14 +29,19 @@

public class OtelLoggerFactory {
private final Function<ConfigProperties, LogRecordExporter> logRecordExporter;
private final Function<DeclarativeConfigProperties, LogRecordExporter>
declarativeLogRecordExporter;

public OtelLoggerFactory() {
this(LogExporterBuilder::fromConfig);
this(LogExporterBuilder::fromConfig, LogExporterBuilder::fromConfig);
}

@VisibleForTesting
public OtelLoggerFactory(Function<ConfigProperties, LogRecordExporter> logRecordExporter) {
public OtelLoggerFactory(
Function<ConfigProperties, LogRecordExporter> logRecordExporter,
Function<DeclarativeConfigProperties, LogRecordExporter> declarativeLogRecordExporter) {
this.logRecordExporter = logRecordExporter;
this.declarativeLogRecordExporter = declarativeLogRecordExporter;
}

public Logger build(ConfigProperties properties, Resource resource) {
Expand All @@ -44,10 +50,20 @@ public Logger build(ConfigProperties properties, Resource resource) {
return buildOtelLogger(processor, resource);
}

public Logger build(DeclarativeConfigProperties properties, Resource resource) {
LogRecordExporter exporter = createLogRecordExporter(properties);
LogRecordProcessor processor = SimpleLogRecordProcessor.create(exporter);
return buildOtelLogger(processor, resource);
}

private LogRecordExporter createLogRecordExporter(ConfigProperties properties) {
return logRecordExporter.apply(properties);
}

private LogRecordExporter createLogRecordExporter(DeclarativeConfigProperties properties) {
return declarativeLogRecordExporter.apply(properties);
}

private Logger buildOtelLogger(LogRecordProcessor logProcessor, Resource resource) {
return SdkLoggerProvider.builder()
.addLogRecordProcessor(logProcessor)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,24 @@ public class ProfilerDeclarativeConfiguration implements ProfilerConfiguration {
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 config;
private final DeclarativeConfigProperties profilingConfig;

public ProfilerDeclarativeConfiguration(DeclarativeConfigProperties config) {
this.config = config;
public ProfilerDeclarativeConfiguration(DeclarativeConfigProperties profilingConfig) {
this.profilingConfig = profilingConfig;
}

@Override
public boolean isEnabled() {
return !config.equals(empty());
return (profilingConfig != null) && profilingConfig.getPropertyKeys().contains(ROOT_NODE_NAME);
}

@Override
Expand Down Expand Up @@ -127,7 +129,7 @@ public boolean getTracingStacksOnly() {

@Override
public int getStackDepth() {
return getConfigRoot().getInt("stack_depth_limit", 1024);
return getConfigRoot().getInt("stack_depth", 1024);
}

@Override
Expand All @@ -147,11 +149,11 @@ public Duration getRecordingDuration() {

@Override
public DeclarativeConfigProperties getConfigProperties() {
return config;
return profilingConfig;
}

private DeclarativeConfigProperties getConfigRoot() {
return config.getStructured("always_on", empty());
return profilingConfig.getStructured(ROOT_NODE_NAME, empty());
}

private DeclarativeConfigProperties getMemoryProfilerConfig() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class AsyncStackTraceExporter implements StackTraceExporter {

@Override
public void export(Collection<StackTrace> stackTraces) {
if (closed) {
if (closed || stackTraces.isEmpty()) {
return;
}
executor.submit(pprofExporter(otelLogger, stackTraces));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,102 +16,70 @@

package com.splunk.opentelemetry.profiler.snapshot;

import static io.opentelemetry.api.incubator.config.DeclarativeConfigProperties.empty;

import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties;
import io.opentelemetry.sdk.autoconfigure.AutoConfigureUtil;
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import java.time.Duration;
import java.util.Optional;
import java.util.logging.Logger;

public class SnapshotProfilingConfiguration {
private static final Logger logger =
Logger.getLogger(SnapshotProfilingConfiguration.class.getName());
public interface SnapshotProfilingConfiguration {
double MAX_SELECTION_PROBABILITY = 1.0;
double DEFAULT_SELECTION_PROBABILITY = 0.01;
int DEFAULT_STACK_DEPTH = 1024;
long DEFAULT_SAMPLING_INTERVAL = 10;
long DEFAULT_EXPORT_INTERVAL = 5000;
int DEFAULT_STAGING_CAPACITY = 2000;

public static final String CONFIG_KEY_ENABLE_SNAPSHOT_PROFILER =
"splunk.snapshot.profiler.enabled";
private static final String SELECTION_PROBABILITY_KEY = "splunk.snapshot.selection.probability";
private static final String STACK_DEPTH_KEY = "splunk.snapshot.profiler.max.stack.depth";
private static final String SAMPLING_INTERVAL_KEY = "splunk.snapshot.sampling.interval";
private static final String EXPORT_INTERVAL_KEY = "splunk.snapshot.profiler.export.interval";
private static final String STAGING_CAPACITY_KEY = "splunk.snapshot.profiler.staging.capacity";
void log();

private static final double DEFAULT_SELECTION_PROBABILITY = 0.01;
private static final double MAX_SELECTION_PROBABILITY = 1.0;
private static final int DEFAULT_STACK_DEPTH = 1024;
private static final Duration DEFAULT_SAMPLING_INTERVAL = Duration.ofMillis(10);
private static final Duration DEFAULT_EXPORT_INTERVAL = Duration.ofSeconds(5);
private static final int DEFAULT_STAGING_CAPACITY = 2000;
boolean isEnabled();

static void log(ConfigProperties properties) {
logger.fine("Snapshot Profiler Configuration:");
logger.fine("-------------------------------------------------------");
double getSnapshotSelectionProbability();

log(CONFIG_KEY_ENABLE_SNAPSHOT_PROFILER, isSnapshotProfilingEnabled(properties));
log(SELECTION_PROBABILITY_KEY, getSnapshotSelectionProbability(properties));
log(STACK_DEPTH_KEY, getStackDepth(properties));
log(SAMPLING_INTERVAL_KEY, getSamplingInterval(properties));
log(EXPORT_INTERVAL_KEY, getExportInterval(properties));
log(STAGING_CAPACITY_KEY, getStagingCapacity(properties));
logger.fine("-------------------------------------------------------");
}
int getStackDepth();

static boolean isSnapshotProfilingEnabled(ConfigProperties properties) {
return properties.getBoolean(CONFIG_KEY_ENABLE_SNAPSHOT_PROFILER, false);
}
Duration getSamplingInterval();

static double getSnapshotSelectionProbability(ConfigProperties properties) {
String selectionProbabilityPropertyValue =
properties.getString(
SELECTION_PROBABILITY_KEY, String.valueOf(DEFAULT_SELECTION_PROBABILITY));
try {
double selectionProbability = Double.parseDouble(selectionProbabilityPropertyValue);
if (selectionProbability > MAX_SELECTION_PROBABILITY) {
logger.warning(
"Configured snapshot selection probability of '"
+ selectionProbabilityPropertyValue
+ "' is higher than the maximum allowed probability. Using maximum allowed snapshot selection probability of '"
+ MAX_SELECTION_PROBABILITY
+ "'");
return MAX_SELECTION_PROBABILITY;
}
if (selectionProbability <= 0) {
logger.warning(
"Snapshot selection probability must be greater than 0. Using default snapshot"
+ "selection probability of '"
+ DEFAULT_SELECTION_PROBABILITY
+ "' instead.");
return DEFAULT_SELECTION_PROBABILITY;
}
return selectionProbability;
} catch (NumberFormatException e) {
logger.warning(
"Invalid snapshot selection probability: '"
+ selectionProbabilityPropertyValue
+ "', using default probability of '"
+ DEFAULT_SELECTION_PROBABILITY
+ "'");
return DEFAULT_SELECTION_PROBABILITY;
}
}
Duration getExportInterval();

static int getStackDepth(ConfigProperties properties) {
return properties.getInt(STACK_DEPTH_KEY, DEFAULT_STACK_DEPTH);
}
int getStagingCapacity();

static Duration getSamplingInterval(ConfigProperties properties) {
return properties.getDuration(SAMPLING_INTERVAL_KEY, DEFAULT_SAMPLING_INTERVAL);
}
Object getConfigProperties();

static Duration getExportInterval(ConfigProperties properties) {
return properties.getDuration(EXPORT_INTERVAL_KEY, DEFAULT_EXPORT_INTERVAL);
}

static int getStagingCapacity(ConfigProperties properties) {
return properties.getInt(STAGING_CAPACITY_KEY, DEFAULT_STAGING_CAPACITY);
}

private static void log(String key, Object value) {
logger.fine(" " + pad(key) + " : " + value);
static SnapshotProfilingConfiguration fromSdk(AutoConfiguredOpenTelemetrySdk sdk) {
if (AutoConfigureUtil.isDeclarativeConfig(sdk)) {
DeclarativeConfigProperties distributionConfig = AutoConfigureUtil.getDistributionConfig(sdk);
distributionConfig = Optional.ofNullable(distributionConfig).orElse(empty());
return new SnapshotProfilingDeclarativeConfiguration(
distributionConfig.getStructured("splunk", empty()).getStructured("profiling", empty()));
} else {
ConfigProperties configProperties = AutoConfigureUtil.getConfig(sdk);
return new SnapshotProfilingEnvVarsConfiguration(configProperties);
}
}

private static String pad(String str) {
return String.format("%42s", str);
static double validateSelectionProbability(double selectionProbability, Logger logger) {
if (selectionProbability > MAX_SELECTION_PROBABILITY) {
logger.warning(
"Configured snapshot selection probability of '"
+ selectionProbability
+ "' is higher than the maximum allowed probability. Using maximum allowed snapshot selection probability of '"
+ MAX_SELECTION_PROBABILITY
+ "'");
return MAX_SELECTION_PROBABILITY;
}
if (selectionProbability <= 0) {
logger.warning(
"Snapshot selection probability must be greater than 0. Using default snapshot"
+ "selection probability of '"
+ DEFAULT_SELECTION_PROBABILITY
+ "' instead.");
return DEFAULT_SELECTION_PROBABILITY;
}
return selectionProbability;
}
}
Loading