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-2.2'
|
||||||
include ':instrumentation:okhttp:okhttp-3.0'
|
include ':instrumentation:okhttp:okhttp-3.0'
|
||||||
include ':instrumentation:opentelemetry-api-beta'
|
include ':instrumentation:opentelemetry-api-beta'
|
||||||
|
include ':instrumentation:oshi:library'
|
||||||
include ':instrumentation:play:play-2.3'
|
include ':instrumentation:play:play-2.3'
|
||||||
include ':instrumentation:play:play-2.4'
|
include ':instrumentation:play:play-2.4'
|
||||||
include ':instrumentation:play:play-2.6'
|
include ':instrumentation:play:play-2.6'
|
||||||
|
|
Loading…
Reference in New Issue