Skip to content
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

Add histogram collection benchmark #4912

Merged
merged 2 commits into from Nov 30, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -0,0 +1,160 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.sdk.metrics.internal.aggregator;

import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.metrics.DoubleHistogram;
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.metrics.Aggregation;
import io.opentelemetry.sdk.metrics.InstrumentType;
import io.opentelemetry.sdk.metrics.SdkMeterProvider;
import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder;
import io.opentelemetry.sdk.metrics.data.AggregationTemporality;
import io.opentelemetry.sdk.metrics.data.MetricData;
import io.opentelemetry.sdk.metrics.export.MetricExporter;
import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader;
import io.opentelemetry.sdk.metrics.internal.SdkMeterProviderUtil;
import io.opentelemetry.sdk.metrics.internal.exemplar.ExemplarFilter;
import io.opentelemetry.sdk.metrics.internal.view.ExponentialHistogramAggregation;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.TearDown;
import org.openjdk.jmh.annotations.Threads;
import org.openjdk.jmh.annotations.Warmup;

/**
* Measures the collection path for various histogram aggregations. Should be used primarily to
* compare memory allocation rates.
*/
@BenchmarkMode(Mode.SingleShotTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Measurement(iterations = 5, batchSize = 100)
@Warmup(iterations = 2, batchSize = 10)
@Fork(1)
public class HistogramCollectBenchmark {

private static final int cardinality = 100;
private static final int measurementsPerSeries = 10_000;

@State(Scope.Benchmark)
public static class ThreadState {
@Param private AggregationTemporality aggregationTemporality;
@Param private AggregationGenerator aggregationGenerator;

private SdkMeterProvider sdkMeterProvider;
private DoubleHistogram histogram;
private Random random;
private List<Attributes> attributesList;

@Setup
public void setup() {
SdkMeterProviderBuilder builder =
SdkMeterProvider.builder()
.registerMetricReader(
PeriodicMetricReader.builder(
// Configure an exporter that configures the temporality and aggregation
// for the test case, but otherwise drops the data on export
new NoopMetricExporter(
aggregationTemporality, aggregationGenerator.aggregation))
// Effectively disable periodic reading so reading is only done on #flush()
.setInterval(Duration.ofSeconds(Integer.MAX_VALUE))
.build());
// Disable examplars
SdkMeterProviderUtil.setExemplarFilter(builder, ExemplarFilter.neverSample());
sdkMeterProvider = builder.build();
histogram = sdkMeterProvider.get("meter").histogramBuilder("histogram").build();

random = new Random();
attributesList = new ArrayList<>(cardinality);
String last = "aaaaaaaaaaaaaaaaaaaaaaaaaa";
for (int i = 0; i < cardinality; i++) {
char[] chars = last.toCharArray();
chars[random.nextInt(last.length())] = (char) (random.nextInt(26) + 'a');
last = new String(chars);
attributesList.add(Attributes.builder().put("key", last).build());
}
}

@TearDown
public void tearDown() {
sdkMeterProvider.shutdown().join(10, TimeUnit.SECONDS);
}
}

@Benchmark
@Threads(value = 1)
public void recordAndCollect(ThreadState threadState) {
for (Attributes attributes : threadState.attributesList) {
for (int i = 0; i < measurementsPerSeries; i++) {
int value = threadState.random.nextInt(10_000);
threadState.histogram.record(value, attributes);
}
}
threadState.sdkMeterProvider.forceFlush().join(10, TimeUnit.SECONDS);
}

@SuppressWarnings("ImmutableEnumChecker")
public enum AggregationGenerator {
EXPLICIT_BUCKET_HISTOGRAM(Aggregation.explicitBucketHistogram()),
EXPONENTIAL_BUCKET_HISTOGRAM(ExponentialHistogramAggregation.getDefault());

private final Aggregation aggregation;

AggregationGenerator(Aggregation aggregation) {
this.aggregation = aggregation;
}
}

private static class NoopMetricExporter implements MetricExporter {
private final AggregationTemporality aggregationTemporality;
private final Aggregation aggregation;

private NoopMetricExporter(
AggregationTemporality aggregationTemporality, Aggregation aggregation) {
this.aggregationTemporality = aggregationTemporality;
this.aggregation = aggregation;
}

@Override
public CompletableResultCode export(Collection<MetricData> metrics) {
return CompletableResultCode.ofSuccess();
}

@Override
public CompletableResultCode flush() {
return CompletableResultCode.ofSuccess();
}

@Override
public CompletableResultCode shutdown() {
return CompletableResultCode.ofSuccess();
}

@Override
public Aggregation getDefaultAggregation(InstrumentType instrumentType) {
return aggregation;
}

@Override
public AggregationTemporality getAggregationTemporality(InstrumentType instrumentType) {
return aggregationTemporality;
}
}
}
Expand Up @@ -60,11 +60,9 @@ public double getAsDouble() {
}
/** Constructs a pool using explicit bucket histogram boundaries. */
private static double[] explicitDefaultBucketPool() {
List<Double> fixedBoundaries = new ArrayList<Double>();
// Add minimal recording value.
fixedBoundaries.add(0.0);
// Add the bucket LE bucket boundaries (starts at 5).
fixedBoundaries.addAll(ExplicitBucketHistogramUtils.DEFAULT_HISTOGRAM_BUCKET_BOUNDARIES);
// Add the bucket LE bucket boundaries.
List<Double> fixedBoundaries =
new ArrayList<>(ExplicitBucketHistogramUtils.DEFAULT_HISTOGRAM_BUCKET_BOUNDARIES);
// Add Double max value as our other extreme.
fixedBoundaries.add(Double.MAX_VALUE);
return ExplicitBucketHistogramUtils.createBoundaryArray(fixedBoundaries);
Expand Down