Spring boot runtime metrics native reduced (#13173)

Co-authored-by: Jean Bisutti <jean.bisutti@gmail.com>
This commit is contained in:
Gregor Zeitlinger 2025-02-14 15:29:25 +01:00 committed by GitHub
parent 0e2cc4f81e
commit 9101f0300f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 560 additions and 117 deletions

View File

@ -1,2 +1,4 @@
Args=\ Args=\
--initialize-at-build-time=io.opentelemetry.instrumentation.api.internal.cache.concurrentlinkedhashmap.ConcurrentLinkedHashMap --initialize-at-build-time=io.opentelemetry.instrumentation.api.internal.cache.concurrentlinkedhashmap.ConcurrentLinkedHashMap \
--initialize-at-build-time=io.opentelemetry.instrumentation.api.internal.cache.MapBackedCache \
--initialize-at-build-time=io.opentelemetry.api.internal.InternalAttributeKeyImpl

View File

@ -7,12 +7,11 @@ package io.opentelemetry.instrumentation.javaagent.runtimemetrics.java17;
import com.google.auto.service.AutoService; import com.google.auto.service.AutoService;
import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.runtimemetrics.java17.RuntimeMetrics; import io.opentelemetry.instrumentation.runtimemetrics.java17.RuntimeMetrics;
import io.opentelemetry.instrumentation.runtimemetrics.java17.RuntimeMetricsBuilder; import io.opentelemetry.instrumentation.runtimemetrics.java17.internal.RuntimeMetricsConfigUtil;
import io.opentelemetry.javaagent.bootstrap.internal.AgentInstrumentationConfig;
import io.opentelemetry.javaagent.extension.AgentListener; import io.opentelemetry.javaagent.extension.AgentListener;
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
/** An {@link AgentListener} that enables runtime metrics during agent startup. */ /** An {@link AgentListener} that enables runtime metrics during agent startup. */
@AutoService(AgentListener.class) @AutoService(AgentListener.class)
@ -20,34 +19,13 @@ public class Java17RuntimeMetricsInstaller implements AgentListener {
@Override @Override
public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredSdk) { public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredSdk) {
ConfigProperties config = AgentListener.resolveConfigProperties(autoConfiguredSdk); RuntimeMetrics runtimeMetrics =
RuntimeMetricsConfigUtil.configure(
OpenTelemetry openTelemetry = GlobalOpenTelemetry.get(); RuntimeMetrics.builder(GlobalOpenTelemetry.get()), AgentInstrumentationConfig.get());
RuntimeMetricsBuilder builder = null; if (runtimeMetrics != null) {
/* Runtime.getRuntime()
By default don't use any JFR metrics. May change this once semantic conventions are updated. .addShutdownHook(
If enabled, default to only the metrics not already covered by runtime-telemetry-java8 new Thread(runtimeMetrics::close, "OpenTelemetry RuntimeMetricsShutdownHook"));
*/
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);
} }
} }
} }

View File

@ -21,7 +21,7 @@ import jdk.jfr.FlightRecorder;
import jdk.jfr.consumer.RecordingStream; import jdk.jfr.consumer.RecordingStream;
/** The entry point class for runtime metrics support using JFR and JMX. */ /** The entry point class for runtime metrics support using JFR and JMX. */
public final class RuntimeMetrics implements Closeable { public final class RuntimeMetrics implements AutoCloseable {
private static final Logger logger = Logger.getLogger(RuntimeMetrics.class.getName()); private static final Logger logger = Logger.getLogger(RuntimeMetrics.class.getName());
@ -101,7 +101,7 @@ public final class RuntimeMetrics implements Closeable {
recordingStream.onEvent(handler.getEventName(), handler); recordingStream.onEvent(handler.getEventName(), handler);
}); });
recordingStream.onMetadata(event -> startUpLatch.countDown()); recordingStream.onMetadata(event -> startUpLatch.countDown());
Thread daemonRunner = new Thread(() -> recordingStream.start()); Thread daemonRunner = new Thread(recordingStream::start, "OpenTelemetry JFR-Metrics-Runner");
daemonRunner.setDaemon(true); daemonRunner.setDaemon(true);
daemonRunner.start(); daemonRunner.start();
} }
@ -138,7 +138,8 @@ public final class RuntimeMetrics implements Closeable {
private static boolean isJfrAvailable() { private static boolean isJfrAvailable() {
try { try {
Class.forName("jdk.jfr.FlightRecorder"); Class.forName("jdk.jfr.FlightRecorder");
} catch (ClassNotFoundException e) { // UnsatisfiedLinkError or ClassNotFoundException
} catch (Exception e) {
return false; return false;
} }

View File

@ -7,17 +7,8 @@ package io.opentelemetry.instrumentation.runtimemetrics.java17;
import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.CanIgnoreReturnValue;
import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.runtimemetrics.java8.Classes; import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.JmxRuntimeMetricsFactory;
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 java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.List; import java.util.List;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -83,7 +74,7 @@ public final class RuntimeMetricsBuilder {
return this; return this;
} }
/** Disable telemetry collection associated with the {@link JfrFeature}. */ /** Enable experimental JMX telemetry collection. */
@CanIgnoreReturnValue @CanIgnoreReturnValue
public RuntimeMetricsBuilder enableExperimentalJmxTelemetry() { public RuntimeMetricsBuilder enableExperimentalJmxTelemetry() {
enableExperimentalJmxTelemetry = true; enableExperimentalJmxTelemetry = true;
@ -92,35 +83,15 @@ public final class RuntimeMetricsBuilder {
/** Build and start an {@link RuntimeMetrics} with the config from this builder. */ /** Build and start an {@link RuntimeMetrics} with the config from this builder. */
public RuntimeMetrics build() { public RuntimeMetrics build() {
List<AutoCloseable> observables = buildObservables(); List<AutoCloseable> observables =
disableJmx
? List.of()
: JmxRuntimeMetricsFactory.buildObservables(
openTelemetry, enableExperimentalJmxTelemetry);
RuntimeMetrics.JfrRuntimeMetrics jfrRuntimeMetrics = buildJfrMetrics(); RuntimeMetrics.JfrRuntimeMetrics jfrRuntimeMetrics = buildJfrMetrics();
return new RuntimeMetrics(openTelemetry, observables, jfrRuntimeMetrics); return new RuntimeMetrics(openTelemetry, observables, jfrRuntimeMetrics);
} }
@SuppressWarnings("CatchingUnchecked")
private List<AutoCloseable> buildObservables() {
if (disableJmx) {
return Collections.emptyList();
}
try {
// Set up metrics gathered by JMX
List<AutoCloseable> 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 @Nullable
private RuntimeMetrics.JfrRuntimeMetrics buildJfrMetrics() { private RuntimeMetrics.JfrRuntimeMetrics buildJfrMetrics() {
if (enabledFeatureMap.values().stream().noneMatch(isEnabled -> isEnabled)) { if (enabledFeatureMap.values().stream().noneMatch(isEnabled -> isEnabled)) {

View File

@ -0,0 +1,48 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.runtimemetrics.java17.internal;
import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig;
import io.opentelemetry.instrumentation.runtimemetrics.java17.RuntimeMetrics;
import io.opentelemetry.instrumentation.runtimemetrics.java17.RuntimeMetricsBuilder;
import javax.annotation.Nullable;
/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
* any time.
*/
public final class RuntimeMetricsConfigUtil {
private RuntimeMetricsConfigUtil() {}
@Nullable
public static RuntimeMetrics configure(
RuntimeMetricsBuilder builder, InstrumentationConfig config) {
/*
By default, don't use any JFR metrics. May change this once semantic conventions are updated.
If enabled, default to only the metrics not already covered by runtime-telemetry-java8
*/
boolean defaultEnabled = config.getBoolean("otel.instrumentation.common.default-enabled", true);
if (config.getBoolean("otel.instrumentation.runtime-telemetry-java17.enable-all", false)) {
builder.enableAllFeatures();
} else if (config.getBoolean("otel.instrumentation.runtime-telemetry-java17.enabled", false)) {
// default configuration
} else if (config.getBoolean(
"otel.instrumentation.runtime-telemetry.enabled", defaultEnabled)) {
// This only uses metrics gathered by JMX
builder.disableAllFeatures();
} else {
// nothing is enabled
return null;
}
if (config.getBoolean(
"otel.instrumentation.runtime-telemetry.emit-experimental-telemetry", false)) {
builder.enableExperimentalJmxTelemetry();
}
return builder.build();
}
}

View File

@ -7,21 +7,11 @@ package io.opentelemetry.instrumentation.javaagent.runtimemetrics.java8;
import com.google.auto.service.AutoService; import com.google.auto.service.AutoService;
import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.runtimemetrics.java8.RuntimeMetrics;
import io.opentelemetry.instrumentation.runtimemetrics.java8.Classes; import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.RuntimeMetricsConfigUtil;
import io.opentelemetry.instrumentation.runtimemetrics.java8.Cpu; import io.opentelemetry.javaagent.bootstrap.internal.AgentInstrumentationConfig;
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.javaagent.extension.AgentListener; import io.opentelemetry.javaagent.extension.AgentListener;
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; 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. */ /** An {@link AgentListener} that enables runtime metrics during agent startup. */
@AutoService(AgentListener.class) @AutoService(AgentListener.class)
@ -29,30 +19,13 @@ public class Java8RuntimeMetricsInstaller implements AgentListener {
@Override @Override
public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredSdk) { public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredSdk) {
ConfigProperties config = AgentListener.resolveConfigProperties(autoConfiguredSdk); RuntimeMetrics runtimeMetrics =
RuntimeMetricsConfigUtil.configure(
boolean defaultEnabled = config.getBoolean("otel.instrumentation.common.default-enabled", true); RuntimeMetrics.builder(GlobalOpenTelemetry.get()), AgentInstrumentationConfig.get());
if (!config.getBoolean("otel.instrumentation.runtime-telemetry.enabled", defaultEnabled) if (runtimeMetrics != null) {
|| Double.parseDouble(System.getProperty("java.specification.version")) >= 17) { Runtime.getRuntime()
return; .addShutdownHook(
new Thread(runtimeMetrics::close, "OpenTelemetry RuntimeMetricsShutdownHook"));
} }
OpenTelemetry openTelemetry = GlobalOpenTelemetry.get();
List<AutoCloseable> 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);
} }
} }

View File

@ -0,0 +1,59 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.runtimemetrics.java8;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.JmxRuntimeMetricsUtil;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
/** The entry point class for runtime metrics support using JMX. */
public final class RuntimeMetrics implements AutoCloseable {
private static final Logger logger = Logger.getLogger(RuntimeMetrics.class.getName());
private final AtomicBoolean isClosed = new AtomicBoolean();
private final List<AutoCloseable> observables;
RuntimeMetrics(List<AutoCloseable> observables) {
this.observables = Collections.unmodifiableList(observables);
}
/**
* Create and start {@link RuntimeMetrics}.
*
* <p>Listens for select JMX beans, extracts data, and records to various metrics. Recording will
* continue until {@link #close()} is called.
*
* @param openTelemetry the {@link OpenTelemetry} instance used to record telemetry
*/
public static RuntimeMetrics create(OpenTelemetry openTelemetry) {
return new RuntimeMetricsBuilder(openTelemetry).build();
}
/**
* Create a builder for configuring {@link RuntimeMetrics}.
*
* @param openTelemetry the {@link OpenTelemetry} instance used to record telemetry
*/
public static RuntimeMetricsBuilder builder(OpenTelemetry openTelemetry) {
return new RuntimeMetricsBuilder(openTelemetry);
}
/** Stop recording JMX metrics. */
@Override
public void close() {
if (!isClosed.compareAndSet(false, true)) {
logger.log(Level.WARNING, "RuntimeMetrics is already closed");
return;
}
JmxRuntimeMetricsUtil.closeObservers(observables);
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.runtimemetrics.java8;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.JmxRuntimeMetricsFactory;
import java.util.List;
/** Builder for {@link RuntimeMetrics}. */
public final class RuntimeMetricsBuilder {
private final OpenTelemetry openTelemetry;
private boolean enableExperimentalJmxTelemetry = false;
RuntimeMetricsBuilder(OpenTelemetry openTelemetry) {
this.openTelemetry = openTelemetry;
}
/** Enable all JMX telemetry collection. */
@CanIgnoreReturnValue
public RuntimeMetricsBuilder enableExperimentalJmxTelemetry() {
enableExperimentalJmxTelemetry = true;
return this;
}
/** Build and start an {@link RuntimeMetrics} with the config from this builder. */
public RuntimeMetrics build() {
List<AutoCloseable> observables =
JmxRuntimeMetricsFactory.buildObservables(openTelemetry, enableExperimentalJmxTelemetry);
return new RuntimeMetrics(observables);
}
}

View File

@ -57,7 +57,7 @@ public final class Threads {
/** Register observers for java runtime class metrics. */ /** Register observers for java runtime class metrics. */
public static List<AutoCloseable> registerObservers(OpenTelemetry openTelemetry) { public static List<AutoCloseable> registerObservers(OpenTelemetry openTelemetry) {
return INSTANCE.registerObservers(openTelemetry, !isJava9OrNewer()); return INSTANCE.registerObservers(openTelemetry, useThreads());
} }
private List<AutoCloseable> registerObservers(OpenTelemetry openTelemetry, boolean useThread) { private List<AutoCloseable> registerObservers(OpenTelemetry openTelemetry, boolean useThread) {
@ -116,6 +116,13 @@ public final class Threads {
return THREAD_INFO_IS_DAEMON != null; return THREAD_INFO_IS_DAEMON != null;
} }
private static boolean useThreads() {
// GraalVM native image does not support ThreadMXBean yet
// see https://github.com/oracle/graal/issues/6101
boolean isNativeExecution = System.getProperty("org.graalvm.nativeimage.imagecode") != null;
return !isJava9OrNewer() || isNativeExecution;
}
private static Consumer<ObservableLongMeasurement> java8Callback(ThreadMXBean threadBean) { private static Consumer<ObservableLongMeasurement> java8Callback(ThreadMXBean threadBean) {
return measurement -> { return measurement -> {
int daemonThreadCount = threadBean.getDaemonThreadCount(); int daemonThreadCount = threadBean.getDaemonThreadCount();

View File

@ -0,0 +1,41 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.runtimemetrics.java8.internal;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.runtimemetrics.java8.Classes;
import io.opentelemetry.instrumentation.runtimemetrics.java8.Cpu;
import io.opentelemetry.instrumentation.runtimemetrics.java8.GarbageCollector;
import io.opentelemetry.instrumentation.runtimemetrics.java8.MemoryPools;
import io.opentelemetry.instrumentation.runtimemetrics.java8.Threads;
import java.util.ArrayList;
import java.util.List;
/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
* any time.
*/
public class JmxRuntimeMetricsFactory {
@SuppressWarnings("CatchingUnchecked")
public static List<AutoCloseable> buildObservables(
OpenTelemetry openTelemetry, boolean enableExperimentalJmxTelemetry) {
// Set up metrics gathered by JMX
List<AutoCloseable> observables = new ArrayList<>();
observables.addAll(Classes.registerObservers(openTelemetry));
observables.addAll(Cpu.registerObservers(openTelemetry));
observables.addAll(GarbageCollector.registerObservers(openTelemetry));
observables.addAll(MemoryPools.registerObservers(openTelemetry));
observables.addAll(Threads.registerObservers(openTelemetry));
if (enableExperimentalJmxTelemetry) {
observables.addAll(ExperimentalBufferPools.registerObservers(openTelemetry));
observables.addAll(ExperimentalCpu.registerObservers(openTelemetry));
observables.addAll(ExperimentalMemoryPools.registerObservers(openTelemetry));
}
return observables;
}
private JmxRuntimeMetricsFactory() {}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.runtimemetrics.java8.internal;
import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig;
import io.opentelemetry.instrumentation.runtimemetrics.java8.RuntimeMetrics;
import io.opentelemetry.instrumentation.runtimemetrics.java8.RuntimeMetricsBuilder;
import javax.annotation.Nullable;
/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
* any time.
*/
public final class RuntimeMetricsConfigUtil {
private RuntimeMetricsConfigUtil() {}
@Nullable
public static RuntimeMetrics configure(
RuntimeMetricsBuilder builder, InstrumentationConfig config) {
boolean defaultEnabled = config.getBoolean("otel.instrumentation.common.default-enabled", true);
if (!config.getBoolean("otel.instrumentation.runtime-telemetry.enabled", defaultEnabled)) {
// nothing is enabled
return null;
}
if (config.getBoolean(
"otel.instrumentation.runtime-telemetry.emit-experimental-telemetry", false)) {
builder.enableExperimentalJmxTelemetry();
}
return builder.build();
}
}

View File

@ -0,0 +1,10 @@
[
{
"name": "com.sun.management.OperatingSystemMXBean",
"allPublicMethods": true
},
{
"name": "com.ibm.lang.management.OperatingSystemMXBean",
"allPublicMethods": true
}
]

View File

@ -53,6 +53,8 @@ dependencies {
implementation(project(":instrumentation:logback:logback-mdc-1.0:library")) implementation(project(":instrumentation:logback:logback-mdc-1.0:library"))
compileOnly("ch.qos.logback:logback-classic:1.0.0") compileOnly("ch.qos.logback:logback-classic:1.0.0")
implementation(project(":instrumentation:jdbc:library")) implementation(project(":instrumentation:jdbc:library"))
implementation(project(":instrumentation:runtime-telemetry:runtime-telemetry-java8:library"))
implementation(project(":instrumentation:runtime-telemetry:runtime-telemetry-java17:library"))
library("org.springframework.kafka:spring-kafka:2.9.0") library("org.springframework.kafka:spring-kafka:2.9.0")
library("org.springframework.boot:spring-boot-starter-actuator:$springBootVersion") library("org.springframework.boot:spring-boot-starter-actuator:$springBootVersion")

View File

@ -0,0 +1,34 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.runtimemetrics;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig;
import io.opentelemetry.instrumentation.runtimemetrics.java17.RuntimeMetrics;
import io.opentelemetry.instrumentation.runtimemetrics.java17.internal.RuntimeMetricsConfigUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Configures runtime metrics collection for Java 17+.
*
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
* at any time.
*/
public class Java17RuntimeMetricsProvider implements RuntimeMetricsProvider {
private static final Logger logger = LoggerFactory.getLogger(Java17RuntimeMetricsProvider.class);
@Override
public int minJavaVersion() {
return 17;
}
@Override
public AutoCloseable start(OpenTelemetry openTelemetry, InstrumentationConfig config) {
logger.debug("Use runtime metrics instrumentation for Java 17+");
return RuntimeMetricsConfigUtil.configure(RuntimeMetrics.builder(openTelemetry), config);
}
}

View File

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

View File

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

View File

@ -0,0 +1,23 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.runtimemetrics;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig;
import javax.annotation.Nullable;
/**
* Configures runtime metrics collection.
*
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
* at any time.
*/
public interface RuntimeMetricsProvider {
int minJavaVersion();
@Nullable
AutoCloseable start(OpenTelemetry openTelemetry, InstrumentationConfig config);
}

View File

@ -13,7 +13,13 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import javax.annotation.Nullable; import javax.annotation.Nullable;
final class ConfigPropertiesBridge implements InstrumentationConfig { /**
* Support for {@link ConfigProperties} in {@link InstrumentationConfig}.
*
* <p>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; private final ConfigProperties configProperties;

View File

@ -0,0 +1,25 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.runtimemetrics;
import org.springframework.beans.factory.aot.BeanRegistrationExcludeFilter;
import org.springframework.beans.factory.support.RegisteredBean;
/**
* Configures runtime metrics collection for Java 17+.
*
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
* at any time.
*/
public class RuntimeMetricsBeanRegistrationExcludeFilter implements BeanRegistrationExcludeFilter {
@Override
public boolean isExcludedFromAotProcessing(RegisteredBean registeredBean) {
// The JFR-based runtime metric code is excluded from the Spring AOT processing step.
// That way, this code is not included in a Spring native image application.
return Java17RuntimeMetricsProvider.class.getName().equals(registeredBean.getBeanName());
}
}

View File

@ -476,6 +476,30 @@
"description": "Enables the DB statement sanitization.", "description": "Enables the DB statement sanitization.",
"defaultValue": true "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", "name": "otel.instrumentation.spring-web.enabled",
"type": "java.lang.Boolean", "type": "java.lang.Boolean",

View File

@ -10,7 +10,10 @@ io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.r
io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.web.SpringWebInstrumentationAutoConfiguration,\ io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.web.SpringWebInstrumentationAutoConfiguration,\
io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.webflux.SpringWebfluxInstrumentationAutoConfiguration,\ 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.webmvc.SpringWebMvc5InstrumentationAutoConfiguration,\
io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.scheduling.SpringSchedulingInstrumentationAutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.scheduling.SpringSchedulingInstrumentationAutoConfiguration,\
io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.runtimemetrics.RuntimeMetricsAutoConfiguration,\
io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.runtimemetrics.Java8RuntimeMetricsProvider,\
io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.runtimemetrics.Java17RuntimeMetricsProvider
org.springframework.context.ApplicationListener=\ org.springframework.context.ApplicationListener=\
io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.logging.LogbackAppenderApplicationListener io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.logging.LogbackAppenderApplicationListener

View File

@ -1,2 +1,5 @@
org.springframework.aot.hint.RuntimeHintsRegistrar=\ org.springframework.aot.hint.RuntimeHintsRegistrar=\
io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.annotations.OpenTelemetryAnnotationsRuntimeHints io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.annotations.OpenTelemetryAnnotationsRuntimeHints
org.springframework.beans.factory.aot.BeanRegistrationExcludeFilter=\
io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.runtimemetrics.RuntimeMetricsBeanRegistrationExcludeFilter

View File

@ -11,3 +11,6 @@ io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.w
io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.web.RestClientInstrumentationAutoConfiguration io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.web.RestClientInstrumentationAutoConfiguration
io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.webmvc.SpringWebMvc6InstrumentationAutoConfiguration 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.scheduling.SpringSchedulingInstrumentationAutoConfiguration
io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.runtimemetrics.RuntimeMetricsAutoConfiguration
io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.runtimemetrics.Java8RuntimeMetricsProvider
io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.runtimemetrics.Java17RuntimeMetricsProvider

View File

@ -16,6 +16,7 @@ import org.springframework.boot.test.context.SpringBootTest;
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = { properties = {
// The headers are simply set here to make sure that headers can be parsed // 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 {} class OtelSpringStarterSmokeTest extends AbstractOtelSpringStarterSmokeTest {}

View File

@ -5,6 +5,8 @@
package io.opentelemetry.spring.smoketest; package io.opentelemetry.spring.smoketest;
import java.util.List;
import org.assertj.core.api.AbstractIterableAssert;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest( @SpringBootTest(
@ -16,6 +18,36 @@ import org.springframework.boot.test.context.SpringBootTest;
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = { properties = {
// The headers are simply set here to make sure that headers can be parsed // The headers are simply set here to make sure that headers can be parsed
"otel.exporter.otlp.headers.c=3" "otel.exporter.otlp.headers.c=3",
"otel.instrumentation.runtime-telemetry.emit-experimental-telemetry=true",
"otel.instrumentation.runtime-telemetry-java17.enable-all=true",
}) })
class OtelSpringStarterSmokeTest extends AbstractOtelSpringStarterSmokeTest {} class OtelSpringStarterSmokeTest extends AbstractOtelSpringStarterSmokeTest {
@Override
protected void assertAdditionalMetrics() {
if (System.getProperty("org.graalvm.nativeimage.imagecode") != null) {
// GraalVM native image does not support JFR
return;
}
// JFR based metrics
for (String metric :
List.of(
"jvm.cpu.limit",
"jvm.buffer.count",
"jvm.class.count",
"jvm.cpu.context_switch",
"jvm.cpu.longlock",
"jvm.system.cpu.utilization",
"jvm.gc.duration",
"jvm.memory.init",
"jvm.memory.used",
"jvm.memory.allocation",
"jvm.network.io",
"jvm.thread.count")) {
testing.waitAndAssertMetrics(
"io.opentelemetry.runtime-telemetry-java17", metric, AbstractIterableAssert::isNotEmpty);
}
}
}

View File

@ -29,6 +29,8 @@ import io.opentelemetry.semconv.incubating.CodeIncubatingAttributes;
import io.opentelemetry.semconv.incubating.DbIncubatingAttributes; import io.opentelemetry.semconv.incubating.DbIncubatingAttributes;
import io.opentelemetry.semconv.incubating.ServiceIncubatingAttributes; import io.opentelemetry.semconv.incubating.ServiceIncubatingAttributes;
import java.net.URI; import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import org.assertj.core.api.AbstractCharSequenceAssert; import org.assertj.core.api.AbstractCharSequenceAssert;
@ -210,10 +212,33 @@ class AbstractOtelSpringStarterSmokeTest extends AbstractSpringStarterSmokeTest
OtelSpringStarterSmokeTestController.TEST_HISTOGRAM, OtelSpringStarterSmokeTestController.TEST_HISTOGRAM,
AbstractIterableAssert::isNotEmpty); AbstractIterableAssert::isNotEmpty);
// JMX based metrics - test one per JMX bean
List<String> jmxMetrics =
new ArrayList<>(
Arrays.asList(
"jvm.thread.count",
"jvm.memory.used",
"jvm.system.cpu.load_1m",
"jvm.memory.init"));
boolean noNative = System.getProperty("org.graalvm.nativeimage.imagecode") == null;
if (noNative) {
// GraalVM native image does not support buffer pools - have to investigate why
jmxMetrics.add("jvm.buffer.memory.usage");
}
jmxMetrics.forEach(
metricName ->
testing.waitAndAssertMetrics(
"io.opentelemetry.runtime-telemetry-java8",
metricName,
AbstractIterableAssert::isNotEmpty));
assertAdditionalMetrics();
// Log // Log
List<LogRecordData> exportedLogRecords = testing.getExportedLogRecords(); List<LogRecordData> exportedLogRecords = testing.getExportedLogRecords();
assertThat(exportedLogRecords).as("No log record exported.").isNotEmpty(); assertThat(exportedLogRecords).as("No log record exported.").isNotEmpty();
if (System.getProperty("org.graalvm.nativeimage.imagecode") == null) { if (noNative) {
// log records differ in native image mode due to different startup timing // log records differ in native image mode due to different startup timing
LogRecordData firstLog = exportedLogRecords.get(0); LogRecordData firstLog = exportedLogRecords.get(0);
assertThat(firstLog.getBodyValue().asString()) assertThat(firstLog.getBodyValue().asString())
@ -228,6 +253,8 @@ class AbstractOtelSpringStarterSmokeTest extends AbstractSpringStarterSmokeTest
} }
} }
protected void assertAdditionalMetrics() {}
@Test @Test
void databaseQuery() { void databaseQuery() {
testing.clearAllExportedData(); testing.clearAllExportedData();

View File

@ -15,8 +15,6 @@ import org.springframework.web.bind.annotation.RestController;
public class OtelSpringStarterSmokeTestController { public class OtelSpringStarterSmokeTestController {
public static final String PING = "/ping"; 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 TEST_HISTOGRAM = "histogram-test-otel-spring-starter";
public static final String METER_SCOPE_NAME = "scope"; public static final String METER_SCOPE_NAME = "scope";
private final LongHistogram histogram; private final LongHistogram histogram;