diff --git a/.github/workflows/lint-rest.yml b/.github/workflows/lint-rest.yml index 89acf1dad..07a61ab2d 100644 --- a/.github/workflows/lint-rest.yml +++ b/.github/workflows/lint-rest.yml @@ -3,15 +3,13 @@ name: Lint What Super Linter Can't on: pull_request: - push: - branches: - - main -permissions: - contents: read +permissions: {} jobs: lint: + permissions: + contents: read runs-on: ubuntu-24.04 steps: - name: Check out @@ -21,10 +19,15 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: jdx/mise-action@6d1e696aa24c1aa1bcc1adea0212707c71ab78a8 # v3.6.1 + - name: Remap main branch URLs to PR branch for link checking + env: + GITHUB_HEAD_REF: ${{ github.head_ref }} + run: | + sed -i "/^remap = \[$/a\ \"https://github.com/prometheus/client_java/blob/main/(.*) https://github.com/prometheus/client_java/blob/${GITHUB_HEAD_REF}/\$1\"," .github/config/lychee.toml + sed -i "/^remap = \[$/a\ \"https://github.com/prometheus/client_java/tree/main/(.*) https://github.com/prometheus/client_java/tree/${GITHUB_HEAD_REF}/\$1\"," .github/config/lychee.toml + - name: Lint for pull requests - if: github.event_name == 'pull_request' env: GITHUB_TOKEN: ${{ github.token }} - GITHUB_BASE_REF: ${{ github.base_ref }} GITHUB_HEAD_SHA: ${{ github.event.pull_request.head.sha }} run: mise run lint:rest-ci diff --git a/docs/content/instrumentation/jvm.md b/docs/content/instrumentation/jvm.md index 804c1b09b..a9a15341f 100644 --- a/docs/content/instrumentation/jvm.md +++ b/docs/content/instrumentation/jvm.md @@ -3,6 +3,16 @@ title: JVM weight: 1 --- +{{< hint type=note >}} + +Looking for JVM metrics that follow OTel semantic +conventions? See +[OTel JVM Runtime Metrics]({{< relref "../otel/jvm-runtime-metrics.md" >}}) +for an alternative based on OpenTelemetry's +runtime-telemetry module. + +{{< /hint >}} + The JVM instrumentation module provides a variety of out-of-the-box JVM and process metrics. To use it, add the following dependency: diff --git a/docs/content/otel/jvm-runtime-metrics.md b/docs/content/otel/jvm-runtime-metrics.md new file mode 100644 index 000000000..4d7904ae0 --- /dev/null +++ b/docs/content/otel/jvm-runtime-metrics.md @@ -0,0 +1,236 @@ +--- +title: JVM Runtime Metrics +weight: 4 +--- + +OpenTelemetry's +[runtime-telemetry](https://github.com/open-telemetry/opentelemetry-java-instrumentation/tree/main/instrumentation/runtime-telemetry) +module is an alternative to +[prometheus-metrics-instrumentation-jvm]({{< relref "../instrumentation/jvm.md" >}}) +for users who want JVM metrics following OTel semantic conventions. + +Key advantages: + +- Metric names follow + [OTel semantic conventions](https://opentelemetry.io/docs/specs/semconv/runtime/jvm-metrics/) +- Java 17+ JFR support (context switches, network I/O, + lock contention, memory allocation) +- Alignment with the broader OTel ecosystem + +Since OpenTelemetry's `opentelemetry-exporter-prometheus` +already depends on this library's `PrometheusRegistry`, +no additional code is needed in this library — only the +OTel SDK wiring shown below. + +## Dependencies + +{{< tabs "jvm-runtime-deps" >}} +{{< tab "Gradle" >}} + +```groovy +implementation 'io.opentelemetry:opentelemetry-sdk' +implementation 'io.opentelemetry:opentelemetry-exporter-prometheus' + +// Pick ONE of the following: +// Java 8+: +implementation 'io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java8' +// Java 17+ (adds JFR-based metrics): +// implementation 'io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java17' +``` + +{{< /tab >}} +{{< tab "Maven" >}} + +```xml + + io.opentelemetry + opentelemetry-sdk + + + io.opentelemetry + opentelemetry-exporter-prometheus + + + + + + io.opentelemetry.instrumentation + opentelemetry-runtime-telemetry-java8 + + + +``` + +{{< /tab >}} +{{< /tabs >}} + +## Standalone Setup + +If you **only** want OTel runtime metrics exposed as +Prometheus, without any Prometheus Java client metrics: + +```java +import io.opentelemetry.exporter.prometheus.PrometheusHttpServer; +import io.opentelemetry.instrumentation.runtimemetrics.java8.RuntimeMetrics; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; + +PrometheusHttpServer prometheusServer = + PrometheusHttpServer.builder() + .setPort(9464) + .build(); + +OpenTelemetrySdk openTelemetry = + OpenTelemetrySdk.builder() + .setMeterProvider( + SdkMeterProvider.builder() + .registerMetricReader(prometheusServer) + .build()) + .build(); + +RuntimeMetrics runtimeMetrics = + RuntimeMetrics.builder(openTelemetry).build(); + +// Close on shutdown to stop JMX metric collection +Runtime.getRuntime() + .addShutdownHook(new Thread(runtimeMetrics::close)); + +// Scrape at http://localhost:9464/metrics +``` + +## Combined with Prometheus Java Client Metrics + +If you already have Prometheus Java client metrics and want to +add OTel runtime metrics to the **same** `/metrics` +endpoint, use `PrometheusMetricReader` to bridge OTel +metrics into a `PrometheusRegistry`: + +```java +import io.prometheus.metrics.core.metrics.Counter; +import io.prometheus.metrics.exporter.httpserver.HTTPServer; +import io.prometheus.metrics.model.registry.PrometheusRegistry; +import io.opentelemetry.exporter.prometheus.PrometheusMetricReader; +import io.opentelemetry.instrumentation.runtimemetrics.java8.RuntimeMetrics; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; + +PrometheusRegistry registry = + new PrometheusRegistry(); + +// Register Prometheus metrics as usual +Counter myCounter = Counter.builder() + .name("my_requests_total") + .register(registry); + +// Bridge OTel metrics into the same registry +PrometheusMetricReader reader = + PrometheusMetricReader.create(); +registry.register(reader); + +OpenTelemetrySdk openTelemetry = + OpenTelemetrySdk.builder() + .setMeterProvider( + SdkMeterProvider.builder() + .registerMetricReader(reader) + .build()) + .build(); + +RuntimeMetrics runtimeMetrics = + RuntimeMetrics.builder(openTelemetry).build(); +Runtime.getRuntime() + .addShutdownHook(new Thread(runtimeMetrics::close)); + +// Expose everything on one endpoint +HTTPServer.builder() + .port(9400) + .registry(registry) + .buildAndStart(); +``` + +The [examples/example-otel-jvm-runtime-metrics](https://github.com/prometheus/client_java/tree/main/examples/example-otel-jvm-runtime-metrics) +directory has a complete runnable example. + +## Configuration + +The `RuntimeMetricsBuilder` supports two configuration +options: + +### `captureGcCause()` + +Adds a `jvm.gc.cause` attribute to the `jvm.gc.duration` +metric, indicating why the garbage collection occurred +(e.g. `G1 Evacuation Pause`, `System.gc()`): + +```java +RuntimeMetrics.builder(openTelemetry) + .captureGcCause() + .build(); +``` + +### `emitExperimentalTelemetry()` + +Enables additional experimental metrics beyond the stable +set. These are not yet part of the OTel semantic conventions +and may change in future releases: + +- Buffer pool metrics (direct and mapped byte buffers) +- Extended CPU metrics +- Extended memory pool metrics +- File descriptor metrics + +```java +RuntimeMetrics.builder(openTelemetry) + .emitExperimentalTelemetry() + .build(); +``` + +Both options can be combined: + +```java +RuntimeMetrics.builder(openTelemetry) + .captureGcCause() + .emitExperimentalTelemetry() + .build(); +``` + +Selective per-metric registration is not supported by the +runtime-telemetry API — it is all-or-nothing with these +two toggles. + +## Java 17 JFR Support + +The `opentelemetry-runtime-telemetry-java17` variant adds +JFR-based metrics. You can selectively enable features: + +```java +import io.opentelemetry.instrumentation.runtimemetrics.java17.JfrFeature; +import io.opentelemetry.instrumentation.runtimemetrics.java17.RuntimeMetrics; + +RuntimeMetrics.builder(openTelemetry) + .enableFeature(JfrFeature.BUFFER_METRICS) + .enableFeature(JfrFeature.NETWORK_IO_METRICS) + .enableFeature(JfrFeature.LOCK_METRICS) + .enableFeature(JfrFeature.CONTEXT_SWITCH_METRICS) + .build(); +``` + +## Metric Names + +OTel metric names are converted to Prometheus format by +the exporter. Examples: + +| OTel name | Prometheus name | +| ---------------------------- | ---------------------------------- | +| `jvm.memory.used` | `jvm_memory_used_bytes` | +| `jvm.gc.duration` | `jvm_gc_duration_seconds` | +| `jvm.thread.count` | `jvm_thread_count` | +| `jvm.class.loaded` | `jvm_class_loaded` | +| `jvm.cpu.recent_utilization` | `jvm_cpu_recent_utilization_ratio` | + +See [Names]({{< relref "names.md" >}}) for full details on +how OTel names map to Prometheus names. diff --git a/examples/example-otel-jvm-runtime-metrics/README.md b/examples/example-otel-jvm-runtime-metrics/README.md new file mode 100644 index 000000000..a58584694 --- /dev/null +++ b/examples/example-otel-jvm-runtime-metrics/README.md @@ -0,0 +1,41 @@ +# OTel JVM Runtime Metrics with Prometheus HTTPServer + +## Build + +This example is built as part of the `client_java` project. + +```shell +./mvnw package +``` + +## Run + +The build creates a JAR file with the example application in +`./examples/example-otel-jvm-runtime-metrics/target/`. + +```shell +java -jar ./examples/example-otel-jvm-runtime-metrics/target/example-otel-jvm-runtime-metrics.jar +``` + +## Manually Testing the Metrics Endpoint + +Accessing +[http://localhost:9400/metrics](http://localhost:9400/metrics) +with a Web browser should yield both a Prometheus counter metric +and OTel JVM runtime metrics on the same endpoint. + +Prometheus counter: + +```text +# HELP uptime_seconds_total total number of seconds since this application was started +# TYPE uptime_seconds_total counter +uptime_seconds_total 42.0 +``` + +OTel JVM runtime metrics (excerpt): + +```text +# HELP jvm_memory_used_bytes Measure of memory used. +# TYPE jvm_memory_used_bytes gauge +jvm_memory_used_bytes{jvm_memory_pool_name="G1 Eden Space",jvm_memory_type="heap"} 4194304.0 +``` diff --git a/examples/example-otel-jvm-runtime-metrics/pom.xml b/examples/example-otel-jvm-runtime-metrics/pom.xml new file mode 100644 index 000000000..e2059e811 --- /dev/null +++ b/examples/example-otel-jvm-runtime-metrics/pom.xml @@ -0,0 +1,82 @@ + + + 4.0.0 + + + io.prometheus + examples + 1.5.0-SNAPSHOT + + + example-otel-jvm-runtime-metrics + + Example - OTel JVM Runtime Metrics + + Example of combining Prometheus metrics with OpenTelemetry JVM runtime metrics on one endpoint + + + + + + io.opentelemetry.instrumentation + opentelemetry-instrumentation-bom-alpha + 2.24.0-alpha + pom + import + + + + + + + io.prometheus + prometheus-metrics-core + ${project.version} + + + io.prometheus + prometheus-metrics-exporter-httpserver + ${project.version} + + + io.opentelemetry + opentelemetry-sdk + + + io.opentelemetry + opentelemetry-exporter-prometheus + + + io.opentelemetry.instrumentation + opentelemetry-runtime-telemetry-java8 + + + + + ${project.artifactId} + + + org.apache.maven.plugins + maven-shade-plugin + + + package + + shade + + + + + io.prometheus.metrics.examples.otelruntimemetrics.Main + + + + + + + + + diff --git a/examples/example-otel-jvm-runtime-metrics/src/main/java/io/prometheus/metrics/examples/otelruntimemetrics/Main.java b/examples/example-otel-jvm-runtime-metrics/src/main/java/io/prometheus/metrics/examples/otelruntimemetrics/Main.java new file mode 100644 index 000000000..9adf15d28 --- /dev/null +++ b/examples/example-otel-jvm-runtime-metrics/src/main/java/io/prometheus/metrics/examples/otelruntimemetrics/Main.java @@ -0,0 +1,70 @@ +package io.prometheus.metrics.examples.otelruntimemetrics; + +import io.opentelemetry.exporter.prometheus.PrometheusMetricReader; +import io.opentelemetry.instrumentation.runtimemetrics.java8.RuntimeMetrics; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.prometheus.metrics.core.metrics.Counter; +import io.prometheus.metrics.exporter.httpserver.HTTPServer; +import io.prometheus.metrics.model.registry.PrometheusRegistry; +import io.prometheus.metrics.model.snapshots.Unit; +import java.io.IOException; + +/** + * Example combining Prometheus metrics with OpenTelemetry JVM runtime metrics on a single endpoint. + * + *

This demonstrates: + * + *

+ */ +public class Main { + + public static void main(String[] args) throws IOException, InterruptedException { + + PrometheusRegistry registry = new PrometheusRegistry(); + + // 1. Register a Prometheus counter metric + Counter counter = + Counter.builder() + .name("uptime_seconds_total") + .help("total number of seconds since this application was started") + .unit(Unit.SECONDS) + .register(registry); + + // 2. Create a PrometheusMetricReader and register it with the same registry. + // This bridges OTel metrics into the Prometheus registry. + PrometheusMetricReader reader = PrometheusMetricReader.create(); + registry.register(reader); + + // 3. Build the OTel SDK with the reader. + OpenTelemetrySdk openTelemetry = + OpenTelemetrySdk.builder() + .setMeterProvider(SdkMeterProvider.builder().registerMetricReader(reader).build()) + .build(); + + // 4. Start OTel JVM runtime metrics collection. + // - captureGcCause() adds a jvm.gc.cause attribute to jvm.gc.duration + // - emitExperimentalTelemetry() enables buffer pools, extended CPU, + // extended memory pools, and file descriptor metrics + RuntimeMetrics runtimeMetrics = + RuntimeMetrics.builder(openTelemetry).captureGcCause().emitExperimentalTelemetry().build(); + + // 5. Close RuntimeMetrics on shutdown to stop JMX metric collection. + Runtime.getRuntime().addShutdownHook(new Thread(runtimeMetrics::close)); + + // 6. Expose both Prometheus and OTel metrics on a single endpoint. + HTTPServer server = HTTPServer.builder().port(9400).registry(registry).buildAndStart(); + + System.out.println( + "HTTPServer listening on port http://localhost:" + server.getPort() + "/metrics"); + + while (true) { + Thread.sleep(1000); + counter.inc(); + } + } +} diff --git a/examples/pom.xml b/examples/pom.xml index d0c364067..1b0059655 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -32,6 +32,7 @@ example-native-histogram example-custom-buckets example-prometheus-properties + example-otel-jvm-runtime-metrics