io.opentelemetry.sdk.metrics.internal.state.AsynchronousMetricStorage Maven / Gradle / Ivy
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.metrics.internal.state;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.metrics.ObservableDoubleMeasurement;
import io.opentelemetry.api.metrics.ObservableLongMeasurement;
import io.opentelemetry.context.Context;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import io.opentelemetry.sdk.internal.ThrottlingLogger;
import io.opentelemetry.sdk.metrics.View;
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.data.PointData;
import io.opentelemetry.sdk.metrics.internal.aggregator.Aggregator;
import io.opentelemetry.sdk.metrics.internal.aggregator.AggregatorFactory;
import io.opentelemetry.sdk.metrics.internal.descriptor.InstrumentDescriptor;
import io.opentelemetry.sdk.metrics.internal.descriptor.MetricDescriptor;
import io.opentelemetry.sdk.metrics.internal.exemplar.ExemplarFilter;
import io.opentelemetry.sdk.metrics.internal.export.RegisteredReader;
import io.opentelemetry.sdk.metrics.internal.view.AttributesProcessor;
import io.opentelemetry.sdk.metrics.internal.view.RegisteredView;
import io.opentelemetry.sdk.resources.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Stores aggregated {@link MetricData} for asynchronous instruments.
*
* This class is internal and is hence not for public use. Its APIs are unstable and can change
* at any time.
*/
final class AsynchronousMetricStorage
implements MetricStorage {
private static final Logger logger = Logger.getLogger(AsynchronousMetricStorage.class.getName());
private final ThrottlingLogger throttlingLogger = new ThrottlingLogger(logger);
private final RegisteredReader registeredReader;
private final MetricDescriptor metricDescriptor;
private final AggregationTemporality aggregationTemporality;
private final Aggregator aggregator;
private final AttributesProcessor attributesProcessor;
private Map points = new HashMap<>();
private Map lastPoints =
new HashMap<>(); // Only populated if aggregationTemporality == DELTA
private AsynchronousMetricStorage(
RegisteredReader registeredReader,
MetricDescriptor metricDescriptor,
Aggregator aggregator,
AttributesProcessor attributesProcessor) {
this.registeredReader = registeredReader;
this.metricDescriptor = metricDescriptor;
this.aggregationTemporality =
registeredReader
.getReader()
.getAggregationTemporality(metricDescriptor.getSourceInstrument().getType());
this.aggregator = aggregator;
this.attributesProcessor = attributesProcessor;
}
/**
* Create an asynchronous storage instance for the {@link View} and {@link InstrumentDescriptor}.
*/
// TODO(anuraaga): The cast to generic type here looks suspicious.
static AsynchronousMetricStorage create(
RegisteredReader registeredReader,
RegisteredView registeredView,
InstrumentDescriptor instrumentDescriptor) {
View view = registeredView.getView();
MetricDescriptor metricDescriptor =
MetricDescriptor.create(view, registeredView.getViewSourceInfo(), instrumentDescriptor);
Aggregator aggregator =
((AggregatorFactory) view.getAggregation())
.createAggregator(instrumentDescriptor, ExemplarFilter.alwaysOff());
return new AsynchronousMetricStorage<>(
registeredReader,
metricDescriptor,
aggregator,
registeredView.getViewAttributesProcessor());
}
/**
* Record callback measurement from {@link ObservableLongMeasurement} or {@link
* ObservableDoubleMeasurement}.
*/
void record(Measurement measurement) {
Context context = Context.current();
Attributes processedAttributes = attributesProcessor.process(measurement.attributes(), context);
long start =
aggregationTemporality == AggregationTemporality.DELTA
? registeredReader.getLastCollectEpochNanos()
: measurement.startEpochNanos();
measurement =
measurement.hasDoubleValue()
? Measurement.doubleMeasurement(
start, measurement.epochNanos(), measurement.doubleValue(), processedAttributes)
: Measurement.longMeasurement(
start, measurement.epochNanos(), measurement.longValue(), processedAttributes);
recordPoint(aggregator.toPoint(measurement));
}
private void recordPoint(T point) {
Attributes attributes = point.getAttributes();
if (points.size() >= MetricStorage.MAX_CARDINALITY) {
throttlingLogger.log(
Level.WARNING,
"Instrument "
+ metricDescriptor.getSourceInstrument().getName()
+ " has exceeded the maximum allowed cardinality ("
+ MetricStorage.MAX_CARDINALITY
+ ").");
return;
}
// Check there is not already a recording for the attributes
if (points.containsKey(attributes)) {
throttlingLogger.log(
Level.WARNING,
"Instrument "
+ metricDescriptor.getSourceInstrument().getName()
+ " has recorded multiple values for the same attributes.");
return;
}
points.put(attributes, point);
}
@Override
public MetricDescriptor getMetricDescriptor() {
return metricDescriptor;
}
/** Returns the registered reader this storage is associated with. */
public RegisteredReader getRegisteredReader() {
return registeredReader;
}
@Override
public MetricData collect(
Resource resource,
InstrumentationScopeInfo instrumentationScopeInfo,
long startEpochNanos,
long epochNanos) {
Map result;
if (aggregationTemporality == AggregationTemporality.DELTA) {
Map points = this.points;
Map lastPoints = this.lastPoints;
lastPoints.entrySet().removeIf(entry -> !points.containsKey(entry.getKey()));
points.forEach(
(k, v) -> lastPoints.compute(k, (k2, v2) -> v2 == null ? v : aggregator.diff(v2, v)));
result = lastPoints;
this.lastPoints = points;
} else {
result = points;
}
this.points = new HashMap<>();
return aggregator.toMetricData(
resource,
instrumentationScopeInfo,
metricDescriptor,
result.values(),
aggregationTemporality);
}
@Override
public boolean isEmpty() {
return aggregator == Aggregator.drop();
}
}