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 #13078

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 1 addition & 3 deletions .github/workflows/reusable-native-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
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 {

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