org.apache.rocketmq.shaded.io.opentelemetry.sdk.metrics.internal.state.TemporalMetricStorage Maven / Gradle / Ivy
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package org.apache.rocketmq.shaded.io.opentelemetry.sdk.metrics.internal.state;
import org.apache.rocketmq.shaded.io.opentelemetry.api.common.Attributes;
import org.apache.rocketmq.shaded.io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import org.apache.rocketmq.shaded.io.opentelemetry.sdk.metrics.data.AggregationTemporality;
import org.apache.rocketmq.shaded.io.opentelemetry.sdk.metrics.data.ExemplarData;
import org.apache.rocketmq.shaded.io.opentelemetry.sdk.metrics.data.MetricData;
import org.apache.rocketmq.shaded.io.opentelemetry.sdk.metrics.internal.aggregator.Aggregator;
import org.apache.rocketmq.shaded.io.opentelemetry.sdk.metrics.internal.aggregator.EmptyMetricData;
import org.apache.rocketmq.shaded.io.opentelemetry.sdk.metrics.internal.descriptor.MetricDescriptor;
import org.apache.rocketmq.shaded.io.opentelemetry.sdk.metrics.internal.export.CollectionHandle;
import org.apache.rocketmq.shaded.io.opentelemetry.sdk.resources.Resource;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.concurrent.ThreadSafe;
/** Stores last reported time and (optional) accumulation for metrics. */
@ThreadSafe
class TemporalMetricStorage {
private final Aggregator aggregator;
private final boolean isSynchronous;
private final Map> reportHistory = new HashMap<>();
TemporalMetricStorage(Aggregator aggregator, boolean isSynchronous) {
this.aggregator = aggregator;
this.isSynchronous = isSynchronous;
}
/**
* Builds the {@link MetricData} streams to report against a specific metric reader.
*
* @param collector The handle of the metric reader.
* @param resource The resource to attach these metrics against.
* @param instrumentationScopeInfo The instrumentation scope that generated these metrics.
* @param temporality The aggregation temporality requested by the reader.
* @param currentAccumulation THe current accumulation of metric data from instruments. This might
* be delta (for synchronous) or cumulative (for asynchronous).
* @param startEpochNanos The timestamp when the metrics SDK started.
* @param epochNanos The current collection timestamp.
* @return The {@link MetricData} points.
*/
synchronized MetricData buildMetricFor(
CollectionHandle collector,
Resource resource,
InstrumentationScopeInfo instrumentationScopeInfo,
MetricDescriptor descriptor,
// Temporality is requested by the collector.
AggregationTemporality temporality,
Map currentAccumulation,
long startEpochNanos,
long epochNanos) {
// In case it's our first collection, default to start timestamp.
long lastCollectionEpoch = startEpochNanos;
Map result = currentAccumulation;
// Check our last report time.
if (reportHistory.containsKey(collector)) {
LastReportedAccumulation last = reportHistory.get(collector);
lastCollectionEpoch = last.getEpochNanos();
// Use aggregation temporality + instrument to determine if we do a merge or a diff of
// previous. We have the following four scenarios:
// 1. Delta Aggregation (temporality) + Cumulative recording (async instrument).
// Here we diff with last cumulative to get a delta.
// 2. Cumulative Aggregation + Delta recording (sync instrument).
// Here we merge with our last record to get a cumulative aggregation.
// 3. Cumulative Aggregation + Cumulative recording - do nothing
// 4. Delta Aggregation + Delta recording - do nothing.
if (temporality == AggregationTemporality.DELTA && !isSynchronous) {
MetricStorageUtils.diffInPlace(last.getAccumulation(), currentAccumulation, aggregator);
result = last.getAccumulation();
} else if (temporality == AggregationTemporality.CUMULATIVE && isSynchronous) {
// We need to make sure the current delta recording gets merged into the previous cumulative
// for the next cumulative measurement.
MetricStorageUtils.mergeAndPreserveInPlace(
last.getAccumulation(), currentAccumulation, aggregator);
// Note: We allow going over our hard limit on attribute streams when first merging, but
// preserve after this point.
if (last.getAccumulation().size() > MetricStorageUtils.MAX_ACCUMULATIONS) {
MetricStorageUtils.removeUnseen(last.getAccumulation(), currentAccumulation);
}
result = last.getAccumulation();
}
}
// Update last reported (cumulative) accumulation.
// For synchronous instruments, we need the merge result.
// For asynchronous instruments, we need the recorded value.
// This assumes aggregation remains consistent for the lifetime of a collector, and
// could be optimised to not record results for cases 3+4 listed above.
if (isSynchronous) {
// Sync instruments remember the full recording.
reportHistory.put(collector, new LastReportedAccumulation<>(result, epochNanos));
} else {
// Async instruments record the raw measurement.
reportHistory.put(collector, new LastReportedAccumulation<>(currentAccumulation, epochNanos));
}
if (result.isEmpty()) {
return EmptyMetricData.getInstance();
}
return aggregator.toMetricData(
resource,
instrumentationScopeInfo,
descriptor,
result,
temporality,
startEpochNanos,
lastCollectionEpoch,
epochNanos);
}
/** Remembers what was presented to a specific exporter. */
private static class LastReportedAccumulation {
private final Map accumulation;
private final long epochNanos;
/**
* Constructs a new reporting record.
*
* @param accumulation The last accumulation of metric data.
* @param epochNanos The timestamp the data was reported.
*/
LastReportedAccumulation(Map accumulation, long epochNanos) {
this.accumulation = accumulation;
this.epochNanos = epochNanos;
}
long getEpochNanos() {
return epochNanos;
}
Map getAccumulation() {
return accumulation;
}
}
}