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

Implement memory metrics #652

Merged
merged 11 commits into from
Jan 14, 2023
60 changes: 60 additions & 0 deletions jfr-streaming/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
plugins {
id("otel.java-conventions")
id("java-library")
id("java-test-fixtures")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL about java-test-fixtures. Nice!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

dependencies {
implementation("io.opentelemetry:opentelemetry-api")
testImplementation(testFixtures(project))

testImplementation("io.opentelemetry:opentelemetry-sdk-testing")

testFixturesImplementation("org.junit.jupiter:junit-jupiter:5.8.1")
testFixturesImplementation("io.opentelemetry:opentelemetry-api")
testFixturesImplementation("io.opentelemetry:opentelemetry-sdk-testing")
testFixturesImplementation("org.awaitility:awaitility")
testFixturesImplementation("org.assertj:assertj-core:3.24.1")
}

tasks {
Expand All @@ -23,3 +32,54 @@ tasks {
useJUnitPlatform()
}
}

testing {
suites {

val serialGcTest by registering(JvmTestSuite::class) {
dependencies {
implementation("io.opentelemetry:opentelemetry-sdk-testing")
implementation(project.dependencies.testFixtures(project))
}
targets {
all {
testTask {
jvmArgs = listOf("-XX:+UseSerialGC")
}
}
}
}
val parallelGcTest by registering(JvmTestSuite::class) {
dependencies {
implementation("io.opentelemetry:opentelemetry-sdk-testing")
implementation(project.dependencies.testFixtures(project))
}
targets {
all {
testTask {
jvmArgs = listOf("-XX:+UseParallelGC")
}
}
}
}
val g1GcTest by registering(JvmTestSuite::class) {
dependencies {
implementation("io.opentelemetry:opentelemetry-sdk-testing")
implementation(project.dependencies.testFixtures(project))
}
targets {
all {
testTask {
jvmArgs = listOf("-XX:+UseG1GC")
}
}
}
}
}
}

tasks {
check {
dependsOn(testing.suites)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.contrib.jfr.metrics;

import static io.opentelemetry.contrib.jfr.metrics.internal.Constants.ATTR_ACTION;
import static io.opentelemetry.contrib.jfr.metrics.internal.Constants.ATTR_G1_EDEN_SPACE;
import static io.opentelemetry.contrib.jfr.metrics.internal.Constants.ATTR_G1_SURVIVOR_SPACE;
import static io.opentelemetry.contrib.jfr.metrics.internal.Constants.ATTR_GC;
import static io.opentelemetry.contrib.jfr.metrics.internal.Constants.BYTES;
import static io.opentelemetry.contrib.jfr.metrics.internal.Constants.END_OF_MAJOR_GC;
import static io.opentelemetry.contrib.jfr.metrics.internal.Constants.END_OF_MINOR_GC;
import static io.opentelemetry.contrib.jfr.metrics.internal.Constants.METRIC_DESCRIPTION_COMMITTED;
import static io.opentelemetry.contrib.jfr.metrics.internal.Constants.METRIC_DESCRIPTION_GC_DURATION;
import static io.opentelemetry.contrib.jfr.metrics.internal.Constants.METRIC_DESCRIPTION_MEMORY;
import static io.opentelemetry.contrib.jfr.metrics.internal.Constants.METRIC_DESCRIPTION_MEMORY_AFTER;
import static io.opentelemetry.contrib.jfr.metrics.internal.Constants.METRIC_NAME_MEMORY;
import static io.opentelemetry.contrib.jfr.metrics.internal.Constants.METRIC_NAME_MEMORY_AFTER;
import static io.opentelemetry.contrib.jfr.metrics.internal.Constants.MILLISECONDS;
import static org.assertj.core.api.Assertions.assertThat;

import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.sdk.metrics.data.HistogramData;
import io.opentelemetry.sdk.metrics.data.HistogramPointData;
import io.opentelemetry.sdk.metrics.data.MetricData;
import io.opentelemetry.sdk.metrics.data.SumData;
import org.assertj.core.api.ThrowingConsumer;
import org.junit.jupiter.api.Test;

class G1GcMemoryMetricTest extends AbstractJfrTest {
private void usageCheck(ThrowingConsumer<MetricData> attributeCheck) {
waitAndAssertMetrics(
metric ->
metric
.hasName(METRIC_NAME_MEMORY)
.hasUnit(BYTES)
.hasDescription(METRIC_DESCRIPTION_MEMORY)
.satisfies(attributeCheck),
metric ->
metric
.hasName(METRIC_NAME_MEMORY_AFTER)
.hasUnit(BYTES)
.hasDescription(METRIC_DESCRIPTION_MEMORY_AFTER)
.satisfies(attributeCheck));
}

@Test
void shouldHaveMemoryUsageMetrics() {
System.gc();
// Test to make sure there's metric data for both eden and survivor spaces.
// TODO: once G1 old gen usage added to jdk.G1HeapSummary (in JDK 21), test for it here too.
usageCheck(
metricData -> {
SumData<?> sumData = metricData.getLongSumData();
assertThat(sumData.getPoints())
.anyMatch(p -> p.getAttributes().equals(ATTR_G1_EDEN_SPACE))
.anyMatch(p -> p.getAttributes().equals(ATTR_G1_SURVIVOR_SPACE));
});
}

@Test
void shouldHaveMemoryLimitMetrics() {
// TODO: needs JFR support. Placeholder.
}

@Test
void shouldHaveMemoryCommittedMetrics() {
System.gc();
// TODO: need JFR support for the other G1 pools
waitAndAssertMetrics(
metric ->
metric
.hasName("process.runtime.jvm.memory.committed")
.hasUnit(BYTES)
.hasDescription(METRIC_DESCRIPTION_COMMITTED)
.satisfies(
metricData -> {
SumData<?> sumData = metricData.getLongSumData();
assertThat(sumData.getPoints())
.anyMatch(p -> p.getAttributes().equals(ATTR_G1_EDEN_SPACE));
}));
}

@Test
void shouldHaveGCDurationMetrics() {
// TODO: Need a reliable way to test old and young gen GC in isolation.
System.gc();
waitAndAssertMetrics(
metric ->
metric
.hasName("process.runtime.jvm.gc.duration")
.hasUnit(MILLISECONDS)
.hasDescription(METRIC_DESCRIPTION_GC_DURATION)
.satisfies(
metricData -> {
HistogramData data = metricData.getHistogramData();
assertThat(data.getPoints())
.map(HistogramPointData.class::cast)
.anyMatch(
p ->
p.getSum() > 0
&& (p.getAttributes()
.equals(
Attributes.of(
ATTR_GC,
"G1 Young Generation",
ATTR_ACTION,
END_OF_MINOR_GC))
|| p.getAttributes()
.equals(
Attributes.of(
ATTR_GC,
"G1 Old Generation",
ATTR_ACTION,
END_OF_MAJOR_GC))));
}));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
import io.opentelemetry.contrib.jfr.metrics.internal.cpu.ContextSwitchRateHandler;
import io.opentelemetry.contrib.jfr.metrics.internal.cpu.LongLockHandler;
import io.opentelemetry.contrib.jfr.metrics.internal.cpu.OverallCPULoadHandler;
import io.opentelemetry.contrib.jfr.metrics.internal.memory.CodeCacheConfigurationHandler;
import io.opentelemetry.contrib.jfr.metrics.internal.memory.G1HeapSummaryHandler;
import io.opentelemetry.contrib.jfr.metrics.internal.memory.GCHeapSummaryHandler;
import io.opentelemetry.contrib.jfr.metrics.internal.memory.MetaspaceSummaryHandler;
import io.opentelemetry.contrib.jfr.metrics.internal.memory.ObjectAllocationInNewTLABHandler;
import io.opentelemetry.contrib.jfr.metrics.internal.memory.ObjectAllocationOutsideTLABHandler;
import io.opentelemetry.contrib.jfr.metrics.internal.memory.ParallelHeapSummaryHandler;
Expand All @@ -41,7 +42,6 @@ private HandlerRegistry(List<? extends RecordedEventHandler> mappers) {

static HandlerRegistry createDefault(MeterProvider meterProvider) {
var handlers = new ArrayList<RecordedEventHandler>();

for (var bean : ManagementFactory.getGarbageCollectorMXBeans()) {
var name = bean.getName();
switch (name) {
Expand Down Expand Up @@ -83,13 +83,14 @@ static HandlerRegistry createDefault(MeterProvider meterProvider) {
new ObjectAllocationOutsideTLABHandler(grouper),
new NetworkReadHandler(grouper),
new NetworkWriteHandler(grouper),
new GCHeapSummaryHandler(),
new ContextSwitchRateHandler(),
new OverallCPULoadHandler(),
new ContainerConfigurationHandler(),
new LongLockHandler(grouper),
new ThreadCountHandler(),
new ClassesLoadedHandler(),
new MetaspaceSummaryHandler(),
new CodeCacheConfigurationHandler(),
new DirectBufferStatisticsHandler());
handlers.addAll(basicHandlers);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package io.opentelemetry.contrib.jfr.metrics.internal;

import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;

public final class Constants {
private Constants() {}
Expand Down Expand Up @@ -71,6 +72,23 @@ private Constants() {}
public static final AttributeKey<String> ATTR_GC = AttributeKey.stringKey("pool");
public static final AttributeKey<String> ATTR_ACTION = AttributeKey.stringKey("action");
public static final AttributeKey<Boolean> ATTR_DAEMON = AttributeKey.booleanKey(DAEMON);
public static final Attributes ATTR_PS_EDEN_SPACE =
Attributes.of(ATTR_TYPE, HEAP, ATTR_POOL, "PS Eden Space");
public static final Attributes ATTR_PS_SURVIVOR_SPACE =
Attributes.of(ATTR_TYPE, HEAP, ATTR_POOL, "PS Survivor Space");
public static final Attributes ATTR_PS_OLD_GEN =
Attributes.of(ATTR_TYPE, HEAP, ATTR_POOL, "PS Old Gen");
public static final Attributes ATTR_G1_SURVIVOR_SPACE =
Attributes.of(ATTR_TYPE, HEAP, ATTR_POOL, "G1 Survivor Space");
public static final Attributes ATTR_G1_EDEN_SPACE =
Attributes.of(ATTR_TYPE, HEAP, ATTR_POOL, "G1 Eden Space");
public static final Attributes ATTR_METASPACE =
Attributes.of(ATTR_TYPE, NON_HEAP, ATTR_POOL, "Metaspace");
public static final Attributes ATTR_COMPRESSED_CLASS_SPACE =
Attributes.of(ATTR_TYPE, NON_HEAP, ATTR_POOL, "Compressed Class Space");
public static final Attributes ATTR_CODE_CACHE =
Attributes.of(ATTR_TYPE, NON_HEAP, ATTR_POOL, "CodeCache");

public static final String UNIT_CLASSES = "{classes}";
public static final String UNIT_THREADS = "{threads}";
public static final String UNIT_BUFFERS = "{buffers}";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.contrib.jfr.metrics.internal.memory;

import static io.opentelemetry.contrib.jfr.metrics.internal.Constants.ATTR_POOL;
import static io.opentelemetry.contrib.jfr.metrics.internal.Constants.ATTR_TYPE;
import static io.opentelemetry.contrib.jfr.metrics.internal.Constants.BYTES;
import static io.opentelemetry.contrib.jfr.metrics.internal.Constants.INITIAL_SIZE;
import static io.opentelemetry.contrib.jfr.metrics.internal.Constants.METRIC_DESCRIPTION_MEMORY_INIT;
import static io.opentelemetry.contrib.jfr.metrics.internal.Constants.METRIC_NAME_MEMORY_INIT;
import static io.opentelemetry.contrib.jfr.metrics.internal.Constants.NON_HEAP;
import static io.opentelemetry.contrib.jfr.metrics.internal.RecordedEventHandler.defaultMeter;

import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.contrib.jfr.metrics.internal.RecordedEventHandler;
import java.time.Duration;
import java.util.Optional;
import jdk.jfr.consumer.RecordedEvent;

/** Handles attributes with pool value CodeCache */
public final class CodeCacheConfigurationHandler implements RecordedEventHandler {
private static final String EVENT_NAME = "jdk.CodeCacheConfiguration";

private static final Attributes ATTR = Attributes.of(ATTR_TYPE, NON_HEAP, ATTR_POOL, "CodeCache");

private volatile long initialSize = 0;

public CodeCacheConfigurationHandler() {
initializeMeter(defaultMeter());
}

@Override
public void initializeMeter(Meter meter) {
meter
.upDownCounterBuilder(METRIC_NAME_MEMORY_INIT)
.setDescription(METRIC_DESCRIPTION_MEMORY_INIT)
.setUnit(BYTES)
.buildWithCallback(measurement -> measurement.record(initialSize, ATTR));
}

@Override
public String getEventName() {
return EVENT_NAME;
}

@Override
public void accept(RecordedEvent event) {
if (event.hasField(INITIAL_SIZE)) {
initialSize = event.getLong(INITIAL_SIZE);
}
}

@Override
public Optional<Duration> getPollingDuration() {
return Optional.of(Duration.ofSeconds(1));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
import static io.opentelemetry.contrib.jfr.metrics.internal.Constants.ATTR_TYPE;
import static io.opentelemetry.contrib.jfr.metrics.internal.Constants.BYTES;
import static io.opentelemetry.contrib.jfr.metrics.internal.Constants.HEAP;
import static io.opentelemetry.contrib.jfr.metrics.internal.Constants.METRIC_DESCRIPTION_COMMITTED;
import static io.opentelemetry.contrib.jfr.metrics.internal.Constants.METRIC_DESCRIPTION_MEMORY;
import static io.opentelemetry.contrib.jfr.metrics.internal.Constants.METRIC_DESCRIPTION_MEMORY_AFTER;
import static io.opentelemetry.contrib.jfr.metrics.internal.Constants.METRIC_NAME_COMMITTED;
import static io.opentelemetry.contrib.jfr.metrics.internal.Constants.METRIC_NAME_MEMORY;
import static io.opentelemetry.contrib.jfr.metrics.internal.Constants.METRIC_NAME_MEMORY_AFTER;
import static io.opentelemetry.contrib.jfr.metrics.internal.RecordedEventHandler.defaultMeter;
Expand All @@ -33,11 +35,12 @@ public final class G1HeapSummaryHandler implements RecordedEventHandler {
private static final String AFTER = "After GC";
private static final String GC_ID = "gcId";
private static final String EDEN_USED_SIZE = "edenUsedSize";
private static final String EDEN_TOTAL_SIZE = "edenTotalSize";
private static final String SURVIVOR_USED_SIZE = "survivorUsedSize";
private static final String WHEN = "when";
private static final Attributes ATTR_MEMORY_EDEN_USED =
private static final Attributes ATTR_MEMORY_EDEN =
Attributes.of(ATTR_TYPE, HEAP, ATTR_POOL, "G1 Eden Space");
private static final Attributes ATTR_MEMORY_SURVIVOR_USED =
private static final Attributes ATTR_MEMORY_SURVIVOR =
Attributes.of(ATTR_TYPE, HEAP, ATTR_POOL, "G1 Survivor Space");
// private static final Attributes ATTR_MEMORY_OLD_USED =
// Attributes.of(ATTR_TYPE, HEAP, ATTR_POOL, "G1 Old Gen"); // TODO needs jdk JFR support
Expand All @@ -46,6 +49,7 @@ public final class G1HeapSummaryHandler implements RecordedEventHandler {
private volatile long usageEdenAfter = 0;
private volatile long usageSurvivor = 0;
private volatile long usageSurvivorAfter = 0;
private volatile long committedEden = 0;

public G1HeapSummaryHandler() {
initializeMeter(defaultMeter());
Expand All @@ -59,18 +63,23 @@ public void initializeMeter(Meter meter) {
.setUnit(BYTES)
.buildWithCallback(
measurement -> {
measurement.record(usageEden, ATTR_MEMORY_EDEN_USED);
measurement.record(usageSurvivor, ATTR_MEMORY_SURVIVOR_USED);
measurement.record(usageEden, ATTR_MEMORY_EDEN);
measurement.record(usageSurvivor, ATTR_MEMORY_SURVIVOR);
});
meter
.upDownCounterBuilder(METRIC_NAME_MEMORY_AFTER)
.setDescription(METRIC_DESCRIPTION_MEMORY_AFTER)
.setUnit(BYTES)
.buildWithCallback(
measurement -> {
measurement.record(usageEdenAfter, ATTR_MEMORY_EDEN_USED);
measurement.record(usageSurvivorAfter, ATTR_MEMORY_SURVIVOR_USED);
measurement.record(usageEdenAfter, ATTR_MEMORY_EDEN);
measurement.record(usageSurvivorAfter, ATTR_MEMORY_SURVIVOR);
});
meter
.upDownCounterBuilder(METRIC_NAME_COMMITTED)
.setDescription(METRIC_DESCRIPTION_COMMITTED)
.setUnit(BYTES)
.buildWithCallback(measurement -> measurement.record(committedEden, ATTR_MEMORY_EDEN));
}

@Override
Expand Down Expand Up @@ -115,5 +124,9 @@ private void recordValues(RecordedEvent event, boolean before) {
usageSurvivorAfter = event.getLong(SURVIVOR_USED_SIZE);
}
}

if (event.hasField(EDEN_TOTAL_SIZE)) {
committedEden = event.getLong(EDEN_TOTAL_SIZE);
}
}
}
Loading