From 7bba258c1ea902d65a4fc3506c249dc20bfcc2d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B8ydahl?= Date: Wed, 5 Aug 2026 12:36:02 +0200 Subject: [PATCH 1/5] Fix consumers of renamed metric jvm_system_cpu_utilization The OpenTelemetry Prometheus exporter 1.63 (updated in #4594) no longer maps unit "1" to a "_ratio" name suffix, so the JVM metric previously exposed as jvm_system_cpu_utilization_ratio is now exposed as jvm_system_cpu_utilization. Update the three consumers still using the old name: NodeMetricImpl.SYSLOAD_AVG and NodeValueFetcher.SYSLOADAVG (broke PlacementPluginIntegrationTest.testAttributeFetcherImpl / replica placement attribute fetching) and CPUCircuitBreaker (silently returned -1 for CPU usage). --- .../unreleased/fix-jvm-cpu-utilization-metric-name.yml | 7 +++++++ .../apache/solr/cluster/placement/impl/NodeMetricImpl.java | 2 +- .../apache/solr/util/circuitbreaker/CPUCircuitBreaker.java | 2 +- .../apache/solr/client/solrj/impl/NodeValueFetcher.java | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 changelog/unreleased/fix-jvm-cpu-utilization-metric-name.yml diff --git a/changelog/unreleased/fix-jvm-cpu-utilization-metric-name.yml b/changelog/unreleased/fix-jvm-cpu-utilization-metric-name.yml new file mode 100644 index 000000000000..41690635d7db --- /dev/null +++ b/changelog/unreleased/fix-jvm-cpu-utilization-metric-name.yml @@ -0,0 +1,7 @@ +title: > + Fixed replica placement attribute fetching (sysLoadAvg) and the CPU circuit breaker, + which stopped working after the OpenTelemetry update renamed the Prometheus metric + `jvm_system_cpu_utilization_ratio` to `jvm_system_cpu_utilization`. +type: fixed +authors: + - name: Jan Høydahl diff --git a/solr/core/src/java/org/apache/solr/cluster/placement/impl/NodeMetricImpl.java b/solr/core/src/java/org/apache/solr/cluster/placement/impl/NodeMetricImpl.java index 4f76896a320b..96ba2d1b6764 100644 --- a/solr/core/src/java/org/apache/solr/cluster/placement/impl/NodeMetricImpl.java +++ b/solr/core/src/java/org/apache/solr/cluster/placement/impl/NodeMetricImpl.java @@ -38,7 +38,7 @@ public class NodeMetricImpl extends MetricImpl implements NodeMetric { /** System load average. */ public static final NodeMetricImpl SYSLOAD_AVG = - new NodeMetricImpl<>("sysLoadAvg", "jvm_system_cpu_utilization_ratio"); + new NodeMetricImpl<>("sysLoadAvg", "jvm_system_cpu_utilization"); /** Number of available processors. */ public static final NodeMetricImpl AVAILABLE_PROCESSORS = diff --git a/solr/core/src/java/org/apache/solr/util/circuitbreaker/CPUCircuitBreaker.java b/solr/core/src/java/org/apache/solr/util/circuitbreaker/CPUCircuitBreaker.java index 09248363bcdb..391f88049dce 100644 --- a/solr/core/src/java/org/apache/solr/util/circuitbreaker/CPUCircuitBreaker.java +++ b/solr/core/src/java/org/apache/solr/util/circuitbreaker/CPUCircuitBreaker.java @@ -112,7 +112,7 @@ protected double calculateLiveCPUUsage() { return this.cc .getMetricManager() .getPrometheusMetricReader("solr.jvm") - .collect(name -> name.contains("jvm_system_cpu_utilization_ratio")) + .collect(name -> name.contains("jvm_system_cpu_utilization")) .stream() .filter(GaugeSnapshot.class::isInstance) .map(GaugeSnapshot.class::cast) diff --git a/solr/solrj-zookeeper/src/java/org/apache/solr/client/solrj/impl/NodeValueFetcher.java b/solr/solrj-zookeeper/src/java/org/apache/solr/client/solrj/impl/NodeValueFetcher.java index 3dd91fca5d30..abd31c5d86c8 100644 --- a/solr/solrj-zookeeper/src/java/org/apache/solr/client/solrj/impl/NodeValueFetcher.java +++ b/solr/solrj-zookeeper/src/java/org/apache/solr/client/solrj/impl/NodeValueFetcher.java @@ -74,7 +74,7 @@ public Object extractFromPrometheus(List prometheusLines) { .sum(); } }, - SYSLOADAVG("sysLoadAvg", "jvm_system_cpu_utilization_ratio"); + SYSLOADAVG("sysLoadAvg", "jvm_system_cpu_utilization"); public final String tagName; public final String metricName; From dcce1dea41439a52b6ef1e923a9b22709d2d625b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B8ydahl?= Date: Wed, 5 Aug 2026 12:55:32 +0200 Subject: [PATCH 2/5] Expose legacy _ratio aliases for renamed JVM CPU utilization metrics The OTel Prometheus exporter rename of jvm_system_cpu_utilization_ratio and jvm_cpu_recent_utilization_ratio (unit "1" no longer maps to a _ratio suffix) breaks dashboards, alerts and API clients using the old names. FilterablePrometheusMetricReader now duplicates these gauges under their legacy names, in both full scrapes and name-filtered /admin/metrics requests. --- .../fix-jvm-cpu-utilization-metric-name.yml | 5 ++- .../FilterablePrometheusMetricReader.java | 41 ++++++++++++++++-- .../apache/solr/metrics/JvmMetricsTest.java | 37 ++++++++++++++++ .../FilterablePrometheusMetricReaderTest.java | 43 +++++++++++++++++++ 4 files changed, 122 insertions(+), 4 deletions(-) diff --git a/changelog/unreleased/fix-jvm-cpu-utilization-metric-name.yml b/changelog/unreleased/fix-jvm-cpu-utilization-metric-name.yml index 41690635d7db..7fa2fef6d4a1 100644 --- a/changelog/unreleased/fix-jvm-cpu-utilization-metric-name.yml +++ b/changelog/unreleased/fix-jvm-cpu-utilization-metric-name.yml @@ -1,7 +1,10 @@ title: > Fixed replica placement attribute fetching (sysLoadAvg) and the CPU circuit breaker, which stopped working after the OpenTelemetry update renamed the Prometheus metric - `jvm_system_cpu_utilization_ratio` to `jvm_system_cpu_utilization`. + `jvm_system_cpu_utilization_ratio` to `jvm_system_cpu_utilization` (and + `jvm_cpu_recent_utilization_ratio` to `jvm_cpu_recent_utilization`). The old + `_ratio` names are still exposed as deprecated aliases so existing dashboards + and alerts keep working. type: fixed authors: - name: Jan Høydahl diff --git a/solr/core/src/java/org/apache/solr/metrics/otel/FilterablePrometheusMetricReader.java b/solr/core/src/java/org/apache/solr/metrics/otel/FilterablePrometheusMetricReader.java index 6461b3bb52a8..25e5a90af4c9 100644 --- a/solr/core/src/java/org/apache/solr/metrics/otel/FilterablePrometheusMetricReader.java +++ b/solr/core/src/java/org/apache/solr/metrics/otel/FilterablePrometheusMetricReader.java @@ -25,6 +25,7 @@ import io.prometheus.metrics.model.snapshots.HistogramSnapshot; import io.prometheus.metrics.model.snapshots.InfoSnapshot; import io.prometheus.metrics.model.snapshots.Labels; +import io.prometheus.metrics.model.snapshots.MetricMetadata; import io.prometheus.metrics.model.snapshots.MetricSnapshot; import io.prometheus.metrics.model.snapshots.MetricSnapshots; import java.lang.invoke.MethodHandles; @@ -44,11 +45,45 @@ public class FilterablePrometheusMetricReader extends PrometheusMetricReader { private static final Set PROM_SUFFIXES = Set.of("_total", "_sum", "_bucket", "_created", "_info"); + // The OTel Prometheus exporter no longer (since 1.57) maps the OTel unit "1" to a "_ratio" + // name suffix, which renamed these JVM metrics. Also expose them under the old names so + // existing dashboards, alerts and API clients keep working. + static final Map LEGACY_NAME_ALIASES = + Map.of( + "jvm_system_cpu_utilization", "jvm_system_cpu_utilization_ratio", + "jvm_cpu_recent_utilization", "jvm_cpu_recent_utilization_ratio"); + public FilterablePrometheusMetricReader( boolean otelScopeEnabled, Predicate allowedResourceAttributesFilter) { super(otelScopeEnabled, allowedResourceAttributesFilter); } + @Override + public MetricSnapshots collect() { + return withLegacyAliases(super.collect()); + } + + /** Duplicates snapshots named in {@link #LEGACY_NAME_ALIASES} under their legacy name. */ + static MetricSnapshots withLegacyAliases(MetricSnapshots snapshots) { + MetricSnapshots.Builder builder = MetricSnapshots.builder(); + for (MetricSnapshot snapshot : snapshots) { + builder.metricSnapshot(snapshot); + } + for (MetricSnapshot snapshot : snapshots) { + String legacyName = LEGACY_NAME_ALIASES.get(snapshot.getMetadata().getPrometheusName()); + if (legacyName != null + && snapshot instanceof GaugeSnapshot gauge + && !builder.containsMetricName(legacyName)) { + MetricMetadata metadata = gauge.getMetadata(); + builder.metricSnapshot( + new GaugeSnapshot( + new MetricMetadata(legacyName, metadata.getHelp(), metadata.getUnit()), + gauge.getDataPoints())); + } + } + return builder.build(); + } + /** * Collect metrics with filtering support for metric names and labels. * @@ -62,7 +97,7 @@ public MetricSnapshots collect( // If no filtering is requested then return all metrics if (includedNames.isEmpty() && requiredLabels.isEmpty()) { - return super.collect(); + return collect(); } // Users may filter by Prometheus-format names (e.g. "solr_core_requests") or with a @@ -83,13 +118,13 @@ public MetricSnapshots collect( MetricSnapshots snapshotsToFilter; if (sanitizedNames.isEmpty()) { - snapshotsToFilter = super.collect(); + snapshotsToFilter = collect(); } else { // We collect all metrics and filter by Prometheus name rather than using // super.collect(Predicate) which matches on OTel internal names. This avoids a mismatch // when OTel names use dot-separators (e.g. "solr.core.requests") but users filter by the // Prometheus underscore-format name they see in the output (e.g. "solr_core_requests"). - MetricSnapshots all = super.collect(); + MetricSnapshots all = collect(); MetricSnapshots.Builder nameFiltered = MetricSnapshots.builder(); for (MetricSnapshot snapshot : all) { if (sanitizedNames.contains(snapshot.getMetadata().getPrometheusName())) { diff --git a/solr/core/src/test/org/apache/solr/metrics/JvmMetricsTest.java b/solr/core/src/test/org/apache/solr/metrics/JvmMetricsTest.java index f099ba7d1d3a..4052c2844569 100644 --- a/solr/core/src/test/org/apache/solr/metrics/JvmMetricsTest.java +++ b/solr/core/src/test/org/apache/solr/metrics/JvmMetricsTest.java @@ -25,6 +25,7 @@ import java.nio.file.Path; import java.util.Arrays; import java.util.Set; +import java.util.TreeMap; import java.util.stream.Collectors; import org.apache.lucene.util.SuppressForbidden; import org.apache.solr.SolrTestCaseJ4; @@ -110,6 +111,42 @@ public void testSetupJvmMetrics() throws InterruptedException { metricNames.stream().anyMatch(name -> name.startsWith("jvm_buffer"))); } + @Test + public void testLegacyCpuUtilizationAliases() { + var reader = + solrTestRule + .getJetty() + .getCoreContainer() + .getMetricManager() + .getPrometheusMetricReader("solr.jvm"); + Set names = + reader.collect().stream() + .map(metric -> metric.getMetadata().getPrometheusName()) + .collect(Collectors.toSet()); + + // The JFR-based CPU metrics may be unavailable in exotic environments; skip if so + Assume.assumeTrue( + "Skipping: jvm_system_cpu_utilization not available", + names.contains("jvm_system_cpu_utilization")); + + // Legacy names from before the OTel exporter dropped the "_ratio" unit suffix + assertTrue( + "Should expose legacy alias jvm_system_cpu_utilization_ratio", + names.contains("jvm_system_cpu_utilization_ratio")); + assertTrue( + "Should expose legacy alias jvm_cpu_recent_utilization_ratio", + names.contains("jvm_cpu_recent_utilization_ratio")); + + // The legacy name also works with the name filter used by /admin/metrics + MetricSnapshots filtered = + reader.collect(Set.of("jvm_system_cpu_utilization_ratio"), new TreeMap<>()); + assertEquals( + Set.of("jvm_system_cpu_utilization_ratio"), + filtered.stream() + .map(metric -> metric.getMetadata().getPrometheusName()) + .collect(Collectors.toSet())); + } + @Test @SuppressForbidden(reason = "Testing com.sun.management.OperatingSystemMXBean availability") public void testSystemMemoryMetrics() { diff --git a/solr/core/src/test/org/apache/solr/metrics/otel/FilterablePrometheusMetricReaderTest.java b/solr/core/src/test/org/apache/solr/metrics/otel/FilterablePrometheusMetricReaderTest.java index ec91e149b4be..259cb8ade0f7 100644 --- a/solr/core/src/test/org/apache/solr/metrics/otel/FilterablePrometheusMetricReaderTest.java +++ b/solr/core/src/test/org/apache/solr/metrics/otel/FilterablePrometheusMetricReaderTest.java @@ -16,11 +16,16 @@ */ package org.apache.solr.metrics.otel; +import io.prometheus.metrics.model.snapshots.GaugeSnapshot; import io.prometheus.metrics.model.snapshots.Labels; +import io.prometheus.metrics.model.snapshots.MetricMetadata; +import io.prometheus.metrics.model.snapshots.MetricSnapshots; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; +import java.util.stream.Collectors; import org.apache.solr.SolrTestCaseJ4; import org.junit.Test; @@ -92,4 +97,42 @@ public void testFilterEmptyLabelValues() { assertFalse( FilterablePrometheusMetricReader.requiredLabelsFilter(actualLabels, requiredLabels)); } + + @Test + public void testWithLegacyAliases() { + GaugeSnapshot renamed = + new GaugeSnapshot( + new MetricMetadata("jvm_system_cpu_utilization", "help", null), + List.of(GaugeSnapshot.GaugeDataPointSnapshot.builder().value(0.42).build())); + GaugeSnapshot unrelated = + new GaugeSnapshot( + new MetricMetadata("jvm_memory_used_bytes", "help", null), + List.of(GaugeSnapshot.GaugeDataPointSnapshot.builder().value(1.0).build())); + + MetricSnapshots result = + FilterablePrometheusMetricReader.withLegacyAliases( + new MetricSnapshots(List.of(renamed, unrelated))); + + assertEquals( + Set.of( + "jvm_system_cpu_utilization", + "jvm_system_cpu_utilization_ratio", + "jvm_memory_used_bytes"), + result.stream() + .map(snapshot -> snapshot.getMetadata().getPrometheusName()) + .collect(Collectors.toSet())); + + GaugeSnapshot alias = + result.stream() + .filter( + snapshot -> + snapshot + .getMetadata() + .getPrometheusName() + .equals("jvm_system_cpu_utilization_ratio")) + .map(GaugeSnapshot.class::cast) + .findFirst() + .orElseThrow(); + assertEquals(0.42, alias.getDataPoints().get(0).getValue(), 0.0); + } } From c8f89a4f0fee1dc4f3e1231f0ac25791b67c37ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B8ydahl?= Date: Wed, 5 Aug 2026 12:59:51 +0200 Subject: [PATCH 3/5] Tighten comments and changelog wording --- .../fix-jvm-cpu-utilization-metric-name.yml | 11 ++++------- .../otel/FilterablePrometheusMetricReader.java | 5 ++--- .../test/org/apache/solr/metrics/JvmMetricsTest.java | 3 +-- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/changelog/unreleased/fix-jvm-cpu-utilization-metric-name.yml b/changelog/unreleased/fix-jvm-cpu-utilization-metric-name.yml index 7fa2fef6d4a1..30c62f329900 100644 --- a/changelog/unreleased/fix-jvm-cpu-utilization-metric-name.yml +++ b/changelog/unreleased/fix-jvm-cpu-utilization-metric-name.yml @@ -1,10 +1,7 @@ title: > - Fixed replica placement attribute fetching (sysLoadAvg) and the CPU circuit breaker, - which stopped working after the OpenTelemetry update renamed the Prometheus metric - `jvm_system_cpu_utilization_ratio` to `jvm_system_cpu_utilization` (and - `jvm_cpu_recent_utilization_ratio` to `jvm_cpu_recent_utilization`). The old - `_ratio` names are still exposed as deprecated aliases so existing dashboards - and alerts keep working. -type: fixed + JVM CPU metrics are named `jvm_system_cpu_utilization` and `jvm_cpu_recent_utilization` + after the OpenTelemetry Prometheus exporter update. The former `_ratio` suffixed names + are still exposed as deprecated aliases. +type: changed authors: - name: Jan Høydahl diff --git a/solr/core/src/java/org/apache/solr/metrics/otel/FilterablePrometheusMetricReader.java b/solr/core/src/java/org/apache/solr/metrics/otel/FilterablePrometheusMetricReader.java index 25e5a90af4c9..5d44871edf7c 100644 --- a/solr/core/src/java/org/apache/solr/metrics/otel/FilterablePrometheusMetricReader.java +++ b/solr/core/src/java/org/apache/solr/metrics/otel/FilterablePrometheusMetricReader.java @@ -45,9 +45,8 @@ public class FilterablePrometheusMetricReader extends PrometheusMetricReader { private static final Set PROM_SUFFIXES = Set.of("_total", "_sum", "_bucket", "_created", "_info"); - // The OTel Prometheus exporter no longer (since 1.57) maps the OTel unit "1" to a "_ratio" - // name suffix, which renamed these JVM metrics. Also expose them under the old names so - // existing dashboards, alerts and API clients keep working. + // The OTel Prometheus exporter (1.57+) no longer maps unit "1" to a "_ratio" name suffix, + // renaming these JVM metrics. Keep exposing the old names as deprecated aliases. static final Map LEGACY_NAME_ALIASES = Map.of( "jvm_system_cpu_utilization", "jvm_system_cpu_utilization_ratio", diff --git a/solr/core/src/test/org/apache/solr/metrics/JvmMetricsTest.java b/solr/core/src/test/org/apache/solr/metrics/JvmMetricsTest.java index 4052c2844569..6bff7c8ae131 100644 --- a/solr/core/src/test/org/apache/solr/metrics/JvmMetricsTest.java +++ b/solr/core/src/test/org/apache/solr/metrics/JvmMetricsTest.java @@ -124,12 +124,11 @@ public void testLegacyCpuUtilizationAliases() { .map(metric -> metric.getMetadata().getPrometheusName()) .collect(Collectors.toSet()); - // The JFR-based CPU metrics may be unavailable in exotic environments; skip if so + // JFR-based metric; may be unavailable in some environments Assume.assumeTrue( "Skipping: jvm_system_cpu_utilization not available", names.contains("jvm_system_cpu_utilization")); - // Legacy names from before the OTel exporter dropped the "_ratio" unit suffix assertTrue( "Should expose legacy alias jvm_system_cpu_utilization_ratio", names.contains("jvm_system_cpu_utilization_ratio")); From 25c326ef5c5ee9a6aaa6152b9d0c2ea9cd9d170a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B8ydahl?= Date: Wed, 5 Aug 2026 13:07:52 +0200 Subject: [PATCH 4/5] Add PR link to changelog entry --- changelog/unreleased/fix-jvm-cpu-utilization-metric-name.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/changelog/unreleased/fix-jvm-cpu-utilization-metric-name.yml b/changelog/unreleased/fix-jvm-cpu-utilization-metric-name.yml index 30c62f329900..c113c06ac120 100644 --- a/changelog/unreleased/fix-jvm-cpu-utilization-metric-name.yml +++ b/changelog/unreleased/fix-jvm-cpu-utilization-metric-name.yml @@ -5,3 +5,6 @@ title: > type: changed authors: - name: Jan Høydahl +links: + - name: PR#4711 + url: https://github.com/apache/solr/pull/4711 From 1d395a2fef09137abb57065387133376ba221da8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=B8ydahl?= Date: Thu, 6 Aug 2026 10:30:54 +0200 Subject: [PATCH 5/5] Drop Prometheus name compat shim; document break in 10.1 upgrade notes; test CPU breaker Per review, remove the FilterablePrometheusMetricReader alias shim and instead document the jvm_system_cpu_utilization / jvm_cpu_recent_utilization Prometheus renames in major-changes-in-solr-10.adoc under 10.1. Add a TestCircuitBreakers test asserting calculateLiveCPUUsage() >= 0, gated by an independent native-CPU probe so a metric-name regression fails rather than being mistaken for an unsupported machine. --- .../fix-jvm-cpu-utilization-metric-name.yml | 6 +-- .../FilterablePrometheusMetricReader.java | 40 ++-------------- .../apache/solr/metrics/JvmMetricsTest.java | 36 -------------- .../FilterablePrometheusMetricReaderTest.java | 43 ----------------- .../apache/solr/util/TestCircuitBreakers.java | 48 +++++++++++++++++++ .../pages/major-changes-in-solr-10.adoc | 3 ++ 6 files changed, 57 insertions(+), 119 deletions(-) diff --git a/changelog/unreleased/fix-jvm-cpu-utilization-metric-name.yml b/changelog/unreleased/fix-jvm-cpu-utilization-metric-name.yml index c113c06ac120..1c329ff841d1 100644 --- a/changelog/unreleased/fix-jvm-cpu-utilization-metric-name.yml +++ b/changelog/unreleased/fix-jvm-cpu-utilization-metric-name.yml @@ -1,7 +1,7 @@ title: > - JVM CPU metrics are named `jvm_system_cpu_utilization` and `jvm_cpu_recent_utilization` - after the OpenTelemetry Prometheus exporter update. The former `_ratio` suffixed names - are still exposed as deprecated aliases. + The Prometheus JVM CPU metrics are now named `jvm_system_cpu_utilization` and + `jvm_cpu_recent_utilization` (the `_ratio` suffix was dropped) following the + OpenTelemetry Prometheus exporter update. type: changed authors: - name: Jan Høydahl diff --git a/solr/core/src/java/org/apache/solr/metrics/otel/FilterablePrometheusMetricReader.java b/solr/core/src/java/org/apache/solr/metrics/otel/FilterablePrometheusMetricReader.java index 5d44871edf7c..6461b3bb52a8 100644 --- a/solr/core/src/java/org/apache/solr/metrics/otel/FilterablePrometheusMetricReader.java +++ b/solr/core/src/java/org/apache/solr/metrics/otel/FilterablePrometheusMetricReader.java @@ -25,7 +25,6 @@ import io.prometheus.metrics.model.snapshots.HistogramSnapshot; import io.prometheus.metrics.model.snapshots.InfoSnapshot; import io.prometheus.metrics.model.snapshots.Labels; -import io.prometheus.metrics.model.snapshots.MetricMetadata; import io.prometheus.metrics.model.snapshots.MetricSnapshot; import io.prometheus.metrics.model.snapshots.MetricSnapshots; import java.lang.invoke.MethodHandles; @@ -45,44 +44,11 @@ public class FilterablePrometheusMetricReader extends PrometheusMetricReader { private static final Set PROM_SUFFIXES = Set.of("_total", "_sum", "_bucket", "_created", "_info"); - // The OTel Prometheus exporter (1.57+) no longer maps unit "1" to a "_ratio" name suffix, - // renaming these JVM metrics. Keep exposing the old names as deprecated aliases. - static final Map LEGACY_NAME_ALIASES = - Map.of( - "jvm_system_cpu_utilization", "jvm_system_cpu_utilization_ratio", - "jvm_cpu_recent_utilization", "jvm_cpu_recent_utilization_ratio"); - public FilterablePrometheusMetricReader( boolean otelScopeEnabled, Predicate allowedResourceAttributesFilter) { super(otelScopeEnabled, allowedResourceAttributesFilter); } - @Override - public MetricSnapshots collect() { - return withLegacyAliases(super.collect()); - } - - /** Duplicates snapshots named in {@link #LEGACY_NAME_ALIASES} under their legacy name. */ - static MetricSnapshots withLegacyAliases(MetricSnapshots snapshots) { - MetricSnapshots.Builder builder = MetricSnapshots.builder(); - for (MetricSnapshot snapshot : snapshots) { - builder.metricSnapshot(snapshot); - } - for (MetricSnapshot snapshot : snapshots) { - String legacyName = LEGACY_NAME_ALIASES.get(snapshot.getMetadata().getPrometheusName()); - if (legacyName != null - && snapshot instanceof GaugeSnapshot gauge - && !builder.containsMetricName(legacyName)) { - MetricMetadata metadata = gauge.getMetadata(); - builder.metricSnapshot( - new GaugeSnapshot( - new MetricMetadata(legacyName, metadata.getHelp(), metadata.getUnit()), - gauge.getDataPoints())); - } - } - return builder.build(); - } - /** * Collect metrics with filtering support for metric names and labels. * @@ -96,7 +62,7 @@ public MetricSnapshots collect( // If no filtering is requested then return all metrics if (includedNames.isEmpty() && requiredLabels.isEmpty()) { - return collect(); + return super.collect(); } // Users may filter by Prometheus-format names (e.g. "solr_core_requests") or with a @@ -117,13 +83,13 @@ public MetricSnapshots collect( MetricSnapshots snapshotsToFilter; if (sanitizedNames.isEmpty()) { - snapshotsToFilter = collect(); + snapshotsToFilter = super.collect(); } else { // We collect all metrics and filter by Prometheus name rather than using // super.collect(Predicate) which matches on OTel internal names. This avoids a mismatch // when OTel names use dot-separators (e.g. "solr.core.requests") but users filter by the // Prometheus underscore-format name they see in the output (e.g. "solr_core_requests"). - MetricSnapshots all = collect(); + MetricSnapshots all = super.collect(); MetricSnapshots.Builder nameFiltered = MetricSnapshots.builder(); for (MetricSnapshot snapshot : all) { if (sanitizedNames.contains(snapshot.getMetadata().getPrometheusName())) { diff --git a/solr/core/src/test/org/apache/solr/metrics/JvmMetricsTest.java b/solr/core/src/test/org/apache/solr/metrics/JvmMetricsTest.java index 6bff7c8ae131..f099ba7d1d3a 100644 --- a/solr/core/src/test/org/apache/solr/metrics/JvmMetricsTest.java +++ b/solr/core/src/test/org/apache/solr/metrics/JvmMetricsTest.java @@ -25,7 +25,6 @@ import java.nio.file.Path; import java.util.Arrays; import java.util.Set; -import java.util.TreeMap; import java.util.stream.Collectors; import org.apache.lucene.util.SuppressForbidden; import org.apache.solr.SolrTestCaseJ4; @@ -111,41 +110,6 @@ public void testSetupJvmMetrics() throws InterruptedException { metricNames.stream().anyMatch(name -> name.startsWith("jvm_buffer"))); } - @Test - public void testLegacyCpuUtilizationAliases() { - var reader = - solrTestRule - .getJetty() - .getCoreContainer() - .getMetricManager() - .getPrometheusMetricReader("solr.jvm"); - Set names = - reader.collect().stream() - .map(metric -> metric.getMetadata().getPrometheusName()) - .collect(Collectors.toSet()); - - // JFR-based metric; may be unavailable in some environments - Assume.assumeTrue( - "Skipping: jvm_system_cpu_utilization not available", - names.contains("jvm_system_cpu_utilization")); - - assertTrue( - "Should expose legacy alias jvm_system_cpu_utilization_ratio", - names.contains("jvm_system_cpu_utilization_ratio")); - assertTrue( - "Should expose legacy alias jvm_cpu_recent_utilization_ratio", - names.contains("jvm_cpu_recent_utilization_ratio")); - - // The legacy name also works with the name filter used by /admin/metrics - MetricSnapshots filtered = - reader.collect(Set.of("jvm_system_cpu_utilization_ratio"), new TreeMap<>()); - assertEquals( - Set.of("jvm_system_cpu_utilization_ratio"), - filtered.stream() - .map(metric -> metric.getMetadata().getPrometheusName()) - .collect(Collectors.toSet())); - } - @Test @SuppressForbidden(reason = "Testing com.sun.management.OperatingSystemMXBean availability") public void testSystemMemoryMetrics() { diff --git a/solr/core/src/test/org/apache/solr/metrics/otel/FilterablePrometheusMetricReaderTest.java b/solr/core/src/test/org/apache/solr/metrics/otel/FilterablePrometheusMetricReaderTest.java index 259cb8ade0f7..ec91e149b4be 100644 --- a/solr/core/src/test/org/apache/solr/metrics/otel/FilterablePrometheusMetricReaderTest.java +++ b/solr/core/src/test/org/apache/solr/metrics/otel/FilterablePrometheusMetricReaderTest.java @@ -16,16 +16,11 @@ */ package org.apache.solr.metrics.otel; -import io.prometheus.metrics.model.snapshots.GaugeSnapshot; import io.prometheus.metrics.model.snapshots.Labels; -import io.prometheus.metrics.model.snapshots.MetricMetadata; -import io.prometheus.metrics.model.snapshots.MetricSnapshots; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; -import java.util.stream.Collectors; import org.apache.solr.SolrTestCaseJ4; import org.junit.Test; @@ -97,42 +92,4 @@ public void testFilterEmptyLabelValues() { assertFalse( FilterablePrometheusMetricReader.requiredLabelsFilter(actualLabels, requiredLabels)); } - - @Test - public void testWithLegacyAliases() { - GaugeSnapshot renamed = - new GaugeSnapshot( - new MetricMetadata("jvm_system_cpu_utilization", "help", null), - List.of(GaugeSnapshot.GaugeDataPointSnapshot.builder().value(0.42).build())); - GaugeSnapshot unrelated = - new GaugeSnapshot( - new MetricMetadata("jvm_memory_used_bytes", "help", null), - List.of(GaugeSnapshot.GaugeDataPointSnapshot.builder().value(1.0).build())); - - MetricSnapshots result = - FilterablePrometheusMetricReader.withLegacyAliases( - new MetricSnapshots(List.of(renamed, unrelated))); - - assertEquals( - Set.of( - "jvm_system_cpu_utilization", - "jvm_system_cpu_utilization_ratio", - "jvm_memory_used_bytes"), - result.stream() - .map(snapshot -> snapshot.getMetadata().getPrometheusName()) - .collect(Collectors.toSet())); - - GaugeSnapshot alias = - result.stream() - .filter( - snapshot -> - snapshot - .getMetadata() - .getPrometheusName() - .equals("jvm_system_cpu_utilization_ratio")) - .map(GaugeSnapshot.class::cast) - .findFirst() - .orElseThrow(); - assertEquals(0.42, alias.getDataPoints().get(0).getValue(), 0.0); - } } diff --git a/solr/core/src/test/org/apache/solr/util/TestCircuitBreakers.java b/solr/core/src/test/org/apache/solr/util/TestCircuitBreakers.java index 648ac0358549..88bdee78087c 100644 --- a/solr/core/src/test/org/apache/solr/util/TestCircuitBreakers.java +++ b/solr/core/src/test/org/apache/solr/util/TestCircuitBreakers.java @@ -19,8 +19,10 @@ import static org.hamcrest.CoreMatchers.containsString; +import com.sun.management.OperatingSystemMXBean; import java.io.IOException; import java.lang.invoke.MethodHandles; +import java.lang.management.ManagementFactory; import java.util.ArrayList; import java.util.Comparator; import java.util.List; @@ -30,6 +32,7 @@ import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; +import org.apache.lucene.util.SuppressForbidden; import org.apache.solr.SolrTestCaseJ4; import org.apache.solr.client.solrj.SolrRequest; import org.apache.solr.common.SolrException; @@ -43,6 +46,7 @@ import org.apache.solr.util.circuitbreaker.LoadAverageCircuitBreaker; import org.apache.solr.util.circuitbreaker.MemoryCircuitBreaker; import org.junit.After; +import org.junit.Assume; import org.junit.BeforeClass; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -271,6 +275,39 @@ public void testFakeLoadAverageCircuitBreaker() { assertThatHighQueryLoadTrips(circuitBreaker, 5); } + /** + * Reads a real CPU usage value from the metrics, guarding against a regression where a renamed + * JVM metric made {@link CPUCircuitBreaker#calculateLiveCPUUsage()} silently return -1. Gated by + * an independent native-CPU probe so that a name regression fails here rather than being mistaken + * for an unsupported machine (both otherwise yield -1). + */ + public void testCPUCircuitBreakerReadsLiveUsage() { + Assume.assumeTrue("No native CPU measurement on this machine", nativeCpuMeasurementSupported()); + + double usage = new ExposedCPUCircuitBreaker(h.getCoreContainer()).liveCPUUsage(); + assertTrue("Expected CPU usage >= 0 but got " + usage, usage >= 0); + } + + @SuppressForbidden(reason = "Probing com.sun OperatingSystemMXBean for native CPU support") + private static boolean nativeCpuMeasurementSupported() { + if (!(ManagementFactory.getOperatingSystemMXBean() instanceof OperatingSystemMXBean osBean)) { + return false; + } + // getCpuLoad() needs two samples and may return a negative value on the first calls + for (int i = 0; i < 10; i++) { + if (osBean.getCpuLoad() >= 0) { + return true; + } + try { + Thread.sleep(50); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return false; + } + } + return false; + } + /** * Common assert method to be reused in tests * @@ -441,6 +478,17 @@ protected double calculateLiveCPUUsage() { } } + /** Exposes the real (protected) CPU usage calculation for testing. */ + private static class ExposedCPUCircuitBreaker extends CPUCircuitBreaker { + public ExposedCPUCircuitBreaker(CoreContainer coreContainer) { + super(coreContainer); + } + + double liveCPUUsage() { + return calculateLiveCPUUsage(); + } + } + private static class FakeLoadAverageCircuitBreaker extends LoadAverageCircuitBreaker { @Override protected double calculateLiveLoadAverage() { diff --git a/solr/solr-ref-guide/modules/upgrade-notes/pages/major-changes-in-solr-10.adoc b/solr/solr-ref-guide/modules/upgrade-notes/pages/major-changes-in-solr-10.adoc index c3ae79d9afa6..a065a7413e1e 100644 --- a/solr/solr-ref-guide/modules/upgrade-notes/pages/major-changes-in-solr-10.adoc +++ b/solr/solr-ref-guide/modules/upgrade-notes/pages/major-changes-in-solr-10.adoc @@ -420,6 +420,9 @@ Users who consume Solr metrics via OTLP and rely on the 10.0 underscore-format n (e.g., `crossdc.consumer.output.total` → `solr.crossdc.consumer.output.total`) * All CrossDC producer metrics have been renamed: `solr.core.crossdc.producer.*` → `solr.crossdc.producer.*` (e.g., `solr.core.crossdc.producer.submitted` → `solr.crossdc.producer.submitted`) +* The Prometheus JVM CPU utilization metrics lost their `_ratio` suffix, as the updated OpenTelemetry + exporter no longer maps the OTel unit `1` to a `_ratio` suffix: `jvm_system_cpu_utilization_ratio` → + `jvm_system_cpu_utilization` and `jvm_cpu_recent_utilization_ratio` → `jvm_cpu_recent_utilization`. Update your dashboards or other metrics consumers accordingly.