-
Notifications
You must be signed in to change notification settings - Fork 993
Histogram Aggregation - core SDK - aggregator #2924
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
Merged
jkwatson
merged 16 commits into
open-telemetry:main
from
atlassian-forks:histogram-aggregation-core-aggregator
Mar 1, 2021
Merged
Changes from 6 commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
e675fd1
add histogram aggregator
beanliu 7ffbc2a
implement DoubleHistogramAggregator.toMetricData
beanliu a0aff16
pass temporality instead of a boolean for the creation of histogram a…
beanliu 9456910
a53b6c9
remove ImmutableDoubleArray
beanliu 9062d33
remove ImmutableLongArray
beanliu 590f0df
fixup! remove ImmutableDoubleArray
beanliu bc80f02
simplify the implementation of DoubleHistogramAggregator
beanliu 826523b
accumulate value with configured boundaries
beanliu 8af20d1
use nanoseconds as timeunit
beanliu 9c41d8d
update benchmark mode
beanliu 6e2dfde
List<Double> instead of double[] for histogram factory
beanliu cb8bf6b
update var names
beanliu dc92511
switch to using assertj
beanliu 4d75f5a
simpler boundary check
beanliu cd43a0e
simplify multi-threaded test
beanliu File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
75 changes: 75 additions & 0 deletions
75
...etrics/src/jmh/java/io/opentelemetry/sdk/metrics/aggregator/DoubleHistogramBenchmark.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| /* | ||
| * Copyright The OpenTelemetry Authors | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| package io.opentelemetry.sdk.metrics.aggregator; | ||
|
|
||
| import io.opentelemetry.sdk.common.InstrumentationLibraryInfo; | ||
| import io.opentelemetry.sdk.metrics.common.InstrumentDescriptor; | ||
| import io.opentelemetry.sdk.metrics.common.InstrumentType; | ||
| import io.opentelemetry.sdk.metrics.common.InstrumentValueType; | ||
| import io.opentelemetry.sdk.metrics.data.AggregationTemporality; | ||
| import io.opentelemetry.sdk.resources.Resource; | ||
| import java.util.concurrent.TimeUnit; | ||
| import org.openjdk.jmh.annotations.Benchmark; | ||
| import org.openjdk.jmh.annotations.Fork; | ||
| import org.openjdk.jmh.annotations.Level; | ||
| import org.openjdk.jmh.annotations.Measurement; | ||
| import org.openjdk.jmh.annotations.OutputTimeUnit; | ||
| import org.openjdk.jmh.annotations.Scope; | ||
| import org.openjdk.jmh.annotations.Setup; | ||
| import org.openjdk.jmh.annotations.State; | ||
| import org.openjdk.jmh.annotations.Threads; | ||
| import org.openjdk.jmh.annotations.Warmup; | ||
|
|
||
| @State(Scope.Benchmark) | ||
| public class DoubleHistogramBenchmark { | ||
| private static final Aggregator<HistogramAccumulation> aggregator = | ||
| AggregatorFactory.histogram(new double[] {10, 100, 1_000}, AggregationTemporality.DELTA) | ||
| .create( | ||
| Resource.getDefault(), | ||
| InstrumentationLibraryInfo.empty(), | ||
| InstrumentDescriptor.create( | ||
| "name", | ||
| "description", | ||
| "1", | ||
| InstrumentType.VALUE_RECORDER, | ||
| InstrumentValueType.DOUBLE)); | ||
| private AggregatorHandle<HistogramAccumulation> aggregatorHandle; | ||
|
|
||
| @Setup(Level.Trial) | ||
| public final void setup() { | ||
| aggregatorHandle = aggregator.createHandle(); | ||
| } | ||
|
|
||
| @Benchmark | ||
| @Fork(1) | ||
| @Warmup(iterations = 5, time = 1) | ||
| @Measurement(iterations = 10, time = 1) | ||
| @OutputTimeUnit(TimeUnit.MILLISECONDS) | ||
| @Threads(value = 10) | ||
| public void aggregate_10Threads() { | ||
|
bogdandrutu marked this conversation as resolved.
|
||
| aggregatorHandle.recordDouble(100.0056); | ||
| } | ||
|
|
||
| @Benchmark | ||
| @Fork(1) | ||
| @Warmup(iterations = 5, time = 1) | ||
| @Measurement(iterations = 10, time = 1) | ||
| @OutputTimeUnit(TimeUnit.MILLISECONDS) | ||
| @Threads(value = 5) | ||
| public void aggregate_5Threads() { | ||
| aggregatorHandle.recordDouble(100.0056); | ||
| } | ||
|
|
||
| @Benchmark | ||
| @Fork(1) | ||
| @Warmup(iterations = 5, time = 1) | ||
| @Measurement(iterations = 10, time = 1) | ||
| @OutputTimeUnit(TimeUnit.MILLISECONDS) | ||
| @Threads(value = 1) | ||
| public void aggregate_1Threads() { | ||
| aggregatorHandle.recordDouble(100.0056); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
166 changes: 166 additions & 0 deletions
166
...rics/src/main/java/io/opentelemetry/sdk/metrics/aggregator/DoubleHistogramAggregator.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,166 @@ | ||
| /* | ||
| * Copyright The OpenTelemetry Authors | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| package io.opentelemetry.sdk.metrics.aggregator; | ||
|
|
||
| import io.opentelemetry.api.metrics.common.Labels; | ||
| import io.opentelemetry.sdk.common.InstrumentationLibraryInfo; | ||
| import io.opentelemetry.sdk.metrics.common.InstrumentDescriptor; | ||
| import io.opentelemetry.sdk.metrics.data.AggregationTemporality; | ||
| import io.opentelemetry.sdk.metrics.data.DoubleHistogramData; | ||
| import io.opentelemetry.sdk.metrics.data.MetricData; | ||
| import io.opentelemetry.sdk.resources.Resource; | ||
| import java.util.ArrayList; | ||
| import java.util.Arrays; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.concurrent.locks.ReentrantLock; | ||
| import javax.annotation.concurrent.GuardedBy; | ||
|
|
||
| final class DoubleHistogramAggregator extends AbstractAggregator<HistogramAccumulation> { | ||
| private static final long[] countsOfOne = new long[] {1}; | ||
|
|
||
| private final double[] boundaries; | ||
|
|
||
| DoubleHistogramAggregator( | ||
| Resource resource, | ||
| InstrumentationLibraryInfo instrumentationLibraryInfo, | ||
| InstrumentDescriptor instrumentDescriptor, | ||
| double[] boundaries, | ||
| boolean stateful) { | ||
| super(resource, instrumentationLibraryInfo, instrumentDescriptor, stateful); | ||
| this.boundaries = boundaries; | ||
| } | ||
|
|
||
| @Override | ||
| public AggregatorHandle<HistogramAccumulation> createHandle() { | ||
| return new Handle(this.boundaries); | ||
| } | ||
|
|
||
| /** | ||
| * Return the result of the merge of two histogram accumulations. As long as one Aggregator | ||
|
beanliu marked this conversation as resolved.
|
||
| * instance produces all Accumulations with constant boundaries we don't need to worry about | ||
| * merging accumulations with different boundaries. | ||
| */ | ||
| @Override | ||
| public final HistogramAccumulation merge(HistogramAccumulation x, HistogramAccumulation y) { | ||
| long[] mergedCounts = new long[x.getCounts().length]; | ||
| for (int i = 0; i < x.getCounts().length; ++i) { | ||
| mergedCounts[i] = x.getCounts()[i] + y.getCounts()[i]; | ||
| } | ||
| return HistogramAccumulation.create(x.getSum() + y.getSum(), mergedCounts); | ||
| } | ||
|
|
||
| @Override | ||
| public final MetricData toMetricData( | ||
|
beanliu marked this conversation as resolved.
|
||
| Map<Labels, HistogramAccumulation> accumulationByLabels, | ||
| long startEpochNanos, | ||
| long lastCollectionEpoch, | ||
| long epochNanos) { | ||
| List<Double> boundaries = new ArrayList<>(this.boundaries.length); | ||
| for (double v : this.boundaries) { | ||
| boundaries.add(v); | ||
| } | ||
|
beanliu marked this conversation as resolved.
Outdated
|
||
| return MetricData.createDoubleHistogram( | ||
| getResource(), | ||
| getInstrumentationLibraryInfo(), | ||
| getInstrumentDescriptor().getName(), | ||
| getInstrumentDescriptor().getDescription(), | ||
| getInstrumentDescriptor().getUnit(), | ||
| DoubleHistogramData.create( | ||
| isStateful() ? AggregationTemporality.CUMULATIVE : AggregationTemporality.DELTA, | ||
| MetricDataUtils.toDoubleHistogramPointList( | ||
| accumulationByLabels, | ||
| isStateful() ? startEpochNanos : lastCollectionEpoch, | ||
| epochNanos, | ||
| boundaries))); | ||
| } | ||
|
|
||
| @Override | ||
| public HistogramAccumulation accumulateDouble(double value) { | ||
| return HistogramAccumulation.create(value, countsOfOne); | ||
|
beanliu marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| @Override | ||
| public HistogramAccumulation accumulateLong(long value) { | ||
| return HistogramAccumulation.create(value, countsOfOne); | ||
|
beanliu marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| static final class Handle extends AggregatorHandle<HistogramAccumulation> { | ||
| private final double[] boundaries; | ||
|
|
||
| private final ReentrantLock lock = new ReentrantLock(); | ||
|
|
||
| @GuardedBy("lock") | ||
| private final State current; | ||
|
|
||
| Handle(double[] boundaries) { | ||
| this.boundaries = boundaries; | ||
| this.current = new State(this.boundaries.length + 1); | ||
| } | ||
|
|
||
| // Benchmark shows that linear search performs better than binary search with ordinary | ||
| // buckets. | ||
| private int findBucketIndex(double value) { | ||
| for (int i = 0; i < boundaries.length; ++i) { | ||
| if (Double.compare(value, boundaries[i]) <= 0) { | ||
| return i; | ||
| } | ||
| } | ||
| return boundaries.length; | ||
| } | ||
|
|
||
| @Override | ||
| protected HistogramAccumulation doAccumulateThenReset() { | ||
| lock.lock(); | ||
| try { | ||
| HistogramAccumulation acc = | ||
| HistogramAccumulation.create( | ||
| current.sum, Arrays.copyOf(current.counts, current.counts.length)); | ||
| current.reset(); | ||
| return acc; | ||
| } finally { | ||
| lock.unlock(); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| protected void doRecordDouble(double value) { | ||
| int bucketIndex = findBucketIndex(value); | ||
|
|
||
| lock.lock(); | ||
| try { | ||
| current.record(bucketIndex, value); | ||
| } finally { | ||
| lock.unlock(); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| protected void doRecordLong(long value) { | ||
| doRecordDouble((double) value); | ||
| } | ||
|
|
||
| private static final class State { | ||
|
beanliu marked this conversation as resolved.
Outdated
|
||
| private double sum; | ||
| private final long[] counts; | ||
|
|
||
| public State(int bucketSize) { | ||
| this.counts = new long[bucketSize]; | ||
| reset(); | ||
| } | ||
|
|
||
| private void reset() { | ||
| this.sum = 0; | ||
| Arrays.fill(this.counts, 0); | ||
| } | ||
|
|
||
| private void record(int bucketIndex, double value) { | ||
| this.sum += value; | ||
| this.counts[bucketIndex]++; | ||
| } | ||
| } | ||
| } | ||
| } | ||
41 changes: 41 additions & 0 deletions
41
sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/aggregator/HistogramAccumulation.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| /* | ||
| * Copyright The OpenTelemetry Authors | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| package io.opentelemetry.sdk.metrics.aggregator; | ||
|
|
||
| import com.google.auto.value.AutoValue; | ||
| import javax.annotation.concurrent.Immutable; | ||
|
|
||
| @Immutable | ||
| @AutoValue | ||
| abstract class HistogramAccumulation { | ||
| /** | ||
| * Creates a new {@link HistogramAccumulation} with the given values. Assume `counts` is read-only | ||
| * so we don't need a defensive-copy here. | ||
| * | ||
| * @return a new {@link HistogramAccumulation} with the given values. | ||
| */ | ||
| static HistogramAccumulation create(double sum, long[] counts) { | ||
| return new AutoValue_HistogramAccumulation(sum, counts); | ||
| } | ||
|
|
||
| HistogramAccumulation() {} | ||
|
|
||
| /** | ||
| * The sum of all measurements recorded. | ||
| * | ||
| * @return the sum of recorded measurements. | ||
| */ | ||
| abstract double getSum(); | ||
|
|
||
| /** | ||
| * The counts in each bucket. The returned type is a mutable object, but it should be fine because | ||
| * the class is only used internally. | ||
| * | ||
| * @return the counts in each bucket. <b>do not mutate</b> the returned object. | ||
| */ | ||
| @SuppressWarnings("mutable") | ||
| abstract long[] getCounts(); | ||
| } |
53 changes: 53 additions & 0 deletions
53
...ics/src/main/java/io/opentelemetry/sdk/metrics/aggregator/HistogramAggregatorFactory.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| /* | ||
| * Copyright The OpenTelemetry Authors | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| package io.opentelemetry.sdk.metrics.aggregator; | ||
|
|
||
| import io.opentelemetry.sdk.common.InstrumentationLibraryInfo; | ||
| import io.opentelemetry.sdk.metrics.common.InstrumentDescriptor; | ||
| import io.opentelemetry.sdk.metrics.data.AggregationTemporality; | ||
| import io.opentelemetry.sdk.resources.Resource; | ||
|
|
||
| final class HistogramAggregatorFactory implements AggregatorFactory { | ||
| private final double[] boundaries; | ||
| private final AggregationTemporality temporality; | ||
|
|
||
| HistogramAggregatorFactory(double[] boundaries, AggregationTemporality temporality) { | ||
| this.boundaries = boundaries; | ||
|
beanliu marked this conversation as resolved.
Outdated
|
||
| this.temporality = temporality; | ||
|
|
||
|
beanliu marked this conversation as resolved.
|
||
| for (int i = 1; i < this.boundaries.length; ++i) { | ||
| if (Double.compare(this.boundaries[i - 1], this.boundaries[i]) >= 0) { | ||
|
beanliu marked this conversation as resolved.
Outdated
|
||
| throw new IllegalArgumentException( | ||
| "invalid bucket boundary: " + this.boundaries[i - 1] + " >= " + this.boundaries[i]); | ||
| } | ||
| } | ||
| if (this.boundaries.length > 0) { | ||
| if (this.boundaries[0] == Double.NEGATIVE_INFINITY) { | ||
| throw new IllegalArgumentException("invalid bucket boundary: -Inf"); | ||
| } | ||
| if (this.boundaries[this.boundaries.length - 1] == Double.POSITIVE_INFINITY) { | ||
| throw new IllegalArgumentException("invalid bucket boundary: +Inf"); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| @SuppressWarnings("unchecked") | ||
| public <T> Aggregator<T> create( | ||
| Resource resource, | ||
| InstrumentationLibraryInfo instrumentationLibraryInfo, | ||
| InstrumentDescriptor descriptor) { | ||
| final boolean stateful = this.temporality == AggregationTemporality.CUMULATIVE; | ||
| switch (descriptor.getValueType()) { | ||
| case LONG: | ||
| case DOUBLE: | ||
| return (Aggregator<T>) | ||
| new DoubleHistogramAggregator( | ||
| resource, instrumentationLibraryInfo, descriptor, this.boundaries, stateful); | ||
| } | ||
| throw new IllegalArgumentException("Invalid instrument value type"); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.