From 638ae4470ffc22499032775b318592db35293f50 Mon Sep 17 00:00:00 2001 From: jack-berg <34418638+jack-berg@users.noreply.github.com> Date: Thu, 31 Mar 2022 11:56:58 -0500 Subject: [PATCH] Update runtime memory metrics to reflect semantic conventions (#5718) * Update runtime memory metrics to reflect semantic conventions * Fix SpringBootSmokeTest * Fix PrometheusSmokeTest --- .../runtimemetrics/RuntimeMetricsTest.groovy | 6 +- .../runtimemetrics/MemoryPools.java | 172 +++++++----------- .../runtimemetrics/MemoryPoolsTest.java | 91 +++++---- .../smoketest/PrometheusSmokeTest.groovy | 2 +- .../smoketest/SpringBootSmokeTest.groovy | 6 +- 5 files changed, 129 insertions(+), 148 deletions(-) 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 a3aa6c676d4e..4e66df5e090a 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 @@ -18,8 +18,10 @@ class RuntimeMetricsTest extends AgentInstrumentationSpecification { conditions.eventually { assert getMetrics().any { it.name == "runtime.jvm.gc.time" } assert getMetrics().any { it.name == "runtime.jvm.gc.count" } - assert getMetrics().any { it.name == "runtime.jvm.memory.area" } - assert getMetrics().any { it.name == "runtime.jvm.memory.pool" } + assert getMetrics().any { it.name == "process.runtime.jvm.memory.init" } + 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" } } } } diff --git a/instrumentation/runtime-metrics/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/MemoryPools.java b/instrumentation/runtime-metrics/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/MemoryPools.java index 77a2fe771cc4..35f040b08b3a 100644 --- a/instrumentation/runtime-metrics/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/MemoryPools.java +++ b/instrumentation/runtime-metrics/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/MemoryPools.java @@ -12,14 +12,16 @@ import io.opentelemetry.api.metrics.Meter; import io.opentelemetry.api.metrics.ObservableLongMeasurement; import java.lang.management.ManagementFactory; -import java.lang.management.MemoryMXBean; import java.lang.management.MemoryPoolMXBean; +import java.lang.management.MemoryType; import java.lang.management.MemoryUsage; import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; +import java.util.function.Function; /** - * Registers measurements that generate metrics about JVM memory areas. + * Registers measurements that generate metrics about JVM memory pools. * *

Example usage: * @@ -30,132 +32,94 @@ *

Example metrics being exported: Component * *

- *   runtime.jvm.memory.area{type="used",area="heap"} 2000000
- *   runtime.jvm.memory.area{type="committed",area="non_heap"} 200000
- *   runtime.jvm.memory.area{type="used",pool="PS Eden Space"} 2000
+ *   process.runtime.jvm.memory.init{type="heap",pool="G1 Eden Space"} 1000000
+ *   process.runtime.jvm.memory.usage{type="heap",pool="G1 Eden Space"} 2500000
+ *   process.runtime.jvm.memory.committed{type="heap",pool="G1 Eden Space"} 3000000
+ *   process.runtime.jvm.memory.max{type="heap",pool="G1 Eden Space"} 4000000
+ *   process.runtime.jvm.memory.init{type="non_heap",pool="Metaspace"} 200
+ *   process.runtime.jvm.memory.usage{type="non_heap",pool="Metaspace"} 400
+ *   process.runtime.jvm.memory.committed{type="non_heap",pool="Metaspace"} 500
  * 
*/ public final class MemoryPools { - // Visible for testing - static final AttributeKey TYPE_KEY = AttributeKey.stringKey("type"); - // Visible for testing - static final AttributeKey AREA_KEY = AttributeKey.stringKey("area"); + + private static final AttributeKey TYPE_KEY = AttributeKey.stringKey("type"); private static final AttributeKey POOL_KEY = AttributeKey.stringKey("pool"); - private static final String USED = "used"; - private static final String COMMITTED = "committed"; - private static final String MAX = "max"; private static final String HEAP = "heap"; private static final String NON_HEAP = "non_heap"; - private static final Attributes COMMITTED_HEAP = - Attributes.of(TYPE_KEY, COMMITTED, AREA_KEY, HEAP); - private static final Attributes USED_HEAP = Attributes.of(TYPE_KEY, USED, AREA_KEY, HEAP); - private static final Attributes MAX_HEAP = Attributes.of(TYPE_KEY, MAX, AREA_KEY, HEAP); - - private static final Attributes COMMITTED_NON_HEAP = - Attributes.of(TYPE_KEY, COMMITTED, AREA_KEY, NON_HEAP); - private static final Attributes USED_NON_HEAP = Attributes.of(TYPE_KEY, USED, AREA_KEY, NON_HEAP); - private static final Attributes MAX_NON_HEAP = Attributes.of(TYPE_KEY, MAX, AREA_KEY, NON_HEAP); - - /** Register only the "area" measurements. */ + /** + * Register observers for java runtime memory metrics. + * + * @deprecated use {@link #registerObservers(OpenTelemetry openTelemetry)} + */ @Deprecated - public static void registerMemoryAreaObservers() { - registerMemoryPoolObservers(GlobalOpenTelemetry.get()); + public static void registerObservers() { + registerObservers(GlobalOpenTelemetry.get()); } - public static void registerMemoryAreaObservers(OpenTelemetry openTelemetry) { - MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean(); - Meter meter = openTelemetry.getMeterProvider().get(MemoryPools.class.getName()); + /** Register observers for java runtime memory metrics. */ + public static void registerObservers(OpenTelemetry openTelemetry) { + List poolBeans = ManagementFactory.getMemoryPoolMXBeans(); + Meter meter = openTelemetry.getMeter("io.opentelemetry.runtime-metrics"); + meter - .upDownCounterBuilder("runtime.jvm.memory.area") - .setDescription("Bytes of a given JVM memory area.") + .upDownCounterBuilder("process.runtime.jvm.memory.usage") + .setDescription("Measure of memory used") .setUnit("By") - .buildWithCallback( - resultLongObserver -> { - recordHeap(resultLongObserver, memoryBean.getHeapMemoryUsage()); - recordNonHeap(resultLongObserver, memoryBean.getNonHeapMemoryUsage()); - }); - } + .buildWithCallback(callback(poolBeans, MemoryUsage::getUsed)); - /** Register only the "pool" measurements. */ - @Deprecated - public static void registerMemoryPoolObservers() { - registerMemoryPoolObservers(GlobalOpenTelemetry.get()); - } - - public static void registerMemoryPoolObservers(OpenTelemetry openTelemetry) { - List poolBeans = ManagementFactory.getMemoryPoolMXBeans(); - Meter meter = openTelemetry.getMeterProvider().get(MemoryPools.class.getName()); - List usedLabelSets = new ArrayList<>(poolBeans.size()); - List committedLabelSets = new ArrayList<>(poolBeans.size()); - List maxLabelSets = new ArrayList<>(poolBeans.size()); - for (MemoryPoolMXBean pool : poolBeans) { - usedLabelSets.add(Attributes.of(TYPE_KEY, USED, POOL_KEY, pool.getName())); - committedLabelSets.add(Attributes.of(TYPE_KEY, COMMITTED, POOL_KEY, pool.getName())); - maxLabelSets.add(Attributes.of(TYPE_KEY, MAX, POOL_KEY, pool.getName())); - } meter - .upDownCounterBuilder("runtime.jvm.memory.pool") - .setDescription("Bytes of a given JVM memory pool.") + .upDownCounterBuilder("process.runtime.jvm.memory.init") + .setDescription("Measure of initial memory requested") .setUnit("By") - .buildWithCallback( - resultLongObserver -> { - for (int i = 0; i < poolBeans.size(); i++) { - MemoryUsage poolUsage = poolBeans.get(i).getUsage(); - if (poolUsage != null) { - record( - resultLongObserver, - poolUsage, - usedLabelSets.get(i), - committedLabelSets.get(i), - maxLabelSets.get(i)); - } - } - }); - } + .buildWithCallback(callback(poolBeans, MemoryUsage::getInit)); - /** - * Register all measurements provided by this module. - * - * @deprecated use {@link #registerObservers(OpenTelemetry openTelemetry)} - */ - @Deprecated - public static void registerObservers() { - registerMemoryAreaObservers(); - registerMemoryPoolObservers(); - } + meter + .upDownCounterBuilder("process.runtime.jvm.memory.committed") + .setDescription("Measure of memory committed") + .setUnit("By") + .buildWithCallback(callback(poolBeans, MemoryUsage::getCommitted)); - /** Register all measurements provided by this module. */ - public static void registerObservers(OpenTelemetry openTelemetry) { - registerMemoryAreaObservers(openTelemetry); - registerMemoryPoolObservers(openTelemetry); + meter + .upDownCounterBuilder("process.runtime.jvm.memory.max") + .setDescription("Measure of max obtainable memory") + .setUnit("By") + .buildWithCallback(callback(poolBeans, MemoryUsage::getMax)); } - static void recordHeap(ObservableLongMeasurement measurement, MemoryUsage usage) { - record(measurement, usage, USED_HEAP, COMMITTED_HEAP, MAX_HEAP); - } + // Visible for testing + static Consumer callback( + List poolBeans, Function extractor) { + List attributeSets = new ArrayList<>(poolBeans.size()); + for (MemoryPoolMXBean pool : poolBeans) { + attributeSets.add( + Attributes.builder() + .put(POOL_KEY, pool.getName()) + .put(TYPE_KEY, memoryType(pool.getType())) + .build()); + } - static void recordNonHeap(ObservableLongMeasurement measurement, MemoryUsage usage) { - record(measurement, usage, USED_NON_HEAP, COMMITTED_NON_HEAP, MAX_NON_HEAP); + return measurement -> { + for (int i = 0; i < poolBeans.size(); i++) { + Attributes attributes = attributeSets.get(i); + long value = extractor.apply(poolBeans.get(i).getUsage()); + if (value != -1) { + measurement.record(value, attributes); + } + } + }; } - private static void record( - ObservableLongMeasurement measurement, - MemoryUsage usage, - Attributes usedAttributes, - Attributes committedAttributes, - Attributes maxAttributes) { - // TODO: Decide if init is needed or not. It is a constant that can be queried once on startup. - // if (usage.getInit() != -1) { - // measurement.record(usage.getInit(), ...); - // } - measurement.record(usage.getUsed(), usedAttributes); - measurement.record(usage.getCommitted(), committedAttributes); - // TODO: Decide if max is needed or not. It is a constant that can be queried once on startup. - if (usage.getMax() != -1) { - measurement.record(usage.getMax(), maxAttributes); + private static String memoryType(MemoryType memoryType) { + switch (memoryType) { + case HEAP: + return HEAP; + case NON_HEAP: + return NON_HEAP; } + return "unknown"; } private MemoryPools() {} diff --git a/instrumentation/runtime-metrics/library/src/test/java/io/opentelemetry/instrumentation/runtimemetrics/MemoryPoolsTest.java b/instrumentation/runtime-metrics/library/src/test/java/io/opentelemetry/instrumentation/runtimemetrics/MemoryPoolsTest.java index fa498126194a..cac71df834ec 100644 --- a/instrumentation/runtime-metrics/library/src/test/java/io/opentelemetry/instrumentation/runtimemetrics/MemoryPoolsTest.java +++ b/instrumentation/runtime-metrics/library/src/test/java/io/opentelemetry/instrumentation/runtimemetrics/MemoryPoolsTest.java @@ -5,64 +5,77 @@ package io.opentelemetry.instrumentation.runtimemetrics; -import static org.mockito.Mockito.mock; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.metrics.ObservableLongMeasurement; +import java.lang.management.MemoryPoolMXBean; +import java.lang.management.MemoryType; import java.lang.management.MemoryUsage; +import java.util.Arrays; +import java.util.List; +import java.util.function.Consumer; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; +@ExtendWith(MockitoExtension.class) class MemoryPoolsTest { - @Test - void observeHeap() { - ObservableLongMeasurement measurement = mock(ObservableLongMeasurement.class); - MemoryPools.recordHeap(measurement, new MemoryUsage(-1, 1, 2, 3)); - verify(measurement) - .record(1, Attributes.of(MemoryPools.TYPE_KEY, "used", MemoryPools.AREA_KEY, "heap")); - verify(measurement) - .record(2, Attributes.of(MemoryPools.TYPE_KEY, "committed", MemoryPools.AREA_KEY, "heap")); - verify(measurement) - .record(3, Attributes.of(MemoryPools.TYPE_KEY, "max", MemoryPools.AREA_KEY, "heap")); - verifyNoMoreInteractions(measurement); - } + @Spy private ObservableLongMeasurement measurement; - @Test - void observeHeapNoMax() { - ObservableLongMeasurement measurement = mock(ObservableLongMeasurement.class); - MemoryPools.recordHeap(measurement, new MemoryUsage(-1, 1, 2, -1)); - verify(measurement) - .record(1, Attributes.of(MemoryPools.TYPE_KEY, "used", MemoryPools.AREA_KEY, "heap")); - verify(measurement) - .record(2, Attributes.of(MemoryPools.TYPE_KEY, "committed", MemoryPools.AREA_KEY, "heap")); - verifyNoMoreInteractions(measurement); + @Mock private MemoryPoolMXBean heapPoolBean; + @Mock private MemoryPoolMXBean nonHeapPoolBean; + + @Mock private MemoryUsage heapPoolUsage; + @Mock private MemoryUsage nonHeapUsage; + + private List beans; + + @BeforeEach + void setup() { + when(heapPoolBean.getName()).thenReturn("heap_pool"); + when(heapPoolBean.getType()).thenReturn(MemoryType.HEAP); + when(heapPoolBean.getUsage()).thenReturn(heapPoolUsage); + when(nonHeapPoolBean.getName()).thenReturn("non_heap_pool"); + when(nonHeapPoolBean.getType()).thenReturn(MemoryType.NON_HEAP); + when(nonHeapPoolBean.getUsage()).thenReturn(nonHeapUsage); + beans = Arrays.asList(heapPoolBean, nonHeapPoolBean); } @Test - void observeNonHeap() { - ObservableLongMeasurement measurement = mock(ObservableLongMeasurement.class); - MemoryPools.recordNonHeap(measurement, new MemoryUsage(-1, 4, 5, 6)); + void callback_Records() { + when(heapPoolUsage.getUsed()).thenReturn(1L); + when(nonHeapUsage.getUsed()).thenReturn(2L); + + Consumer callback = + MemoryPools.callback(beans, MemoryUsage::getUsed); + callback.accept(measurement); + verify(measurement) - .record(4, Attributes.of(MemoryPools.TYPE_KEY, "used", MemoryPools.AREA_KEY, "non_heap")); + .record(1, Attributes.builder().put("pool", "heap_pool").put("type", "heap").build()); verify(measurement) .record( - 5, Attributes.of(MemoryPools.TYPE_KEY, "committed", MemoryPools.AREA_KEY, "non_heap")); - verify(measurement) - .record(6, Attributes.of(MemoryPools.TYPE_KEY, "max", MemoryPools.AREA_KEY, "non_heap")); - verifyNoMoreInteractions(measurement); + 2, Attributes.builder().put("pool", "non_heap_pool").put("type", "non_heap").build()); } @Test - void observeNonHeapNoMax() { - ObservableLongMeasurement measurement = mock(ObservableLongMeasurement.class); - MemoryPools.recordNonHeap(measurement, new MemoryUsage(-1, 4, 5, -1)); - verify(measurement) - .record(4, Attributes.of(MemoryPools.TYPE_KEY, "used", MemoryPools.AREA_KEY, "non_heap")); + void callback_SkipRecord() { + when(heapPoolUsage.getMax()).thenReturn(1L); + when(nonHeapUsage.getMax()).thenReturn(-1L); + + Consumer callback = MemoryPools.callback(beans, MemoryUsage::getMax); + callback.accept(measurement); + verify(measurement) - .record( - 5, Attributes.of(MemoryPools.TYPE_KEY, "committed", MemoryPools.AREA_KEY, "non_heap")); - verifyNoMoreInteractions(measurement); + .record(1, Attributes.builder().put("pool", "heap_pool").put("type", "heap").build()); + verify(measurement, never()).record(eq(-1), any()); } } diff --git a/smoke-tests/src/test/groovy/io/opentelemetry/smoketest/PrometheusSmokeTest.groovy b/smoke-tests/src/test/groovy/io/opentelemetry/smoketest/PrometheusSmokeTest.groovy index d9c1d50e7cc4..ce86e16160db 100644 --- a/smoke-tests/src/test/groovy/io/opentelemetry/smoketest/PrometheusSmokeTest.groovy +++ b/smoke-tests/src/test/groovy/io/opentelemetry/smoketest/PrometheusSmokeTest.groovy @@ -44,7 +44,7 @@ class PrometheusSmokeTest extends SmokeTest { def prometheusClient = WebClient.of("h1c://localhost:${containerManager.getTargetMappedPort(9090)}") def prometheusData = prometheusClient.get("/").aggregate().join().contentUtf8() - prometheusData.contains("runtime_jvm_memory_pool") + prometheusData.contains("process_runtime_jvm_memory_usage") cleanup: stopTarget() diff --git a/smoke-tests/src/test/groovy/io/opentelemetry/smoketest/SpringBootSmokeTest.groovy b/smoke-tests/src/test/groovy/io/opentelemetry/smoketest/SpringBootSmokeTest.groovy index 73235aa0a73b..bf8f9199d6e1 100644 --- a/smoke-tests/src/test/groovy/io/opentelemetry/smoketest/SpringBootSmokeTest.groovy +++ b/smoke-tests/src/test/groovy/io/opentelemetry/smoketest/SpringBootSmokeTest.groovy @@ -79,8 +79,10 @@ class SpringBootSmokeTest extends SmokeTest { def metrics = new MetricsInspector(waitForMetrics()) metrics.hasMetricsNamed("runtime.jvm.gc.time") metrics.hasMetricsNamed("runtime.jvm.gc.count") - metrics.hasMetricsNamed("runtime.jvm.memory.area") - metrics.hasMetricsNamed("runtime.jvm.memory.pool") + metrics.hasMetricsNamed("process.runtime.jvm.memory.init") + metrics.hasMetricsNamed("process.runtime.jvm.memory.usage") + metrics.hasMetricsNamed("process.runtime.jvm.memory.committed") + metrics.hasMetricsNamed("process.runtime.jvm.memory.max") cleanup: stopTarget()