Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spring boot runtime metrics native reduced #13173

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
646ec13
spring boot runtime metrics for java 17+
zeitlinger Jan 20, 2025
d94f2d6
spring boot runtime metrics for java 8
zeitlinger Jan 21, 2025
6b9c026
spring boot runtime metrics for java 8
zeitlinger Jan 21, 2025
7792de6
fix native
zeitlinger Jan 22, 2025
b262de9
use spring shutdown hook
zeitlinger Jan 22, 2025
e10e8c1
disable threads
zeitlinger Jan 28, 2025
17dd251
disable threads
zeitlinger Jan 28, 2025
697c197
test more metric types
zeitlinger Jan 28, 2025
a4eec27
add metadata
zeitlinger Jan 28, 2025
66a8a9a
fix jfr and native image
zeitlinger Jan 29, 2025
c659bcb
fix jfr and native image
zeitlinger Jan 29, 2025
f0c06cd
review
zeitlinger Jan 30, 2025
1b3b510
test more metrics
zeitlinger Jan 30, 2025
3f64db1
test more metrics
zeitlinger Jan 30, 2025
bcd43e6
format
zeitlinger Jan 30, 2025
215d857
use newer graalvm
zeitlinger Jan 30, 2025
638af92
init at build time
zeitlinger Jan 30, 2025
b51bec3
init at build time
zeitlinger Jan 31, 2025
b7e8dbc
disable jfr for native for now
zeitlinger Feb 3, 2025
fca99a5
fix jfr
zeitlinger Feb 3, 2025
4d9792e
fix buffer pools
zeitlinger Feb 3, 2025
611404a
fix buffer pools
zeitlinger Feb 3, 2025
52e3632
extract metrics providers
zeitlinger Feb 5, 2025
b805da9
exclude JFR from native image at build time
zeitlinger Feb 5, 2025
67fb8d2
format
zeitlinger Feb 5, 2025
c5790fd
Update instrumentation/spring/spring-boot-autoconfigure/src/main/java…
zeitlinger Feb 6, 2025
8cc8c00
format
zeitlinger Feb 6, 2025
a6a87c5
fix java version parsing
zeitlinger Feb 10, 2025
a1bd518
fix java version parsing
zeitlinger Feb 10, 2025
1eea5d3
Update instrumentation/runtime-telemetry/runtime-telemetry-java8/libr…
zeitlinger Feb 10, 2025
59c1425
cleanup
zeitlinger Feb 10, 2025
53a3e6a
cleanup
zeitlinger Feb 10, 2025
6a1e1b2
pr review
zeitlinger Feb 14, 2025
3c10824
pr review
zeitlinger Feb 14, 2025
838af2e
pr review
zeitlinger Feb 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
Args=\
--initialize-at-build-time=io.opentelemetry.instrumentation.api.internal.cache.concurrentlinkedhashmap.ConcurrentLinkedHashMap
--initialize-at-build-time=io.opentelemetry.instrumentation.api.internal.cache.concurrentlinkedhashmap.ConcurrentLinkedHashMap \
--initialize-at-build-time=io.opentelemetry.instrumentation.api.internal.cache.MapBackedCache \
--initialize-at-build-time=io.opentelemetry.api.internal.InternalAttributeKeyImpl
Original file line number Diff line number Diff line change
Expand Up @@ -7,47 +7,25 @@

import com.google.auto.service.AutoService;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.runtimemetrics.java17.RuntimeMetrics;
import io.opentelemetry.instrumentation.runtimemetrics.java17.RuntimeMetricsBuilder;
import io.opentelemetry.instrumentation.runtimemetrics.java17.internal.RuntimeMetricsConfigUtil;
import io.opentelemetry.javaagent.bootstrap.internal.AgentInstrumentationConfig;
import io.opentelemetry.javaagent.extension.AgentListener;
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;

/** An {@link AgentListener} that enables runtime metrics during agent startup. */
@AutoService(AgentListener.class)
public class Java17RuntimeMetricsInstaller implements AgentListener {

@Override
public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredSdk) {
ConfigProperties config = AgentListener.resolveConfigProperties(autoConfiguredSdk);

OpenTelemetry openTelemetry = GlobalOpenTelemetry.get();
RuntimeMetricsBuilder builder = null;
/*
By default don't use any JFR metrics. May change this once semantic conventions are updated.
If enabled, default to only the metrics not already covered by runtime-telemetry-java8
*/
boolean defaultEnabled = config.getBoolean("otel.instrumentation.common.default-enabled", true);
if (config.getBoolean("otel.instrumentation.runtime-telemetry-java17.enable-all", false)) {
builder = RuntimeMetrics.builder(openTelemetry).enableAllFeatures();
} else if (config.getBoolean("otel.instrumentation.runtime-telemetry-java17.enabled", false)) {
builder = RuntimeMetrics.builder(openTelemetry);
} else if (config.getBoolean(
"otel.instrumentation.runtime-telemetry.enabled", defaultEnabled)) {
// This only uses metrics gathered by JMX
builder = RuntimeMetrics.builder(openTelemetry).disableAllFeatures();
}

if (builder != null) {
if (config.getBoolean(
"otel.instrumentation.runtime-telemetry.emit-experimental-telemetry", false)) {
builder.enableExperimentalJmxTelemetry();
}

RuntimeMetrics finalJfrTelemetry = builder.build();
Thread cleanupTelemetry = new Thread(() -> finalJfrTelemetry.close());
Runtime.getRuntime().addShutdownHook(cleanupTelemetry);
RuntimeMetrics runtimeMetrics =
RuntimeMetricsConfigUtil.configure(
RuntimeMetrics.builder(GlobalOpenTelemetry.get()), AgentInstrumentationConfig.get());
if (runtimeMetrics != null) {
Runtime.getRuntime()
.addShutdownHook(
new Thread(runtimeMetrics::close, "OpenTelemetry RuntimeMetricsShutdownHook"));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import jdk.jfr.consumer.RecordingStream;

/** The entry point class for runtime metrics support using JFR and JMX. */
public final class RuntimeMetrics implements Closeable {
public final class RuntimeMetrics implements AutoCloseable {

private static final Logger logger = Logger.getLogger(RuntimeMetrics.class.getName());

Expand Down Expand Up @@ -101,7 +101,7 @@ private JfrRuntimeMetrics(OpenTelemetry openTelemetry, Predicate<JfrFeature> fea
recordingStream.onEvent(handler.getEventName(), handler);
});
recordingStream.onMetadata(event -> startUpLatch.countDown());
Thread daemonRunner = new Thread(() -> recordingStream.start());
Thread daemonRunner = new Thread(recordingStream::start, "OpenTelemetry JFR-Metrics-Runner");
daemonRunner.setDaemon(true);
daemonRunner.start();
}
Expand Down Expand Up @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,8 @@

import com.google.errorprone.annotations.CanIgnoreReturnValue;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.runtimemetrics.java8.Classes;
import io.opentelemetry.instrumentation.runtimemetrics.java8.Cpu;
import io.opentelemetry.instrumentation.runtimemetrics.java8.GarbageCollector;
import io.opentelemetry.instrumentation.runtimemetrics.java8.MemoryPools;
import io.opentelemetry.instrumentation.runtimemetrics.java8.Threads;
import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.ExperimentalBufferPools;
import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.ExperimentalCpu;
import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.ExperimentalMemoryPools;
import java.util.ArrayList;
import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.JmxRuntimeMetricsFactory;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import javax.annotation.Nullable;
Expand Down Expand Up @@ -83,7 +74,7 @@ public RuntimeMetricsBuilder disableAllJmx() {
return this;
}

/** Disable telemetry collection associated with the {@link JfrFeature}. */
/** Enable experimental JMX telemetry collection. */
@CanIgnoreReturnValue
public RuntimeMetricsBuilder enableExperimentalJmxTelemetry() {
enableExperimentalJmxTelemetry = true;
Expand All @@ -92,35 +83,15 @@ public RuntimeMetricsBuilder enableExperimentalJmxTelemetry() {

/** Build and start an {@link RuntimeMetrics} with the config from this builder. */
public RuntimeMetrics build() {
List<AutoCloseable> observables = buildObservables();
List<AutoCloseable> observables =
disableJmx
? List.of()
: JmxRuntimeMetricsFactory.buildObservables(
openTelemetry, enableExperimentalJmxTelemetry);
RuntimeMetrics.JfrRuntimeMetrics jfrRuntimeMetrics = buildJfrMetrics();
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
private RuntimeMetrics.JfrRuntimeMetrics buildJfrMetrics() {
if (enabledFeatureMap.values().stream().noneMatch(isEnabled -> isEnabled)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,52 +7,25 @@

import com.google.auto.service.AutoService;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.runtimemetrics.java8.Classes;
import io.opentelemetry.instrumentation.runtimemetrics.java8.Cpu;
import io.opentelemetry.instrumentation.runtimemetrics.java8.GarbageCollector;
import io.opentelemetry.instrumentation.runtimemetrics.java8.MemoryPools;
import io.opentelemetry.instrumentation.runtimemetrics.java8.Threads;
import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.ExperimentalBufferPools;
import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.ExperimentalCpu;
import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.ExperimentalMemoryPools;
import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.JmxRuntimeMetricsUtil;
import io.opentelemetry.instrumentation.runtimemetrics.java8.RuntimeMetrics;
import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.RuntimeMetricsConfigUtil;
import io.opentelemetry.javaagent.bootstrap.internal.AgentInstrumentationConfig;
import io.opentelemetry.javaagent.extension.AgentListener;
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import java.util.ArrayList;
import java.util.List;

/** An {@link AgentListener} that enables runtime metrics during agent startup. */
@AutoService(AgentListener.class)
public class Java8RuntimeMetricsInstaller implements AgentListener {

@Override
public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredSdk) {
ConfigProperties config = AgentListener.resolveConfigProperties(autoConfiguredSdk);

boolean defaultEnabled = config.getBoolean("otel.instrumentation.common.default-enabled", true);
if (!config.getBoolean("otel.instrumentation.runtime-telemetry.enabled", defaultEnabled)
|| Double.parseDouble(System.getProperty("java.specification.version")) >= 17) {
return;
RuntimeMetrics runtimeMetrics =
RuntimeMetricsConfigUtil.configure(
RuntimeMetrics.builder(GlobalOpenTelemetry.get()), AgentInstrumentationConfig.get());
if (runtimeMetrics != null) {
Runtime.getRuntime()
.addShutdownHook(
new Thread(runtimeMetrics::close, "OpenTelemetry RuntimeMetricsShutdownHook"));
}

OpenTelemetry openTelemetry = GlobalOpenTelemetry.get();
List<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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public final class Threads {

/** Register observers for java runtime class metrics. */
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) {
Expand Down Expand Up @@ -116,6 +116,13 @@ private static boolean isJava9OrNewer() {
return THREAD_INFO_IS_DAEMON != null;
}

private static boolean useThreads() {
// GraalVM native image does not support ThreadMXBean yet
// see https://github.com/oracle/graal/issues/6101
boolean isNativeExecution = System.getProperty("org.graalvm.nativeimage.imagecode") != null;
return !isJava9OrNewer() || isNativeExecution;
}

private static Consumer<ObservableLongMeasurement> java8Callback(ThreadMXBean threadBean) {
return measurement -> {
int daemonThreadCount = threadBean.getDaemonThreadCount();
Expand Down
Loading
Loading