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 <[email protected]> AuthorDate: Fri Sep 19 16:06:06 2025 -0400 Migrate FieldCacheMetrics to OTEL (#3619) Co-authored-by: Matthew Biscocho <[email protected]> --- .../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) {} }
