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 <[email protected]> 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 <[email protected]> --- 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
