Update runtime memory metrics to reflect semantic conventions (#5718)
* Update runtime memory metrics to reflect semantic conventions * Fix SpringBootSmokeTest * Fix PrometheusSmokeTest
This commit is contained in:
parent
7bc748a2ff
commit
fbc100a0f6
|
|
@ -18,8 +18,10 @@ class RuntimeMetricsTest extends AgentInstrumentationSpecification {
|
|||
conditions.eventually {
|
||||
assert getMetrics().any { it.name == "runtime.jvm.gc.time" }
|
||||
assert getMetrics().any { it.name == "runtime.jvm.gc.count" }
|
||||
assert getMetrics().any { it.name == "runtime.jvm.memory.area" }
|
||||
assert getMetrics().any { it.name == "runtime.jvm.memory.pool" }
|
||||
assert getMetrics().any { it.name == "process.runtime.jvm.memory.init" }
|
||||
assert getMetrics().any { it.name == "process.runtime.jvm.memory.usage" }
|
||||
assert getMetrics().any { it.name == "process.runtime.jvm.memory.committed" }
|
||||
assert getMetrics().any { it.name == "process.runtime.jvm.memory.max" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,14 +12,16 @@ import io.opentelemetry.api.common.Attributes;
|
|||
import io.opentelemetry.api.metrics.Meter;
|
||||
import io.opentelemetry.api.metrics.ObservableLongMeasurement;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.MemoryMXBean;
|
||||
import java.lang.management.MemoryPoolMXBean;
|
||||
import java.lang.management.MemoryType;
|
||||
import java.lang.management.MemoryUsage;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Registers measurements that generate metrics about JVM memory areas.
|
||||
* Registers measurements that generate metrics about JVM memory pools.
|
||||
*
|
||||
* <p>Example usage:
|
||||
*
|
||||
|
|
@ -30,132 +32,94 @@ import java.util.List;
|
|||
* <p>Example metrics being exported: Component
|
||||
*
|
||||
* <pre>
|
||||
* runtime.jvm.memory.area{type="used",area="heap"} 2000000
|
||||
* runtime.jvm.memory.area{type="committed",area="non_heap"} 200000
|
||||
* runtime.jvm.memory.area{type="used",pool="PS Eden Space"} 2000
|
||||
* process.runtime.jvm.memory.init{type="heap",pool="G1 Eden Space"} 1000000
|
||||
* process.runtime.jvm.memory.usage{type="heap",pool="G1 Eden Space"} 2500000
|
||||
* process.runtime.jvm.memory.committed{type="heap",pool="G1 Eden Space"} 3000000
|
||||
* process.runtime.jvm.memory.max{type="heap",pool="G1 Eden Space"} 4000000
|
||||
* process.runtime.jvm.memory.init{type="non_heap",pool="Metaspace"} 200
|
||||
* process.runtime.jvm.memory.usage{type="non_heap",pool="Metaspace"} 400
|
||||
* process.runtime.jvm.memory.committed{type="non_heap",pool="Metaspace"} 500
|
||||
* </pre>
|
||||
*/
|
||||
public final class MemoryPools {
|
||||
// Visible for testing
|
||||
static final AttributeKey<String> TYPE_KEY = AttributeKey.stringKey("type");
|
||||
// Visible for testing
|
||||
static final AttributeKey<String> AREA_KEY = AttributeKey.stringKey("area");
|
||||
|
||||
private static final AttributeKey<String> TYPE_KEY = AttributeKey.stringKey("type");
|
||||
private static final AttributeKey<String> POOL_KEY = AttributeKey.stringKey("pool");
|
||||
|
||||
private static final String USED = "used";
|
||||
private static final String COMMITTED = "committed";
|
||||
private static final String MAX = "max";
|
||||
private static final String HEAP = "heap";
|
||||
private static final String NON_HEAP = "non_heap";
|
||||
|
||||
private static final Attributes COMMITTED_HEAP =
|
||||
Attributes.of(TYPE_KEY, COMMITTED, AREA_KEY, HEAP);
|
||||
private static final Attributes USED_HEAP = Attributes.of(TYPE_KEY, USED, AREA_KEY, HEAP);
|
||||
private static final Attributes MAX_HEAP = Attributes.of(TYPE_KEY, MAX, AREA_KEY, HEAP);
|
||||
|
||||
private static final Attributes COMMITTED_NON_HEAP =
|
||||
Attributes.of(TYPE_KEY, COMMITTED, AREA_KEY, NON_HEAP);
|
||||
private static final Attributes USED_NON_HEAP = Attributes.of(TYPE_KEY, USED, AREA_KEY, NON_HEAP);
|
||||
private static final Attributes MAX_NON_HEAP = Attributes.of(TYPE_KEY, MAX, AREA_KEY, NON_HEAP);
|
||||
|
||||
/** Register only the "area" measurements. */
|
||||
@Deprecated
|
||||
public static void registerMemoryAreaObservers() {
|
||||
registerMemoryPoolObservers(GlobalOpenTelemetry.get());
|
||||
}
|
||||
|
||||
public static void registerMemoryAreaObservers(OpenTelemetry openTelemetry) {
|
||||
MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
|
||||
Meter meter = openTelemetry.getMeterProvider().get(MemoryPools.class.getName());
|
||||
meter
|
||||
.upDownCounterBuilder("runtime.jvm.memory.area")
|
||||
.setDescription("Bytes of a given JVM memory area.")
|
||||
.setUnit("By")
|
||||
.buildWithCallback(
|
||||
resultLongObserver -> {
|
||||
recordHeap(resultLongObserver, memoryBean.getHeapMemoryUsage());
|
||||
recordNonHeap(resultLongObserver, memoryBean.getNonHeapMemoryUsage());
|
||||
});
|
||||
}
|
||||
|
||||
/** Register only the "pool" measurements. */
|
||||
@Deprecated
|
||||
public static void registerMemoryPoolObservers() {
|
||||
registerMemoryPoolObservers(GlobalOpenTelemetry.get());
|
||||
}
|
||||
|
||||
public static void registerMemoryPoolObservers(OpenTelemetry openTelemetry) {
|
||||
List<MemoryPoolMXBean> poolBeans = ManagementFactory.getMemoryPoolMXBeans();
|
||||
Meter meter = openTelemetry.getMeterProvider().get(MemoryPools.class.getName());
|
||||
List<Attributes> usedLabelSets = new ArrayList<>(poolBeans.size());
|
||||
List<Attributes> committedLabelSets = new ArrayList<>(poolBeans.size());
|
||||
List<Attributes> maxLabelSets = new ArrayList<>(poolBeans.size());
|
||||
for (MemoryPoolMXBean pool : poolBeans) {
|
||||
usedLabelSets.add(Attributes.of(TYPE_KEY, USED, POOL_KEY, pool.getName()));
|
||||
committedLabelSets.add(Attributes.of(TYPE_KEY, COMMITTED, POOL_KEY, pool.getName()));
|
||||
maxLabelSets.add(Attributes.of(TYPE_KEY, MAX, POOL_KEY, pool.getName()));
|
||||
}
|
||||
meter
|
||||
.upDownCounterBuilder("runtime.jvm.memory.pool")
|
||||
.setDescription("Bytes of a given JVM memory pool.")
|
||||
.setUnit("By")
|
||||
.buildWithCallback(
|
||||
resultLongObserver -> {
|
||||
for (int i = 0; i < poolBeans.size(); i++) {
|
||||
MemoryUsage poolUsage = poolBeans.get(i).getUsage();
|
||||
if (poolUsage != null) {
|
||||
record(
|
||||
resultLongObserver,
|
||||
poolUsage,
|
||||
usedLabelSets.get(i),
|
||||
committedLabelSets.get(i),
|
||||
maxLabelSets.get(i));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register all measurements provided by this module.
|
||||
* Register observers for java runtime memory metrics.
|
||||
*
|
||||
* @deprecated use {@link #registerObservers(OpenTelemetry openTelemetry)}
|
||||
*/
|
||||
@Deprecated
|
||||
public static void registerObservers() {
|
||||
registerMemoryAreaObservers();
|
||||
registerMemoryPoolObservers();
|
||||
registerObservers(GlobalOpenTelemetry.get());
|
||||
}
|
||||
|
||||
/** Register all measurements provided by this module. */
|
||||
/** Register observers for java runtime memory metrics. */
|
||||
public static void registerObservers(OpenTelemetry openTelemetry) {
|
||||
registerMemoryAreaObservers(openTelemetry);
|
||||
registerMemoryPoolObservers(openTelemetry);
|
||||
List<MemoryPoolMXBean> poolBeans = ManagementFactory.getMemoryPoolMXBeans();
|
||||
Meter meter = openTelemetry.getMeter("io.opentelemetry.runtime-metrics");
|
||||
|
||||
meter
|
||||
.upDownCounterBuilder("process.runtime.jvm.memory.usage")
|
||||
.setDescription("Measure of memory used")
|
||||
.setUnit("By")
|
||||
.buildWithCallback(callback(poolBeans, MemoryUsage::getUsed));
|
||||
|
||||
meter
|
||||
.upDownCounterBuilder("process.runtime.jvm.memory.init")
|
||||
.setDescription("Measure of initial memory requested")
|
||||
.setUnit("By")
|
||||
.buildWithCallback(callback(poolBeans, MemoryUsage::getInit));
|
||||
|
||||
meter
|
||||
.upDownCounterBuilder("process.runtime.jvm.memory.committed")
|
||||
.setDescription("Measure of memory committed")
|
||||
.setUnit("By")
|
||||
.buildWithCallback(callback(poolBeans, MemoryUsage::getCommitted));
|
||||
|
||||
meter
|
||||
.upDownCounterBuilder("process.runtime.jvm.memory.max")
|
||||
.setDescription("Measure of max obtainable memory")
|
||||
.setUnit("By")
|
||||
.buildWithCallback(callback(poolBeans, MemoryUsage::getMax));
|
||||
}
|
||||
|
||||
static void recordHeap(ObservableLongMeasurement measurement, MemoryUsage usage) {
|
||||
record(measurement, usage, USED_HEAP, COMMITTED_HEAP, MAX_HEAP);
|
||||
}
|
||||
|
||||
static void recordNonHeap(ObservableLongMeasurement measurement, MemoryUsage usage) {
|
||||
record(measurement, usage, USED_NON_HEAP, COMMITTED_NON_HEAP, MAX_NON_HEAP);
|
||||
}
|
||||
|
||||
private static void record(
|
||||
ObservableLongMeasurement measurement,
|
||||
MemoryUsage usage,
|
||||
Attributes usedAttributes,
|
||||
Attributes committedAttributes,
|
||||
Attributes maxAttributes) {
|
||||
// TODO: Decide if init is needed or not. It is a constant that can be queried once on startup.
|
||||
// if (usage.getInit() != -1) {
|
||||
// measurement.record(usage.getInit(), ...);
|
||||
// }
|
||||
measurement.record(usage.getUsed(), usedAttributes);
|
||||
measurement.record(usage.getCommitted(), committedAttributes);
|
||||
// TODO: Decide if max is needed or not. It is a constant that can be queried once on startup.
|
||||
if (usage.getMax() != -1) {
|
||||
measurement.record(usage.getMax(), maxAttributes);
|
||||
// Visible for testing
|
||||
static Consumer<ObservableLongMeasurement> callback(
|
||||
List<MemoryPoolMXBean> poolBeans, Function<MemoryUsage, Long> extractor) {
|
||||
List<Attributes> attributeSets = new ArrayList<>(poolBeans.size());
|
||||
for (MemoryPoolMXBean pool : poolBeans) {
|
||||
attributeSets.add(
|
||||
Attributes.builder()
|
||||
.put(POOL_KEY, pool.getName())
|
||||
.put(TYPE_KEY, memoryType(pool.getType()))
|
||||
.build());
|
||||
}
|
||||
|
||||
return measurement -> {
|
||||
for (int i = 0; i < poolBeans.size(); i++) {
|
||||
Attributes attributes = attributeSets.get(i);
|
||||
long value = extractor.apply(poolBeans.get(i).getUsage());
|
||||
if (value != -1) {
|
||||
measurement.record(value, attributes);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static String memoryType(MemoryType memoryType) {
|
||||
switch (memoryType) {
|
||||
case HEAP:
|
||||
return HEAP;
|
||||
case NON_HEAP:
|
||||
return NON_HEAP;
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
private MemoryPools() {}
|
||||
|
|
|
|||
|
|
@ -5,64 +5,77 @@
|
|||
|
||||
package io.opentelemetry.instrumentation.runtimemetrics;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import io.opentelemetry.api.common.Attributes;
|
||||
import io.opentelemetry.api.metrics.ObservableLongMeasurement;
|
||||
import java.lang.management.MemoryPoolMXBean;
|
||||
import java.lang.management.MemoryType;
|
||||
import java.lang.management.MemoryUsage;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Spy;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class MemoryPoolsTest {
|
||||
|
||||
@Test
|
||||
void observeHeap() {
|
||||
ObservableLongMeasurement measurement = mock(ObservableLongMeasurement.class);
|
||||
MemoryPools.recordHeap(measurement, new MemoryUsage(-1, 1, 2, 3));
|
||||
verify(measurement)
|
||||
.record(1, Attributes.of(MemoryPools.TYPE_KEY, "used", MemoryPools.AREA_KEY, "heap"));
|
||||
verify(measurement)
|
||||
.record(2, Attributes.of(MemoryPools.TYPE_KEY, "committed", MemoryPools.AREA_KEY, "heap"));
|
||||
verify(measurement)
|
||||
.record(3, Attributes.of(MemoryPools.TYPE_KEY, "max", MemoryPools.AREA_KEY, "heap"));
|
||||
verifyNoMoreInteractions(measurement);
|
||||
@Spy private ObservableLongMeasurement measurement;
|
||||
|
||||
@Mock private MemoryPoolMXBean heapPoolBean;
|
||||
@Mock private MemoryPoolMXBean nonHeapPoolBean;
|
||||
|
||||
@Mock private MemoryUsage heapPoolUsage;
|
||||
@Mock private MemoryUsage nonHeapUsage;
|
||||
|
||||
private List<MemoryPoolMXBean> beans;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
when(heapPoolBean.getName()).thenReturn("heap_pool");
|
||||
when(heapPoolBean.getType()).thenReturn(MemoryType.HEAP);
|
||||
when(heapPoolBean.getUsage()).thenReturn(heapPoolUsage);
|
||||
when(nonHeapPoolBean.getName()).thenReturn("non_heap_pool");
|
||||
when(nonHeapPoolBean.getType()).thenReturn(MemoryType.NON_HEAP);
|
||||
when(nonHeapPoolBean.getUsage()).thenReturn(nonHeapUsage);
|
||||
beans = Arrays.asList(heapPoolBean, nonHeapPoolBean);
|
||||
}
|
||||
|
||||
@Test
|
||||
void observeHeapNoMax() {
|
||||
ObservableLongMeasurement measurement = mock(ObservableLongMeasurement.class);
|
||||
MemoryPools.recordHeap(measurement, new MemoryUsage(-1, 1, 2, -1));
|
||||
verify(measurement)
|
||||
.record(1, Attributes.of(MemoryPools.TYPE_KEY, "used", MemoryPools.AREA_KEY, "heap"));
|
||||
verify(measurement)
|
||||
.record(2, Attributes.of(MemoryPools.TYPE_KEY, "committed", MemoryPools.AREA_KEY, "heap"));
|
||||
verifyNoMoreInteractions(measurement);
|
||||
}
|
||||
void callback_Records() {
|
||||
when(heapPoolUsage.getUsed()).thenReturn(1L);
|
||||
when(nonHeapUsage.getUsed()).thenReturn(2L);
|
||||
|
||||
Consumer<ObservableLongMeasurement> callback =
|
||||
MemoryPools.callback(beans, MemoryUsage::getUsed);
|
||||
callback.accept(measurement);
|
||||
|
||||
@Test
|
||||
void observeNonHeap() {
|
||||
ObservableLongMeasurement measurement = mock(ObservableLongMeasurement.class);
|
||||
MemoryPools.recordNonHeap(measurement, new MemoryUsage(-1, 4, 5, 6));
|
||||
verify(measurement)
|
||||
.record(4, Attributes.of(MemoryPools.TYPE_KEY, "used", MemoryPools.AREA_KEY, "non_heap"));
|
||||
.record(1, Attributes.builder().put("pool", "heap_pool").put("type", "heap").build());
|
||||
verify(measurement)
|
||||
.record(
|
||||
5, Attributes.of(MemoryPools.TYPE_KEY, "committed", MemoryPools.AREA_KEY, "non_heap"));
|
||||
verify(measurement)
|
||||
.record(6, Attributes.of(MemoryPools.TYPE_KEY, "max", MemoryPools.AREA_KEY, "non_heap"));
|
||||
verifyNoMoreInteractions(measurement);
|
||||
2, Attributes.builder().put("pool", "non_heap_pool").put("type", "non_heap").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
void observeNonHeapNoMax() {
|
||||
ObservableLongMeasurement measurement = mock(ObservableLongMeasurement.class);
|
||||
MemoryPools.recordNonHeap(measurement, new MemoryUsage(-1, 4, 5, -1));
|
||||
void callback_SkipRecord() {
|
||||
when(heapPoolUsage.getMax()).thenReturn(1L);
|
||||
when(nonHeapUsage.getMax()).thenReturn(-1L);
|
||||
|
||||
Consumer<ObservableLongMeasurement> callback = MemoryPools.callback(beans, MemoryUsage::getMax);
|
||||
callback.accept(measurement);
|
||||
|
||||
verify(measurement)
|
||||
.record(4, Attributes.of(MemoryPools.TYPE_KEY, "used", MemoryPools.AREA_KEY, "non_heap"));
|
||||
verify(measurement)
|
||||
.record(
|
||||
5, Attributes.of(MemoryPools.TYPE_KEY, "committed", MemoryPools.AREA_KEY, "non_heap"));
|
||||
verifyNoMoreInteractions(measurement);
|
||||
.record(1, Attributes.builder().put("pool", "heap_pool").put("type", "heap").build());
|
||||
verify(measurement, never()).record(eq(-1), any());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ class PrometheusSmokeTest extends SmokeTest {
|
|||
def prometheusClient = WebClient.of("h1c://localhost:${containerManager.getTargetMappedPort(9090)}")
|
||||
def prometheusData = prometheusClient.get("/").aggregate().join().contentUtf8()
|
||||
|
||||
prometheusData.contains("runtime_jvm_memory_pool")
|
||||
prometheusData.contains("process_runtime_jvm_memory_usage")
|
||||
|
||||
cleanup:
|
||||
stopTarget()
|
||||
|
|
|
|||
|
|
@ -79,8 +79,10 @@ class SpringBootSmokeTest extends SmokeTest {
|
|||
def metrics = new MetricsInspector(waitForMetrics())
|
||||
metrics.hasMetricsNamed("runtime.jvm.gc.time")
|
||||
metrics.hasMetricsNamed("runtime.jvm.gc.count")
|
||||
metrics.hasMetricsNamed("runtime.jvm.memory.area")
|
||||
metrics.hasMetricsNamed("runtime.jvm.memory.pool")
|
||||
metrics.hasMetricsNamed("process.runtime.jvm.memory.init")
|
||||
metrics.hasMetricsNamed("process.runtime.jvm.memory.usage")
|
||||
metrics.hasMetricsNamed("process.runtime.jvm.memory.committed")
|
||||
metrics.hasMetricsNamed("process.runtime.jvm.memory.max")
|
||||
|
||||
cleanup:
|
||||
stopTarget()
|
||||
|
|
|
|||
Loading…
Reference in New Issue