From d0d45ecd29413b25fe5b7962ed5c744eef2c2a8d Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Mon, 20 Jan 2025 16:42:33 +0100 Subject: [PATCH 01/20] spring boot runtime metrics for java 17+ --- .../java17/Java17RuntimeMetricsInstaller.java | 32 ++----------- .../java17/RuntimeMetricsBuilder.java | 30 ++++++++++++ .../build.gradle.kts | 1 + ...Java17RuntimeMetricsAutoConfiguration.java | 48 +++++++++++++++++++ .../properties/ConfigPropertiesBridge.java | 8 +++- .../main/resources/META-INF/spring.factories | 3 +- ...ot.autoconfigure.AutoConfiguration.imports | 1 + .../AbstractOtelSpringStarterSmokeTest.java | 5 ++ .../OtelSpringStarterSmokeTestController.java | 2 - 9 files changed, 97 insertions(+), 33 deletions(-) create mode 100644 instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/Java17RuntimeMetricsAutoConfiguration.java 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..32fbd70c656f 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 @@ -9,10 +9,9 @@ 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.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,9 @@ 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.builder(openTelemetry) + .startFromInstrumentationConfig(AgentInstrumentationConfig.get()); } } 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..c1c011a06ccf 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,6 +7,7 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; import io.opentelemetry.instrumentation.runtimemetrics.java8.Classes; import io.opentelemetry.instrumentation.runtimemetrics.java8.Cpu; import io.opentelemetry.instrumentation.runtimemetrics.java8.GarbageCollector; @@ -90,6 +91,35 @@ public RuntimeMetricsBuilder enableExperimentalJmxTelemetry() { return this; } + public void startFromInstrumentationConfig(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)) { + this.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 + this.disableAllFeatures(); + } else { + // nothing is enabled + return; + } + + if (config.getBoolean( + "otel.instrumentation.runtime-telemetry.emit-experimental-telemetry", false)) { + this.enableExperimentalJmxTelemetry(); + } + + RuntimeMetrics finalJfrTelemetry = this.build(); + Thread cleanupTelemetry = new Thread(finalJfrTelemetry::close); + Runtime.getRuntime().addShutdownHook(cleanupTelemetry); + } + /** Build and start an {@link RuntimeMetrics} with the config from this builder. */ public RuntimeMetrics build() { List observables = buildObservables(); diff --git a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts index d701141e3f65..6cc1f6cbf6a9 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts +++ b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts @@ -53,6 +53,7 @@ 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-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/Java17RuntimeMetricsAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/Java17RuntimeMetricsAutoConfiguration.java new file mode 100644 index 000000000000..3c33b8d3e068 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/Java17RuntimeMetricsAutoConfiguration.java @@ -0,0 +1,48 @@ +/* + * 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.runtimemetrics.java17.RuntimeMetrics; +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 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-java17") +@Configuration +public class Java17RuntimeMetricsAutoConfiguration { + + private static final Logger logger = + LoggerFactory.getLogger(Java17RuntimeMetricsAutoConfiguration.class); + + @EventListener + public void handleApplicationReadyEvent(ApplicationReadyEvent event) { + if (Double.parseDouble(System.getProperty("java.specification.version")) < 17) { + logger.debug( + "Java 17 runtime metrics instrumentation enabled but running on Java version < 17"); + return; + } + logger.debug( + "Java 17 runtime metrics instrumentation enabled and running on Java version >= 17"); + ConfigurableApplicationContext applicationContext = event.getApplicationContext(); + OpenTelemetry openTelemetry = applicationContext.getBean(OpenTelemetry.class); + ConfigProperties configProperties = applicationContext.getBean(ConfigProperties.class); + RuntimeMetrics.builder(openTelemetry) + .startFromInstrumentationConfig(new ConfigPropertiesBridge(configProperties)); + } +} 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/resources/META-INF/spring.factories b/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories index 049b3b068f1d..a9c9e379f88c 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,8 @@ 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.Java17RuntimeMetricsAutoConfiguration 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/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..c7a9ffefac86 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,4 @@ 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.Java17RuntimeMetricsAutoConfiguration 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..9056a3fad25e 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 @@ -209,6 +209,11 @@ void shouldSendTelemetry() { OtelSpringStarterSmokeTestController.METER_SCOPE_NAME, OtelSpringStarterSmokeTestController.TEST_HISTOGRAM, AbstractIterableAssert::isNotEmpty); + // runtime metrics + testing.waitAndAssertMetrics( + "io.opentelemetry.runtime-telemetry-java8", + "jvm.thread.count", + AbstractIterableAssert::isNotEmpty); // Log List exportedLogRecords = testing.getExportedLogRecords(); 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; From 6d34a199ec60c47826d52bd15b21aae449e28b21 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 21 Jan 2025 14:19:23 +0100 Subject: [PATCH 02/20] spring boot runtime metrics for java 8 --- .../java17/Java17RuntimeMetricsInstaller.java | 5 +- .../java17/RuntimeMetricsBuilder.java | 41 ++----------- .../java8/Java8RuntimeMetricsInstaller.java | 42 ++----------- .../runtimemetrics/java8/RuntimeMetrics.java | 60 ++++++++++++++++++ .../java8/RuntimeMetricsBuilder.java | 61 +++++++++++++++++++ .../internal/JmxRuntimeMetricsFactory.java | 49 +++++++++++++++ .../build.gradle.kts | 1 + ...a => RuntimeMetricsAutoConfiguration.java} | 29 ++++----- .../main/resources/META-INF/spring.factories | 2 +- ...ot.autoconfigure.AutoConfiguration.imports | 2 +- 10 files changed, 198 insertions(+), 94 deletions(-) create mode 100644 instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/RuntimeMetrics.java create mode 100644 instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/RuntimeMetricsBuilder.java create mode 100644 instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/internal/JmxRuntimeMetricsFactory.java rename instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/{Java17RuntimeMetricsAutoConfiguration.java => RuntimeMetricsAutoConfiguration.java} (66%) 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 32fbd70c656f..10a535f8d393 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,7 +7,6 @@ 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.javaagent.bootstrap.internal.AgentInstrumentationConfig; import io.opentelemetry.javaagent.extension.AgentListener; @@ -19,9 +18,7 @@ public class Java17RuntimeMetricsInstaller implements AgentListener { @Override public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredSdk) { - OpenTelemetry openTelemetry = GlobalOpenTelemetry.get(); - - RuntimeMetrics.builder(openTelemetry) + RuntimeMetrics.builder(GlobalOpenTelemetry.get()) .startFromInstrumentationConfig(AgentInstrumentationConfig.get()); } } 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 c1c011a06ccf..025623659454 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 @@ -8,17 +8,8 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; -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; @@ -84,7 +75,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; @@ -122,35 +113,13 @@ public void startFromInstrumentationConfig(InstrumentationConfig config) { /** Build and start an {@link RuntimeMetrics} with the config from this builder. */ public RuntimeMetrics build() { - List observables = buildObservables(); + List observables = + JmxRuntimeMetricsFactory.buildObservables( + openTelemetry, disableJmx, 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-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..57a569a976dc 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,10 @@ 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.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 +18,7 @@ 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; - } - - 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); + RuntimeMetrics.builder(GlobalOpenTelemetry.get()) + .startFromInstrumentationConfig(AgentInstrumentationConfig.get()); } } 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..86afabd17301 --- /dev/null +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/RuntimeMetrics.java @@ -0,0 +1,60 @@ +/* + * 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.io.Closeable; +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 JFR and JMX. */ +public final class RuntimeMetrics implements Closeable { + + 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 JFR events, 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 JFR events. */ + @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..d82020c23fd7 --- /dev/null +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/RuntimeMetricsBuilder.java @@ -0,0 +1,61 @@ +/* + * 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.api.incubator.config.internal.InstrumentationConfig; +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 disableJmx = false; + 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, disableJmx, enableExperimentalJmxTelemetry); + return new RuntimeMetrics(observables); + } + + public void startFromInstrumentationConfig(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.enabled", defaultEnabled)) { + // nothing is enabled + return; + } + + if (config.getBoolean( + "otel.instrumentation.runtime-telemetry.emit-experimental-telemetry", false)) { + this.enableExperimentalJmxTelemetry(); + } + + RuntimeMetrics finalJfrTelemetry = this.build(); + Thread cleanupTelemetry = new Thread(finalJfrTelemetry::close); + Runtime.getRuntime().addShutdownHook(cleanupTelemetry); + } +} 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..16fdf3e000fd --- /dev/null +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/internal/JmxRuntimeMetricsFactory.java @@ -0,0 +1,49 @@ +/* + * 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.Collections; +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 disableJmx, boolean enableExperimentalJmxTelemetry) { + 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); + } + } + + private JmxRuntimeMetricsFactory() {} +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts index 6cc1f6cbf6a9..99fbcbd98b8e 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts +++ b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts @@ -53,6 +53,7 @@ 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") diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/Java17RuntimeMetricsAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/RuntimeMetricsAutoConfiguration.java similarity index 66% rename from instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/Java17RuntimeMetricsAutoConfiguration.java rename to instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/RuntimeMetricsAutoConfiguration.java index 3c33b8d3e068..c04a0fe704a5 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/Java17RuntimeMetricsAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/RuntimeMetricsAutoConfiguration.java @@ -6,7 +6,6 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.runtimemetrics; import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.instrumentation.runtimemetrics.java17.RuntimeMetrics; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.ConfigPropertiesBridge; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; @@ -23,26 +22,28 @@ *

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-java17") +@ConditionalOnEnabledInstrumentation(module = "runtime-telemetry") @Configuration -public class Java17RuntimeMetricsAutoConfiguration { +public class RuntimeMetricsAutoConfiguration { private static final Logger logger = - LoggerFactory.getLogger(Java17RuntimeMetricsAutoConfiguration.class); + LoggerFactory.getLogger(RuntimeMetricsAutoConfiguration.class); @EventListener public void handleApplicationReadyEvent(ApplicationReadyEvent event) { - if (Double.parseDouble(System.getProperty("java.specification.version")) < 17) { - logger.debug( - "Java 17 runtime metrics instrumentation enabled but running on Java version < 17"); - return; - } - logger.debug( - "Java 17 runtime metrics instrumentation enabled and running on Java version >= 17"); ConfigurableApplicationContext applicationContext = event.getApplicationContext(); OpenTelemetry openTelemetry = applicationContext.getBean(OpenTelemetry.class); - ConfigProperties configProperties = applicationContext.getBean(ConfigProperties.class); - RuntimeMetrics.builder(openTelemetry) - .startFromInstrumentationConfig(new ConfigPropertiesBridge(configProperties)); + ConfigPropertiesBridge config = + new ConfigPropertiesBridge(applicationContext.getBean(ConfigProperties.class)); + + if (Double.parseDouble(System.getProperty("java.specification.version")) >= 17) { + logger.debug("Use runtime metrics instrumentation for Java 17+"); + io.opentelemetry.instrumentation.runtimemetrics.java17.RuntimeMetrics.builder(openTelemetry) + .startFromInstrumentationConfig(config); + } else { + logger.debug("Use runtime metrics instrumentation for Java 8"); + io.opentelemetry.instrumentation.runtimemetrics.java8.RuntimeMetrics.builder(openTelemetry) + .startFromInstrumentationConfig(config); + } } } 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 a9c9e379f88c..bf7f5dc46fe2 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 @@ -11,7 +11,7 @@ io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.w 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.runtimemetrics.Java17RuntimeMetricsAutoConfiguration +io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.runtimemetrics.RuntimeMetricsAutoConfiguration 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/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 c7a9ffefac86..fda05853c855 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,4 +11,4 @@ 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.Java17RuntimeMetricsAutoConfiguration +io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.runtimemetrics.RuntimeMetricsAutoConfiguration From c68df50056d00e103107099e3bfa22a8df3e3d53 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 21 Jan 2025 15:06:11 +0100 Subject: [PATCH 03/20] spring boot runtime metrics for java 8 --- .../runtimemetrics/java8/RuntimeMetricsBuilder.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 index d82020c23fd7..dbe7435c1166 100644 --- 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 @@ -16,7 +16,6 @@ public final class RuntimeMetricsBuilder { private final OpenTelemetry openTelemetry; - private boolean disableJmx = false; private boolean enableExperimentalJmxTelemetry = false; RuntimeMetricsBuilder(OpenTelemetry openTelemetry) { @@ -34,7 +33,7 @@ public RuntimeMetricsBuilder enableExperimentalJmxTelemetry() { public RuntimeMetrics build() { List observables = JmxRuntimeMetricsFactory.buildObservables( - openTelemetry, disableJmx, enableExperimentalJmxTelemetry); + openTelemetry, false, enableExperimentalJmxTelemetry); return new RuntimeMetrics(observables); } From 38941cf75584e18cd15fe2d12db1f6abe9d2ed31 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 22 Jan 2025 11:31:29 +0100 Subject: [PATCH 04/20] fix native --- .../META-INF/native-image/reflect-config.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/resources/META-INF/native-image/reflect-config.json 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 + } +] From c667920a3a88d4341e6905cb1a13008524bb9c3a Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 22 Jan 2025 13:26:45 +0100 Subject: [PATCH 05/20] use spring shutdown hook --- .../java17/RuntimeMetricsBuilder.java | 23 +++++++++++++++---- .../java8/RuntimeMetricsBuilder.java | 20 ++++++++++++---- .../internal/JmxRuntimeMetricsFactory.java | 6 +---- .../RuntimeMetricsAutoConfiguration.java | 12 ++++++++++ 4 files changed, 46 insertions(+), 15 deletions(-) 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 025623659454..7411dc747886 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 @@ -12,6 +12,7 @@ import java.util.Arrays; import java.util.EnumMap; import java.util.List; +import java.util.function.Consumer; import javax.annotation.Nullable; /** Builder for {@link RuntimeMetrics}. */ @@ -23,6 +24,10 @@ public final class RuntimeMetricsBuilder { private boolean disableJmx = false; private boolean enableExperimentalJmxTelemetry = false; + private Consumer shutdownHook = + runnable -> { + Runtime.getRuntime().addShutdownHook(new Thread(runnable)); + }; RuntimeMetricsBuilder(OpenTelemetry openTelemetry) { this.openTelemetry = openTelemetry; @@ -82,6 +87,13 @@ public RuntimeMetricsBuilder enableExperimentalJmxTelemetry() { return this; } + /** Set a custom shutdown hook for the {@link RuntimeMetrics}. */ + @CanIgnoreReturnValue + public RuntimeMetricsBuilder setShutdownHook(Consumer shutdownHook) { + this.shutdownHook = shutdownHook; + return this; + } + public void startFromInstrumentationConfig(InstrumentationConfig config) { /* By default, don't use any JFR metrics. May change this once semantic conventions are updated. @@ -106,16 +118,17 @@ public void startFromInstrumentationConfig(InstrumentationConfig config) { this.enableExperimentalJmxTelemetry(); } - RuntimeMetrics finalJfrTelemetry = this.build(); - Thread cleanupTelemetry = new Thread(finalJfrTelemetry::close); - Runtime.getRuntime().addShutdownHook(cleanupTelemetry); + RuntimeMetrics runtimeMetrics = this.build(); + shutdownHook.accept(runtimeMetrics::close); } /** Build and start an {@link RuntimeMetrics} with the config from this builder. */ public RuntimeMetrics build() { List observables = - JmxRuntimeMetricsFactory.buildObservables( - openTelemetry, disableJmx, enableExperimentalJmxTelemetry); + disableJmx + ? List.of() + : JmxRuntimeMetricsFactory.buildObservables( + openTelemetry, enableExperimentalJmxTelemetry); RuntimeMetrics.JfrRuntimeMetrics jfrRuntimeMetrics = buildJfrMetrics(); return new RuntimeMetrics(openTelemetry, observables, jfrRuntimeMetrics); } 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 index dbe7435c1166..e6cceaa095d9 100644 --- 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 @@ -10,6 +10,7 @@ import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.JmxRuntimeMetricsFactory; import java.util.List; +import java.util.function.Consumer; /** Builder for {@link RuntimeMetrics}. */ public final class RuntimeMetricsBuilder { @@ -17,6 +18,10 @@ public final class RuntimeMetricsBuilder { private final OpenTelemetry openTelemetry; private boolean enableExperimentalJmxTelemetry = false; + private Consumer shutdownHook = + runnable -> { + Runtime.getRuntime().addShutdownHook(new Thread(runnable)); + }; RuntimeMetricsBuilder(OpenTelemetry openTelemetry) { this.openTelemetry = openTelemetry; @@ -32,11 +37,17 @@ public RuntimeMetricsBuilder enableExperimentalJmxTelemetry() { /** Build and start an {@link RuntimeMetrics} with the config from this builder. */ public RuntimeMetrics build() { List observables = - JmxRuntimeMetricsFactory.buildObservables( - openTelemetry, false, enableExperimentalJmxTelemetry); + JmxRuntimeMetricsFactory.buildObservables(openTelemetry, enableExperimentalJmxTelemetry); return new RuntimeMetrics(observables); } + /** Set a custom shutdown hook for the {@link RuntimeMetrics}. */ + @CanIgnoreReturnValue + public RuntimeMetricsBuilder setShutdownHook(Consumer shutdownHook) { + this.shutdownHook = shutdownHook; + return this; + } + public void startFromInstrumentationConfig(InstrumentationConfig config) { /* By default, don't use any JFR metrics. May change this once semantic conventions are updated. @@ -53,8 +64,7 @@ public void startFromInstrumentationConfig(InstrumentationConfig config) { this.enableExperimentalJmxTelemetry(); } - RuntimeMetrics finalJfrTelemetry = this.build(); - Thread cleanupTelemetry = new Thread(finalJfrTelemetry::close); - Runtime.getRuntime().addShutdownHook(cleanupTelemetry); + RuntimeMetrics runtimeMetrics = this.build(); + shutdownHook.accept(runtimeMetrics::close); } } 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 index 16fdf3e000fd..405fbb48c80e 100644 --- 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 @@ -12,7 +12,6 @@ import io.opentelemetry.instrumentation.runtimemetrics.java8.MemoryPools; import io.opentelemetry.instrumentation.runtimemetrics.java8.Threads; import java.util.ArrayList; -import java.util.Collections; import java.util.List; /** @@ -22,10 +21,7 @@ public class JmxRuntimeMetricsFactory { @SuppressWarnings("CatchingUnchecked") public static List buildObservables( - OpenTelemetry openTelemetry, boolean disableJmx, boolean enableExperimentalJmxTelemetry) { - if (disableJmx) { - return Collections.emptyList(); - } + OpenTelemetry openTelemetry, boolean enableExperimentalJmxTelemetry) { try { // Set up metrics gathered by JMX List observables = new ArrayList<>(); 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 index c04a0fe704a5..7c3a312131e8 100644 --- 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 @@ -9,6 +9,7 @@ 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 javax.annotation.PreDestroy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.context.event.ApplicationReadyEvent; @@ -29,6 +30,15 @@ public class RuntimeMetricsAutoConfiguration { private static final Logger logger = LoggerFactory.getLogger(RuntimeMetricsAutoConfiguration.class); + private Runnable shutdownHook; + + @PreDestroy + public void stopMetrics() { + if (shutdownHook != null) { + shutdownHook.run(); + } + } + @EventListener public void handleApplicationReadyEvent(ApplicationReadyEvent event) { ConfigurableApplicationContext applicationContext = event.getApplicationContext(); @@ -39,10 +49,12 @@ public void handleApplicationReadyEvent(ApplicationReadyEvent event) { if (Double.parseDouble(System.getProperty("java.specification.version")) >= 17) { logger.debug("Use runtime metrics instrumentation for Java 17+"); io.opentelemetry.instrumentation.runtimemetrics.java17.RuntimeMetrics.builder(openTelemetry) + .setShutdownHook(runnable -> shutdownHook = runnable) .startFromInstrumentationConfig(config); } else { logger.debug("Use runtime metrics instrumentation for Java 8"); io.opentelemetry.instrumentation.runtimemetrics.java8.RuntimeMetrics.builder(openTelemetry) + .setShutdownHook(runnable -> shutdownHook = runnable) .startFromInstrumentationConfig(config); } } From 5b7548ad9d2beae01d259eaa8c21cff17c42b3d9 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 28 Jan 2025 09:24:46 +0100 Subject: [PATCH 06/20] disable threads --- .../instrumentation/runtimemetrics/java8/Threads.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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..0705fbdbedb3 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,13 @@ 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 static boolean useThreads() { + // GraalVM native image does not support ThreadMXBean yet + // see https://github.com/oracle/graal/issues/6101 + return !isJava9OrNewer() || System.getProperty("org.graalvm.nativeimage.imagecode") != null; } private List registerObservers(OpenTelemetry openTelemetry, boolean useThread) { From b15e5cb84a7e888cd29b7b876db7ac8329f678ec Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 28 Jan 2025 09:42:04 +0100 Subject: [PATCH 07/20] disable threads --- .../runtimemetrics/java8/Threads.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) 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 0705fbdbedb3..2b720ab813bd 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 @@ -60,12 +60,6 @@ public static List registerObservers(OpenTelemetry openTelemetry) return INSTANCE.registerObservers(openTelemetry, useThreads()); } - private static boolean useThreads() { - // GraalVM native image does not support ThreadMXBean yet - // see https://github.com/oracle/graal/issues/6101 - return !isJava9OrNewer() || System.getProperty("org.graalvm.nativeimage.imagecode") != null; - } - private List registerObservers(OpenTelemetry openTelemetry, boolean useThread) { if (useThread) { return registerObservers(openTelemetry, Threads::getThreads); @@ -122,6 +116,12 @@ 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 + return !isJava9OrNewer() || System.getProperty("org.graalvm.nativeimage.imagecode") != null; + } + private static Consumer java8Callback(ThreadMXBean threadBean) { return measurement -> { int daemonThreadCount = threadBean.getDaemonThreadCount(); From a9a2262ab3ce91eee89e9d8cce0f94996ee6ebcc Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 28 Jan 2025 18:56:21 +0100 Subject: [PATCH 08/20] test more metric types --- .../smoketest/OtelSpringStarterSmokeTest.java | 16 ++++++++++++++-- .../AbstractOtelSpringStarterSmokeTest.java | 11 +++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) 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..eee483ccb319 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,7 @@ package io.opentelemetry.spring.smoketest; +import org.assertj.core.api.AbstractIterableAssert; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest( @@ -16,6 +17,17 @@ 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-java17.enabled=true", }) -class OtelSpringStarterSmokeTest extends AbstractOtelSpringStarterSmokeTest {} +class OtelSpringStarterSmokeTest extends AbstractOtelSpringStarterSmokeTest { + + @Override + protected void assertAdditionalMetrics() { + // JFR based metrics + testing.waitAndAssertMetrics( + "io.opentelemetry.runtime-telemetry-java17", + "jvm.cpu.limit", + 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 9056a3fad25e..1988b1e900df 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 @@ -209,11 +209,20 @@ void shouldSendTelemetry() { OtelSpringStarterSmokeTestController.METER_SCOPE_NAME, OtelSpringStarterSmokeTestController.TEST_HISTOGRAM, AbstractIterableAssert::isNotEmpty); + // runtime metrics + // from special logic for threads that is automatically detected in GraalVM native image + // see io.opentelemetry.instrumentation.runtimemetrics.java8.Threads testing.waitAndAssertMetrics( "io.opentelemetry.runtime-telemetry-java8", "jvm.thread.count", AbstractIterableAssert::isNotEmpty); + // JMX based metrics + testing.waitAndAssertMetrics( + "io.opentelemetry.runtime-telemetry-java8", + "jvm.memory.used", + AbstractIterableAssert::isNotEmpty); + assertAdditionalMetrics(); // Log List exportedLogRecords = testing.getExportedLogRecords(); @@ -233,6 +242,8 @@ void shouldSendTelemetry() { } } + protected void assertAdditionalMetrics() {} + @Test void databaseQuery() { testing.clearAllExportedData(); From 56f4b84d00afe3cc471f01b2c03d7298a2eb05bc Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 28 Jan 2025 19:10:42 +0100 Subject: [PATCH 09/20] add metadata --- ...itional-spring-configuration-metadata.json | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) 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", From 5762f4b8091388c7f67292ef10e068c845ea76ad Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 29 Jan 2025 13:28:33 +0100 Subject: [PATCH 10/20] fix jfr and native image --- .../runtimemetrics/java17/RuntimeMetrics.java | 5 +++-- .../runtimemetrics/java17/RuntimeMetricsBuilder.java | 2 +- .../runtimemetrics/java8/RuntimeMetricsBuilder.java | 2 +- smoke-tests-otel-starter/spring-boot-3/build.gradle.kts | 4 ++++ 4 files changed, 9 insertions(+), 4 deletions(-) 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..188d939576f7 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 @@ -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, "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 7411dc747886..add6f0460656 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 @@ -26,7 +26,7 @@ public final class RuntimeMetricsBuilder { private boolean enableExperimentalJmxTelemetry = false; private Consumer shutdownHook = runnable -> { - Runtime.getRuntime().addShutdownHook(new Thread(runnable)); + Runtime.getRuntime().addShutdownHook(new Thread(runnable, "RuntimeMetricsShutdownHook")); }; RuntimeMetricsBuilder(OpenTelemetry openTelemetry) { 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 index e6cceaa095d9..7e8e9a036f06 100644 --- 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 @@ -20,7 +20,7 @@ public final class RuntimeMetricsBuilder { private boolean enableExperimentalJmxTelemetry = false; private Consumer shutdownHook = runnable -> { - Runtime.getRuntime().addShutdownHook(new Thread(runnable)); + Runtime.getRuntime().addShutdownHook(new Thread(runnable, "RuntimeMetricsShutdownHook")); }; RuntimeMetricsBuilder(OpenTelemetry openTelemetry) { diff --git a/smoke-tests-otel-starter/spring-boot-3/build.gradle.kts b/smoke-tests-otel-starter/spring-boot-3/build.gradle.kts index 78e01e6685e1..284d3543fc0b 100644 --- a/smoke-tests-otel-starter/spring-boot-3/build.gradle.kts +++ b/smoke-tests-otel-starter/spring-boot-3/build.gradle.kts @@ -66,6 +66,10 @@ graalvmNative { // Workaround for https://github.com/junit-team/junit5/issues/3405 buildArgs.add("--initialize-at-build-time=org.junit.platform.launcher.core.LauncherConfig") buildArgs.add("--initialize-at-build-time=org.junit.jupiter.engine.config.InstantiatingConfigurationParameterConverter") + + // enable JFR - see https://www.graalvm.org/22.0/reference-manual/native-image/JFR/ + buildArgs.add("-H:+AllowVMInspection") + jvmArgs("-XX:+FlightRecorder") } // See https://github.com/graalvm/native-build-tools/issues/572 From dc491421d8a4dc1658f45f4e869c2a99b5d0cd7e Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 29 Jan 2025 13:29:38 +0100 Subject: [PATCH 11/20] fix jfr and native image --- smoke-tests-otel-starter/spring-boot-3/build.gradle.kts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/smoke-tests-otel-starter/spring-boot-3/build.gradle.kts b/smoke-tests-otel-starter/spring-boot-3/build.gradle.kts index 284d3543fc0b..3aacaf77d539 100644 --- a/smoke-tests-otel-starter/spring-boot-3/build.gradle.kts +++ b/smoke-tests-otel-starter/spring-boot-3/build.gradle.kts @@ -68,8 +68,7 @@ graalvmNative { buildArgs.add("--initialize-at-build-time=org.junit.jupiter.engine.config.InstantiatingConfigurationParameterConverter") // enable JFR - see https://www.graalvm.org/22.0/reference-manual/native-image/JFR/ - buildArgs.add("-H:+AllowVMInspection") - jvmArgs("-XX:+FlightRecorder") + buildArgs.add("--enable-monitoring=jfr") } // See https://github.com/graalvm/native-build-tools/issues/572 From 61fe87f3e7b9dad1701f15c8865306d045d4fbdb Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Thu, 30 Jan 2025 12:28:14 +0100 Subject: [PATCH 12/20] review --- .../runtimemetrics/java17/RuntimeMetrics.java | 2 +- .../runtimemetrics/java17/RuntimeMetricsBuilder.java | 2 +- .../runtimemetrics/java8/RuntimeMetrics.java | 6 +++--- .../runtimemetrics/java8/RuntimeMetricsBuilder.java | 6 +----- smoke-tests-otel-starter/spring-boot-3/build.gradle.kts | 2 +- 5 files changed, 7 insertions(+), 11 deletions(-) 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 188d939576f7..aabe280576cb 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 @@ -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, "JFR-Metrics-Runner"); + Thread daemonRunner = new Thread(recordingStream::start, "OpenTelemetry JFR-Metrics-Runner"); daemonRunner.setDaemon(true); daemonRunner.start(); } 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 add6f0460656..155ba20b7297 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 @@ -26,7 +26,7 @@ public final class RuntimeMetricsBuilder { private boolean enableExperimentalJmxTelemetry = false; private Consumer shutdownHook = runnable -> { - Runtime.getRuntime().addShutdownHook(new Thread(runnable, "RuntimeMetricsShutdownHook")); + Runtime.getRuntime().addShutdownHook(new Thread(runnable, "OpenTelemetry RuntimeMetricsShutdownHook")); }; RuntimeMetricsBuilder(OpenTelemetry openTelemetry) { 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 index 86afabd17301..dec3beac8685 100644 --- 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 @@ -14,7 +14,7 @@ import java.util.logging.Level; import java.util.logging.Logger; -/** The entry point class for runtime metrics support using JFR and JMX. */ +/** The entry point class for runtime metrics support using JMX. */ public final class RuntimeMetrics implements Closeable { private static final Logger logger = Logger.getLogger(RuntimeMetrics.class.getName()); @@ -29,7 +29,7 @@ public final class RuntimeMetrics implements Closeable { /** * Create and start {@link RuntimeMetrics}. * - *

Listens for select JFR events, extracts data, and records to various metrics. Recording will + *

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 @@ -47,7 +47,7 @@ public static RuntimeMetricsBuilder builder(OpenTelemetry openTelemetry) { return new RuntimeMetricsBuilder(openTelemetry); } - /** Stop recording JFR events. */ + /** Stop recording JMX metrics. */ @Override public void close() { if (!isClosed.compareAndSet(false, true)) { 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 index 7e8e9a036f06..0c7c973d1034 100644 --- 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 @@ -20,7 +20,7 @@ public final class RuntimeMetricsBuilder { private boolean enableExperimentalJmxTelemetry = false; private Consumer shutdownHook = runnable -> { - Runtime.getRuntime().addShutdownHook(new Thread(runnable, "RuntimeMetricsShutdownHook")); + Runtime.getRuntime().addShutdownHook(new Thread(runnable, "OpenTelemetry RuntimeMetricsShutdownHook")); }; RuntimeMetricsBuilder(OpenTelemetry openTelemetry) { @@ -49,10 +49,6 @@ public RuntimeMetricsBuilder setShutdownHook(Consumer shutdownHook) { } public void startFromInstrumentationConfig(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.enabled", defaultEnabled)) { // nothing is enabled diff --git a/smoke-tests-otel-starter/spring-boot-3/build.gradle.kts b/smoke-tests-otel-starter/spring-boot-3/build.gradle.kts index 3aacaf77d539..1392ad4844ab 100644 --- a/smoke-tests-otel-starter/spring-boot-3/build.gradle.kts +++ b/smoke-tests-otel-starter/spring-boot-3/build.gradle.kts @@ -67,7 +67,7 @@ graalvmNative { buildArgs.add("--initialize-at-build-time=org.junit.platform.launcher.core.LauncherConfig") buildArgs.add("--initialize-at-build-time=org.junit.jupiter.engine.config.InstantiatingConfigurationParameterConverter") - // enable JFR - see https://www.graalvm.org/22.0/reference-manual/native-image/JFR/ + // enable JFR - see https://www.graalvm.org/latest/reference-manual/native-image/debugging-and-diagnostics/JFR/ buildArgs.add("--enable-monitoring=jfr") } From 4b915768665a71fa14309e3bb2398a23badc5769 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Thu, 30 Jan 2025 13:01:44 +0100 Subject: [PATCH 13/20] test more metrics --- .../smoketest/OtelSpringStarterSmokeTest.java | 3 ++- .../smoketest/OtelSpringStarterSmokeTest.java | 25 +++++++++++++++---- .../AbstractOtelSpringStarterSmokeTest.java | 18 ++++++++++--- 3 files changed, 36 insertions(+), 10 deletions(-) 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 eee483ccb319..76184abc1f6b 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,7 @@ package io.opentelemetry.spring.smoketest; +import java.util.List; import org.assertj.core.api.AbstractIterableAssert; import org.springframework.boot.test.context.SpringBootTest; @@ -18,16 +19,30 @@ properties = { // The headers are simply set here to make sure that headers can be parsed "otel.exporter.otlp.headers.c=3", - "otel.instrumentation.runtime-telemetry-java17.enabled=true", + "otel.instrumentation.runtime-telemetry.emit-experimental-telemetry=true", + "otel.instrumentation.runtime-telemetry-java17.enable-all=true", }) class OtelSpringStarterSmokeTest extends AbstractOtelSpringStarterSmokeTest { @Override protected void assertAdditionalMetrics() { // JFR based metrics - testing.waitAndAssertMetrics( - "io.opentelemetry.runtime-telemetry-java17", - "jvm.cpu.limit", - AbstractIterableAssert::isNotEmpty); + 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 1988b1e900df..d4a0a2425a78 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,7 @@ import io.opentelemetry.semconv.incubating.DbIncubatingAttributes; import io.opentelemetry.semconv.incubating.ServiceIncubatingAttributes; import java.net.URI; +import java.util.Arrays; import java.util.Collections; import java.util.List; import org.assertj.core.api.AbstractCharSequenceAssert; @@ -217,11 +218,20 @@ void shouldSendTelemetry() { "io.opentelemetry.runtime-telemetry-java8", "jvm.thread.count", AbstractIterableAssert::isNotEmpty); + // JMX based metrics - testing.waitAndAssertMetrics( - "io.opentelemetry.runtime-telemetry-java8", - "jvm.memory.used", - AbstractIterableAssert::isNotEmpty); + Arrays.asList( + "jvm.memory.used", + "jvm.system.cpu.load_1m", + "jvm.buffer.memory.usage", + "jvm.memory.init") + .forEach( + metricName -> + testing.waitAndAssertMetrics( + "io.opentelemetry.runtime-telemetry-java8", + metricName, + AbstractIterableAssert::isNotEmpty)); + assertAdditionalMetrics(); // Log From 2cf304e8401b582ef3cdcf8d24dd1a7498324e5c Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 28 Jan 2025 18:59:30 +0100 Subject: [PATCH 14/20] improve assertions by showing the last result on a timeout --- .../testing/InstrumentationTestRunner.java | 68 ++++++++----------- .../testing/internal/AwaitUtil.java | 44 ++++++++++++ .../junit/InstrumentationExtension.java | 10 ++- 3 files changed, 75 insertions(+), 47 deletions(-) create mode 100644 testing-common/src/main/java/io/opentelemetry/instrumentation/testing/internal/AwaitUtil.java diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/InstrumentationTestRunner.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/InstrumentationTestRunner.java index 15d197f9b57e..b85e7422236c 100644 --- a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/InstrumentationTestRunner.java +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/InstrumentationTestRunner.java @@ -6,9 +6,9 @@ package io.opentelemetry.instrumentation.testing; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; -import static org.awaitility.Awaitility.await; import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.testing.internal.AwaitUtil; import io.opentelemetry.instrumentation.testing.util.TelemetryDataUtil; import io.opentelemetry.instrumentation.testing.util.ThrowingRunnable; import io.opentelemetry.instrumentation.testing.util.ThrowingSupplier; @@ -29,7 +29,6 @@ import java.util.stream.Collectors; import javax.annotation.Nullable; import org.assertj.core.api.ListAssert; -import org.awaitility.core.ConditionTimeoutException; /** * This interface defines a common set of operations for interaction with OpenTelemetry SDK and @@ -118,25 +117,8 @@ private > void waitAndAssertTraces( List assertionsList = new ArrayList<>(); assertions.forEach(assertionsList::add); - try { - await() - .untilAsserted(() -> doAssertTraces(traceComparator, assertionsList, verifyScopeVersion)); - } catch (Throwable t) { - // awaitility is doing a jmx call that is not implemented in GraalVM: - // call: - // https://github.com/awaitility/awaitility/blob/fbe16add874b4260dd240108304d5c0be84eabc8/awaitility/src/main/java/org/awaitility/core/ConditionAwaiter.java#L157 - // see https://github.com/oracle/graal/issues/6101 (spring boot graal native image) - if (t.getClass().getName().equals("com.oracle.svm.core.jdk.UnsupportedFeatureError") - || t instanceof ConditionTimeoutException) { - // Don't throw this failure since the stack is the awaitility thread, causing confusion. - // Instead, just assert one more time on the test thread, which will fail with a better - // stack trace. - // TODO: There is probably a better way to do this. - doAssertTraces(traceComparator, assertionsList, verifyScopeVersion); - } else { - throw t; - } - } + AwaitUtil.awaitUntilAsserted( + () -> doAssertTraces(traceComparator, assertionsList, verifyScopeVersion)); } private > void doAssertTraces( @@ -159,31 +141,35 @@ private > void doAssertTraces( */ public final void waitAndAssertMetrics( String instrumentationName, String metricName, Consumer> assertion) { - await() - .untilAsserted( - () -> - assertion.accept( - assertThat(getExportedMetrics()) - .filteredOn( - data -> - data.getInstrumentationScopeInfo() - .getName() - .equals(instrumentationName) - && data.getName().equals(metricName)))); + + AwaitUtil.awaitUntilAsserted( + () -> + assertion.accept( + assertThat(getExportedMetrics()) + .describedAs( + "Metrics for instrumentation %s and metric name %s", + instrumentationName, metricName) + .filteredOn( + data -> + data.getInstrumentationScopeInfo().getName().equals(instrumentationName) + && data.getName().equals(metricName)))); } @SafeVarargs public final void waitAndAssertMetrics( String instrumentationName, Consumer... assertions) { - await() - .untilAsserted( - () -> { - Collection metrics = instrumentationMetrics(instrumentationName); - assertThat(metrics).isNotEmpty(); - for (Consumer assertion : assertions) { - assertThat(metrics).anySatisfy(metric -> assertion.accept(assertThat(metric))); - } - }); + AwaitUtil.awaitUntilAsserted( + () -> { + Collection metrics = instrumentationMetrics(instrumentationName); + assertThat(metrics).isNotEmpty(); + for (int i = 0; i < assertions.length; i++) { + int index = i; + assertThat(metrics) + .describedAs( + "Metrics for instrumentation %s and assertion %d", instrumentationName, index) + .anySatisfy(metric -> assertions[index].accept(assertThat(metric))); + } + }); } private List instrumentationMetrics(String instrumentationName) { diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/internal/AwaitUtil.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/internal/AwaitUtil.java new file mode 100644 index 000000000000..c4900901c943 --- /dev/null +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/internal/AwaitUtil.java @@ -0,0 +1,44 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.testing.internal; + +import static org.awaitility.Awaitility.await; + +import org.awaitility.core.ConditionFactory; +import org.awaitility.core.ConditionTimeoutException; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public class AwaitUtil { + private AwaitUtil() {} + + public static void awaitUntilAsserted(Runnable runnable) { + awaitUntilAsserted(runnable, await()); + } + + public static void awaitUntilAsserted(Runnable runnable, ConditionFactory conditionFactory) { + try { + conditionFactory.untilAsserted(runnable::run); + } catch (Throwable t) { + // awaitility is doing a jmx call that is not implemented in GraalVM: + // call: + // https://github.com/awaitility/awaitility/blob/fbe16add874b4260dd240108304d5c0be84eabc8/awaitility/src/main/java/org/awaitility/core/ConditionAwaiter.java#L157 + // see https://github.com/oracle/graal/issues/6101 (spring boot graal native image) + if (t.getClass().getName().equals("com.oracle.svm.core.jdk.UnsupportedFeatureError") + || t instanceof ConditionTimeoutException) { + // Don't throw this failure since the stack is the awaitility thread, causing confusion. + // Instead, just assert one more time on the test thread, which will fail with a better + // stack trace - that is on the same thread as the test. + // TODO: There is probably a better way to do this. + runnable.run(); + } else { + throw t; + } + } + } +} diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/InstrumentationExtension.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/InstrumentationExtension.java index d1d8735be37a..a9bf57c5c56f 100644 --- a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/InstrumentationExtension.java +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/InstrumentationExtension.java @@ -12,6 +12,7 @@ import io.opentelemetry.context.ContextStorage; import io.opentelemetry.instrumentation.testing.InstrumentationTestRunner; import io.opentelemetry.instrumentation.testing.LibraryTestRunner; +import io.opentelemetry.instrumentation.testing.internal.AwaitUtil; import io.opentelemetry.instrumentation.testing.util.ContextStorageCloser; import io.opentelemetry.instrumentation.testing.util.ThrowingRunnable; import io.opentelemetry.instrumentation.testing.util.ThrowingSupplier; @@ -125,12 +126,9 @@ public List> waitForTraces(int numberOfTraces) { * This waits up to 20 seconds, then times out. */ public List waitForLogRecords(int numberOfLogRecords) { - await() - .timeout(Duration.ofSeconds(20)) - .untilAsserted( - () -> - assertThat(testRunner.getExportedLogRecords().size()) - .isEqualTo(numberOfLogRecords)); + AwaitUtil.awaitUntilAsserted( + () -> assertThat(testRunner.getExportedLogRecords().size()).isEqualTo(numberOfLogRecords), + await().timeout(Duration.ofSeconds(20))); return testRunner.getExportedLogRecords(); } From e05779773ef4b166c1871dc7bfce76c12ad1c3bc Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Thu, 30 Jan 2025 13:11:59 +0100 Subject: [PATCH 15/20] test more metrics --- .../spring/smoketest/OtelSpringStarterSmokeTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 76184abc1f6b..b94278398ff4 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 @@ -39,7 +39,7 @@ protected void assertAdditionalMetrics() { "jvm.memory.init", "jvm.memory.used", "jvm.memory.allocation", - "jvm.network.io ", + "jvm.network.io", "jvm.thread.count")) { testing.waitAndAssertMetrics( "io.opentelemetry.runtime-telemetry-java17", metric, AbstractIterableAssert::isNotEmpty); From 466b16db6ab2600d03b9b29e0bf4f0aeff92244c Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Thu, 30 Jan 2025 14:17:32 +0100 Subject: [PATCH 16/20] format --- .../runtimemetrics/java17/RuntimeMetricsBuilder.java | 3 ++- .../runtimemetrics/java8/RuntimeMetricsBuilder.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) 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 155ba20b7297..c6886403f01f 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 @@ -26,7 +26,8 @@ public final class RuntimeMetricsBuilder { private boolean enableExperimentalJmxTelemetry = false; private Consumer shutdownHook = runnable -> { - Runtime.getRuntime().addShutdownHook(new Thread(runnable, "OpenTelemetry RuntimeMetricsShutdownHook")); + Runtime.getRuntime() + .addShutdownHook(new Thread(runnable, "OpenTelemetry RuntimeMetricsShutdownHook")); }; RuntimeMetricsBuilder(OpenTelemetry openTelemetry) { 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 index 0c7c973d1034..584c68433e1b 100644 --- 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 @@ -20,7 +20,8 @@ public final class RuntimeMetricsBuilder { private boolean enableExperimentalJmxTelemetry = false; private Consumer shutdownHook = runnable -> { - Runtime.getRuntime().addShutdownHook(new Thread(runnable, "OpenTelemetry RuntimeMetricsShutdownHook")); + Runtime.getRuntime() + .addShutdownHook(new Thread(runnable, "OpenTelemetry RuntimeMetricsShutdownHook")); }; RuntimeMetricsBuilder(OpenTelemetry openTelemetry) { From c927ceb6a94b3662ef7ac3510712165b6b4919f2 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Thu, 30 Jan 2025 14:18:06 +0100 Subject: [PATCH 17/20] use newer graalvm --- .github/workflows/reusable-native-tests.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/reusable-native-tests.yml b/.github/workflows/reusable-native-tests.yml index 24f6f0fd0109..ae58a0d84a8c 100644 --- a/.github/workflows/reusable-native-tests.yml +++ b/.github/workflows/reusable-native-tests.yml @@ -16,12 +16,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - id: read-java - run: echo "version=$(cat .java-version)" >> "$GITHUB_OUTPUT" - uses: graalvm/setup-graalvm@aafbedb8d382ed0ca6167d3a051415f20c859274 # v1.2.8.1 with: version: "latest" - java-version: "${{ steps.read-java.outputs.version }}" + java-version: "23" # earlier versions have different arguments to enable JFR components: "native-image" - name: Running test env: From 5a82c35bbead13d2dbd2ce41a6dc60396df1b7f3 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Thu, 30 Jan 2025 14:18:24 +0100 Subject: [PATCH 18/20] Revert "improve assertions by showing the last result on a timeout" This reverts commit 2cf304e8401b582ef3cdcf8d24dd1a7498324e5c. --- .../testing/InstrumentationTestRunner.java | 68 +++++++++++-------- .../testing/internal/AwaitUtil.java | 44 ------------ .../junit/InstrumentationExtension.java | 10 +-- 3 files changed, 47 insertions(+), 75 deletions(-) delete mode 100644 testing-common/src/main/java/io/opentelemetry/instrumentation/testing/internal/AwaitUtil.java diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/InstrumentationTestRunner.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/InstrumentationTestRunner.java index b85e7422236c..15d197f9b57e 100644 --- a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/InstrumentationTestRunner.java +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/InstrumentationTestRunner.java @@ -6,9 +6,9 @@ package io.opentelemetry.instrumentation.testing; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static org.awaitility.Awaitility.await; import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.instrumentation.testing.internal.AwaitUtil; import io.opentelemetry.instrumentation.testing.util.TelemetryDataUtil; import io.opentelemetry.instrumentation.testing.util.ThrowingRunnable; import io.opentelemetry.instrumentation.testing.util.ThrowingSupplier; @@ -29,6 +29,7 @@ import java.util.stream.Collectors; import javax.annotation.Nullable; import org.assertj.core.api.ListAssert; +import org.awaitility.core.ConditionTimeoutException; /** * This interface defines a common set of operations for interaction with OpenTelemetry SDK and @@ -117,8 +118,25 @@ private > void waitAndAssertTraces( List assertionsList = new ArrayList<>(); assertions.forEach(assertionsList::add); - AwaitUtil.awaitUntilAsserted( - () -> doAssertTraces(traceComparator, assertionsList, verifyScopeVersion)); + try { + await() + .untilAsserted(() -> doAssertTraces(traceComparator, assertionsList, verifyScopeVersion)); + } catch (Throwable t) { + // awaitility is doing a jmx call that is not implemented in GraalVM: + // call: + // https://github.com/awaitility/awaitility/blob/fbe16add874b4260dd240108304d5c0be84eabc8/awaitility/src/main/java/org/awaitility/core/ConditionAwaiter.java#L157 + // see https://github.com/oracle/graal/issues/6101 (spring boot graal native image) + if (t.getClass().getName().equals("com.oracle.svm.core.jdk.UnsupportedFeatureError") + || t instanceof ConditionTimeoutException) { + // Don't throw this failure since the stack is the awaitility thread, causing confusion. + // Instead, just assert one more time on the test thread, which will fail with a better + // stack trace. + // TODO: There is probably a better way to do this. + doAssertTraces(traceComparator, assertionsList, verifyScopeVersion); + } else { + throw t; + } + } } private > void doAssertTraces( @@ -141,35 +159,31 @@ private > void doAssertTraces( */ public final void waitAndAssertMetrics( String instrumentationName, String metricName, Consumer> assertion) { - - AwaitUtil.awaitUntilAsserted( - () -> - assertion.accept( - assertThat(getExportedMetrics()) - .describedAs( - "Metrics for instrumentation %s and metric name %s", - instrumentationName, metricName) - .filteredOn( - data -> - data.getInstrumentationScopeInfo().getName().equals(instrumentationName) - && data.getName().equals(metricName)))); + await() + .untilAsserted( + () -> + assertion.accept( + assertThat(getExportedMetrics()) + .filteredOn( + data -> + data.getInstrumentationScopeInfo() + .getName() + .equals(instrumentationName) + && data.getName().equals(metricName)))); } @SafeVarargs public final void waitAndAssertMetrics( String instrumentationName, Consumer... assertions) { - AwaitUtil.awaitUntilAsserted( - () -> { - Collection metrics = instrumentationMetrics(instrumentationName); - assertThat(metrics).isNotEmpty(); - for (int i = 0; i < assertions.length; i++) { - int index = i; - assertThat(metrics) - .describedAs( - "Metrics for instrumentation %s and assertion %d", instrumentationName, index) - .anySatisfy(metric -> assertions[index].accept(assertThat(metric))); - } - }); + await() + .untilAsserted( + () -> { + Collection metrics = instrumentationMetrics(instrumentationName); + assertThat(metrics).isNotEmpty(); + for (Consumer assertion : assertions) { + assertThat(metrics).anySatisfy(metric -> assertion.accept(assertThat(metric))); + } + }); } private List instrumentationMetrics(String instrumentationName) { diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/internal/AwaitUtil.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/internal/AwaitUtil.java deleted file mode 100644 index c4900901c943..000000000000 --- a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/internal/AwaitUtil.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.testing.internal; - -import static org.awaitility.Awaitility.await; - -import org.awaitility.core.ConditionFactory; -import org.awaitility.core.ConditionTimeoutException; - -/** - * This class is internal and is hence not for public use. Its APIs are unstable and can change at - * any time. - */ -public class AwaitUtil { - private AwaitUtil() {} - - public static void awaitUntilAsserted(Runnable runnable) { - awaitUntilAsserted(runnable, await()); - } - - public static void awaitUntilAsserted(Runnable runnable, ConditionFactory conditionFactory) { - try { - conditionFactory.untilAsserted(runnable::run); - } catch (Throwable t) { - // awaitility is doing a jmx call that is not implemented in GraalVM: - // call: - // https://github.com/awaitility/awaitility/blob/fbe16add874b4260dd240108304d5c0be84eabc8/awaitility/src/main/java/org/awaitility/core/ConditionAwaiter.java#L157 - // see https://github.com/oracle/graal/issues/6101 (spring boot graal native image) - if (t.getClass().getName().equals("com.oracle.svm.core.jdk.UnsupportedFeatureError") - || t instanceof ConditionTimeoutException) { - // Don't throw this failure since the stack is the awaitility thread, causing confusion. - // Instead, just assert one more time on the test thread, which will fail with a better - // stack trace - that is on the same thread as the test. - // TODO: There is probably a better way to do this. - runnable.run(); - } else { - throw t; - } - } - } -} diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/InstrumentationExtension.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/InstrumentationExtension.java index a9bf57c5c56f..d1d8735be37a 100644 --- a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/InstrumentationExtension.java +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/InstrumentationExtension.java @@ -12,7 +12,6 @@ import io.opentelemetry.context.ContextStorage; import io.opentelemetry.instrumentation.testing.InstrumentationTestRunner; import io.opentelemetry.instrumentation.testing.LibraryTestRunner; -import io.opentelemetry.instrumentation.testing.internal.AwaitUtil; import io.opentelemetry.instrumentation.testing.util.ContextStorageCloser; import io.opentelemetry.instrumentation.testing.util.ThrowingRunnable; import io.opentelemetry.instrumentation.testing.util.ThrowingSupplier; @@ -126,9 +125,12 @@ public List> waitForTraces(int numberOfTraces) { * This waits up to 20 seconds, then times out. */ public List waitForLogRecords(int numberOfLogRecords) { - AwaitUtil.awaitUntilAsserted( - () -> assertThat(testRunner.getExportedLogRecords().size()).isEqualTo(numberOfLogRecords), - await().timeout(Duration.ofSeconds(20))); + await() + .timeout(Duration.ofSeconds(20)) + .untilAsserted( + () -> + assertThat(testRunner.getExportedLogRecords().size()) + .isEqualTo(numberOfLogRecords)); return testRunner.getExportedLogRecords(); } From 12e9dc5f7fa8805521c99e5d3acf2c961f6dfa70 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Thu, 30 Jan 2025 15:19:47 +0100 Subject: [PATCH 19/20] init at build time --- .../opentelemetry-instrumentation-api/native-image.properties | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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..5620b43240bb 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,3 @@ 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.api.internal.InternalAttributeKeyImpl From 13512c07667aab9a4038da5990bdb427663a0a90 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Fri, 31 Jan 2025 10:47:00 +0100 Subject: [PATCH 20/20] init at build time --- .../opentelemetry-instrumentation-api/native-image.properties | 1 + 1 file changed, 1 insertion(+) 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 5620b43240bb..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,3 +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.MapBackedCache \ --initialize-at-build-time=io.opentelemetry.api.internal.InternalAttributeKeyImpl