Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 9807805

Browse files
committedApr 12, 2025··
Add HistogramFlavorPerMeterLookup and MaxBucketsPerMeterLookup
1 parent 6880ce0 commit 9807805

File tree

4 files changed

+537
-119
lines changed

4 files changed

+537
-119
lines changed
 

‎implementations/micrometer-registry-otlp/src/main/java/io/micrometer/registry/otlp/OtlpConfig.java

+18-48
Original file line numberDiff line numberDiff line change
@@ -231,38 +231,23 @@ default HistogramFlavor histogramFlavor() {
231231
/**
232232
* Configures the histogram flavor mapping to use on a per-meter level. This can
233233
* override the {@link #histogramFlavor()} configuration for matching Meters. By
234-
* default, {@link #histogramFlavorPerMeter(Meter.Id)} uses the result of this method
235-
* to look up the {@link HistogramFlavor} by {@link Meter.Id} (exact match on the
236-
* Meter's name by default). This means that this method provides the data while
237-
* {@link #histogramFlavorPerMeter(Meter.Id)} provides the logic for the lookup, and
238-
* you can override them independently.
234+
* default, {@link OtlpMeterRegistry} (through
235+
* {@link io.micrometer.registry.otlp.OtlpMeterRegistry.HistogramFlavorPerMeterLookup})
236+
* uses the result of this method to look up the {@link HistogramFlavor} by
237+
* {@link Meter.Id} (prefix match on the Meter's name by default). This means that
238+
* this method provides the data while
239+
* {@link io.micrometer.registry.otlp.OtlpMeterRegistry.HistogramFlavorPerMeterLookup}
240+
* provides the logic for the lookup, and you can override them independently.
239241
* @return mapping of meter name to histogram flavor
240242
* @since 1.15.0
241-
* @see #histogramFlavorPerMeter(Meter.Id)
243+
* @see io.micrometer.registry.otlp.OtlpMeterRegistry.HistogramFlavorPerMeterLookup
242244
* @see #histogramFlavor()
243245
*/
244246
default Map<String, HistogramFlavor> histogramFlavorPerMeter() {
245247
return getStringMap(this, "histogramFlavorPerMeter", HistogramFlavor::fromString)
246248
.orElse(Collections.emptyMap());
247249
}
248250

249-
/**
250-
* Looks up the histogram flavor to use on a per-meter level. This will override the
251-
* {@link #histogramFlavor()} lookup behavior for matching Meters. By default, the key
252-
* is used to do an exact match on the Meter's name in the map that
253-
* {@link #histogramFlavorPerMeter()} returns. This means that
254-
* {@link #histogramFlavorPerMeter()} provides the data while this method provides the
255-
* logic for the lookup, and you can override them independently.
256-
* @param id the {@link Meter.Id} the {@link HistogramFlavor} is configured for
257-
* @return the histogram flavor mapped to the {@link Meter.Id}
258-
* @since 1.15.0
259-
* @see #histogramFlavorPerMeter()
260-
* @see #histogramFlavor()
261-
*/
262-
default HistogramFlavor histogramFlavorPerMeter(Meter.Id id) {
263-
return histogramFlavorPerMeter().getOrDefault(id.getName(), histogramFlavor());
264-
}
265-
266251
/**
267252
* Max scale to use for exponential histograms, if configured.
268253
* @return maxScale
@@ -291,39 +276,24 @@ default int maxBucketCount() {
291276
/**
292277
* Configures the max bucket count mapping to use on a per-meter level. This can
293278
* override the {@link #maxBucketCount()} configuration for matching Meters. By
294-
* default {@link #maxBucketsPerMeter(Meter.Id)} uses the result of this method to
295-
* look up the max bucket count by {@link Meter.Id} (exact match on the Meter's name
296-
* by default). This means that this method provides the data while
297-
* {@link #maxBucketsPerMeter(Meter.Id)} provides the logic for the lookup, and you
298-
* can override them independently. This has no effect on a meter if it does not have
299-
* an exponential bucket histogram configured.
279+
* default, {@link OtlpMeterRegistry} (through
280+
* {@link io.micrometer.registry.otlp.OtlpMeterRegistry.MaxBucketsPerMeterLookup})
281+
* uses the result of this method to look up the max bucket count by {@link Meter.Id}
282+
* (prefix match on the Meter's name by default). This means that this method provides
283+
* the data while
284+
* {@link io.micrometer.registry.otlp.OtlpMeterRegistry.MaxBucketsPerMeterLookup}
285+
* provides the logic for the lookup, and you can override them independently. This
286+
* has no effect on a meter if it does not have an exponential bucket histogram
287+
* configured.
300288
* @return mapping of meter name to max bucket count
301289
* @since 1.15.0
302-
* @see #maxBucketsPerMeter(Meter.Id)
290+
* @see io.micrometer.registry.otlp.OtlpMeterRegistry.MaxBucketsPerMeterLookup
303291
* @see #maxBucketCount()
304292
*/
305293
default Map<String, Integer> maxBucketsPerMeter() {
306294
return getStringMap(this, "maxBucketsPerMeter", Integer::parseInt).orElse(Collections.emptyMap());
307295
}
308296

309-
/**
310-
* Looks up the max bucket count to use on a per-meter level. This will override the
311-
* {@link #maxBucketCount()} lookup behavior for matching Meters. By default, the key
312-
* is used to do an exact match on the Meter's name in the map that
313-
* {@link #maxBucketsPerMeter()} returns. This means that
314-
* {@link #maxBucketsPerMeter()} provides the data while this method provides the
315-
* logic for the lookup, and you can override them independently. This has no effect
316-
* on a meter if it does not have an exponential bucket histogram configured.
317-
* @param id the {@link Meter.Id} the max bucket count is configured for
318-
* @return the max bucket count mapped to the {@link Meter.Id}
319-
* @since 1.15.0
320-
* @see #maxBucketsPerMeter()
321-
* @see #maxBucketCount()
322-
*/
323-
default Integer maxBucketsPerMeter(Meter.Id id) {
324-
return maxBucketsPerMeter().getOrDefault(id.getName(), maxBucketCount());
325-
}
326-
327297
@Override
328298
default Validated<?> validate() {
329299
return checkAll(this, c -> PushRegistryConfig.validate(c), checkRequired("url", OtlpConfig::url),

‎implementations/micrometer-registry-otlp/src/main/java/io/micrometer/registry/otlp/OtlpMeterRegistry.java

+100-9
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ public class OtlpMeterRegistry extends PushMeterRegistry {
8282

8383
private final OtlpMetricsSender metricsSender;
8484

85+
private final HistogramFlavorPerMeterLookup histogramFlavorPerMeterLookup;
86+
87+
private final MaxBucketsPerMeterLookup maxBucketsPerMeterLookup;
88+
8589
private final Resource resource;
8690

8791
private final AggregationTemporality aggregationTemporality;
@@ -111,15 +115,19 @@ public OtlpMeterRegistry(OtlpConfig config, Clock clock) {
111115
* @since 1.14.0
112116
*/
113117
public OtlpMeterRegistry(OtlpConfig config, Clock clock, ThreadFactory threadFactory) {
114-
this(config, clock, threadFactory, new OtlpHttpMetricsSender(new HttpUrlConnectionSender()));
118+
this(config, clock, threadFactory, new OtlpHttpMetricsSender(new HttpUrlConnectionSender()),
119+
OtlpMeterRegistry::histogramFlavorPerMeter, OtlpMeterRegistry::maxBucketsPerMeter);
115120
}
116121

117122
private OtlpMeterRegistry(OtlpConfig config, Clock clock, ThreadFactory threadFactory,
118-
OtlpMetricsSender metricsSender) {
123+
OtlpMetricsSender metricsSender, HistogramFlavorPerMeterLookup histogramFlavorPerMeterLookup,
124+
MaxBucketsPerMeterLookup maxBucketsPerMeterLookup) {
119125
super(config, clock);
120126
this.config = config;
121127
this.baseTimeUnit = config.baseTimeUnit();
122128
this.metricsSender = metricsSender;
129+
this.histogramFlavorPerMeterLookup = histogramFlavorPerMeterLookup;
130+
this.maxBucketsPerMeterLookup = maxBucketsPerMeterLookup;
123131
this.resource = Resource.newBuilder().addAllAttributes(getResourceAttributes()).build();
124132
this.aggregationTemporality = config.aggregationTemporality();
125133
config().namingConvention(NamingConvention.dot);
@@ -430,16 +438,12 @@ private Histogram getHistogram(Meter.Id id, DistributionStatisticConfig distribu
430438
}
431439

432440
private int getMaxBuckets(Meter.Id id) {
433-
Integer perMeterMaxBuckets = config.maxBucketsPerMeter(id);
434-
return perMeterMaxBuckets == null ? config.maxBucketCount() : perMeterMaxBuckets;
441+
return maxBucketsPerMeterLookup.getMaxBuckets(config, id);
435442
}
436443

437444
private HistogramFlavor histogramFlavor(Meter.Id id, OtlpConfig otlpConfig,
438445
DistributionStatisticConfig distributionStatisticConfig) {
439-
HistogramFlavor preferredHistogramFlavor = otlpConfig.histogramFlavorPerMeter(id);
440-
preferredHistogramFlavor = preferredHistogramFlavor == null ? otlpConfig.histogramFlavor()
441-
: preferredHistogramFlavor;
442-
446+
HistogramFlavor preferredHistogramFlavor = histogramFlavorPerMeterLookup.getHistogramFlavor(otlpConfig, id);
443447
final double[] serviceLevelObjectiveBoundaries = distributionStatisticConfig
444448
.getServiceLevelObjectiveBoundaries();
445449
if (distributionStatisticConfig.isPublishingHistogram()
@@ -499,6 +503,76 @@ static double[] getSloWithPositiveInf(DistributionStatisticConfig distributionSt
499503
return sloWithPositiveInf;
500504
}
501505

506+
private static HistogramFlavor histogramFlavorPerMeter(OtlpConfig config, Meter.Id id) {
507+
for (Map.Entry<String, HistogramFlavor> entry : config.histogramFlavorPerMeter().entrySet()) {
508+
if (id.getName().startsWith(entry.getKey())) {
509+
return entry.getValue();
510+
}
511+
}
512+
return config.histogramFlavor();
513+
}
514+
515+
private static Integer maxBucketsPerMeter(OtlpConfig config, Meter.Id id) {
516+
for (Map.Entry<String, Integer> entry : config.maxBucketsPerMeter().entrySet()) {
517+
if (id.getName().startsWith(entry.getKey())) {
518+
return entry.getValue();
519+
}
520+
}
521+
return config.maxBucketCount();
522+
}
523+
524+
/**
525+
* Overridable lookup mechanism for {@link HistogramFlavor}.
526+
*
527+
* @since 1.15.0
528+
*/
529+
@FunctionalInterface
530+
public interface HistogramFlavorPerMeterLookup {
531+
532+
/**
533+
* Looks up the histogram flavor to use on a per-meter level. This will override
534+
* the {@link OtlpConfig#histogramFlavor()} for matching Meters. The default
535+
* implementation in {@link OtlpMeterRegistry} does a prefix match on the Meter's
536+
* name in the map that {@link OtlpConfig#histogramFlavorPerMeter()} returns. This
537+
* means that {@link OtlpConfig#histogramFlavorPerMeter()} provides the data while
538+
* this method provides the logic for the lookup, and you can override them
539+
* independently.
540+
* @param id the {@link Meter.Id} the {@link HistogramFlavor} is configured for
541+
* @return the histogram flavor mapped to the {@link Meter.Id}
542+
* @see OtlpConfig#histogramFlavorPerMeter()
543+
* @see OtlpConfig#histogramFlavor()
544+
*/
545+
HistogramFlavor getHistogramFlavor(OtlpConfig config, Meter.Id id);
546+
547+
}
548+
549+
/**
550+
* Overridable lookup mechanism for max bucket count. This has no effect on a meter if
551+
* it does not have an exponential bucket histogram configured.
552+
*
553+
* @since 1.15.0
554+
*/
555+
@FunctionalInterface
556+
public interface MaxBucketsPerMeterLookup {
557+
558+
/**
559+
* Looks up the max bucket count to use on a per-meter level. This will override
560+
* the {@link OtlpConfig#maxBucketCount()} for matching Meters. The default
561+
* implementation in {@link OtlpMeterRegistry} does a prefix match on the Meter's
562+
* name in the map that {@link OtlpConfig#maxBucketsPerMeter()} returns. This
563+
* means that {@link OtlpConfig#maxBucketsPerMeter()} provides the data while this
564+
* method provides the logic for the lookup, and you can override them
565+
* independently. This has no effect on a meter if it does not have an exponential
566+
* bucket histogram configured.
567+
* @param id the {@link Meter.Id} the max bucket count is configured for
568+
* @return the max bucket count mapped to the {@link Meter.Id}
569+
* @see OtlpConfig#maxBucketsPerMeter()
570+
* @see OtlpConfig#maxBucketCount()
571+
*/
572+
Integer getMaxBuckets(OtlpConfig config, Meter.Id id);
573+
574+
}
575+
502576
/**
503577
* Builder for {@link OtlpMeterRegistry}.
504578
*
@@ -514,9 +588,15 @@ public static class Builder {
514588

515589
private OtlpMetricsSender metricsSender;
516590

591+
private HistogramFlavorPerMeterLookup histogramFlavorPerMeterLookup;
592+
593+
private MaxBucketsPerMeterLookup maxBucketsPerMeterLookup;
594+
517595
private Builder(OtlpConfig otlpConfig) {
518596
this.otlpConfig = otlpConfig;
519597
this.metricsSender = new OtlpHttpMetricsSender(new HttpUrlConnectionSender());
598+
this.histogramFlavorPerMeterLookup = OtlpMeterRegistry::histogramFlavorPerMeter;
599+
this.maxBucketsPerMeterLookup = OtlpMeterRegistry::maxBucketsPerMeter;
520600
}
521601

522602
/** Override the default clock. */
@@ -542,8 +622,19 @@ public Builder metricsSender(OtlpMetricsSender metricsSender) {
542622
return this;
543623
}
544624

625+
public Builder histogramFlavorPerMeterLookup(HistogramFlavorPerMeterLookup histogramFlavorPerMeterLookup) {
626+
this.histogramFlavorPerMeterLookup = histogramFlavorPerMeterLookup;
627+
return this;
628+
}
629+
630+
public Builder maxBucketsPerMeterLookup(MaxBucketsPerMeterLookup maxBucketsPerMeterLookup) {
631+
this.maxBucketsPerMeterLookup = maxBucketsPerMeterLookup;
632+
return this;
633+
}
634+
545635
public OtlpMeterRegistry build() {
546-
return new OtlpMeterRegistry(otlpConfig, clock, threadFactory, metricsSender);
636+
return new OtlpMeterRegistry(otlpConfig, clock, threadFactory, metricsSender, histogramFlavorPerMeterLookup,
637+
maxBucketsPerMeterLookup);
547638
}
548639

549640
}

‎implementations/micrometer-registry-otlp/src/test/java/io/micrometer/registry/otlp/OtlpConfigTest.java

+9-49
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
*/
1616
package io.micrometer.registry.otlp;
1717

18-
import io.micrometer.core.instrument.Meter;
19-
import io.micrometer.core.instrument.Tags;
2018
import io.micrometer.core.instrument.config.InvalidConfigurationException;
2119
import org.junit.jupiter.api.Test;
2220

@@ -27,8 +25,9 @@
2725
import java.util.concurrent.TimeUnit;
2826
import java.util.stream.Stream;
2927

30-
import static org.assertj.core.api.Assertions.assertThat;
31-
import static org.assertj.core.api.Assertions.assertThatThrownBy;
28+
import static io.micrometer.registry.otlp.HistogramFlavor.BASE2_EXPONENTIAL_BUCKET_HISTOGRAM;
29+
import static io.micrometer.registry.otlp.HistogramFlavor.EXPLICIT_BUCKET_HISTOGRAM;
30+
import static org.assertj.core.api.Assertions.*;
3231
import static uk.org.webcompere.systemstubs.SystemStubs.withEnvironmentVariable;
3332
import static uk.org.webcompere.systemstubs.SystemStubs.withEnvironmentVariables;
3433

@@ -258,24 +257,22 @@ void histogramPreference() {
258257

259258
OtlpConfig otlpConfig = properties::get;
260259
assertThat(otlpConfig.validate().isValid()).isTrue();
261-
assertThat(otlpConfig.histogramFlavor()).isEqualTo(HistogramFlavor.BASE2_EXPONENTIAL_BUCKET_HISTOGRAM);
260+
assertThat(otlpConfig.histogramFlavor()).isEqualTo(BASE2_EXPONENTIAL_BUCKET_HISTOGRAM);
262261
}
263262

264263
@Test
265264
void histogramPreferenceConfigTakesPrecedenceOverEnvVars() throws Exception {
266265
OtlpConfig config = k -> "base2_exponential_bucket_histogram";
267266
withEnvironmentVariable("OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION", "explicit_bucket_histogram")
268-
.execute(() -> assertThat(config.histogramFlavor())
269-
.isEqualTo(HistogramFlavor.BASE2_EXPONENTIAL_BUCKET_HISTOGRAM));
267+
.execute(() -> assertThat(config.histogramFlavor()).isEqualTo(BASE2_EXPONENTIAL_BUCKET_HISTOGRAM));
270268
}
271269

272270
@Test
273271
void histogramPreferenceUseEnvVarWhenConfigNotSet() throws Exception {
274272
OtlpConfig config = k -> null;
275273
withEnvironmentVariable("OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION",
276274
"base2_exponential_bucket_histogram")
277-
.execute(() -> assertThat(config.histogramFlavor())
278-
.isEqualTo(HistogramFlavor.BASE2_EXPONENTIAL_BUCKET_HISTOGRAM));
275+
.execute(() -> assertThat(config.histogramFlavor()).isEqualTo(BASE2_EXPONENTIAL_BUCKET_HISTOGRAM));
279276
}
280277

281278
@Test
@@ -285,41 +282,8 @@ void histogramFlavorPerMeter() {
285282
"a.b.c=explicit_bucket_histogram ,expo =base2_exponential_bucket_histogram");
286283
OtlpConfig otlpConfig = properties::get;
287284
assertThat(otlpConfig.validate().isValid()).isTrue();
288-
assertThat(otlpConfig.histogramFlavorPerMeter(idWithName("a.b.c")))
289-
.isEqualTo(HistogramFlavor.EXPLICIT_BUCKET_HISTOGRAM);
290-
assertThat(otlpConfig.histogramFlavorPerMeter(idWithName("expo")))
291-
.isEqualTo(HistogramFlavor.BASE2_EXPONENTIAL_BUCKET_HISTOGRAM);
292-
}
293-
294-
@Test
295-
void customHistogramFlavorPerMeterFunction() {
296-
Map<String, String> properties = new HashMap<>();
297-
properties.put("otlp.histogramFlavorPerMeter",
298-
"a.b.c=explicit_bucket_histogram,expo=base2_exponential_bucket_histogram");
299-
OtlpConfig otlpConfig = new OtlpConfig() {
300-
@Override
301-
public String get(String key) {
302-
return properties.get(key);
303-
}
304-
305-
@Override
306-
public HistogramFlavor histogramFlavorPerMeter(Meter.Id id) {
307-
for (Map.Entry<String, HistogramFlavor> entry : histogramFlavorPerMeter().entrySet()) {
308-
if (id.getName().startsWith(entry.getKey())) {
309-
return entry.getValue();
310-
}
311-
}
312-
return histogramFlavor();
313-
}
314-
};
315-
assertThat(otlpConfig.validate().isValid()).isTrue();
316-
assertThat(otlpConfig.histogramFlavorPerMeter(idWithName("something"))).isEqualTo(otlpConfig.histogramFlavor());
317-
assertThat(otlpConfig.histogramFlavorPerMeter(idWithName("a.b.c")))
318-
.isEqualTo(HistogramFlavor.EXPLICIT_BUCKET_HISTOGRAM);
319-
assertThat(otlpConfig.histogramFlavorPerMeter(idWithName("expo")))
320-
.isEqualTo(HistogramFlavor.BASE2_EXPONENTIAL_BUCKET_HISTOGRAM);
321-
assertThat(otlpConfig.histogramFlavorPerMeter(idWithName("expo.other")))
322-
.isEqualTo(HistogramFlavor.BASE2_EXPONENTIAL_BUCKET_HISTOGRAM);
285+
assertThat(otlpConfig.histogramFlavorPerMeter()).containsExactly(entry("a.b.c", EXPLICIT_BUCKET_HISTOGRAM),
286+
entry("expo", BASE2_EXPONENTIAL_BUCKET_HISTOGRAM));
323287
}
324288

325289
@Test
@@ -328,11 +292,7 @@ void maxBucketsPerMeter() {
328292
properties.put("otlp.maxBucketsPerMeter", "a.b.c = 10");
329293
OtlpConfig otlpConfig = properties::get;
330294
assertThat(otlpConfig.validate().isValid()).isTrue();
331-
assertThat(otlpConfig.maxBucketsPerMeter(idWithName("a.b.c"))).isEqualTo(10);
332-
}
333-
334-
Meter.Id idWithName(String name) {
335-
return new Meter.Id(name, Tags.empty(), null, null, Meter.Type.OTHER);
295+
assertThat(otlpConfig.maxBucketsPerMeter()).containsExactly(entry("a.b.c", 10));
336296
}
337297

338298
}
There was a problem loading the remainder of the diff.

0 commit comments

Comments
 (0)
Please sign in to comment.