From d2813c838d956b6db05f9ca975b8bac3c82dcc71 Mon Sep 17 00:00:00 2001 From: Sergei Malafeev Date: Tue, 27 Oct 2020 23:48:36 +0800 Subject: [PATCH] Add system metrics (#1309) Signed-off-by: Sergei Malafeev --- .../oshi/library/oshi-library.gradle | 9 + .../instrumentation/oshi/ProcessMetrics.java | 61 ++++++ .../instrumentation/oshi/SystemMetrics.java | 179 ++++++++++++++++++ .../oshi/AbstractMetricsTest.java | 104 ++++++++++ .../oshi/ProcessMetricsTest.java | 25 +++ .../oshi/SystemMetricsTest.java | 32 ++++ settings.gradle | 1 + 7 files changed, 411 insertions(+) create mode 100644 instrumentation/oshi/library/oshi-library.gradle create mode 100644 instrumentation/oshi/library/src/main/java/io/opentelemetry/instrumentation/oshi/ProcessMetrics.java create mode 100644 instrumentation/oshi/library/src/main/java/io/opentelemetry/instrumentation/oshi/SystemMetrics.java create mode 100644 instrumentation/oshi/library/src/test/java/io/opentelemetry/instrumentation/oshi/AbstractMetricsTest.java create mode 100644 instrumentation/oshi/library/src/test/java/io/opentelemetry/instrumentation/oshi/ProcessMetricsTest.java create mode 100644 instrumentation/oshi/library/src/test/java/io/opentelemetry/instrumentation/oshi/SystemMetricsTest.java diff --git a/instrumentation/oshi/library/oshi-library.gradle b/instrumentation/oshi/library/oshi-library.gradle new file mode 100644 index 0000000000..576c09aef4 --- /dev/null +++ b/instrumentation/oshi/library/oshi-library.gradle @@ -0,0 +1,9 @@ +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 diff --git a/instrumentation/oshi/library/src/main/java/io/opentelemetry/instrumentation/oshi/ProcessMetrics.java b/instrumentation/oshi/library/src/main/java/io/opentelemetry/instrumentation/oshi/ProcessMetrics.java new file mode 100644 index 0000000000..097f40282d --- /dev/null +++ b/instrumentation/oshi/library/src/main/java/io/opentelemetry/instrumentation/oshi/ProcessMetrics.java @@ -0,0 +1,61 @@ +/* + * 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() { + @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() { + @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")); + } + }); + } +} diff --git a/instrumentation/oshi/library/src/main/java/io/opentelemetry/instrumentation/oshi/SystemMetrics.java b/instrumentation/oshi/library/src/main/java/io/opentelemetry/instrumentation/oshi/SystemMetrics.java new file mode 100644 index 0000000000..3292d6f356 --- /dev/null +++ b/instrumentation/oshi/library/src/main/java/io/opentelemetry/instrumentation/oshi/SystemMetrics.java @@ -0,0 +1,179 @@ +/* + * 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() { + @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() { + @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() { + @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() { + @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() { + @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() { + @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() { + @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")); + } + }); + } +} diff --git a/instrumentation/oshi/library/src/test/java/io/opentelemetry/instrumentation/oshi/AbstractMetricsTest.java b/instrumentation/oshi/library/src/test/java/io/opentelemetry/instrumentation/oshi/AbstractMetricsTest.java new file mode 100644 index 0000000000..c628d11c05 --- /dev/null +++ b/instrumentation/oshi/library/src/test/java/io/opentelemetry/instrumentation/oshi/AbstractMetricsTest.java @@ -0,0 +1,104 @@ +/* + * 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 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 metricDataList = new CopyOnWriteArrayList<>(); + private final CountDownLatch latch = new CountDownLatch(1); + + @Override + public CompletableResultCode export(Collection 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); + } + } +} diff --git a/instrumentation/oshi/library/src/test/java/io/opentelemetry/instrumentation/oshi/ProcessMetricsTest.java b/instrumentation/oshi/library/src/test/java/io/opentelemetry/instrumentation/oshi/ProcessMetricsTest.java new file mode 100644 index 0000000000..a2c7725402 --- /dev/null +++ b/instrumentation/oshi/library/src/test/java/io/opentelemetry/instrumentation/oshi/ProcessMetricsTest.java @@ -0,0 +1,25 @@ +/* + * 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); + } +} diff --git a/instrumentation/oshi/library/src/test/java/io/opentelemetry/instrumentation/oshi/SystemMetricsTest.java b/instrumentation/oshi/library/src/test/java/io/opentelemetry/instrumentation/oshi/SystemMetricsTest.java new file mode 100644 index 0000000000..bf0ada1496 --- /dev/null +++ b/instrumentation/oshi/library/src/test/java/io/opentelemetry/instrumentation/oshi/SystemMetricsTest.java @@ -0,0 +1,32 @@ +/* + * 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); + } +} diff --git a/settings.gradle b/settings.gradle index 3d35b2d55b..51f4995702 100644 --- a/settings.gradle +++ b/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'