Add system metrics
ADD
instrumentation/oshi/library/oshi-library.gradle
+apply from: "$rootDir/gradle/instrumentation-library.gradle"
+
+
+dependencies {
+ library group: 'com.github.oshi', name: 'oshi-core', version: '5.3.1'
+
+ testImplementation project(':testing-common')
+ testImplementation group: 'org.assertj', name: 'assertj-core', version: '1.7.1'
+}
\ No newline at end of file

ADD
instrumentation/oshi/library/src/main/java/io/opentelemetry/instrumentation/oshi/ProcessMetrics.java
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.oshi;
+
+import io.opentelemetry.OpenTelemetry;
+import io.opentelemetry.common.Labels;
+import io.opentelemetry.metrics.AsynchronousInstrument.Callback;
+import io.opentelemetry.metrics.AsynchronousInstrument.DoubleResult;
+import io.opentelemetry.metrics.AsynchronousInstrument.LongResult;
+import io.opentelemetry.metrics.Meter;
+import oshi.SystemInfo;
+import oshi.software.os.OSProcess;
+import oshi.software.os.OperatingSystem;
+
+/** Java Runtime Metrics Utility */
+public class ProcessMetrics {
+ private static final String TYPE_LABEL_KEY = "type";
+
+ private ProcessMetrics() {}
+
+ /** Register observers for java runtime metrics */
+ public static void registerObservers() {
+ Meter meter = OpenTelemetry.getGlobalMeterProvider().get(ProcessMetrics.class.getName());
+ SystemInfo systemInfo = new SystemInfo();
+ OperatingSystem osInfo = systemInfo.getOperatingSystem();
+ OSProcess processInfo = osInfo.getProcess(osInfo.getProcessId());
+
+ meter
+ .longUpDownSumObserverBuilder("runtime.java.memory")
+ .setDescription("Runtime Java memory")
+ .setUnit("bytes")
+ .build()
+ .setCallback(
+ new Callback<LongResult>() {
+ @Override
+ public void update(LongResult r) {
+ processInfo.updateAttributes();
+ r.observe(processInfo.getResidentSetSize(), Labels.of(TYPE_LABEL_KEY, "rss"));
+ r.observe(processInfo.getVirtualSize(), Labels.of(TYPE_LABEL_KEY, "vms"));
+ }
+ });
+
+ meter
+ .doubleValueObserverBuilder("runtime.java.cpu_time")
+ .setDescription("Runtime Java CPU time")
+ .setUnit("seconds")
+ .build()
+ .setCallback(
+ new Callback<DoubleResult>() {
+ @Override
+ public void update(DoubleResult r) {
+ processInfo.updateAttributes();
+ r.observe(processInfo.getUserTime() * 1000, Labels.of(TYPE_LABEL_KEY, "user"));
+ r.observe(processInfo.getKernelTime() * 1000, Labels.of(TYPE_LABEL_KEY, "system"));
+ }
+ });
+ }
+}

ADD
instrumentation/oshi/library/src/main/java/io/opentelemetry/instrumentation/oshi/SystemMetrics.java
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.oshi;
+
+import io.opentelemetry.OpenTelemetry;
+import io.opentelemetry.common.Labels;
+import io.opentelemetry.metrics.AsynchronousInstrument.Callback;
+import io.opentelemetry.metrics.AsynchronousInstrument.DoubleResult;
+import io.opentelemetry.metrics.AsynchronousInstrument.LongResult;
+import io.opentelemetry.metrics.Meter;
+import oshi.SystemInfo;
+import oshi.hardware.GlobalMemory;
+import oshi.hardware.HWDiskStore;
+import oshi.hardware.HardwareAbstractionLayer;
+import oshi.hardware.NetworkIF;
+
+/** System Metrics Utility */
+public class SystemMetrics {
+ private static final String TYPE_LABEL_KEY = "type";
+
+ private SystemMetrics() {}
+
+ /** Register observers for system metrics */
+ public static void registerObservers() {
+ Meter meter = OpenTelemetry.getGlobalMeterProvider().get(SystemMetrics.class.getName());
+ SystemInfo systemInfo = new SystemInfo();
+ HardwareAbstractionLayer hal = systemInfo.getHardware();
+
+ meter
+ .longUpDownSumObserverBuilder("system.memory.usage")
+ .setDescription("System memory usage")
+ .setUnit("bytes")
+ .build()
+ .setCallback(
+ new Callback<LongResult>() {
+ @Override
+ public void update(LongResult r) {
+ GlobalMemory mem = hal.getMemory();
+ r.observe(mem.getTotal() - mem.getAvailable(), Labels.of(TYPE_LABEL_KEY, "used"));
+ r.observe(mem.getAvailable(), Labels.of(TYPE_LABEL_KEY, "free"));
+ }
+ });
+
+ meter
+ .doubleValueObserverBuilder("system.memory.utilization")
+ .setDescription("System memory utilization")
+ .setUnit("1")
+ .build()
+ .setCallback(
+ new Callback<DoubleResult>() {
+ @Override
+ public void update(DoubleResult r) {
+ GlobalMemory mem = hal.getMemory();
+ r.observe(
+ ((double) (mem.getTotal() - mem.getAvailable())) / mem.getTotal(),
+ Labels.of(TYPE_LABEL_KEY, "used"));
+ r.observe(
+ ((double) mem.getAvailable()) / mem.getTotal(),
+ Labels.of(TYPE_LABEL_KEY, "free"));
+ }
+ });
+
+ meter
+ .longSumObserverBuilder("system.network.io")
+ .setDescription("System network IO")
+ .setUnit("bytes")
+ .build()
+ .setCallback(
+ new Callback<LongResult>() {
+ @Override
+ public void update(LongResult r) {
+ long recv = 0;
+ long sent = 0;
+
+ for (NetworkIF networkIf : hal.getNetworkIFs()) {
+ networkIf.updateAttributes();
+ recv += networkIf.getBytesRecv();
+ sent += networkIf.getBytesSent();
+ }
+
+ r.observe(recv, Labels.of(TYPE_LABEL_KEY, "receive"));
+ r.observe(sent, Labels.of(TYPE_LABEL_KEY, "transmit"));
+ }
+ });
+
+ meter
+ .longSumObserverBuilder("system.network.packets")
+ .setDescription("System network packets")
+ .setUnit("packets")
+ .build()
+ .setCallback(
+ new Callback<LongResult>() {
+ @Override
+ public void update(LongResult r) {
+ long recv = 0;
+ long sent = 0;
+
+ for (NetworkIF networkIf : hal.getNetworkIFs()) {
+ networkIf.updateAttributes();
+ recv += networkIf.getPacketsRecv();
+ sent += networkIf.getPacketsSent();
+ }
+
+ r.observe(recv, Labels.of(TYPE_LABEL_KEY, "receive"));
+ r.observe(sent, Labels.of(TYPE_LABEL_KEY, "transmit"));
+ }
+ });
+
+ meter
+ .longSumObserverBuilder("system.network.errors")
+ .setDescription("System network errors")
+ .setUnit("errors")
+ .build()
+ .setCallback(
+ new Callback<LongResult>() {
+ @Override
+ public void update(LongResult r) {
+ long recv = 0;
+ long sent = 0;
+
+ for (NetworkIF networkIf : hal.getNetworkIFs()) {
+ networkIf.updateAttributes();
+ recv += networkIf.getInErrors();
+ sent += networkIf.getOutErrors();
+ }
+
+ r.observe(recv, Labels.of(TYPE_LABEL_KEY, "receive"));
+ r.observe(sent, Labels.of(TYPE_LABEL_KEY, "transmit"));
+ }
+ });
+
+ meter
+ .longSumObserverBuilder("system.disk.io")
+ .setDescription("System disk IO")
+ .setUnit("bytes")
+ .build()
+ .setCallback(
+ new Callback<LongResult>() {
+ @Override
+ public void update(LongResult r) {
+ long read = 0;
+ long write = 0;
+
+ for (HWDiskStore diskStore : hal.getDiskStores()) {
+ read += diskStore.getReadBytes();
+ write += diskStore.getWriteBytes();
+ }
+
+ r.observe(read, Labels.of(TYPE_LABEL_KEY, "read"));
+ r.observe(write, Labels.of(TYPE_LABEL_KEY, "write"));
+ }
+ });
+
+ meter
+ .longSumObserverBuilder("system.disk.operations")
+ .setDescription("System disk operations")
+ .setUnit("operations")
+ .build()
+ .setCallback(
+ new Callback<LongResult>() {
+ @Override
+ public void update(LongResult r) {
+ long read = 0;
+ long write = 0;
+
+ for (HWDiskStore diskStore : hal.getDiskStores()) {
+ read += diskStore.getReads();
+ write += diskStore.getWrites();
+ }
+
+ r.observe(read, Labels.of(TYPE_LABEL_KEY, "read"));
+ r.observe(write, Labels.of(TYPE_LABEL_KEY, "write"));
+ }
+ });
+ }
+}

ADD
instrumentation/oshi/library/src/test/java/io/opentelemetry/instrumentation/oshi/AbstractMetricsTest.java
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.oshi;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import io.opentelemetry.sdk.OpenTelemetrySdk;
+import io.opentelemetry.sdk.common.CompletableResultCode;
+import io.opentelemetry.sdk.metrics.data.MetricData;
+import io.opentelemetry.sdk.metrics.data.MetricData.DoublePoint;
+import io.opentelemetry.sdk.metrics.data.MetricData.LongPoint;
+import io.opentelemetry.sdk.metrics.data.MetricData.Point;
+import io.opentelemetry.sdk.metrics.data.MetricData.SummaryPoint;
+import io.opentelemetry.sdk.metrics.data.MetricData.Type;
+import io.opentelemetry.sdk.metrics.export.IntervalMetricReader;
+import io.opentelemetry.sdk.metrics.export.MetricExporter;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+
+public class AbstractMetricsTest {
+ TestMetricExporter testMetricExporter;
+
+ @BeforeEach
+ public void beforeEach() {
+ testMetricExporter = new TestMetricExporter();
+ }
+
+ IntervalMetricReader createIntervalMetricReader() {
+ return IntervalMetricReader.builder()
+ .setExportIntervalMillis(100)
+ .setMetricExporter(testMetricExporter)
+ .setMetricProducers(
+ Collections.singletonList(
+ OpenTelemetrySdk.getGlobalMeterProvider().getMetricProducer()))
+ .build();
+ }
+
+ public void verify(
+ String metricName, String unit, MetricData.Type type, boolean checkNonZeroValue) {
+ List<MetricData> metricDataList = testMetricExporter.metricDataList;
+ for (MetricData metricData : metricDataList) {
+ if (metricData.getName().equals(metricName)) {
+ assertThat(metricData.getDescription()).isNotEmpty();
+ assertThat(metricData.getUnit()).isEqualTo(unit);
+ assertThat(metricData.getPoints()).isNotEmpty();
+ assertThat(metricData.getType()).isEqualTo(type);
+ if (checkNonZeroValue) {
+ for (Point point : metricData.getPoints()) {
+ if (metricData.getType() == Type.NON_MONOTONIC_LONG) {
+ LongPoint longPoint = (LongPoint) point;
+ assertThat(longPoint.getValue()).isGreaterThan(0);
+ } else if (metricData.getType() == Type.NON_MONOTONIC_DOUBLE) {
+ DoublePoint doublePoint = (DoublePoint) point;
+ assertThat(doublePoint.getValue()).isGreaterThan(0.0);
+ } else if (metricData.getType() == Type.SUMMARY) {
+ SummaryPoint summaryPoint = (SummaryPoint) point;
+ assertThat(summaryPoint.getSum()).isGreaterThan(0.0);
+ } else if (metricData.getType() == Type.GAUGE_DOUBLE) {
+ DoublePoint doublePoint = (DoublePoint) point;
+ assertThat(doublePoint.getValue()).isGreaterThan(0.0);
+ } else {
+ Assertions.fail("unexpected type " + metricData.getType());
+ }
+ }
+ }
+ return;
+ }
+ }
+ Assertions.fail("No metric for " + metricName);
+ }
+
+ static class TestMetricExporter implements MetricExporter {
+ private final List<MetricData> metricDataList = new CopyOnWriteArrayList<>();
+ private final CountDownLatch latch = new CountDownLatch(1);
+
+ @Override
+ public CompletableResultCode export(Collection<MetricData> collection) {
+ metricDataList.addAll(collection);
+ latch.countDown();
+ return CompletableResultCode.ofSuccess();
+ }
+
+ @Override
+ public CompletableResultCode flush() {
+ return CompletableResultCode.ofSuccess();
+ }
+
+ @Override
+ public void shutdown() {}
+
+ public void waitForData() throws InterruptedException {
+ latch.await(1, TimeUnit.SECONDS);
+ }
+ }
+}

ADD
instrumentation/oshi/library/src/test/java/io/opentelemetry/instrumentation/oshi/ProcessMetricsTest.java
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.oshi;
+
+import io.opentelemetry.sdk.metrics.data.MetricData.Type;
+import io.opentelemetry.sdk.metrics.export.IntervalMetricReader;
+import org.junit.jupiter.api.Test;
+
+public class ProcessMetricsTest extends AbstractMetricsTest {
+
+ @Test
+ public void test() throws Exception {
+ ProcessMetrics.registerObservers();
+ IntervalMetricReader intervalMetricReader = createIntervalMetricReader();
+
+ testMetricExporter.waitForData();
+ intervalMetricReader.shutdown();
+
+ verify("runtime.java.memory", "bytes", Type.NON_MONOTONIC_LONG, true);
+ verify("runtime.java.cpu_time", "seconds", Type.GAUGE_DOUBLE, true);
+ }
+}

ADD
instrumentation/oshi/library/src/test/java/io/opentelemetry/instrumentation/oshi/SystemMetricsTest.java
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.oshi;
+
+import io.opentelemetry.sdk.metrics.data.MetricData.Type;
+import io.opentelemetry.sdk.metrics.export.IntervalMetricReader;
+import org.junit.jupiter.api.Test;
+
+public class SystemMetricsTest extends AbstractMetricsTest {
+
+ @Test
+ public void test() throws Exception {
+ SystemMetrics.registerObservers();
+ IntervalMetricReader intervalMetricReader = createIntervalMetricReader();
+
+ testMetricExporter.waitForData();
+ intervalMetricReader.shutdown();
+
+ verify("system.memory.usage", "bytes", Type.NON_MONOTONIC_LONG, true);
+ verify("system.memory.utilization", "1", Type.GAUGE_DOUBLE, true);
+
+ verify("system.network.io", "bytes", Type.MONOTONIC_LONG, false);
+ verify("system.network.packets", "packets", Type.MONOTONIC_LONG, false);
+ verify("system.network.errors", "errors", Type.MONOTONIC_LONG, false);
+
+ verify("system.disk.io", "bytes", Type.MONOTONIC_LONG, false);
+ verify("system.disk.operations", "operations", Type.MONOTONIC_LONG, false);
+ }
+}

MODIFY
settings.gradle
settings.gradle
@@ -148,6 +148,7 @@ include ':instrumentation:netty:netty-4.1'
include ':instrumentation:okhttp:okhttp-2.2'
include ':instrumentation:okhttp:okhttp-3.0'
include ':instrumentation:opentelemetry-api-beta'
+include ':instrumentation:oshi:library'
include ':instrumentation:play:play-2.3'
include ':instrumentation:play:play-2.4'
include ':instrumentation:play:play-2.6'

Add system metrics (#1309)

Signed-off-by: Sergei Malafeev <sergei@malafeev.org>