Add system metrics (#1309)
Signed-off-by: Sergei Malafeev <sergei@malafeev.org>
This commit is contained in:
parent
54a85f7501
commit
d2813c838d
|
@ -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'
|
||||
}
|
|
@ -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<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"));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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<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"));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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<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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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'
|
||||
|
|
Loading…
Reference in New Issue