Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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 @@ -29,18 +29,20 @@ runtimeOnly("io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-ja

### Usage

Register observers for the desired runtime metrics:
Register JVM runtime metrics:

```java
OpenTelemetry openTelemetry = // OpenTelemetry instance configured elsewhere

Classes.registerObservers(openTelemetry);
Cpu.registerObservers(openTelemetry);
MemoryPools.registerObservers(openTelemetry);
Threads.registerObservers(openTelemetry);
GarbageCollector.registerObservers(openTelemetry);
RuntimeMetrics runtimeMetrics = RuntimeMetrics.create(openTelemetry);

// When done, close to stop metric collection
runtimeMetrics.close();
```

To select specific metrics, configure [metric views](https://opentelemetry.io/docs/specs/otel/metrics/sdk/#view)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do we have an example implementation with Java SDK we could link to ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

on the SDK to filter which metrics are exported.

## Garbage Collector Dependent Metrics

The attributes reported on the memory metrics (`jvm.memory.*`) and gc metrics (`jvm.gc.*`) are dependent on the garbage collector used by the application, since each garbage collector organizes memory pools differently and has different strategies for reclaiming memory during garbage collection.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,74 +6,21 @@
package io.opentelemetry.instrumentation.runtimemetrics.java8;

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.JmxRuntimeMetricsUtil;
import java.lang.management.ClassLoadingMXBean;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.List;

/**
* Registers measurements that generate metrics about JVM classes. The metrics generated by this
* class follow <a
* href="https://github.com/open-telemetry/semantic-conventions/blob/main/docs/runtime/jvm-metrics.md">the
* stable JVM metrics semantic conventions</a>.
* Registers measurements that generate metrics about JVM classes.
*
* <p>Example usage:
*
* <pre>{@code
* Classes.registerObservers(GlobalOpenTelemetry.get());
* }</pre>
*
* <p>Example metrics being exported:
*
* <pre>
* jvm.class.loaded 100
* jvm.class.unloaded 2
* jvm.class.count 98
* </pre>
* @deprecated Use {@link RuntimeMetrics} instead, and configure metric views to select specific
* metrics.
*/
@Deprecated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

if your goal is just to move these to internal then maybe it would be easier to move the class, drop final, add deprecated class in original location that extends the moved class

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

done in cfbec4d

public final class Classes {

// Visible for testing
static final Classes INSTANCE = new Classes();

/** Register observers for java runtime class metrics. */
public static List<AutoCloseable> registerObservers(OpenTelemetry openTelemetry) {
return INSTANCE.registerObservers(openTelemetry, ManagementFactory.getClassLoadingMXBean());
}

// Visible for testing
List<AutoCloseable> registerObservers(OpenTelemetry openTelemetry, ClassLoadingMXBean classBean) {
Meter meter = JmxRuntimeMetricsUtil.getMeter(openTelemetry);
List<AutoCloseable> observables = new ArrayList<>();

observables.add(
meter
.counterBuilder("jvm.class.loaded")
.setDescription("Number of classes loaded since JVM start.")
.setUnit("{class}")
.buildWithCallback(
observableMeasurement ->
observableMeasurement.record(classBean.getTotalLoadedClassCount())));
observables.add(
meter
.counterBuilder("jvm.class.unloaded")
.setDescription("Number of classes unloaded since JVM start.")
.setUnit("{class}")
.buildWithCallback(
observableMeasurement ->
observableMeasurement.record(classBean.getUnloadedClassCount())));
observables.add(
meter
.upDownCounterBuilder("jvm.class.count")
.setDescription("Number of classes currently loaded.")
.setUnit("{class}")
.buildWithCallback(
observableMeasurement ->
observableMeasurement.record(classBean.getLoadedClassCount())));

return observables;
return io.opentelemetry.instrumentation.runtimemetrics.java8.internal.Classes.registerObservers(
openTelemetry);
}

private Classes() {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,100 +6,21 @@
package io.opentelemetry.instrumentation.runtimemetrics.java8;

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.CpuMethods;
import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.JmxRuntimeMetricsUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.IntSupplier;
import java.util.function.Supplier;
import javax.annotation.Nullable;

/**
* Registers measurements that generate metrics about CPU. The metrics generated by this class
* follow <a
* href="https://github.com/open-telemetry/semantic-conventions/blob/main/docs/runtime/jvm-metrics.md">the
* stable JVM metrics semantic conventions</a>.
* Registers measurements that generate metrics about CPU.
*
* <p>Example usage:
*
* <pre>{@code
* Cpu.registerObservers(GlobalOpenTelemetry.get());
* }</pre>
*
* <p>Example metrics being exported:
*
* <pre>
* jvm.cpu.time 20.42
* jvm.cpu.count 8
* jvm.cpu.recent_utilization 0.1
* </pre>
* @deprecated Use {@link RuntimeMetrics} instead, and configure metric views to select specific
* metrics.
*/
@Deprecated
public final class Cpu {

// Visible for testing
static final Cpu INSTANCE = new Cpu();

private static final double NANOS_PER_S = TimeUnit.SECONDS.toNanos(1);

/** Register observers for java runtime CPU metrics. */
public static List<AutoCloseable> registerObservers(OpenTelemetry openTelemetry) {
return INSTANCE.registerObservers(
openTelemetry,
Runtime.getRuntime()::availableProcessors,
CpuMethods.processCpuTime(),
CpuMethods.processCpuUtilization());
}

// Visible for testing
List<AutoCloseable> registerObservers(
OpenTelemetry openTelemetry,
IntSupplier availableProcessors,
@Nullable Supplier<Long> processCpuTime,
@Nullable Supplier<Double> processCpuUtilization) {
Meter meter = JmxRuntimeMetricsUtil.getMeter(openTelemetry);
List<AutoCloseable> observables = new ArrayList<>();

if (processCpuTime != null) {
observables.add(
meter
.counterBuilder("jvm.cpu.time")
.ofDoubles()
.setDescription("CPU time used by the process as reported by the JVM.")
.setUnit("s")
.buildWithCallback(
observableMeasurement -> {
Long cpuTimeNanos = processCpuTime.get();
if (cpuTimeNanos != null && cpuTimeNanos >= 0) {
observableMeasurement.record(cpuTimeNanos / NANOS_PER_S);
}
}));
}
observables.add(
meter
.upDownCounterBuilder("jvm.cpu.count")
.setDescription("Number of processors available to the Java virtual machine.")
.setUnit("{cpu}")
.buildWithCallback(
observableMeasurement ->
observableMeasurement.record(availableProcessors.getAsInt())));
if (processCpuUtilization != null) {
observables.add(
meter
.gaugeBuilder("jvm.cpu.recent_utilization")
.setDescription("Recent CPU utilization for the process as reported by the JVM.")
.setUnit("1")
.buildWithCallback(
observableMeasurement -> {
Double cpuUsage = processCpuUtilization.get();
if (cpuUsage != null && cpuUsage >= 0) {
observableMeasurement.record(cpuUsage);
}
}));
}

return observables;
return io.opentelemetry.instrumentation.runtimemetrics.java8.internal.Cpu.registerObservers(
openTelemetry);
}

private Cpu() {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,170 +5,23 @@

package io.opentelemetry.instrumentation.runtimemetrics.java8;

import static java.util.Arrays.asList;
import static java.util.Collections.unmodifiableList;

import com.sun.management.GarbageCollectionNotificationInfo;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.api.metrics.DoubleHistogram;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.instrumentation.runtimemetrics.java8.internal.JmxRuntimeMetricsUtil;
import io.opentelemetry.semconv.JvmAttributes;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.logging.Logger;
import javax.management.Notification;
import javax.management.NotificationEmitter;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.openmbean.CompositeData;

/**
* Registers instruments that generate metrics about JVM garbage collection. The metrics generated
* by this class follow <a
* href="https://github.com/open-telemetry/semantic-conventions/blob/main/docs/runtime/jvm-metrics.md">the
* stable JVM metrics semantic conventions</a>.
*
* <p>Example usage:
*
* <pre>{@code
* GarbageCollector.registerObservers(GlobalOpenTelemetry.get());
* }</pre>
* Registers instruments that generate metrics about JVM garbage collection.
*
* <p>Example metrics being exported:
*
* <pre>
* jvm.gc.duration{jvm.gc.name="G1 Young Generation",jvm.gc.action="end of minor GC"} 0.022
* </pre>
* @deprecated Use {@link RuntimeMetrics} instead, and configure metric views to select specific
* metrics.
*/
@Deprecated
public final class GarbageCollector {

private static final Logger logger = Logger.getLogger(GarbageCollector.class.getName());

private static final double MILLIS_PER_S = TimeUnit.SECONDS.toMillis(1);

static final List<Double> GC_DURATION_BUCKETS = unmodifiableList(asList(0.01, 0.1, 1., 10.));

private static final AttributeKey<String> JVM_GC_CAUSE = AttributeKey.stringKey("jvm.gc.cause");

private static final NotificationFilter GC_FILTER =
notification ->
notification
.getType()
.equals(GarbageCollectionNotificationInfo.GARBAGE_COLLECTION_NOTIFICATION);

/** Register observers for java runtime memory metrics. */
public static List<AutoCloseable> registerObservers(
OpenTelemetry openTelemetry, boolean captureGcCause) {
if (!isNotificationClassPresent()) {
logger.fine(
"The com.sun.management.GarbageCollectionNotificationInfo class is not available;"
+ " GC metrics will not be reported.");
return Collections.emptyList();
}

return registerObservers(
openTelemetry,
ManagementFactory.getGarbageCollectorMXBeans(),
GarbageCollector::extractNotificationInfo,
captureGcCause);
}

// Visible for testing
static List<AutoCloseable> registerObservers(
OpenTelemetry openTelemetry,
List<GarbageCollectorMXBean> gcBeans,
Function<Notification, GarbageCollectionNotificationInfo> notificationInfoExtractor,
boolean captureGcCause) {
Meter meter = JmxRuntimeMetricsUtil.getMeter(openTelemetry);

DoubleHistogram gcDuration =
meter
.histogramBuilder("jvm.gc.duration")
.setDescription("Duration of JVM garbage collection actions.")
.setUnit("s")
.setExplicitBucketBoundariesAdvice(GC_DURATION_BUCKETS)
.build();

List<AutoCloseable> result = new ArrayList<>();
for (GarbageCollectorMXBean gcBean : gcBeans) {
if (!(gcBean instanceof NotificationEmitter)) {
continue;
}
NotificationEmitter notificationEmitter = (NotificationEmitter) gcBean;
GcNotificationListener listener =
new GcNotificationListener(gcDuration, notificationInfoExtractor, captureGcCause);
notificationEmitter.addNotificationListener(listener, GC_FILTER, null);
result.add(() -> notificationEmitter.removeNotificationListener(listener));
}
return result;
}

private static final class GcNotificationListener implements NotificationListener {

private final boolean captureGcCause;
private final DoubleHistogram gcDuration;
private final Function<Notification, GarbageCollectionNotificationInfo>
notificationInfoExtractor;

private GcNotificationListener(
DoubleHistogram gcDuration,
Function<Notification, GarbageCollectionNotificationInfo> notificationInfoExtractor,
boolean captureGcCause) {
this.captureGcCause = captureGcCause;
this.gcDuration = gcDuration;
this.notificationInfoExtractor = notificationInfoExtractor;
}

@Override
public void handleNotification(Notification notification, Object unused) {
GarbageCollectionNotificationInfo notificationInfo =
notificationInfoExtractor.apply(notification);

String gcName = notificationInfo.getGcName();
String gcAction = notificationInfo.getGcAction();
double duration = notificationInfo.getGcInfo().getDuration() / MILLIS_PER_S;
AttributesBuilder builder = Attributes.builder();
builder.put(JvmAttributes.JVM_GC_NAME, gcName);
builder.put(JvmAttributes.JVM_GC_ACTION, gcAction);
if (captureGcCause) {
String gcCause = notificationInfo.getGcCause();
builder.put(JVM_GC_CAUSE, gcCause);
}
gcDuration.record(duration, builder.build());
}
}

/**
* Extract {@link GarbageCollectionNotificationInfo} from the {@link Notification}.
*
* <p>Note: this exists as a separate function so that the behavior can be overridden with mocks
* in tests. It's very challenging to create a mock {@link CompositeData} that can be parsed by
* {@link GarbageCollectionNotificationInfo#from(CompositeData)}.
*/
private static GarbageCollectionNotificationInfo extractNotificationInfo(
Notification notification) {
return GarbageCollectionNotificationInfo.from((CompositeData) notification.getUserData());
}

private static boolean isNotificationClassPresent() {
try {
Class.forName(
"com.sun.management.GarbageCollectionNotificationInfo",
false,
GarbageCollectorMXBean.class.getClassLoader());
return true;
} catch (ClassNotFoundException e) {
return false;
}
return io.opentelemetry.instrumentation.runtimemetrics.java8.internal.GarbageCollector
.registerObservers(openTelemetry, captureGcCause);
}

private GarbageCollector() {}
Expand Down
Loading
Loading