Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add jvm cpu metrics #6107

Merged
merged 3 commits into from
Jun 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.instrumentation.api.config.Config;
import io.opentelemetry.instrumentation.runtimemetrics.Classes;
import io.opentelemetry.instrumentation.runtimemetrics.Cpu;
import io.opentelemetry.instrumentation.runtimemetrics.GarbageCollector;
import io.opentelemetry.instrumentation.runtimemetrics.MemoryPools;
import io.opentelemetry.instrumentation.runtimemetrics.Threads;
Expand All @@ -30,6 +31,7 @@ public void afterAgent(Config config, AutoConfiguredOpenTelemetrySdk unused) {
.isInstrumentationEnabled(Collections.singleton("runtime-metrics"), DEFAULT_ENABLED)) {

Classes.registerObservers(GlobalOpenTelemetry.get());
Cpu.registerObservers(GlobalOpenTelemetry.get());
jack-berg marked this conversation as resolved.
Show resolved Hide resolved
MemoryPools.registerObservers(GlobalOpenTelemetry.get());
Threads.registerObservers(GlobalOpenTelemetry.get());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ class RuntimeMetricsTest extends AgentInstrumentationSpecification {
assert getMetrics().any { it.name == "process.runtime.jvm.classes.loaded" }
assert getMetrics().any { it.name == "process.runtime.jvm.classes.unloaded" }
assert getMetrics().any { it.name == "process.runtime.jvm.classes.current_loaded" }
assert getMetrics().any { it.name == "process.runtime.jvm.system.cpu.load_1m" }
assert getMetrics().any { it.name == "process.runtime.jvm.system.cpu.utilization" }
assert getMetrics().any { it.name == "process.runtime.jvm.cpu.utilization" }
assert getMetrics().any { it.name == "runtime.jvm.gc.time" }
assert getMetrics().any { it.name == "runtime.jvm.gc.count" }
assert getMetrics().any { it.name == "process.runtime.jvm.memory.init" }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.runtimemetrics;

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.metrics.Meter;
import java.lang.management.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;

/**
* Registers measurements that generate metrics about CPU.
*
* <p>Example usage:
*
* <pre>{@code
* Cpu.registerObservers(GlobalOpenTelemetry.get());
* }</pre>
*
* <p>Example metrics being exported:
*
* <pre>
* process.runtime.jvm.system.cpu.load_1m 2.2
* process.runtime.jvm.system.cpu.utilization 0.15
* process.runtime.jvm.cpu.utilization 0.1
* </pre>
*/
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<Double> processCpu;
@Nullable private static final Supplier<Double> systemCpu;

static {
OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean();
Supplier<Double> 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<Double> 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;
}

/** Register observers for java runtime class metrics. */
public static void registerObservers(OpenTelemetry openTelemetry) {
INSTANCE.registerObservers(
openTelemetry, ManagementFactory.getOperatingSystemMXBean(), systemCpu, processCpu);
}

// Visible for testing
void registerObservers(
OpenTelemetry openTelemetry,
OperatingSystemMXBean osBean,
@Nullable Supplier<Double> systemCpuUsage,
@Nullable Supplier<Double> processCpuUsage) {
Meter meter = openTelemetry.getMeter("io.opentelemetry.runtime-metrics");

meter
.gaugeBuilder("process.runtime.jvm.system.cpu.load_1m")
.setDescription("Average CPU load of the whole system for the last minute")
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This definition from the semantic convention doesn't really match the the description from the OperatingSystemMXBean. It's a pretty odd calculation as it isn't bounded to the range [0,1], [0, 100] or even to [0, 100*N] where N is the number of processors.

This stack overflow post describes it as the "average is the number of waiting threads", and says the upper limit is equal to the number of threads you have.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this the same as unix load average that is displayed for example by uptime?

.setUnit("1")
.buildWithCallback(
observableMeasurement -> {
double loadAverage = osBean.getSystemLoadAverage();
if (loadAverage >= 0) {
observableMeasurement.record(loadAverage);
}
});

if (systemCpuUsage != null) {
meter
.gaugeBuilder("process.runtime.jvm.system.cpu.utilization")
.setDescription("Recent cpu utilization for the whole system")
.setUnit("1")
.buildWithCallback(
observableMeasurement -> {
Double cpuUsage = systemCpuUsage.get();
if (cpuUsage != null && cpuUsage >= 0) {
observableMeasurement.record(cpuUsage);
}
});
}

if (processCpuUsage != null) {
meter
.gaugeBuilder("process.runtime.jvm.cpu.utilization")
.setDescription("Recent cpu utilization for the process")
.setUnit("1")
.buildWithCallback(
observableMeasurement -> {
Double cpuUsage = processCpuUsage.get();
if (cpuUsage != null && cpuUsage >= 0) {
observableMeasurement.record(cpuUsage);
}
});
}
}

@Nullable
@SuppressWarnings("ReturnValueIgnored")
private static Supplier<Double> 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() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* <p>Example usage:
*
* <pre>{@code
* Classes.registerObservers(GlobalOpenTelemetry.get());
* Threads.registerObservers(GlobalOpenTelemetry.get());
* }</pre>
*
* <p>Example metrics being exported:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.runtimemetrics;

import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
import static org.mockito.Mockito.when;

import io.opentelemetry.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 CpuTest {

@RegisterExtension
static final InstrumentationExtension testing = LibraryInstrumentationExtension.create();

@Mock private OperatingSystemMXBean osBean;

@Test
void registerObservers() {
when(osBean.getSystemLoadAverage()).thenReturn(2.2);
Supplier<Double> systemCpuUsage = () -> 0.11;
Supplier<Double> processCpuUsage = () -> 0.05;

Cpu.INSTANCE.registerObservers(
testing.getOpenTelemetry(), osBean, systemCpuUsage, processCpuUsage);

testing.waitAndAssertMetrics(
"io.opentelemetry.runtime-metrics",
"process.runtime.jvm.system.cpu.load_1m",
metrics ->
metrics.anySatisfy(
metricData ->
assertThat(metricData)
.hasDescription("Average CPU load of the whole system for the last minute")
.hasUnit("1")
.hasDoubleGaugeSatisfying(
gauge -> gauge.hasPointsSatisfying(point -> point.hasValue(2.2)))));
testing.waitAndAssertMetrics(
"io.opentelemetry.runtime-metrics",
"process.runtime.jvm.system.cpu.utilization",
metrics ->
metrics.anySatisfy(
metricData ->
assertThat(metricData)
.hasDescription("Recent cpu utilization for the whole system")
.hasUnit("1")
.hasDoubleGaugeSatisfying(
gauge -> gauge.hasPointsSatisfying(point -> point.hasValue(0.11)))));
testing.waitAndAssertMetrics(
"io.opentelemetry.runtime-metrics",
"process.runtime.jvm.cpu.utilization",
metrics ->
metrics.anySatisfy(
metricData ->
assertThat(metricData)
.hasDescription("Recent cpu utilization for the process")
.hasUnit("1")
.hasDoubleGaugeSatisfying(
gauge -> gauge.hasPointsSatisfying(point -> point.hasValue(0.05)))));
}
}