Skip to content

Commit

Permalink
spring boot runtime metrics for java 8
Browse files Browse the repository at this point in the history
  • Loading branch information
zeitlinger committed Jan 21, 2025
1 parent 4b196cc commit 5099e45
Show file tree
Hide file tree
Showing 10 changed files with 198 additions and 94 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

import com.google.auto.service.AutoService;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.runtimemetrics.java17.RuntimeMetrics;
import io.opentelemetry.javaagent.bootstrap.internal.AgentInstrumentationConfig;
import io.opentelemetry.javaagent.extension.AgentListener;
Expand All @@ -19,9 +18,7 @@ public class Java17RuntimeMetricsInstaller implements AgentListener {

@Override
public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredSdk) {
OpenTelemetry openTelemetry = GlobalOpenTelemetry.get();

RuntimeMetrics.builder(openTelemetry)
RuntimeMetrics.builder(GlobalOpenTelemetry.get())
.startFromInstrumentationConfig(AgentInstrumentationConfig.get());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,8 @@
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig;
import io.opentelemetry.instrumentation.runtimemetrics.java8.Classes;
import io.opentelemetry.instrumentation.runtimemetrics.java8.Cpu;
import io.opentelemetry.instrumentation.runtimemetrics.java8.GarbageCollector;
import io.opentelemetry.instrumentation.runtimemetrics.java8.MemoryPools;
import io.opentelemetry.instrumentation.runtimemetrics.java8.Threads;
import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.ExperimentalBufferPools;
import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.ExperimentalCpu;
import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.ExperimentalMemoryPools;
import java.util.ArrayList;
import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.JmxRuntimeMetricsFactory;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import javax.annotation.Nullable;
Expand Down Expand Up @@ -84,7 +75,7 @@ public RuntimeMetricsBuilder disableAllJmx() {
return this;
}

/** Disable telemetry collection associated with the {@link JfrFeature}. */
/** Enable experimental JMX telemetry collection. */
@CanIgnoreReturnValue
public RuntimeMetricsBuilder enableExperimentalJmxTelemetry() {
enableExperimentalJmxTelemetry = true;
Expand Down Expand Up @@ -122,35 +113,13 @@ public void startFromInstrumentationConfig(InstrumentationConfig config) {

/** Build and start an {@link RuntimeMetrics} with the config from this builder. */
public RuntimeMetrics build() {
List<AutoCloseable> observables = buildObservables();
List<AutoCloseable> observables =
JmxRuntimeMetricsFactory.buildObservables(
openTelemetry, disableJmx, 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
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 JFR and JMX. */
public final class RuntimeMetrics implements Closeable {

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

private final AtomicBoolean isClosed = new AtomicBoolean();
private final List<AutoCloseable> observables;

RuntimeMetrics(List<AutoCloseable> observables) {
this.observables = Collections.unmodifiableList(observables);
}

/**
* Create and start {@link RuntimeMetrics}.
*
* <p>Listens for select JFR events, extracts data, and records to various metrics. Recording will
* continue until {@link #close()} is called.
*
* @param openTelemetry the {@link OpenTelemetry} instance used to record telemetry
*/
public static RuntimeMetrics create(OpenTelemetry openTelemetry) {
return new RuntimeMetricsBuilder(openTelemetry).build();
}

/**
* Create a builder for configuring {@link RuntimeMetrics}.
*
* @param openTelemetry the {@link OpenTelemetry} instance used to record telemetry
*/
public static RuntimeMetricsBuilder builder(OpenTelemetry openTelemetry) {
return new RuntimeMetricsBuilder(openTelemetry);
}

/** Stop recording JFR events. */
@Override
public void close() {
if (!isClosed.compareAndSet(false, true)) {
logger.log(Level.WARNING, "RuntimeMetrics is already closed");
return;
}

JmxRuntimeMetricsUtil.closeObservers(observables);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.runtimemetrics.java8;

import com.google.errorprone.annotations.CanIgnoreReturnValue;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig;
import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.JmxRuntimeMetricsFactory;
import java.util.List;

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

private final OpenTelemetry openTelemetry;

private boolean disableJmx = false;
private boolean enableExperimentalJmxTelemetry = false;

RuntimeMetricsBuilder(OpenTelemetry openTelemetry) {
this.openTelemetry = openTelemetry;
}

/** Enable all JMX telemetry collection. */
@CanIgnoreReturnValue
public RuntimeMetricsBuilder enableExperimentalJmxTelemetry() {
enableExperimentalJmxTelemetry = true;
return this;
}

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

public void startFromInstrumentationConfig(InstrumentationConfig config) {
/*
By default, don't use any JFR metrics. May change this once semantic conventions are updated.
If enabled, default to only the metrics not already covered by runtime-telemetry-java8
*/
boolean defaultEnabled = config.getBoolean("otel.instrumentation.common.default-enabled", true);
if (!config.getBoolean("otel.instrumentation.runtime-telemetry.enabled", defaultEnabled)) {
// nothing is enabled
return;
}

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

RuntimeMetrics finalJfrTelemetry = this.build();
Thread cleanupTelemetry = new Thread(finalJfrTelemetry::close);
Runtime.getRuntime().addShutdownHook(cleanupTelemetry);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.runtimemetrics.java8.internal;

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.runtimemetrics.java8.Classes;
import io.opentelemetry.instrumentation.runtimemetrics.java8.Cpu;
import io.opentelemetry.instrumentation.runtimemetrics.java8.GarbageCollector;
import io.opentelemetry.instrumentation.runtimemetrics.java8.MemoryPools;
import io.opentelemetry.instrumentation.runtimemetrics.java8.Threads;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
* any time.
*/
public class JmxRuntimeMetricsFactory {
@SuppressWarnings("CatchingUnchecked")
public static List<AutoCloseable> buildObservables(
OpenTelemetry openTelemetry, boolean disableJmx, boolean enableExperimentalJmxTelemetry) {
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);
}
}

private JmxRuntimeMetricsFactory() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ dependencies {
implementation(project(":instrumentation:logback:logback-mdc-1.0:library"))
compileOnly("ch.qos.logback:logback-classic:1.0.0")
implementation(project(":instrumentation:jdbc:library"))
implementation(project(":instrumentation:runtime-telemetry:runtime-telemetry-java8:library"))
implementation(project(":instrumentation:runtime-telemetry:runtime-telemetry-java17:library"))

library("org.springframework.kafka:spring-kafka:2.9.0")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.runtimemetrics;

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.runtimemetrics.java17.RuntimeMetrics;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.ConfigPropertiesBridge;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
Expand All @@ -23,26 +22,28 @@
* <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-java17")
@ConditionalOnEnabledInstrumentation(module = "runtime-telemetry")
@Configuration
public class Java17RuntimeMetricsAutoConfiguration {
public class RuntimeMetricsAutoConfiguration {

private static final Logger logger =
LoggerFactory.getLogger(Java17RuntimeMetricsAutoConfiguration.class);
LoggerFactory.getLogger(RuntimeMetricsAutoConfiguration.class);

@EventListener
public void handleApplicationReadyEvent(ApplicationReadyEvent event) {
if (Double.parseDouble(System.getProperty("java.specification.version")) < 17) {
logger.debug(
"Java 17 runtime metrics instrumentation enabled but running on Java version < 17");
return;
}
logger.debug(
"Java 17 runtime metrics instrumentation enabled and running on Java version >= 17");
ConfigurableApplicationContext applicationContext = event.getApplicationContext();
OpenTelemetry openTelemetry = applicationContext.getBean(OpenTelemetry.class);
ConfigProperties configProperties = applicationContext.getBean(ConfigProperties.class);
RuntimeMetrics.builder(openTelemetry)
.startFromInstrumentationConfig(new ConfigPropertiesBridge(configProperties));
ConfigPropertiesBridge config =
new ConfigPropertiesBridge(applicationContext.getBean(ConfigProperties.class));

if (Double.parseDouble(System.getProperty("java.specification.version")) >= 17) {
logger.debug("Use runtime metrics instrumentation for Java 17+");
io.opentelemetry.instrumentation.runtimemetrics.java17.RuntimeMetrics.builder(openTelemetry)
.startFromInstrumentationConfig(config);
} else {
logger.debug("Use runtime metrics instrumentation for Java 8");
io.opentelemetry.instrumentation.runtimemetrics.java8.RuntimeMetrics.builder(openTelemetry)
.startFromInstrumentationConfig(config);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.w
io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.webflux.SpringWebfluxInstrumentationAutoConfiguration,\
io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.webmvc.SpringWebMvc5InstrumentationAutoConfiguration,\
io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.scheduling.SpringSchedulingInstrumentationAutoConfiguration,\
io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.runtimemetrics.Java17RuntimeMetricsAutoConfiguration
io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.runtimemetrics.RuntimeMetricsAutoConfiguration

org.springframework.context.ApplicationListener=\
io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.logging.LogbackAppenderApplicationListener
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.w
io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.web.RestClientInstrumentationAutoConfiguration
io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.webmvc.SpringWebMvc6InstrumentationAutoConfiguration
io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.scheduling.SpringSchedulingInstrumentationAutoConfiguration
io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.runtimemetrics.Java17RuntimeMetricsAutoConfiguration
io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.runtimemetrics.RuntimeMetricsAutoConfiguration

0 comments on commit 5099e45

Please sign in to comment.