diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java17/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java17/Java17RuntimeMetricsInstaller.java b/instrumentation/runtime-telemetry/runtime-telemetry-java17/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java17/Java17RuntimeMetricsInstaller.java index efe12ae65c58..a01e4885c209 100644 --- a/instrumentation/runtime-telemetry/runtime-telemetry-java17/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java17/Java17RuntimeMetricsInstaller.java +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java17/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java17/Java17RuntimeMetricsInstaller.java @@ -9,6 +9,7 @@ import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.runtimemetrics.java17.RuntimeMetrics; +import io.opentelemetry.instrumentation.runtimemetrics.java17.RuntimeMetricsBuilder; import io.opentelemetry.javaagent.extension.AgentListener; import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil; @@ -23,24 +24,29 @@ public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredSdk) { ConfigProperties config = AutoConfigureUtil.getConfig(autoConfiguredSdk); OpenTelemetry openTelemetry = GlobalOpenTelemetry.get(); - RuntimeMetrics runtimeMetrics = null; + RuntimeMetricsBuilder builder = null; /* By default don't use any JFR metrics. May change this once semantic conventions are updated. If enabled, default to only the metrics not already covered by runtime-telemetry-java8 */ boolean defaultEnabled = config.getBoolean("otel.instrumentation.common.default-enabled", true); if (config.getBoolean("otel.instrumentation.runtime-telemetry-java17.enable-all", false)) { - runtimeMetrics = RuntimeMetrics.builder(openTelemetry).enableAllFeatures().build(); + builder = RuntimeMetrics.builder(openTelemetry).enableAllFeatures(); } else if (config.getBoolean("otel.instrumentation.runtime-telemetry-java17.enabled", false)) { - runtimeMetrics = RuntimeMetrics.create(openTelemetry); + builder = RuntimeMetrics.builder(openTelemetry); } else if (config.getBoolean( "otel.instrumentation.runtime-telemetry.enabled", defaultEnabled)) { // This only uses metrics gathered by JMX - runtimeMetrics = RuntimeMetrics.builder(openTelemetry).disableAllFeatures().build(); + builder = RuntimeMetrics.builder(openTelemetry).disableAllFeatures(); } - if (runtimeMetrics != null) { - RuntimeMetrics finalJfrTelemetry = runtimeMetrics; + if (builder != null) { + if (config.getBoolean( + "otel.instrumentation.runtime-telemetry.emit-experimental-telemetry", false)) { + builder.enableExperimentalJmxTelemetry(); + } + + RuntimeMetrics finalJfrTelemetry = builder.build(); Thread cleanupTelemetry = new Thread(() -> finalJfrTelemetry.close()); Runtime.getRuntime().addShutdownHook(cleanupTelemetry); } diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java17/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java17/RuntimeMetricsBuilder.java b/instrumentation/runtime-telemetry/runtime-telemetry-java17/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java17/RuntimeMetricsBuilder.java index 84c7ca01ad23..3ddf63ebbdd0 100644 --- a/instrumentation/runtime-telemetry/runtime-telemetry-java17/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java17/RuntimeMetricsBuilder.java +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java17/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java17/RuntimeMetricsBuilder.java @@ -13,6 +13,9 @@ import io.opentelemetry.instrumentation.runtimemetrics.java8.GarbageCollector; import io.opentelemetry.instrumentation.runtimemetrics.java8.MemoryPools; import io.opentelemetry.instrumentation.runtimemetrics.java8.Threads; +import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.ExperimentalBufferPools; +import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.ExperimentalCpu; +import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.ExperimentalMemoryPools; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -28,6 +31,7 @@ public final class RuntimeMetricsBuilder { final EnumMap enabledFeatureMap; private boolean disableJmx = false; + private boolean enableExperimentalJmxTelemetry = false; RuntimeMetricsBuilder(OpenTelemetry openTelemetry) { this.openTelemetry = openTelemetry; @@ -73,13 +77,20 @@ public RuntimeMetricsBuilder disableFeature(JfrFeature feature) { return this; } - /** Disable telemetry collection associated with the {@link JfrFeature}. */ + /** Disable all JMX telemetry collection. */ @CanIgnoreReturnValue public RuntimeMetricsBuilder disableAllJmx() { disableJmx = true; return this; } + /** Disable telemetry collection associated with the {@link JfrFeature}. */ + @CanIgnoreReturnValue + public RuntimeMetricsBuilder enableExperimentalJmxTelemetry() { + enableExperimentalJmxTelemetry = true; + return this; + } + /** Build and start an {@link RuntimeMetrics} with the config from this builder. */ public RuntimeMetrics build() { List observables = buildObservables(); @@ -98,9 +109,14 @@ private List buildObservables() { observables.addAll(BufferPools.registerObservers(openTelemetry)); observables.addAll(Classes.registerObservers(openTelemetry)); observables.addAll(Cpu.registerObservers(openTelemetry)); + observables.addAll(GarbageCollector.registerObservers(openTelemetry)); observables.addAll(MemoryPools.registerObservers(openTelemetry)); observables.addAll(Threads.registerObservers(openTelemetry)); - observables.addAll(GarbageCollector.registerObservers(openTelemetry)); + if (enableExperimentalJmxTelemetry) { + observables.addAll(ExperimentalBufferPools.registerObservers(openTelemetry)); + observables.addAll(ExperimentalCpu.registerObservers(openTelemetry)); + observables.addAll(ExperimentalMemoryPools.registerObservers(openTelemetry)); + } return observables; } catch (Exception e) { throw new IllegalStateException("Error building RuntimeMetrics", e); diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java8/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java8/Java8RuntimeMetricsInstaller.java b/instrumentation/runtime-telemetry/runtime-telemetry-java8/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java8/Java8RuntimeMetricsInstaller.java index 83f895a7b508..bb6ec112be38 100644 --- a/instrumentation/runtime-telemetry/runtime-telemetry-java8/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java8/Java8RuntimeMetricsInstaller.java +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java8/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/runtimemetrics/java8/Java8RuntimeMetricsInstaller.java @@ -14,6 +14,9 @@ import io.opentelemetry.instrumentation.runtimemetrics.java8.GarbageCollector; import io.opentelemetry.instrumentation.runtimemetrics.java8.MemoryPools; import io.opentelemetry.instrumentation.runtimemetrics.java8.Threads; +import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.ExperimentalBufferPools; +import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.ExperimentalCpu; +import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.ExperimentalMemoryPools; import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.JmxRuntimeMetricsUtil; import io.opentelemetry.javaagent.extension.AgentListener; import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; @@ -35,19 +38,24 @@ public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredSdk) { || Double.parseDouble(System.getProperty("java.specification.version")) >= 17) { return; } + OpenTelemetry openTelemetry = GlobalOpenTelemetry.get(); List observables = new ArrayList<>(); observables.addAll(BufferPools.registerObservers(openTelemetry)); observables.addAll(Classes.registerObservers(openTelemetry)); observables.addAll(Cpu.registerObservers(openTelemetry)); + observables.addAll(GarbageCollector.registerObservers(openTelemetry)); observables.addAll(MemoryPools.registerObservers(openTelemetry)); observables.addAll(Threads.registerObservers(openTelemetry)); - observables.addAll(GarbageCollector.registerObservers(openTelemetry)); - Thread cleanupTelemetry = - new Thread( - () -> { - JmxRuntimeMetricsUtil.closeObservers(observables); - }); + + if (config.getBoolean( + "otel.instrumentation.runtime-telemetry.emit-experimental-telemetry", false)) { + observables.addAll(ExperimentalBufferPools.registerObservers(openTelemetry)); + observables.addAll(ExperimentalCpu.registerObservers(openTelemetry)); + observables.addAll(ExperimentalMemoryPools.registerObservers(openTelemetry)); + } + + Thread cleanupTelemetry = new Thread(() -> JmxRuntimeMetricsUtil.closeObservers(observables)); Runtime.getRuntime().addShutdownHook(cleanupTelemetry); } } diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/README.md b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/README.md index 44c6c9a02ae3..9488f25922e5 100644 --- a/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/README.md +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/README.md @@ -34,7 +34,6 @@ Register observers for the desired runtime metrics: ```java OpenTelemetry openTelemetry = // OpenTelemetry instance configured elsewhere -BufferPools.registerObservers(openTelemetry); Classes.registerObservers(openTelemetry); Cpu.registerObservers(openTelemetry); MemoryPools.registerObservers(openTelemetry); @@ -42,6 +41,19 @@ Threads.registerObservers(openTelemetry); GarbageCollector.registerObservers(openTelemetry); ``` +## Stable JVM metrics preview + +If you want to enable the preview of the stable JVM semantic conventions, you need to set either +the `otel.semconv-stability.opt-in` system property or the `OTEL_SEMCONV_STABILITY_OPT_IN` +environment variable to one of the following values: + +- `jvm` - this will make the runtime metrics emit only the new, stable conventions, and stop + emitting the old experimental conventions that the instrumentation emitted previously. +- `jvm/dup` - emit both the old and the stable JVM conventions, allowing for a seamless transition. + +Note that the `otel.semconv-stability.opt-in` setting is a comma-separated list, and you can specify +more than one value, e.g. `-Dotel.semconv-stability.opt-in=http,jvm`. + ## Garbage Collector Dependent Metrics The attributes reported on the memory metrics (`process.runtime.jvm.memory.*`) and gc metrics (`process.runtime.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. diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/BufferPools.java b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/BufferPools.java index 2da60ef67e04..4cbdc4e1557b 100644 --- a/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/BufferPools.java +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/BufferPools.java @@ -10,10 +10,12 @@ import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.metrics.Meter; import io.opentelemetry.api.metrics.ObservableLongMeasurement; +import io.opentelemetry.instrumentation.api.internal.SemconvStability; import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.JmxRuntimeMetricsUtil; import java.lang.management.BufferPoolMXBean; import java.lang.management.ManagementFactory; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.function.Consumer; import java.util.function.Function; @@ -48,6 +50,11 @@ public static List registerObservers(OpenTelemetry openTelemetry) // Visible for testing static List registerObservers( OpenTelemetry openTelemetry, List bufferBeans) { + + // buffer pool metrics are experimental in the new semconv + if (!SemconvStability.emitOldJvmSemconv()) { + return Collections.emptyList(); + } List observables = new ArrayList<>(); Meter meter = JmxRuntimeMetricsUtil.getMeter(openTelemetry); observables.add( diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/Cpu.java b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/Cpu.java index 555f66fb40cf..046613ab5047 100644 --- a/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/Cpu.java +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/Cpu.java @@ -7,13 +7,15 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.instrumentation.api.internal.SemconvStability; +import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.CpuMethods; import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.JmxRuntimeMetricsUtil; import java.lang.management.ManagementFactory; import java.lang.management.OperatingSystemMXBean; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.function.IntSupplier; import java.util.function.Supplier; import javax.annotation.Nullable; @@ -33,122 +35,132 @@ * process.runtime.jvm.system.cpu.utilization 0.15 * process.runtime.jvm.cpu.utilization 0.1 * + * + *

In case you enable the preview of stable JVM semantic conventions (e.g. by setting the {@code + * otel.semconv-stability.opt-in} system property to {@code jvm}), the metrics being exported will + * follow the + * most recent JVM semantic conventions. This is how the example above looks when stable JVM + * semconv is enabled: + * + *

+ *   jvm.cpu.time 20.42
+ *   jvm.cpu.count 8
+ *   jvm.cpu.recent_utilization 0.1
+ * 
*/ public final class Cpu { // Visible for testing static final Cpu INSTANCE = new Cpu(); - private static final String OS_BEAN_J9 = "com.ibm.lang.management.OperatingSystemMXBean"; - private static final String OS_BEAN_HOTSPOT = "com.sun.management.OperatingSystemMXBean"; - private static final String METHOD_PROCESS_CPU_LOAD = "getProcessCpuLoad"; - private static final String METHOD_CPU_LOAD = "getCpuLoad"; - private static final String METHOD_SYSTEM_CPU_LOAD = "getSystemCpuLoad"; - - @Nullable private static final Supplier processCpu; - @Nullable private static final Supplier systemCpu; - - static { - OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean(); - Supplier processCpuSupplier = - methodInvoker(osBean, OS_BEAN_HOTSPOT, METHOD_PROCESS_CPU_LOAD); - if (processCpuSupplier == null) { - // More users will be on hotspot than j9, so check for j9 second - processCpuSupplier = methodInvoker(osBean, OS_BEAN_J9, METHOD_PROCESS_CPU_LOAD); - } - processCpu = processCpuSupplier; - - // As of java 14, com.sun.management.OperatingSystemMXBean#getCpuLoad() is preferred and - // #getSystemCpuLoad() is deprecated - Supplier systemCpuSupplier = methodInvoker(osBean, OS_BEAN_HOTSPOT, METHOD_CPU_LOAD); - if (systemCpuSupplier == null) { - systemCpuSupplier = methodInvoker(osBean, OS_BEAN_HOTSPOT, METHOD_SYSTEM_CPU_LOAD); - } - if (systemCpuSupplier == null) { - // More users will be on hotspot than j9, so check for j9 second - systemCpuSupplier = methodInvoker(osBean, OS_BEAN_J9, METHOD_SYSTEM_CPU_LOAD); - } - systemCpu = systemCpuSupplier; - } + private static final double NANOS_PER_S = TimeUnit.SECONDS.toNanos(1); - /** Register observers for java runtime class metrics. */ + /** Register observers for java runtime CPU metrics. */ public static List registerObservers(OpenTelemetry openTelemetry) { return INSTANCE.registerObservers( - openTelemetry, ManagementFactory.getOperatingSystemMXBean(), systemCpu, processCpu); + openTelemetry, + ManagementFactory.getOperatingSystemMXBean(), + Runtime.getRuntime()::availableProcessors, + CpuMethods.processCpuTime(), + CpuMethods.systemCpuUtilization(), + CpuMethods.processCpuUtilization()); } // Visible for testing List registerObservers( OpenTelemetry openTelemetry, OperatingSystemMXBean osBean, - @Nullable Supplier systemCpuUsage, - @Nullable Supplier processCpuUsage) { + IntSupplier availableProcessors, + @Nullable Supplier processCpuTime, + @Nullable Supplier systemCpuUtilization, + @Nullable Supplier processCpuUtilization) { Meter meter = JmxRuntimeMetricsUtil.getMeter(openTelemetry); List observables = new ArrayList<>(); - observables.add( - meter - .gaugeBuilder("process.runtime.jvm.system.cpu.load_1m") - .setDescription("Average CPU load of the whole system for the last minute") - .setUnit("{run_queue_item}") - .buildWithCallback( - observableMeasurement -> { - double loadAverage = osBean.getSystemLoadAverage(); - if (loadAverage >= 0) { - observableMeasurement.record(loadAverage); - } - })); - if (systemCpuUsage != null) { + if (SemconvStability.emitOldJvmSemconv()) { observables.add( meter - .gaugeBuilder("process.runtime.jvm.system.cpu.utilization") - .setDescription("Recent cpu utilization for the whole system") - .setUnit("1") + .gaugeBuilder("process.runtime.jvm.system.cpu.load_1m") + .setDescription("Average CPU load of the whole system for the last minute") + .setUnit("{run_queue_item}") .buildWithCallback( observableMeasurement -> { - Double cpuUsage = systemCpuUsage.get(); - if (cpuUsage != null && cpuUsage >= 0) { - observableMeasurement.record(cpuUsage); + double loadAverage = osBean.getSystemLoadAverage(); + if (loadAverage >= 0) { + observableMeasurement.record(loadAverage); } })); + if (systemCpuUtilization != null) { + observables.add( + meter + .gaugeBuilder("process.runtime.jvm.system.cpu.utilization") + .setDescription("Recent cpu utilization for the whole system") + .setUnit("1") + .buildWithCallback( + observableMeasurement -> { + Double cpuUsage = systemCpuUtilization.get(); + if (cpuUsage != null && cpuUsage >= 0) { + observableMeasurement.record(cpuUsage); + } + })); + } + if (processCpuUtilization != null) { + observables.add( + meter + .gaugeBuilder("process.runtime.jvm.cpu.utilization") + .setDescription("Recent cpu utilization for the process") + .setUnit("1") + .buildWithCallback( + observableMeasurement -> { + Double cpuUsage = processCpuUtilization.get(); + if (cpuUsage != null && cpuUsage >= 0) { + observableMeasurement.record(cpuUsage); + } + })); + } } - - if (processCpuUsage != null) { + if (SemconvStability.emitStableJvmSemconv()) { + if (processCpuTime != null) { + observables.add( + meter + .counterBuilder("jvm.cpu.time") + .ofDoubles() + .setDescription("CPU time used by the process as reported by the JVM.") + .setUnit("s") + .buildWithCallback( + observableMeasurement -> { + Long cpuTimeNanos = processCpuTime.get(); + if (cpuTimeNanos != null && cpuTimeNanos >= 0) { + observableMeasurement.record(cpuTimeNanos / NANOS_PER_S); + } + })); + } observables.add( meter - .gaugeBuilder("process.runtime.jvm.cpu.utilization") - .setDescription("Recent cpu utilization for the process") - .setUnit("1") + .upDownCounterBuilder("jvm.cpu.count") + .setDescription("Number of processors available to the Java virtual machine.") + .setUnit("{cpu}") .buildWithCallback( - observableMeasurement -> { - Double cpuUsage = processCpuUsage.get(); - if (cpuUsage != null && cpuUsage >= 0) { - observableMeasurement.record(cpuUsage); - } - })); + observableMeasurement -> + observableMeasurement.record(availableProcessors.getAsInt()))); + if (processCpuUtilization != null) { + observables.add( + meter + .gaugeBuilder("jvm.cpu.recent_utilization") + .setDescription("Recent CPU utilization for the process as reported by the JVM.") + .setUnit("1") + .buildWithCallback( + observableMeasurement -> { + Double cpuUsage = processCpuUtilization.get(); + if (cpuUsage != null && cpuUsage >= 0) { + observableMeasurement.record(cpuUsage); + } + })); + } } return observables; } - @Nullable - @SuppressWarnings("ReturnValueIgnored") - private static Supplier methodInvoker( - OperatingSystemMXBean osBean, String osBeanClassName, String methodName) { - try { - Class osBeanClass = Class.forName(osBeanClassName); - osBeanClass.cast(osBean); - Method method = osBeanClass.getDeclaredMethod(methodName); - return () -> { - try { - return (double) method.invoke(osBean); - } catch (IllegalAccessException | InvocationTargetException e) { - return null; - } - }; - } catch (ClassNotFoundException | ClassCastException | NoSuchMethodException e) { - return null; - } - } - private Cpu() {} } diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/MemoryPools.java b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/MemoryPools.java index 3dd3385f66e3..e400a5ea91e4 100644 --- a/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/MemoryPools.java +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/MemoryPools.java @@ -5,11 +5,14 @@ package io.opentelemetry.instrumentation.runtimemetrics.java8; +import static io.opentelemetry.api.common.AttributeKey.stringKey; + import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.metrics.Meter; import io.opentelemetry.api.metrics.ObservableLongMeasurement; +import io.opentelemetry.instrumentation.api.internal.SemconvStability; import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.JmxRuntimeMetricsUtil; import java.lang.management.ManagementFactory; import java.lang.management.MemoryPoolMXBean; @@ -41,11 +44,32 @@ * process.runtime.jvm.memory.usage{type="non_heap",pool="Metaspace"} 400 * process.runtime.jvm.memory.committed{type="non_heap",pool="Metaspace"} 500 * + * + *

In case you enable the preview of stable JVM semantic conventions (e.g. by setting the {@code + * otel.semconv-stability.opt-in} system property to {@code jvm}), the metrics being exported will + * follow the + * most recent JVM semantic conventions. This is how the example above looks when stable JVM + * semconv is enabled: + * + *

+ *   jvm.memory.used{type="heap",pool="G1 Eden Space"} 2500000
+ *   jvm.memory.committed{type="heap",pool="G1 Eden Space"} 3000000
+ *   jvm.memory.limit{type="heap",pool="G1 Eden Space"} 4000000
+ *   jvm.memory.used_after_last_gc{type="heap",pool="G1 Eden Space"} 1500000
+ *   jvm.memory.used{type="non_heap",pool="Metaspace"} 400
+ *   jvm.memory.committed{type="non_heap",pool="Metaspace"} 500
+ * 
*/ public final class MemoryPools { - private static final AttributeKey TYPE_KEY = AttributeKey.stringKey("type"); - private static final AttributeKey POOL_KEY = AttributeKey.stringKey("pool"); + private static final AttributeKey TYPE_KEY = stringKey("type"); + private static final AttributeKey POOL_KEY = stringKey("pool"); + + // TODO: use the opentelemetry-semconv classes once we have metrics attributes there + private static final AttributeKey JVM_MEMORY_POOL_NAME = + stringKey("jvm.memory.pool.name"); + private static final AttributeKey JVM_MEMORY_TYPE = stringKey("jvm.memory.type"); private static final String HEAP = "heap"; private static final String NON_HEAP = "non_heap"; @@ -60,51 +84,130 @@ static List registerObservers( OpenTelemetry openTelemetry, List poolBeans) { Meter meter = JmxRuntimeMetricsUtil.getMeter(openTelemetry); List observables = new ArrayList<>(); - observables.add( - meter - .upDownCounterBuilder("process.runtime.jvm.memory.usage") - .setDescription("Measure of memory used") - .setUnit("By") - .buildWithCallback( - callback(poolBeans, MemoryPoolMXBean::getUsage, MemoryUsage::getUsed))); - - observables.add( - meter - .upDownCounterBuilder("process.runtime.jvm.memory.init") - .setDescription("Measure of initial memory requested") - .setUnit("By") - .buildWithCallback( - callback(poolBeans, MemoryPoolMXBean::getUsage, MemoryUsage::getInit))); - - observables.add( - meter - .upDownCounterBuilder("process.runtime.jvm.memory.committed") - .setDescription("Measure of memory committed") - .setUnit("By") - .buildWithCallback( - callback(poolBeans, MemoryPoolMXBean::getUsage, MemoryUsage::getCommitted))); - - observables.add( - meter - .upDownCounterBuilder("process.runtime.jvm.memory.limit") - .setDescription("Measure of max obtainable memory") - .setUnit("By") - .buildWithCallback( - callback(poolBeans, MemoryPoolMXBean::getUsage, MemoryUsage::getMax))); - - observables.add( - meter - .upDownCounterBuilder("process.runtime.jvm.memory.usage_after_last_gc") - .setDescription( - "Measure of memory used after the most recent garbage collection event on this pool") - .setUnit("By") - .buildWithCallback( - callback(poolBeans, MemoryPoolMXBean::getCollectionUsage, MemoryUsage::getUsed))); + + if (SemconvStability.emitOldJvmSemconv()) { + observables.add( + meter + .upDownCounterBuilder("process.runtime.jvm.memory.usage") + .setDescription("Measure of memory used") + .setUnit("By") + .buildWithCallback( + callback( + POOL_KEY, + TYPE_KEY, + poolBeans, + MemoryPoolMXBean::getUsage, + MemoryUsage::getUsed))); + observables.add( + meter + .upDownCounterBuilder("process.runtime.jvm.memory.init") + .setDescription("Measure of initial memory requested") + .setUnit("By") + .buildWithCallback( + callback( + POOL_KEY, + TYPE_KEY, + poolBeans, + MemoryPoolMXBean::getUsage, + MemoryUsage::getInit))); + observables.add( + meter + .upDownCounterBuilder("process.runtime.jvm.memory.committed") + .setDescription("Measure of memory committed") + .setUnit("By") + .buildWithCallback( + callback( + POOL_KEY, + TYPE_KEY, + poolBeans, + MemoryPoolMXBean::getUsage, + MemoryUsage::getCommitted))); + observables.add( + meter + .upDownCounterBuilder("process.runtime.jvm.memory.limit") + .setDescription("Measure of max obtainable memory") + .setUnit("By") + .buildWithCallback( + callback( + POOL_KEY, + TYPE_KEY, + poolBeans, + MemoryPoolMXBean::getUsage, + MemoryUsage::getMax))); + observables.add( + meter + .upDownCounterBuilder("process.runtime.jvm.memory.usage_after_last_gc") + .setDescription( + "Measure of memory used after the most recent garbage collection event on this pool") + .setUnit("By") + .buildWithCallback( + callback( + POOL_KEY, + TYPE_KEY, + poolBeans, + MemoryPoolMXBean::getCollectionUsage, + MemoryUsage::getUsed))); + } + + if (SemconvStability.emitStableJvmSemconv()) { + observables.add( + meter + .upDownCounterBuilder("jvm.memory.used") + .setDescription("Measure of memory used.") + .setUnit("By") + .buildWithCallback( + callback( + JVM_MEMORY_POOL_NAME, + JVM_MEMORY_TYPE, + poolBeans, + MemoryPoolMXBean::getUsage, + MemoryUsage::getUsed))); + observables.add( + meter + .upDownCounterBuilder("jvm.memory.committed") + .setDescription("Measure of memory committed.") + .setUnit("By") + .buildWithCallback( + callback( + JVM_MEMORY_POOL_NAME, + JVM_MEMORY_TYPE, + poolBeans, + MemoryPoolMXBean::getUsage, + MemoryUsage::getCommitted))); + observables.add( + meter + .upDownCounterBuilder("jvm.memory.limit") + .setDescription("Measure of max obtainable memory.") + .setUnit("By") + .buildWithCallback( + callback( + JVM_MEMORY_POOL_NAME, + JVM_MEMORY_TYPE, + poolBeans, + MemoryPoolMXBean::getUsage, + MemoryUsage::getMax))); + observables.add( + meter + .upDownCounterBuilder("jvm.memory.used_after_last_gc") + .setDescription( + "Measure of memory used, as measured after the most recent garbage collection event on this pool.") + .setUnit("By") + .buildWithCallback( + callback( + JVM_MEMORY_POOL_NAME, + JVM_MEMORY_TYPE, + poolBeans, + MemoryPoolMXBean::getCollectionUsage, + MemoryUsage::getUsed))); + } + return observables; } // Visible for testing static Consumer callback( + AttributeKey poolNameKey, + AttributeKey memoryTypeKey, List poolBeans, Function memoryUsageExtractor, Function valueExtractor) { @@ -112,8 +215,8 @@ static Consumer callback( for (MemoryPoolMXBean pool : poolBeans) { attributeSets.add( Attributes.builder() - .put(POOL_KEY, pool.getName()) - .put(TYPE_KEY, memoryType(pool.getType())) + .put(poolNameKey, pool.getName()) + .put(memoryTypeKey, memoryType(pool.getType())) .build()); } diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/Threads.java b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/Threads.java index bc9ea0f7cc53..83ab5f1c9c94 100644 --- a/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/Threads.java +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/Threads.java @@ -66,6 +66,8 @@ public final class Threads { static final Threads INSTANCE = new Threads(); static final AttributeKey DAEMON = booleanKey("daemon"); + + // TODO: use the opentelemetry-semconv classes once we have metrics attributes there static final AttributeKey JVM_THREAD_DAEMON = booleanKey("jvm.thread.daemon"); static final AttributeKey JVM_THREAD_STATE = stringKey("jvm.thread.state"); diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/internal/CpuMethods.java b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/internal/CpuMethods.java new file mode 100644 index 000000000000..479170865901 --- /dev/null +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/internal/CpuMethods.java @@ -0,0 +1,103 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.runtimemetrics.java8.internal; + +import java.lang.management.ManagementFactory; +import java.lang.management.OperatingSystemMXBean; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.function.Supplier; +import javax.annotation.Nullable; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public final class CpuMethods { + + private static final String OS_BEAN_J9 = "com.ibm.lang.management.OperatingSystemMXBean"; + private static final String OS_BEAN_HOTSPOT = "com.sun.management.OperatingSystemMXBean"; + private static final String METHOD_PROCESS_CPU_TIME = "getProcessCpuTime"; + private static final String METHOD_PROCESS_CPU_LOAD = "getProcessCpuLoad"; + private static final String METHOD_CPU_LOAD = "getCpuLoad"; + private static final String METHOD_SYSTEM_CPU_LOAD = "getSystemCpuLoad"; + + @Nullable private static final Supplier processCpuTime; + @Nullable private static final Supplier processCpuUtilization; + @Nullable private static final Supplier systemCpuUtilization; + + static { + OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean(); + + Supplier processCpuTimeSupplier = + methodInvoker(osBean, OS_BEAN_HOTSPOT, METHOD_PROCESS_CPU_TIME, Long.class); + if (processCpuTimeSupplier == null) { + // More users will be on hotspot than j9, so check for j9 second + processCpuTimeSupplier = + methodInvoker(osBean, OS_BEAN_J9, METHOD_PROCESS_CPU_TIME, Long.class); + } + processCpuTime = processCpuTimeSupplier; + + Supplier processCpuSupplier = + methodInvoker(osBean, OS_BEAN_HOTSPOT, METHOD_PROCESS_CPU_LOAD, Double.class); + if (processCpuSupplier == null) { + // More users will be on hotspot than j9, so check for j9 second + processCpuSupplier = methodInvoker(osBean, OS_BEAN_J9, METHOD_PROCESS_CPU_LOAD, Double.class); + } + processCpuUtilization = processCpuSupplier; + + // As of java 14, com.sun.management.OperatingSystemMXBean#getCpuLoad() is preferred and + // #getSystemCpuLoad() is deprecated + Supplier systemCpuSupplier = + methodInvoker(osBean, OS_BEAN_HOTSPOT, METHOD_CPU_LOAD, Double.class); + if (systemCpuSupplier == null) { + systemCpuSupplier = + methodInvoker(osBean, OS_BEAN_HOTSPOT, METHOD_SYSTEM_CPU_LOAD, Double.class); + } + if (systemCpuSupplier == null) { + // More users will be on hotspot than j9, so check for j9 second + systemCpuSupplier = methodInvoker(osBean, OS_BEAN_J9, METHOD_SYSTEM_CPU_LOAD, Double.class); + } + systemCpuUtilization = systemCpuSupplier; + } + + @Nullable + @SuppressWarnings("ReturnValueIgnored") + private static Supplier methodInvoker( + OperatingSystemMXBean osBean, + String osBeanClassName, + String methodName, + Class returnType) { + try { + Class osBeanClass = Class.forName(osBeanClassName); + osBeanClass.cast(osBean); + Method method = osBeanClass.getDeclaredMethod(methodName); + return () -> { + try { + return returnType.cast(method.invoke(osBean)); + } catch (IllegalAccessException | InvocationTargetException e) { + return null; + } + }; + } catch (ClassNotFoundException | ClassCastException | NoSuchMethodException e) { + return null; + } + } + + public static Supplier processCpuTime() { + return processCpuTime; + } + + public static Supplier processCpuUtilization() { + return processCpuUtilization; + } + + public static Supplier systemCpuUtilization() { + return systemCpuUtilization; + } + + private CpuMethods() {} +} diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/internal/ExperimentalBufferPools.java b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/internal/ExperimentalBufferPools.java new file mode 100644 index 000000000000..d66c54e46085 --- /dev/null +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/internal/ExperimentalBufferPools.java @@ -0,0 +1,93 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.runtimemetrics.java8.internal; + +import static io.opentelemetry.api.common.AttributeKey.stringKey; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.api.metrics.ObservableLongMeasurement; +import io.opentelemetry.instrumentation.api.internal.SemconvStability; +import java.lang.management.BufferPoolMXBean; +import java.lang.management.ManagementFactory; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Registers measurements that generate experimental metrics about buffer pools. These metrics will + * only be generated the preview of stable JVM semantic conventions (e.g. by setting the {@code + * otel.semconv-stability.opt-in} system property to {@code jvm}) is enabled. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public final class ExperimentalBufferPools { + + private static final AttributeKey JVM_BUFFER_POOL_NAME = + stringKey("jvm.buffer.pool.name"); + + /** Register observers for java runtime buffer pool metrics. */ + public static List registerObservers(OpenTelemetry openTelemetry) { + List bufferBeans = + ManagementFactory.getPlatformMXBeans(BufferPoolMXBean.class); + return registerObservers(openTelemetry, bufferBeans); + } + + // Visible for testing + static List registerObservers( + OpenTelemetry openTelemetry, List bufferBeans) { + + if (!SemconvStability.emitStableJvmSemconv()) { + return Collections.emptyList(); + } + List observables = new ArrayList<>(); + Meter meter = JmxRuntimeMetricsUtil.getMeter(openTelemetry); + observables.add( + meter + .upDownCounterBuilder("jvm.buffer.memory.usage") + .setDescription("Measure of memory used by buffers.") + .setUnit("By") + .buildWithCallback(callback(bufferBeans, BufferPoolMXBean::getMemoryUsed))); + observables.add( + meter + .upDownCounterBuilder("jvm.buffer.memory.limit") + .setDescription("Measure of total memory capacity of buffers.") + .setUnit("By") + .buildWithCallback(callback(bufferBeans, BufferPoolMXBean::getTotalCapacity))); + observables.add( + meter + .upDownCounterBuilder("jvm.buffer.count") + .setDescription("Number of buffers in the pool.") + .setUnit("{buffer}") + .buildWithCallback(callback(bufferBeans, BufferPoolMXBean::getCount))); + return observables; + } + + // Visible for testing + static Consumer callback( + List bufferPools, Function extractor) { + List attributeSets = new ArrayList<>(bufferPools.size()); + for (BufferPoolMXBean pool : bufferPools) { + attributeSets.add(Attributes.builder().put(JVM_BUFFER_POOL_NAME, pool.getName()).build()); + } + return measurement -> { + for (int i = 0; i < bufferPools.size(); i++) { + Attributes attributes = attributeSets.get(i); + long value = extractor.apply(bufferPools.get(i)); + if (value != -1) { + measurement.record(value, attributes); + } + } + }; + } + + private ExperimentalBufferPools() {} +} diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/internal/ExperimentalCpu.java b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/internal/ExperimentalCpu.java new file mode 100644 index 000000000000..9dcbdd0523bc --- /dev/null +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/internal/ExperimentalCpu.java @@ -0,0 +1,80 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.runtimemetrics.java8.internal; + +import static java.util.Collections.emptyList; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.instrumentation.api.internal.SemconvStability; +import java.lang.management.ManagementFactory; +import java.lang.management.OperatingSystemMXBean; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; +import javax.annotation.Nullable; + +/** + * Registers measurements that generate experimental metrics about CPU. These metrics will only be + * generated the preview of stable JVM semantic conventions (e.g. by setting the {@code + * otel.semconv-stability.opt-in} system property to {@code jvm}) is enabled. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public final class ExperimentalCpu { + + /** Register observers for java runtime experimental CPU metrics. */ + public static List registerObservers(OpenTelemetry openTelemetry) { + return registerObservers( + openTelemetry, + ManagementFactory.getOperatingSystemMXBean(), + CpuMethods.systemCpuUtilization()); + } + + // Visible for testing + static List registerObservers( + OpenTelemetry openTelemetry, + OperatingSystemMXBean osBean, + @Nullable Supplier systemCpuUtilization) { + + if (!SemconvStability.emitStableJvmSemconv()) { + return emptyList(); + } + Meter meter = JmxRuntimeMetricsUtil.getMeter(openTelemetry); + List observables = new ArrayList<>(); + observables.add( + meter + .gaugeBuilder("jvm.system.cpu.load_1m") + .setDescription( + "Average CPU load of the whole system for the last minute as reported by the JVM.") + .setUnit("{run_queue_item}") + .buildWithCallback( + observableMeasurement -> { + double loadAverage = osBean.getSystemLoadAverage(); + if (loadAverage >= 0) { + observableMeasurement.record(loadAverage); + } + })); + if (systemCpuUtilization != null) { + observables.add( + meter + .gaugeBuilder("jvm.system.cpu.utilization") + .setDescription("Recent CPU utilization for the whole system as reported by the JVM.") + .setUnit("1") + .buildWithCallback( + observableMeasurement -> { + Double cpuUsage = systemCpuUtilization.get(); + if (cpuUsage != null && cpuUsage >= 0) { + observableMeasurement.record(cpuUsage); + } + })); + } + return observables; + } + + private ExperimentalCpu() {} +} diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/internal/ExperimentalMemoryPools.java b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/internal/ExperimentalMemoryPools.java new file mode 100644 index 000000000000..18211debc4cf --- /dev/null +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/internal/ExperimentalMemoryPools.java @@ -0,0 +1,103 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.runtimemetrics.java8.internal; + +import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.api.metrics.ObservableLongMeasurement; +import io.opentelemetry.instrumentation.api.internal.SemconvStability; +import java.lang.management.ManagementFactory; +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; + +/** + * Registers measurements that generate experimental metrics about memory pools. These metrics will + * only be generated the preview of stable JVM semantic conventions (e.g. by setting the {@code + * otel.semconv-stability.opt-in} system property to {@code jvm}) is enabled. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public final class ExperimentalMemoryPools { + + // TODO: use the opentelemetry-semconv classes once we have metrics attributes there + private static final AttributeKey JVM_MEMORY_POOL_NAME = + stringKey("jvm.memory.pool.name"); + private static final AttributeKey JVM_MEMORY_TYPE = stringKey("jvm.memory.type"); + + private static final String HEAP = "heap"; + private static final String NON_HEAP = "non_heap"; + + /** Register observers for java runtime experimental memory metrics. */ + public static List registerObservers(OpenTelemetry openTelemetry) { + return registerObservers(openTelemetry, ManagementFactory.getMemoryPoolMXBeans()); + } + + // Visible for testing + static List registerObservers( + OpenTelemetry openTelemetry, List poolBeans) { + + if (!SemconvStability.emitStableJvmSemconv()) { + return emptyList(); + } + + Meter meter = JmxRuntimeMetricsUtil.getMeter(openTelemetry); + return singletonList( + meter + .upDownCounterBuilder("jvm.memory.init") + .setDescription("Measure of initial memory requested.") + .setUnit("By") + .buildWithCallback(callback(poolBeans))); + } + + private static Consumer callback(List poolBeans) { + List attributeSets = new ArrayList<>(poolBeans.size()); + for (MemoryPoolMXBean pool : poolBeans) { + attributeSets.add( + Attributes.builder() + .put(JVM_MEMORY_POOL_NAME, pool.getName()) + .put(JVM_MEMORY_TYPE, memoryType(pool.getType())) + .build()); + } + + return measurement -> { + for (int i = 0; i < poolBeans.size(); i++) { + Attributes attributes = attributeSets.get(i); + MemoryUsage memoryUsage = poolBeans.get(i).getUsage(); + if (memoryUsage == null) { + // JVM may return null in special cases for MemoryPoolMXBean.getUsage() + continue; + } + long value = memoryUsage.getInit(); + if (value != -1) { + measurement.record(value, attributes); + } + } + }; + } + + private static String memoryType(MemoryType memoryType) { + switch (memoryType) { + case HEAP: + return HEAP; + case NON_HEAP: + return NON_HEAP; + } + return "unknown"; + } + + private ExperimentalMemoryPools() {} +} diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/test/java/io/opentelemetry/instrumentation/runtimemetrics/java8/BufferPoolsTest.java b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/test/java/io/opentelemetry/instrumentation/runtimemetrics/java8/BufferPoolsTest.java index 060bac1c356b..42612e004f65 100644 --- a/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/test/java/io/opentelemetry/instrumentation/runtimemetrics/java8/BufferPoolsTest.java +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/test/java/io/opentelemetry/instrumentation/runtimemetrics/java8/BufferPoolsTest.java @@ -31,7 +31,7 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -public class BufferPoolsTest { +class BufferPoolsTest { @RegisterExtension static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/test/java/io/opentelemetry/instrumentation/runtimemetrics/java8/CpuTest.java b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/test/java/io/opentelemetry/instrumentation/runtimemetrics/java8/CpuTest.java index 17e88913ea4c..da596c7d5854 100644 --- a/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/test/java/io/opentelemetry/instrumentation/runtimemetrics/java8/CpuTest.java +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/test/java/io/opentelemetry/instrumentation/runtimemetrics/java8/CpuTest.java @@ -34,7 +34,7 @@ void registerObservers() { Supplier processCpuUsage = () -> 0.05; Cpu.INSTANCE.registerObservers( - testing.getOpenTelemetry(), osBean, systemCpuUsage, processCpuUsage); + testing.getOpenTelemetry(), osBean, () -> 0, () -> null, systemCpuUsage, processCpuUsage); testing.waitAndAssertMetrics( "io.opentelemetry.runtime-telemetry-java8", diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/test/java/io/opentelemetry/instrumentation/runtimemetrics/java8/MemoryPoolsTest.java b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/test/java/io/opentelemetry/instrumentation/runtimemetrics/java8/MemoryPoolsTest.java index 2286d62163fa..fac209d14b6a 100644 --- a/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/test/java/io/opentelemetry/instrumentation/runtimemetrics/java8/MemoryPoolsTest.java +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/test/java/io/opentelemetry/instrumentation/runtimemetrics/java8/MemoryPoolsTest.java @@ -5,8 +5,10 @@ package io.opentelemetry.instrumentation.runtimemetrics.java8; +import static io.opentelemetry.api.common.AttributeKey.stringKey; import static io.opentelemetry.instrumentation.runtimemetrics.java8.ScopeUtil.EXPECTED_SCOPE; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static java.util.Collections.singletonList; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; @@ -14,7 +16,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.metrics.ObservableLongMeasurement; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; @@ -23,7 +24,6 @@ import java.lang.management.MemoryType; import java.lang.management.MemoryUsage; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.function.Consumer; import org.junit.jupiter.api.BeforeEach; @@ -98,16 +98,13 @@ void registerObservers() { point -> point .hasValue(10) - .hasAttribute( - AttributeKey.stringKey("pool"), "heap_pool") - .hasAttribute(AttributeKey.stringKey("type"), "heap"), + .hasAttribute(stringKey("pool"), "heap_pool") + .hasAttribute(stringKey("type"), "heap"), point -> point .hasValue(14) - .hasAttribute( - AttributeKey.stringKey("pool"), "non_heap_pool") - .hasAttribute( - AttributeKey.stringKey("type"), "non_heap"))))); + .hasAttribute(stringKey("pool"), "non_heap_pool") + .hasAttribute(stringKey("type"), "non_heap"))))); testing.waitAndAssertMetrics( "io.opentelemetry.runtime-telemetry-java8", "process.runtime.jvm.memory.usage", @@ -124,16 +121,13 @@ void registerObservers() { point -> point .hasValue(11) - .hasAttribute( - AttributeKey.stringKey("pool"), "heap_pool") - .hasAttribute(AttributeKey.stringKey("type"), "heap"), + .hasAttribute(stringKey("pool"), "heap_pool") + .hasAttribute(stringKey("type"), "heap"), point -> point .hasValue(15) - .hasAttribute( - AttributeKey.stringKey("pool"), "non_heap_pool") - .hasAttribute( - AttributeKey.stringKey("type"), "non_heap"))))); + .hasAttribute(stringKey("pool"), "non_heap_pool") + .hasAttribute(stringKey("type"), "non_heap"))))); testing.waitAndAssertMetrics( "io.opentelemetry.runtime-telemetry-java8", "process.runtime.jvm.memory.committed", @@ -150,16 +144,13 @@ void registerObservers() { point -> point .hasValue(12) - .hasAttribute( - AttributeKey.stringKey("pool"), "heap_pool") - .hasAttribute(AttributeKey.stringKey("type"), "heap"), + .hasAttribute(stringKey("pool"), "heap_pool") + .hasAttribute(stringKey("type"), "heap"), point -> point .hasValue(16) - .hasAttribute( - AttributeKey.stringKey("pool"), "non_heap_pool") - .hasAttribute( - AttributeKey.stringKey("type"), "non_heap"))))); + .hasAttribute(stringKey("pool"), "non_heap_pool") + .hasAttribute(stringKey("type"), "non_heap"))))); testing.waitAndAssertMetrics( "io.opentelemetry.runtime-telemetry-java8", "process.runtime.jvm.memory.limit", @@ -176,16 +167,13 @@ void registerObservers() { point -> point .hasValue(13) - .hasAttribute( - AttributeKey.stringKey("pool"), "heap_pool") - .hasAttribute(AttributeKey.stringKey("type"), "heap"), + .hasAttribute(stringKey("pool"), "heap_pool") + .hasAttribute(stringKey("type"), "heap"), point -> point .hasValue(17) - .hasAttribute( - AttributeKey.stringKey("pool"), "non_heap_pool") - .hasAttribute( - AttributeKey.stringKey("type"), "non_heap"))))); + .hasAttribute(stringKey("pool"), "non_heap_pool") + .hasAttribute(stringKey("type"), "non_heap"))))); testing.waitAndAssertMetrics( "io.opentelemetry.runtime-telemetry-java8", "process.runtime.jvm.memory.usage_after_last_gc", @@ -203,16 +191,13 @@ void registerObservers() { point -> point .hasValue(18) - .hasAttribute( - AttributeKey.stringKey("pool"), "heap_pool") - .hasAttribute(AttributeKey.stringKey("type"), "heap"), + .hasAttribute(stringKey("pool"), "heap_pool") + .hasAttribute(stringKey("type"), "heap"), point -> point .hasValue(19) - .hasAttribute( - AttributeKey.stringKey("pool"), "non_heap_pool") - .hasAttribute( - AttributeKey.stringKey("type"), "non_heap"))))); + .hasAttribute(stringKey("pool"), "non_heap_pool") + .hasAttribute(stringKey("type"), "non_heap"))))); } @Test @@ -221,7 +206,12 @@ void callback_Records() { when(nonHeapUsage.getUsed()).thenReturn(2L); Consumer callback = - MemoryPools.callback(beans, MemoryPoolMXBean::getUsage, MemoryUsage::getUsed); + MemoryPools.callback( + stringKey("pool"), + stringKey("type"), + beans, + MemoryPoolMXBean::getUsage, + MemoryUsage::getUsed); callback.accept(measurement); verify(measurement) @@ -237,7 +227,12 @@ void callback_SkipRecord() { when(nonHeapUsage.getMax()).thenReturn(-1L); Consumer callback = - MemoryPools.callback(beans, MemoryPoolMXBean::getUsage, MemoryUsage::getMax); + MemoryPools.callback( + stringKey("pool"), + stringKey("type"), + beans, + MemoryPoolMXBean::getUsage, + MemoryUsage::getMax); callback.accept(measurement); verify(measurement) @@ -251,7 +246,9 @@ void callback_NullUsage() { Consumer callback = MemoryPools.callback( - Collections.singletonList(heapPoolBean), + stringKey("pool"), + stringKey("type"), + singletonList(heapPoolBean), MemoryPoolMXBean::getCollectionUsage, MemoryUsage::getUsed); callback.accept(measurement); diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/testStableSemconv/java/io/opentelemetry/instrumentation/runtimemetrics/java8/CpuStableSemconvTest.java b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/testStableSemconv/java/io/opentelemetry/instrumentation/runtimemetrics/java8/CpuStableSemconvTest.java new file mode 100644 index 000000000000..3721742202d6 --- /dev/null +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/testStableSemconv/java/io/opentelemetry/instrumentation/runtimemetrics/java8/CpuStableSemconvTest.java @@ -0,0 +1,83 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.runtimemetrics.java8; + +import static io.opentelemetry.instrumentation.runtimemetrics.java8.ScopeUtil.EXPECTED_SCOPE; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; + +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import java.util.concurrent.TimeUnit; +import java.util.function.IntSupplier; +import java.util.function.Supplier; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +class CpuStableSemconvTest { + + @RegisterExtension + static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + @Test + void registerObservers() { + IntSupplier availableProcessors = () -> 8; + Supplier processCpuTime = () -> TimeUnit.SECONDS.toNanos(42); + Supplier processCpuUtilization = () -> 0.05; + + Cpu.INSTANCE.registerObservers( + testing.getOpenTelemetry(), + null, + availableProcessors, + processCpuTime, + () -> null, + processCpuUtilization); + + testing.waitAndAssertMetrics( + "io.opentelemetry.runtime-telemetry-java8", + "jvm.cpu.time", + metrics -> + metrics.anySatisfy( + metricData -> + assertThat(metricData) + .hasInstrumentationScope(EXPECTED_SCOPE) + .hasDescription("CPU time used by the process as reported by the JVM.") + .hasUnit("s") + .hasDoubleSumSatisfying( + count -> + count + .isMonotonic() + .hasPointsSatisfying(point -> point.hasValue(42))))); + testing.waitAndAssertMetrics( + "io.opentelemetry.runtime-telemetry-java8", + "jvm.cpu.count", + metrics -> + metrics.anySatisfy( + metricData -> + assertThat(metricData) + .hasInstrumentationScope(EXPECTED_SCOPE) + .hasDescription( + "Number of processors available to the Java virtual machine.") + .hasUnit("{cpu}") + .hasLongSumSatisfying( + count -> + count + .isNotMonotonic() + .hasPointsSatisfying(point -> point.hasValue(8))))); + testing.waitAndAssertMetrics( + "io.opentelemetry.runtime-telemetry-java8", + "jvm.cpu.recent_utilization", + metrics -> + metrics.anySatisfy( + metricData -> + assertThat(metricData) + .hasInstrumentationScope(EXPECTED_SCOPE) + .hasDescription( + "Recent CPU utilization for the process as reported by the JVM.") + .hasUnit("1") + .hasDoubleGaugeSatisfying( + gauge -> gauge.hasPointsSatisfying(point -> point.hasValue(0.05))))); + } +} diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/testStableSemconv/java/io/opentelemetry/instrumentation/runtimemetrics/java8/MemoryPoolsStableSemconvTest.java b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/testStableSemconv/java/io/opentelemetry/instrumentation/runtimemetrics/java8/MemoryPoolsStableSemconvTest.java new file mode 100644 index 000000000000..391bdb7f10c9 --- /dev/null +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/testStableSemconv/java/io/opentelemetry/instrumentation/runtimemetrics/java8/MemoryPoolsStableSemconvTest.java @@ -0,0 +1,258 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.runtimemetrics.java8; + +import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.instrumentation.runtimemetrics.java8.ScopeUtil.EXPECTED_SCOPE; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static java.util.Collections.singletonList; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.ObservableLongMeasurement; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +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.junit.jupiter.api.extension.RegisterExtension; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +class MemoryPoolsStableSemconvTest { + + @RegisterExtension + static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + @Spy private ObservableLongMeasurement measurement; + + @Mock private MemoryPoolMXBean heapPoolBean; + @Mock private MemoryPoolMXBean nonHeapPoolBean; + + @Mock private MemoryUsage heapPoolUsage; + @Mock private MemoryUsage nonHeapUsage; + @Mock private MemoryUsage heapCollectionUsage; + @Mock private MemoryUsage nonHeapCollectionUsage; + + private List beans; + + @BeforeEach + void setup() { + when(heapPoolBean.getName()).thenReturn("heap_pool"); + when(heapPoolBean.getType()).thenReturn(MemoryType.HEAP); + when(heapPoolBean.getUsage()).thenReturn(heapPoolUsage); + when(heapPoolBean.getCollectionUsage()).thenReturn(heapCollectionUsage); + when(nonHeapPoolBean.getName()).thenReturn("non_heap_pool"); + when(nonHeapPoolBean.getType()).thenReturn(MemoryType.NON_HEAP); + when(nonHeapPoolBean.getUsage()).thenReturn(nonHeapUsage); + when(nonHeapPoolBean.getCollectionUsage()).thenReturn(nonHeapCollectionUsage); + beans = Arrays.asList(heapPoolBean, nonHeapPoolBean); + } + + @Test + void registerObservers() { + when(heapPoolUsage.getUsed()).thenReturn(11L); + when(heapPoolUsage.getCommitted()).thenReturn(12L); + when(heapPoolUsage.getMax()).thenReturn(13L); + when(nonHeapUsage.getUsed()).thenReturn(15L); + when(nonHeapUsage.getCommitted()).thenReturn(16L); + when(nonHeapUsage.getMax()).thenReturn(17L); + when(heapCollectionUsage.getUsed()).thenReturn(18L); + when(nonHeapCollectionUsage.getUsed()).thenReturn(19L); + MemoryPools.registerObservers(testing.getOpenTelemetry(), beans); + + testing.waitAndAssertMetrics( + "io.opentelemetry.runtime-telemetry-java8", + "jvm.memory.used", + metrics -> + metrics.anySatisfy( + metricData -> + assertThat(metricData) + .hasInstrumentationScope(EXPECTED_SCOPE) + .hasDescription("Measure of memory used.") + .hasUnit("By") + .hasLongSumSatisfying( + sum -> + sum.hasPointsSatisfying( + point -> + point + .hasValue(11) + .hasAttribute( + stringKey("jvm.memory.pool.name"), "heap_pool") + .hasAttribute(stringKey("jvm.memory.type"), "heap"), + point -> + point + .hasValue(15) + .hasAttribute( + stringKey("jvm.memory.pool.name"), "non_heap_pool") + .hasAttribute( + stringKey("jvm.memory.type"), "non_heap"))))); + testing.waitAndAssertMetrics( + "io.opentelemetry.runtime-telemetry-java8", + "jvm.memory.committed", + metrics -> + metrics.anySatisfy( + metricData -> + assertThat(metricData) + .hasInstrumentationScope(EXPECTED_SCOPE) + .hasDescription("Measure of memory committed.") + .hasUnit("By") + .hasLongSumSatisfying( + sum -> + sum.hasPointsSatisfying( + point -> + point + .hasValue(12) + .hasAttribute( + stringKey("jvm.memory.pool.name"), "heap_pool") + .hasAttribute(stringKey("jvm.memory.type"), "heap"), + point -> + point + .hasValue(16) + .hasAttribute( + stringKey("jvm.memory.pool.name"), "non_heap_pool") + .hasAttribute( + stringKey("jvm.memory.type"), "non_heap"))))); + testing.waitAndAssertMetrics( + "io.opentelemetry.runtime-telemetry-java8", + "jvm.memory.limit", + metrics -> + metrics.anySatisfy( + metricData -> + assertThat(metricData) + .hasInstrumentationScope(EXPECTED_SCOPE) + .hasDescription("Measure of max obtainable memory.") + .hasUnit("By") + .hasLongSumSatisfying( + sum -> + sum.hasPointsSatisfying( + point -> + point + .hasValue(13) + .hasAttribute( + stringKey("jvm.memory.pool.name"), "heap_pool") + .hasAttribute(stringKey("jvm.memory.type"), "heap"), + point -> + point + .hasValue(17) + .hasAttribute( + stringKey("jvm.memory.pool.name"), "non_heap_pool") + .hasAttribute( + stringKey("jvm.memory.type"), "non_heap"))))); + testing.waitAndAssertMetrics( + "io.opentelemetry.runtime-telemetry-java8", + "jvm.memory.used_after_last_gc", + metrics -> + metrics.anySatisfy( + metricData -> + assertThat(metricData) + .hasInstrumentationScope(EXPECTED_SCOPE) + .hasDescription( + "Measure of memory used, as measured after the most recent garbage collection event on this pool.") + .hasUnit("By") + .hasLongSumSatisfying( + sum -> + sum.hasPointsSatisfying( + point -> + point + .hasValue(18) + .hasAttribute( + stringKey("jvm.memory.pool.name"), "heap_pool") + .hasAttribute(stringKey("jvm.memory.type"), "heap"), + point -> + point + .hasValue(19) + .hasAttribute( + stringKey("jvm.memory.pool.name"), "non_heap_pool") + .hasAttribute( + stringKey("jvm.memory.type"), "non_heap"))))); + } + + @Test + void callback_Records() { + when(heapPoolUsage.getUsed()).thenReturn(1L); + when(nonHeapUsage.getUsed()).thenReturn(2L); + + Consumer callback = + MemoryPools.callback( + stringKey("jvm.memory.pool.name"), + stringKey("jvm.memory.type"), + beans, + MemoryPoolMXBean::getUsage, + MemoryUsage::getUsed); + callback.accept(measurement); + + verify(measurement) + .record( + 1, + Attributes.builder() + .put("jvm.memory.pool.name", "heap_pool") + .put("jvm.memory.type", "heap") + .build()); + verify(measurement) + .record( + 2, + Attributes.builder() + .put("jvm.memory.pool.name", "non_heap_pool") + .put("jvm.memory.type", "non_heap") + .build()); + } + + @Test + void callback_SkipRecord() { + when(heapPoolUsage.getMax()).thenReturn(1L); + when(nonHeapUsage.getMax()).thenReturn(-1L); + + Consumer callback = + MemoryPools.callback( + stringKey("jvm.memory.pool.name"), + stringKey("jvm.memory.type"), + beans, + MemoryPoolMXBean::getUsage, + MemoryUsage::getMax); + callback.accept(measurement); + + verify(measurement) + .record( + 1, + Attributes.builder() + .put("jvm.memory.pool.name", "heap_pool") + .put("jvm.memory.type", "heap") + .build()); + verify(measurement, never()).record(eq(-1), any()); + } + + @Test + void callback_NullUsage() { + when(heapPoolBean.getCollectionUsage()).thenReturn(null); + + Consumer callback = + MemoryPools.callback( + stringKey("jvm.memory.pool.name"), + stringKey("jvm.memory.type"), + singletonList(heapPoolBean), + MemoryPoolMXBean::getCollectionUsage, + MemoryUsage::getUsed); + callback.accept(measurement); + verify(measurement, never()).record(anyLong(), any()); + } +} diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/testStableSemconv/java/io/opentelemetry/instrumentation/runtimemetrics/java8/ScopeUtil.java b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/testStableSemconv/java/io/opentelemetry/instrumentation/runtimemetrics/java8/ScopeUtil.java index d865e067df5a..3769d2a4ce11 100644 --- a/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/testStableSemconv/java/io/opentelemetry/instrumentation/runtimemetrics/java8/ScopeUtil.java +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/testStableSemconv/java/io/opentelemetry/instrumentation/runtimemetrics/java8/ScopeUtil.java @@ -10,14 +10,15 @@ import io.opentelemetry.sdk.common.InstrumentationScopeInfoBuilder; import javax.annotation.Nullable; -class ScopeUtil { +public final class ScopeUtil { + private static final String INSTRUMENTATION_NAME = "io.opentelemetry.runtime-telemetry-java8"; @Nullable private static final String INSTRUMENTATION_VERSION = EmbeddedInstrumentationProperties.findVersion(INSTRUMENTATION_NAME); - static final InstrumentationScopeInfo EXPECTED_SCOPE; + public static final InstrumentationScopeInfo EXPECTED_SCOPE; static { InstrumentationScopeInfoBuilder builder = diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/testStableSemconv/java/io/opentelemetry/instrumentation/runtimemetrics/java8/internal/ExperimentalBufferPoolsTest.java b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/testStableSemconv/java/io/opentelemetry/instrumentation/runtimemetrics/java8/internal/ExperimentalBufferPoolsTest.java new file mode 100644 index 000000000000..346c784c6f8b --- /dev/null +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/testStableSemconv/java/io/opentelemetry/instrumentation/runtimemetrics/java8/internal/ExperimentalBufferPoolsTest.java @@ -0,0 +1,134 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.runtimemetrics.java8.internal; + +import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.instrumentation.runtimemetrics.java8.ScopeUtil.EXPECTED_SCOPE; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static java.util.Collections.singletonList; +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.when; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.ObservableLongMeasurement; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import java.lang.management.BufferPoolMXBean; +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.junit.jupiter.api.extension.RegisterExtension; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class ExperimentalBufferPoolsTest { + + @RegisterExtension + static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + @Spy private ObservableLongMeasurement measurement; + @Mock private BufferPoolMXBean bufferPoolBean; + private List beans; + + @BeforeEach + void setup() { + when(bufferPoolBean.getName()).thenReturn("buffer_pool_1"); + beans = singletonList(bufferPoolBean); + } + + @Test + void registerObservers() { + when(bufferPoolBean.getMemoryUsed()).thenReturn(10L); + when(bufferPoolBean.getTotalCapacity()).thenReturn(11L); + when(bufferPoolBean.getCount()).thenReturn(12L); + + ExperimentalBufferPools.registerObservers(testing.getOpenTelemetry(), beans); + + testing.waitAndAssertMetrics( + "io.opentelemetry.runtime-telemetry-java8", + "jvm.buffer.memory.usage", + metrics -> + metrics.anySatisfy( + metricData -> + assertThat(metricData) + .hasInstrumentationScope(EXPECTED_SCOPE) + .hasDescription("Measure of memory used by buffers.") + .hasUnit("By") + .hasLongSumSatisfying( + sum -> + sum.hasPointsSatisfying( + point -> + point + .hasValue(10) + .hasAttribute( + stringKey("jvm.buffer.pool.name"), + "buffer_pool_1"))))); + testing.waitAndAssertMetrics( + "io.opentelemetry.runtime-telemetry-java8", + "jvm.buffer.memory.limit", + metrics -> + metrics.anySatisfy( + metricData -> + assertThat(metricData) + .hasInstrumentationScope(EXPECTED_SCOPE) + .hasDescription("Measure of total memory capacity of buffers.") + .hasUnit("By") + .hasLongSumSatisfying( + sum -> + sum.hasPointsSatisfying( + point -> + point + .hasValue(11) + .hasAttribute( + stringKey("jvm.buffer.pool.name"), + "buffer_pool_1"))))); + testing.waitAndAssertMetrics( + "io.opentelemetry.runtime-telemetry-java8", + "jvm.buffer.count", + metrics -> + metrics.anySatisfy( + metricData -> + assertThat(metricData) + .hasInstrumentationScope(EXPECTED_SCOPE) + .hasDescription("Number of buffers in the pool.") + .hasUnit("{buffer}") + .hasLongSumSatisfying( + sum -> + sum.hasPointsSatisfying( + point -> + point + .hasValue(12) + .hasAttribute( + stringKey("jvm.buffer.pool.name"), + "buffer_pool_1"))))); + } + + @Test + void callback_Records() { + when(bufferPoolBean.getMemoryUsed()).thenReturn(1L); + Consumer callback = + ExperimentalBufferPools.callback(beans, BufferPoolMXBean::getMemoryUsed); + callback.accept(measurement); + verify(measurement) + .record(1, Attributes.builder().put("jvm.buffer.pool.name", "buffer_pool_1").build()); + } + + @Test + void callback_SkipRecord() { + when(bufferPoolBean.getMemoryUsed()).thenReturn(-1L); + Consumer callback = + ExperimentalBufferPools.callback(beans, BufferPoolMXBean::getMemoryUsed); + callback.accept(measurement); + verify(measurement, never()).record(eq(-1), any()); + } +} diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/testStableSemconv/java/io/opentelemetry/instrumentation/runtimemetrics/java8/internal/ExperimentalCpuTest.java b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/testStableSemconv/java/io/opentelemetry/instrumentation/runtimemetrics/java8/internal/ExperimentalCpuTest.java new file mode 100644 index 000000000000..be537e020cdb --- /dev/null +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/testStableSemconv/java/io/opentelemetry/instrumentation/runtimemetrics/java8/internal/ExperimentalCpuTest.java @@ -0,0 +1,64 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.runtimemetrics.java8.internal; + +import static io.opentelemetry.instrumentation.runtimemetrics.java8.ScopeUtil.EXPECTED_SCOPE; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static org.mockito.Mockito.when; + +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import java.lang.management.OperatingSystemMXBean; +import java.util.function.Supplier; +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 ExperimentalCpuTest { + + @RegisterExtension + static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + @Mock private OperatingSystemMXBean osBean; + + @Test + void registerObservers() { + when(osBean.getSystemLoadAverage()).thenReturn(2.2); + Supplier systemCpuUtilization = () -> 0.11; + + ExperimentalCpu.registerObservers(testing.getOpenTelemetry(), osBean, systemCpuUtilization); + + testing.waitAndAssertMetrics( + "io.opentelemetry.runtime-telemetry-java8", + "jvm.system.cpu.load_1m", + metrics -> + metrics.anySatisfy( + metricData -> + assertThat(metricData) + .hasInstrumentationScope(EXPECTED_SCOPE) + .hasDescription( + "Average CPU load of the whole system for the last minute as reported by the JVM.") + .hasUnit("{run_queue_item}") + .hasDoubleGaugeSatisfying( + gauge -> gauge.hasPointsSatisfying(point -> point.hasValue(2.2))))); + testing.waitAndAssertMetrics( + "io.opentelemetry.runtime-telemetry-java8", + "jvm.system.cpu.utilization", + metrics -> + metrics.anySatisfy( + metricData -> + assertThat(metricData) + .hasInstrumentationScope(EXPECTED_SCOPE) + .hasDescription( + "Recent CPU utilization for the whole system as reported by the JVM.") + .hasUnit("1") + .hasDoubleGaugeSatisfying( + gauge -> gauge.hasPointsSatisfying(point -> point.hasValue(0.11))))); + } +} diff --git a/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/testStableSemconv/java/io/opentelemetry/instrumentation/runtimemetrics/java8/internal/ExperimentalMemoryPoolsTest.java b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/testStableSemconv/java/io/opentelemetry/instrumentation/runtimemetrics/java8/internal/ExperimentalMemoryPoolsTest.java new file mode 100644 index 000000000000..80e3990d8973 --- /dev/null +++ b/instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/testStableSemconv/java/io/opentelemetry/instrumentation/runtimemetrics/java8/internal/ExperimentalMemoryPoolsTest.java @@ -0,0 +1,88 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.runtimemetrics.java8.internal; + +import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.instrumentation.runtimemetrics.java8.ScopeUtil.EXPECTED_SCOPE; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static org.mockito.Mockito.when; + +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import java.lang.management.MemoryPoolMXBean; +import java.lang.management.MemoryType; +import java.lang.management.MemoryUsage; +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +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; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +class ExperimentalMemoryPoolsTest { + + @RegisterExtension + static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + @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 registerObservers() { + when(heapPoolUsage.getInit()).thenReturn(11L); + when(nonHeapUsage.getInit()).thenReturn(15L); + ExperimentalMemoryPools.registerObservers(testing.getOpenTelemetry(), beans); + + testing.waitAndAssertMetrics( + "io.opentelemetry.runtime-telemetry-java8", + "jvm.memory.init", + metrics -> + metrics.anySatisfy( + metricData -> + assertThat(metricData) + .hasInstrumentationScope(EXPECTED_SCOPE) + .hasDescription("Measure of initial memory requested.") + .hasUnit("By") + .hasLongSumSatisfying( + sum -> + sum.hasPointsSatisfying( + point -> + point + .hasValue(11) + .hasAttribute( + stringKey("jvm.memory.pool.name"), "heap_pool") + .hasAttribute(stringKey("jvm.memory.type"), "heap"), + point -> + point + .hasValue(15) + .hasAttribute( + stringKey("jvm.memory.pool.name"), "non_heap_pool") + .hasAttribute( + stringKey("jvm.memory.type"), "non_heap"))))); + } +}