-
Notifications
You must be signed in to change notification settings - Fork 1k
Fix metric sdk when multiple readers are present #4436
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3602eb4
148cc51
ec9f1b8
ab11d74
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,8 @@ | |
|
|
||
| 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; | ||
|
|
@@ -13,24 +15,18 @@ | |
| import io.opentelemetry.sdk.metrics.data.MetricData; | ||
| import io.opentelemetry.sdk.metrics.export.MetricReader; | ||
| import io.opentelemetry.sdk.metrics.internal.exemplar.ExemplarFilter; | ||
| import io.opentelemetry.sdk.metrics.internal.export.CollectionHandle; | ||
| import io.opentelemetry.sdk.metrics.internal.export.CollectionInfo; | ||
| 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.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.HashMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.Set; | ||
| import java.util.concurrent.TimeUnit; | ||
| import java.util.concurrent.atomic.AtomicBoolean; | ||
| import java.util.concurrent.atomic.AtomicLong; | ||
| import java.util.function.Supplier; | ||
| import java.util.logging.Logger; | ||
|
|
||
| /** SDK implementation for {@link MeterProvider}. */ | ||
|
|
@@ -39,13 +35,10 @@ 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<MetricReader> metricReaders; | ||
| private final ComponentRegistry<SdkMeter> registry; | ||
| private final List<RegisteredReader> registeredReaders; | ||
| private final MeterProviderSharedState sharedState; | ||
| private final Map<CollectionHandle, CollectionInfo> collectionInfoMap; | ||
| private final ComponentRegistry<SdkMeter> registry; | ||
| private final AtomicBoolean isClosed = new AtomicBoolean(false); | ||
| private final AtomicLong lastCollectionTimestamp; | ||
| private final long minimumCollectionIntervalNanos; | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The The refactor negates the need for it and assists in reducing complexity. |
||
|
|
||
| /** | ||
| * Returns a new {@link SdkMeterProviderBuilder} for {@link SdkMeterProvider}. | ||
|
|
@@ -57,39 +50,27 @@ public static SdkMeterProviderBuilder builder() { | |
| } | ||
|
|
||
| SdkMeterProvider( | ||
| List<MetricReader> metricReaders, | ||
| List<RegisteredReader> registeredReaders, | ||
| Clock clock, | ||
| Resource resource, | ||
| ViewRegistry viewRegistry, | ||
| ExemplarFilter exemplarFilter, | ||
| long minimumCollectionIntervalNanos) { | ||
| this.metricReaders = metricReaders; | ||
| ExemplarFilter exemplarFilter) { | ||
| this.registeredReaders = registeredReaders; | ||
| this.sharedState = | ||
| MeterProviderSharedState.create(clock, resource, viewRegistry, exemplarFilter); | ||
| this.registry = | ||
| new ComponentRegistry<>( | ||
| instrumentationLibraryInfo -> new SdkMeter(sharedState, instrumentationLibraryInfo)); | ||
| this.lastCollectionTimestamp = | ||
| new AtomicLong(clock.nanoTime() - minimumCollectionIntervalNanos); | ||
| this.minimumCollectionIntervalNanos = minimumCollectionIntervalNanos; | ||
|
|
||
| // Here we construct our own unique handle ids for this SDK. | ||
| // These are guaranteed to be unique per-reader for this SDK, and only this SDK. | ||
| // These are *only* mutated in our constructor, and safe to use concurrently after construction. | ||
| Set<CollectionHandle> collectors = CollectionHandle.mutableSet(); | ||
| collectionInfoMap = new HashMap<>(); | ||
| Supplier<CollectionHandle> handleSupplier = CollectionHandle.createSupplier(); | ||
| for (MetricReader metricReader : metricReaders) { | ||
| CollectionHandle handle = handleSupplier.get(); | ||
| collectionInfoMap.put(handle, CollectionInfo.create(handle, collectors, metricReader)); | ||
| metricReader.register(new LeasedMetricProducer(handle)); | ||
| collectors.add(handle); | ||
| instrumentationLibraryInfo -> | ||
| new SdkMeter(sharedState, instrumentationLibraryInfo, registeredReaders)); | ||
| for (RegisteredReader registeredReader : registeredReaders) { | ||
| MetricProducer producer = new LeasedMetricProducer(registry, sharedState, registeredReader); | ||
| registeredReader.getReader().register(producer); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public MeterBuilder meterBuilder(String instrumentationScopeName) { | ||
| if (collectionInfoMap.isEmpty()) { | ||
| if (registeredReaders.isEmpty()) { | ||
| return MeterProvider.noop().meterBuilder(instrumentationScopeName); | ||
| } | ||
| if (instrumentationScopeName == null || instrumentationScopeName.isEmpty()) { | ||
|
|
@@ -109,12 +90,12 @@ void resetForTest() { | |
| * resulting {@link CompletableResultCode} completes when all complete. | ||
| */ | ||
| public CompletableResultCode forceFlush() { | ||
| if (collectionInfoMap.isEmpty()) { | ||
| if (registeredReaders.isEmpty()) { | ||
| return CompletableResultCode.ofSuccess(); | ||
| } | ||
| List<CompletableResultCode> results = new ArrayList<>(); | ||
| for (CollectionInfo collectionInfo : collectionInfoMap.values()) { | ||
| results.add(collectionInfo.getReader().forceFlush()); | ||
| for (RegisteredReader registeredReader : registeredReaders) { | ||
| results.add(registeredReader.getReader().forceFlush()); | ||
| } | ||
| return CompletableResultCode.ofAll(results); | ||
| } | ||
|
|
@@ -128,11 +109,11 @@ public CompletableResultCode shutdown() { | |
| LOGGER.info("Multiple close calls"); | ||
| return CompletableResultCode.ofSuccess(); | ||
| } | ||
| if (collectionInfoMap.isEmpty()) { | ||
| if (registeredReaders.isEmpty()) { | ||
| return CompletableResultCode.ofSuccess(); | ||
| } | ||
| List<CompletableResultCode> results = new ArrayList<>(); | ||
| for (CollectionInfo info : collectionInfoMap.values()) { | ||
| for (RegisteredReader info : registeredReaders) { | ||
| results.add(info.getReader().shutdown()); | ||
| } | ||
| return CompletableResultCode.ofAll(results); | ||
|
|
@@ -152,45 +133,34 @@ public String toString() { | |
| + ", resource=" | ||
| + sharedState.getResource() | ||
| + ", metricReaders=" | ||
| + metricReaders | ||
| + registeredReaders.stream().map(RegisteredReader::getReader).collect(toList()) | ||
| + ", views=" | ||
| + sharedState.getViewRegistry().getViews() | ||
| + "}"; | ||
| } | ||
|
|
||
| /** Helper class to expose registered metric exports. */ | ||
| private class LeasedMetricProducer implements MetricProducer { | ||
| private final CollectionHandle handle; | ||
|
|
||
| LeasedMetricProducer(CollectionHandle handle) { | ||
| this.handle = handle; | ||
| private static class LeasedMetricProducer implements MetricProducer { | ||
|
|
||
| private final ComponentRegistry<SdkMeter> registry; | ||
| private final MeterProviderSharedState sharedState; | ||
| private final RegisteredReader registeredReader; | ||
|
|
||
| LeasedMetricProducer( | ||
| ComponentRegistry<SdkMeter> registry, | ||
| MeterProviderSharedState sharedState, | ||
| RegisteredReader registeredReader) { | ||
| this.registry = registry; | ||
| this.sharedState = sharedState; | ||
| this.registeredReader = registeredReader; | ||
| } | ||
|
|
||
| @Override | ||
| public Collection<MetricData> collectAllMetrics() { | ||
| Collection<SdkMeter> meters = registry.getComponents(); | ||
| // Suppress too-frequent-collection. | ||
| long currentNanoTime = sharedState.getClock().nanoTime(); | ||
| long pastNanoTime = lastCollectionTimestamp.get(); | ||
| // It hasn't been long enough since the last collection. | ||
| boolean disableSynchronousCollection = | ||
| (currentNanoTime - pastNanoTime) < minimumCollectionIntervalNanos; | ||
| // If we're not disabling metrics, write the current collection time. | ||
| // We don't care if this happens in more than one thread, suppression is optimistic, and the | ||
| // interval is small enough some jitter isn't important. | ||
| if (!disableSynchronousCollection) { | ||
| lastCollectionTimestamp.lazySet(currentNanoTime); | ||
| } | ||
| CollectionInfo info = collectionInfoMap.get(handle); | ||
| if (info == null) { | ||
| throw new IllegalStateException( | ||
| "No collection info for handle, this is a bug in the OpenTelemetry SDK."); | ||
| } | ||
|
|
||
| List<MetricData> result = new ArrayList<>(); | ||
| for (SdkMeter meter : meters) { | ||
| result.addAll( | ||
| meter.collectAll(info, sharedState.getClock().now(), disableSynchronousCollection)); | ||
| result.addAll(meter.collectAll(registeredReader, sharedState.getClock().now())); | ||
| } | ||
| return Collections.unmodifiableCollection(result); | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CollectionHandle and CollectionInfo had overlapping responsibilities. I've replaced them with a single class with a name which more clearly conveys its responsibilities:
RegisteredReader.