Skip to content

Commit 95551db

Browse files
authored
Finish wiring exemplars into metrics SDK (#3599)
* Initial implementation of simple ExemplarReservoirs - Add an implementation of naive fixed-bucket reservoir sampling - Add an implementation of FixedSizeHistogram reservoir sampling where latest-measurement per-histogram-bucket is kept. - Add assertions for exemplars. * Remove explicit locks for mild performance boost. * Create a new random holder abstraction. - Create new random holder abstraction that allows - ThreadLocalRandom when efficient - simple detection of Android + workaround for TLR - Overridable with mocked random for testing. - Update tracing RandomIdGenerator to leverage new RnadomHolder - Expand exemplar reservoir tests to use new random holder - Remove android-only random id generator test * Add api diff + fix silly naming thing. * Move random hodler to be Supplier, other updates from code review. * Fixes from review. * Fix bytebuddy issue with mocking. * Expose aggregation configuration and wire in exemplar sampler - Expose classes for aggregation configuration that are visible in exemplar sampler - Create default exemplar sampler that samples w/ traces - Expand metric benchmarks to include exemplar + no-exemplar to check overhead. * Fix merge conflict. * Wire exemplar filter config into autoconfigure module. * Expose aggregation configuration and wire in exemplar sampler - Expose classes for aggregation configuration that are visible in exemplar sampler - Create default exemplar sampler that samples w/ traces - Expand metric benchmarks to include exemplar + no-exemplar to check overhead. * Fix merge conflict. * Wire exemplar filter config into autoconfigure module. * Migrate exemplar reservoir instantiation into aggregations. Start migrating off AggregatorFactory -> Aggregation. * Small cleanup. * Fixes from review (and report of clsasloader fixes from other laptop. * Migrate to null-object pattern. * Add internal boilerplate. * Run spotless. * Fix toString of aggregation values. * Fix ==
1 parent 9dd3104 commit 95551db

29 files changed

+772
-363
lines changed

sdk-extensions/autoconfigure/README.md

+6
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,12 @@ These properties can be used to control the maximum size of recordings per span.
191191
| otel.span.event.count.limit | OTEL_SPAN_EVENT_COUNT_LIMIT | The maximum number of events per span. Default is `128`. |
192192
| otel.span.link.count.limit | OTEL_SPAN_LINK_COUNT_LIMIT | The maximum number of links per span. Default is `128` |
193193

194+
## Exemplars
195+
196+
| System property | Environment variable | Description |
197+
|--------------------------|--------------------------|-----------------------------------------------------------------------------------|
198+
| otel.metrics.exemplar.filter | OTEL_METRICS_EXEMPLAR_FILTER | The filter for exemplar sampling. Can be `NONE`, `ALL` or `WITH_SAMPLED_TRACE`. Default is `WITH_SAMPLED_TRACE`.|
199+
194200
## Interval metric reader
195201

196202
| System property | Environment variable | Description |

sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/OpenTelemetrySdkAutoConfiguration.java

+19
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import io.opentelemetry.sdk.autoconfigure.spi.metrics.SdkMeterProviderConfigurer;
1313
import io.opentelemetry.sdk.metrics.SdkMeterProvider;
1414
import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder;
15+
import io.opentelemetry.sdk.metrics.exemplar.ExemplarFilter;
1516
import io.opentelemetry.sdk.resources.Resource;
1617
import io.opentelemetry.sdk.trace.SdkTracerProvider;
1718
import java.util.ServiceLoader;
@@ -90,6 +91,24 @@ private static void configureMeterProvider(Resource resource, ConfigProperties c
9091

9192
SdkMeterProviderBuilder meterProviderBuilder = SdkMeterProvider.builder().setResource(resource);
9293

94+
// Configure default exemplar filters.
95+
String exemplarFilter = config.getString("otel.metrics.exemplar.filter");
96+
if (exemplarFilter == null) {
97+
exemplarFilter = "WITH_SAMPLED_TRACE";
98+
}
99+
switch (exemplarFilter) {
100+
case "NONE":
101+
meterProviderBuilder.setExemplarFilter(ExemplarFilter.neverSample());
102+
break;
103+
case "ALL":
104+
meterProviderBuilder.setExemplarFilter(ExemplarFilter.alwaysSample());
105+
break;
106+
case "WITH_SAMPLED_TRACE":
107+
default:
108+
meterProviderBuilder.setExemplarFilter(ExemplarFilter.sampleWithTraces());
109+
break;
110+
}
111+
93112
for (SdkMeterProviderConfigurer configurer :
94113
ServiceLoader.load(SdkMeterProviderConfigurer.class)) {
95114
configurer.configure(meterProviderBuilder, config);

sdk/metrics/build.gradle.kts

+2
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,6 @@ dependencies {
2323
testImplementation(project(":sdk:metrics-testing"))
2424
testImplementation(project(":sdk:testing"))
2525
testImplementation("com.google.guava:guava")
26+
27+
jmh(project(":sdk:trace"))
2628
}

sdk/metrics/src/jmh/java/io/opentelemetry/sdk/metrics/MetricsBenchmarks.java

+16
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
import io.opentelemetry.api.common.Attributes;
99
import io.opentelemetry.api.metrics.Meter;
10+
import io.opentelemetry.api.trace.Span;
11+
import io.opentelemetry.api.trace.Tracer;
1012
import java.util.concurrent.TimeUnit;
1113
import org.openjdk.jmh.annotations.Benchmark;
1214
import org.openjdk.jmh.annotations.BenchmarkMode;
@@ -18,6 +20,7 @@
1820
import org.openjdk.jmh.annotations.Scope;
1921
import org.openjdk.jmh.annotations.Setup;
2022
import org.openjdk.jmh.annotations.State;
23+
import org.openjdk.jmh.annotations.TearDown;
2124
import org.openjdk.jmh.annotations.Threads;
2225
import org.openjdk.jmh.annotations.Warmup;
2326
import org.openjdk.jmh.infra.ThreadParams;
@@ -37,16 +40,29 @@ public static class ThreadState {
3740
@Param MetricsTestOperationBuilder opBuilder;
3841

3942
MetricsTestOperationBuilder.Operation op;
43+
Span span;
44+
io.opentelemetry.context.Scope contextScope;
4045
final Attributes sharedLabelSet = Attributes.builder().put("KEY", "VALUE").build();
4146
Attributes threadUniqueLabelSet;
4247

4348
@Setup
49+
@SuppressWarnings("MustBeClosedChecker")
4450
public void setup(ThreadParams threadParams) {
4551
Meter meter = sdk.getMeter();
52+
Tracer tracer = sdk.getTracer();
53+
span = tracer.spanBuilder("benchmark").startSpan();
54+
// We suppress warnings on closing here, as we rely on tests to make sure context is closed.
55+
contextScope = span.makeCurrent();
4656
op = opBuilder.build(meter);
4757
threadUniqueLabelSet =
4858
Attributes.builder().put("KEY", String.valueOf(threadParams.getThreadIndex())).build();
4959
}
60+
61+
@TearDown
62+
public void tearDown(ThreadParams threadParms) {
63+
contextScope.close();
64+
span.end();
65+
}
5066
}
5167

5268
@Benchmark

sdk/metrics/src/jmh/java/io/opentelemetry/sdk/metrics/MetricsTestOperationBuilder.java

+6-4
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import io.opentelemetry.api.metrics.LongCounter;
1616
import io.opentelemetry.api.metrics.LongHistogram;
1717
import io.opentelemetry.api.metrics.Meter;
18+
import java.util.concurrent.ThreadLocalRandom;
1819

1920
/**
2021
* This enum allows for iteration over all of the operations that we want to benchmark. To ensure
@@ -80,12 +81,13 @@ public void performBound() {
8081

8182
@Override
8283
public void perform(Attributes labels) {
83-
metric.record(5.0d, labels);
84+
// We record different values to try to hit more areas of the histogram buckets.
85+
metric.record(ThreadLocalRandom.current().nextDouble(0, 20_000d), labels);
8486
}
8587

8688
@Override
8789
public void performBound() {
88-
boundMetric.record(5.0d);
90+
boundMetric.record(ThreadLocalRandom.current().nextDouble(0, 20_000d));
8991
}
9092
};
9193
}),
@@ -103,12 +105,12 @@ public void performBound() {
103105

104106
@Override
105107
public void perform(Attributes labels) {
106-
metric.record(5L, labels);
108+
metric.record(ThreadLocalRandom.current().nextLong(0, 20_000L), labels);
107109
}
108110

109111
@Override
110112
public void performBound() {
111-
boundMetric.record(5L);
113+
boundMetric.record(ThreadLocalRandom.current().nextLong(0, 20_000L));
112114
}
113115
};
114116
});

sdk/metrics/src/jmh/java/io/opentelemetry/sdk/metrics/TestSdk.java

+27
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@
77

88
import io.opentelemetry.api.metrics.Meter;
99
import io.opentelemetry.api.metrics.MeterProvider;
10+
import io.opentelemetry.api.trace.Tracer;
1011
import io.opentelemetry.sdk.common.Clock;
12+
import io.opentelemetry.sdk.metrics.exemplar.ExemplarFilter;
1113
import io.opentelemetry.sdk.resources.Resource;
14+
import io.opentelemetry.sdk.trace.SdkTracerProvider;
15+
import io.opentelemetry.sdk.trace.samplers.Sampler;
1216

1317
@SuppressWarnings("ImmutableEnumChecker")
1418
public enum TestSdk {
@@ -19,6 +23,18 @@ Meter build() {
1923
return MeterProvider.noop().get("io.opentelemetry.sdk.metrics");
2024
}
2125
}),
26+
SDK_NO_EXEMPLARS(
27+
new SdkBuilder() {
28+
@Override
29+
Meter build() {
30+
return SdkMeterProvider.builder()
31+
.setClock(Clock.getDefault())
32+
.setResource(Resource.empty())
33+
.setExemplarFilter(ExemplarFilter.neverSample())
34+
.build()
35+
.get("io.opentelemetry.sdk.metrics");
36+
}
37+
}),
2238
SDK(
2339
new SdkBuilder() {
2440
@Override
@@ -41,7 +57,18 @@ public Meter getMeter() {
4157
return sdkBuilder.build();
4258
}
4359

60+
public Tracer getTracer() {
61+
return sdkBuilder.buildTracer();
62+
}
63+
4464
private abstract static class SdkBuilder {
4565
abstract Meter build();
66+
67+
protected Tracer buildTracer() {
68+
return SdkTracerProvider.builder()
69+
.setSampler(Sampler.alwaysOn())
70+
.build()
71+
.get("io.opentelemetry.sdk.metrics");
72+
}
4673
}
4774
}

sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkMeterProvider.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import io.opentelemetry.sdk.common.Clock;
1212
import io.opentelemetry.sdk.internal.ComponentRegistry;
1313
import io.opentelemetry.sdk.metrics.data.MetricData;
14-
import io.opentelemetry.sdk.metrics.exemplar.ExemplarSampler;
14+
import io.opentelemetry.sdk.metrics.exemplar.ExemplarFilter;
1515
import io.opentelemetry.sdk.metrics.export.MetricProducer;
1616
import io.opentelemetry.sdk.metrics.internal.state.MeterProviderSharedState;
1717
import io.opentelemetry.sdk.metrics.internal.view.ViewRegistry;
@@ -42,9 +42,9 @@ public final class SdkMeterProvider implements MeterProvider, MetricProducer {
4242
private final MeterProviderSharedState sharedState;
4343

4444
SdkMeterProvider(
45-
Clock clock, Resource resource, ViewRegistry viewRegistry, ExemplarSampler exemplarSampler) {
45+
Clock clock, Resource resource, ViewRegistry viewRegistry, ExemplarFilter exemplarFilter) {
4646
this.sharedState =
47-
MeterProviderSharedState.create(clock, resource, viewRegistry, exemplarSampler);
47+
MeterProviderSharedState.create(clock, resource, viewRegistry, exemplarFilter);
4848
this.registry =
4949
new ComponentRegistry<>(
5050
instrumentationLibraryInfo -> new SdkMeter(sharedState, instrumentationLibraryInfo));

sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkMeterProviderBuilder.java

+6-6
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import io.opentelemetry.api.metrics.GlobalMeterProvider;
99
import io.opentelemetry.sdk.common.Clock;
10-
import io.opentelemetry.sdk.metrics.exemplar.ExemplarSampler;
10+
import io.opentelemetry.sdk.metrics.exemplar.ExemplarFilter;
1111
import io.opentelemetry.sdk.metrics.internal.view.ViewRegistry;
1212
import io.opentelemetry.sdk.metrics.internal.view.ViewRegistryBuilder;
1313
import io.opentelemetry.sdk.metrics.view.InstrumentSelector;
@@ -25,7 +25,7 @@ public final class SdkMeterProviderBuilder {
2525
private Resource resource = Resource.getDefault();
2626
private final ViewRegistryBuilder viewRegistryBuilder = ViewRegistry.builder();
2727
// Default the sampling strategy.
28-
private ExemplarSampler exemplarSampler = ExemplarSampler.builder().build();
28+
private ExemplarFilter exemplarFilter = ExemplarFilter.sampleWithTraces();
2929

3030
SdkMeterProviderBuilder() {}
3131

@@ -54,12 +54,12 @@ public SdkMeterProviderBuilder setResource(Resource resource) {
5454
}
5555

5656
/**
57-
* Assign an {@link ExemplarSampler} for all metrics created by Meters.
57+
* Assign an {@link ExemplarFilter} for all metrics created by Meters.
5858
*
5959
* @return this
6060
*/
61-
public SdkMeterProviderBuilder setExemplarSampler(ExemplarSampler sampler) {
62-
this.exemplarSampler = sampler;
61+
public SdkMeterProviderBuilder setExemplarFilter(ExemplarFilter filter) {
62+
this.exemplarFilter = filter;
6363
return this;
6464
}
6565

@@ -118,6 +118,6 @@ public SdkMeterProvider buildAndRegisterGlobal() {
118118
* @see GlobalMeterProvider
119119
*/
120120
public SdkMeterProvider build() {
121-
return new SdkMeterProvider(clock, resource, viewRegistryBuilder.build(), exemplarSampler);
121+
return new SdkMeterProvider(clock, resource, viewRegistryBuilder.build(), exemplarFilter);
122122
}
123123
}

sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/exemplar/ExemplarReservoir.java

+4
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ public static ExemplarReservoir noSamples() {
2727

2828
/** Wraps a {@link ExemplarReservoir} with a measurement pre-filter. */
2929
public static ExemplarReservoir filtered(ExemplarFilter filter, ExemplarReservoir original) {
30+
// Optimisation on memory usage.
31+
if (filter == ExemplarFilter.neverSample()) {
32+
return ExemplarReservoir.noSamples();
33+
}
3034
return new FilteredExemplarReservoir(filter, original);
3135
}
3236

sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/exemplar/ExemplarReservoirFactory.java

-19
This file was deleted.

sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/exemplar/ExemplarSampler.java

-58
This file was deleted.

sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/exemplar/FixedSizeExemplarReservoir.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,7 @@ public FixedSizeExemplarReservoir(Clock clock, int size, Supplier<Random> random
3939

4040
@Override
4141
protected int reservoirIndexFor(double value, Attributes attributes, Context context) {
42-
// Purposefuly truncate here.
43-
int count = (int) numMeasurements.sum() + 1;
42+
int count = numMeasurements.intValue() + 1;
4443
int index = this.randomSupplier.get().nextInt(count > 0 ? count : 1);
4544
numMeasurements.increment();
4645
if (index < maxSize()) {

sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/aggregator/Aggregator.java

+6
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@
2525
*/
2626
@Immutable
2727
public interface Aggregator<T> {
28+
/**
29+
* Returns the empty aggregator, an aggregator that never records measurements or reports values.
30+
*/
31+
static Aggregator<Void> empty() {
32+
return EmptyAggregator.INSTANCE;
33+
}
2834
/**
2935
* Returns a new {@link AggregatorHandle}. This MUST by used by the synchronous to aggregate
3036
* recorded measurements during the collection cycle.

0 commit comments

Comments
 (0)