From 1c2a75d1a4c6e05d6aee54f65c9143f10a9c4672 Mon Sep 17 00:00:00 2001 From: jack-berg Date: Thu, 19 May 2022 10:40:20 -0500 Subject: [PATCH 1/3] Add JVM classes instrumentation --- .../RuntimeMetricsInstaller.java | 2 + .../runtimemetrics/RuntimeMetricsTest.groovy | 3 + .../runtimemetrics/Classes.java | 66 ++++++++++++++++ .../runtimemetrics/ClassesTest.java | 77 +++++++++++++++++++ 4 files changed, 148 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..c2bee7bd8c39 --- /dev/null +++ b/instrumentation/runtime-metrics/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/Classes.java @@ -0,0 +1,66 @@ +/* + * 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") + .buildWithCallback( + observableMeasurement -> + observableMeasurement.record(classBean.getTotalLoadedClassCount())); + + meter + .counterBuilder("process.runtime.jvm.classes.unloaded") + .setDescription("Number of classes unloaded since JVM start") + .buildWithCallback( + observableMeasurement -> + observableMeasurement.record(classBean.getUnloadedClassCount())); + + meter + .upDownCounterBuilder("process.runtime.jvm.classes.current_loaded") + .setDescription("Number of classes currently loaded") + .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..58fcf275857c --- /dev/null +++ b/instrumentation/runtime-metrics/library/src/test/java/io/opentelemetry/instrumentation/runtimemetrics/ClassesTest.java @@ -0,0 +1,77 @@ +/* + * 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") + .hasLongSumSatisfying( + sum -> + sum.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") + .hasLongSumSatisfying( + sum -> + sum.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") + .hasLongSumSatisfying( + sum -> + sum.hasPointsSatisfying( + point -> + point.hasValue(1).hasAttributes(Attributes.empty()))))); + } +} From 00cc50c1fc5bd56ec5c4f819b05f4719be4c6192 Mon Sep 17 00:00:00 2001 From: jack-berg Date: Thu, 19 May 2022 11:03:53 -0500 Subject: [PATCH 2/3] Add unit of 1 --- .../opentelemetry/instrumentation/runtimemetrics/Classes.java | 3 +++ .../instrumentation/runtimemetrics/ClassesTest.java | 3 +++ 2 files changed, 6 insertions(+) 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 index c2bee7bd8c39..334c5b169c55 100644 --- 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 @@ -44,6 +44,7 @@ void registerObservers(OpenTelemetry openTelemetry, ClassLoadingMXBean classBean meter .counterBuilder("process.runtime.jvm.classes.loaded") .setDescription("Number of classes loaded since JVM start") + .setUnit("1") .buildWithCallback( observableMeasurement -> observableMeasurement.record(classBean.getTotalLoadedClassCount())); @@ -51,6 +52,7 @@ void registerObservers(OpenTelemetry openTelemetry, ClassLoadingMXBean classBean meter .counterBuilder("process.runtime.jvm.classes.unloaded") .setDescription("Number of classes unloaded since JVM start") + .setUnit("1") .buildWithCallback( observableMeasurement -> observableMeasurement.record(classBean.getUnloadedClassCount())); @@ -58,6 +60,7 @@ void registerObservers(OpenTelemetry openTelemetry, ClassLoadingMXBean classBean meter .upDownCounterBuilder("process.runtime.jvm.classes.current_loaded") .setDescription("Number of classes currently loaded") + .setUnit("1") .buildWithCallback( observableMeasurement -> observableMeasurement.record(classBean.getLoadedClassCount())); } 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 index 58fcf275857c..9d3c34b87e8b 100644 --- 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 @@ -42,6 +42,7 @@ void registerObservers() { metricData -> assertThat(metricData) .hasDescription("Number of classes loaded since JVM start") + .hasUnit("1") .hasLongSumSatisfying( sum -> sum.hasPointsSatisfying( @@ -55,6 +56,7 @@ void registerObservers() { metricData -> assertThat(metricData) .hasDescription("Number of classes unloaded since JVM start") + .hasUnit("1") .hasLongSumSatisfying( sum -> sum.hasPointsSatisfying( @@ -68,6 +70,7 @@ void registerObservers() { metricData -> assertThat(metricData) .hasDescription("Number of classes currently loaded") + .hasUnit("1") .hasLongSumSatisfying( sum -> sum.hasPointsSatisfying( From e8af9857c28b1daaffa9ccb6c58147784c27bc36 Mon Sep 17 00:00:00 2001 From: jack-berg Date: Thu, 19 May 2022 14:44:46 -0500 Subject: [PATCH 3/3] Assert monotonicity --- .../runtimemetrics/ClassesTest.java | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) 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 index 9d3c34b87e8b..8cdbc2fd93b9 100644 --- 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 @@ -45,9 +45,10 @@ void registerObservers() { .hasUnit("1") .hasLongSumSatisfying( sum -> - sum.hasPointsSatisfying( - point -> - point.hasValue(3).hasAttributes(Attributes.empty()))))); + sum.isMonotonic() + .hasPointsSatisfying( + point -> + point.hasValue(3).hasAttributes(Attributes.empty()))))); testing.waitAndAssertMetrics( "io.opentelemetry.runtime-metrics", "process.runtime.jvm.classes.unloaded", @@ -59,9 +60,10 @@ void registerObservers() { .hasUnit("1") .hasLongSumSatisfying( sum -> - sum.hasPointsSatisfying( - point -> - point.hasValue(2).hasAttributes(Attributes.empty()))))); + sum.isMonotonic() + .hasPointsSatisfying( + point -> + point.hasValue(2).hasAttributes(Attributes.empty()))))); testing.waitAndAssertMetrics( "io.opentelemetry.runtime-metrics", "process.runtime.jvm.classes.current_loaded", @@ -73,8 +75,9 @@ void registerObservers() { .hasUnit("1") .hasLongSumSatisfying( sum -> - sum.hasPointsSatisfying( - point -> - point.hasValue(1).hasAttributes(Attributes.empty()))))); + sum.isNotMonotonic() + .hasPointsSatisfying( + point -> + point.hasValue(1).hasAttributes(Attributes.empty()))))); } }