Skip to content

Commit

Permalink
Update runtime memory metrics to reflect semantic conventions (#5718)
Browse files Browse the repository at this point in the history
* Update runtime memory metrics to reflect semantic conventions

* Fix SpringBootSmokeTest

* Fix PrometheusSmokeTest
  • Loading branch information
jack-berg authored Mar 31, 2022
1 parent 7bc748a commit fbc100a
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 148 deletions.
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

0 comments on commit fbc100a

Please sign in to comment.