From 6f0f608cbb35a717b097e6f14c663f477651aabd Mon Sep 17 00:00:00 2001 From: jack-berg <34418638+jack-berg@users.noreply.github.com> Date: Thu, 19 May 2022 18:14:14 -0500 Subject: [PATCH] Add JVM classes instrumentation (#6069) * Add JVM classes instrumentation * Add unit of 1 * Assert monotonicity --- .../RuntimeMetricsInstaller.java | 2 + .../runtimemetrics/RuntimeMetricsTest.groovy | 3 + .../runtimemetrics/Classes.java | 69 +++++++++++++++ .../runtimemetrics/ClassesTest.java | 83 +++++++++++++++++++ 4 files changed, 157 insertions(+) create mode 100644 instrumentation/runtime-metrics/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/Classes.java create mode 100644 instrumentation/runtime-metrics/library/src/test/java/io/opentelemetry/instrumentation/runtimemetrics/ClassesTest.java diff --git a/instrumentation/runtime-metrics/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/RuntimeMetricsInstaller.java b/instrumentation/runtime-metrics/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/RuntimeMetricsInstaller.java index a55aabe5642e..a35bf41b1258 100644 --- a/instrumentation/runtime-metrics/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/RuntimeMetricsInstaller.java +++ b/instrumentation/runtime-metrics/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/RuntimeMetricsInstaller.java @@ -8,6 +8,7 @@ import com.google.auto.service.AutoService; import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.instrumentation.api.config.Config; +import io.opentelemetry.instrumentation.runtimemetrics.Classes; import io.opentelemetry.instrumentation.runtimemetrics.GarbageCollector; import io.opentelemetry.instrumentation.runtimemetrics.MemoryPools; import io.opentelemetry.javaagent.extension.AgentListener; @@ -28,6 +29,7 @@ public void afterAgent(Config config, AutoConfiguredOpenTelemetrySdk unused) { .isInstrumentationEnabled(Collections.singleton("runtime-metrics"), DEFAULT_ENABLED)) { MemoryPools.registerObservers(GlobalOpenTelemetry.get()); + Classes.registerObservers(GlobalOpenTelemetry.get()); if (config.getBoolean( "otel.instrumentation.runtime-metrics.experimental-metrics.enabled", false)) { diff --git a/instrumentation/runtime-metrics/javaagent/src/test/groovy/io/opentelemetry/instrumentation/javaagent/runtimemetrics/RuntimeMetricsTest.groovy b/instrumentation/runtime-metrics/javaagent/src/test/groovy/io/opentelemetry/instrumentation/javaagent/runtimemetrics/RuntimeMetricsTest.groovy index 4e66df5e090a..c8ee1553ac6f 100644 --- a/instrumentation/runtime-metrics/javaagent/src/test/groovy/io/opentelemetry/instrumentation/javaagent/runtimemetrics/RuntimeMetricsTest.groovy +++ b/instrumentation/runtime-metrics/javaagent/src/test/groovy/io/opentelemetry/instrumentation/javaagent/runtimemetrics/RuntimeMetricsTest.groovy @@ -22,6 +22,9 @@ class RuntimeMetricsTest extends AgentInstrumentationSpecification { assert getMetrics().any { it.name == "process.runtime.jvm.memory.usage" } assert getMetrics().any { it.name == "process.runtime.jvm.memory.committed" } assert getMetrics().any { it.name == "process.runtime.jvm.memory.max" } + assert getMetrics().any { it.name == "process.runtime.jvm.classes.loaded" } + assert getMetrics().any { it.name == "process.runtime.jvm.classes.unloaded" } + assert getMetrics().any { it.name == "process.runtime.jvm.classes.current_loaded" } } } } diff --git a/instrumentation/runtime-metrics/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/Classes.java b/instrumentation/runtime-metrics/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/Classes.java new file mode 100644 index 000000000000..334c5b169c55 --- /dev/null +++ b/instrumentation/runtime-metrics/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/Classes.java @@ -0,0 +1,69 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.runtimemetrics; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.metrics.Meter; +import java.lang.management.ClassLoadingMXBean; +import java.lang.management.ManagementFactory; + +/** + * Registers measurements that generate metrics about JVM classes. + * + *
Example usage: + * + *
{@code + * Classes.registerObservers(GlobalOpenTelemetry.get()); + * }+ * + *
Example metrics being exported: + * + *
+ * process.runtime.jvm.classes.loaded 100 + * process.runtime.jvm.classes.unloaded 2 + * process.runtime.jvm.classes.current_loaded 98 + *+ */ +public final class Classes { + + // Visible for testing + static final Classes INSTANCE = new Classes(); + + /** Register observers for java runtime class metrics. */ + public static void registerObservers(OpenTelemetry openTelemetry) { + INSTANCE.registerObservers(openTelemetry, ManagementFactory.getClassLoadingMXBean()); + } + + // Visible for testing + void registerObservers(OpenTelemetry openTelemetry, ClassLoadingMXBean classBean) { + Meter meter = openTelemetry.getMeter("io.opentelemetry.runtime-metrics"); + + meter + .counterBuilder("process.runtime.jvm.classes.loaded") + .setDescription("Number of classes loaded since JVM start") + .setUnit("1") + .buildWithCallback( + observableMeasurement -> + observableMeasurement.record(classBean.getTotalLoadedClassCount())); + + meter + .counterBuilder("process.runtime.jvm.classes.unloaded") + .setDescription("Number of classes unloaded since JVM start") + .setUnit("1") + .buildWithCallback( + observableMeasurement -> + observableMeasurement.record(classBean.getUnloadedClassCount())); + + meter + .upDownCounterBuilder("process.runtime.jvm.classes.current_loaded") + .setDescription("Number of classes currently loaded") + .setUnit("1") + .buildWithCallback( + observableMeasurement -> observableMeasurement.record(classBean.getLoadedClassCount())); + } + + private Classes() {} +} diff --git a/instrumentation/runtime-metrics/library/src/test/java/io/opentelemetry/instrumentation/runtimemetrics/ClassesTest.java b/instrumentation/runtime-metrics/library/src/test/java/io/opentelemetry/instrumentation/runtimemetrics/ClassesTest.java new file mode 100644 index 000000000000..8cdbc2fd93b9 --- /dev/null +++ b/instrumentation/runtime-metrics/library/src/test/java/io/opentelemetry/instrumentation/runtimemetrics/ClassesTest.java @@ -0,0 +1,83 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.runtimemetrics; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static org.mockito.Mockito.when; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import java.lang.management.ClassLoadingMXBean; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class ClassesTest { + + @RegisterExtension + static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + @Mock private ClassLoadingMXBean classBean; + + @Test + void registerObservers() { + when(classBean.getTotalLoadedClassCount()).thenReturn(3L); + when(classBean.getUnloadedClassCount()).thenReturn(2L); + when(classBean.getLoadedClassCount()).thenReturn(1); + + Classes.INSTANCE.registerObservers(testing.getOpenTelemetry(), classBean); + + testing.waitAndAssertMetrics( + "io.opentelemetry.runtime-metrics", + "process.runtime.jvm.classes.loaded", + metrics -> + metrics.anySatisfy( + metricData -> + assertThat(metricData) + .hasDescription("Number of classes loaded since JVM start") + .hasUnit("1") + .hasLongSumSatisfying( + sum -> + sum.isMonotonic() + .hasPointsSatisfying( + point -> + point.hasValue(3).hasAttributes(Attributes.empty()))))); + testing.waitAndAssertMetrics( + "io.opentelemetry.runtime-metrics", + "process.runtime.jvm.classes.unloaded", + metrics -> + metrics.anySatisfy( + metricData -> + assertThat(metricData) + .hasDescription("Number of classes unloaded since JVM start") + .hasUnit("1") + .hasLongSumSatisfying( + sum -> + sum.isMonotonic() + .hasPointsSatisfying( + point -> + point.hasValue(2).hasAttributes(Attributes.empty()))))); + testing.waitAndAssertMetrics( + "io.opentelemetry.runtime-metrics", + "process.runtime.jvm.classes.current_loaded", + metrics -> + metrics.anySatisfy( + metricData -> + assertThat(metricData) + .hasDescription("Number of classes currently loaded") + .hasUnit("1") + .hasLongSumSatisfying( + sum -> + sum.isNotMonotonic() + .hasPointsSatisfying( + point -> + point.hasValue(1).hasAttributes(Attributes.empty()))))); + } +}