diff --git a/instrumentation-api/src/main/resources/META-INF/native-image/io.opentelemetry.instrumentation/opentelemetry-instrumentation-api/native-image.properties b/instrumentation-api/src/main/resources/META-INF/native-image/io.opentelemetry.instrumentation/opentelemetry-instrumentation-api/native-image.properties index 758891265640..83efd36a27e8 100644 --- a/instrumentation-api/src/main/resources/META-INF/native-image/io.opentelemetry.instrumentation/opentelemetry-instrumentation-api/native-image.properties +++ b/instrumentation-api/src/main/resources/META-INF/native-image/io.opentelemetry.instrumentation/opentelemetry-instrumentation-api/native-image.properties @@ -1,2 +1,4 @@ Args=\ - --initialize-at-build-time=io.opentelemetry.instrumentation.api.internal.cache.concurrentlinkedhashmap.ConcurrentLinkedHashMap + --initialize-at-build-time=io.opentelemetry.instrumentation.api.internal.cache.concurrentlinkedhashmap.ConcurrentLinkedHashMap \ + --initialize-at-build-time=io.opentelemetry.instrumentation.api.internal.cache.MapBackedCache \ + --initialize-at-build-time=io.opentelemetry.api.internal.InternalAttributeKeyImpl diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java17/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java17/Java17RuntimeMetricsInstaller.java b/instrumentation/runtime-telemetry/runtime-telemetry-java17/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java17/Java17RuntimeMetricsInstaller.java index ec4fab1528af..e4286d0e7148 100644 --- a/instrumentation/runtime-telemetry/runtime-telemetry-java17/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java17/Java17RuntimeMetricsInstaller.java +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java17/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java17/Java17RuntimeMetricsInstaller.java @@ -7,12 +7,11 @@ import com.google.auto.service.AutoService; import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.runtimemetrics.java17.RuntimeMetrics; -import io.opentelemetry.instrumentation.runtimemetrics.java17.RuntimeMetricsBuilder; +import io.opentelemetry.instrumentation.runtimemetrics.java17.internal.RuntimeMetricsConfigUtil; +import io.opentelemetry.javaagent.bootstrap.internal.AgentInstrumentationConfig; import io.opentelemetry.javaagent.extension.AgentListener; import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; /** An {@link AgentListener} that enables runtime metrics during agent startup. */ @AutoService(AgentListener.class) @@ -20,34 +19,13 @@ public class Java17RuntimeMetricsInstaller implements AgentListener { @Override public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredSdk) { - ConfigProperties config = AgentListener.resolveConfigProperties(autoConfiguredSdk); - - OpenTelemetry openTelemetry = GlobalOpenTelemetry.get(); - RuntimeMetricsBuilder builder = null; - /* - By default don't use any JFR metrics. May change this once semantic conventions are updated. - If enabled, default to only the metrics not already covered by runtime-telemetry-java8 - */ - boolean defaultEnabled = config.getBoolean("otel.instrumentation.common.default-enabled", true); - if (config.getBoolean("otel.instrumentation.runtime-telemetry-java17.enable-all", false)) { - builder = RuntimeMetrics.builder(openTelemetry).enableAllFeatures(); - } else if (config.getBoolean("otel.instrumentation.runtime-telemetry-java17.enabled", false)) { - builder = RuntimeMetrics.builder(openTelemetry); - } else if (config.getBoolean( - "otel.instrumentation.runtime-telemetry.enabled", defaultEnabled)) { - // This only uses metrics gathered by JMX - builder = RuntimeMetrics.builder(openTelemetry).disableAllFeatures(); - } - - if (builder != null) { - if (config.getBoolean( - "otel.instrumentation.runtime-telemetry.emit-experimental-telemetry", false)) { - builder.enableExperimentalJmxTelemetry(); - } - - RuntimeMetrics finalJfrTelemetry = builder.build(); - Thread cleanupTelemetry = new Thread(() -> finalJfrTelemetry.close()); - Runtime.getRuntime().addShutdownHook(cleanupTelemetry); + RuntimeMetrics runtimeMetrics = + RuntimeMetricsConfigUtil.configure( + RuntimeMetrics.builder(GlobalOpenTelemetry.get()), AgentInstrumentationConfig.get()); + if (runtimeMetrics != null) { + Runtime.getRuntime() + .addShutdownHook( + new Thread(runtimeMetrics::close, "OpenTelemetry RuntimeMetricsShutdownHook")); } } } diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java17/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java17/RuntimeMetrics.java b/instrumentation/runtime-telemetry/runtime-telemetry-java17/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java17/RuntimeMetrics.java index 96a98b7fcdbd..42c175d2df4c 100644 --- a/instrumentation/runtime-telemetry/runtime-telemetry-java17/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java17/RuntimeMetrics.java +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java17/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java17/RuntimeMetrics.java @@ -21,7 +21,7 @@ import jdk.jfr.consumer.RecordingStream; /** The entry point class for runtime metrics support using JFR and JMX. */ -public final class RuntimeMetrics implements Closeable { +public final class RuntimeMetrics implements AutoCloseable { private static final Logger logger = Logger.getLogger(RuntimeMetrics.class.getName()); @@ -101,7 +101,7 @@ private JfrRuntimeMetrics(OpenTelemetry openTelemetry, Predicate fea recordingStream.onEvent(handler.getEventName(), handler); }); recordingStream.onMetadata(event -> startUpLatch.countDown()); - Thread daemonRunner = new Thread(() -> recordingStream.start()); + Thread daemonRunner = new Thread(recordingStream::start, "OpenTelemetry JFR-Metrics-Runner"); daemonRunner.setDaemon(true); daemonRunner.start(); } @@ -138,7 +138,8 @@ CountDownLatch getStartUpLatch() { private static boolean isJfrAvailable() { try { Class.forName("jdk.jfr.FlightRecorder"); - } catch (ClassNotFoundException e) { + // UnsatisfiedLinkError or ClassNotFoundException + } catch (Exception e) { return false; } diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java17/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java17/RuntimeMetricsBuilder.java b/instrumentation/runtime-telemetry/runtime-telemetry-java17/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java17/RuntimeMetricsBuilder.java index e73c535e5803..103a3efef6ca 100644 --- a/instrumentation/runtime-telemetry/runtime-telemetry-java17/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java17/RuntimeMetricsBuilder.java +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java17/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java17/RuntimeMetricsBuilder.java @@ -7,17 +7,8 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.instrumentation.runtimemetrics.java8.Classes; -import io.opentelemetry.instrumentation.runtimemetrics.java8.Cpu; -import io.opentelemetry.instrumentation.runtimemetrics.java8.GarbageCollector; -import io.opentelemetry.instrumentation.runtimemetrics.java8.MemoryPools; -import io.opentelemetry.instrumentation.runtimemetrics.java8.Threads; -import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.ExperimentalBufferPools; -import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.ExperimentalCpu; -import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.ExperimentalMemoryPools; -import java.util.ArrayList; +import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.JmxRuntimeMetricsFactory; import java.util.Arrays; -import java.util.Collections; import java.util.EnumMap; import java.util.List; import javax.annotation.Nullable; @@ -83,7 +74,7 @@ public RuntimeMetricsBuilder disableAllJmx() { return this; } - /** Disable telemetry collection associated with the {@link JfrFeature}. */ + /** Enable experimental JMX telemetry collection. */ @CanIgnoreReturnValue public RuntimeMetricsBuilder enableExperimentalJmxTelemetry() { enableExperimentalJmxTelemetry = true; @@ -92,35 +83,15 @@ public RuntimeMetricsBuilder enableExperimentalJmxTelemetry() { /** Build and start an {@link RuntimeMetrics} with the config from this builder. */ public RuntimeMetrics build() { - List observables = buildObservables(); + List observables = + disableJmx + ? List.of() + : JmxRuntimeMetricsFactory.buildObservables( + openTelemetry, enableExperimentalJmxTelemetry); RuntimeMetrics.JfrRuntimeMetrics jfrRuntimeMetrics = buildJfrMetrics(); return new RuntimeMetrics(openTelemetry, observables, jfrRuntimeMetrics); } - @SuppressWarnings("CatchingUnchecked") - private List buildObservables() { - if (disableJmx) { - return Collections.emptyList(); - } - try { - // Set up metrics gathered by JMX - List observables = new ArrayList<>(); - observables.addAll(Classes.registerObservers(openTelemetry)); - observables.addAll(Cpu.registerObservers(openTelemetry)); - observables.addAll(GarbageCollector.registerObservers(openTelemetry)); - observables.addAll(MemoryPools.registerObservers(openTelemetry)); - observables.addAll(Threads.registerObservers(openTelemetry)); - if (enableExperimentalJmxTelemetry) { - observables.addAll(ExperimentalBufferPools.registerObservers(openTelemetry)); - observables.addAll(ExperimentalCpu.registerObservers(openTelemetry)); - observables.addAll(ExperimentalMemoryPools.registerObservers(openTelemetry)); - } - return observables; - } catch (Exception e) { - throw new IllegalStateException("Error building RuntimeMetrics", e); - } - } - @Nullable private RuntimeMetrics.JfrRuntimeMetrics buildJfrMetrics() { if (enabledFeatureMap.values().stream().noneMatch(isEnabled -> isEnabled)) { diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java17/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java17/internal/RuntimeMetricsConfigUtil.java b/instrumentation/runtime-telemetry/runtime-telemetry-java17/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java17/internal/RuntimeMetricsConfigUtil.java new file mode 100644 index 000000000000..f8ec8a57e05b --- /dev/null +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java17/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java17/internal/RuntimeMetricsConfigUtil.java @@ -0,0 +1,48 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.runtimemetrics.java17.internal; + +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; +import io.opentelemetry.instrumentation.runtimemetrics.java17.RuntimeMetrics; +import io.opentelemetry.instrumentation.runtimemetrics.java17.RuntimeMetricsBuilder; +import javax.annotation.Nullable; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public final class RuntimeMetricsConfigUtil { + private RuntimeMetricsConfigUtil() {} + + @Nullable + public static RuntimeMetrics configure( + RuntimeMetricsBuilder builder, InstrumentationConfig config) { + /* + By default, don't use any JFR metrics. May change this once semantic conventions are updated. + If enabled, default to only the metrics not already covered by runtime-telemetry-java8 + */ + boolean defaultEnabled = config.getBoolean("otel.instrumentation.common.default-enabled", true); + if (config.getBoolean("otel.instrumentation.runtime-telemetry-java17.enable-all", false)) { + builder.enableAllFeatures(); + } else if (config.getBoolean("otel.instrumentation.runtime-telemetry-java17.enabled", false)) { + // default configuration + } else if (config.getBoolean( + "otel.instrumentation.runtime-telemetry.enabled", defaultEnabled)) { + // This only uses metrics gathered by JMX + builder.disableAllFeatures(); + } else { + // nothing is enabled + return null; + } + + if (config.getBoolean( + "otel.instrumentation.runtime-telemetry.emit-experimental-telemetry", false)) { + builder.enableExperimentalJmxTelemetry(); + } + + return builder.build(); + } +} diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java8/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java8/Java8RuntimeMetricsInstaller.java b/instrumentation/runtime-telemetry/runtime-telemetry-java8/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java8/Java8RuntimeMetricsInstaller.java index b93bfa57e835..4af7dd610896 100644 --- a/instrumentation/runtime-telemetry/runtime-telemetry-java8/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java8/Java8RuntimeMetricsInstaller.java +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java8/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java8/Java8RuntimeMetricsInstaller.java @@ -7,21 +7,11 @@ import com.google.auto.service.AutoService; import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.instrumentation.runtimemetrics.java8.Classes; -import io.opentelemetry.instrumentation.runtimemetrics.java8.Cpu; -import io.opentelemetry.instrumentation.runtimemetrics.java8.GarbageCollector; -import io.opentelemetry.instrumentation.runtimemetrics.java8.MemoryPools; -import io.opentelemetry.instrumentation.runtimemetrics.java8.Threads; -import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.ExperimentalBufferPools; -import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.ExperimentalCpu; -import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.ExperimentalMemoryPools; -import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.JmxRuntimeMetricsUtil; +import io.opentelemetry.instrumentation.runtimemetrics.java8.RuntimeMetrics; +import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.RuntimeMetricsConfigUtil; +import io.opentelemetry.javaagent.bootstrap.internal.AgentInstrumentationConfig; import io.opentelemetry.javaagent.extension.AgentListener; import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; -import java.util.ArrayList; -import java.util.List; /** An {@link AgentListener} that enables runtime metrics during agent startup. */ @AutoService(AgentListener.class) @@ -29,30 +19,13 @@ public class Java8RuntimeMetricsInstaller implements AgentListener { @Override public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredSdk) { - ConfigProperties config = AgentListener.resolveConfigProperties(autoConfiguredSdk); - - boolean defaultEnabled = config.getBoolean("otel.instrumentation.common.default-enabled", true); - if (!config.getBoolean("otel.instrumentation.runtime-telemetry.enabled", defaultEnabled) - || Double.parseDouble(System.getProperty("java.specification.version")) >= 17) { - return; + RuntimeMetrics runtimeMetrics = + RuntimeMetricsConfigUtil.configure( + RuntimeMetrics.builder(GlobalOpenTelemetry.get()), AgentInstrumentationConfig.get()); + if (runtimeMetrics != null) { + Runtime.getRuntime() + .addShutdownHook( + new Thread(runtimeMetrics::close, "OpenTelemetry RuntimeMetricsShutdownHook")); } - - OpenTelemetry openTelemetry = GlobalOpenTelemetry.get(); - List observables = new ArrayList<>(); - observables.addAll(Classes.registerObservers(openTelemetry)); - observables.addAll(Cpu.registerObservers(openTelemetry)); - observables.addAll(GarbageCollector.registerObservers(openTelemetry)); - observables.addAll(MemoryPools.registerObservers(openTelemetry)); - observables.addAll(Threads.registerObservers(openTelemetry)); - - if (config.getBoolean( - "otel.instrumentation.runtime-telemetry.emit-experimental-telemetry", false)) { - observables.addAll(ExperimentalBufferPools.registerObservers(openTelemetry)); - observables.addAll(ExperimentalCpu.registerObservers(openTelemetry)); - observables.addAll(ExperimentalMemoryPools.registerObservers(openTelemetry)); - } - - Thread cleanupTelemetry = new Thread(() -> JmxRuntimeMetricsUtil.closeObservers(observables)); - Runtime.getRuntime().addShutdownHook(cleanupTelemetry); } } diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/RuntimeMetrics.java b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/RuntimeMetrics.java new file mode 100644 index 000000000000..62e711ebd9aa --- /dev/null +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/RuntimeMetrics.java @@ -0,0 +1,59 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.runtimemetrics.java8; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.JmxRuntimeMetricsUtil; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** The entry point class for runtime metrics support using JMX. */ +public final class RuntimeMetrics implements AutoCloseable { + + private static final Logger logger = Logger.getLogger(RuntimeMetrics.class.getName()); + + private final AtomicBoolean isClosed = new AtomicBoolean(); + private final List observables; + + RuntimeMetrics(List observables) { + this.observables = Collections.unmodifiableList(observables); + } + + /** + * Create and start {@link RuntimeMetrics}. + * + *

Listens for select JMX beans, extracts data, and records to various metrics. Recording will + * continue until {@link #close()} is called. + * + * @param openTelemetry the {@link OpenTelemetry} instance used to record telemetry + */ + public static RuntimeMetrics create(OpenTelemetry openTelemetry) { + return new RuntimeMetricsBuilder(openTelemetry).build(); + } + + /** + * Create a builder for configuring {@link RuntimeMetrics}. + * + * @param openTelemetry the {@link OpenTelemetry} instance used to record telemetry + */ + public static RuntimeMetricsBuilder builder(OpenTelemetry openTelemetry) { + return new RuntimeMetricsBuilder(openTelemetry); + } + + /** Stop recording JMX metrics. */ + @Override + public void close() { + if (!isClosed.compareAndSet(false, true)) { + logger.log(Level.WARNING, "RuntimeMetrics is already closed"); + return; + } + + JmxRuntimeMetricsUtil.closeObservers(observables); + } +} diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/RuntimeMetricsBuilder.java b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/RuntimeMetricsBuilder.java new file mode 100644 index 000000000000..e342626ac377 --- /dev/null +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/RuntimeMetricsBuilder.java @@ -0,0 +1,37 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.runtimemetrics.java8; + +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.JmxRuntimeMetricsFactory; +import java.util.List; + +/** Builder for {@link RuntimeMetrics}. */ +public final class RuntimeMetricsBuilder { + + private final OpenTelemetry openTelemetry; + + private boolean enableExperimentalJmxTelemetry = false; + + RuntimeMetricsBuilder(OpenTelemetry openTelemetry) { + this.openTelemetry = openTelemetry; + } + + /** Enable all JMX telemetry collection. */ + @CanIgnoreReturnValue + public RuntimeMetricsBuilder enableExperimentalJmxTelemetry() { + enableExperimentalJmxTelemetry = true; + return this; + } + + /** Build and start an {@link RuntimeMetrics} with the config from this builder. */ + public RuntimeMetrics build() { + List observables = + JmxRuntimeMetricsFactory.buildObservables(openTelemetry, enableExperimentalJmxTelemetry); + return new RuntimeMetrics(observables); + } +} diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/Threads.java b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/Threads.java index d3af5bf00ea9..204eff083329 100644 --- a/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/Threads.java +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/Threads.java @@ -57,7 +57,7 @@ public final class Threads { /** Register observers for java runtime class metrics. */ public static List registerObservers(OpenTelemetry openTelemetry) { - return INSTANCE.registerObservers(openTelemetry, !isJava9OrNewer()); + return INSTANCE.registerObservers(openTelemetry, useThreads()); } private List registerObservers(OpenTelemetry openTelemetry, boolean useThread) { @@ -116,6 +116,13 @@ private static boolean isJava9OrNewer() { return THREAD_INFO_IS_DAEMON != null; } + private static boolean useThreads() { + // GraalVM native image does not support ThreadMXBean yet + // see https://github.com/oracle/graal/issues/6101 + boolean isNativeExecution = System.getProperty("org.graalvm.nativeimage.imagecode") != null; + return !isJava9OrNewer() || isNativeExecution; + } + private static Consumer java8Callback(ThreadMXBean threadBean) { return measurement -> { int daemonThreadCount = threadBean.getDaemonThreadCount(); diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/internal/JmxRuntimeMetricsFactory.java b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/internal/JmxRuntimeMetricsFactory.java new file mode 100644 index 000000000000..2cb1b55b3d72 --- /dev/null +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/internal/JmxRuntimeMetricsFactory.java @@ -0,0 +1,41 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.runtimemetrics.java8.internal; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.runtimemetrics.java8.Classes; +import io.opentelemetry.instrumentation.runtimemetrics.java8.Cpu; +import io.opentelemetry.instrumentation.runtimemetrics.java8.GarbageCollector; +import io.opentelemetry.instrumentation.runtimemetrics.java8.MemoryPools; +import io.opentelemetry.instrumentation.runtimemetrics.java8.Threads; +import java.util.ArrayList; +import java.util.List; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public class JmxRuntimeMetricsFactory { + @SuppressWarnings("CatchingUnchecked") + public static List buildObservables( + OpenTelemetry openTelemetry, boolean enableExperimentalJmxTelemetry) { + // Set up metrics gathered by JMX + List observables = new ArrayList<>(); + observables.addAll(Classes.registerObservers(openTelemetry)); + observables.addAll(Cpu.registerObservers(openTelemetry)); + observables.addAll(GarbageCollector.registerObservers(openTelemetry)); + observables.addAll(MemoryPools.registerObservers(openTelemetry)); + observables.addAll(Threads.registerObservers(openTelemetry)); + if (enableExperimentalJmxTelemetry) { + observables.addAll(ExperimentalBufferPools.registerObservers(openTelemetry)); + observables.addAll(ExperimentalCpu.registerObservers(openTelemetry)); + observables.addAll(ExperimentalMemoryPools.registerObservers(openTelemetry)); + } + return observables; + } + + private JmxRuntimeMetricsFactory() {} +} diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/internal/RuntimeMetricsConfigUtil.java b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/internal/RuntimeMetricsConfigUtil.java new file mode 100644 index 000000000000..f0059cf7f9d3 --- /dev/null +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/internal/RuntimeMetricsConfigUtil.java @@ -0,0 +1,36 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.runtimemetrics.java8.internal; + +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; +import io.opentelemetry.instrumentation.runtimemetrics.java8.RuntimeMetrics; +import io.opentelemetry.instrumentation.runtimemetrics.java8.RuntimeMetricsBuilder; +import javax.annotation.Nullable; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public final class RuntimeMetricsConfigUtil { + private RuntimeMetricsConfigUtil() {} + + @Nullable + public static RuntimeMetrics configure( + RuntimeMetricsBuilder builder, InstrumentationConfig config) { + boolean defaultEnabled = config.getBoolean("otel.instrumentation.common.default-enabled", true); + if (!config.getBoolean("otel.instrumentation.runtime-telemetry.enabled", defaultEnabled)) { + // nothing is enabled + return null; + } + + if (config.getBoolean( + "otel.instrumentation.runtime-telemetry.emit-experimental-telemetry", false)) { + builder.enableExperimentalJmxTelemetry(); + } + + return builder.build(); + } +} diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/resources/META-INF/native-image/reflect-config.json b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/resources/META-INF/native-image/reflect-config.json new file mode 100644 index 000000000000..1e250d63300c --- /dev/null +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/resources/META-INF/native-image/reflect-config.json @@ -0,0 +1,10 @@ +[ + { + "name": "com.sun.management.OperatingSystemMXBean", + "allPublicMethods": true + }, + { + "name": "com.ibm.lang.management.OperatingSystemMXBean", + "allPublicMethods": true + } +] diff --git a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts index d701141e3f65..99fbcbd98b8e 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts +++ b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts @@ -53,6 +53,8 @@ dependencies { implementation(project(":instrumentation:logback:logback-mdc-1.0:library")) compileOnly("ch.qos.logback:logback-classic:1.0.0") implementation(project(":instrumentation:jdbc:library")) + implementation(project(":instrumentation:runtime-telemetry:runtime-telemetry-java8:library")) + implementation(project(":instrumentation:runtime-telemetry:runtime-telemetry-java17:library")) library("org.springframework.kafka:spring-kafka:2.9.0") library("org.springframework.boot:spring-boot-starter-actuator:$springBootVersion") diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/Java17RuntimeMetricsProvider.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/Java17RuntimeMetricsProvider.java new file mode 100644 index 000000000000..8f258004a853 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/Java17RuntimeMetricsProvider.java @@ -0,0 +1,34 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.runtimemetrics; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; +import io.opentelemetry.instrumentation.runtimemetrics.java17.RuntimeMetrics; +import io.opentelemetry.instrumentation.runtimemetrics.java17.internal.RuntimeMetricsConfigUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Configures runtime metrics collection for Java 17+. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public class Java17RuntimeMetricsProvider implements RuntimeMetricsProvider { + private static final Logger logger = LoggerFactory.getLogger(Java17RuntimeMetricsProvider.class); + + @Override + public int minJavaVersion() { + return 17; + } + + @Override + public AutoCloseable start(OpenTelemetry openTelemetry, InstrumentationConfig config) { + logger.debug("Use runtime metrics instrumentation for Java 17+"); + return RuntimeMetricsConfigUtil.configure(RuntimeMetrics.builder(openTelemetry), config); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/Java8RuntimeMetricsProvider.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/Java8RuntimeMetricsProvider.java new file mode 100644 index 000000000000..fbb10d9e935b --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/Java8RuntimeMetricsProvider.java @@ -0,0 +1,34 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.runtimemetrics; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; +import io.opentelemetry.instrumentation.runtimemetrics.java8.RuntimeMetrics; +import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.RuntimeMetricsConfigUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Configures runtime metrics collection for Java 8. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public class Java8RuntimeMetricsProvider implements RuntimeMetricsProvider { + private static final Logger logger = LoggerFactory.getLogger(Java8RuntimeMetricsProvider.class); + + @Override + public int minJavaVersion() { + return 8; + } + + @Override + public AutoCloseable start(OpenTelemetry openTelemetry, InstrumentationConfig config) { + logger.debug("Use runtime metrics instrumentation for Java 8"); + return RuntimeMetricsConfigUtil.configure(RuntimeMetrics.builder(openTelemetry), config); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/RuntimeMetricsAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/RuntimeMetricsAutoConfiguration.java new file mode 100644 index 000000000000..f58f4da5f217 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/RuntimeMetricsAutoConfiguration.java @@ -0,0 +1,65 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.runtimemetrics; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.ConfigPropertiesBridge; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import java.util.Comparator; +import java.util.Optional; +import javax.annotation.PreDestroy; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.event.EventListener; + +/** + * Configures runtime metrics collection. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +@ConditionalOnEnabledInstrumentation(module = "runtime-telemetry") +@Configuration +public class RuntimeMetricsAutoConfiguration { + + private static final Logger logger = + LoggerFactory.getLogger(RuntimeMetricsAutoConfiguration.class); + + private AutoCloseable closeable; + + @PreDestroy + public void stopMetrics() throws Exception { + if (closeable != null) { + closeable.close(); + } + } + + @EventListener + public void handleApplicationReadyEvent(ApplicationReadyEvent event) { + ConfigurableApplicationContext applicationContext = event.getApplicationContext(); + OpenTelemetry openTelemetry = applicationContext.getBean(OpenTelemetry.class); + ConfigPropertiesBridge config = + new ConfigPropertiesBridge(applicationContext.getBean(ConfigProperties.class)); + + double version = + Math.max(8, Double.parseDouble(System.getProperty("java.specification.version"))); + Optional metricsProvider = + applicationContext.getBeanProvider(RuntimeMetricsProvider.class).stream() + .sorted(Comparator.comparing(RuntimeMetricsProvider::minJavaVersion).reversed()) + .filter(provider -> provider.minJavaVersion() <= version) + .findFirst(); + + if (metricsProvider.isPresent()) { + this.closeable = metricsProvider.get().start(openTelemetry, config); + } else { + logger.debug("No runtime metrics instrumentation available for Java {}", version); + } + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/RuntimeMetricsProvider.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/RuntimeMetricsProvider.java new file mode 100644 index 000000000000..8549f9394e61 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/RuntimeMetricsProvider.java @@ -0,0 +1,23 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.runtimemetrics; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; +import javax.annotation.Nullable; + +/** + * Configures runtime metrics collection. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public interface RuntimeMetricsProvider { + int minJavaVersion(); + + @Nullable + AutoCloseable start(OpenTelemetry openTelemetry, InstrumentationConfig config); +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/ConfigPropertiesBridge.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/ConfigPropertiesBridge.java index 49d50f94cc5c..8305aef6a4f0 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/ConfigPropertiesBridge.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/ConfigPropertiesBridge.java @@ -13,7 +13,13 @@ import java.util.Map; import javax.annotation.Nullable; -final class ConfigPropertiesBridge implements InstrumentationConfig { +/** + * Support for {@link ConfigProperties} in {@link InstrumentationConfig}. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public final class ConfigPropertiesBridge implements InstrumentationConfig { private final ConfigProperties configProperties; diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring3/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/RuntimeMetricsBeanRegistrationExcludeFilter.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring3/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/RuntimeMetricsBeanRegistrationExcludeFilter.java new file mode 100644 index 000000000000..e6b98f3fa675 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring3/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/RuntimeMetricsBeanRegistrationExcludeFilter.java @@ -0,0 +1,25 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.runtimemetrics; + +import org.springframework.beans.factory.aot.BeanRegistrationExcludeFilter; +import org.springframework.beans.factory.support.RegisteredBean; + +/** + * Configures runtime metrics collection for Java 17+. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public class RuntimeMetricsBeanRegistrationExcludeFilter implements BeanRegistrationExcludeFilter { + @Override + public boolean isExcludedFromAotProcessing(RegisteredBean registeredBean) { + // The JFR-based runtime metric code is excluded from the Spring AOT processing step. + // That way, this code is not included in a Spring native image application. + + return Java17RuntimeMetricsProvider.class.getName().equals(registeredBean.getBeanName()); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 68a06350a09d..f179b7e7f9bd 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -476,6 +476,30 @@ "description": "Enables the DB statement sanitization.", "defaultValue": true }, + { + "name": "otel.instrumentation.runtime-telemetry.enabled", + "type": "java.lang.Boolean", + "description": "Enable runtime telemetry metrics.", + "defaultValue": true + }, + { + "name": "otel.instrumentation.runtime-telemetry.emit-experimental-telemetry", + "type": "java.lang.Boolean", + "description": "Enable the capture of experimental metrics.", + "defaultValue": false + }, + { + "name": "otel.instrumentation.runtime-telemetry-java17.enable-all", + "type": "java.lang.Boolean", + "description": "Enable the capture of all JFR based metrics.", + "defaultValue": false + }, + { + "name": "otel.instrumentation.runtime-telemetry-java17.enabled", + "type": "java.lang.Boolean", + "description": "Enable the capture of JFR based metrics.", + "defaultValue": false + }, { "name": "otel.instrumentation.spring-web.enabled", "type": "java.lang.Boolean", diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories index 049b3b068f1d..cb026785d354 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -10,7 +10,10 @@ io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.r io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.web.SpringWebInstrumentationAutoConfiguration,\ io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.webflux.SpringWebfluxInstrumentationAutoConfiguration,\ io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.webmvc.SpringWebMvc5InstrumentationAutoConfiguration,\ -io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.scheduling.SpringSchedulingInstrumentationAutoConfiguration +io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.scheduling.SpringSchedulingInstrumentationAutoConfiguration,\ +io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.runtimemetrics.RuntimeMetricsAutoConfiguration,\ +io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.runtimemetrics.Java8RuntimeMetricsProvider,\ +io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.runtimemetrics.Java17RuntimeMetricsProvider org.springframework.context.ApplicationListener=\ io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.logging.LogbackAppenderApplicationListener diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring/aot.factories b/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring/aot.factories index 8d199e0f8345..e97c4b46ae9c 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring/aot.factories +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring/aot.factories @@ -1,2 +1,5 @@ org.springframework.aot.hint.RuntimeHintsRegistrar=\ io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.annotations.OpenTelemetryAnnotationsRuntimeHints + +org.springframework.beans.factory.aot.BeanRegistrationExcludeFilter=\ +io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.runtimemetrics.RuntimeMetricsBeanRegistrationExcludeFilter diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 73b6f4a6f840..2392e0c35592 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -11,3 +11,6 @@ io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.w io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.web.RestClientInstrumentationAutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.webmvc.SpringWebMvc6InstrumentationAutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.scheduling.SpringSchedulingInstrumentationAutoConfiguration +io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.runtimemetrics.RuntimeMetricsAutoConfiguration +io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.runtimemetrics.Java8RuntimeMetricsProvider +io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.runtimemetrics.Java17RuntimeMetricsProvider diff --git a/smoke-tests-otel-starter/spring-boot-2/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-2/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTest.java index 315e8155413b..65315b926d5b 100644 --- a/smoke-tests-otel-starter/spring-boot-2/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTest.java +++ b/smoke-tests-otel-starter/spring-boot-2/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTest.java @@ -16,6 +16,7 @@ webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = { // The headers are simply set here to make sure that headers can be parsed - "otel.exporter.otlp.headers.c=3" + "otel.exporter.otlp.headers.c=3", + "otel.instrumentation.runtime-telemetry.emit-experimental-telemetry=true", }) class OtelSpringStarterSmokeTest extends AbstractOtelSpringStarterSmokeTest {} diff --git a/smoke-tests-otel-starter/spring-boot-3/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-3/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTest.java index 315e8155413b..89a0a07c1b3a 100644 --- a/smoke-tests-otel-starter/spring-boot-3/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTest.java +++ b/smoke-tests-otel-starter/spring-boot-3/src/test/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTest.java @@ -5,6 +5,8 @@ package io.opentelemetry.spring.smoketest; +import java.util.List; +import org.assertj.core.api.AbstractIterableAssert; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest( @@ -16,6 +18,36 @@ webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = { // The headers are simply set here to make sure that headers can be parsed - "otel.exporter.otlp.headers.c=3" + "otel.exporter.otlp.headers.c=3", + "otel.instrumentation.runtime-telemetry.emit-experimental-telemetry=true", + "otel.instrumentation.runtime-telemetry-java17.enable-all=true", }) -class OtelSpringStarterSmokeTest extends AbstractOtelSpringStarterSmokeTest {} +class OtelSpringStarterSmokeTest extends AbstractOtelSpringStarterSmokeTest { + + @Override + protected void assertAdditionalMetrics() { + if (System.getProperty("org.graalvm.nativeimage.imagecode") != null) { + // GraalVM native image does not support JFR + return; + } + + // JFR based metrics + for (String metric : + List.of( + "jvm.cpu.limit", + "jvm.buffer.count", + "jvm.class.count", + "jvm.cpu.context_switch", + "jvm.cpu.longlock", + "jvm.system.cpu.utilization", + "jvm.gc.duration", + "jvm.memory.init", + "jvm.memory.used", + "jvm.memory.allocation", + "jvm.network.io", + "jvm.thread.count")) { + testing.waitAndAssertMetrics( + "io.opentelemetry.runtime-telemetry-java17", metric, AbstractIterableAssert::isNotEmpty); + } + } +} diff --git a/smoke-tests-otel-starter/spring-boot-common/src/main/java/io/opentelemetry/spring/smoketest/AbstractOtelSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-common/src/main/java/io/opentelemetry/spring/smoketest/AbstractOtelSpringStarterSmokeTest.java index 0709434bc883..e4a85eb343f0 100644 --- a/smoke-tests-otel-starter/spring-boot-common/src/main/java/io/opentelemetry/spring/smoketest/AbstractOtelSpringStarterSmokeTest.java +++ b/smoke-tests-otel-starter/spring-boot-common/src/main/java/io/opentelemetry/spring/smoketest/AbstractOtelSpringStarterSmokeTest.java @@ -29,6 +29,8 @@ import io.opentelemetry.semconv.incubating.DbIncubatingAttributes; import io.opentelemetry.semconv.incubating.ServiceIncubatingAttributes; import java.net.URI; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import org.assertj.core.api.AbstractCharSequenceAssert; @@ -210,10 +212,33 @@ void shouldSendTelemetry() { OtelSpringStarterSmokeTestController.TEST_HISTOGRAM, AbstractIterableAssert::isNotEmpty); + // JMX based metrics - test one per JMX bean + List jmxMetrics = + new ArrayList<>( + Arrays.asList( + "jvm.thread.count", + "jvm.memory.used", + "jvm.system.cpu.load_1m", + "jvm.memory.init")); + + boolean noNative = System.getProperty("org.graalvm.nativeimage.imagecode") == null; + if (noNative) { + // GraalVM native image does not support buffer pools - have to investigate why + jmxMetrics.add("jvm.buffer.memory.usage"); + } + jmxMetrics.forEach( + metricName -> + testing.waitAndAssertMetrics( + "io.opentelemetry.runtime-telemetry-java8", + metricName, + AbstractIterableAssert::isNotEmpty)); + + assertAdditionalMetrics(); + // Log List exportedLogRecords = testing.getExportedLogRecords(); assertThat(exportedLogRecords).as("No log record exported.").isNotEmpty(); - if (System.getProperty("org.graalvm.nativeimage.imagecode") == null) { + if (noNative) { // log records differ in native image mode due to different startup timing LogRecordData firstLog = exportedLogRecords.get(0); assertThat(firstLog.getBodyValue().asString()) @@ -228,6 +253,8 @@ void shouldSendTelemetry() { } } + protected void assertAdditionalMetrics() {} + @Test void databaseQuery() { testing.clearAllExportedData(); diff --git a/smoke-tests-otel-starter/spring-boot-common/src/main/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTestController.java b/smoke-tests-otel-starter/spring-boot-common/src/main/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTestController.java index 7d02c29d5fc1..c4902b2d65d5 100644 --- a/smoke-tests-otel-starter/spring-boot-common/src/main/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTestController.java +++ b/smoke-tests-otel-starter/spring-boot-common/src/main/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTestController.java @@ -15,8 +15,6 @@ public class OtelSpringStarterSmokeTestController { public static final String PING = "/ping"; - public static final String REST_CLIENT = "/rest-client"; - public static final String REST_TEMPLATE = "/rest-template"; public static final String TEST_HISTOGRAM = "histogram-test-otel-spring-starter"; public static final String METER_SCOPE_NAME = "scope"; private final LongHistogram histogram;