From 6af684c91bce7b98555ca6cf5da8b76947de7f96 Mon Sep 17 00:00:00 2001 From: Maximo Bautista Date: Fri, 27 Mar 2026 12:10:00 -0400 Subject: [PATCH 1/2] POC: Send JVM runtime metrics via OTLP using OTel-native naming Adds jvm.memory.used, jvm.memory.committed, jvm.memory.limit, jvm.gc.duration, jvm.gc.count, jvm.thread.count, jvm.class.loaded, jvm.class.unloaded, jvm.cpu.recent_utilization, jvm.cpu.count as OTel instruments on the existing OTLP metrics pipeline. Includes jvm.memory.type attribute for heap/non_heap breakdown required by semantic-core equivalence mappings. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../shim/metrics/JvmOtlpRuntimeMetrics.java | 243 ++++++++++++++++++ .../metrics/JvmOtlpRuntimeMetricsTest.groovy | 139 ++++++++++ 2 files changed, 382 insertions(+) create mode 100644 dd-java-agent/agent-otel/otel-shim/src/main/java/datadog/opentelemetry/shim/metrics/JvmOtlpRuntimeMetrics.java create mode 100644 dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.47/src/test/groovy/opentelemetry147/metrics/JvmOtlpRuntimeMetricsTest.groovy diff --git a/dd-java-agent/agent-otel/otel-shim/src/main/java/datadog/opentelemetry/shim/metrics/JvmOtlpRuntimeMetrics.java b/dd-java-agent/agent-otel/otel-shim/src/main/java/datadog/opentelemetry/shim/metrics/JvmOtlpRuntimeMetrics.java new file mode 100644 index 0000000000..68120f7215 --- /dev/null +++ b/dd-java-agent/agent-otel/otel-shim/src/main/java/datadog/opentelemetry/shim/metrics/JvmOtlpRuntimeMetrics.java @@ -0,0 +1,243 @@ +package datadog.opentelemetry.shim.metrics; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.Meter; +import java.lang.management.GarbageCollectorMXBean; +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryMXBean; +import java.lang.management.MemoryPoolMXBean; +import java.lang.management.ThreadMXBean; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Registers JVM runtime metrics using OTel semantic convention names via the dd-trace-java OTLP + * metrics pipeline. These metrics flow via OTLP without requiring a Datadog Agent or DogStatsD. + * + *

OTel JVM runtime metrics conventions: + * https://opentelemetry.io/docs/specs/semconv/runtime/jvm-metrics/ + * + *

Semantic-core equivalence mappings: + * https://github.com/DataDog/semantic-core/blob/main/sor/domains/metrics/integrations/java/_equivalence/ + */ +public final class JvmOtlpRuntimeMetrics { + + private static final Logger log = LoggerFactory.getLogger(JvmOtlpRuntimeMetrics.class); + private static final String INSTRUMENTATION_SCOPE = "datadog.jvm.runtime"; + + private static volatile boolean started = false; + + /** Registers all JVM runtime metric instruments on the OTel MeterProvider. */ + public static void start() { + if (started) { + return; + } + started = true; + + try { + Meter meter = OtelMeterProvider.INSTANCE.get(INSTRUMENTATION_SCOPE); + registerMemoryMetrics(meter); + registerGcMetrics(meter); + registerThreadMetrics(meter); + registerClassLoadingMetrics(meter); + registerCpuMetrics(meter); + log.debug("Started OTLP runtime metrics with OTel-native naming (jvm.*)"); + } catch (Exception e) { + log.error("Failed to start JVM OTLP runtime metrics", e); + } + } + + /** + * jvm.memory.used - JVM memory used, split by type (heap/non_heap) and pool. + * + *

Maps to: jvm.heap_memory, jvm.non_heap_memory (via semantic-core, requires Sum By) + */ + private static void registerMemoryMetrics(Meter meter) { + MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean(); + List pools = ManagementFactory.getMemoryPoolMXBeans(); + + // jvm.memory.used - Measure of memory used + meter + .upDownCounterBuilder("jvm.memory.used") + .setDescription("Measure of memory used.") + .setUnit("By") + .buildWithCallback( + measurement -> { + // Heap total + measurement.record( + memoryBean.getHeapMemoryUsage().getUsed(), + Attributes.of(AttributeKey.stringKey("jvm.memory.type"), "heap")); + // Non-heap total + measurement.record( + memoryBean.getNonHeapMemoryUsage().getUsed(), + Attributes.of(AttributeKey.stringKey("jvm.memory.type"), "non_heap")); + // Per-pool breakdown + for (MemoryPoolMXBean pool : pools) { + measurement.record( + pool.getUsage().getUsed(), + Attributes.of( + AttributeKey.stringKey("jvm.memory.type"), + pool.getType().name().toLowerCase(), + AttributeKey.stringKey("jvm.memory.pool.name"), + pool.getName())); + } + }); + + // jvm.memory.committed - Measure of memory committed + meter + .upDownCounterBuilder("jvm.memory.committed") + .setDescription("Measure of memory committed.") + .setUnit("By") + .buildWithCallback( + measurement -> { + measurement.record( + memoryBean.getHeapMemoryUsage().getCommitted(), + Attributes.of(AttributeKey.stringKey("jvm.memory.type"), "heap")); + measurement.record( + memoryBean.getNonHeapMemoryUsage().getCommitted(), + Attributes.of(AttributeKey.stringKey("jvm.memory.type"), "non_heap")); + }); + + // jvm.memory.limit - Measure of max obtainable memory + meter + .upDownCounterBuilder("jvm.memory.limit") + .setDescription("Measure of max obtainable memory.") + .setUnit("By") + .buildWithCallback( + measurement -> { + long heapMax = memoryBean.getHeapMemoryUsage().getMax(); + if (heapMax > 0) { + measurement.record( + heapMax, + Attributes.of(AttributeKey.stringKey("jvm.memory.type"), "heap")); + } + long nonHeapMax = memoryBean.getNonHeapMemoryUsage().getMax(); + if (nonHeapMax > 0) { + measurement.record( + nonHeapMax, + Attributes.of(AttributeKey.stringKey("jvm.memory.type"), "non_heap")); + } + }); + } + + /** + * jvm.gc.duration - Duration of JVM garbage collection actions. Maps to: jvm.gc.pause_time (via + * semantic-core) + */ + private static void registerGcMetrics(Meter meter) { + List gcBeans = ManagementFactory.getGarbageCollectorMXBeans(); + + // jvm.gc.duration - GC collection time (monotonic counter, in seconds) + meter + .counterBuilder("jvm.gc.duration") + .ofDoubles() + .setDescription("Duration of JVM garbage collection actions.") + .setUnit("s") + .buildWithCallback( + measurement -> { + for (GarbageCollectorMXBean gc : gcBeans) { + long timeMs = gc.getCollectionTime(); + if (timeMs >= 0) { + measurement.record( + timeMs / 1000.0, + Attributes.of( + AttributeKey.stringKey("jvm.gc.name"), + gc.getName(), + AttributeKey.stringKey("jvm.gc.action"), + gc.getName())); + } + } + }); + + // jvm.gc.count - Number of GC collections + meter + .counterBuilder("jvm.gc.count") + .setDescription("Number of executions of the garbage collector.") + .setUnit("{collection}") + .buildWithCallback( + measurement -> { + for (GarbageCollectorMXBean gc : gcBeans) { + long count = gc.getCollectionCount(); + if (count >= 0) { + measurement.record( + count, + Attributes.of(AttributeKey.stringKey("jvm.gc.name"), gc.getName())); + } + } + }); + } + + /** jvm.thread.count - Number of executing threads. Maps to: jvm.thread_count (via semantic-core) */ + private static void registerThreadMetrics(Meter meter) { + ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); + + meter + .upDownCounterBuilder("jvm.thread.count") + .setDescription("Number of executing platform threads.") + .setUnit("{thread}") + .buildWithCallback( + measurement -> { + measurement.record(threadBean.getThreadCount()); + }); + } + + /** jvm.class.loaded - Number of loaded classes. */ + private static void registerClassLoadingMetrics(Meter meter) { + meter + .upDownCounterBuilder("jvm.class.loaded") + .setDescription("Number of classes currently loaded.") + .setUnit("{class}") + .buildWithCallback( + measurement -> { + measurement.record( + ManagementFactory.getClassLoadingMXBean().getLoadedClassCount()); + }); + + meter + .counterBuilder("jvm.class.unloaded") + .setDescription("Number of classes unloaded since JVM start.") + .setUnit("{class}") + .buildWithCallback( + measurement -> { + measurement.record( + ManagementFactory.getClassLoadingMXBean().getUnloadedClassCount()); + }); + } + + /** jvm.cpu.recent_utilization - Recent CPU utilization by the JVM process. */ + private static void registerCpuMetrics(Meter meter) { + meter + .gaugeBuilder("jvm.cpu.recent_utilization") + .setDescription("Recent CPU utilization for the process as reported by the JVM.") + .setUnit("1") + .buildWithCallback( + measurement -> { + try { + java.lang.management.OperatingSystemMXBean osBean = + ManagementFactory.getOperatingSystemMXBean(); + if (osBean instanceof com.sun.management.OperatingSystemMXBean) { + double cpuLoad = + ((com.sun.management.OperatingSystemMXBean) osBean).getProcessCpuLoad(); + if (cpuLoad >= 0) { + measurement.record(cpuLoad); + } + } + } catch (Exception e) { + // com.sun.management may not be available on all JVMs + } + }); + + meter + .upDownCounterBuilder("jvm.cpu.count") + .setDescription("Number of processors available to the JVM.") + .setUnit("{cpu}") + .buildWithCallback( + measurement -> { + measurement.record(Runtime.getRuntime().availableProcessors()); + }); + } + + private JvmOtlpRuntimeMetrics() {} +} diff --git a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.47/src/test/groovy/opentelemetry147/metrics/JvmOtlpRuntimeMetricsTest.groovy b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.47/src/test/groovy/opentelemetry147/metrics/JvmOtlpRuntimeMetricsTest.groovy new file mode 100644 index 0000000000..b3296620ef --- /dev/null +++ b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.47/src/test/groovy/opentelemetry147/metrics/JvmOtlpRuntimeMetricsTest.groovy @@ -0,0 +1,139 @@ +package opentelemetry147.metrics + +import datadog.opentelemetry.shim.metrics.JvmOtlpRuntimeMetrics +import datadog.opentelemetry.shim.metrics.OtelMeterProvider +import datadog.trace.agent.test.InstrumentationSpecification +import datadog.trace.bootstrap.otel.common.OtelInstrumentationScope +import datadog.trace.bootstrap.otel.metrics.OtelInstrumentDescriptor +import datadog.trace.bootstrap.otel.metrics.data.OtlpDoublePoint +import datadog.trace.bootstrap.otel.metrics.data.OtlpLongPoint +import datadog.trace.bootstrap.otel.metrics.data.OtelMetricRegistry +import datadog.trace.bootstrap.otel.metrics.data.OtlpDataPoint +import datadog.trace.bootstrap.otel.metrics.export.OtlpMetricVisitor +import datadog.trace.bootstrap.otel.metrics.export.OtlpMetricsVisitor +import datadog.trace.bootstrap.otel.metrics.export.OtlpScopedMetricsVisitor + +/** + * Tests that JVM runtime metrics are registered and exported via OTLP + * using OTel semantic convention names (jvm.memory.used, jvm.thread.count, etc.). + * + * Ref: https://opentelemetry.io/docs/specs/semconv/runtime/jvm-metrics/ + * Ref: https://github.com/DataDog/semantic-core/blob/main/sor/domains/metrics/integrations/java/_equivalence/ + */ +class JvmOtlpRuntimeMetricsTest extends InstrumentationSpecification { + + @Override + void configurePreAgent() { + super.configurePreAgent() + injectSysConfig("dd.metrics.otel.enabled", "true") + } + + def "JVM runtime metrics are registered and produce data points"() { + when: + JvmOtlpRuntimeMetrics.start() + def collector = new MetricCollector() + OtelMetricRegistry.INSTANCE.collectMetrics(collector) + + then: + // OtelInstrumentDescriptor.name is UTF8BytesString, convert to String for comparison + def names = collector.metricNames.collect { it.toString() } + "jvm.memory.used" in names + "jvm.memory.committed" in names + "jvm.thread.count" in names + "jvm.class.loaded" in names + "jvm.cpu.count" in names + "jvm.gc.duration" in names + "jvm.gc.count" in names + "jvm.class.unloaded" in names + "jvm.cpu.recent_utilization" in names + } + + def "jvm.memory.used has heap and non_heap type attributes"() { + when: + JvmOtlpRuntimeMetrics.start() + def collector = new MetricCollector() + OtelMetricRegistry.INSTANCE.collectMetrics(collector) + + then: + def types = collector.attributeValues("jvm.memory.used", "jvm.memory.type") + types.contains("heap") + types.contains("non_heap") + } + + def "jvm.memory.used heap value is positive"() { + when: + JvmOtlpRuntimeMetrics.start() + def collector = new MetricCollector() + OtelMetricRegistry.INSTANCE.collectMetrics(collector) + + then: + def heapPoints = collector.points["jvm.memory.used"] + .findAll { it.attrs["jvm.memory.type"] == "heap" } + heapPoints.size() > 0 + heapPoints[0].value > 0 + } + + def "jvm.thread.count is positive"() { + when: + JvmOtlpRuntimeMetrics.start() + def collector = new MetricCollector() + OtelMetricRegistry.INSTANCE.collectMetrics(collector) + + then: + def threadPoints = collector.points["jvm.thread.count"] + threadPoints.size() > 0 + threadPoints[0].value > 0 + } + + static class DataPointEntry { + Map attrs + Number value + } + + static class MetricCollector implements OtlpMetricsVisitor, OtlpScopedMetricsVisitor, OtlpMetricVisitor { + String currentInstrument = "" + Map currentAttrs = [:] + Set metricNames = new LinkedHashSet<>() + Map> points = [:].withDefault { [] } + + @Override + OtlpScopedMetricsVisitor visitScopedMetrics(OtelInstrumentationScope scope) { + return this + } + + @Override + OtlpMetricVisitor visitMetric(OtelInstrumentDescriptor descriptor) { + currentInstrument = descriptor.name.toString() + metricNames.add(descriptor.name.toString()) + return this + } + + @Override + void visitAttribute(int type, String key, Object value) { + currentAttrs.put(key.toString(), value.toString()) + } + + @Override + void visitDataPoint(OtlpDataPoint point) { + def attrs = new HashMap(currentAttrs) + currentAttrs.clear() + Number value = 0 + if (point instanceof OtlpLongPoint) { + value = ((OtlpLongPoint) point).value + } else if (point instanceof OtlpDoublePoint) { + value = ((OtlpDoublePoint) point).value + } + def entry = new DataPointEntry() + entry.attrs = attrs + entry.value = value + points[currentInstrument].add(entry) + } + + Set attributeValues(String metricName, String attrKey) { + points[metricName] + .collect { it.attrs[attrKey] } + .findAll { it != null } + .toSet() + } + } +} From c0174a38a519016104ce1dbd1a1b462acfc43701 Mon Sep 17 00:00:00 2001 From: Maximo Bautista Date: Fri, 27 Mar 2026 13:13:10 -0400 Subject: [PATCH 2/2] Add missing JVM metrics: jvm.memory.init, jvm.buffer.*, jvm.system.cpu.utilization, jvm.class.count Aligns with OTel JVM semantic conventions spreadsheet. Updates test to verify all 16 metrics are registered. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../shim/metrics/JvmOtlpRuntimeMetrics.java | 140 +++++++++++++++++- .../metrics/JvmOtlpRuntimeMetricsTest.groovy | 19 ++- 2 files changed, 155 insertions(+), 4 deletions(-) diff --git a/dd-java-agent/agent-otel/otel-shim/src/main/java/datadog/opentelemetry/shim/metrics/JvmOtlpRuntimeMetrics.java b/dd-java-agent/agent-otel/otel-shim/src/main/java/datadog/opentelemetry/shim/metrics/JvmOtlpRuntimeMetrics.java index 68120f7215..72fd6d4a6a 100644 --- a/dd-java-agent/agent-otel/otel-shim/src/main/java/datadog/opentelemetry/shim/metrics/JvmOtlpRuntimeMetrics.java +++ b/dd-java-agent/agent-otel/otel-shim/src/main/java/datadog/opentelemetry/shim/metrics/JvmOtlpRuntimeMetrics.java @@ -3,6 +3,7 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.metrics.Meter; +import java.lang.management.BufferPoolMXBean; import java.lang.management.GarbageCollectorMXBean; import java.lang.management.ManagementFactory; import java.lang.management.MemoryMXBean; @@ -39,6 +40,7 @@ public static void start() { try { Meter meter = OtelMeterProvider.INSTANCE.get(INSTRUMENTATION_SCOPE); registerMemoryMetrics(meter); + registerBufferMetrics(meter); registerGcMetrics(meter); registerThreadMetrics(meter); registerClassLoadingMetrics(meter); @@ -98,6 +100,15 @@ private static void registerMemoryMetrics(Meter meter) { measurement.record( memoryBean.getNonHeapMemoryUsage().getCommitted(), Attributes.of(AttributeKey.stringKey("jvm.memory.type"), "non_heap")); + for (MemoryPoolMXBean pool : pools) { + measurement.record( + pool.getUsage().getCommitted(), + Attributes.of( + AttributeKey.stringKey("jvm.memory.type"), + pool.getType().name().toLowerCase(), + AttributeKey.stringKey("jvm.memory.pool.name"), + pool.getName())); + } }); // jvm.memory.limit - Measure of max obtainable memory @@ -119,6 +130,101 @@ private static void registerMemoryMetrics(Meter meter) { nonHeapMax, Attributes.of(AttributeKey.stringKey("jvm.memory.type"), "non_heap")); } + for (MemoryPoolMXBean pool : pools) { + long max = pool.getUsage().getMax(); + if (max > 0) { + measurement.record( + max, + Attributes.of( + AttributeKey.stringKey("jvm.memory.type"), + pool.getType().name().toLowerCase(), + AttributeKey.stringKey("jvm.memory.pool.name"), + pool.getName())); + } + } + }); + + // jvm.memory.init - Measure of initial memory requested + meter + .upDownCounterBuilder("jvm.memory.init") + .setDescription("Measure of initial memory requested.") + .setUnit("By") + .buildWithCallback( + measurement -> { + long heapInit = memoryBean.getHeapMemoryUsage().getInit(); + if (heapInit > 0) { + measurement.record( + heapInit, + Attributes.of(AttributeKey.stringKey("jvm.memory.type"), "heap")); + } + long nonHeapInit = memoryBean.getNonHeapMemoryUsage().getInit(); + if (nonHeapInit > 0) { + measurement.record( + nonHeapInit, + Attributes.of(AttributeKey.stringKey("jvm.memory.type"), "non_heap")); + } + }); + } + + /** + * jvm.buffer.* - JVM buffer pool metrics (direct, mapped). Maps to: jvm.buffer_pool.* (via + * semantic-core) + */ + private static void registerBufferMetrics(Meter meter) { + List bufferPools = ManagementFactory.getPlatformMXBeans(BufferPoolMXBean.class); + + // jvm.buffer.memory.used + meter + .upDownCounterBuilder("jvm.buffer.memory.used") + .setDescription("Measure of memory used by buffers.") + .setUnit("By") + .buildWithCallback( + measurement -> { + for (BufferPoolMXBean pool : bufferPools) { + long used = pool.getMemoryUsed(); + if (used >= 0) { + measurement.record( + used, + Attributes.of( + AttributeKey.stringKey("jvm.buffer.pool.name"), pool.getName())); + } + } + }); + + // jvm.buffer.memory.limit + meter + .upDownCounterBuilder("jvm.buffer.memory.limit") + .setDescription("Measure of total memory capacity of buffers.") + .setUnit("By") + .buildWithCallback( + measurement -> { + for (BufferPoolMXBean pool : bufferPools) { + long limit = pool.getTotalCapacity(); + if (limit >= 0) { + measurement.record( + limit, + Attributes.of( + AttributeKey.stringKey("jvm.buffer.pool.name"), pool.getName())); + } + } + }); + + // jvm.buffer.count + meter + .upDownCounterBuilder("jvm.buffer.count") + .setDescription("Number of buffers in the pool.") + .setUnit("{buffer}") + .buildWithCallback( + measurement -> { + for (BufferPoolMXBean pool : bufferPools) { + long count = pool.getCount(); + if (count >= 0) { + measurement.record( + count, + Attributes.of( + AttributeKey.stringKey("jvm.buffer.pool.name"), pool.getName())); + } + } }); } @@ -183,10 +289,20 @@ private static void registerThreadMetrics(Meter meter) { }); } - /** jvm.class.loaded - Number of loaded classes. */ + /** jvm.class.* - Class loading metrics. */ private static void registerClassLoadingMetrics(Meter meter) { meter .upDownCounterBuilder("jvm.class.loaded") + .setDescription("Number of classes loaded since JVM start.") + .setUnit("{class}") + .buildWithCallback( + measurement -> { + measurement.record( + ManagementFactory.getClassLoadingMXBean().getTotalLoadedClassCount()); + }); + + meter + .upDownCounterBuilder("jvm.class.count") .setDescription("Number of classes currently loaded.") .setUnit("{class}") .buildWithCallback( @@ -237,6 +353,28 @@ private static void registerCpuMetrics(Meter meter) { measurement -> { measurement.record(Runtime.getRuntime().availableProcessors()); }); + + // jvm.system.cpu.utilization - Recent CPU utilization for the whole system + meter + .gaugeBuilder("jvm.system.cpu.utilization") + .setDescription("Recent CPU utilization for the whole system as reported by the JVM.") + .setUnit("1") + .buildWithCallback( + measurement -> { + try { + java.lang.management.OperatingSystemMXBean osBean = + ManagementFactory.getOperatingSystemMXBean(); + if (osBean instanceof com.sun.management.OperatingSystemMXBean) { + double load = + ((com.sun.management.OperatingSystemMXBean) osBean).getSystemCpuLoad(); + if (load >= 0) { + measurement.record(load); + } + } + } catch (Exception e) { + // com.sun.management may not be available + } + }); } private JvmOtlpRuntimeMetrics() {} diff --git a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.47/src/test/groovy/opentelemetry147/metrics/JvmOtlpRuntimeMetricsTest.groovy b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.47/src/test/groovy/opentelemetry147/metrics/JvmOtlpRuntimeMetricsTest.groovy index b3296620ef..a9d8dfb80f 100644 --- a/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.47/src/test/groovy/opentelemetry147/metrics/JvmOtlpRuntimeMetricsTest.groovy +++ b/dd-java-agent/instrumentation/opentelemetry/opentelemetry-1.47/src/test/groovy/opentelemetry147/metrics/JvmOtlpRuntimeMetricsTest.groovy @@ -37,15 +37,28 @@ class JvmOtlpRuntimeMetricsTest extends InstrumentationSpecification { then: // OtelInstrumentDescriptor.name is UTF8BytesString, convert to String for comparison def names = collector.metricNames.collect { it.toString() } + // Memory "jvm.memory.used" in names "jvm.memory.committed" in names - "jvm.thread.count" in names - "jvm.class.loaded" in names - "jvm.cpu.count" in names + "jvm.memory.limit" in names + "jvm.memory.init" in names + // Buffers + "jvm.buffer.memory.used" in names + "jvm.buffer.memory.limit" in names + "jvm.buffer.count" in names + // GC "jvm.gc.duration" in names "jvm.gc.count" in names + // Threads + "jvm.thread.count" in names + // Classes + "jvm.class.loaded" in names + "jvm.class.count" in names "jvm.class.unloaded" in names + // CPU + "jvm.cpu.count" in names "jvm.cpu.recent_utilization" in names + "jvm.system.cpu.utilization" in names } def "jvm.memory.used has heap and non_heap type attributes"() {