Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update runtime memory metrics to reflect semantic conventions #5718

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,16 @@
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:
*
Expand All @@ -30,132 +32,94 @@
* <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. */
/**
* Register observers for java runtime memory metrics.
*
* @deprecated use {@link #registerObservers(OpenTelemetry openTelemetry)}
*/
@Deprecated
public static void registerMemoryAreaObservers() {
registerMemoryPoolObservers(GlobalOpenTelemetry.get());
public static void registerObservers() {
registerObservers(GlobalOpenTelemetry.get());
}

public static void registerMemoryAreaObservers(OpenTelemetry openTelemetry) {
MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
Meter meter = openTelemetry.getMeterProvider().get(MemoryPools.class.getName());
/** Register observers for java runtime memory metrics. */
public static void registerObservers(OpenTelemetry openTelemetry) {
List<MemoryPoolMXBean> poolBeans = ManagementFactory.getMemoryPoolMXBeans();
Meter meter = openTelemetry.getMeter("io.opentelemetry.runtime-metrics");

meter
.upDownCounterBuilder("runtime.jvm.memory.area")
.setDescription("Bytes of a given JVM memory area.")
.upDownCounterBuilder("process.runtime.jvm.memory.usage")
.setDescription("Measure of memory used")
.setUnit("By")
.buildWithCallback(
resultLongObserver -> {
recordHeap(resultLongObserver, memoryBean.getHeapMemoryUsage());
recordNonHeap(resultLongObserver, memoryBean.getNonHeapMemoryUsage());
});
}
.buildWithCallback(callback(poolBeans, MemoryUsage::getUsed));

/** 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.")
.upDownCounterBuilder("process.runtime.jvm.memory.init")
.setDescription("Measure of initial memory requested")
.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));
}
}
});
}
.buildWithCallback(callback(poolBeans, MemoryUsage::getInit));

/**
* Register all measurements provided by this module.
*
* @deprecated use {@link #registerObservers(OpenTelemetry openTelemetry)}
*/
@Deprecated
public static void registerObservers() {
registerMemoryAreaObservers();
registerMemoryPoolObservers();
}
meter
.upDownCounterBuilder("process.runtime.jvm.memory.committed")
.setDescription("Measure of memory committed")
.setUnit("By")
.buildWithCallback(callback(poolBeans, MemoryUsage::getCommitted));

/** Register all measurements provided by this module. */
public static void registerObservers(OpenTelemetry openTelemetry) {
registerMemoryAreaObservers(openTelemetry);
registerMemoryPoolObservers(openTelemetry);
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);
}
// 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());
}

static void recordNonHeap(ObservableLongMeasurement measurement, MemoryUsage usage) {
record(measurement, usage, USED_NON_HEAP, COMMITTED_NON_HEAP, MAX_NON_HEAP);
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 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);
private static String memoryType(MemoryType memoryType) {
switch (memoryType) {
case HEAP:
return HEAP;
case NON_HEAP:
return NON_HEAP;
}
return "unknown";
}

private MemoryPools() {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

@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);
@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 observeNonHeap() {
ObservableLongMeasurement measurement = mock(ObservableLongMeasurement.class);
MemoryPools.recordNonHeap(measurement, new MemoryUsage(-1, 4, 5, 6));
void callback_Records() {
when(heapPoolUsage.getUsed()).thenReturn(1L);
when(nonHeapUsage.getUsed()).thenReturn(2L);

Consumer<ObservableLongMeasurement> callback =
MemoryPools.callback(beans, MemoryUsage::getUsed);
callback.accept(measurement);

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));
verify(measurement)
.record(4, Attributes.of(MemoryPools.TYPE_KEY, "used", MemoryPools.AREA_KEY, "non_heap"));
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(
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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down