You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by ml...@apache.org on 2025/09/26 22:11:54 UTC
(solr) 27/33: Migrate FieldCacheMetrics to OTEL (#3619)
This is an automated email from the ASF dual-hosted git repository.
mlbiscoc pushed a commit to branch feature/SOLR-17458-rebased
in repository https://gitbox.apache.org/repos/asf/solr.git
commit 598b5d75d4fe5e39799b9637cc629a8c15ad4b43
Author: Matthew Biscocho <ml...@apache.org>
AuthorDate: Fri Sep 19 16:06:06 2025 -0400
Migrate FieldCacheMetrics to OTEL (#3619)
Co-authored-by: Matthew Biscocho <mb...@bloomberg.net>
---
.../java/org/apache/solr/core/CoreContainer.java | 6 +-
.../src/java/org/apache/solr/core/SolrCore.java | 34 ++++----
.../org/apache/solr/search/SolrFieldCacheBean.java | 37 +++++----
.../apache/solr/uninverting/UninvertingReader.java | 8 +-
.../apache/solr/search/TestSolrFieldCacheBean.java | 96 +++++++++++++---------
5 files changed, 103 insertions(+), 78 deletions(-)
diff --git a/solr/core/src/java/org/apache/solr/core/CoreContainer.java b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
index 2da7fc9ef91..cfcdbd451c1 100644
--- a/solr/core/src/java/org/apache/solr/core/CoreContainer.java
+++ b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
@@ -1027,8 +1027,10 @@ public class CoreContainer {
"version");
SolrFieldCacheBean fieldCacheBean = new SolrFieldCacheBean();
- // NOCOMMIT SOLR-17458: Otel migration
- fieldCacheBean.initializeMetrics(solrMetricsContext, Attributes.empty(), "");
+ fieldCacheBean.initializeMetrics(
+ solrMetricsContext,
+ Attributes.of(CATEGORY_ATTR, SolrInfoBean.Category.CACHE.toString()),
+ "");
if (isZooKeeperAware()) {
metricManager.loadClusterReporters(metricReporters, this);
diff --git a/solr/core/src/java/org/apache/solr/core/SolrCore.java b/solr/core/src/java/org/apache/solr/core/SolrCore.java
index a8cb4800fde..8118ab6c824 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrCore.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrCore.java
@@ -259,6 +259,7 @@ public class SolrCore implements SolrInfoBean, Closeable {
private volatile boolean newSearcherReady = false;
+ private final Attributes coreAttributes;
private AttributedLongCounter newSearcherCounter;
private AttributedLongCounter newSearcherMaxReachedCounter;
private AttributedLongCounter newSearcherOtherErrorsCounter;
@@ -1088,6 +1089,17 @@ public class SolrCore implements SolrInfoBean, Closeable {
this.solrMetricsContext = coreMetricManager.getSolrMetricsContext();
this.coreMetricManager.loadReporters();
+ if (coreContainer.isZooKeeperAware()) {
+ this.coreAttributes =
+ Attributes.builder()
+ .put(COLLECTION_ATTR, coreDescriptor.getCollectionName())
+ .put(CORE_ATTR, coreDescriptor.getName())
+ .put(SHARD_ATTR, coreDescriptor.getCloudDescriptor().getShardId())
+ .build();
+ } else {
+ this.coreAttributes = Attributes.builder().put(CORE_ATTR, coreDescriptor.getName()).build();
+ }
+
if (updateHandler == null) {
directoryFactory = initDirectoryFactory();
recoveryStrategyBuilder = initRecoveryStrategyBuilder();
@@ -1109,21 +1121,7 @@ public class SolrCore implements SolrInfoBean, Closeable {
setLatestSchema(schema);
// initialize core metrics
- if (coreContainer.isZooKeeperAware()) {
- initializeMetrics(
- solrMetricsContext,
- Attributes.builder()
- .put(COLLECTION_ATTR, coreDescriptor.getCollectionName())
- .put(CORE_ATTR, coreDescriptor.getName())
- .put(SHARD_ATTR, coreDescriptor.getCloudDescriptor().getShardId())
- .build(),
- "");
- } else {
- initializeMetrics(
- solrMetricsContext,
- Attributes.builder().put(CORE_ATTR, coreDescriptor.getName()).build(),
- "");
- }
+ initializeMetrics(solrMetricsContext, coreAttributes, "");
// init pluggable circuit breakers, after metrics because some circuit breakers use metrics
initPlugins(null, CircuitBreaker.class);
@@ -1131,8 +1129,10 @@ public class SolrCore implements SolrInfoBean, Closeable {
SolrFieldCacheBean solrFieldCacheBean = new SolrFieldCacheBean();
// this is registered at the CONTAINER level because it's not core-specific - for now we
// also register it here for back-compat
- // NOCOMMIT SOLR-17458: Add Otel
- solrFieldCacheBean.initializeMetrics(solrMetricsContext, Attributes.empty(), "core");
+ solrFieldCacheBean.initializeMetrics(
+ solrMetricsContext,
+ coreAttributes.toBuilder().put(CATEGORY_ATTR, Category.CACHE.toString()).build(),
+ "");
infoRegistry.put("fieldCache", solrFieldCacheBean);
this.maxWarmingSearchers = solrConfig.maxWarmingSearchers;
diff --git a/solr/core/src/java/org/apache/solr/search/SolrFieldCacheBean.java b/solr/core/src/java/org/apache/solr/search/SolrFieldCacheBean.java
index b0ae73075ab..d341ed743c2 100644
--- a/solr/core/src/java/org/apache/solr/search/SolrFieldCacheBean.java
+++ b/solr/core/src/java/org/apache/solr/search/SolrFieldCacheBean.java
@@ -18,9 +18,10 @@ package org.apache.solr.search;
import io.opentelemetry.api.common.Attributes;
import org.apache.solr.common.util.EnvUtils;
+import org.apache.solr.common.util.IOUtils;
import org.apache.solr.core.SolrInfoBean;
-import org.apache.solr.metrics.MetricsMap;
import org.apache.solr.metrics.SolrMetricsContext;
+import org.apache.solr.metrics.otel.OtelUnit;
import org.apache.solr.uninverting.UninvertingReader;
/** A SolrInfoBean that provides introspection of the Solr FieldCache */
@@ -30,6 +31,7 @@ public class SolrFieldCacheBean implements SolrInfoBean {
EnvUtils.getPropertyAsBool("solr.metrics.fieldcache.entries.enabled", true);
private boolean enableJmxEntryList =
EnvUtils.getPropertyAsBool("solr.metrics.fieldcache.entries.jmx.enabled", true);
+ private AutoCloseable toClose;
private SolrMetricsContext solrMetricsContext;
@@ -53,28 +55,35 @@ public class SolrFieldCacheBean implements SolrInfoBean {
return solrMetricsContext;
}
- // TODO SOLR-17458: Migrate to Otel
@Override
public void initializeMetrics(
SolrMetricsContext parentContext, Attributes attributes, String scope) {
this.solrMetricsContext = parentContext;
- MetricsMap metricsMap =
- new MetricsMap(
- map -> {
+ var solrCacheStats =
+ solrMetricsContext.longMeasurement(
+ "solr_field_cache_entries", "Number of field cache entries");
+ var solrCacheSize =
+ solrMetricsContext.longMeasurement(
+ "solr_field_cache_size", "Size of field cache in bytes", OtelUnit.BYTES);
+ this.toClose =
+ solrMetricsContext.batchCallback(
+ () -> {
if (enableEntryList && enableJmxEntryList) {
UninvertingReader.FieldCacheStats fieldCacheStats =
UninvertingReader.getUninvertedStats();
String[] entries = fieldCacheStats.info;
- map.put("entries_count", entries.length);
- map.put("total_size", fieldCacheStats.totalSize);
- for (int i = 0; i < entries.length; i++) {
- final String entry = entries[i];
- map.put("entry#" + i, entry);
- }
+ solrCacheStats.record(entries.length, attributes);
+ solrCacheSize.record(fieldCacheStats.totalSize, attributes);
} else {
- map.put("entries_count", UninvertingReader.getUninvertedStatsSize());
+ solrCacheStats.record(UninvertingReader.getUninvertedStatsSize(), attributes);
}
- });
- solrMetricsContext.gauge(metricsMap, true, "fieldCache", Category.CACHE.toString(), scope);
+ },
+ solrCacheStats,
+ solrCacheSize);
+ }
+
+ @Override
+ public void close() {
+ IOUtils.closeQuietly(toClose);
}
}
diff --git a/solr/core/src/java/org/apache/solr/uninverting/UninvertingReader.java b/solr/core/src/java/org/apache/solr/uninverting/UninvertingReader.java
index c0f4f209921..5f85aac034e 100644
--- a/solr/core/src/java/org/apache/solr/uninverting/UninvertingReader.java
+++ b/solr/core/src/java/org/apache/solr/uninverting/UninvertingReader.java
@@ -38,7 +38,6 @@ import org.apache.lucene.index.LeafReader;
import org.apache.lucene.index.NumericDocValues;
import org.apache.lucene.index.SortedDocValues;
import org.apache.lucene.index.SortedSetDocValues;
-import org.apache.lucene.util.RamUsageEstimator;
import org.apache.solr.uninverting.FieldCache.CacheEntry;
/**
@@ -467,8 +466,7 @@ public class UninvertingReader extends FilterLeafReader {
info[i] = entries[i].toString();
totalBytesUsed += entries[i].getValue().ramBytesUsed();
}
- String totalSize = RamUsageEstimator.humanReadableUnits(totalBytesUsed);
- return new FieldCacheStats(totalSize, info);
+ return new FieldCacheStats(totalBytesUsed, info);
}
public static int getUninvertedStatsSize() {
@@ -481,10 +479,10 @@ public class UninvertingReader extends FilterLeafReader {
* @lucene.internal
*/
public static class FieldCacheStats {
- public String totalSize;
+ public Long totalSize;
public String[] info;
- public FieldCacheStats(String totalSize, String[] info) {
+ public FieldCacheStats(Long totalSize, String[] info) {
this.totalSize = totalSize;
this.info = info;
}
diff --git a/solr/core/src/test/org/apache/solr/search/TestSolrFieldCacheBean.java b/solr/core/src/test/org/apache/solr/search/TestSolrFieldCacheBean.java
index a69730afc4b..d39411ad5d8 100644
--- a/solr/core/src/test/org/apache/solr/search/TestSolrFieldCacheBean.java
+++ b/solr/core/src/test/org/apache/solr/search/TestSolrFieldCacheBean.java
@@ -16,12 +16,14 @@
*/
package org.apache.solr.search;
+import static org.apache.solr.metrics.SolrMetricProducer.CATEGORY_ATTR;
+
import io.opentelemetry.api.common.Attributes;
-import java.util.Map;
-import java.util.Random;
+import io.prometheus.metrics.model.snapshots.GaugeSnapshot;
+import java.util.Optional;
import org.apache.lucene.tests.util.TestUtil;
import org.apache.solr.SolrTestCaseJ4;
-import org.apache.solr.metrics.MetricsMap;
+import org.apache.solr.core.SolrInfoBean;
import org.apache.solr.metrics.SolrMetricManager;
import org.apache.solr.metrics.SolrMetricsContext;
import org.junit.BeforeClass;
@@ -29,6 +31,12 @@ import org.junit.Test;
public class TestSolrFieldCacheBean extends SolrTestCaseJ4 {
+ private static final String ENTRIES_METRIC_NAME = "solr_field_cache_entries";
+ private static final String SIZE_BYTES_METRIC_NAME = "solr_field_cache_size_bytes";
+ private static final String DISABLE_ENTRY_LIST_PROPERTY = "disableSolrFieldCacheMBeanEntryList";
+ private static final String DISABLE_ENTRY_LIST_JMX_PROPERTY =
+ "disableSolrFieldCacheMBeanEntryListJmx";
+
@BeforeClass
public static void beforeClass() throws Exception {
initCore("solrconfig.xml", "schema-minimal.xml");
@@ -36,69 +44,77 @@ public class TestSolrFieldCacheBean extends SolrTestCaseJ4 {
@Test
public void testEntryList() {
- // ensure entries to FieldCache
+ // Ensure entries to FieldCache
assertU(adoc("id", "id0"));
assertU(commit());
assertQ(req("q", "*:*", "sort", "id asc"), "//*[@numFound='1']");
// Test with entry list enabled
- assertEntryListIncluded(false);
+ assertEntryList(true);
// Test again with entry list disabled
System.setProperty("solr.metrics.fieldcache.entries.enabled", "false");
try {
- assertEntryListNotIncluded(false);
+ assertEntryList(false);
} finally {
System.clearProperty("solr.metrics.fieldcache.entries.enabled");
}
- // Test with entry list enabled for jmx
- assertEntryListIncluded(true);
+ // Test with entry list enabled again
+ assertEntryList(true);
// Test with entry list disabled for jmx
System.setProperty("solr.metrics.fieldcache.entries.jmx.enabled", "false");
try {
- assertEntryListNotIncluded(true);
+ assertEntryList(false);
} finally {
System.clearProperty("solr.metrics.fieldcache.entries.jmx.enabled");
}
}
- private void assertEntryListIncluded(boolean checkJmx) {
- SolrFieldCacheBean mbean = new SolrFieldCacheBean();
- Random r = random();
- String registryName = TestUtil.randomSimpleString(r, 1, 10);
- SolrMetricManager metricManager = h.getCoreContainer().getMetricManager();
- SolrMetricsContext solrMetricsContext =
- new SolrMetricsContext(metricManager, registryName, "foo");
- mbean.initializeMetrics(solrMetricsContext, Attributes.empty(), null);
- MetricsMap metricsMap =
- (MetricsMap)
- ((SolrMetricManager.GaugeWrapper)
- metricManager.registry(registryName).getMetrics().get("CACHE.fieldCache"))
- .getGauge();
- Map<String, Object> metrics = checkJmx ? metricsMap.getValue(true) : metricsMap.getValue();
- assertTrue(((Number) metrics.get("entries_count")).longValue() > 0);
- assertNotNull(metrics.get("total_size"));
- assertNotNull(metrics.get("entry#0"));
+ private void assertEntryList(boolean bytesMetricIncluded) {
+ FieldCacheMetrics metrics = getFieldCacheMetrics();
+ assertTrue(
+ "Field cache entries count should be greater than 0",
+ metrics.entries().get().getDataPoints().getFirst().getValue() > 0);
+
+ if (bytesMetricIncluded) {
+ assertTrue("Size bytes metric should be present", metrics.sizeBytes().isPresent());
+ } else {
+ assertTrue("Size bytes metric should not be present", metrics.sizeBytes().isEmpty());
+ }
}
- private void assertEntryListNotIncluded(boolean checkJmx) {
- SolrFieldCacheBean mbean = new SolrFieldCacheBean();
- Random r = random();
- String registryName = TestUtil.randomSimpleString(r, 1, 10);
+ private FieldCacheMetrics getFieldCacheMetrics() {
+ String registryName = TestUtil.randomSimpleString(random(), 1, 10);
SolrMetricManager metricManager = h.getCoreContainer().getMetricManager();
SolrMetricsContext solrMetricsContext =
new SolrMetricsContext(metricManager, registryName, "foo");
- mbean.initializeMetrics(solrMetricsContext, Attributes.empty(), null);
- MetricsMap metricsMap =
- (MetricsMap)
- ((SolrMetricManager.GaugeWrapper)
- metricManager.registry(registryName).getMetrics().get("CACHE.fieldCache"))
- .getGauge();
- Map<String, Object> metrics = checkJmx ? metricsMap.getValue(true) : metricsMap.getValue();
- assertTrue(((Number) metrics.get("entries_count")).longValue() > 0);
- assertNull(metrics.get("total_size"));
- assertNull(metrics.get("entry#0"));
+
+ try (SolrFieldCacheBean mbean = new SolrFieldCacheBean()) {
+ mbean.initializeMetrics(
+ solrMetricsContext,
+ Attributes.of(CATEGORY_ATTR, SolrInfoBean.Category.CACHE.toString()),
+ null);
+
+ var metrics = metricManager.getPrometheusMetricReader(registryName).collect();
+
+ var entryCount =
+ metrics.stream()
+ .filter(ms -> ENTRIES_METRIC_NAME.equals(ms.getMetadata().getPrometheusName()))
+ .map(GaugeSnapshot.class::cast)
+ .findFirst();
+
+ var sizeBytes =
+ metrics.stream()
+ .filter(ms -> SIZE_BYTES_METRIC_NAME.equals(ms.getMetadata().getPrometheusName()))
+ .map(GaugeSnapshot.class::cast)
+ .findFirst();
+
+ return new FieldCacheMetrics(entryCount, sizeBytes);
+ }
}
+
+ private record FieldCacheMetrics(
+ Optional<GaugeSnapshot> entries, Optional<GaugeSnapshot> sizeBytes) {}
}