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 GC duration metric #653

Merged
merged 5 commits into from
Jan 3, 2023
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 @@ -6,6 +6,9 @@
package io.opentelemetry.contrib.jfr.metrics;

import io.opentelemetry.api.metrics.MeterProvider;
import io.opentelemetry.contrib.jfr.metrics.internal.GarbageCollection.G1GarbageCollectionHandler;
import io.opentelemetry.contrib.jfr.metrics.internal.GarbageCollection.OldGarbageCollectionHandler;
import io.opentelemetry.contrib.jfr.metrics.internal.GarbageCollection.YoungGarbageCollectionHandler;
import io.opentelemetry.contrib.jfr.metrics.internal.RecordedEventHandler;
import io.opentelemetry.contrib.jfr.metrics.internal.ThreadGrouper;
import io.opentelemetry.contrib.jfr.metrics.internal.classes.ClassesLoadedHandler;
Expand All @@ -23,42 +26,55 @@
import io.opentelemetry.contrib.jfr.metrics.internal.threads.ThreadCountHandler;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;

final class HandlerRegistry {
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.contrib.jfr";
private static final String INSTRUMENTATION_VERSION = "1.7.0-SNAPSHOT";

private final List<RecordedEventHandler> mappers;

private static final Map<String, List<Supplier<RecordedEventHandler>>> HANDLERS_PER_GC =
Map.of(
"G1",
List.of(G1HeapSummaryHandler::new),
"Parallel",
List.of(ParallelHeapSummaryHandler::new));

private HandlerRegistry(List<? extends RecordedEventHandler> mappers) {
this.mappers = new ArrayList<>(mappers);
}

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

for (var bean : ManagementFactory.getGarbageCollectorMXBeans()) {
var name = bean.getName();
for (var gcType : HANDLERS_PER_GC.keySet()) {
if (name.contains(gcType)
&& !seen.contains(gcType)
&& HANDLERS_PER_GC.get(gcType) != null) {
handlers.addAll(HANDLERS_PER_GC.get(gcType).stream().map(s -> s.get()).toList());
seen.add(gcType);
}
switch (name) {
roberttoyonaga marked this conversation as resolved.
Show resolved Hide resolved
case "G1 Young Generation":
handlers.add(new G1HeapSummaryHandler());
handlers.add(new G1GarbageCollectionHandler());
break;

case "Copy":
handlers.add(new YoungGarbageCollectionHandler(name));
break;

case "PS Scavenge":
handlers.add(new YoungGarbageCollectionHandler(name));
handlers.add(new ParallelHeapSummaryHandler());
break;

case "G1 Old Generation":
handlers.add(new OldGarbageCollectionHandler(name));
break;

case "PS MarkSweep":
handlers.add(new OldGarbageCollectionHandler(name));
break;

case "MarkSweepCompact":
handlers.add(new OldGarbageCollectionHandler(name));
break;

default:
// If none of the above GCs are detected, no action.
}
}

var grouper = new ThreadGrouper();
var basicHandlers =
List.of(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,37 +26,53 @@ private Constants() {}
public static final String REGION_COUNT = "region.count";
public static final String COMMITTED = "committed";
public static final String RESERVED = "reserved";
public static final String INITIAL_SIZE = "initialSize";
public static final String USED = "used";
public static final String COMMITTED_SIZE = "committedSize";
public static final String RESERVED_SIZE = "reservedSize";

public static final String DAEMON = "daemon";
public static final String HEAP = "heap";
public static final String NON_HEAP = "nonheap";
public static final String NETWORK_MODE_READ = "read";
public static final String NETWORK_MODE_WRITE = "write";
public static final String DURATION = "duration";
public static final String END_OF_MINOR_GC = "end of minor GC";
public static final String END_OF_MAJOR_GC = "end of major GC";

public static final String METRIC_NAME_NETWORK_BYTES = "process.runtime.jvm.network.io";
public static final String METRIC_DESCRIPTION_NETWORK_BYTES = "Network read/write bytes";
public static final String METRIC_NAME_NETWORK_DURATION = "process.runtime.jvm.network.time";
public static final String METRIC_DESCRIPTION_NETWORK_DURATION = "Network read/write duration";
public static final String METRIC_NAME_COMMITTED = "process.runtime.jvm.memory.committed";
public static final String METRIC_DESCRIPTION_COMMITTED = "Measure of memory committed";
public static final String NETWORK_MODE_READ = "read";
public static final String NETWORK_MODE_WRITE = "write";
public static final String METRIC_NAME_MEMORY = "process.runtime.jvm.memory.usage";
public static final String METRIC_DESCRIPTION_MEMORY = "Measure of memory used";
public static final String METRIC_NAME_MEMORY_AFTER =
"process.runtime.jvm.memory.usage_after_last_gc";
public static final String METRIC_DESCRIPTION_MEMORY = "Measure of memory used";
public static final String METRIC_DESCRIPTION_MEMORY_AFTER =
"Measure of memory used, as measured after the most recent garbage collection event on this pool";
public static final String METRIC_NAME_MEMORY_ALLOCATION =
"process.runtime.jvm.memory.allocation";
public static final String METRIC_DESCRIPTION_MEMORY_ALLOCATION = "Allocation";
public static final String METRIC_NAME_MEMORY_INIT = "process.runtime.jvm.memory.init";
public static final String METRIC_DESCRIPTION_MEMORY_INIT = "Measure of initial memory requested";
public static final String METRIC_NAME_MEMORY_LIMIT = "process.runtime.jvm.memory.limit";
public static final String METRIC_DESCRIPTION_MEMORY_LIMIT = "Measure of max obtainable memory";
public static final String METRIC_NAME_GC_DURATION = "process.runtime.jvm.gc.duration";
public static final String METRIC_DESCRIPTION_GC_DURATION =
"Duration of JVM garbage collection actions";

public static final AttributeKey<String> ATTR_THREAD_NAME = AttributeKey.stringKey("thread.name");
public static final AttributeKey<String> ATTR_ARENA_NAME = AttributeKey.stringKey("arena");
public static final AttributeKey<String> ATTR_NETWORK_MODE = AttributeKey.stringKey("mode");
public static final AttributeKey<String> ATTR_TYPE = AttributeKey.stringKey("type");
public static final AttributeKey<String> ATTR_POOL = AttributeKey.stringKey("pool");
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 String UNIT_CLASSES = "{classes}";
public static final String UNIT_THREADS = "{threads}";
public static final String UNIT_BUFFERS = "{buffers}";
public static final String UNIT_UTILIZATION = "1";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

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

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.DURATION;
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.METRIC_NAME_GC_DURATION;
import static io.opentelemetry.contrib.jfr.metrics.internal.Constants.MILLISECONDS;
import static io.opentelemetry.contrib.jfr.metrics.internal.RecordedEventHandler.defaultMeter;

import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.metrics.LongHistogram;
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;

public final class G1GarbageCollectionHandler implements RecordedEventHandler {
private static final String EVENT_NAME = "jdk.G1GarbageCollection";
private static final Attributes ATTR =
Attributes.of(ATTR_GC, "G1 Young Generation", ATTR_ACTION, END_OF_MINOR_GC);
private LongHistogram histogram;

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

@Override
public void accept(RecordedEvent ev) {
histogram.record(ev.getLong(DURATION), ATTR);
}

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

@Override
public void initializeMeter(Meter meter) {
histogram =
meter
.histogramBuilder(METRIC_NAME_GC_DURATION)
.setDescription(METRIC_DESCRIPTION_GC_DURATION)
.setUnit(MILLISECONDS)
.ofLongs()
.build();
}

@Override
public Optional<Duration> getPollingDuration() {
return Optional.of(Duration.ofSeconds(1));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

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

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.DURATION;
import static io.opentelemetry.contrib.jfr.metrics.internal.Constants.END_OF_MAJOR_GC;
import static io.opentelemetry.contrib.jfr.metrics.internal.Constants.METRIC_DESCRIPTION_GC_DURATION;
import static io.opentelemetry.contrib.jfr.metrics.internal.Constants.METRIC_NAME_GC_DURATION;
import static io.opentelemetry.contrib.jfr.metrics.internal.Constants.MILLISECONDS;
import static io.opentelemetry.contrib.jfr.metrics.internal.RecordedEventHandler.defaultMeter;

import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.metrics.LongHistogram;
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;

public final class OldGarbageCollectionHandler implements RecordedEventHandler {
private static final String EVENT_NAME = "jdk.OldGarbageCollection";
// This could be changed later
private Attributes attributes =
Attributes.of(ATTR_GC, "MarkSweepCompact", ATTR_ACTION, END_OF_MAJOR_GC);

private LongHistogram histogram;

public OldGarbageCollectionHandler(String gc) {
// Set the attribute's GC based on which GC is being used.
attributes = Attributes.of(ATTR_GC, gc, ATTR_ACTION, END_OF_MAJOR_GC);
initializeMeter(defaultMeter());
}

@Override
public void accept(RecordedEvent ev) {
histogram.record(ev.getLong(DURATION), attributes);
}

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

@Override
public void initializeMeter(Meter meter) {
histogram =
meter
.histogramBuilder(METRIC_NAME_GC_DURATION)
.setDescription(METRIC_DESCRIPTION_GC_DURATION)
.setUnit(MILLISECONDS)
.ofLongs()
.build();
}

@Override
public Optional<Duration> getPollingDuration() {
return Optional.of(Duration.ofSeconds(1));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

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

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.DURATION;
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.METRIC_NAME_GC_DURATION;
import static io.opentelemetry.contrib.jfr.metrics.internal.Constants.MILLISECONDS;
import static io.opentelemetry.contrib.jfr.metrics.internal.RecordedEventHandler.defaultMeter;

import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.metrics.LongHistogram;
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;

public final class YoungGarbageCollectionHandler implements RecordedEventHandler {
private static final String EVENT_NAME = "jdk.YoungGarbageCollection";
private Attributes attributes =
Attributes.of(ATTR_GC, "PS Scavenge", ATTR_ACTION, END_OF_MINOR_GC);
private LongHistogram histogram;

public YoungGarbageCollectionHandler(String gc) {
// Set the attribute's GC based on which GC is being used.
// G1 young collection is already handled by G1GarbageCollectionHandler.
attributes = Attributes.of(ATTR_GC, gc, ATTR_ACTION, END_OF_MINOR_GC);
initializeMeter(defaultMeter());
}

@Override
public void accept(RecordedEvent ev) {
histogram.record(ev.getLong(DURATION), attributes);
}

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

@Override
public void initializeMeter(Meter meter) {
histogram =
meter
.histogramBuilder(METRIC_NAME_GC_DURATION)
.setDescription(METRIC_DESCRIPTION_GC_DURATION)
.setUnit(MILLISECONDS)
.ofLongs()
.build();
}

@Override
public Optional<Duration> getPollingDuration() {
return Optional.of(Duration.ofSeconds(1));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
import io.opentelemetry.sdk.metrics.data.MetricData;
import io.opentelemetry.sdk.testing.assertj.MetricAssert;
import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader;
import java.lang.management.ManagementFactory;
import java.util.Collection;
import java.util.HashSet;
import java.util.function.Consumer;
import org.junit.jupiter.api.BeforeAll;

Expand All @@ -23,6 +25,7 @@ public class AbstractMetricsTest {
static SdkMeterProvider meterProvider;
static InMemoryMetricReader metricReader;
static boolean isInitialized = false;
static HashSet<String> garbageCollectors = new HashSet<String>();

@BeforeAll
static void initializeOpenTelemetry() {
Expand All @@ -34,6 +37,10 @@ static void initializeOpenTelemetry() {
meterProvider = SdkMeterProvider.builder().registerMetricReader(metricReader).build();
GlobalOpenTelemetry.set(OpenTelemetrySdk.builder().setMeterProvider(meterProvider).build());
JfrMetrics.enable(meterProvider);

for (var bean : ManagementFactory.getGarbageCollectorMXBeans()) {
garbageCollectors.add(bean.getName());
}
}

@SafeVarargs
Expand Down
Loading