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:59 UTC
(solr) 32/33: SOLR-17798: Integrate SDK OTLP metric exporter (#3413)
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 fc3545764a0a8abe8062158f295ad49d7bd8e9c6
Author: Matthew Biscocho <ml...@apache.org>
AuthorDate: Fri Sep 26 12:10:44 2025 -0400
SOLR-17798: Integrate SDK OTLP metric exporter (#3413)
* Create dynamic SDK Meter Providers and tests
* Bad merge from feature branch
* Wrap meter providers and metric readers
* Test cleanup
* Cleanup
* Initialize a OTLP exporter
* Add Noop metric expoter
* Add test for exporter
* Create exporter factory
* Drop revert opentelemetry version upgrade
* Add some java docs
* Wrong merged conflicts
* Tidy lines
* Add trace based exemplars
* Move to OpenMetricsTextFormatWriter
* Change context type to open metrics
* Revert "Change context type to open metrics"
This reverts commit b594327aae48c813cf81e36c5e2dfbe3bcb76bc8.
* Revert "Move to OpenMetricsTextFormatWriter"
This reverts commit 96435ebe2ac15b7cc3781c031d1229a1504e1fdc.
* Revert "Add trace based exemplars"
This reverts commit d33a85df3ac5ddb4401f36d9ed3c34f7a02c43fc.
* Move Otlp exporter into Open Telemetry module
* Close meterProviders and readers
* Retry instead of sleep
---------
Co-authored-by: Matthew Biscocho <mb...@bloomberg.net>
---
gradle/libs.versions.toml | 1 +
.../java/org/apache/solr/core/CoreContainer.java | 3 ++
.../apache/solr/handler/RequestHandlerBase.java | 8 +--
.../apache/solr/metrics/SolrCoreMetricManager.java | 1 +
.../org/apache/solr/metrics/SolrMetricManager.java | 62 +++++++++++++++++++---
.../solr/metrics/otel/MetricExporterFactory.java | 34 ++++++++++++
.../solr/metrics/otel/NoopMetricExporter.java | 46 ++++++++++++++++
.../apache/solr/blockcache/BufferStoreTest.java | 2 +-
.../apache/solr/metrics/SolrMetricManagerTest.java | 46 +++++++++++++---
.../solr/metrics/SolrMetricReporterTest.java | 2 +-
.../org/apache/solr/search/TestCaffeineCache.java | 2 +-
.../org/apache/solr/search/TestSolrCachePerf.java | 4 +-
.../test/org/apache/solr/search/TestThinCache.java | 2 +-
.../stats/OtelInstrumentedExecutorServiceTest.java | 2 +-
.../handler/MirroringCollectionsHandlerTest.java | 3 --
solr/modules/opentelemetry/build.gradle | 4 +-
solr/modules/opentelemetry/gradle.lockfile | 2 +-
.../solr/opentelemetry/OtlpExporterFactory.java | 57 ++++++++++++++++++++
.../src/java/org/apache/solr/SolrTestCaseJ4.java | 3 +-
19 files changed, 249 insertions(+), 35 deletions(-)
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 9f7d5561da4..f07b3cb1f61 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -485,6 +485,7 @@ opentelemetry-bom = { module = "io.opentelemetry:opentelemetry-bom", version.ref
opentelemetry-context = { module = "io.opentelemetry:opentelemetry-context", version.ref = "opentelemetry" }
opentelemetry-exporter-otlp = { module = "io.opentelemetry:opentelemetry-exporter-otlp", version.ref = "opentelemetry" }
opentelemetry-exporter-prometheus = { module = "io.opentelemetry:opentelemetry-exporter-prometheus", version.ref = "opentelemetry-prometheus" }
+opentelemetry-exporter-sender-okhttp = { module = "io.opentelemetry:opentelemetry-exporter-sender-okhttp", version.ref = "opentelemetry" }
opentelemetry-runtime-telemetry = { module = "io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java17", version.ref = "opentelemetry-runtime-telemetry" }
opentelemetry-sdk = { module = "io.opentelemetry:opentelemetry-sdk", version.ref = "opentelemetry" }
opentelemetry-sdkextension-autoconfigure = { module = "io.opentelemetry:opentelemetry-sdk-extension-autoconfigure", version.ref = "opentelemetry" }
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 6590349ad87..064e5ed2aee 100644
--- a/solr/core/src/java/org/apache/solr/core/CoreContainer.java
+++ b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
@@ -1362,6 +1362,9 @@ public class CoreContainer {
SolrMetricManager.getRegistryName(SolrInfoBean.Group.jvm), metricTag);
metricManager.unregisterGauges(
SolrMetricManager.getRegistryName(SolrInfoBean.Group.jetty), metricTag);
+
+ // Close all OTEL meter providers and metrics
+ metricManager.closeAllRegistries();
}
if (isZooKeeperAware()) {
diff --git a/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java b/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
index d34b482402a..4ac39cda833 100644
--- a/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
+++ b/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
@@ -37,7 +37,6 @@ import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.SuppressForbidden;
import org.apache.solr.core.CoreContainer;
-import org.apache.solr.core.MetricsConfig;
import org.apache.solr.core.PluginBag;
import org.apache.solr.core.PluginInfo;
import org.apache.solr.core.SolrCore;
@@ -193,11 +192,7 @@ public abstract class RequestHandlerBase
public static class HandlerMetrics {
public static final HandlerMetrics NO_OP =
new HandlerMetrics(
- new SolrMetricsContext(
- new SolrMetricManager(
- null, new MetricsConfig.MetricsConfigBuilder().setEnabled(false).build()),
- "NO_OP",
- "NO_OP"),
+ new SolrMetricsContext(new SolrMetricManager(null), "NO_OP", "NO_OP"),
Attributes.empty());
public AttributedLongCounter requests;
@@ -207,7 +202,6 @@ public abstract class RequestHandlerBase
public AttributedLongTimer requestTimes;
public HandlerMetrics(SolrMetricsContext solrMetricsContext, Attributes attributes) {
-
LongCounter requestMetric;
LongCounter errorRequestMetric;
LongCounter timeoutRequestMetric;
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java b/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java
index 86c81c83bd8..aa64dcbacb1 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java
@@ -190,6 +190,7 @@ public class SolrCoreMetricManager implements Closeable {
metricManager.unregisterGauges(
solrMetricsContext.getRegistryName(), solrMetricsContext.getTag());
+ metricManager.meterProvider(solrMetricsContext.getRegistryName()).close();
registeredProducers.clear();
}
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java b/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
index b4e1e708d86..43da56a7f75 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
@@ -16,6 +16,9 @@
*/
package org.apache.solr.metrics;
+import static org.apache.solr.metrics.otel.MetricExporterFactory.OTLP_EXPORTER_ENABLED;
+import static org.apache.solr.metrics.otel.MetricExporterFactory.OTLP_EXPORTER_INTERVAL;
+
import com.codahale.metrics.Counter;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.Histogram;
@@ -57,6 +60,8 @@ import io.opentelemetry.sdk.metrics.InstrumentSelector;
import io.opentelemetry.sdk.metrics.InstrumentType;
import io.opentelemetry.sdk.metrics.SdkMeterProvider;
import io.opentelemetry.sdk.metrics.View;
+import io.opentelemetry.sdk.metrics.export.MetricExporter;
+import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader;
import io.opentelemetry.sdk.metrics.internal.SdkMeterProviderUtil;
import io.opentelemetry.sdk.metrics.internal.exemplar.ExemplarFilter;
import java.io.IOException;
@@ -81,6 +86,8 @@ import java.util.function.Consumer;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import java.util.stream.Collectors;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.util.IOUtils;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.core.MetricsConfig;
@@ -90,6 +97,8 @@ import org.apache.solr.core.SolrInfoBean;
import org.apache.solr.core.SolrResourceLoader;
import org.apache.solr.logging.MDCLoggingContext;
import org.apache.solr.metrics.otel.FilterablePrometheusMetricReader;
+import org.apache.solr.metrics.otel.MetricExporterFactory;
+import org.apache.solr.metrics.otel.NoopMetricExporter;
import org.apache.solr.metrics.otel.OtelUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -161,6 +170,8 @@ public class SolrMetricManager {
private final ConcurrentMap<String, MeterProviderAndReaders> meterProviderAndReaders =
new ConcurrentHashMap<>();
+ private final MetricExporter metricExporter;
+
private static final List<Double> SOLR_NANOSECOND_HISTOGRAM_BOUNDARIES =
List.of(
0.0,
@@ -178,8 +189,9 @@ public class SolrMetricManager {
100_000_000.0,
1_000_000_000.0);
- public SolrMetricManager() {
+ public SolrMetricManager(MetricExporter exporter) {
metricsConfig = new MetricsConfig.MetricsConfigBuilder().build();
+ metricExporter = exporter;
counterSupplier = MetricSuppliers.counterSupplier(null, null);
meterSupplier = MetricSuppliers.meterSupplier(null, null);
timerSupplier = MetricSuppliers.timerSupplier(null, null);
@@ -188,6 +200,7 @@ public class SolrMetricManager {
public SolrMetricManager(SolrResourceLoader loader, MetricsConfig metricsConfig) {
this.metricsConfig = metricsConfig;
+ this.metricExporter = loadMetricExporter(loader);
counterSupplier = MetricSuppliers.counterSupplier(loader, metricsConfig.getCounterSupplier());
meterSupplier = MetricSuppliers.meterSupplier(loader, metricsConfig.getMeterSupplier());
timerSupplier = MetricSuppliers.timerSupplier(loader, metricsConfig.getTimerSupplier());
@@ -827,9 +840,7 @@ public class SolrMetricManager {
providerName,
key -> {
var reader = new FilterablePrometheusMetricReader(true, null);
- // NOCOMMIT: We need to add a Periodic Metric Reader here if we want to push with OTLP
- // with an exporter
- var provider =
+ var builder =
SdkMeterProvider.builder()
.registerMetricReader(reader)
.registerView(
@@ -841,9 +852,14 @@ public class SolrMetricManager {
.setAggregation(
Aggregation.explicitBucketHistogram(
SOLR_NANOSECOND_HISTOGRAM_BOUNDARIES))
- .build()); // TODO: Make histogram bucket boundaries configurable
- SdkMeterProviderUtil.setExemplarFilter(provider, ExemplarFilter.traceBased());
- return new MeterProviderAndReaders(provider.build(), reader);
+ .build()); // TODO: Make histogram bucket boundaries configurable;
+ if (metricExporter != null)
+ builder.registerMetricReader(
+ PeriodicMetricReader.builder(metricExporter)
+ .setInterval(OTLP_EXPORTER_INTERVAL, TimeUnit.MILLISECONDS)
+ .build());
+ SdkMeterProviderUtil.setExemplarFilter(builder, ExemplarFilter.traceBased());
+ return new MeterProviderAndReaders(builder.build(), reader);
})
.sdkMeterProvider();
}
@@ -871,15 +887,27 @@ public class SolrMetricManager {
*
* @param registry name of the registry to remove
*/
+ // NOCOMMIT: Remove this
public void removeRegistry(String registry) {
meterProviderAndReaders.computeIfPresent(
enforcePrefix(registry),
(key, meterAndReader) -> {
- meterAndReader.sdkMeterProvider().close();
+ IOUtils.closeQuietly(meterAndReader.sdkMeterProvider());
return null;
});
}
+ /** Close all meter providers and their associated metric readers. */
+ public void closeAllRegistries() {
+ meterProviderAndReaders
+ .values()
+ .forEach(
+ meterAndReader -> {
+ IOUtils.closeQuietly(meterAndReader.sdkMeterProvider);
+ });
+ meterProviderAndReaders.clear();
+ }
+
/**
* Potential conflict resolution strategies when attempting to register a new metric that already
* exists
@@ -1736,6 +1764,24 @@ public class SolrMetricManager {
return (mpr != null) ? mpr.prometheusMetricReader() : null;
}
+ public MetricExporter getMetricExporter() {
+ return metricExporter;
+ }
+
+ private MetricExporter loadMetricExporter(SolrResourceLoader loader) {
+ if (!OTLP_EXPORTER_ENABLED) return new NoopMetricExporter();
+ try {
+ MetricExporterFactory exporterFactory =
+ loader.newInstance(
+ "org.apache.solr.opentelemetry.OtlpExporterFactory", MetricExporterFactory.class);
+ return exporterFactory.getExporter();
+ } catch (SolrException e) {
+ log.error(
+ "Could not load OTLP exporter. Check that the Open Telemetry module is enabled.", e);
+ return new NoopMetricExporter();
+ }
+ }
+
private record MeterProviderAndReaders(
SdkMeterProvider sdkMeterProvider, FilterablePrometheusMetricReader prometheusMetricReader) {}
}
diff --git a/solr/core/src/java/org/apache/solr/metrics/otel/MetricExporterFactory.java b/solr/core/src/java/org/apache/solr/metrics/otel/MetricExporterFactory.java
new file mode 100644
index 00000000000..5d0ecc67427
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/metrics/otel/MetricExporterFactory.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.metrics.otel;
+
+import io.opentelemetry.sdk.metrics.export.MetricExporter;
+import org.apache.solr.common.util.EnvUtils;
+
+public interface MetricExporterFactory {
+
+ public static final Boolean OTLP_EXPORTER_ENABLED =
+ Boolean.parseBoolean(EnvUtils.getProperty("solr.metrics.otlpExporterEnabled", "false"));
+
+ public static final String OTLP_EXPORTER_PROTOCOL =
+ EnvUtils.getProperty("solr.metrics.otlpExporterProtocol", "grpc");
+
+ public static final int OTLP_EXPORTER_INTERVAL =
+ Integer.parseInt(EnvUtils.getProperty("solr.metrics.otlpExporterInterval", "60000"));
+
+ MetricExporter getExporter();
+}
diff --git a/solr/core/src/java/org/apache/solr/metrics/otel/NoopMetricExporter.java b/solr/core/src/java/org/apache/solr/metrics/otel/NoopMetricExporter.java
new file mode 100644
index 00000000000..578a8f5a01c
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/metrics/otel/NoopMetricExporter.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.metrics.otel;
+
+import io.opentelemetry.sdk.common.CompletableResultCode;
+import io.opentelemetry.sdk.metrics.InstrumentType;
+import io.opentelemetry.sdk.metrics.data.AggregationTemporality;
+import io.opentelemetry.sdk.metrics.data.MetricData;
+import io.opentelemetry.sdk.metrics.export.MetricExporter;
+import java.util.Collection;
+
+public class NoopMetricExporter implements MetricExporter {
+ @Override
+ public CompletableResultCode export(Collection<MetricData> metrics) {
+ return CompletableResultCode.ofSuccess();
+ }
+
+ @Override
+ public CompletableResultCode flush() {
+ return CompletableResultCode.ofSuccess();
+ }
+
+ @Override
+ public CompletableResultCode shutdown() {
+ return CompletableResultCode.ofSuccess();
+ }
+
+ @Override
+ public AggregationTemporality getAggregationTemporality(InstrumentType instrumentType) {
+ return AggregationTemporality.CUMULATIVE;
+ }
+}
diff --git a/solr/core/src/test/org/apache/solr/blockcache/BufferStoreTest.java b/solr/core/src/test/org/apache/solr/blockcache/BufferStoreTest.java
index a628b9263a7..f42067715a0 100644
--- a/solr/core/src/test/org/apache/solr/blockcache/BufferStoreTest.java
+++ b/solr/core/src/test/org/apache/solr/blockcache/BufferStoreTest.java
@@ -39,7 +39,7 @@ public class BufferStoreTest extends SolrTestCase {
@Before
public void setup() {
metrics = new Metrics();
- metricManager = new SolrMetricManager();
+ metricManager = new SolrMetricManager(null);
registry = TestUtil.randomSimpleString(random(), 2, 10);
String scope = TestUtil.randomSimpleString(random(), 2, 10);
SolrMetricsContext solrMetricsContext = new SolrMetricsContext(metricManager, registry, "foo");
diff --git a/solr/core/src/test/org/apache/solr/metrics/SolrMetricManagerTest.java b/solr/core/src/test/org/apache/solr/metrics/SolrMetricManagerTest.java
index 683ed940733..4af0a5f3809 100644
--- a/solr/core/src/test/org/apache/solr/metrics/SolrMetricManagerTest.java
+++ b/solr/core/src/test/org/apache/solr/metrics/SolrMetricManagerTest.java
@@ -30,24 +30,30 @@ import io.opentelemetry.api.metrics.LongGauge;
import io.opentelemetry.api.metrics.LongHistogram;
import io.opentelemetry.api.metrics.LongUpDownCounter;
import io.opentelemetry.exporter.prometheus.PrometheusMetricReader;
+import io.opentelemetry.sdk.metrics.data.MetricData;
+import io.opentelemetry.sdk.testing.exporter.InMemoryMetricExporter;
import io.prometheus.metrics.model.snapshots.CounterSnapshot;
import io.prometheus.metrics.model.snapshots.GaugeSnapshot;
import io.prometheus.metrics.model.snapshots.HistogramSnapshot;
import io.prometheus.metrics.model.snapshots.MetricSnapshot;
import io.prometheus.metrics.model.snapshots.MetricSnapshots;
+import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.DoubleAdder;
import java.util.concurrent.atomic.LongAdder;
import org.apache.lucene.tests.util.TestUtil;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.common.util.NamedList;
+import org.apache.solr.common.util.RetryUtil;
import org.apache.solr.core.PluginInfo;
import org.apache.solr.core.SolrInfoBean;
import org.apache.solr.util.SolrMetricTestUtils;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -59,20 +65,24 @@ public class SolrMetricManagerTest extends SolrTestCaseJ4 {
@Before
public void setUp() throws Exception {
super.setUp();
- this.metricManager = new SolrMetricManager();
+ this.metricManager = new SolrMetricManager(InMemoryMetricExporter.create());
// Initialize a metric reader for tests
metricManager.meterProvider(METER_PROVIDER_NAME);
this.reader = metricManager.getPrometheusMetricReader(METER_PROVIDER_NAME);
}
+ @After
+ public void tearDown() throws Exception {
+ metricManager.closeAllRegistries();
+ super.tearDown();
+ }
+
// NOCOMMIT: Migration of this to OTEL isn't possible. You can't register instruments to a
// meterprovider that the provider itself didn't create
@Test
public void testRegisterAll() throws Exception {
Random r = random();
- SolrMetricManager metricManager = new SolrMetricManager();
-
Map<String, Counter> metrics = SolrMetricTestUtils.getRandomMetrics(r, true);
MetricRegistry mr = new MetricRegistry();
for (Map.Entry<String, Counter> entry : metrics.entrySet()) {
@@ -101,8 +111,6 @@ public class SolrMetricManagerTest extends SolrTestCaseJ4 {
public void testClearMetrics() {
Random r = random();
- SolrMetricManager metricManager = new SolrMetricManager();
-
Map<String, Counter> metrics = SolrMetricTestUtils.getRandomMetrics(r, true);
String registryName = TestUtil.randomSimpleString(r, 1, 10);
@@ -144,8 +152,6 @@ public class SolrMetricManagerTest extends SolrTestCaseJ4 {
public void testSimpleMetrics() {
Random r = random();
- SolrMetricManager metricManager = new SolrMetricManager();
-
String registryName = TestUtil.randomSimpleString(r, 1, 10);
metricManager.counter(null, registryName, "simple_counter", "foo", "bar");
@@ -390,6 +396,32 @@ public class SolrMetricManagerTest extends SolrTestCaseJ4 {
assertEquals(-10.1, actual.getDataPoints().getFirst().getValue(), 1e-6);
}
+ @Test
+ public void testMetricExporter() throws Exception {
+ LongCounter counter =
+ metricManager.longCounter(METER_PROVIDER_NAME, "my_counter", "desc", null);
+ counter.add(5);
+ counter.add(3);
+ InMemoryMetricExporter exporter = (InMemoryMetricExporter) metricManager.getMetricExporter();
+
+ RetryUtil.retryUntil(
+ "my_counter metric not found from exporter within timeout",
+ 50,
+ 100L,
+ TimeUnit.MILLISECONDS,
+ () -> {
+ Collection<MetricData> metrics = exporter.getFinishedMetricItems();
+ return metrics.stream()
+ .filter(m -> "my_counter".equals(m.getName()))
+ .findFirst()
+ .map(
+ actual ->
+ actual.getLongSumData().getPoints().stream()
+ .anyMatch(p -> p.getValue() == 8L))
+ .orElse(false);
+ });
+ }
+
@Test
public void testCloseMeterProviders() {
LongCounter counter =
diff --git a/solr/core/src/test/org/apache/solr/metrics/SolrMetricReporterTest.java b/solr/core/src/test/org/apache/solr/metrics/SolrMetricReporterTest.java
index c0ff0827d57..b9dd45e467c 100644
--- a/solr/core/src/test/org/apache/solr/metrics/SolrMetricReporterTest.java
+++ b/solr/core/src/test/org/apache/solr/metrics/SolrMetricReporterTest.java
@@ -33,7 +33,7 @@ public class SolrMetricReporterTest extends SolrTestCaseJ4 {
public void testInit() throws Exception {
Random random = random();
- SolrMetricManager metricManager = new SolrMetricManager();
+ SolrMetricManager metricManager = new SolrMetricManager(null);
final String registryName = TestUtil.randomSimpleString(random);
final MockMetricReporter reporter = new MockMetricReporter(metricManager, registryName);
diff --git a/solr/core/src/test/org/apache/solr/search/TestCaffeineCache.java b/solr/core/src/test/org/apache/solr/search/TestCaffeineCache.java
index 12d7813ec2d..d045a1d46de 100644
--- a/solr/core/src/test/org/apache/solr/search/TestCaffeineCache.java
+++ b/solr/core/src/test/org/apache/solr/search/TestCaffeineCache.java
@@ -45,7 +45,7 @@ import org.junit.Test;
/** Test for {@link CaffeineCache}. */
public class TestCaffeineCache extends SolrTestCase {
- SolrMetricManager metricManager = new SolrMetricManager();
+ SolrMetricManager metricManager = new SolrMetricManager(null);
String registry = TestUtil.randomSimpleString(random(), 2, 10);
String scope = TestUtil.randomSimpleString(random(), 2, 10);
diff --git a/solr/core/src/test/org/apache/solr/search/TestSolrCachePerf.java b/solr/core/src/test/org/apache/solr/search/TestSolrCachePerf.java
index 8e9e4717676..0675e43e752 100644
--- a/solr/core/src/test/org/apache/solr/search/TestSolrCachePerf.java
+++ b/solr/core/src/test/org/apache/solr/search/TestSolrCachePerf.java
@@ -33,6 +33,7 @@ import org.apache.commons.math3.stat.descriptive.SummaryStatistics;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.metrics.SolrMetricManager;
import org.apache.solr.metrics.SolrMetricsContext;
+import org.apache.solr.metrics.otel.NoopMetricExporter;
import org.apache.solr.util.SolrMetricTestUtils;
import org.junit.Before;
import org.junit.Test;
@@ -102,7 +103,7 @@ public class TestSolrCachePerf extends SolrTestCaseJ4 {
boolean useCompute)
throws Exception {
for (Class<? extends SolrCache> clazz : IMPLS) {
- SolrMetricManager metricManager = new SolrMetricManager();
+ SolrMetricManager metricManager = new SolrMetricManager(new NoopMetricExporter());
@SuppressWarnings({"unchecked"})
SolrCache<String, String> cache = clazz.getDeclaredConstructor().newInstance();
Map<String, String> params = new HashMap<>();
@@ -182,6 +183,7 @@ public class TestSolrCachePerf extends SolrTestCaseJ4 {
perImplRatio.addValue(hitRatio);
perImplTime.addValue((double) (stopTime - startTime));
cache.close();
+ metricManager.closeAllRegistries();
}
}
}
diff --git a/solr/core/src/test/org/apache/solr/search/TestThinCache.java b/solr/core/src/test/org/apache/solr/search/TestThinCache.java
index 00c7d7a02a1..ff174290d65 100644
--- a/solr/core/src/test/org/apache/solr/search/TestThinCache.java
+++ b/solr/core/src/test/org/apache/solr/search/TestThinCache.java
@@ -84,7 +84,7 @@ public class TestThinCache extends SolrTestCaseJ4 {
lrf = h.getRequestFactory("/select", 0, 20);
}
- SolrMetricManager metricManager = new SolrMetricManager();
+ SolrMetricManager metricManager = new SolrMetricManager(null);
String registry = TestUtil.randomSimpleString(random(), 2, 10);
String scope = TestUtil.randomSimpleString(random(), 2, 10);
diff --git a/solr/core/src/test/org/apache/solr/util/stats/OtelInstrumentedExecutorServiceTest.java b/solr/core/src/test/org/apache/solr/util/stats/OtelInstrumentedExecutorServiceTest.java
index 2d7ad162e4f..550477620e0 100644
--- a/solr/core/src/test/org/apache/solr/util/stats/OtelInstrumentedExecutorServiceTest.java
+++ b/solr/core/src/test/org/apache/solr/util/stats/OtelInstrumentedExecutorServiceTest.java
@@ -49,7 +49,7 @@ public class OtelInstrumentedExecutorServiceTest extends SolrTestCase {
@Before
public void setUpMetrics() {
- metricsContext = new SolrMetricsContext(new SolrMetricManager(), REGISTRY_NAME, TAG_NAME);
+ metricsContext = new SolrMetricsContext(new SolrMetricManager(null), REGISTRY_NAME, TAG_NAME);
}
@Test
diff --git a/solr/modules/cross-dc/src/test/org/apache/solr/crossdc/handler/MirroringCollectionsHandlerTest.java b/solr/modules/cross-dc/src/test/org/apache/solr/crossdc/handler/MirroringCollectionsHandlerTest.java
index ed914dfaeb1..a4cb9a97cc3 100644
--- a/solr/modules/cross-dc/src/test/org/apache/solr/crossdc/handler/MirroringCollectionsHandlerTest.java
+++ b/solr/modules/cross-dc/src/test/org/apache/solr/crossdc/handler/MirroringCollectionsHandlerTest.java
@@ -31,7 +31,6 @@ import org.apache.solr.common.cloud.Replica;
import org.apache.solr.common.cloud.SolrZkClient;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.core.CoreContainer;
-import org.apache.solr.core.OpenTelemetryConfigurator;
import org.apache.solr.core.SolrXmlConfig;
import org.apache.solr.crossdc.common.KafkaCrossDcConf;
import org.apache.solr.crossdc.common.KafkaMirroringSink;
@@ -161,8 +160,6 @@ public class MirroringCollectionsHandlerTest extends SolrTestCaseJ4 {
@Test
public void testCoreContainerInit() throws Exception {
- OpenTelemetryConfigurator.resetForTest();
-
Path home = createTempDir();
String solrXml = IOUtils.resourceToString("/mirroring-solr.xml", StandardCharsets.UTF_8);
CoreContainer cores = new CoreContainer(SolrXmlConfig.fromString(home, solrXml));
diff --git a/solr/modules/opentelemetry/build.gradle b/solr/modules/opentelemetry/build.gradle
index 7ab50a32421..00a693d3fd6 100644
--- a/solr/modules/opentelemetry/build.gradle
+++ b/solr/modules/opentelemetry/build.gradle
@@ -17,7 +17,7 @@
apply plugin: 'java-library'
-description = 'Open Telemetry (OTEL) tracer'
+description = 'Open Telemetry (OTEL) tracer and OTLP exporter'
dependencies {
implementation platform(project(':platform'))
@@ -31,7 +31,7 @@ dependencies {
implementation libs.opentelemetry.api
implementation libs.opentelemetry.sdkextension.autoconfigure
- runtimeOnly libs.opentelemetry.exporter.otlp
+ implementation libs.opentelemetry.exporter.otlp
// End users must recompile with jaeger exporter and/or zipkin exporter if they need these
// NOTE: sdk-autoconfigure needs both opentelemetry-sdk-metrics and opentelemetry-sdk-logs even if we don't use them
diff --git a/solr/modules/opentelemetry/gradle.lockfile b/solr/modules/opentelemetry/gradle.lockfile
index 9e62cfe1990..934ef9815c7 100644
--- a/solr/modules/opentelemetry/gradle.lockfile
+++ b/solr/modules/opentelemetry/gradle.lockfile
@@ -88,7 +88,7 @@ io.opentelemetry:opentelemetry-context:1.53.0=compileClasspath,jarValidation,run
io.opentelemetry:opentelemetry-exporter-common:1.50.0=solrPlatformLibs
io.opentelemetry:opentelemetry-exporter-common:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,testRuntimeClasspath
io.opentelemetry:opentelemetry-exporter-otlp-common:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,testRuntimeClasspath
-io.opentelemetry:opentelemetry-exporter-otlp:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-otlp:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
io.opentelemetry:opentelemetry-exporter-prometheus:1.50.0-alpha=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
io.opentelemetry:opentelemetry-exporter-sender-okhttp:1.53.0=jarValidation,runtimeClasspath,runtimeLibs,testRuntimeClasspath
io.opentelemetry:opentelemetry-sdk-common:1.53.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
diff --git a/solr/modules/opentelemetry/src/java/org/apache/solr/opentelemetry/OtlpExporterFactory.java b/solr/modules/opentelemetry/src/java/org/apache/solr/opentelemetry/OtlpExporterFactory.java
new file mode 100644
index 00000000000..879aecd99b8
--- /dev/null
+++ b/solr/modules/opentelemetry/src/java/org/apache/solr/opentelemetry/OtlpExporterFactory.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.opentelemetry;
+
+import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter;
+import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter;
+import io.opentelemetry.sdk.metrics.export.MetricExporter;
+import java.lang.invoke.MethodHandles;
+import org.apache.solr.metrics.otel.MetricExporterFactory;
+import org.apache.solr.metrics.otel.NoopMetricExporter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Factory class for creating OpenTelemetry OTLP metric exporters and its configuration properties.
+ *
+ * @see io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter
+ * @see io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter
+ * @see NoopMetricExporter
+ */
+public class OtlpExporterFactory implements MetricExporterFactory {
+
+ private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ public MetricExporter getExporter() {
+ if (!OTLP_EXPORTER_ENABLED) {
+ log.info("OTLP metric exporter is disabled.");
+ return new NoopMetricExporter();
+ }
+
+ return switch (OTLP_EXPORTER_PROTOCOL) {
+ case "grpc" -> OtlpGrpcMetricExporter.getDefault();
+ case "http" -> OtlpHttpMetricExporter.getDefault();
+ case "none" -> new NoopMetricExporter();
+ default -> {
+ log.warn(
+ "Unknown OTLP exporter type: {}. Defaulting to NO-OP exporter.",
+ OTLP_EXPORTER_PROTOCOL);
+ yield new NoopMetricExporter();
+ }
+ };
+ }
+}
diff --git a/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java b/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
index 769c7d7bf13..90cfdbff7ec 100644
--- a/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
+++ b/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
@@ -250,7 +250,6 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
@BeforeClass
public static void setupTestCases() {
- OpenTelemetryConfigurator.resetForTest();
resetExceptionIgnores();
testExecutor =
@@ -283,6 +282,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
System.setProperty("solr.filterCache.async", String.valueOf(random().nextBoolean()));
System.setProperty("solr.http.disableCookies", Boolean.toString(rarely()));
System.setProperty("solr.metrics.jvm.enabled", "false");
+ System.setProperty("solr.metrics.otlpExporterInterval", "1000");
startTrackingSearchers();
ignoreException("ignore_exception");
@@ -299,6 +299,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
}
ExecutorUtil.resetThreadLocalProviders();
+ OpenTelemetryConfigurator.resetForTest();
}
@AfterClass