Unify runtime-telemetry modules#16087
Conversation
f45a4f0 to
bd6ea19
Compare
9ea8fa0 to
951da56
Compare
951da56 to
74b4b47
Compare
| private static final String METRIC_NAME = "jvm.thread.count"; | ||
| private static final String EVENT_NAME = "jdk.JavaThreadStatistics"; | ||
| private static final String METRIC_DESCRIPTION = "Number of executing threads"; | ||
| private static final String METRIC_DESCRIPTION = "Number of executing platform threads."; |
There was a problem hiding this comment.
updating metric description is not a breaking change
| assertThat( | ||
| path.matches( | ||
| "opentelemetry-javaagent-runtime-telemetry-java8-[0-9a-zA-Z-\\.]+\\.jar")) | ||
| "opentelemetry-javaagent-runtime-telemetry-[0-9a-zA-Z-\\.]+\\.jar")) |
There was a problem hiding this comment.
this is just a path (not an instrumentation name)
| .loggerBuilder(instrumentationName) | ||
| .setInstrumentationVersion(instrumentationVersion) | ||
| .build(); | ||
| Worker worker = new Worker(logger, toProcess, jarsPerSecond); |
There was a problem hiding this comment.
unrelated improvement: Using Logger instead LogRecordBuilder (since it's used to emit multiple logs)
There was a problem hiding this comment.
Copied from runtime-telemetry-java8/library/.../JmxRuntimeMetricsFactory.java
diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/internal/JmxRuntimeMetricsFactory.java b/instrumentation/runtime-telemetry/library/src/main/java/io/opentelemetry/instrumentation/runtimetelemetry/internal/JmxRuntimeMetricsFactory.java
index de9464b4f8..10f16c4cee 100644
--- a/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/internal/JmxRuntimeMetricsFactory.java
+++ b/instrumentation/runtime-telemetry/library/src/main/java/io/opentelemetry/instrumentation/runtimetelemetry/internal/JmxRuntimeMetricsFactory.java
@@ -3,9 +3,9 @@
* SPDX-License-Identifier: Apache-2.0
*/
-package io.opentelemetry.instrumentation.runtimemetrics.java8.internal;
+package io.opentelemetry.instrumentation.runtimetelemetry.internal;
-import io.opentelemetry.api.OpenTelemetry;
+import io.opentelemetry.api.metrics.Meter;
import java.util.ArrayList;
import java.util.List;
@@ -14,21 +14,30 @@ import java.util.List;
* any time.
*/
public class JmxRuntimeMetricsFactory {
- @SuppressWarnings({"CatchingUnchecked", "deprecation"}) // ExperimentalXxx classes are deprecated
+ @SuppressWarnings("deprecation") // ExperimentalXxx classes are deprecated
public static List<AutoCloseable> buildObservables(
- OpenTelemetry openTelemetry, boolean emitExperimentalTelemetry, boolean captureGcCause) {
+ boolean emitExperimentalTelemetry,
+ boolean captureGcCause,
+ boolean preferJfrMetrics,
+ Meter meter) {
// Set up metrics gathered by JMX
+ // When preferJfrMetrics is true, skip JMX metrics that have JFR equivalents
List<AutoCloseable> observables = new ArrayList<>();
- observables.addAll(Classes.registerObservers(openTelemetry));
- observables.addAll(Cpu.registerObservers(openTelemetry));
- observables.addAll(GarbageCollector.registerObservers(openTelemetry, captureGcCause));
- observables.addAll(MemoryPools.registerObservers(openTelemetry));
- observables.addAll(Threads.registerObservers(openTelemetry));
+ if (!preferJfrMetrics) {
+ observables.addAll(Classes.registerObservers(meter));
+ observables.addAll(Cpu.registerObservers(meter));
+ observables.addAll(GarbageCollector.registerObservers(meter, captureGcCause));
+ observables.addAll(MemoryPools.registerObservers(meter));
+ observables.addAll(Threads.registerObservers(meter));
+ }
if (emitExperimentalTelemetry) {
- observables.addAll(ExperimentalBufferPools.registerObservers(openTelemetry));
- observables.addAll(ExperimentalCpu.registerObservers(openTelemetry));
- observables.addAll(ExperimentalMemoryPools.registerObservers(openTelemetry));
- observables.addAll(ExperimentalFileDescriptor.registerObservers(openTelemetry));
+ if (!preferJfrMetrics) {
+ observables.addAll(ExperimentalBufferPools.registerObservers(meter));
+ observables.addAll(ExperimentalCpu.registerObservers(meter));
+ observables.addAll(ExperimentalMemoryPools.registerObservers(meter));
+ }
+ // ExperimentalFileDescriptor has no JFR equivalent, always register
+ observables.addAll(ExperimentalFileDescriptor.registerObservers(meter));
}
return observables;
}There was a problem hiding this comment.
Copied from runtime-telemetry-java8/library/.../RuntimeMetrics.java
diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/RuntimeMetrics.java b/instrumentation/runtime-telemetry/library/src/main/java/io/opentelemetry/instrumentation/runtimetelemetry/RuntimeTelemetry.java
index 62e711ebd9..2cd832b8f4 100644
--- a/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/RuntimeMetrics.java
+++ b/instrumentation/runtime-telemetry/library/src/main/java/io/opentelemetry/instrumentation/runtimetelemetry/RuntimeTelemetry.java
@@ -3,57 +3,79 @@
* SPDX-License-Identifier: Apache-2.0
*/
-package io.opentelemetry.instrumentation.runtimemetrics.java8;
+package io.opentelemetry.instrumentation.runtimetelemetry;
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;
+import javax.annotation.Nullable;
-/** The entry point class for runtime metrics support using JMX. */
-public final class RuntimeMetrics implements AutoCloseable {
+/** The entry point class for runtime telemetry support using JMX (Java 8+) and JFR (Java 17+). */
+public final class RuntimeTelemetry implements AutoCloseable {
- private static final Logger logger = Logger.getLogger(RuntimeMetrics.class.getName());
+ private static final Logger logger = Logger.getLogger(RuntimeTelemetry.class.getName());
private final AtomicBoolean isClosed = new AtomicBoolean();
private final List<AutoCloseable> observables;
+ @Nullable private final AutoCloseable jfrTelemetry;
- RuntimeMetrics(List<AutoCloseable> observables) {
+ RuntimeTelemetry(List<AutoCloseable> observables, @Nullable AutoCloseable jfrTelemetry) {
this.observables = Collections.unmodifiableList(observables);
+ this.jfrTelemetry = jfrTelemetry;
}
/**
- * Create and start {@link RuntimeMetrics}.
+ * Create and start {@link RuntimeTelemetry}.
*
- * <p>Listens for select JMX beans, extracts data, and records to various metrics. Recording will
- * continue until {@link #close()} is called.
+ * <p>Listens for select JMX beans (and JFR events on Java 17+), 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();
+ public static RuntimeTelemetry create(OpenTelemetry openTelemetry) {
+ return new RuntimeTelemetryBuilder(openTelemetry).build();
}
/**
- * Create a builder for configuring {@link RuntimeMetrics}.
+ * Create a builder for configuring {@link RuntimeTelemetry}.
*
* @param openTelemetry the {@link OpenTelemetry} instance used to record telemetry
*/
- public static RuntimeMetricsBuilder builder(OpenTelemetry openTelemetry) {
- return new RuntimeMetricsBuilder(openTelemetry);
+ public static RuntimeTelemetryBuilder builder(OpenTelemetry openTelemetry) {
+ return new RuntimeTelemetryBuilder(openTelemetry);
}
- /** Stop recording JMX metrics. */
+ // Only used by tests
+ @Nullable
+ AutoCloseable getJfrTelemetry() {
+ return jfrTelemetry;
+ }
+
+ /** Stop recording metrics. */
@Override
public void close() {
if (!isClosed.compareAndSet(false, true)) {
- logger.log(Level.WARNING, "RuntimeMetrics is already closed");
+ logger.log(Level.WARNING, "RuntimeTelemetry is already closed");
return;
}
- JmxRuntimeMetricsUtil.closeObservers(observables);
+ if (jfrTelemetry != null) {
+ try {
+ jfrTelemetry.close();
+ } catch (Exception e) {
+ logger.log(Level.WARNING, "Error closing JFR telemetry", e);
+ }
+ }
+
+ for (AutoCloseable observable : observables) {
+ try {
+ observable.close();
+ } catch (Exception e) {
+ // Ignore
+ }
+ }
}
}There was a problem hiding this comment.
Copied from runtime-telemetry-java17/library/.../JfrFeature.java
diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java17/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java17/JfrFeature.java b/instrumentation/runtime-telemetry/library/src/main/java17/io/opentelemetry/instrumentation/runtimetelemetry/internal/JfrFeature.java
index aacaaf8fa6..598462e9bc 100644
--- a/instrumentation/runtime-telemetry/runtime-telemetry-java17/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java17/JfrFeature.java
+++ b/instrumentation/runtime-telemetry/library/src/main/java17/io/opentelemetry/instrumentation/runtimetelemetry/internal/JfrFeature.java
@@ -3,36 +3,47 @@
* SPDX-License-Identifier: Apache-2.0
*/
-package io.opentelemetry.instrumentation.runtimemetrics.java17;
+package io.opentelemetry.instrumentation.runtimetelemetry.internal;
/**
- * Enumeration of JFR features, which can be toggled on or off via {@link RuntimeMetricsBuilder}.
+ * Enumeration of JFR features, used internally to control which JFR events are registered.
*
- * <p>Features are disabled by default if they are already available through {@code
- * io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java8} JMX based
- * instrumentation.
+ * <p>Features that overlap with stable JMX-based instrumentation are disabled by default to avoid
+ * duplicate metrics. Experimental features (those not marked stable in the semantic conventions)
+ * are also disabled by default and require {@code emit_experimental_metrics=true} to enable.
+ *
+ * <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
+ * at any time.
*/
public enum JfrFeature {
- BUFFER_METRICS(/* defaultEnabled= */ false),
- CLASS_LOAD_METRICS(/* defaultEnabled= */ false),
- CONTEXT_SWITCH_METRICS(/* defaultEnabled= */ true),
- CPU_COUNT_METRICS(/* defaultEnabled= */ true),
- CPU_UTILIZATION_METRICS(/* defaultEnabled= */ false),
- GC_DURATION_METRICS(/* defaultEnabled= */ false),
- LOCK_METRICS(/* defaultEnabled= */ true),
- MEMORY_ALLOCATION_METRICS(/* defaultEnabled= */ true),
- MEMORY_POOL_METRICS(/* defaultEnabled= */ false),
- NETWORK_IO_METRICS(/* defaultEnabled= */ true),
- THREAD_METRICS(/* defaultEnabled= */ false),
+ BUFFER_METRICS(/* overlapsWithJmx= */ true, /* experimental= */ true),
+ CLASS_LOAD_METRICS(/* overlapsWithJmx= */ true, /* experimental= */ false),
+ CONTEXT_SWITCH_METRICS(/* overlapsWithJmx= */ false, /* experimental= */ true),
+ CPU_COUNT_METRICS(/* overlapsWithJmx= */ true, /* experimental= */ false),
+ CPU_UTILIZATION_METRICS(/* overlapsWithJmx= */ true, /* experimental= */ false),
+ GC_DURATION_METRICS(/* overlapsWithJmx= */ true, /* experimental= */ false),
+ LOCK_METRICS(/* overlapsWithJmx= */ false, /* experimental= */ true),
+ MEMORY_ALLOCATION_METRICS(/* overlapsWithJmx= */ false, /* experimental= */ true),
+ MEMORY_POOL_METRICS(/* overlapsWithJmx= */ true, /* experimental= */ false),
+ NETWORK_IO_METRICS(/* overlapsWithJmx= */ false, /* experimental= */ true),
+ THREAD_METRICS(/* overlapsWithJmx= */ true, /* experimental= */ false),
;
- private final boolean defaultEnabled;
+ private final boolean overlapsWithJmx;
+ private final boolean experimental;
+
+ JfrFeature(boolean overlapsWithJmx, boolean experimental) {
+ this.overlapsWithJmx = overlapsWithJmx;
+ this.experimental = experimental;
+ }
- JfrFeature(boolean defaultEnabled) {
- this.defaultEnabled = defaultEnabled;
+ /** Returns true if this JFR feature overlaps with JMX-based metrics. */
+ public boolean overlapsWithJmx() {
+ return overlapsWithJmx;
}
- boolean isDefaultEnabled() {
- return defaultEnabled;
+ /** Returns true if this JFR feature produces experimental (non-stable) metrics. */
+ public boolean isExperimental() {
+ return experimental;
}
}There was a problem hiding this comment.
Exact copy from runtime-telemetry-java8/library/.../reflect-config.json
git diff main:instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/resources/META-INF/native-image/reflect-config.json HEAD:instrumentation/runtime-telemetry/library/src/main/resources/META-INF/native-image/reflect-config.jsonThere was a problem hiding this comment.
Copied from runtime-telemetry-java17/library.../RuntimeMetricsBuilderTest.java
diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java17/library/src/test/java/io/opentelemetry/instrumentation/runtimemetrics/java17/RuntimeMetricsBuilderTest.java b/instrumentation/runtime-telemetry/library/src/testJava17/java/io/opentelemetry/instrumentation/runtimetelemetry/RuntimeTelemetryBuilderTest.java
index 78f88af3f4..be16d5f587 100644
--- a/instrumentation/runtime-telemetry/runtime-telemetry-java17/library/src/test/java/io/opentelemetry/instrumentation/runtimemetrics/java17/RuntimeMetricsBuilderTest.java
+++ b/instrumentation/runtime-telemetry/library/src/testJava17/java/io/opentelemetry/instrumentation/runtimetelemetry/RuntimeTelemetryBuilderTest.java
@@ -3,11 +3,13 @@
* SPDX-License-Identifier: Apache-2.0
*/
-package io.opentelemetry.instrumentation.runtimemetrics.java17;
+package io.opentelemetry.instrumentation.runtimetelemetry;
import static org.assertj.core.api.Assertions.assertThat;
import io.opentelemetry.api.OpenTelemetry;
+import io.opentelemetry.instrumentation.runtimetelemetry.internal.JfrConfig;
+import io.opentelemetry.instrumentation.runtimetelemetry.internal.JfrFeature;
import java.util.Arrays;
import java.util.HashMap;
import jdk.jfr.FlightRecorder;
@@ -15,7 +17,7 @@ import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
-class RuntimeMetricsBuilderTest {
+class RuntimeTelemetryBuilderTest {
@BeforeAll
static void setup() {
@@ -29,48 +31,68 @@ class RuntimeMetricsBuilderTest {
@Test
void defaultFeatures() {
+ // By default, features that don't overlap with JMX AND are not experimental are enabled
var defaultFeatures = new HashMap<JfrFeature, Boolean>();
Arrays.stream(JfrFeature.values())
- .forEach(jfrFeature -> defaultFeatures.put(jfrFeature, jfrFeature.isDefaultEnabled()));
+ .forEach(
+ jfrFeature ->
+ defaultFeatures.put(
+ jfrFeature, !jfrFeature.overlapsWithJmx() && !jfrFeature.isExperimental()));
- assertThat(new RuntimeMetricsBuilder(OpenTelemetry.noop()).enabledFeatureMap)
- .isEqualTo(defaultFeatures);
+ assertThat(newBuilder().getJfrConfig().enabledFeatureMap).isEqualTo(defaultFeatures);
}
@Test
void enableAllFeatures() {
- assertThat(
- new RuntimeMetricsBuilder(OpenTelemetry.noop()).enableAllFeatures().enabledFeatureMap)
+ assertThat(newBuilder().getJfrConfig().enableAllFeatures().enabledFeatureMap)
.allSatisfy((unused, enabled) -> assertThat(enabled).isTrue());
}
@Test
void disableAllFeatures() {
- assertThat(
- new RuntimeMetricsBuilder(OpenTelemetry.noop()).disableAllFeatures().enabledFeatureMap)
+ assertThat(newBuilder().getJfrConfig().disableAllFeatures().enabledFeatureMap)
.allSatisfy((unused, enabled) -> assertThat(enabled).isFalse());
}
@Test
void enableDisableFeature() {
- var builder = new RuntimeMetricsBuilder(OpenTelemetry.noop());
+ var builder = RuntimeTelemetry.builder(OpenTelemetry.noop());
- assertThat(builder.enabledFeatureMap.get(JfrFeature.BUFFER_METRICS)).isFalse();
+ // BUFFER_METRICS overlaps with JMX and is experimental, so it's disabled by default
+ assertThat(builder.getJfrConfig().enabledFeatureMap.get(JfrFeature.BUFFER_METRICS)).isFalse();
- builder.enableFeature(JfrFeature.BUFFER_METRICS);
- assertThat(builder.enabledFeatureMap.get(JfrFeature.BUFFER_METRICS)).isTrue();
- builder.disableFeature(JfrFeature.BUFFER_METRICS);
- assertThat(builder.enabledFeatureMap.get(JfrFeature.BUFFER_METRICS)).isFalse();
+ builder.getJfrConfig().enableFeature(JfrFeature.BUFFER_METRICS);
+ assertThat(builder.getJfrConfig().enabledFeatureMap.get(JfrFeature.BUFFER_METRICS)).isTrue();
+ builder.getJfrConfig().disableFeature(JfrFeature.BUFFER_METRICS);
+ assertThat(builder.getJfrConfig().enabledFeatureMap.get(JfrFeature.BUFFER_METRICS)).isFalse();
}
@Test
- void build() {
+ void build_DefaultNoJfr() {
+ // By default, no JFR features are enabled because all features either overlap
+ // with JMX or are experimental
var openTelemetry = OpenTelemetry.noop();
- try (var jfrTelemetry = new RuntimeMetricsBuilder(openTelemetry).build()) {
- assertThat(jfrTelemetry.getOpenTelemetry()).isSameAs(openTelemetry);
- assertThat(jfrTelemetry.getJfrRuntimeMetrics().getRecordedEventHandlers())
+ try (var runtimeTelemetry = RuntimeTelemetry.builder(openTelemetry).build()) {
+ assertThat(runtimeTelemetry.getJfrTelemetry()).isNull();
+ }
+ }
+
+ @Test
+ void build_WithFeatureEnabled() {
+ var openTelemetry = OpenTelemetry.noop();
+ var builder = RuntimeTelemetry.builder(openTelemetry);
+ builder.getJfrConfig().enableFeature(JfrFeature.LOCK_METRICS);
+ try (var runtimeTelemetry = builder.build()) {
+ var jfrRuntimeMetrics = (JfrConfig.JfrRuntimeMetrics) runtimeTelemetry.getJfrTelemetry();
+ assertThat(jfrRuntimeMetrics).isNotNull();
+ assertThat(jfrRuntimeMetrics.getRecordedEventHandlers())
.hasSizeGreaterThan(0)
- .allSatisfy(handler -> assertThat(handler.getFeature().isDefaultEnabled()).isTrue());
+ .allSatisfy(
+ handler -> assertThat(handler.getFeature()).isEqualTo(JfrFeature.LOCK_METRICS));
}
}
+
+ private static RuntimeTelemetryBuilder newBuilder() {
+ return RuntimeTelemetry.builder(OpenTelemetry.noop());
+ }
}There was a problem hiding this comment.
Copied from runtime-telemetry-java17/library/.../RuntimeMetricsTest.java
diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java17/library/src/test/java/io/opentelemetry/instrumentation/runtimemetrics/java17/RuntimeMetricsTest.java b/instrumentation/runtime-telemetry/library/src/testJava17/java/io/opentelemetry/instrumentation/runtimetelemetry/RuntimeTelemetryTest.java
index 15b178abf8..142e23bcf1 100644
--- a/instrumentation/runtime-telemetry/runtime-telemetry-java17/library/src/test/java/io/opentelemetry/instrumentation/runtimemetrics/java17/RuntimeMetricsTest.java
+++ b/instrumentation/runtime-telemetry/library/src/testJava17/java/io/opentelemetry/instrumentation/runtimetelemetry/RuntimeTelemetryTest.java
@@ -3,11 +3,13 @@
* SPDX-License-Identifier: Apache-2.0
*/
-package io.opentelemetry.instrumentation.runtimemetrics.java17;
+package io.opentelemetry.instrumentation.runtimetelemetry;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
import io.github.netmikey.logunit.api.LogCapturer;
+import io.opentelemetry.instrumentation.runtimetelemetry.internal.JfrConfig;
+import io.opentelemetry.instrumentation.runtimetelemetry.internal.JfrFeature;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.metrics.SdkMeterProvider;
import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader;
@@ -19,9 +21,9 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
-class RuntimeMetricsTest {
+class RuntimeTelemetryTest {
- @RegisterExtension LogCapturer logs = LogCapturer.create().captureForType(RuntimeMetrics.class);
+ @RegisterExtension LogCapturer logs = LogCapturer.create().captureForType(RuntimeTelemetry.class);
private InMemoryMetricReader reader;
private OpenTelemetrySdk sdk;
@@ -51,48 +53,60 @@ class RuntimeMetricsTest {
@Test
void create_Default() {
- try (RuntimeMetrics unused = RuntimeMetrics.create(sdk)) {
+ try (RuntimeTelemetry unused = RuntimeTelemetry.create(sdk)) {
assertThat(reader.collectAllMetrics())
.isNotEmpty()
.allSatisfy(
metric -> {
assertThat(metric.getInstrumentationScopeInfo().getName())
- .contains("io.opentelemetry.runtime-telemetry-java");
+ .isEqualTo("io.opentelemetry.runtime-telemetry");
});
}
}
@Test
- void create_AllDisabled() {
- try (RuntimeMetrics unused = RuntimeMetrics.builder(sdk).disableAllMetrics().build()) {
- assertThat(reader.collectAllMetrics()).isEmpty();
+ void builder_DefaultNoJfr() {
+ // By default, no JFR features are enabled because all features either overlap
+ // with JMX or are experimental
+ try (var runtimeTelemetry = RuntimeTelemetry.builder(sdk).build()) {
+ assertThat(runtimeTelemetry.getJfrTelemetry()).isNull();
}
}
@Test
- void builder() {
- try (var jfrTelemetry = RuntimeMetrics.builder(sdk).build()) {
- assertThat(jfrTelemetry.getOpenTelemetry()).isSameAs(sdk);
- assertThat(jfrTelemetry.getJfrRuntimeMetrics().getRecordedEventHandlers())
+ void builder_WithFeatureEnabled() {
+ RuntimeTelemetryBuilder builder = RuntimeTelemetry.builder(sdk);
+ builder.getJfrConfig().enableFeature(JfrFeature.LOCK_METRICS);
+ try (var runtimeTelemetry = builder.build()) {
+ JfrConfig.JfrRuntimeMetrics jfrRuntimeMetrics =
+ (JfrConfig.JfrRuntimeMetrics) runtimeTelemetry.getJfrTelemetry();
+ assertThat(jfrRuntimeMetrics).isNotNull();
+ assertThat(jfrRuntimeMetrics.getRecordedEventHandlers())
.hasSizeGreaterThan(0)
- .allSatisfy(handler -> assertThat(handler.getFeature().isDefaultEnabled()).isTrue());
+ .allSatisfy(
+ handler -> {
+ assertThat(handler.getFeature()).isEqualTo(JfrFeature.LOCK_METRICS);
+ });
}
}
@Test
void close() throws InterruptedException {
- try (RuntimeMetrics jfrTelemetry = RuntimeMetrics.builder(sdk).build()) {
+ RuntimeTelemetryBuilder builder = RuntimeTelemetry.builder(sdk);
+ // Enable a feature to test close behavior with JFR
+ builder.getJfrConfig().enableFeature(JfrFeature.LOCK_METRICS);
+ try (RuntimeTelemetry jfrTelemetry = builder.build()) {
+ JfrConfig.JfrRuntimeMetrics jfrRuntimeMetrics =
+ (JfrConfig.JfrRuntimeMetrics) jfrTelemetry.getJfrTelemetry();
+
// Track whether RecordingStream has been closed
AtomicBoolean recordingStreamClosed = new AtomicBoolean(false);
- jfrTelemetry
- .getJfrRuntimeMetrics()
- .getRecordingStream()
- .onClose(() -> recordingStreamClosed.set(true));
+ jfrRuntimeMetrics.getRecordingStream().onClose(() -> recordingStreamClosed.set(true));
assertThat(reader.collectAllMetrics()).isNotEmpty();
jfrTelemetry.close();
- logs.assertDoesNotContain("RuntimeMetrics is already closed");
+ logs.assertDoesNotContain("RuntimeTelemetry is already closed");
assertThat(recordingStreamClosed.get()).isTrue();
// clear all metrics that might have arrived after close
@@ -103,7 +117,7 @@ class RuntimeMetricsTest {
assertThat(reader.collectAllMetrics()).isEmpty();
jfrTelemetry.close();
- logs.assertContains("RuntimeMetrics is already closed");
+ logs.assertContains("RuntimeTelemetry is already closed");
}
}
}There was a problem hiding this comment.
Copied from runtime-telemetry-java8/library/README.md
diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/README.md b/instrumentation/runtime-telemetry/library/README.md
index 7ddde09e1f..29339b2138 100644
--- a/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/README.md
+++ b/instrumentation/runtime-telemetry/library/README.md
@@ -2,12 +2,16 @@
This module provides JVM runtime metrics as documented in the [semantic conventions](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/runtime/jvm-metrics.md).
+This is the unified runtime telemetry module that works on all Java versions. On Java 8-16, it uses
+JMX for metrics collection. On Java 17+, it can additionally use JFR (Java Flight Recorder) for
+metrics that are not available via JMX.
+
## Quickstart
### Add these dependencies to your project
Replace `OPENTELEMETRY_VERSION` with the [latest
-release]( https://central.sonatype.com/artifact/io.opentelemetry.instrumentation/opentelemetry-runtime-telemetry-java8).
+release](https://central.sonatype.com/artifact/io.opentelemetry.instrumentation/opentelemetry-runtime-telemetry).
For Maven, add to your `pom.xml` dependencies:
@@ -15,7 +19,7 @@ For Maven, add to your `pom.xml` dependencies:
<dependencies>
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
- <artifactId>opentelemetry-runtime-telemetry-java8</artifactId>
+ <artifactId>opentelemetry-runtime-telemetry</artifactId>
<version>OPENTELEMETRY_VERSION</version>
</dependency>
</dependencies>
@@ -24,7 +28,7 @@ For Maven, add to your `pom.xml` dependencies:
For Gradle, add to your dependencies:
```groovy
-runtimeOnly("io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java8:OPENTELEMETRY_VERSION")
+runtimeOnly("io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry:OPENTELEMETRY_VERSION")
```
### Usage
@@ -34,10 +38,10 @@ Register JVM runtime metrics:
```java
OpenTelemetry openTelemetry = // OpenTelemetry instance configured elsewhere
-RuntimeMetrics runtimeMetrics = RuntimeMetrics.create(openTelemetry);
+RuntimeTelemetry runtimeTelemetry = RuntimeTelemetry.create(openTelemetry);
// When done, close to stop metric collection
-runtimeMetrics.close();
+runtimeTelemetry.close();
```
To select specific metrics, configure [metric views](https://opentelemetry.io/docs/languages/java/sdk/#views)
@@ -70,17 +74,61 @@ meter_provider:
views:
# Drop all metrics from this instrumentation scope
- selector:
- meter_name: io.opentelemetry.runtime-telemetry-java8
+ meter_name: io.opentelemetry.runtime-telemetry
stream:
aggregation:
drop:
# Keep jvm.memory.used (views are additive, this creates a second stream)
- selector:
- meter_name: io.opentelemetry.runtime-telemetry-java8
+ meter_name: io.opentelemetry.runtime-telemetry
instrument_name: jvm.memory.used
stream: {}
```
+## Metrics
+
+### Stable Metrics (enabled by default)
+
+These metrics are collected via JMX on all Java versions:
+
+| Metric | Description |
+| -------- | ----------- |
+| `jvm.class.count` | Number of classes currently loaded |
+| `jvm.class.loaded` | Number of classes loaded since JVM start |
+| `jvm.class.unloaded` | Number of classes unloaded since JVM start |
+| `jvm.cpu.recent_utilization` | Recent CPU utilization for the process |
+| `jvm.cpu.time` | CPU time used by the process |
+| `jvm.gc.duration` | Duration of JVM garbage collection actions |
+| `jvm.memory.committed` | Measure of memory committed |
+| `jvm.memory.limit` | Measure of max obtainable memory |
+| `jvm.memory.used` | Measure of memory used |
+| `jvm.memory.used_after_last_gc` | Measure of memory used, as measured after the most recent garbage collection event on this pool |
+| `jvm.thread.count` | Number of executing platform threads |
+
+### Experimental Metrics
+
+These metrics are enabled with `emitExperimentalMetrics()`:
+
+**JMX-based (all Java versions):**
+
+| Metric | Description |
+| -------- | ----------- |
+| `jvm.buffer.count` | Number of buffers in the pool |
+| `jvm.buffer.memory.limit` | Measure of total memory capacity of buffers |
+| `jvm.buffer.memory.used` | Measure of memory used by buffers |
+| `jvm.memory.init` | Measure of initial memory requested |
+| `jvm.system.cpu.utilization` | System-wide CPU utilization |
+
+**JFR-based (Java 17+ only):**
+
+| Metric | Description |
+| -------- | ----------- |
+| `jvm.cpu.context_switch` | Context switch rate |
+| `jvm.cpu.longlock` | Long lock contention |
+| `jvm.memory.allocation` | Memory allocation rate |
+| `jvm.network.io` | Network I/O bytes |
+| `jvm.network.time` | Network I/O time |
+
## Garbage Collector Dependent Metrics
The attributes reported on the memory metrics (`jvm.memory.*`) and gc metrics (`jvm.gc.*`) are dependent on the garbage collector used by the application, since each garbage collector organizes memory pools differently and has different strategies for reclaiming memory during garbage collection.
It's opt-in according to sem conv - is it going to be stable then? |
Good point. The only reason this particular attribute is opt-in is because it was added after stabilizing the metric, and it's a breaking change in semconv to add a recommended attribute to a stable metric (in most cases). But I think instrumentation that takes a major version bump should add it. Need to confirm / codify in semconv though. I'll raise in Monday's semconv SIG and figure out if / how to do this properly. |
zeitlinger
left a comment
There was a problem hiding this comment.
initial review - will continue once I understand how we want to get this aligned with #15822
| final class JarAnalyzer implements ClassFileTransformer { | ||
|
|
||
| private static final Logger logger = Logger.getLogger(JarAnalyzer.class.getName()); | ||
| private static final java.util.logging.Logger logger = |
There was a problem hiding this comment.
can't because also using io.opentelemetry.api.logs.Logger in this class
| .logRecordBuilder(); | ||
| Worker worker = new Worker(logRecordBuilder, toProcess, jarsPerSecond); | ||
| private JarAnalyzer(OpenTelemetry openTelemetry, String instrumentationName, int jarsPerSecond) { | ||
| String instrumentationVersion = |
There was a problem hiding this comment.
version is nullable - add requireNonNull
| @Override | ||
| public void beforeAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk) { | ||
| OpenTelemetry openTelemetry = GlobalOpenTelemetry.get(); | ||
| DeclarativeConfigProperties config = |
There was a problem hiding this comment.
do I get it right that we're no longer checking if a module is active?
There was a problem hiding this comment.
it's still being checked, a few lines down
|
|
||
| // Determine which configuration is being used | ||
| boolean baseEnabled = config.getBoolean("enabled", instrumentationMode.equals("default")); | ||
| boolean java17Enabled = java17Config.getBoolean("enabled", false); |
There was a problem hiding this comment.
we're still checking if a module is enabled as before - so I don't see how this helps with #15822
There was a problem hiding this comment.
good question, the way I'm thinking about it is that this Internal.configure() method is just to provide backcompat, and so we wouldn't update this method at all in #15822.
And in #15822, we would check the new distro property, and if the unified runtime-telemetry modules isn't enabled, we wouldn't even call Internal.configure().
Then in 3.0 we can remove the whole Internal class and move reading the configuration (other than distro node) into the RuntimeTelemetryBuilder class itself (so library users can also benefit), and javaagent and spring starter can use RuntimeTelemetryBuilder directly without relying on any internal classes (other than maybe the Experimental class which is a different story).
There was a problem hiding this comment.
The whole config class is in an internal package right now - we don't need to create something for backcompat.
Having a class Internal doesn't make it more internal, IMO.
There was a problem hiding this comment.
just named it Internal as it kind of pairs with Experimental class name, can rename if you have preference
| public static RuntimeTelemetry configure( | ||
| OpenTelemetry openTelemetry, String instrumentationMode) { |
There was a problem hiding this comment.
in Distro Node PR, we can look up the single unified enabled property in declarative config distro node, and not call this entirely
7acc658 to
4cf6b55
Compare
…emetry # Conflicts: # instrumentation/runtime-telemetry/runtime-telemetry-java17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/runtimemetrics/java17/Java17RuntimeMetricsInstaller.java # instrumentation/runtime-telemetry/runtime-telemetry-java17/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java17/internal/RuntimeMetricsConfigUtil.java # instrumentation/runtime-telemetry/runtime-telemetry-java8/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/runtimemetrics/java8/Java8RuntimeMetricsInstaller.java # instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/internal/RuntimeMetricsConfigUtil.java # instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/Java8RuntimeMetricsAutoConfiguration.java # instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimetelemetry/RuntimeMetricsAutoConfiguration.java
AgentConfig was removed in upstream commit 3c573ea and replaced with AgentDistributionConfig. Update Internal.configure() to accept boolean defaultEnabled instead of String instrumentationMode, and update all callers accordingly.
d81deb3 to
2238d83
Compare
2238d83 to
3ae6d19
Compare
| <module name="WhitespaceAround"> | ||
| <property name="allowEmptyConstructors" value="true"/> | ||
| <property name="allowEmptyLambdas" value="true"/> | ||
| <property name="allowEmptyMethods" value="true"/> | ||
| <property name="allowEmptyTypes" value="true"/> | ||
| <property name="allowEmptyLoops" value="true"/> | ||
| <property name="ignoreEnhancedForColon" value="false"/> | ||
| <property name="tokens" | ||
| value="ASSIGN, BAND, BAND_ASSIGN, BOR, BOR_ASSIGN, BSR, BSR_ASSIGN, BXOR, | ||
| BXOR_ASSIGN, COLON, DIV, DIV_ASSIGN, DO_WHILE, EQUAL, GE, GT, LAMBDA, LAND, | ||
| LCURLY, LE, LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY, | ||
| LITERAL_FOR, LITERAL_IF, LITERAL_RETURN, LITERAL_SWITCH, LITERAL_SYNCHRONIZED, | ||
| LITERAL_TRY, LITERAL_WHILE, LOR, LT, MINUS, MINUS_ASSIGN, MOD, MOD_ASSIGN, | ||
| NOT_EQUAL, PLUS, PLUS_ASSIGN, QUESTION, RCURLY, SL, SLIST, SL_ASSIGN, SR, | ||
| SR_ASSIGN, STAR, STAR_ASSIGN, LITERAL_ASSERT, TYPE_EXTENSION_AND"/> | ||
| <message key="ws.notFollowed" | ||
| value="WhitespaceAround: ''{0}'' is not followed by whitespace. Empty blocks may only be represented as '{}' when not part of a multi-block statement (4.1.3)"/> | ||
| <message key="ws.notPreceded" | ||
| value="WhitespaceAround: ''{0}'' is not preceded with whitespace."/> | ||
| </module> |
There was a problem hiding this comment.
this rule conflicted with spotless handling of empty default expression in Java 17 switch statement
we could probably get rid of a lot of checkstyle checks that are already covered by spotless
|
Thanks @laurit for reviewing! |
Currently built on top of
First of all, I apologize for such a large PR. A good bit of the diff is because of copied files (I've annotated these) and imports, but there is still a lot.
This unifies
runtime-telemetry-java8andruntime-telemetry-java17modules into a singleruntime-telemetrymodule that supports both Java 8 and Java 17 via multijar.The motivation was that I was having a lot of trouble understanding the configuration story for the existing runtime-telemetry modules which ended up blocking progress on #15822.
A second motivation is stabilizing the runtime-telemetry instrumentation:
Full backcompat is provided for existing users of both javaagent and library instrumentation, but several stable options have been deprecated and will be removed in 3.0 (see below).
Note: do not merge this PR until after the next release, because a prior deprecation PR needs to be released first:
Settings for the unified Runtime Telemetry instrumentation
otel.instrumentation.runtime-telemetry.emit-experimental-metricsfalseotel.instrumentation.runtime-telemetry.experimental.prefer-jfrfalseotel.instrumentation.runtime-telemetry.experimental.package-emitter.enabledfalseotel.instrumentation.runtime-telemetry.experimental.package-emitter.jars-per-secondDeprecated Properties (to be removed in 3.0)
otel.instrumentation.runtime-telemetry.capture-gc-causefalseotel.instrumentation.runtime-telemetry.emit-experimental-telemetryfalseemit-experimental-metricsinstead.otel.instrumentation.runtime-telemetry.package-emitter.enabledfalseexperimental.package-emitter.enabledinstead.otel.instrumentation.runtime-telemetry.package-emitter.jars-per-secondexperimental.package-emitter.jars-per-secondinstead.otel.instrumentation.runtime-telemetry-java17.enabledfalseemit-experimental-metricsfor experimental JFR features.otel.instrumentation.runtime-telemetry-java17.enable-allfalseemit-experimental-metricsandexperimental.prefer-jfr.