Skip to content

Commit 815f0c4

Browse files
Make per-meter OTLP configuration more flexible (#6102)
The previous logic for matching required the exact meter name, which was straightforward but cumbersome for configuring groups of meters. This changes the default matching for the per-meter config to use the longest dot-separated segments match. See the updated JavaDocs on the OtlpConfig methods. Co-authored-by: Jonatan Ivanov <[email protected]>
1 parent c3ed54e commit 815f0c4

File tree

4 files changed

+298
-38
lines changed

4 files changed

+298
-38
lines changed

Diff for: implementations/micrometer-registry-otlp/src/main/java/io/micrometer/registry/otlp/OtlpConfig.java

+21-9
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,13 @@
1515
*/
1616
package io.micrometer.registry.otlp;
1717

18+
import io.micrometer.core.instrument.Meter;
1819
import io.micrometer.core.instrument.config.InvalidConfigurationException;
1920
import io.micrometer.core.instrument.config.validate.Validated;
2021
import io.micrometer.core.instrument.push.PushRegistryConfig;
2122

22-
import java.time.Duration;
2323
import java.net.URLDecoder;
24+
import java.time.Duration;
2425
import java.util.*;
2526
import java.util.concurrent.TimeUnit;
2627
import java.util.stream.Collectors;
@@ -228,10 +229,16 @@ default HistogramFlavor histogramFlavor() {
228229
}
229230

230231
/**
231-
* Configures the histogram flavor to use on a per-meter level. This will override the
232-
* {@link #histogramFlavor()} configuration for matching Meters. The key is used to do
233-
* an exact match on the Meter's name.
234-
* @return mapping of meter name to histogram flavor
232+
* Configures the histogram flavor mapping to use on a per-meter level. This can
233+
* override the {@link #histogramFlavor()} configuration for matching Meters.
234+
* {@link OtlpMeterRegistry} uses the result of this method to look up the
235+
* {@link HistogramFlavor} by {@link Meter.Id}. The longest dot-separated match wins.
236+
* For example, if the returned Map has keys {@literal http} and
237+
* {@literal http.server}, an ID with a name {@literal http.server.requests} would
238+
* match with the entry having key {@literal http.server}, whereas an ID with name
239+
* {@literal http.client.requests} would match with the entry having the key
240+
* {@literal http}.
241+
* @return mapping of meter name (or prefix) to histogram flavor
235242
* @since 1.15.0
236243
* @see #histogramFlavor()
237244
*/
@@ -266,10 +273,15 @@ default int maxBucketCount() {
266273
}
267274

268275
/**
269-
* Configures the max bucket count to use on a per-meter level. This will override the
270-
* {@link #maxBucketCount()} configuration for matching Meters. The key is used to do
271-
* an exact match on the Meter's name. This has no effect on a meter if it does not
272-
* have an exponential bucket histogram configured.
276+
* Configures the max bucket count mapping to use on a per-meter level. This can
277+
* override the {@link #maxBucketCount()} configuration for matching Meters.
278+
* {@link OtlpMeterRegistry} uses the result of this method to look up the max bucket
279+
* count by {@link Meter.Id}. The longest dot-separated match wins. For example, if
280+
* the returned Map has keys {@literal http} and {@literal http.server}, an ID with a
281+
* name {@literal http.server.requests} would match with the entry having key
282+
* {@literal http.server}, whereas an ID with name {@literal http.client.requests}
283+
* would match with the entry having the key {@literal http}. This has no effect on a
284+
* meter if it does not have an exponential bucket histogram configured.
273285
* @return mapping of meter name to max bucket count
274286
* @since 1.15.0
275287
* @see #maxBucketCount()

Diff for: implementations/micrometer-registry-otlp/src/main/java/io/micrometer/registry/otlp/OtlpMeterRegistry.java

+99-5
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@
1616
package io.micrometer.registry.otlp;
1717

1818
import io.micrometer.common.lang.Nullable;
19+
import io.micrometer.common.util.StringUtils;
1920
import io.micrometer.common.util.internal.logging.InternalLogger;
2021
import io.micrometer.common.util.internal.logging.InternalLoggerFactory;
21-
import io.micrometer.core.instrument.Timer;
2222
import io.micrometer.core.instrument.*;
23+
import io.micrometer.core.instrument.Timer;
2324
import io.micrometer.core.instrument.config.NamingConvention;
2425
import io.micrometer.core.instrument.distribution.*;
2526
import io.micrometer.core.instrument.distribution.pause.PauseDetector;
@@ -82,6 +83,10 @@ public class OtlpMeterRegistry extends PushMeterRegistry {
8283

8384
private final OtlpMetricsSender metricsSender;
8485

86+
private final HistogramFlavorPerMeterLookup histogramFlavorPerMeterLookup;
87+
88+
private final MaxBucketsPerMeterLookup maxBucketsPerMeterLookup;
89+
8590
private final Resource resource;
8691

8792
private final AggregationTemporality aggregationTemporality;
@@ -120,6 +125,8 @@ private OtlpMeterRegistry(OtlpConfig config, Clock clock, ThreadFactory threadFa
120125
this.config = config;
121126
this.baseTimeUnit = config.baseTimeUnit();
122127
this.metricsSender = metricsSender;
128+
this.histogramFlavorPerMeterLookup = HistogramFlavorPerMeterLookup.DEFAULT;
129+
this.maxBucketsPerMeterLookup = MaxBucketsPerMeterLookup.DEFAULT;
123130
this.resource = Resource.newBuilder().addAllAttributes(getResourceAttributes()).build();
124131
this.aggregationTemporality = config.aggregationTemporality();
125132
config().namingConvention(NamingConvention.dot);
@@ -430,14 +437,16 @@ private Histogram getHistogram(Meter.Id id, DistributionStatisticConfig distribu
430437
}
431438

432439
private int getMaxBuckets(Meter.Id id) {
433-
return config.maxBucketsPerMeter().getOrDefault(id.getName(), config.maxBucketCount());
440+
Integer maxBuckets = maxBucketsPerMeterLookup.getMaxBuckets(config.maxBucketsPerMeter(), id);
441+
return (maxBuckets == null) ? config.maxBucketCount() : maxBuckets;
434442
}
435443

436444
private HistogramFlavor histogramFlavor(Meter.Id id, OtlpConfig otlpConfig,
437445
DistributionStatisticConfig distributionStatisticConfig) {
438-
HistogramFlavor preferredHistogramFlavor = otlpConfig.histogramFlavorPerMeter()
439-
.getOrDefault(id.getName(), otlpConfig.histogramFlavor());
440-
446+
HistogramFlavor preferredHistogramFlavor = histogramFlavorPerMeterLookup
447+
.getHistogramFlavor(otlpConfig.histogramFlavorPerMeter(), id);
448+
preferredHistogramFlavor = preferredHistogramFlavor == null ? otlpConfig.histogramFlavor()
449+
: preferredHistogramFlavor;
441450
final double[] serviceLevelObjectiveBoundaries = distributionStatisticConfig
442451
.getServiceLevelObjectiveBoundaries();
443452
if (distributionStatisticConfig.isPublishingHistogram()
@@ -497,6 +506,91 @@ static double[] getSloWithPositiveInf(DistributionStatisticConfig distributionSt
497506
return sloWithPositiveInf;
498507
}
499508

509+
/**
510+
* Overridable lookup mechanism for {@link HistogramFlavor}.
511+
*/
512+
// VisibleForTesting
513+
@FunctionalInterface
514+
interface HistogramFlavorPerMeterLookup {
515+
516+
/**
517+
* Default implementation.
518+
*/
519+
HistogramFlavorPerMeterLookup DEFAULT = OtlpMeterRegistry::lookup;
520+
521+
/**
522+
* Looks up the histogram flavor to use on a per-meter level. This will override
523+
* the default {@link OtlpConfig#histogramFlavor()} for matching Meters.
524+
* {@link OtlpConfig#histogramFlavorPerMeter()} provides the data while this
525+
* method provides the logic for the lookup, and you can override them
526+
* independently.
527+
* @param perMeterMapping configured mapping data
528+
* @param id the {@link Meter.Id} the {@link HistogramFlavor} is configured for
529+
* @return the histogram flavor mapped to the {@link Meter.Id} or {@code null} if
530+
* mapping is undefined
531+
* @see OtlpConfig#histogramFlavorPerMeter()
532+
* @see OtlpConfig#histogramFlavor()
533+
*/
534+
@Nullable
535+
HistogramFlavor getHistogramFlavor(Map<String, HistogramFlavor> perMeterMapping, Meter.Id id);
536+
537+
}
538+
539+
/**
540+
* Overridable lookup mechanism for max bucket count. This has no effect on a meter if
541+
* it does not have an exponential bucket histogram configured.
542+
*/
543+
// VisibleForTesting
544+
@FunctionalInterface
545+
interface MaxBucketsPerMeterLookup {
546+
547+
/**
548+
* Default implementation.
549+
*/
550+
MaxBucketsPerMeterLookup DEFAULT = OtlpMeterRegistry::lookup;
551+
552+
/**
553+
* Looks up the max bucket count to use on a per-meter level. This will override
554+
* the default {@link OtlpConfig#maxBucketCount()} for matching Meters.
555+
* {@link OtlpConfig#maxBucketsPerMeter()} provides the data while this method
556+
* provides the logic for the lookup, and you can override them independently.
557+
* This has no effect on a meter if it does not have an exponential bucket
558+
* histogram configured.
559+
* @param perMeterMapping configured mapping data
560+
* @param id the {@link Meter.Id} the max bucket count is configured for
561+
* @return the max bucket count mapped to the {@link Meter.Id} or {@code null} if
562+
* the mapping is undefined
563+
* @see OtlpConfig#maxBucketsPerMeter()
564+
* @see OtlpConfig#maxBucketCount()
565+
*/
566+
@Nullable
567+
Integer getMaxBuckets(Map<String, Integer> perMeterMapping, Meter.Id id);
568+
569+
}
570+
571+
@Nullable
572+
private static <T> T lookup(Map<String, T> values, Meter.Id id) {
573+
if (values.isEmpty()) {
574+
return null;
575+
}
576+
return doLookup(values, id);
577+
}
578+
579+
@Nullable
580+
private static <T> T doLookup(Map<String, T> values, Meter.Id id) {
581+
String name = id.getName();
582+
while (StringUtils.isNotEmpty(name)) {
583+
T result = values.get(name);
584+
if (result != null) {
585+
return result;
586+
}
587+
int lastDot = name.lastIndexOf('.');
588+
name = (lastDot != -1) ? name.substring(0, lastDot) : "";
589+
}
590+
591+
return null;
592+
}
593+
500594
/**
501595
* Builder for {@link OtlpMeterRegistry}.
502596
*

Diff for: implementations/micrometer-registry-otlp/src/test/java/io/micrometer/registry/otlp/OtlpConfigTest.java

+8-11
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@
2525
import java.util.concurrent.TimeUnit;
2626
import java.util.stream.Stream;
2727

28-
import static java.util.Map.entry;
29-
import static org.assertj.core.api.Assertions.assertThat;
30-
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.*;
3131
import static uk.org.webcompere.systemstubs.SystemStubs.withEnvironmentVariable;
3232
import static uk.org.webcompere.systemstubs.SystemStubs.withEnvironmentVariables;
3333

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

258258
OtlpConfig otlpConfig = properties::get;
259259
assertThat(otlpConfig.validate().isValid()).isTrue();
260-
assertThat(otlpConfig.histogramFlavor()).isEqualTo(HistogramFlavor.BASE2_EXPONENTIAL_BUCKET_HISTOGRAM);
260+
assertThat(otlpConfig.histogramFlavor()).isEqualTo(BASE2_EXPONENTIAL_BUCKET_HISTOGRAM);
261261
}
262262

263263
@Test
264264
void histogramPreferenceConfigTakesPrecedenceOverEnvVars() throws Exception {
265265
OtlpConfig config = k -> "base2_exponential_bucket_histogram";
266266
withEnvironmentVariable("OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION", "explicit_bucket_histogram")
267-
.execute(() -> assertThat(config.histogramFlavor())
268-
.isEqualTo(HistogramFlavor.BASE2_EXPONENTIAL_BUCKET_HISTOGRAM));
267+
.execute(() -> assertThat(config.histogramFlavor()).isEqualTo(BASE2_EXPONENTIAL_BUCKET_HISTOGRAM));
269268
}
270269

271270
@Test
272271
void histogramPreferenceUseEnvVarWhenConfigNotSet() throws Exception {
273272
OtlpConfig config = k -> null;
274273
withEnvironmentVariable("OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION",
275274
"base2_exponential_bucket_histogram")
276-
.execute(() -> assertThat(config.histogramFlavor())
277-
.isEqualTo(HistogramFlavor.BASE2_EXPONENTIAL_BUCKET_HISTOGRAM));
275+
.execute(() -> assertThat(config.histogramFlavor()).isEqualTo(BASE2_EXPONENTIAL_BUCKET_HISTOGRAM));
278276
}
279277

280278
@Test
@@ -284,9 +282,8 @@ void histogramFlavorPerMeter() {
284282
"a.b.c=explicit_bucket_histogram ,expo =base2_exponential_bucket_histogram");
285283
OtlpConfig otlpConfig = properties::get;
286284
assertThat(otlpConfig.validate().isValid()).isTrue();
287-
assertThat(otlpConfig.histogramFlavorPerMeter()).containsExactly(
288-
entry("a.b.c", HistogramFlavor.EXPLICIT_BUCKET_HISTOGRAM),
289-
entry("expo", HistogramFlavor.BASE2_EXPONENTIAL_BUCKET_HISTOGRAM));
285+
assertThat(otlpConfig.histogramFlavorPerMeter()).containsExactly(entry("a.b.c", EXPLICIT_BUCKET_HISTOGRAM),
286+
entry("expo", BASE2_EXPONENTIAL_BUCKET_HISTOGRAM));
290287
}
291288

292289
@Test

0 commit comments

Comments
 (0)