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
48 changes: 48 additions & 0 deletions jfr-streaming/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,51 @@ tasks {
useJUnitPlatform()
}
}

testing {
suites {

val serialGcTest by registering(JvmTestSuite::class) {
dependencies {
implementation("io.opentelemetry:opentelemetry-sdk-testing")
}
targets {
all {
testTask {
jvmArgs = listOf("-XX:+UseSerialGC")
}
}
}
}
val parallelGcTest by registering(JvmTestSuite::class) {
dependencies {
implementation("io.opentelemetry:opentelemetry-sdk-testing")
}
targets {
all {
testTask {
jvmArgs = listOf("-XX:+UseParallelGC")
}
}
}
}
val g1GcTest by registering(JvmTestSuite::class) {
dependencies {
implementation("io.opentelemetry:opentelemetry-sdk-testing")
}
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,53 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.contrib.jfr.metrics;

import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
import static org.awaitility.Awaitility.await;

import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.metrics.SdkMeterProvider;
import io.opentelemetry.sdk.metrics.data.MetricData;
import io.opentelemetry.sdk.testing.assertj.MetricAssert;
import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader;
import java.util.Collection;
import java.util.function.Consumer;
import org.junit.jupiter.api.BeforeAll;

public class AbstractMetricsTest {

static SdkMeterProvider meterProvider;
static InMemoryMetricReader metricReader;
static boolean isInitialized = false;

@BeforeAll
static void initializeOpenTelemetry() {
if (isInitialized) {
return;
}
isInitialized = true;
metricReader = InMemoryMetricReader.create();
meterProvider = SdkMeterProvider.builder().registerMetricReader(metricReader).build();
GlobalOpenTelemetry.set(OpenTelemetrySdk.builder().setMeterProvider(meterProvider).build());
JfrMetrics.enable(meterProvider);
Copy link
Member

Choose a reason for hiding this comment

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

Side note: This abstract base class needs some work:

  • Should be renamed to something more descriptive, maybe AbstractJfrTest
  • Should not have state which is shared across all tests. Right now that's required because JfrMetrics can't be cancelled once started. We should change that.
  • Should be put in a common place where it can be used by each of the different testing suites, rather than repeated in each test suite

Can do all this in a future PR.

Copy link
Contributor Author

@roberttoyonaga roberttoyonaga Jan 13, 2023

Choose a reason for hiding this comment

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

Yup I agree with all those points.

Right now that's required because JfrMetrics can't be cancelled once started

Maybe @BeforeEach test I can reinitialize JFR streaming and all openTelemetry variables, and @AfterEach test the JFR streaming can be ended. I think that should be enough for tests to fully clean up after themselves.

Should be put in a common place where it can be used by each of the different testing suites

I was trying to figure out a good way of doing this today. Maybe something related to test fixtures. I'll have to investigate more.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Update:
I renamed the base test class to AbstractJfrTest. I was able to get the testFixtures working. So now the class resides in a single place, instead of being duplicated for each GC type.

}

@SafeVarargs
protected final void waitAndAssertMetrics(Consumer<MetricAssert>... assertions) {
await()
.untilAsserted(
() -> {
Collection<MetricData> metrics = metricReader.collectAllMetrics();

assertThat(metrics).isNotEmpty();

for (Consumer<MetricAssert> assertion : assertions) {
assertThat(metrics).anySatisfy(metric -> assertion.accept(assertThat(metric)));
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* 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_GC;
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_GC_DURATION;
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.PointData;
import org.assertj.core.api.ThrowingConsumer;
import org.junit.jupiter.api.Test;

class G1GcDurationMetricTest extends AbstractMetricsTest {
static class AttributeCheck implements ThrowingConsumer<PointData> {
roberttoyonaga marked this conversation as resolved.
Show resolved Hide resolved
@Override
public void acceptThrows(PointData pointData) {
// Each point must have attributes of one of the following variation:
// First sort by Major/Minor, then by GC.
if (pointData.getAttributes().get(ATTR_ACTION).equals(END_OF_MINOR_GC)) {
assertThat(pointData.getAttributes())
.isEqualTo(Attributes.of(ATTR_GC, "G1 Young Generation", ATTR_ACTION, END_OF_MINOR_GC));
} else {
assertThat(pointData.getAttributes())
.isEqualTo(Attributes.of(ATTR_GC, "G1 Old Generation", ATTR_ACTION, END_OF_MAJOR_GC));
}
}
}

@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)
.hasHistogramSatisfying(
histogram ->
histogram.hasPointsSatisfying(
point -> point.hasSumGreaterThan(0).satisfies(new AttributeCheck()))));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* 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_G1_EDEN_SPACE;
import static io.opentelemetry.contrib.jfr.metrics.internal.Constants.BYTES;
import static io.opentelemetry.contrib.jfr.metrics.internal.Constants.METRIC_DESCRIPTION_COMMITTED;
import static org.assertj.core.api.Assertions.assertThat;

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 G1MemoryCommittedMetricTest extends AbstractMetricsTest {
private void check(ThrowingConsumer<MetricData> attributeCheck) {
roberttoyonaga marked this conversation as resolved.
Show resolved Hide resolved
waitAndAssertMetrics(
metric ->
metric
.hasName("process.runtime.jvm.memory.committed")
.hasUnit(BYTES)
.hasDescription(METRIC_DESCRIPTION_COMMITTED)
.satisfies(attributeCheck));
}

@Test
void shouldHaveMemoryCommittedMetrics() {
System.gc();
// TODO: need JFR support for the other G1 pools
check(
metricData -> {
SumData<?> sumData = metricData.getLongSumData();
assertThat(sumData.getPoints())
.anyMatch(p -> p.getAttributes().equals(ATTR_G1_EDEN_SPACE));
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.contrib.jfr.metrics;

import org.junit.jupiter.api.Test;

class G1MemoryLimitMetricTest extends AbstractMetricsTest {
roberttoyonaga marked this conversation as resolved.
Show resolved Hide resolved
@Test
void shouldHaveMemoryLimitMetrics() {
// TODO: needs JFR support. Placeholder.
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* 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_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.BYTES;
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 org.assertj.core.api.Assertions.assertThat;

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 G1MemoryUsageMetricTest extends AbstractMetricsTest {

private void check(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));
}

/**
* This is a basic test for process.runtime.jvm.memory.usage and
* process.runtime.jvm.memory.usage_after_last_gc metrics.
*/
@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.
check(
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));
});
}
}
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
Loading