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 naive reduced multi config #13236

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
f93d7bb
spring boot runtime metrics for java 17+
zeitlinger Jan 20, 2025
fb54edc
spring boot runtime metrics for java 8
zeitlinger Jan 21, 2025
34d761e
spring boot runtime metrics for java 8
zeitlinger Jan 21, 2025
71643b2
fix native
zeitlinger Jan 22, 2025
526f6c4
use spring shutdown hook
zeitlinger Jan 22, 2025
4b9a290
disable threads
zeitlinger Jan 28, 2025
e64008a
disable threads
zeitlinger Jan 28, 2025
97d9910
test more metric types
zeitlinger Jan 28, 2025
b40bbfd
add metadata
zeitlinger Jan 28, 2025
34ca187
fix jfr and native image
zeitlinger Jan 29, 2025
1610fef
fix jfr and native image
zeitlinger Jan 29, 2025
45897c1
review
zeitlinger Jan 30, 2025
ff4f83b
test more metrics
zeitlinger Jan 30, 2025
7a9f89d
improve assertions by showing the last result on a timeout
zeitlinger Jan 28, 2025
04bfdb1
test more metrics
zeitlinger Jan 30, 2025
2da5ece
format
zeitlinger Jan 30, 2025
1033068
use newer graalvm
zeitlinger Jan 30, 2025
3101320
Revert "improve assertions by showing the last result on a timeout"
zeitlinger Jan 30, 2025
beb4d9a
init at build time
zeitlinger Jan 30, 2025
0785e1f
init at build time
zeitlinger Jan 31, 2025
6850c88
disable jfr for native for now
zeitlinger Feb 3, 2025
b2e74cb
fix jfr
zeitlinger Feb 3, 2025
530ee61
fix buffer pools
zeitlinger Feb 3, 2025
7c68b99
fix buffer pools
zeitlinger Feb 3, 2025
4e4e550
extract metrics providers
zeitlinger Feb 5, 2025
7bd27d7
exclude JFR from native image at build time
zeitlinger Feb 5, 2025
f6cb08e
format
zeitlinger Feb 5, 2025
a348fed
Update instrumentation/spring/spring-boot-autoconfigure/src/main/java…
zeitlinger Feb 6, 2025
fcdc4df
format
zeitlinger Feb 6, 2025
8823ffc
try out using multiple configurations
zeitlinger Feb 6, 2025
09105bf
use spring conditions
zeitlinger Feb 6, 2025
355b253
use spring conditions
zeitlinger Feb 6, 2025
2f9edc8
use spring conditions
zeitlinger Feb 6, 2025
ca201d4
Update instrumentation/spring/spring-boot-autoconfigure/src/main/java…
zeitlinger Feb 6, 2025
77e61d7
format
zeitlinger Feb 6, 2025
b85a04e
use spring conditions
zeitlinger Feb 7, 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,18 @@

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.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.builder(GlobalOpenTelemetry.get())
.startFromInstrumentationConfig(AgentInstrumentationConfig.get());
}
}
Original file line number Diff line number Diff line change
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,19 +7,12 @@

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.api.incubator.config.internal.InstrumentationConfig;
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 java.util.function.Consumer;
import javax.annotation.Nullable;

/** Builder for {@link RuntimeMetrics}. */
Expand All @@ -31,6 +24,11 @@ public final class RuntimeMetricsBuilder {

private boolean disableJmx = false;
private boolean enableExperimentalJmxTelemetry = false;
private Consumer<Runnable> shutdownHook =
runnable -> {
Runtime.getRuntime()
.addShutdownHook(new Thread(runnable, "OpenTelemetry RuntimeMetricsShutdownHook"));
};

RuntimeMetricsBuilder(OpenTelemetry openTelemetry) {
this.openTelemetry = openTelemetry;
Expand Down Expand Up @@ -83,42 +81,57 @@ public RuntimeMetricsBuilder disableAllJmx() {
return this;
}

/** Disable telemetry collection associated with the {@link JfrFeature}. */
/** Enable experimental 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 = buildObservables();
RuntimeMetrics.JfrRuntimeMetrics jfrRuntimeMetrics = buildJfrMetrics();
return new RuntimeMetrics(openTelemetry, observables, jfrRuntimeMetrics);
/** Set a custom shutdown hook for the {@link RuntimeMetrics}. */
@CanIgnoreReturnValue
public RuntimeMetricsBuilder setShutdownHook(Consumer<Runnable> shutdownHook) {
this.shutdownHook = shutdownHook;
return this;
}

@SuppressWarnings("CatchingUnchecked")
private List<AutoCloseable> buildObservables() {
if (disableJmx) {
return Collections.emptyList();
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;
}
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);

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

RuntimeMetrics runtimeMetrics = this.build();
shutdownHook.accept(runtimeMetrics::close);
}

/** Build and start an {@link RuntimeMetrics} with the config from this builder. */
public RuntimeMetrics build() {
List<AutoCloseable> observables =
disableJmx
? List.of()
: JmxRuntimeMetricsFactory.buildObservables(
openTelemetry, enableExperimentalJmxTelemetry);
RuntimeMetrics.JfrRuntimeMetrics jfrRuntimeMetrics = buildJfrMetrics();
return new RuntimeMetrics(openTelemetry, observables, jfrRuntimeMetrics);
}

@Nullable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,52 +7,18 @@

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)
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<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);
RuntimeMetrics.builder(GlobalOpenTelemetry.get())
.startFromInstrumentationConfig(AgentInstrumentationConfig.get());
}
}
Original file line number Diff line number Diff line change
@@ -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 JMX. */
public final class RuntimeMetrics implements Closeable {
Copy link
Member

Choose a reason for hiding this comment

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

(minor)

Having two RuntimeMetrics classes (Java 8 and Java 17) has confused me.

Perhaps renamed this one into Java8RuntimeMetrics?

Same comment for RuntimeMetricsBuilder.

Copy link
Member Author

@zeitlinger zeitlinger Feb 6, 2025

Choose a reason for hiding this comment

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

these classes were not introduced by this PR - let's do it separately

Copy link
Member

Choose a reason for hiding this comment

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

This PR adds RuntimeMetrics and RuntimeMetricsBuilder classes for Java 17. So, these class names exist both for the Java 8 and Java 17 runtime metrics implementations.

Copy link
Member Author

@zeitlinger zeitlinger Feb 7, 2025

Choose a reason for hiding this comment

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

ok - renamed (in #13173)


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,67 @@
/*
* 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;
import java.util.function.Consumer;

/** Builder for {@link RuntimeMetrics}. */
public final class RuntimeMetricsBuilder {

private final OpenTelemetry openTelemetry;

private boolean enableExperimentalJmxTelemetry = false;
private Consumer<Runnable> shutdownHook =
runnable -> {
Runtime.getRuntime()
.addShutdownHook(new Thread(runnable, "OpenTelemetry RuntimeMetricsShutdownHook"));
};

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);
}

/** Set a custom shutdown hook for the {@link RuntimeMetrics}. */
@CanIgnoreReturnValue
public RuntimeMetricsBuilder setShutdownHook(Consumer<Runnable> shutdownHook) {
this.shutdownHook = shutdownHook;
return this;
}

public void startFromInstrumentationConfig(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;
}

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

RuntimeMetrics runtimeMetrics = this.build();
shutdownHook.accept(runtimeMetrics::close);
}
}
Loading
Loading