Skip to content

Commit 9845001

Browse files
Add Exemplars support to Prometheus 1.x (#4867)
Issues: 1. prometheus-metrics-tracer-initializer is needed: We would only need SpanContext from prometheus-metrics-tracer-common but ExemplarSampler imports SpanContextSupplier so that is also needed from prometheus-metrics-tracer-initializer. 2. Testing is not easy because of the rate limiter since the Prometheus Java Client does not have a clock abstraction that we could mock/fake.
1 parent d536a7f commit 9845001

File tree

12 files changed

+335
-369
lines changed

12 files changed

+335
-369
lines changed

gradle/libs.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ newrelic-api = "5.14.0"
6969
# Kotlin 1.7 sample will fail from OkHttp 4.12.0 due to okio dependency being a Kotlin 1.9 module
7070
okhttp = "4.11.0"
7171
postgre = "42.7.2"
72-
prometheus = "1.1.0"
72+
prometheus = "1.2.0"
7373
prometheusSimpleClient = "0.16.0"
7474
reactor = "2022.0.16"
7575
rest-assured = "5.4.0"

implementations/micrometer-registry-prometheus/build.gradle

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ dependencies {
44
api project(':micrometer-core')
55

66
api('io.prometheus:prometheus-metrics-core') {
7-
// We don't need this nor some of its dependencies:
8-
// io.prometheus:prometheus-metrics-tracer-otel
9-
// io.prometheus:prometheus-metrics-tracer-otel-agent
10-
exclude(group: 'io.prometheus', module: 'prometheus-metrics-tracer-initializer')
7+
// We only need SpanContext from prometheus-metrics-tracer-common so we should
8+
// exclude(group: 'io.prometheus', module: 'prometheus-metrics-tracer-initializer')
9+
// But right now we cannot since ExemplarSampler imports SpanContextSupplier
10+
exclude(group: 'io.prometheus', module: 'prometheus-metrics-tracer-otel')
11+
exclude(group: 'io.prometheus', module: 'prometheus-metrics-tracer-otel-agent')
1112
}
1213
implementation 'io.prometheus:prometheus-metrics-exposition-formats'
1314

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2024 VMware, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.micrometer.prometheusmetrics;
17+
18+
import io.prometheus.metrics.config.ExemplarsProperties;
19+
import io.prometheus.metrics.config.PrometheusProperties;
20+
import io.prometheus.metrics.core.exemplars.ExemplarSampler;
21+
import io.prometheus.metrics.core.exemplars.ExemplarSamplerConfig;
22+
import io.prometheus.metrics.tracer.common.SpanContext;
23+
24+
import java.util.concurrent.ConcurrentHashMap;
25+
import java.util.concurrent.ConcurrentMap;
26+
27+
/**
28+
* Default implementation of {@link ExemplarSamplerFactory}.
29+
*
30+
* @author Jonatan Ivanov
31+
* @since 1.13.0
32+
*/
33+
class DefaultExemplarSamplerFactory implements ExemplarSamplerFactory {
34+
35+
private final ExemplarsProperties exemplarProperties = PrometheusProperties.get().getExemplarProperties();
36+
37+
private final ConcurrentMap<Integer, ExemplarSamplerConfig> exemplarSamplerConfigsByNumberOfExemplars = new ConcurrentHashMap<>();
38+
39+
private final ConcurrentMap<double[], ExemplarSamplerConfig> exemplarSamplerConfigsByHistogramUpperBounds = new ConcurrentHashMap<>();
40+
41+
private final SpanContext spanContext;
42+
43+
public DefaultExemplarSamplerFactory(SpanContext spanContext) {
44+
this.spanContext = spanContext;
45+
}
46+
47+
@Override
48+
public ExemplarSampler createExemplarSampler(int numberOfExemplars) {
49+
ExemplarSamplerConfig config = exemplarSamplerConfigsByNumberOfExemplars.computeIfAbsent(numberOfExemplars,
50+
key -> new ExemplarSamplerConfig(exemplarProperties, numberOfExemplars));
51+
return new ExemplarSampler(config, spanContext);
52+
}
53+
54+
@Override
55+
public ExemplarSampler createExemplarSampler(double[] histogramClassicUpperBounds) {
56+
ExemplarSamplerConfig config = exemplarSamplerConfigsByHistogramUpperBounds.computeIfAbsent(
57+
histogramClassicUpperBounds,
58+
key -> new ExemplarSamplerConfig(exemplarProperties, histogramClassicUpperBounds));
59+
return new ExemplarSampler(config, spanContext);
60+
}
61+
62+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright 2024 VMware, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.micrometer.prometheusmetrics;
17+
18+
import io.prometheus.metrics.core.exemplars.ExemplarSampler;
19+
20+
/**
21+
* A factory that creates {@link ExemplarSampler} instances with the desired properties.
22+
*
23+
* @author Jonatan Ivanov
24+
* @since 1.13.0
25+
*/
26+
interface ExemplarSamplerFactory {
27+
28+
/**
29+
* Creates an {@link ExemplarSampler} that stores the defined amount of exemplars.
30+
* @param numberOfExemplars the amount of exemplars stored by the sampler.
31+
* @return a new {@link ExemplarSampler} instance.
32+
*/
33+
ExemplarSampler createExemplarSampler(int numberOfExemplars);
34+
35+
/**
36+
* Creates an {@link ExemplarSampler} that stores exemplars for the defined histogram
37+
* buckets. This means as many exemplars as many buckets are defined.
38+
* @param histogramUpperBounds histogram buckets to store exemplars for.
39+
* @return a new {@link ExemplarSampler} instance.
40+
*/
41+
ExemplarSampler createExemplarSampler(double[] histogramUpperBounds);
42+
43+
}

implementations/micrometer-registry-prometheus/src/main/java/io/micrometer/prometheusmetrics/PrometheusCounter.java

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,13 @@
1515
*/
1616
package io.micrometer.prometheusmetrics;
1717

18-
import io.micrometer.common.lang.NonNull;
1918
import io.micrometer.common.lang.Nullable;
2019
import io.micrometer.core.instrument.AbstractMeter;
2120
import io.micrometer.core.instrument.Counter;
2221
import io.micrometer.core.instrument.Meter;
2322
import io.prometheus.metrics.core.exemplars.ExemplarSampler;
2423
import io.prometheus.metrics.model.snapshots.Exemplar;
2524

26-
import java.util.concurrent.atomic.AtomicReference;
2725
import java.util.concurrent.atomic.DoubleAdder;
2826

2927
/**
@@ -36,26 +34,24 @@ public class PrometheusCounter extends AbstractMeter implements Counter {
3634

3735
private final DoubleAdder count = new DoubleAdder();
3836

39-
private final AtomicReference<Exemplar> exemplar = new AtomicReference<>();
40-
4137
@Nullable
4238
private final ExemplarSampler exemplarSampler;
4339

4440
PrometheusCounter(Meter.Id id) {
4541
this(id, null);
4642
}
4743

48-
PrometheusCounter(Meter.Id id, @Nullable ExemplarSampler exemplarSampler) {
44+
PrometheusCounter(Meter.Id id, @Nullable ExemplarSamplerFactory exemplarSamplerFactory) {
4945
super(id);
50-
this.exemplarSampler = exemplarSampler;
46+
this.exemplarSampler = exemplarSamplerFactory != null ? exemplarSamplerFactory.createExemplarSampler(1) : null;
5147
}
5248

5349
@Override
5450
public void increment(double amount) {
5551
if (amount > 0) {
5652
count.add(amount);
5753
if (exemplarSampler != null) {
58-
updateExemplar(amount, exemplarSampler);
54+
exemplarSampler.observe(amount);
5955
}
6056
}
6157
}
@@ -67,18 +63,7 @@ public double count() {
6763

6864
@Nullable
6965
Exemplar exemplar() {
70-
return exemplar.get();
71-
}
72-
73-
// Similar to exemplar.updateAndGet(...) but it does nothing if the next value is null
74-
private void updateExemplar(double amount, @NonNull ExemplarSampler exemplarSampler) {
75-
// Exemplar prev;
76-
// Exemplar next;
77-
// do {
78-
// prev = exemplar.get();
79-
// next = exemplarSampler.sample(amount, prev);
80-
// }
81-
// while (next != null && next != prev && !exemplar.compareAndSet(prev, next));
66+
return exemplarSampler != null ? exemplarSampler.collect().getLatest() : null;
8267
}
8368

8469
}

implementations/micrometer-registry-prometheus/src/main/java/io/micrometer/prometheusmetrics/PrometheusDistributionSummary.java

Lines changed: 11 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,14 @@
1515
*/
1616
package io.micrometer.prometheusmetrics;
1717

18-
import io.micrometer.common.lang.NonNull;
1918
import io.micrometer.common.lang.Nullable;
2019
import io.micrometer.core.instrument.AbstractDistributionSummary;
2120
import io.micrometer.core.instrument.Clock;
2221
import io.micrometer.core.instrument.DistributionSummary;
2322
import io.micrometer.core.instrument.distribution.*;
2423
import io.prometheus.metrics.core.exemplars.ExemplarSampler;
25-
import io.prometheus.metrics.model.snapshots.Exemplar;
24+
import io.prometheus.metrics.model.snapshots.Exemplars;
2625

27-
import java.util.concurrent.atomic.AtomicReference;
2826
import java.util.concurrent.atomic.DoubleAdder;
2927
import java.util.concurrent.atomic.LongAdder;
3028

@@ -50,13 +48,8 @@ public class PrometheusDistributionSummary extends AbstractDistributionSummary {
5048
@Nullable
5149
private final ExemplarSampler exemplarSampler;
5250

53-
@Nullable
54-
private final AtomicReference<Exemplar> lastExemplar;
55-
56-
private boolean histogramExemplarsEnabled = false;
57-
5851
PrometheusDistributionSummary(Id id, Clock clock, DistributionStatisticConfig distributionStatisticConfig,
59-
double scale, @Nullable ExemplarSampler exemplarSampler) {
52+
double scale, @Nullable ExemplarSamplerFactory exemplarSamplerFactory) {
6053
super(id, clock,
6154
DistributionStatisticConfig.builder()
6255
.percentilesHistogram(false)
@@ -68,22 +61,13 @@ public class PrometheusDistributionSummary extends AbstractDistributionSummary {
6861
this.max = new TimeWindowMax(clock, distributionStatisticConfig);
6962

7063
if (distributionStatisticConfig.isPublishingHistogram()) {
71-
PrometheusHistogram prometheusHistogram = new PrometheusHistogram(clock, distributionStatisticConfig,
72-
exemplarSampler);
73-
this.histogram = prometheusHistogram;
74-
this.histogramExemplarsEnabled = prometheusHistogram.isExemplarsEnabled();
64+
this.histogram = new PrometheusHistogram(clock, distributionStatisticConfig, exemplarSamplerFactory);
65+
this.exemplarSampler = null;
7566
}
7667
else {
7768
this.histogram = null;
78-
}
79-
80-
if (!this.histogramExemplarsEnabled && exemplarSampler != null) {
81-
this.exemplarSampler = exemplarSampler;
82-
this.lastExemplar = new AtomicReference<>();
83-
}
84-
else {
85-
this.exemplarSampler = null;
86-
this.lastExemplar = null;
69+
this.exemplarSampler = exemplarSamplerFactory != null ? exemplarSamplerFactory.createExemplarSampler(1)
70+
: null;
8771
}
8872
}
8973

@@ -96,40 +80,17 @@ protected void recordNonNegative(double amount) {
9680
if (histogram != null) {
9781
histogram.recordDouble(amount);
9882
}
99-
if (!histogramExemplarsEnabled && exemplarSampler != null) {
100-
updateLastExemplar(amount, exemplarSampler);
83+
else if (exemplarSampler != null) {
84+
exemplarSampler.observe(amount);
10185
}
10286
}
10387

104-
// Similar to exemplar.updateAndGet(...) but it does nothing if the next value is null
105-
private void updateLastExemplar(double amount, @NonNull ExemplarSampler exemplarSampler) {
106-
// Exemplar prev;
107-
// Exemplar next;
108-
// do {
109-
// prev = lastExemplar.get();
110-
// next = exemplarSampler.sample(amount, prev);
111-
// }
112-
// while (next != null && next != prev && !lastExemplar.compareAndSet(prev,
113-
// next));
114-
}
115-
116-
@Nullable
117-
Exemplar[] histogramExemplars() {
118-
if (histogramExemplarsEnabled) {
88+
Exemplars exemplars() {
89+
if (histogram != null) {
11990
return ((PrometheusHistogram) histogram).exemplars();
12091
}
12192
else {
122-
return null;
123-
}
124-
}
125-
126-
@Nullable
127-
Exemplar lastExemplar() {
128-
if (histogramExemplarsEnabled) {
129-
return ((PrometheusHistogram) histogram).lastExemplar();
130-
}
131-
else {
132-
return lastExemplar != null ? lastExemplar.get() : null;
93+
return exemplarSampler != null ? exemplarSampler.collect() : Exemplars.EMPTY;
13394
}
13495
}
13596

0 commit comments

Comments
 (0)