diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1e56079c88..8b35b110cf 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -69,7 +69,7 @@ newrelic-api = "5.14.0" # Kotlin 1.7 sample will fail from OkHttp 4.12.0 due to okio dependency being a Kotlin 1.9 module okhttp = "4.11.0" postgre = "42.7.2" -prometheus = "1.2.0" +prometheus = "1.2.1" prometheusSimpleClient = "0.16.0" reactor = "2022.0.16" rest-assured = "5.4.0" diff --git a/implementations/micrometer-registry-prometheus/build.gradle b/implementations/micrometer-registry-prometheus/build.gradle index b9f4e60f05..a154e9eb89 100644 --- a/implementations/micrometer-registry-prometheus/build.gradle +++ b/implementations/micrometer-registry-prometheus/build.gradle @@ -4,12 +4,11 @@ dependencies { api project(':micrometer-core') api('io.prometheus:prometheus-metrics-core') { - // We only need SpanContext from prometheus-metrics-tracer-common so we should - // exclude(group: 'io.prometheus', module: 'prometheus-metrics-tracer-initializer') - // But right now we cannot since ExemplarSampler imports SpanContextSupplier - exclude(group: 'io.prometheus', module: 'prometheus-metrics-tracer-otel') - exclude(group: 'io.prometheus', module: 'prometheus-metrics-tracer-otel-agent') + // We only need SpanContext from prometheus-metrics-tracer-common, we don't need + // prometheus-metrics-tracer-initializer nor the dependencies it pulls in + exclude(group: 'io.prometheus', module: 'prometheus-metrics-tracer-initializer') } + api 'io.prometheus:prometheus-metrics-tracer-common' implementation 'io.prometheus:prometheus-metrics-exposition-formats' testImplementation 'io.prometheus:prometheus-metrics-tracer-common' diff --git a/implementations/micrometer-registry-prometheus/src/main/java/io/micrometer/prometheusmetrics/DefaultExemplarSamplerFactory.java b/implementations/micrometer-registry-prometheus/src/main/java/io/micrometer/prometheusmetrics/DefaultExemplarSamplerFactory.java index 9f0266d82b..295c5d822f 100644 --- a/implementations/micrometer-registry-prometheus/src/main/java/io/micrometer/prometheusmetrics/DefaultExemplarSamplerFactory.java +++ b/implementations/micrometer-registry-prometheus/src/main/java/io/micrometer/prometheusmetrics/DefaultExemplarSamplerFactory.java @@ -16,7 +16,6 @@ package io.micrometer.prometheusmetrics; import io.prometheus.metrics.config.ExemplarsProperties; -import io.prometheus.metrics.config.PrometheusProperties; import io.prometheus.metrics.core.exemplars.ExemplarSampler; import io.prometheus.metrics.core.exemplars.ExemplarSamplerConfig; import io.prometheus.metrics.tracer.common.SpanContext; @@ -32,7 +31,7 @@ */ class DefaultExemplarSamplerFactory implements ExemplarSamplerFactory { - private final ExemplarsProperties exemplarProperties = PrometheusProperties.get().getExemplarProperties(); + private final ExemplarsProperties exemplarsProperties; private final ConcurrentMap exemplarSamplerConfigsByNumberOfExemplars = new ConcurrentHashMap<>(); @@ -40,14 +39,15 @@ class DefaultExemplarSamplerFactory implements ExemplarSamplerFactory { private final SpanContext spanContext; - public DefaultExemplarSamplerFactory(SpanContext spanContext) { + public DefaultExemplarSamplerFactory(SpanContext spanContext, ExemplarsProperties exemplarsProperties) { this.spanContext = spanContext; + this.exemplarsProperties = exemplarsProperties; } @Override public ExemplarSampler createExemplarSampler(int numberOfExemplars) { ExemplarSamplerConfig config = exemplarSamplerConfigsByNumberOfExemplars.computeIfAbsent(numberOfExemplars, - key -> new ExemplarSamplerConfig(exemplarProperties, numberOfExemplars)); + key -> new ExemplarSamplerConfig(exemplarsProperties, numberOfExemplars)); return new ExemplarSampler(config, spanContext); } @@ -55,7 +55,7 @@ public ExemplarSampler createExemplarSampler(int numberOfExemplars) { public ExemplarSampler createExemplarSampler(double[] histogramClassicUpperBounds) { ExemplarSamplerConfig config = exemplarSamplerConfigsByHistogramUpperBounds.computeIfAbsent( histogramClassicUpperBounds, - key -> new ExemplarSamplerConfig(exemplarProperties, histogramClassicUpperBounds)); + key -> new ExemplarSamplerConfig(exemplarsProperties, histogramClassicUpperBounds)); return new ExemplarSampler(config, spanContext); } diff --git a/implementations/micrometer-registry-prometheus/src/main/java/io/micrometer/prometheusmetrics/PrometheusConfig.java b/implementations/micrometer-registry-prometheus/src/main/java/io/micrometer/prometheusmetrics/PrometheusConfig.java index 6c3179a264..13e264cc3f 100644 --- a/implementations/micrometer-registry-prometheus/src/main/java/io/micrometer/prometheusmetrics/PrometheusConfig.java +++ b/implementations/micrometer-registry-prometheus/src/main/java/io/micrometer/prometheusmetrics/PrometheusConfig.java @@ -15,10 +15,12 @@ */ package io.micrometer.prometheusmetrics; +import io.micrometer.common.lang.Nullable; import io.micrometer.core.instrument.config.MeterRegistryConfig; import io.micrometer.core.instrument.config.validate.Validated; import java.time.Duration; +import java.util.Properties; import static io.micrometer.core.instrument.config.MeterRegistryConfigValidator.checkAll; import static io.micrometer.core.instrument.config.MeterRegistryConfigValidator.checkRequired; @@ -58,6 +60,19 @@ default Duration step() { return getDuration(this, "step").orElse(Duration.ofMinutes(1)); } + /** + * @return an instance of {@link Properties} that contains Prometheus Java Client + * config entries, for example + * {@code io.prometheus.exporter.exemplarsOnAllMetricTypes=true}. + * @see https://prometheus.github.io/client_java/config/config/ + */ + @Nullable + default Properties prometheusProperties() { + Properties properties = new Properties(); + properties.setProperty("io.prometheus.exporter.exemplarsOnAllMetricTypes", "true"); + return properties; + } + @Override default Validated validate() { return checkAll(this, checkRequired("step", PrometheusConfig::step)); diff --git a/implementations/micrometer-registry-prometheus/src/main/java/io/micrometer/prometheusmetrics/PrometheusMeterRegistry.java b/implementations/micrometer-registry-prometheus/src/main/java/io/micrometer/prometheusmetrics/PrometheusMeterRegistry.java index 7304b2e699..d7063eba10 100644 --- a/implementations/micrometer-registry-prometheus/src/main/java/io/micrometer/prometheusmetrics/PrometheusMeterRegistry.java +++ b/implementations/micrometer-registry-prometheus/src/main/java/io/micrometer/prometheusmetrics/PrometheusMeterRegistry.java @@ -26,6 +26,7 @@ import io.micrometer.core.instrument.internal.DefaultGauge; import io.micrometer.core.instrument.internal.DefaultMeter; import io.micrometer.core.instrument.util.TimeUtils; +import io.prometheus.metrics.config.PrometheusProperties; import io.prometheus.metrics.config.PrometheusPropertiesLoader; import io.prometheus.metrics.expositionformats.ExpositionFormats; import io.prometheus.metrics.model.registry.PrometheusRegistry; @@ -66,13 +67,12 @@ */ public class PrometheusMeterRegistry extends MeterRegistry { - private final ExpositionFormats expositionFormats = ExpositionFormats - .init(PrometheusPropertiesLoader.load().getExporterProperties()); - private final PrometheusConfig prometheusConfig; private final PrometheusRegistry registry; + private final ExpositionFormats expositionFormats; + private final ConcurrentMap collectorMap = new ConcurrentHashMap<>(); @Nullable @@ -101,7 +101,11 @@ public PrometheusMeterRegistry(PrometheusConfig config, PrometheusRegistry regis this.prometheusConfig = config; this.registry = registry; - this.exemplarSamplerFactory = spanContext != null ? new DefaultExemplarSamplerFactory(spanContext) : null; + PrometheusProperties prometheusProperties = config.prometheusProperties() != null + ? PrometheusPropertiesLoader.load(config.prometheusProperties()) : PrometheusPropertiesLoader.load(); + this.expositionFormats = ExpositionFormats.init(prometheusProperties.getExporterProperties()); + this.exemplarSamplerFactory = spanContext != null + ? new DefaultExemplarSamplerFactory(spanContext, prometheusProperties.getExemplarProperties()) : null; config().namingConvention(new PrometheusNamingConvention()); config().onMeterRemoved(this::onMeterRemoved); diff --git a/implementations/micrometer-registry-prometheus/src/test/java/io/micrometer/prometheusmetrics/PrometheusMeterRegistryTest.java b/implementations/micrometer-registry-prometheus/src/test/java/io/micrometer/prometheusmetrics/PrometheusMeterRegistryTest.java index a0ae0ff306..d13f26f064 100644 --- a/implementations/micrometer-registry-prometheus/src/test/java/io/micrometer/prometheusmetrics/PrometheusMeterRegistryTest.java +++ b/implementations/micrometer-registry-prometheus/src/test/java/io/micrometer/prometheusmetrics/PrometheusMeterRegistryTest.java @@ -17,6 +17,7 @@ import io.micrometer.core.Issue; import io.micrometer.core.instrument.*; +import io.micrometer.core.instrument.Timer; import io.micrometer.core.instrument.binder.BaseUnits; import io.micrometer.core.instrument.binder.jvm.JvmInfoMetrics; import io.micrometer.core.instrument.composite.CompositeMeterRegistry; @@ -30,10 +31,7 @@ import org.junit.jupiter.api.Test; import java.time.Duration; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; +import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; @@ -638,8 +636,23 @@ void openMetricsScrape() { @Test void openMetricsScrapeWithExemplars() throws InterruptedException { - PrometheusMeterRegistry registry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT, prometheusRegistry, - clock, new TestSpanContex()); + PrometheusConfig prometheusConfig = new PrometheusConfig() { + @Override + public String get(String key) { + return null; + } + + @Override + public Properties prometheusProperties() { + Properties properties = new Properties(); + properties.putAll(PrometheusConfig.super.prometheusProperties()); + properties.setProperty("io.prometheus.exemplars.sampleIntervalMilliseconds", "1"); + return properties; + } + }; + + PrometheusMeterRegistry registry = new PrometheusMeterRegistry(prometheusConfig, prometheusRegistry, clock, + new TestSpanContex()); Counter counter = Counter.builder("my.counter").register(registry); counter.increment(); @@ -651,18 +664,18 @@ void openMetricsScrapeWithExemplars() throws InterruptedException { Timer timerWithHistogram = Timer.builder("timer.withHistogram").publishPercentileHistogram().register(registry); timerWithHistogram.record(15, TimeUnit.MILLISECONDS); - Thread.sleep(100); // sleeping 100ms since the sample interval limit is 90ms + Thread.sleep(5); // sleeping 5ms since the sample interval limit is 1ms timerWithHistogram.record(150, TimeUnit.MILLISECONDS); - Thread.sleep(100); // sleeping 100ms since the sample interval limit is 90ms + Thread.sleep(5); // sleeping 5ms since the sample interval limit is 1ms timerWithHistogram.record(60, TimeUnit.SECONDS); Timer timerWithSlos = Timer.builder("timer.withSlos") .serviceLevelObjectives(Duration.ofMillis(100), Duration.ofMillis(200), Duration.ofMillis(300)) .register(registry); timerWithSlos.record(Duration.ofMillis(15)); - Thread.sleep(100); // sleeping 100ms since the sample interval limit is 90ms + Thread.sleep(5); // sleeping 5ms since the sample interval limit is 1ms timerWithSlos.record(Duration.ofMillis(1_500)); - Thread.sleep(100); // sleeping 100ms since the sample interval limit is 90ms + Thread.sleep(5); // sleeping 5ms since the sample interval limit is 1ms timerWithSlos.record(Duration.ofMillis(150)); DistributionSummary summary = DistributionSummary.builder("summary.noHistogram").register(registry); @@ -674,18 +687,18 @@ void openMetricsScrapeWithExemplars() throws InterruptedException { .publishPercentileHistogram() .register(registry); summaryWithHistogram.record(0.15); - Thread.sleep(100); // sleeping 100ms since the sample interval limit is 90ms + Thread.sleep(5); // sleeping 5ms since the sample interval limit is 1ms summaryWithHistogram.record(5E18); - Thread.sleep(100); // sleeping 100ms since the sample interval limit is 90ms + Thread.sleep(5); // sleeping 5ms since the sample interval limit is 1ms summaryWithHistogram.record(15); DistributionSummary slos = DistributionSummary.builder("summary.withSlos") .serviceLevelObjectives(100, 200, 300) .register(registry); slos.record(10); - Thread.sleep(100); // sleeping 100ms since the sample interval limit is 90ms + Thread.sleep(5); // sleeping 5ms since the sample interval limit is 1ms slos.record(1_000); - Thread.sleep(100); // sleeping 100ms since the sample interval limit is 90ms + Thread.sleep(5); // sleeping 5ms since the sample interval limit is 1ms slos.record(250); String scraped = registry.scrape("application/openmetrics-text"); diff --git a/implementations/micrometer-registry-prometheus/src/test/resources/prometheus.properties b/implementations/micrometer-registry-prometheus/src/test/resources/prometheus.properties deleted file mode 100644 index 7b71c95613..0000000000 --- a/implementations/micrometer-registry-prometheus/src/test/resources/prometheus.properties +++ /dev/null @@ -1,17 +0,0 @@ -# -# Copyright 2024 VMware, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -io.prometheus.exporter.exemplarsOnAllMetricTypes=true