io.opentelemetry.sdk.metrics.SdkMeterProvider Maven / Gradle / Ivy
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.metrics;
import static java.util.stream.Collectors.toList;
import io.opentelemetry.api.metrics.MeterBuilder;
import io.opentelemetry.api.metrics.MeterProvider;
import io.opentelemetry.sdk.common.Clock;
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.RegisteredReader;
import io.opentelemetry.sdk.metrics.internal.state.MeterProviderSharedState;
import io.opentelemetry.sdk.metrics.internal.view.RegisteredView;
import io.opentelemetry.sdk.metrics.internal.view.ViewRegistry;
import io.opentelemetry.sdk.resources.Resource;
import java.io.Closeable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
/**
* SDK implementation for {@link MeterProvider}.
*
* @since 1.14.0
*/
public final class SdkMeterProvider implements MeterProvider, Closeable {
private static final Logger LOGGER = Logger.getLogger(SdkMeterProvider.class.getName());
static final String DEFAULT_METER_NAME = "unknown";
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);
/** Returns a new {@link SdkMeterProviderBuilder} for {@link SdkMeterProvider}. */
public static SdkMeterProviderBuilder builder() {
return new SdkMeterProviderBuilder();
}
SdkMeterProvider(
List registeredViews,
IdentityHashMap metricReaders,
List metricProducers,
Clock clock,
Resource resource,
ExemplarFilter exemplarFilter) {
long startEpochNanos = clock.now();
this.registeredViews = registeredViews;
this.registeredReaders =
metricReaders.entrySet().stream()
.map(
entry ->
RegisteredReader.create(
entry.getKey(),
ViewRegistry.create(entry.getKey(), entry.getValue(), registeredViews)))
.collect(toList());
this.metricProducers = metricProducers;
this.sharedState =
MeterProviderSharedState.create(clock, resource, exemplarFilter, startEpochNanos);
this.registry =
new ComponentRegistry<>(
instrumentationLibraryInfo ->
new SdkMeter(sharedState, instrumentationLibraryInfo, registeredReaders));
for (RegisteredReader registeredReader : registeredReaders) {
List readerMetricProducers = new ArrayList<>(metricProducers);
readerMetricProducers.add(new LeasedMetricProducer(registry, sharedState, registeredReader));
registeredReader
.getReader()
.register(new SdkCollectionRegistration(readerMetricProducers, sharedState));
registeredReader.setLastCollectEpochNanos(startEpochNanos);
}
}
@Override
public MeterBuilder meterBuilder(String instrumentationScopeName) {
if (registeredReaders.isEmpty()) {
return MeterProvider.noop().meterBuilder(instrumentationScopeName);
}
if (instrumentationScopeName == null || instrumentationScopeName.isEmpty()) {
LOGGER.fine("Meter requested without instrumentation scope name.");
instrumentationScopeName = DEFAULT_METER_NAME;
}
return new SdkMeterBuilder(registry, instrumentationScopeName);
}
/**
* Reset the provider, clearing all registered instruments.
*
* Note: not currently stable but available for experimental use via {@link
* SdkMeterProviderUtil#resetForTest(SdkMeterProvider)}.
*/
void resetForTest() {
registry.getComponents().forEach(SdkMeter::resetForTest);
}
/**
* Call {@link MetricReader#forceFlush()} on all metric readers associated with this provider. The
* resulting {@link CompletableResultCode} completes when all complete.
*/
public CompletableResultCode forceFlush() {
if (registeredReaders.isEmpty()) {
return CompletableResultCode.ofSuccess();
}
List results = new ArrayList<>();
for (RegisteredReader registeredReader : registeredReaders) {
results.add(registeredReader.getReader().forceFlush());
}
return CompletableResultCode.ofAll(results);
}
/**
* Shutdown the provider. Calls {@link MetricReader#shutdown()} on all metric readers associated
* with this provider. The resulting {@link CompletableResultCode} completes when all complete.
*/
public CompletableResultCode shutdown() {
if (!isClosed.compareAndSet(false, true)) {
LOGGER.info("Multiple close calls");
return CompletableResultCode.ofSuccess();
}
if (registeredReaders.isEmpty()) {
return CompletableResultCode.ofSuccess();
}
List results = new ArrayList<>();
for (RegisteredReader info : registeredReaders) {
results.add(info.getReader().shutdown());
}
return CompletableResultCode.ofAll(results);
}
/** Close the meter provider. Calls {@link #shutdown()} and blocks waiting for it to complete. */
@Override
public void close() {
shutdown().join(10, TimeUnit.SECONDS);
}
@Override
public String toString() {
return "SdkMeterProvider{"
+ "clock="
+ sharedState.getClock()
+ ", resource="
+ sharedState.getResource()
+ ", metricReaders="
+ registeredReaders.stream().map(RegisteredReader::getReader).collect(toList())
+ ", metricProducers="
+ metricProducers
+ ", views="
+ registeredViews
+ "}";
}
/** Helper class to expose registered metric exports. */
private static class LeasedMetricProducer implements MetricProducer {
private final ComponentRegistry registry;
private final MeterProviderSharedState sharedState;
private final RegisteredReader registeredReader;
LeasedMetricProducer(
ComponentRegistry registry,
MeterProviderSharedState sharedState,
RegisteredReader registeredReader) {
this.registry = registry;
this.sharedState = sharedState;
this.registeredReader = registeredReader;
}
@Override
public Collection produce(Resource unused) {
Collection meters = registry.getComponents();
List result = new ArrayList<>();
long collectTime = sharedState.getClock().now();
for (SdkMeter meter : meters) {
result.addAll(meter.collectAll(registeredReader, collectTime));
}
registeredReader.setLastCollectEpochNanos(collectTime);
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);
}
}
}