
io.opentelemetry.sdk.metrics.internal.state.DefaultSynchronousMetricStorage Maven / Gradle / Ivy
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.metrics.internal.state;
import static io.opentelemetry.sdk.metrics.internal.state.MetricStorageUtils.MAX_ACCUMULATIONS;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.context.Context;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import io.opentelemetry.sdk.internal.ThrottlingLogger;
import io.opentelemetry.sdk.metrics.data.AggregationTemporality;
import io.opentelemetry.sdk.metrics.data.ExemplarData;
import io.opentelemetry.sdk.metrics.data.MetricData;
import io.opentelemetry.sdk.metrics.internal.aggregator.Aggregator;
import io.opentelemetry.sdk.metrics.internal.aggregator.AggregatorHandle;
import io.opentelemetry.sdk.metrics.internal.descriptor.MetricDescriptor;
import io.opentelemetry.sdk.metrics.internal.export.RegisteredReader;
import io.opentelemetry.sdk.metrics.internal.view.AttributesProcessor;
import io.opentelemetry.sdk.resources.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Stores aggregated {@link MetricData} for synchronous instruments.
*
* This class is internal and is hence not for public use. Its APIs are unstable and can change
* at any time.
*/
public final class DefaultSynchronousMetricStorage
implements SynchronousMetricStorage {
private static final ThrottlingLogger logger =
new ThrottlingLogger(Logger.getLogger(DefaultSynchronousMetricStorage.class.getName()));
private static final BoundStorageHandle NOOP_STORAGE_HANDLE = new NoopBoundHandle();
private final RegisteredReader registeredReader;
private final MetricDescriptor metricDescriptor;
private final Aggregator aggregator;
private final ConcurrentHashMap> activeCollectionStorage =
new ConcurrentHashMap<>();
private final TemporalMetricStorage temporalMetricStorage;
private final AttributesProcessor attributesProcessor;
DefaultSynchronousMetricStorage(
RegisteredReader registeredReader,
MetricDescriptor metricDescriptor,
Aggregator aggregator,
AttributesProcessor attributesProcessor) {
this.registeredReader = registeredReader;
this.metricDescriptor = metricDescriptor;
AggregationTemporality aggregationTemporality =
registeredReader
.getReader()
.getAggregationTemporality(metricDescriptor.getSourceInstrument().getType());
this.aggregator = aggregator;
this.temporalMetricStorage =
new TemporalMetricStorage<>(
aggregator,
/* isSynchronous= */ true,
registeredReader,
aggregationTemporality,
metricDescriptor);
this.attributesProcessor = attributesProcessor;
}
// This is a storage handle to use when the attributes processor requires
private final BoundStorageHandle lateBoundStorageHandle =
new BoundStorageHandle() {
@Override
public void release() {}
@Override
public void recordLong(long value, Attributes attributes, Context context) {
DefaultSynchronousMetricStorage.this.recordLong(value, attributes, context);
}
@Override
public void recordDouble(double value, Attributes attributes, Context context) {
DefaultSynchronousMetricStorage.this.recordDouble(value, attributes, context);
}
};
@Override
public BoundStorageHandle bind(Attributes attributes) {
Objects.requireNonNull(attributes, "attributes");
if (attributesProcessor.usesContext()) {
// We cannot pre-bind attributes because we need to pull attributes from context.
return lateBoundStorageHandle;
}
return doBind(attributesProcessor.process(attributes, Context.current()));
}
private BoundStorageHandle doBind(Attributes attributes) {
AggregatorHandle aggregatorHandle = activeCollectionStorage.get(attributes);
if (aggregatorHandle != null && aggregatorHandle.acquire()) {
// At this moment it is guaranteed that the Bound is in the map and will not be removed.
return aggregatorHandle;
}
// Missing entry or no longer mapped. Try to add a new one if not exceeded cardinality limits.
aggregatorHandle = aggregator.createHandle();
while (true) {
if (activeCollectionStorage.size() >= MAX_ACCUMULATIONS) {
logger.log(
Level.WARNING,
"Instrument "
+ metricDescriptor.getSourceInstrument().getName()
+ " has exceeded the maximum allowed accumulations ("
+ MAX_ACCUMULATIONS
+ ").");
return NOOP_STORAGE_HANDLE;
}
AggregatorHandle boundAggregatorHandle =
activeCollectionStorage.putIfAbsent(attributes, aggregatorHandle);
if (boundAggregatorHandle != null) {
if (boundAggregatorHandle.acquire()) {
// At this moment it is guaranteed that the Bound is in the map and will not be removed.
return boundAggregatorHandle;
}
// Try to remove the boundAggregator. This will race with the collect method, but only one
// will succeed.
activeCollectionStorage.remove(attributes, boundAggregatorHandle);
continue;
}
return aggregatorHandle;
}
}
// Overridden to make sure attributes processor can pull baggage.
@Override
public void recordLong(long value, Attributes attributes, Context context) {
Objects.requireNonNull(attributes, "attributes");
attributes = attributesProcessor.process(attributes, context);
BoundStorageHandle handle = doBind(attributes);
try {
handle.recordLong(value, attributes, context);
} finally {
handle.release();
}
}
// Overridden to make sure attributes processor can pull baggage.
@Override
public void recordDouble(double value, Attributes attributes, Context context) {
Objects.requireNonNull(attributes, "attributes");
attributes = attributesProcessor.process(attributes, context);
BoundStorageHandle handle = doBind(attributes);
try {
handle.recordDouble(value, attributes, context);
} finally {
handle.release();
}
}
@Override
public MetricData collectAndReset(
Resource resource,
InstrumentationScopeInfo instrumentationScopeInfo,
long startEpochNanos,
long epochNanos) {
// Grab accumulated measurements.
Map accumulations = new HashMap<>();
for (Map.Entry> entry : activeCollectionStorage.entrySet()) {
boolean unmappedEntry = entry.getValue().tryUnmap();
if (unmappedEntry) {
// If able to unmap then remove the record from the current Map. This can race with the
// acquire but because we requested a specific value only one will succeed.
activeCollectionStorage.remove(entry.getKey(), entry.getValue());
}
T accumulation = entry.getValue().accumulateThenReset(entry.getKey());
if (accumulation == null) {
continue;
}
accumulations.put(entry.getKey(), accumulation);
}
return temporalMetricStorage.buildMetricFor(
resource, instrumentationScopeInfo, accumulations, startEpochNanos, epochNanos);
}
@Override
public MetricDescriptor getMetricDescriptor() {
return metricDescriptor;
}
@Override
public RegisteredReader getRegisteredReader() {
return registeredReader;
}
/** An implementation of {@link BoundStorageHandle} that does not record. */
private static class NoopBoundHandle implements BoundStorageHandle {
@Override
public void recordLong(long value, Attributes attributes, Context context) {}
@Override
public void recordDouble(double value, Attributes attributes, Context context) {}
@Override
public void release() {}
}
}