diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-metrics.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-metrics.txt index 5753f2fee9b..6cf519e2823 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-metrics.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-metrics.txt @@ -1,10 +1,21 @@ Comparing source compatibility of against +*** MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.sdk.metrics.export.CollectionRegistration (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW METHOD: PUBLIC(+) java.util.Collection collectAllMetrics() + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.sdk.metrics.export.CollectionRegistration noop() *** MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.sdk.metrics.export.MetricExporter (not serializable) === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.common.export.MemoryMode getMemoryMode() ++++ NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.sdk.metrics.export.MetricProducer (not serializable) + +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. + +++ NEW SUPERCLASS: java.lang.Object + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) java.util.Collection produce(io.opentelemetry.sdk.resources.Resource) *** MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.sdk.metrics.export.MetricReader (not serializable) === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.common.export.MemoryMode getMemoryMode() *** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.sdk.metrics.export.PeriodicMetricReader (not serializable) === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.common.export.MemoryMode getMemoryMode() +*** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder registerMetricProducer(io.opentelemetry.sdk.metrics.export.MetricProducer) diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServer.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServer.java index cd8f22b1cde..cf1515a645e 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServer.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServer.java @@ -22,7 +22,6 @@ import io.opentelemetry.sdk.metrics.data.MetricData; import io.opentelemetry.sdk.metrics.export.CollectionRegistration; import io.opentelemetry.sdk.metrics.export.MetricReader; -import io.opentelemetry.sdk.metrics.internal.export.MetricProducer; import java.io.IOException; import java.io.OutputStream; import java.io.UncheckedIOException; @@ -60,7 +59,7 @@ public final class PrometheusHttpServer implements MetricReader { private final HttpServer server; private final ExecutorService executor; - private volatile MetricProducer metricProducer = MetricProducer.noop(); + private volatile CollectionRegistration collectionRegistration = CollectionRegistration.noop(); /** * Returns a new {@link PrometheusHttpServer} which can be registered to an {@link @@ -83,7 +82,7 @@ public static PrometheusHttpServerBuilder builder() { throw new UncheckedIOException("Could not create Prometheus HTTP server", e); } MetricsHandler metricsHandler = - new MetricsHandler(() -> getMetricProducer().collectAllMetrics()); + new MetricsHandler(() -> collectionRegistration.collectAllMetrics()); server.createContext("/", metricsHandler); server.createContext("/metrics", metricsHandler); server.createContext("/-/healthy", HealthHandler.INSTANCE); @@ -110,10 +109,6 @@ private static HttpServer createServer(String host, int port) throws IOException throw exception; } - private MetricProducer getMetricProducer() { - return metricProducer; - } - private void start() { // server.start must be called from a daemon thread for it to be a daemon. if (Thread.currentThread().isDaemon()) { @@ -131,13 +126,13 @@ private void start() { } @Override - public void register(CollectionRegistration registration) { - this.metricProducer = MetricProducer.asMetricProducer(registration); + public AggregationTemporality getAggregationTemporality(InstrumentType instrumentType) { + return AggregationTemporality.CUMULATIVE; } @Override - public AggregationTemporality getAggregationTemporality(InstrumentType instrumentType) { - return AggregationTemporality.CUMULATIVE; + public void register(CollectionRegistration registration) { + this.collectionRegistration = registration; } @Override diff --git a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServerTest.java b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServerTest.java index c6b5cd8d086..cbda79c4183 100644 --- a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServerTest.java +++ b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServerTest.java @@ -26,17 +26,18 @@ import io.opentelemetry.sdk.common.InstrumentationScopeInfo; import io.opentelemetry.sdk.metrics.data.AggregationTemporality; import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.metrics.export.CollectionRegistration; import io.opentelemetry.sdk.metrics.internal.data.ImmutableDoublePointData; import io.opentelemetry.sdk.metrics.internal.data.ImmutableGaugeData; import io.opentelemetry.sdk.metrics.internal.data.ImmutableLongPointData; import io.opentelemetry.sdk.metrics.internal.data.ImmutableMetricData; import io.opentelemetry.sdk.metrics.internal.data.ImmutableSumData; -import io.opentelemetry.sdk.metrics.internal.export.MetricProducer; import io.opentelemetry.sdk.resources.Resource; import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.ServerSocket; import java.nio.charset.StandardCharsets; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.concurrent.Executors; @@ -56,7 +57,6 @@ class PrometheusHttpServerTest { private static final AtomicReference> metricData = new AtomicReference<>(); - private static final MetricProducer metricProducer = metricData::get; static PrometheusHttpServer prometheusServer; static WebClient client; @@ -68,7 +68,13 @@ class PrometheusHttpServerTest { static void beforeAll() { // Register the SDK metric producer with the prometheus reader. prometheusServer = PrometheusHttpServer.builder().setHost("localhost").setPort(0).build(); - prometheusServer.register(metricProducer); + prometheusServer.register( + new CollectionRegistration() { + @Override + public Collection collectAllMetrics() { + return metricData.get(); + } + }); client = WebClient.builder("http://localhost:" + prometheusServer.getAddress().getPort()) diff --git a/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/OpenCensusMetricProducer.java b/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/OpenCensusMetricProducer.java new file mode 100644 index 00000000000..9a2e29a2bc2 --- /dev/null +++ b/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/OpenCensusMetricProducer.java @@ -0,0 +1,53 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opencensusshim; + +import io.opencensus.metrics.Metrics; +import io.opencensus.metrics.export.MetricProducerManager; +import io.opentelemetry.opencensusshim.internal.metrics.MetricAdapter; +import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.metrics.export.MetricProducer; +import io.opentelemetry.sdk.metrics.export.MetricReader; +import io.opentelemetry.sdk.resources.Resource; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * {@link MetricProducer} for OpenCensus metrics, which allows {@link MetricReader}s to read from + * both OpenTelemetry and OpenCensus metrics. + * + *

To use, register with {@link SdkMeterProviderBuilder#registerMetricProducer(MetricProducer)}. + */ +public final class OpenCensusMetricProducer implements MetricProducer { + private final MetricProducerManager openCensusMetricStorage; + + OpenCensusMetricProducer(MetricProducerManager openCensusMetricStorage) { + this.openCensusMetricStorage = openCensusMetricStorage; + } + + /** + * Constructs a new {@link OpenCensusMetricProducer} that reports against the given {@link + * Resource}. + */ + public static MetricProducer create() { + return new OpenCensusMetricProducer(Metrics.getExportComponent().getMetricProducerManager()); + } + + @Override + public Collection produce(Resource resource) { + List result = new ArrayList<>(); + openCensusMetricStorage + .getAllMetricProducer() + .forEach( + producer -> + producer + .getMetrics() + .forEach(metric -> result.add(MetricAdapter.convert(resource, metric)))); + return result; + } +} diff --git a/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/metrics/MultiMetricProducer.java b/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/metrics/MultiMetricProducer.java deleted file mode 100644 index a2494faf783..00000000000 --- a/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/metrics/MultiMetricProducer.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.opencensusshim.metrics; - -import io.opentelemetry.sdk.metrics.data.MetricData; -import io.opentelemetry.sdk.metrics.internal.export.MetricProducer; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -/** Class that wraps multiple metric producers into one. */ -final class MultiMetricProducer implements MetricProducer { - private final Collection producers; - - public MultiMetricProducer(Collection producers) { - this.producers = producers; - } - - @Override - public Collection collectAllMetrics() { - List result = new ArrayList<>(); - for (MetricProducer p : producers) { - result.addAll(p.collectAllMetrics()); - } - return result; - } -} diff --git a/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/metrics/OpenCensusAttachingMetricReader.java b/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/metrics/OpenCensusAttachingMetricReader.java deleted file mode 100644 index 46e0f9534c2..00000000000 --- a/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/metrics/OpenCensusAttachingMetricReader.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.opencensusshim.metrics; - -import io.opentelemetry.sdk.common.CompletableResultCode; -import io.opentelemetry.sdk.metrics.InstrumentType; -import io.opentelemetry.sdk.metrics.data.AggregationTemporality; -import io.opentelemetry.sdk.metrics.export.CollectionRegistration; -import io.opentelemetry.sdk.metrics.export.MetricReader; -import io.opentelemetry.sdk.metrics.internal.export.MetricProducer; -import io.opentelemetry.sdk.resources.Resource; -import java.util.Arrays; - -/** {@link MetricReader} that appends OpenCensus metrics to anything read. */ -final class OpenCensusAttachingMetricReader implements MetricReader { - private final MetricReader adapted; - - OpenCensusAttachingMetricReader(MetricReader adapted) { - this.adapted = adapted; - } - - @Override - public void register(CollectionRegistration registration) { - // TODO: Find a way to pull the resource off of the SDK. - adapted.register( - new MultiMetricProducer( - Arrays.asList( - MetricProducer.asMetricProducer(registration), - OpenCensusMetricProducer.create(Resource.getDefault())))); - } - - @Override - public AggregationTemporality getAggregationTemporality(InstrumentType instrumentType) { - return adapted.getAggregationTemporality(instrumentType); - } - - @Override - public CompletableResultCode forceFlush() { - return adapted.forceFlush(); - } - - @Override - public CompletableResultCode shutdown() { - return adapted.shutdown(); - } -} diff --git a/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/metrics/OpenCensusMetricProducer.java b/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/metrics/OpenCensusMetricProducer.java deleted file mode 100644 index 77af7ecf5ee..00000000000 --- a/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/metrics/OpenCensusMetricProducer.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.opencensusshim.metrics; - -import io.opencensus.metrics.Metrics; -import io.opencensus.metrics.export.MetricProducerManager; -import io.opentelemetry.opencensusshim.internal.metrics.MetricAdapter; -import io.opentelemetry.sdk.metrics.SdkMeterProvider; -import io.opentelemetry.sdk.metrics.data.MetricData; -import io.opentelemetry.sdk.metrics.export.MetricReader; -import io.opentelemetry.sdk.metrics.internal.export.MetricProducer; -import io.opentelemetry.sdk.resources.Resource; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -/** - * A producer instance of OpenCensus metrics. - * - *

The idea here is we can register a merged {@link MetricProducer} combining this with the - * {@link SdkMeterProvider} producer with a {@link MetricReader}, allowing the reader to pull - * metrics from both OpenTelemetry and OpenCensus backends. - */ -final class OpenCensusMetricProducer implements MetricProducer { - private final Resource resource; - private final MetricProducerManager openCensusMetricStorage; - - OpenCensusMetricProducer(Resource resource, MetricProducerManager openCensusMetricStorage) { - this.resource = resource; - this.openCensusMetricStorage = openCensusMetricStorage; - } - - /** - * Constructs a new {@link OpenCensusMetricProducer} that reports against the given {@link - * Resource}. - */ - static MetricProducer create(Resource resource) { - return new OpenCensusMetricProducer( - resource, Metrics.getExportComponent().getMetricProducerManager()); - } - - @Override - public Collection collectAllMetrics() { - List result = new ArrayList<>(); - openCensusMetricStorage - .getAllMetricProducer() - .forEach( - producer -> { - producer - .getMetrics() - .forEach(metric -> result.add(MetricAdapter.convert(resource, metric))); - }); - return result; - } -} diff --git a/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/metrics/OpenCensusMetrics.java b/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/metrics/OpenCensusMetrics.java deleted file mode 100644 index fc4d5a2b22c..00000000000 --- a/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/metrics/OpenCensusMetrics.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.opencensusshim.metrics; - -import io.opentelemetry.sdk.metrics.export.MetricReader; - -/** Convenience methods for adapting OpenCensus metrics into OpenTelemetry. */ -public final class OpenCensusMetrics { - private OpenCensusMetrics() {} - - /** - * Attaches OpenCensus metrics to metrics read by the given input. - * - * @param input A {@link MetricReader} that will receive OpenCensus metrics. - * @return The adapted MetricReaderFactory. - */ - public static MetricReader attachTo(MetricReader input) { - return new OpenCensusAttachingMetricReader(input); - } -} diff --git a/opencensus-shim/src/test/java/io/opentelemetry/opencensusshim/metrics/OpenCensusMetricProducerTest.java b/opencensus-shim/src/test/java/io/opentelemetry/opencensusshim/metrics/OpenCensusMetricProducerTest.java index 1e9f756ca47..c2465522296 100644 --- a/opencensus-shim/src/test/java/io/opentelemetry/opencensusshim/metrics/OpenCensusMetricProducerTest.java +++ b/opencensus-shim/src/test/java/io/opentelemetry/opencensusshim/metrics/OpenCensusMetricProducerTest.java @@ -21,7 +21,8 @@ import io.opencensus.trace.TraceOptions; import io.opencensus.trace.Tracestate; import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.sdk.metrics.internal.export.MetricProducer; +import io.opentelemetry.opencensusshim.OpenCensusMetricProducer; +import io.opentelemetry.sdk.metrics.export.MetricProducer; import io.opentelemetry.sdk.resources.Resource; import java.time.Duration; import java.util.Arrays; @@ -31,8 +32,7 @@ import org.junit.jupiter.api.Test; class OpenCensusMetricProducerTest { - private final MetricProducer openCensusMetrics = - OpenCensusMetricProducer.create(Resource.empty()); + private final MetricProducer openCensusMetrics = OpenCensusMetricProducer.create(); private static final Measure.MeasureLong LATENCY_MS = Measure.MeasureLong.create("task_latency", "The task latency in milliseconds", "ms"); @@ -69,7 +69,7 @@ void extractHistogram() throws InterruptedException { .atMost(Duration.ofSeconds(10)) .untilAsserted( () -> - assertThat(openCensusMetrics.collectAllMetrics()) + assertThat(openCensusMetrics.produce(Resource.empty())) .satisfiesExactly( metric -> assertThat(metric) diff --git a/opencensus-shim/src/test/java/io/opentelemetry/opencensusshim/metrics/OpenCensusMetricsTest.java b/opencensus-shim/src/test/java/io/opentelemetry/opencensusshim/metrics/OpenCensusMetricsTest.java index 661054ecc1b..368e08ccb2f 100644 --- a/opencensus-shim/src/test/java/io/opentelemetry/opencensusshim/metrics/OpenCensusMetricsTest.java +++ b/opencensus-shim/src/test/java/io/opentelemetry/opencensusshim/metrics/OpenCensusMetricsTest.java @@ -12,6 +12,7 @@ import io.opencensus.stats.Stats; import io.opencensus.stats.StatsRecorder; import io.opencensus.stats.View; +import io.opentelemetry.opencensusshim.OpenCensusMetricProducer; import io.opentelemetry.sdk.metrics.SdkMeterProvider; import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; import java.time.Duration; @@ -26,7 +27,10 @@ class OpenCensusMetricsTest { void capturesOpenCensusAndOtelMetrics() throws InterruptedException { InMemoryMetricReader reader = InMemoryMetricReader.create(); SdkMeterProvider otelMetrics = - SdkMeterProvider.builder().registerMetricReader(OpenCensusMetrics.attachTo(reader)).build(); + SdkMeterProvider.builder() + .registerMetricReader(reader) + .registerMetricProducer(OpenCensusMetricProducer.create()) + .build(); // Record an otel metric. otelMetrics.meterBuilder("otel").build().counterBuilder("otel.sum").build().add(1); // Record an OpenCensus metric. @@ -47,7 +51,7 @@ void capturesOpenCensusAndOtelMetrics() throws InterruptedException { .untilAsserted( () -> assertThat(reader.collectAllMetrics()) - .satisfiesExactly( + .satisfiesExactlyInAnyOrder( metric -> assertThat(metric).hasName("otel.sum").hasLongSumSatisfying(sum -> {}), metric -> diff --git a/sdk/all/src/test/java/io/opentelemetry/sdk/OpenTelemetrySdkTest.java b/sdk/all/src/test/java/io/opentelemetry/sdk/OpenTelemetrySdkTest.java index 09e1a402671..d3216fc9fa2 100644 --- a/sdk/all/src/test/java/io/opentelemetry/sdk/OpenTelemetrySdkTest.java +++ b/sdk/all/src/test/java/io/opentelemetry/sdk/OpenTelemetrySdkTest.java @@ -421,6 +421,7 @@ void stringRepresentation() { + "clock=SystemClock{}, " + "resource=Resource{schemaUrl=null, attributes={service.name=\"otel-test\"}}, " + "metricReaders=[PeriodicMetricReader{exporter=MockMetricExporter{}, intervalNanos=60000000000}], " + + "metricProducers=[], " + "views=[RegisteredView{instrumentSelector=InstrumentSelector{instrumentName=instrument}, view=View{name=new-instrument, aggregation=DefaultAggregation, attributesProcessor=NoopAttributesProcessor{}, cardinalityLimit=2000}}]" + "}, " + "loggerProvider=SdkLoggerProvider{" diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkMeterProvider.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkMeterProvider.java index 431fc41ab30..63b241bc5b1 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkMeterProvider.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkMeterProvider.java @@ -13,11 +13,12 @@ import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.internal.ComponentRegistry; import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.metrics.export.CollectionRegistration; +import io.opentelemetry.sdk.metrics.export.MetricProducer; import io.opentelemetry.sdk.metrics.export.MetricReader; import io.opentelemetry.sdk.metrics.internal.SdkMeterProviderUtil; import io.opentelemetry.sdk.metrics.internal.exemplar.ExemplarFilter; import io.opentelemetry.sdk.metrics.internal.export.CardinalityLimitSelector; -import io.opentelemetry.sdk.metrics.internal.export.MetricProducer; import io.opentelemetry.sdk.metrics.internal.export.RegisteredReader; import io.opentelemetry.sdk.metrics.internal.state.MeterProviderSharedState; import io.opentelemetry.sdk.metrics.internal.view.RegisteredView; @@ -45,6 +46,7 @@ public final class SdkMeterProvider implements MeterProvider, Closeable { private final List registeredViews; private final List registeredReaders; + private final List metricProducers; private final MeterProviderSharedState sharedState; private final ComponentRegistry registry; private final AtomicBoolean isClosed = new AtomicBoolean(false); @@ -57,6 +59,7 @@ public static SdkMeterProviderBuilder builder() { SdkMeterProvider( List registeredViews, IdentityHashMap metricReaders, + List metricProducers, Clock clock, Resource resource, ExemplarFilter exemplarFilter) { @@ -70,6 +73,7 @@ public static SdkMeterProviderBuilder builder() { entry.getKey(), ViewRegistry.create(entry.getKey(), entry.getValue(), registeredViews))) .collect(toList()); + this.metricProducers = metricProducers; this.sharedState = MeterProviderSharedState.create(clock, resource, exemplarFilter, startEpochNanos); this.registry = @@ -77,8 +81,11 @@ public static SdkMeterProviderBuilder builder() { instrumentationLibraryInfo -> new SdkMeter(sharedState, instrumentationLibraryInfo, registeredReaders)); for (RegisteredReader registeredReader : registeredReaders) { - MetricProducer producer = new LeasedMetricProducer(registry, sharedState, registeredReader); - registeredReader.getReader().register(producer); + List readerMetricProducers = new ArrayList<>(metricProducers); + readerMetricProducers.add(new LeasedMetricProducer(registry, sharedState, registeredReader)); + registeredReader + .getReader() + .register(new SdkCollectionRegistration(readerMetricProducers, sharedState)); registeredReader.setLastCollectEpochNanos(startEpochNanos); } } @@ -154,6 +161,8 @@ public String toString() { + sharedState.getResource() + ", metricReaders=" + registeredReaders.stream().map(RegisteredReader::getReader).collect(toList()) + + ", metricProducers=" + + metricProducers + ", views=" + registeredViews + "}"; @@ -176,7 +185,7 @@ private static class LeasedMetricProducer implements MetricProducer { } @Override - public Collection collectAllMetrics() { + public Collection produce(Resource unused) { Collection meters = registry.getComponents(); List result = new ArrayList<>(); long collectTime = sharedState.getClock().now(); @@ -187,4 +196,31 @@ public Collection collectAllMetrics() { return Collections.unmodifiableCollection(result); } } + + private static class SdkCollectionRegistration implements CollectionRegistration { + private final List metricProducers; + private final MeterProviderSharedState sharedState; + + private SdkCollectionRegistration( + List metricProducers, MeterProviderSharedState sharedState) { + this.metricProducers = metricProducers; + this.sharedState = sharedState; + } + + @Override + public Collection collectAllMetrics() { + if (metricProducers.isEmpty()) { + return Collections.emptyList(); + } + Resource resource = sharedState.getResource(); + if (metricProducers.size() == 1) { + return metricProducers.get(0).produce(resource); + } + List metricData = new ArrayList<>(); + for (MetricProducer metricProducer : metricProducers) { + metricData.addAll(metricProducer.produce(resource)); + } + return Collections.unmodifiableList(metricData); + } + } } diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkMeterProviderBuilder.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkMeterProviderBuilder.java index 1ad5efc0c22..3e444f10f93 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkMeterProviderBuilder.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkMeterProviderBuilder.java @@ -6,6 +6,7 @@ package io.opentelemetry.sdk.metrics; import io.opentelemetry.sdk.common.Clock; +import io.opentelemetry.sdk.metrics.export.MetricProducer; import io.opentelemetry.sdk.metrics.export.MetricReader; import io.opentelemetry.sdk.metrics.internal.SdkMeterProviderUtil; import io.opentelemetry.sdk.metrics.internal.debug.SourceInfo; @@ -36,6 +37,7 @@ public final class SdkMeterProviderBuilder { private Resource resource = Resource.getDefault(); private final IdentityHashMap metricReaders = new IdentityHashMap<>(); + private final List metricProducers = new ArrayList<>(); private final List registeredViews = new ArrayList<>(); private ExemplarFilter exemplarFilter = DEFAULT_EXEMPLAR_FILTER; @@ -119,11 +121,7 @@ public SdkMeterProviderBuilder registerView(InstrumentSelector selector, View vi return this; } - /** - * Registers a {@link MetricReader}. - * - *

Note: custom implementations of {@link MetricReader} are not currently supported. - */ + /** Registers a {@link MetricReader}. */ public SdkMeterProviderBuilder registerMetricReader(MetricReader reader) { metricReaders.put(reader, CardinalityLimitSelector.defaultCardinalityLimitSelector()); return this; @@ -142,8 +140,19 @@ SdkMeterProviderBuilder registerMetricReader( return this; } + /** + * Registers a {@link MetricProducer}. + * + * @since 1.31.0 + */ + public SdkMeterProviderBuilder registerMetricProducer(MetricProducer metricProducer) { + metricProducers.add(metricProducer); + return this; + } + /** Returns an {@link SdkMeterProvider} built with the configuration of this builder. */ public SdkMeterProvider build() { - return new SdkMeterProvider(registeredViews, metricReaders, clock, resource, exemplarFilter); + return new SdkMeterProvider( + registeredViews, metricReaders, metricProducers, clock, resource, exemplarFilter); } } diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/export/CollectionRegistration.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/export/CollectionRegistration.java index 707da70ea95..c8511700cb4 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/export/CollectionRegistration.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/export/CollectionRegistration.java @@ -5,7 +5,11 @@ package io.opentelemetry.sdk.metrics.export; +import io.opentelemetry.sdk.common.export.MemoryMode; import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.metrics.data.MetricData; +import java.util.Collection; +import java.util.Collections; /** * A {@link CollectionRegistration} is passed to each {@link MetricReader} registered with {@link @@ -13,5 +17,29 @@ * * @since 1.14.0 */ -// TODO(jack-berg): Have methods when custom MetricReaders are supported -public interface CollectionRegistration {} +public interface CollectionRegistration { + + /** + * Returns a noop {@link CollectionRegistration}, useful for {@link MetricReader}s to hold before + * {@link MetricReader#register(CollectionRegistration)} is called. + */ + static CollectionRegistration noop() { + return new CollectionRegistration() { + @Override + public Collection collectAllMetrics() { + return Collections.emptyList(); + } + }; + } + + /** + * Collect all metrics, including metrics from the SDK and any registered {@link MetricProducer}s. + * + *

If {@link MetricReader#getMemoryMode()} is configured to {@link MemoryMode#REUSABLE_DATA} do + * not keep the result or any of its contained objects as they are to be reused to return the + * result for the next call to this method. + */ + default Collection collectAllMetrics() { + return Collections.emptyList(); + } +} diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/export/MetricProducer.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/export/MetricProducer.java new file mode 100644 index 00000000000..723eb7e1e92 --- /dev/null +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/export/MetricProducer.java @@ -0,0 +1,42 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.metrics.export; + +import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.resources.Resource; +import java.util.Collection; +import javax.annotation.concurrent.ThreadSafe; + +/** + * {@link MetricProducer} is the interface that is used to make metric data available to the {@link + * MetricReader}s. The primary implementation is provided by {@link + * io.opentelemetry.sdk.metrics.SdkMeterProvider}. + * + *

Alternative {@link MetricProducer} implementations can be used to bridge aggregated metrics + * from other frameworks, and are registered with {@link + * SdkMeterProviderBuilder#registerMetricProducer(MetricProducer)}. NOTE: When possible, metrics + * from other frameworks SHOULD be bridged using the metric API, normally with asynchronous + * instruments which observe the aggregated state of the other framework. However, {@link + * MetricProducer} exists to accommodate scenarios where the metric API is insufficient. It should + * be used with caution as it requires the bridge to take a dependency on {@code + * opentelemetry-sdk-metrics}, which is generally not advised. + * + *

Implementations must be thread-safe. + * + * @since 1.31.0 + */ +@ThreadSafe +public interface MetricProducer { + + /** + * Returns a collection of produced {@link MetricData}s to be exported. This will only be those + * metrics that have been produced since the last time this method was called. + * + * @return a collection of produced {@link MetricData}s to be exported. + */ + Collection produce(Resource resource); +} diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/export/MetricReader.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/export/MetricReader.java index 4ff4155f9bd..7ddab97dfc1 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/export/MetricReader.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/export/MetricReader.java @@ -19,20 +19,15 @@ /** * A metric reader reads metrics from an {@link SdkMeterProvider}. * - *

Custom implementations of {@link MetricReader} are not currently supported. Please use one of - * the built-in readers such as {@link PeriodicMetricReader}. - * * @since 1.14.0 */ public interface MetricReader extends AggregationTemporalitySelector, DefaultAggregationSelector, Closeable { /** - * Called by {@link SdkMeterProvider} and supplies the {@link MetricReader} with a handle to - * collect metrics. - * - *

{@link CollectionRegistration} is currently an empty interface because custom - * implementations of {@link MetricReader} are not currently supported. + * Called by {@link SdkMeterProvider} on initialization to supply the {@link MetricReader} with + * {@link MetricProducer}s used to collect metrics. {@link MetricReader} implementations call + * {@link CollectionRegistration#collectAllMetrics()} to read metrics. */ void register(CollectionRegistration registration); diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/export/PeriodicMetricReader.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/export/PeriodicMetricReader.java index d22133677fb..a19dbae5781 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/export/PeriodicMetricReader.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/export/PeriodicMetricReader.java @@ -13,7 +13,6 @@ import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder; import io.opentelemetry.sdk.metrics.data.AggregationTemporality; import io.opentelemetry.sdk.metrics.data.MetricData; -import io.opentelemetry.sdk.metrics.internal.export.MetricProducer; import java.util.Collection; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; @@ -40,8 +39,8 @@ public final class PeriodicMetricReader implements MetricReader { private final ScheduledExecutorService scheduler; private final Scheduled scheduled; private final Object lock = new Object(); + private volatile CollectionRegistration collectionRegistration = CollectionRegistration.noop(); - private volatile MetricProducer metricProducer = MetricProducer.noop(); @Nullable private volatile ScheduledFuture scheduledFuture; /** @@ -117,8 +116,8 @@ public CompletableResultCode shutdown() { } @Override - public void register(CollectionRegistration registration) { - this.metricProducer = MetricProducer.asMetricProducer(registration); + public void register(CollectionRegistration collectionRegistration) { + this.collectionRegistration = collectionRegistration; start(); } @@ -159,7 +158,7 @@ CompletableResultCode doRun() { CompletableResultCode flushResult = new CompletableResultCode(); if (exportAvailable.compareAndSet(true, false)) { try { - Collection metricData = metricProducer.collectAllMetrics(); + Collection metricData = collectionRegistration.collectAllMetrics(); if (metricData.isEmpty()) { logger.log(Level.FINE, "No metric data to export - skipping export."); flushResult.succeed(); diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/export/MetricProducer.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/export/MetricProducer.java deleted file mode 100644 index 63bb152f638..00000000000 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/export/MetricProducer.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.metrics.internal.export; - -import io.opentelemetry.sdk.common.export.MemoryMode; -import io.opentelemetry.sdk.metrics.data.MetricData; -import io.opentelemetry.sdk.metrics.export.CollectionRegistration; -import io.opentelemetry.sdk.metrics.export.MetricReader; -import java.util.Collection; -import java.util.Collections; -import javax.annotation.concurrent.ThreadSafe; - -/** - * {@code MetricProducer} is the interface that is used to make metric data available to the {@link - * MetricReader}s. Implementations should be stateful, in that each call to {@link - * #collectAllMetrics()} will return any metric generated since the last call was made. - * - *

Implementations must be thread-safe. - * - *

This class is internal and is hence not for public use. Its APIs are unstable and can change - * at any time. - */ -@ThreadSafe -public interface MetricProducer extends CollectionRegistration { - - /** Cast the registration to a {@link MetricProducer}. */ - static MetricProducer asMetricProducer(CollectionRegistration registration) { - if (!(registration instanceof MetricProducer)) { - throw new IllegalArgumentException( - "unrecognized CollectionRegistration, custom MetricReader implementations are not currently supported"); - } - return (MetricProducer) registration; - } - - /** Return a noop {@link MetricProducer}. */ - static MetricProducer noop() { - return Collections::emptyList; - } - - /** - * Returns a collection of produced {@link MetricData}s to be exported. This will only be those - * metrics that have been produced since the last time this method was called. - * - *

If {@link MetricReader#getMemoryMode()} is configured to {@link MemoryMode#REUSABLE_DATA} do - * not keep the result or any of its contained objects as they are to be reused to return the - * result for the next call of {@code collectAllMetrics} - * - * @return a collection of produced {@link MetricData}s to be exported. - */ - Collection collectAllMetrics(); -} diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/export/RegisteredReader.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/export/RegisteredReader.java index fbb587741b0..c56d01b9cda 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/export/RegisteredReader.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/export/RegisteredReader.java @@ -8,6 +8,7 @@ import io.opentelemetry.sdk.metrics.SdkMeterProvider; import io.opentelemetry.sdk.metrics.data.AggregationTemporality; import io.opentelemetry.sdk.metrics.data.PointData; +import io.opentelemetry.sdk.metrics.export.MetricProducer; import io.opentelemetry.sdk.metrics.export.MetricReader; import io.opentelemetry.sdk.metrics.internal.view.ViewRegistry; import java.util.concurrent.atomic.AtomicInteger; diff --git a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/export/PeriodicMetricReaderTest.java b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/export/PeriodicMetricReaderTest.java index 142e8816366..03c8d05cfaa 100644 --- a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/export/PeriodicMetricReaderTest.java +++ b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/export/PeriodicMetricReaderTest.java @@ -26,7 +26,6 @@ import io.opentelemetry.sdk.metrics.internal.data.ImmutableLongPointData; import io.opentelemetry.sdk.metrics.internal.data.ImmutableMetricData; import io.opentelemetry.sdk.metrics.internal.data.ImmutableSumData; -import io.opentelemetry.sdk.metrics.internal.export.MetricProducer; import io.opentelemetry.sdk.resources.Resource; import java.io.IOException; import java.time.Duration; @@ -67,17 +66,19 @@ class PeriodicMetricReaderTest { ImmutableSumData.create( /* isMonotonic= */ true, AggregationTemporality.CUMULATIVE, LONG_POINT_LIST)); - @Mock private MetricProducer metricProducer; + @Mock private CollectionRegistration collectionRegistration; @Mock private MetricExporter metricExporter; @BeforeEach void setup() { - when(metricProducer.collectAllMetrics()).thenReturn(Collections.singletonList(METRIC_DATA)); + when(collectionRegistration.collectAllMetrics()) + .thenReturn(Collections.singletonList(METRIC_DATA)); } @Test @SuppressWarnings({"rawtypes", "unchecked"}) void startOnlyOnce() { + ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class); ScheduledFuture mock = mock(ScheduledFuture.class); @@ -89,7 +90,7 @@ void startOnlyOnce() { .setExecutor(scheduler) .build(); - reader.register(metricProducer); + reader.register(collectionRegistration); verify(scheduler, times(1)).scheduleAtFixedRate(any(), anyLong(), anyLong(), any()); } @@ -102,7 +103,7 @@ void periodicExport() throws Exception { .setInterval(Duration.ofMillis(100)) .build(); - reader.register(metricProducer); + reader.register(collectionRegistration); try { assertThat(waitingMetricExporter.waitForNumberOfExports(1)) .containsExactly(Collections.singletonList(METRIC_DATA)); @@ -122,12 +123,12 @@ void periodicExport_NoMetricsSkipsExport() { PeriodicMetricReader.builder(waitingMetricExporter) .setInterval(Duration.ofMillis(100)) .build(); - when(metricProducer.collectAllMetrics()).thenReturn(Collections.emptyList()); - reader.register(metricProducer); + when(collectionRegistration.collectAllMetrics()).thenReturn(Collections.emptyList()); + reader.register(collectionRegistration); try { assertThat(reader.forceFlush().join(10, TimeUnit.SECONDS).isSuccess()).isTrue(); - verify(metricProducer).collectAllMetrics(); + verify(collectionRegistration).collectAllMetrics(); assertThat(waitingMetricExporter.exportTimes.size()).isEqualTo(0); } finally { reader.shutdown(); @@ -142,7 +143,7 @@ void flush() throws Exception { .setInterval(Duration.ofNanos(Long.MAX_VALUE)) .build(); - reader.register(metricProducer); + reader.register(collectionRegistration); assertThat(reader.forceFlush().join(10, TimeUnit.SECONDS).isSuccess()).isTrue(); try { @@ -164,7 +165,7 @@ public void intervalExport_exporterThrowsException() throws Exception { .setInterval(Duration.ofMillis(100)) .build(); - reader.register(metricProducer); + reader.register(collectionRegistration); try { assertThat(waitingMetricExporter.waitForNumberOfExports(2)) .containsExactly( @@ -181,7 +182,7 @@ void shutdown_ExportsOneLastTime() throws Exception { PeriodicMetricReader.builder(waitingMetricExporter) .setInterval(Duration.ofSeconds(Integer.MAX_VALUE)) .build(); - reader.register(metricProducer); + reader.register(collectionRegistration); reader.shutdown(); // This export was called during shutdown. @@ -198,7 +199,7 @@ void close_CallsShutdown() throws IOException { PeriodicMetricReader.builder(new WaitingMetricExporter()) .setInterval(Duration.ofSeconds(Integer.MAX_VALUE)) .build()); - reader.register(metricProducer); + reader.register(collectionRegistration); reader.close(); verify(reader, times(1)).shutdown(); diff --git a/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/exporter/InMemoryMetricReader.java b/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/exporter/InMemoryMetricReader.java index e72a78c52ee..3b79affe346 100644 --- a/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/exporter/InMemoryMetricReader.java +++ b/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/exporter/InMemoryMetricReader.java @@ -17,7 +17,6 @@ import io.opentelemetry.sdk.metrics.export.CollectionRegistration; import io.opentelemetry.sdk.metrics.export.DefaultAggregationSelector; import io.opentelemetry.sdk.metrics.export.MetricReader; -import io.opentelemetry.sdk.metrics.internal.export.MetricProducer; import java.util.Collection; import java.util.Collections; import java.util.concurrent.atomic.AtomicBoolean; @@ -54,7 +53,7 @@ public class InMemoryMetricReader implements MetricReader { private final AggregationTemporalitySelector aggregationTemporalitySelector; private final DefaultAggregationSelector defaultAggregationSelector; private final AtomicBoolean isShutdown = new AtomicBoolean(false); - private volatile MetricProducer metricProducer = MetricProducer.noop(); + private volatile CollectionRegistration collectionRegistration = CollectionRegistration.noop(); private final MemoryMode memoryMode; /** @@ -111,12 +110,12 @@ public Collection collectAllMetrics() { if (isShutdown.get()) { return Collections.emptyList(); } - return metricProducer.collectAllMetrics(); + return collectionRegistration.collectAllMetrics(); } @Override - public void register(CollectionRegistration registration) { - this.metricProducer = MetricProducer.asMetricProducer(registration); + public void register(CollectionRegistration collectionRegistration) { + this.collectionRegistration = collectionRegistration; } @Override