From ede5cc3440c8936c3cdf1b98c9369f319e24477a Mon Sep 17 00:00:00 2001 From: Marcin Kielar Date: Fri, 8 Dec 2023 09:24:05 +0100 Subject: [PATCH] Redesign of Collector architecture (#901). Signed-off-by: Marcin Kielar --- .../metrics/examples/multitarget/Main.java | 2 +- ...ltiCollector.java => SampleCollector.java} | 37 +---- .../exporter/httpserver/HTTPServerSample.java | 6 +- .../jetty/ExporterServletJettySample.java | 6 +- .../tomcat/ExporterServletTomcatSample.java | 6 +- prometheus-metrics-core/pom.xml | 12 ++ .../metrics/core/metrics/Metric.java | 4 +- .../core/metrics/MetricWithFixedMetadata.java | 24 ++-- .../metrics/core/metrics/NameFilterTest.java | 61 ++++++++ .../common/PrometheusScrapeHandler.java | 15 +- .../exporter/httpserver/MetricsHandler.java | 3 +- .../pom.xml | 12 ++ .../jvm/JvmBufferPoolMetrics.java | 39 +++--- .../jvm/ProcessMetricsTest.java | 8 +- prometheus-metrics-model/pom.xml | 12 ++ .../metrics/model/registry/Collector.java | 73 ++-------- .../model/registry/CollectorBuilder.java | 130 ++++++++++++++++++ .../model/registry/MultiCollector.java | 71 ---------- .../model/registry/PrometheusRegistry.java | 110 ++------------- .../registry/PrometheusScrapeRequest.java | 1 - .../model/snapshots/CounterSnapshot.java | 30 ++++ .../model/snapshots/GaugeSnapshot.java | 29 ++++ .../model/snapshots/HistogramSnapshot.java | 43 ++++++ .../metrics/model/snapshots/InfoSnapshot.java | 30 ++++ .../model/snapshots/MetricMetadata.java | 19 +++ .../model/snapshots/MetricSnapshot.java | 13 ++ .../model/snapshots/MetricSnapshots.java | 102 ++++++++------ .../model/snapshots/StateSetSnapshot.java | 39 +++++- .../model/snapshots/SummarySnapshot.java | 31 +++++ .../model/snapshots/UnknownSnapshot.java | 31 +++++ .../CollectorTransformationsTest.java | 54 ++++++++ .../model/registry/MetricNameFilterTest.java | 53 ++++--- .../MultiCollectorNameFilterTest.java | 60 ++------ .../registry/PrometheusRegistryTest.java | 97 +++++-------- .../model/snapshots/MetricSnapshotsTest.java | 47 +++++-- .../bridge/SimpleclientCollector.java | 54 ++++---- 36 files changed, 820 insertions(+), 544 deletions(-) rename examples/example-exporter-multi-target/src/main/java/io/prometheus/metrics/examples/multitarget/{SampleMultiCollector.java => SampleCollector.java} (73%) create mode 100644 prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/NameFilterTest.java create mode 100644 prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/CollectorBuilder.java delete mode 100644 prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/MultiCollector.java create mode 100644 prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/CollectorTransformationsTest.java diff --git a/examples/example-exporter-multi-target/src/main/java/io/prometheus/metrics/examples/multitarget/Main.java b/examples/example-exporter-multi-target/src/main/java/io/prometheus/metrics/examples/multitarget/Main.java index da36346b9..9c7c9c495 100644 --- a/examples/example-exporter-multi-target/src/main/java/io/prometheus/metrics/examples/multitarget/Main.java +++ b/examples/example-exporter-multi-target/src/main/java/io/prometheus/metrics/examples/multitarget/Main.java @@ -12,7 +12,7 @@ public class Main { public static void main(String[] args) throws IOException, InterruptedException { - SampleMultiCollector xmc = new SampleMultiCollector(); + SampleCollector xmc = new SampleCollector(); PrometheusRegistry.defaultRegistry.register(xmc); HTTPServer server = HTTPServer.builder() .port(9401) diff --git a/examples/example-exporter-multi-target/src/main/java/io/prometheus/metrics/examples/multitarget/SampleMultiCollector.java b/examples/example-exporter-multi-target/src/main/java/io/prometheus/metrics/examples/multitarget/SampleCollector.java similarity index 73% rename from examples/example-exporter-multi-target/src/main/java/io/prometheus/metrics/examples/multitarget/SampleMultiCollector.java rename to examples/example-exporter-multi-target/src/main/java/io/prometheus/metrics/examples/multitarget/SampleCollector.java index 819bb3028..71d8f8672 100644 --- a/examples/example-exporter-multi-target/src/main/java/io/prometheus/metrics/examples/multitarget/SampleMultiCollector.java +++ b/examples/example-exporter-multi-target/src/main/java/io/prometheus/metrics/examples/multitarget/SampleCollector.java @@ -1,37 +1,23 @@ package io.prometheus.metrics.examples.multitarget; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; +import java.util.function.Predicate; -import io.prometheus.metrics.model.registry.MultiCollector; +import io.prometheus.metrics.model.registry.Collector; import io.prometheus.metrics.model.registry.PrometheusScrapeRequest; import io.prometheus.metrics.model.snapshots.CounterSnapshot; import io.prometheus.metrics.model.snapshots.CounterSnapshot.CounterDataPointSnapshot.Builder; import io.prometheus.metrics.model.snapshots.GaugeSnapshot; import io.prometheus.metrics.model.snapshots.Labels; -import io.prometheus.metrics.model.snapshots.MetricSnapshot; import io.prometheus.metrics.model.snapshots.MetricSnapshots; import io.prometheus.metrics.model.snapshots.PrometheusNaming; -public class SampleMultiCollector implements MultiCollector { - - public SampleMultiCollector() { - super(); - } - - @Override - public MetricSnapshots collect() { - return new MetricSnapshots(); - } - +public class SampleCollector implements Collector { @Override - public MetricSnapshots collect(PrometheusScrapeRequest scrapeRequest) { - return collectMetricSnapshots(scrapeRequest); + public MetricSnapshots collect(Predicate nameFilter, PrometheusScrapeRequest scrapeRequest) { + return collectMetricSnapshots(scrapeRequest).filter(nameFilter); } protected MetricSnapshots collectMetricSnapshots(PrometheusScrapeRequest scrapeRequest) { - GaugeSnapshot.Builder gaugeBuilder = GaugeSnapshot.builder(); gaugeBuilder.name("x_load").help("process load"); @@ -71,18 +57,7 @@ protected MetricSnapshots collectMetricSnapshots(PrometheusScrapeRequest scrapeR gaugeBuilder.dataPoint(gaugeDataPointBuilder.build()); } } - Collection snaps = new ArrayList(); - snaps.add(counterBuilder.build()); - snaps.add(gaugeBuilder.build()); - MetricSnapshots msnaps = new MetricSnapshots(snaps); + MetricSnapshots msnaps = new MetricSnapshots(counterBuilder.build(), gaugeBuilder.build()); return msnaps; } - - public List getPrometheusNames() { - List names = new ArrayList(); - names.add("x_calls_total"); - names.add("x_load"); - return names; - } - } diff --git a/integration-tests/it-exporter/it-exporter-httpserver-sample/src/main/java/io/prometheus/metrics/it/exporter/httpserver/HTTPServerSample.java b/integration-tests/it-exporter/it-exporter-httpserver-sample/src/main/java/io/prometheus/metrics/it/exporter/httpserver/HTTPServerSample.java index 9187ecfcb..a2602aa82 100644 --- a/integration-tests/it-exporter/it-exporter-httpserver-sample/src/main/java/io/prometheus/metrics/it/exporter/httpserver/HTTPServerSample.java +++ b/integration-tests/it-exporter/it-exporter-httpserver-sample/src/main/java/io/prometheus/metrics/it/exporter/httpserver/HTTPServerSample.java @@ -4,9 +4,9 @@ import io.prometheus.metrics.core.metrics.Gauge; import io.prometheus.metrics.core.metrics.Info; import io.prometheus.metrics.exporter.httpserver.HTTPServer; +import io.prometheus.metrics.model.registry.CollectorBuilder; import io.prometheus.metrics.model.registry.Collector; import io.prometheus.metrics.model.registry.PrometheusRegistry; -import io.prometheus.metrics.model.snapshots.MetricSnapshot; import io.prometheus.metrics.model.snapshots.Unit; import java.io.IOException; @@ -53,9 +53,9 @@ public static void main(String[] args) throws IOException, InterruptedException gauge.labelValues("outside").set(27.0); if (mode == Mode.error) { - Collector failingCollector = () -> { + Collector failingCollector = CollectorBuilder.fromMetric(() -> { throw new RuntimeException("Simulating an error."); - }; + }); PrometheusRegistry.defaultRegistry.register(failingCollector); } diff --git a/integration-tests/it-exporter/it-exporter-servlet-jetty-sample/src/main/java/io/prometheus/metrics/it/exporter/servlet/jetty/ExporterServletJettySample.java b/integration-tests/it-exporter/it-exporter-servlet-jetty-sample/src/main/java/io/prometheus/metrics/it/exporter/servlet/jetty/ExporterServletJettySample.java index 9e1a22487..af506eec0 100644 --- a/integration-tests/it-exporter/it-exporter-servlet-jetty-sample/src/main/java/io/prometheus/metrics/it/exporter/servlet/jetty/ExporterServletJettySample.java +++ b/integration-tests/it-exporter/it-exporter-servlet-jetty-sample/src/main/java/io/prometheus/metrics/it/exporter/servlet/jetty/ExporterServletJettySample.java @@ -4,9 +4,9 @@ import io.prometheus.metrics.core.metrics.Gauge; import io.prometheus.metrics.core.metrics.Info; import io.prometheus.metrics.exporter.servlet.jakarta.PrometheusMetricsServlet; +import io.prometheus.metrics.model.registry.CollectorBuilder; import io.prometheus.metrics.model.registry.Collector; import io.prometheus.metrics.model.registry.PrometheusRegistry; -import io.prometheus.metrics.model.snapshots.MetricSnapshot; import io.prometheus.metrics.model.snapshots.Unit; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Server; @@ -57,9 +57,9 @@ public static void main(String[] args) throws Exception { gauge.labelValues("outside").set(27.0); if (mode == Mode.error) { - Collector failingCollector = () -> { + Collector failingCollector = CollectorBuilder.fromMetrics(() -> { throw new RuntimeException("Simulating an error."); - }; + }); PrometheusRegistry.defaultRegistry.register(failingCollector); } diff --git a/integration-tests/it-exporter/it-exporter-servlet-tomcat-sample/src/main/java/io/prometheus/metrics/it/exporter/servlet/tomcat/ExporterServletTomcatSample.java b/integration-tests/it-exporter/it-exporter-servlet-tomcat-sample/src/main/java/io/prometheus/metrics/it/exporter/servlet/tomcat/ExporterServletTomcatSample.java index 8d13082b7..cd75a7832 100644 --- a/integration-tests/it-exporter/it-exporter-servlet-tomcat-sample/src/main/java/io/prometheus/metrics/it/exporter/servlet/tomcat/ExporterServletTomcatSample.java +++ b/integration-tests/it-exporter/it-exporter-servlet-tomcat-sample/src/main/java/io/prometheus/metrics/it/exporter/servlet/tomcat/ExporterServletTomcatSample.java @@ -4,9 +4,9 @@ import io.prometheus.metrics.core.metrics.Gauge; import io.prometheus.metrics.core.metrics.Info; import io.prometheus.metrics.exporter.servlet.jakarta.PrometheusMetricsServlet; +import io.prometheus.metrics.model.registry.CollectorBuilder; import io.prometheus.metrics.model.registry.Collector; import io.prometheus.metrics.model.registry.PrometheusRegistry; -import io.prometheus.metrics.model.snapshots.MetricSnapshot; import io.prometheus.metrics.model.snapshots.Unit; import org.apache.catalina.Context; import org.apache.catalina.LifecycleException; @@ -61,9 +61,9 @@ public static void main(String[] args) throws LifecycleException, IOException { gauge.labelValues("outside").set(27.0); if (mode == Mode.error) { - Collector failingCollector = () -> { + Collector failingCollector = CollectorBuilder.fromMetrics(() -> { throw new RuntimeException("Simulating an error."); - }; + }); PrometheusRegistry.defaultRegistry.register(failingCollector); } diff --git a/prometheus-metrics-core/pom.xml b/prometheus-metrics-core/pom.xml index 7773301d0..9576bab78 100644 --- a/prometheus-metrics-core/pom.xml +++ b/prometheus-metrics-core/pom.xml @@ -19,6 +19,18 @@ io.prometheus.metrics.core + + + + org.apache.maven.plugins + maven-compiler-plugin + + 16 + 16 + + + + diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Metric.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Metric.java index b7db83208..b10e4e932 100644 --- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Metric.java +++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Metric.java @@ -2,6 +2,7 @@ import io.prometheus.metrics.config.PrometheusProperties; import io.prometheus.metrics.model.registry.Collector; +import io.prometheus.metrics.model.registry.CollectorBuilder; import io.prometheus.metrics.model.registry.PrometheusRegistry; import io.prometheus.metrics.model.snapshots.Label; import io.prometheus.metrics.model.snapshots.Labels; @@ -21,9 +22,6 @@ protected Metric(Builder builder) { this.constLabels = builder.constLabels; } - @Override - public abstract MetricSnapshot collect(); - protected static abstract class Builder, M extends Metric> { protected final List illegalLabelNames; diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/MetricWithFixedMetadata.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/MetricWithFixedMetadata.java index 9d39593eb..27fcd4a62 100644 --- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/MetricWithFixedMetadata.java +++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/MetricWithFixedMetadata.java @@ -1,13 +1,15 @@ package io.prometheus.metrics.core.metrics; import io.prometheus.metrics.config.PrometheusProperties; -import io.prometheus.metrics.model.snapshots.Labels; -import io.prometheus.metrics.model.snapshots.MetricMetadata; -import io.prometheus.metrics.model.snapshots.PrometheusNaming; -import io.prometheus.metrics.model.snapshots.Unit; +import io.prometheus.metrics.model.registry.Collector; +import io.prometheus.metrics.model.registry.CollectorBuilder; +import io.prometheus.metrics.model.registry.PrometheusRegistry; +import io.prometheus.metrics.model.registry.PrometheusScrapeRequest; +import io.prometheus.metrics.model.snapshots.*; import java.util.Arrays; import java.util.List; +import java.util.function.Predicate; /** * Almost all metrics have fixed metadata, i.e. the metric name is known when the metric is created. @@ -30,6 +32,15 @@ protected MetricMetadata getMetadata() { return metadata; } + protected abstract MetricSnapshot collect(); + + public MetricSnapshots collect(Predicate includedNames, PrometheusScrapeRequest scrapeRequest) { + if(includedNames.test(this.metadata.getPrometheusName())) + return MetricSnapshots.of(collect()); + else + return MetricSnapshots.empty(); + } + private String makeName(String name, Unit unit) { if (unit != null) { if (!name.endsWith(unit.toString())) { @@ -39,11 +50,6 @@ private String makeName(String name, Unit unit) { return name; } - @Override - public String getPrometheusName() { - return metadata.getPrometheusName(); - } - public static abstract class Builder, M extends MetricWithFixedMetadata> extends Metric.Builder { protected String name; diff --git a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/NameFilterTest.java b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/NameFilterTest.java new file mode 100644 index 000000000..836a1a26f --- /dev/null +++ b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/NameFilterTest.java @@ -0,0 +1,61 @@ +package io.prometheus.metrics.core.metrics; + +import io.prometheus.metrics.model.registry.MetricNameFilter; +import io.prometheus.metrics.model.registry.PrometheusRegistry; +import org.junit.Assert; +import org.junit.Test; + +import java.util.concurrent.atomic.AtomicInteger; + +public class NameFilterTest { + @Test + public void testCounterWithCallback() { + AtomicInteger accessCount = new AtomicInteger(); + CounterWithCallback metrics = CounterWithCallback.builder() + .name("my_counter") + .callback(cb -> { + accessCount.incrementAndGet(); + cb.call(1.0); + }) + .build(); + + + PrometheusRegistry registry = new PrometheusRegistry(); + registry.register(metrics); + + var result1 = registry.scrape(MetricNameFilter.builder().nameMustBeEqualTo("XXX").build()); + Assert.assertEquals(accessCount.get(), 0); + Assert.assertTrue(result1.stream().toList().isEmpty()); + + var result2 = registry.scrape(MetricNameFilter.builder().nameMustBeEqualTo("my_counter").build()); + Assert.assertEquals(accessCount.get(), 1); + Assert.assertEquals(result2.stream().toList().size(), 1); + Assert.assertEquals(result2.get(0).getMetadata().getPrometheusName(), "my_counter"); + } + + @Test + public void testGaugeWithCallback() { + AtomicInteger accessCount = new AtomicInteger(); + GaugeWithCallback metrics = GaugeWithCallback.builder() + .name("my_gauge") + .callback(cb -> { + accessCount.incrementAndGet(); + cb.call(1.0); + }) + .build(); + + + PrometheusRegistry registry = new PrometheusRegistry(); + registry.register(metrics); + + var result1 = registry.scrape(MetricNameFilter.builder().nameMustBeEqualTo("XXX").build()); + Assert.assertEquals(accessCount.get(), 0); + Assert.assertTrue(result1.stream().toList().isEmpty()); + + var result2 = registry.scrape(MetricNameFilter.builder().nameMustBeEqualTo("my_gauge").build()); + Assert.assertEquals(accessCount.get(), 1); + Assert.assertEquals(result2.stream().toList().size(), 1); + Assert.assertEquals(result2.get(0).getMetadata().getPrometheusName(), "my_gauge"); + } + +} diff --git a/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusScrapeHandler.java b/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusScrapeHandler.java index 5155457df..6260861a3 100644 --- a/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusScrapeHandler.java +++ b/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusScrapeHandler.java @@ -4,6 +4,7 @@ import io.prometheus.metrics.config.PrometheusProperties; import io.prometheus.metrics.expositionformats.ExpositionFormatWriter; import io.prometheus.metrics.expositionformats.ExpositionFormats; +import io.prometheus.metrics.model.registry.Collector; import io.prometheus.metrics.model.registry.MetricNameFilter; import io.prometheus.metrics.model.registry.PrometheusRegistry; import io.prometheus.metrics.model.snapshots.MetricSnapshots; @@ -23,7 +24,7 @@ */ public class PrometheusScrapeHandler { - private final PrometheusRegistry registry; + private final Collector registry; private final ExpositionFormats expositionFormats; private final Predicate nameFilter; private AtomicInteger lastResponseSize = new AtomicInteger(2 << 9); // 0.5 MB @@ -32,7 +33,7 @@ public PrometheusScrapeHandler() { this(PrometheusProperties.get(), PrometheusRegistry.defaultRegistry); } - public PrometheusScrapeHandler(PrometheusRegistry registry) { + public PrometheusScrapeHandler(Collector registry) { this(PrometheusProperties.get(), registry); } @@ -40,7 +41,7 @@ public PrometheusScrapeHandler(PrometheusProperties config) { this(config, PrometheusRegistry.defaultRegistry); } - public PrometheusScrapeHandler(PrometheusProperties config, PrometheusRegistry registry) { + public PrometheusScrapeHandler(PrometheusProperties config, Collector registry) { this.expositionFormats = ExpositionFormats.init(config.getExporterProperties()); this.registry = registry; this.nameFilter = makeNameFilter(config.getExporterFilterProperties()); @@ -106,15 +107,11 @@ private Predicate makeNameFilter(ExporterFilterProperties props) { private MetricSnapshots scrape(PrometheusHttpRequest request) { Predicate filter = makeNameFilter(request.getParameterValues("name[]")); - if (filter != null) { - return registry.scrape(filter, request); - } else { - return registry.scrape(request); - } + return registry.collect(filter, request); } private Predicate makeNameFilter(String[] includedNames) { - Predicate result = null; + Predicate result = MetricNameFilter.ALLOW_ALL; if (includedNames != null && includedNames.length > 0) { result = MetricNameFilter.builder().nameMustBeEqualTo(includedNames).build(); } diff --git a/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/MetricsHandler.java b/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/MetricsHandler.java index 3506ddd4b..6a4a56749 100644 --- a/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/MetricsHandler.java +++ b/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/MetricsHandler.java @@ -4,6 +4,7 @@ import com.sun.net.httpserver.HttpHandler; import io.prometheus.metrics.config.PrometheusProperties; import io.prometheus.metrics.exporter.common.PrometheusScrapeHandler; +import io.prometheus.metrics.model.registry.Collector; import io.prometheus.metrics.model.registry.PrometheusRegistry; import java.io.ByteArrayOutputStream; @@ -26,7 +27,7 @@ public MetricsHandler() { prometheusScrapeHandler = new PrometheusScrapeHandler(); } - public MetricsHandler(PrometheusRegistry registry) { + public MetricsHandler(Collector registry) { prometheusScrapeHandler = new PrometheusScrapeHandler(registry); } diff --git a/prometheus-metrics-instrumentation-jvm/pom.xml b/prometheus-metrics-instrumentation-jvm/pom.xml index f55dd9c6f..38f1e3231 100644 --- a/prometheus-metrics-instrumentation-jvm/pom.xml +++ b/prometheus-metrics-instrumentation-jvm/pom.xml @@ -19,6 +19,18 @@ io.prometheus.metrics.instrumentation.jvm + + + + org.apache.maven.plugins + maven-compiler-plugin + + 10 + 10 + + + + diff --git a/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmBufferPoolMetrics.java b/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmBufferPoolMetrics.java index 9c8cddb46..f7a8631b7 100644 --- a/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmBufferPoolMetrics.java +++ b/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmBufferPoolMetrics.java @@ -2,6 +2,8 @@ import io.prometheus.metrics.config.PrometheusProperties; import io.prometheus.metrics.core.metrics.GaugeWithCallback; +import io.prometheus.metrics.model.registry.Collector; +import io.prometheus.metrics.model.registry.CollectorBuilder; import io.prometheus.metrics.model.registry.PrometheusRegistry; import io.prometheus.metrics.model.snapshots.Unit; @@ -40,17 +42,10 @@ public class JvmBufferPoolMetrics { private static final String JVM_BUFFER_POOL_CAPACITY_BYTES = "jvm_buffer_pool_capacity_bytes"; private static final String JVM_BUFFER_POOL_USED_BUFFERS = "jvm_buffer_pool_used_buffers"; - private final PrometheusProperties config; - private final List bufferPoolBeans; + private static Collector build(List bufferPoolBeans, PrometheusProperties config) { + var builder = CollectorBuilder.compositeBuilder(); - private JvmBufferPoolMetrics(List bufferPoolBeans, PrometheusProperties config) { - this.config = config; - this.bufferPoolBeans = bufferPoolBeans; - } - - private void register(PrometheusRegistry registry) { - - GaugeWithCallback.builder(config) + builder.add(GaugeWithCallback.builder(config) .name(JVM_BUFFER_POOL_USED_BYTES) .help("Used bytes of a given JVM buffer pool.") .unit(Unit.BYTES) @@ -60,9 +55,9 @@ private void register(PrometheusRegistry registry) { callback.call(pool.getMemoryUsed(), pool.getName()); } }) - .register(registry); + .build()); - GaugeWithCallback.builder(config) + builder.add(GaugeWithCallback.builder(config) .name(JVM_BUFFER_POOL_CAPACITY_BYTES) .help("Bytes capacity of a given JVM buffer pool.") .unit(Unit.BYTES) @@ -72,9 +67,9 @@ private void register(PrometheusRegistry registry) { callback.call(pool.getTotalCapacity(), pool.getName()); } }) - .register(registry); + .build()); - GaugeWithCallback.builder(config) + builder.add(GaugeWithCallback.builder(config) .name(JVM_BUFFER_POOL_USED_BUFFERS) .help("Used buffers of a given JVM buffer pool.") .labelNames("pool") @@ -83,7 +78,9 @@ private void register(PrometheusRegistry registry) { callback.call(pool.getCount(), pool.getName()); } }) - .register(registry); + .build()); + + return builder.build(); } public static Builder builder() { @@ -111,16 +108,16 @@ Builder bufferPoolBeans(List bufferPoolBeans) { return this; } + public Collector build() { + return JvmBufferPoolMetrics.build(bufferPoolBeans, config); + } + public void register() { - register(PrometheusRegistry.defaultRegistry); + PrometheusRegistry.defaultRegistry.register(build()); } public void register(PrometheusRegistry registry) { - List bufferPoolBeans = this.bufferPoolBeans; - if (bufferPoolBeans == null) { - bufferPoolBeans = ManagementFactory.getPlatformMXBeans(BufferPoolMXBean.class); - } - new JvmBufferPoolMetrics(bufferPoolBeans, config).register(registry); + registry.register(build()); } } } diff --git a/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/ProcessMetricsTest.java b/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/ProcessMetricsTest.java index 92f286e01..b382537d6 100644 --- a/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/ProcessMetricsTest.java +++ b/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/ProcessMetricsTest.java @@ -16,9 +16,7 @@ import static io.prometheus.metrics.instrumentation.jvm.TestUtil.convertToOpenMetricsFormat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; public class ProcessMetricsTest { @@ -42,8 +40,8 @@ public void setUp() throws IOException { public void testGoodCase() throws IOException { PrometheusRegistry registry = new PrometheusRegistry(); ProcessMetrics.builder() - .osBean(sunOsBean) - .runtimeBean(runtimeBean) + .osBean(sunOsBean) + .runtimeBean(runtimeBean) .grepper(linuxGrepper) .register(registry); MetricSnapshots snapshots = registry.scrape(); diff --git a/prometheus-metrics-model/pom.xml b/prometheus-metrics-model/pom.xml index f45d5b6fb..c69ffbfaa 100644 --- a/prometheus-metrics-model/pom.xml +++ b/prometheus-metrics-model/pom.xml @@ -19,6 +19,18 @@ io.prometheus.metrics.model + + + + org.apache.maven.plugins + maven-compiler-plugin + + 16 + 16 + + + + diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/Collector.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/Collector.java index 0c69a89a9..e268e73f5 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/Collector.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/Collector.java @@ -1,76 +1,25 @@ package io.prometheus.metrics.model.registry; -import io.prometheus.metrics.model.snapshots.MetricSnapshot; +import io.prometheus.metrics.model.snapshots.MetricSnapshots; import java.util.function.Predicate; -import static io.prometheus.metrics.model.snapshots.PrometheusNaming.prometheusName; - /** - * To be registered with the Prometheus collector registry. - * See Overall Structure on - * https://prometheus.io/docs/instrumenting/writing_clientlibs/. + * Basic interface for fetching metrics. + * Accepts name filter (allowing to avoid fetching of unnecessary data) + * as well as scrape request (for multi target). + * + * Used to be named MultiCollector in v1.1. */ @FunctionalInterface public interface Collector { - /** * Called when the Prometheus server scrapes metrics. - */ - MetricSnapshot collect(); - - /** - * Provides Collector with the details of the request issued by Prometheus to allow multi-target pattern implementation - * Override to implement request dependent logic to provide MetricSnapshot - */ - default MetricSnapshot collect(PrometheusScrapeRequest scrapeRequest) { - return collect(); - } - - /** - * Like {@link #collect()}, but returns {@code null} if {@code includedNames.test(name)} is {@code false}. - *

- * Override this if there is a more efficient way than first collecting the snapshot and then discarding it. - */ - default MetricSnapshot collect(Predicate includedNames) { - MetricSnapshot result = collect(); - if (includedNames.test(result.getMetadata().getPrometheusName())) { - return result; - } else { - return null; - } - } - - /** - * Like {@link #collect(Predicate)}, but with support for multi-target pattern. - *

- * Override this if there is a more efficient way than first collecting the snapshot and then discarding it. - */ - default MetricSnapshot collect(Predicate includedNames, PrometheusScrapeRequest scrapeRequest) { - MetricSnapshot result = collect(scrapeRequest); - if (includedNames.test(result.getMetadata().getPrometheusName())) { - return result; - } else { - return null; - } - } - - - /** - * This is called in two places: - *

    - *
  1. During registration to check if a metric with that name already exists.
  2. - *
  3. During scrape to check if this collector can be skipped because a name filter is present and the metric name is excluded.
  4. - *
- * Returning {@code null} means checks are omitted (registration the metric always succeeds), - * and the collector is always scraped (the result is dropped after scraping if a name filter is present and - * the metric name is excluded). - *

- * If your metric has a name that does not change at runtime it is a good idea to overwrite this and return the name. *

- * All metrics in {@code prometheus-metrics-core} override this. + * Should return only the snapshots where {@code includedNames.test(name)} is {@code true}. + * + * @param includedNames prometheusName filter (non-null, use MetricNameFilter.ALLOW_ALL to disable filtering) + * @param scrapeRequest null allowed */ - default String getPrometheusName() { - return null; - } + MetricSnapshots collect(Predicate includedNames, PrometheusScrapeRequest scrapeRequest); } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/CollectorBuilder.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/CollectorBuilder.java new file mode 100644 index 000000000..01d44201f --- /dev/null +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/CollectorBuilder.java @@ -0,0 +1,130 @@ +package io.prometheus.metrics.model.registry; + +import io.prometheus.metrics.model.snapshots.Labels; +import io.prometheus.metrics.model.snapshots.MetricSnapshot; +import io.prometheus.metrics.model.snapshots.MetricSnapshots; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.function.Predicate; +import java.util.function.Supplier; + +public class CollectorBuilder { + /** + * Assembles collector from metric snapshot supplier. + */ + public static Collector fromMetric(Supplier collector) { + return new Collector() { + @Override + public MetricSnapshots collect(Predicate includedNames, PrometheusScrapeRequest scrapeRequest) { + MetricSnapshot result = collector.get(); + if (result.matches(includedNames)) + return MetricSnapshots.of(result); + else + return MetricSnapshots.empty(); + } + }; + } + + /** + * Assembles collector from metric snapshot supplier. + */ + public static Collector fromMetrics(Supplier collector) { + return (includedNames, scrapeRequest) -> collector.get().filter(includedNames); + } + + /** + * Applies name filter over result of another collector. + * Useful if one suspects underlying collector does not follow filtering spec. + * Required mainly for historical reasons. + */ + public static Collector filtered(Collector collector) { + return new Collector() { + @Override + public MetricSnapshots collect(Predicate includedNames, PrometheusScrapeRequest scrapeRequest) { + return collector.collect(includedNames, scrapeRequest).filter(includedNames); + } + }; + } + + /** Applies additional labels to all data points. */ + public static Collector withLabels(Collector collector, Labels labels) { + return new Collector() { + @Override + public MetricSnapshots collect(Predicate includedNames, PrometheusScrapeRequest scrapeRequest) { + return collector.collect(includedNames, scrapeRequest).withLabels(labels); + } + }; + } + + /** Applies name prefix. */ + public static Collector withNamePrefix(Collector collector, String prefix) { + return new Collector() { + @Override + public MetricSnapshots collect(Predicate includedNames, PrometheusScrapeRequest scrapeRequest) { + return collector.collect(name -> includedNames.test(prefix + name), scrapeRequest).withNamePrefix(prefix); + } + }; + } + + /** Constructs composite collector returning all the metrics from underling collectors. */ + public static Collector composite(Collector... collectors) { + return new Collector() { + @Override + public MetricSnapshots collect(Predicate includedNames, PrometheusScrapeRequest scrapeRequest) { + MetricSnapshots.Builder result = MetricSnapshots.builder(); + for (Collector collector : collectors) { + result.metricSnapshots(collector.collect(includedNames, scrapeRequest)); + } + return result.build(); + } + }; + } + + public static CompositeCollector.Builder compositeBuilder() { + return CompositeCollector.builder(); + } + + static class CompositeCollector implements Collector { + private final Collection collectors; + + private CompositeCollector(Collection collectors) { + this.collectors = collectors; + } + + @Override + public MetricSnapshots collect(Predicate includedNames, PrometheusScrapeRequest scrapeRequest) { + MetricSnapshots.Builder result = MetricSnapshots.builder(); + for (Collector collector : collectors) { + result.metricSnapshots(collector.collect(includedNames, scrapeRequest)); + } + return result.build(); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private final List collectors = new ArrayList<>(); + + private Builder() { + } + + public Builder add(Collector collector) { + collectors.add(collector); + return this; + } + + public Builder add(Iterable collectors) { + collectors.forEach(Builder.this.collectors::add); + return this; + } + + public Collector build() { + return new CompositeCollector(collectors); + } + } + } +} diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/MultiCollector.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/MultiCollector.java deleted file mode 100644 index 5434c0ec0..000000000 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/MultiCollector.java +++ /dev/null @@ -1,71 +0,0 @@ -package io.prometheus.metrics.model.registry; - -import io.prometheus.metrics.model.snapshots.MetricSnapshot; -import io.prometheus.metrics.model.snapshots.MetricSnapshots; - -import java.util.Collections; -import java.util.List; -import java.util.function.Predicate; - -/** - * Like {@link Collector}, but collecting multiple Snapshots at once. - */ -@FunctionalInterface -public interface MultiCollector { - - /** - * Called when the Prometheus server scrapes metrics. - */ - MetricSnapshots collect(); - - /** - * Provides Collector with the details of the request issued by Prometheus to allow multi-target pattern implementation - * Override to implement request dependent logic to provide MetricSnapshot - */ - default MetricSnapshots collect(PrometheusScrapeRequest scrapeRequest) { - return collect(); - } - - - /** - * Like {@link #collect()}, but returns only the snapshots where {@code includedNames.test(name)} is {@code true}. - *

- * Override this if there is a more efficient way than first collecting all snapshot and then discarding the excluded ones. - */ - default MetricSnapshots collect(Predicate includedNames) { - return collect(includedNames, null); - } - - /** - * Like {@link #collect(Predicate)}, but with support for multi-target pattern. - *

- * Override this if there is a more efficient way than first collecting the snapshot and then discarding it. - */ - default MetricSnapshots collect(Predicate includedNames, PrometheusScrapeRequest scrapeRequest) { - MetricSnapshots allSnapshots = scrapeRequest == null ? collect(): collect(scrapeRequest); - MetricSnapshots.Builder result = MetricSnapshots.builder(); - for (MetricSnapshot snapshot : allSnapshots) { - if (includedNames.test(snapshot.getMetadata().getPrometheusName())) { - result.metricSnapshot(snapshot); - } - } - return result.build(); - } - - - /** - * This is called in two places: - *

    - *
  1. During registration to check if a metric with that name already exists.
  2. - *
  3. During scrape to check if the collector can be skipped because a name filter is present and all names are excluded.
  4. - *
- * Returning an empty list means checks are omitted (registration metric always succeeds), - * and the collector is always scraped (if a name filter is present and all names are excluded the result is dropped). - *

- * If your collector returns a constant list of metrics that have names that do not change at runtime - * it is a good idea to overwrite this and return the names. - */ - default List getPrometheusNames() { - return Collections.emptyList(); - } -} diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/PrometheusRegistry.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/PrometheusRegistry.java index 8b059adb3..76ef76e12 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/PrometheusRegistry.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/PrometheusRegistry.java @@ -1,129 +1,49 @@ package io.prometheus.metrics.model.registry; -import static io.prometheus.metrics.model.snapshots.PrometheusNaming.prometheusName; - import java.util.List; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Predicate; -import io.prometheus.metrics.model.snapshots.MetricSnapshot; import io.prometheus.metrics.model.snapshots.MetricSnapshots; -public class PrometheusRegistry { +/** Collector that allows dynamic registration/unregistration of scrape sources. + * + * Consider to use CollectorBuilder.CompositeCollector if no dynamic registration is needed. + */ +public class PrometheusRegistry implements Collector { public static final PrometheusRegistry defaultRegistry = new PrometheusRegistry(); - private final Set prometheusNames = ConcurrentHashMap.newKeySet(); private final List collectors = new CopyOnWriteArrayList<>(); - private final List multiCollectors = new CopyOnWriteArrayList<>(); public void register(Collector collector) { - String prometheusName = collector.getPrometheusName(); - if (prometheusName != null) { - if (!prometheusNames.add(prometheusName)) { - throw new IllegalStateException("Can't register " + prometheusName + " because a metric with that name is already registered."); - } - } collectors.add(collector); } - public void register(MultiCollector collector) { - for (String prometheusName : collector.getPrometheusNames()) { - if (!prometheusNames.add(prometheusName)) { - throw new IllegalStateException("Can't register " + prometheusName + " because that name is already registered."); - } - } - multiCollectors.add(collector); - } - public void unregister(Collector collector) { collectors.remove(collector); - String prometheusName = collector.getPrometheusName(); - if (prometheusName != null) { - prometheusNames.remove(collector.getPrometheusName()); - } } - public void unregister(MultiCollector collector) { - multiCollectors.remove(collector); - for (String prometheusName : collector.getPrometheusNames()) { - prometheusNames.remove(prometheusName(prometheusName)); - } + /** Use collect() instead. */ + public MetricSnapshots scrape(Predicate includedNames) { + return collect(includedNames, null); } + /** Use collect() instead. */ public MetricSnapshots scrape() { - return scrape((PrometheusScrapeRequest) null); - } - - public MetricSnapshots scrape(PrometheusScrapeRequest scrapeRequest) { - MetricSnapshots.Builder result = MetricSnapshots.builder(); - for (Collector collector : collectors) { - MetricSnapshot snapshot = scrapeRequest == null ? collector.collect() : collector.collect(scrapeRequest); - if (snapshot != null) { - if (result.containsMetricName(snapshot.getMetadata().getName())) { - throw new IllegalStateException(snapshot.getMetadata().getPrometheusName() + ": duplicate metric name."); - } - result.metricSnapshot(snapshot); - } - } - for (MultiCollector collector : multiCollectors) { - MetricSnapshots snaphots = scrapeRequest == null ? collector.collect() : collector.collect(scrapeRequest); - for (MetricSnapshot snapshot : snaphots) { - if (result.containsMetricName(snapshot.getMetadata().getName())) { - throw new IllegalStateException(snapshot.getMetadata().getPrometheusName() + ": duplicate metric name."); - } - result.metricSnapshot(snapshot); - } - } - return result.build(); + return collect(MetricNameFilter.ALLOW_ALL, null); } - public MetricSnapshots scrape(Predicate includedNames) { - if (includedNames == null) { - return scrape(); - } - return scrape(includedNames, null); + /** Use collect() instead. */ + public MetricSnapshots scrape(Predicate includedNames, PrometheusScrapeRequest scrapeRequest) { + return collect(includedNames, scrapeRequest); } - public MetricSnapshots scrape(Predicate includedNames, PrometheusScrapeRequest scrapeRequest) { - if (includedNames == null) { - return scrape(scrapeRequest); - } + public MetricSnapshots collect(Predicate includedNames, PrometheusScrapeRequest scrapeRequest) { MetricSnapshots.Builder result = MetricSnapshots.builder(); for (Collector collector : collectors) { - String prometheusName = collector.getPrometheusName(); - // prometheusName == null means the name is unknown, and we have to scrape to learn the name. - // prometheusName != null means we can skip the scrape if the name is excluded. - if (prometheusName == null || includedNames.test(prometheusName)) { - MetricSnapshot snapshot = scrapeRequest == null ? collector.collect(includedNames) : collector.collect(includedNames, scrapeRequest); - if (snapshot != null) { - result.metricSnapshot(snapshot); - } - } - } - for (MultiCollector collector : multiCollectors) { - List prometheusNames = collector.getPrometheusNames(); - // empty prometheusNames means the names are unknown, and we have to scrape to learn the names. - // non-empty prometheusNames means we can exclude the collector if all names are excluded by the filter. - boolean excluded = !prometheusNames.isEmpty(); - for (String prometheusName : prometheusNames) { - if (includedNames.test(prometheusName)) { - excluded = false; - break; - } - } - if (!excluded) { - MetricSnapshots snapshots = scrapeRequest == null ? collector.collect(includedNames) : collector.collect(includedNames, scrapeRequest); - for (MetricSnapshot snapshot : snapshots) { - if (snapshot != null) { - result.metricSnapshot(snapshot); - } - } - } + result.metricSnapshots(collector.collect(includedNames, scrapeRequest)); } return result.build(); } - } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/PrometheusScrapeRequest.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/PrometheusScrapeRequest.java index e8651292e..243fc5f33 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/PrometheusScrapeRequest.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/PrometheusScrapeRequest.java @@ -4,7 +4,6 @@ * Infos extracted from the request received by the endpoint */ public interface PrometheusScrapeRequest { - /** * Absolute path of the HTTP request. */ diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/CounterSnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/CounterSnapshot.java index e62aeb9f8..361894bcb 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/CounterSnapshot.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/CounterSnapshot.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.stream.Collectors; /** * Immutable snapshot of a Counter. @@ -26,6 +27,35 @@ public List getDataPoints() { return (List) dataPoints; } + @Override + public CounterSnapshot merge(MetricSnapshot snapshot) { + if (!(getMetadata().equals(snapshot.getMetadata()))) + throw new IllegalArgumentException("Unable to merge - metadata mismatch for metric " + this.getMetadata().getPrometheusName()); + if (snapshot instanceof CounterSnapshot s) { + var result = new ArrayList(); + result.addAll(this.getDataPoints()); + result.addAll(s.getDataPoints()); + return new CounterSnapshot(getMetadata(), result); + } else { + throw new IllegalArgumentException("Unable to merge - invalid type of metric " + this.getMetadata().getPrometheusName()); + } + } + + + @Override + public CounterSnapshot withNamePrefix(String prefix) { + return new CounterSnapshot(getMetadata().withNamePrefix(prefix), getDataPoints()); + } + + /** Merge additional labels to all the data points. */ + public CounterSnapshot withLabels(Labels labels) { + var points = getDataPoints() + .stream() + .map(point -> new CounterSnapshot.CounterDataPointSnapshot(point.value, point.getLabels().merge(labels), point.exemplar, point.getCreatedTimestampMillis(), point.getScrapeTimestampMillis())) + .collect(Collectors.toList()); + return new CounterSnapshot(getMetadata(), points); + } + public static class CounterDataPointSnapshot extends DataPointSnapshot { private final double value; diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/GaugeSnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/GaugeSnapshot.java index f69b8d2a6..4af1c29d8 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/GaugeSnapshot.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/GaugeSnapshot.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.stream.Collectors; /** * Immutable snapshot of a Gauge. @@ -25,6 +26,34 @@ public List getDataPoints() { return (List) dataPoints; } + @Override + public GaugeSnapshot merge(MetricSnapshot snapshot) { + if (!(getMetadata().equals(snapshot.getMetadata()))) + throw new IllegalArgumentException("Unable to merge - metadata mismatch."); + if (snapshot instanceof GaugeSnapshot s) { + var result = new ArrayList(); + result.addAll(this.getDataPoints()); + result.addAll(s.getDataPoints()); + return new GaugeSnapshot(getMetadata(), result); + } else { + throw new IllegalArgumentException("Unable to merge - invalid snapshot type"); + } + } + + @Override + public GaugeSnapshot withNamePrefix(String prefix) { + return new GaugeSnapshot(getMetadata().withNamePrefix(prefix), getDataPoints()); + } + + /** Merge additional labels to all the data points. */ + public GaugeSnapshot withLabels(Labels labels) { + var points = getDataPoints() + .stream() + .map(point -> new GaugeSnapshot.GaugeDataPointSnapshot(point.value, point.getLabels().merge(labels), point.exemplar, point.getScrapeTimestampMillis())) + .collect(Collectors.toList()); + return new GaugeSnapshot(getMetadata(), points); + } + public static final class GaugeDataPointSnapshot extends DataPointSnapshot { private final double value; diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/HistogramSnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/HistogramSnapshot.java index 2e66c1a25..dcaf11571 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/HistogramSnapshot.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/HistogramSnapshot.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.stream.Collectors; /** * Immutable snapshot of a Histogram. @@ -38,6 +39,48 @@ public boolean isGaugeHistogram() { return gaugeHistogram; } + + @Override + public HistogramSnapshot merge(MetricSnapshot snapshot) { + if (!(getMetadata().equals(snapshot.getMetadata()))) + throw new IllegalArgumentException("Unable to merge - metadata mismatch."); + if (snapshot instanceof HistogramSnapshot s) { + var result = new ArrayList(); + result.addAll(this.getDataPoints()); + result.addAll(s.getDataPoints()); + return new HistogramSnapshot(gaugeHistogram, getMetadata(), result); + } else { + throw new IllegalArgumentException("Unable to merge - invalid snapshot type"); + } + } + + @Override + public HistogramSnapshot withNamePrefix(String prefix) { + return new HistogramSnapshot(getMetadata().withNamePrefix(prefix), getDataPoints()); + } + + /** + * Merge additional labels to all the data points. + */ + public HistogramSnapshot withLabels(Labels labels) { + var points = getDataPoints() + .stream() + .map(point -> new HistogramSnapshot.HistogramDataPointSnapshot( + point.classicBuckets, + point.nativeSchema, + point.nativeZeroCount, + point.nativeZeroThreshold, + point.nativeBucketsForPositiveValues, + point.nativeBucketsForNegativeValues, + point.getSum(), + point.getLabels().merge(labels), + point.getExemplars(), + point.getCreatedTimestampMillis(), + point.getScrapeTimestampMillis())) + .collect(Collectors.toList()); + return new HistogramSnapshot(getMetadata(), points); + } + @Override public List getDataPoints() { return (List) dataPoints; diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/InfoSnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/InfoSnapshot.java index e24747181..fd7b03207 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/InfoSnapshot.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/InfoSnapshot.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.stream.Collectors; /** * Immutable snapshot of an Info metric. @@ -30,6 +31,35 @@ public List getDataPoints() { return (List) dataPoints; } + @Override + public InfoSnapshot merge(MetricSnapshot snapshot) { + if (!(getMetadata().equals(snapshot.getMetadata()))) + throw new IllegalArgumentException("Unable to merge - metadata mismatch."); + if (snapshot instanceof InfoSnapshot s) { + var result = new ArrayList(); + result.addAll(this.getDataPoints()); + result.addAll(s.getDataPoints()); + return new InfoSnapshot(getMetadata(), result); + } else { + throw new IllegalArgumentException("Unable to merge - invalid snapshot type"); + } + } + + @Override + public InfoSnapshot withNamePrefix(String prefix) { + return new InfoSnapshot(getMetadata().withNamePrefix(prefix), getDataPoints()); + } + + /** Merge additional labels to all the data points. */ + public MetricSnapshot withLabels(Labels labels) { + var points = getDataPoints() + .stream() + .map(point -> new InfoDataPointSnapshot(point.getLabels().merge(labels), point.getScrapeTimestampMillis())) + .collect(Collectors.toList()); + return new InfoSnapshot(getMetadata(), points); + } + + public static class InfoDataPointSnapshot extends DataPointSnapshot { /** diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java index 366805ddf..c1ddf5fec 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java @@ -1,5 +1,7 @@ package io.prometheus.metrics.model.snapshots; +import java.util.Objects; + /** * Immutable container for metric metadata: name, help, unit. */ @@ -96,6 +98,10 @@ public Unit getUnit() { return unit; } + public MetricMetadata withNamePrefix(String prefix) { + return new MetricMetadata(prefix + name, help, unit); + } + private void validate() { if (name == null) { throw new IllegalArgumentException("Missing required field: name is null"); @@ -106,4 +112,17 @@ private void validate() { + " Call " + PrometheusNaming.class.getSimpleName() + ".sanitizeMetricName(name) to avoid this error."); } } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MetricMetadata that = (MetricMetadata) o; + return Objects.equals(name, that.name) && Objects.equals(prometheusName, that.prometheusName) && Objects.equals(help, that.help) && Objects.equals(unit, that.unit); + } + + @Override + public int hashCode() { + return Objects.hash(prometheusName); + } } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricSnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricSnapshot.java index 273b304eb..ac100e8d1 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricSnapshot.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricSnapshot.java @@ -6,6 +6,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; +import java.util.function.Predicate; /** * Base class for metric snapshots. @@ -33,12 +34,24 @@ protected MetricSnapshot(MetricMetadata metadata, Collection nameFilter) { + return nameFilter.test(getMetadata().getPrometheusName()); + } + public MetricMetadata getMetadata() { return metadata; } public abstract List getDataPoints(); + public abstract MetricSnapshot withNamePrefix(String prefix); + + /** Merge additional labels to all the data points. */ + public abstract MetricSnapshot withLabels(Labels labels); + protected void validateLabels() { // Verify that labels are unique (the same set of names/values must not be used multiple times for the same metric). for (int i = 0; i < dataPoints.size() - 1; i++) { diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricSnapshots.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricSnapshots.java index 6fcf3fd25..408e0531e 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricSnapshots.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricSnapshots.java @@ -1,55 +1,65 @@ package io.prometheus.metrics.model.snapshots; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; +import java.util.*; +import java.util.function.Predicate; +import java.util.stream.Collectors; import java.util.stream.Stream; -import static io.prometheus.metrics.model.snapshots.PrometheusNaming.prometheusName; -import static java.util.Collections.unmodifiableList; -import static java.util.Comparator.comparing; - /** * Immutable list of metric snapshots. + * Guaranteed entries have unique metric names. */ public class MetricSnapshots implements Iterable { private final List snapshots; /** - * See {@link #MetricSnapshots(Collection)} + * To create MetricSnapshots, use builder that takes care of all validations. */ - public MetricSnapshots(MetricSnapshot... snapshots) { - this(Arrays.asList(snapshots)); + private MetricSnapshots(Collection snapshots) { + this.snapshots = Collections.unmodifiableList(new ArrayList<>(snapshots)); } /** - * To create MetricSnapshots, you can either call the constructor directly - * or use {@link #builder()}. - * - * @param snapshots the constructor creates a sorted copy of snapshots. - * @throws IllegalArgumentException if snapshots contains duplicate metric names. - * To avoid duplicate metric names use {@link #builder()} and check - * {@link Builder#containsMetricName(String)} before calling - * {@link Builder#metricSnapshot(MetricSnapshot)}. + * TODO: just for compatibility */ - public MetricSnapshots(Collection snapshots) { - List list = new ArrayList<>(snapshots); - list.sort(comparing(s -> s.getMetadata().getPrometheusName())); - for (int i = 0; i < snapshots.size() - 1; i++) { - if (list.get(i).getMetadata().getPrometheusName().equals(list.get(i + 1).getMetadata().getPrometheusName())) { - throw new IllegalArgumentException(list.get(i).getMetadata().getPrometheusName() + ": duplicate metric name"); - } - } - this.snapshots = unmodifiableList(list); + public MetricSnapshots(MetricSnapshot... snapshots) { + this.snapshots = builder().metricSnapshots(snapshots).build().snapshots; + } + + public static MetricSnapshots empty() { + return new MetricSnapshots(Collections.emptyList()); } public static MetricSnapshots of(MetricSnapshot... snapshots) { - return new MetricSnapshots(snapshots); + return builder().metricSnapshots(snapshots).build(); } + public MetricSnapshots filter(Predicate nameFilter) { + var result = snapshots + .stream() + .filter(snapshot -> snapshot.matches(nameFilter)) + .collect(Collectors.toList()); + return new MetricSnapshots(result); + } + + public MetricSnapshots withLabels(Labels labels) { + var result = snapshots + .stream() + .map(snapshot -> snapshot.withLabels(labels)) + .collect(Collectors.toList()); + return new MetricSnapshots(result); + } + + public MetricSnapshots withNamePrefix(String prefix) { + var result = snapshots + .stream() + .map(snapshot -> snapshot.withNamePrefix(prefix)) + .collect(Collectors.toList()); + return new MetricSnapshots(result); + } + + @Override public Iterator iterator() { return snapshots.iterator(); @@ -73,30 +83,38 @@ public static Builder builder() { public static class Builder { - private final List snapshots = new ArrayList<>(); + /** Used to merge metrics by prometheus names, and to produce correct output order. */ + private final Map result = new TreeMap<>(); private Builder() { } - public boolean containsMetricName(String name) { - for (MetricSnapshot snapshot : snapshots) { - if (snapshot.getMetadata().getPrometheusName().equals(prometheusName(name))) { - return true; - } - } - return false; - } - /** * Add a metric snapshot. Call multiple times to add multiple metric snapshots. */ public Builder metricSnapshot(MetricSnapshot snapshot) { - snapshots.add(snapshot); + result.merge(snapshot.getMetadata().getPrometheusName(), snapshot, MetricSnapshot::merge); + return this; + } + + /** + * Add a metric snapshot collection. + */ + public Builder metricSnapshots(Iterable snapshots) { + snapshots.forEach(this::metricSnapshot); + return this; + } + + /** + * Add a metric snapshot collection. + */ + public Builder metricSnapshots(MetricSnapshot[] snapshots) { + for (MetricSnapshot snapshot : snapshots) this.metricSnapshot(snapshot); return this; } public MetricSnapshots build() { - return new MetricSnapshots(snapshots); + return new MetricSnapshots(result.values()); } } } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/StateSetSnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/StateSetSnapshot.java index 2a6094354..4869bf84c 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/StateSetSnapshot.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/StateSetSnapshot.java @@ -1,11 +1,7 @@ package io.prometheus.metrics.model.snapshots; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; +import java.util.*; +import java.util.stream.Collectors; import java.util.stream.Stream; /** @@ -25,6 +21,34 @@ public StateSetSnapshot(MetricMetadata metadata, Collection(); + result.addAll(this.getDataPoints()); + result.addAll(s.getDataPoints()); + return new StateSetSnapshot(getMetadata(), result); + } else { + throw new IllegalArgumentException("Unable to merge - invalid snapshot type"); + } + } + + @Override + public StateSetSnapshot withNamePrefix(String prefix) { + return new StateSetSnapshot(getMetadata().withNamePrefix(prefix), getDataPoints()); + } + + /** Merge additional labels to all the data points. */ + public StateSetSnapshot withLabels(Labels labels) { + var points = getDataPoints() + .stream() + .map(point -> new StateSetSnapshot.StateSetDataPointSnapshot(point.names, point.values, point.getLabels().merge(labels), point.getScrapeTimestampMillis())) + .collect(Collectors.toList()); + return new StateSetSnapshot(getMetadata(), points); + } + private void validate() { if (getMetadata().hasUnit()) { throw new IllegalArgumentException("An state set metric cannot have a unit."); @@ -151,7 +175,8 @@ public static class Builder extends DataPointSnapshot.Builder { private final ArrayList names = new ArrayList<>(); private final ArrayList values = new ArrayList<>(); - private Builder() {} + private Builder() { + } /** * Add a state. Call multple times to add multiple states. diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SummarySnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SummarySnapshot.java index 16682dbae..cfc16f539 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SummarySnapshot.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SummarySnapshot.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.stream.Collectors; /** * Immutable snapshot of a Summary metric. @@ -20,6 +21,36 @@ public SummarySnapshot(MetricMetadata metadata, Collection(); + result.addAll(this.getDataPoints()); + result.addAll(s.getDataPoints()); + return new SummarySnapshot(getMetadata(), result); + } else { + throw new IllegalArgumentException("Unable to merge - invalid snapshot type"); + } + } + + @Override + public SummarySnapshot withNamePrefix(String prefix) { + return new SummarySnapshot(getMetadata().withNamePrefix(prefix), getDataPoints()); + } + + /** + * Merge additional labels to all the data points. + */ + public SummarySnapshot withLabels(Labels labels) { + var points = getDataPoints() + .stream() + .map(point -> new SummarySnapshot.SummaryDataPointSnapshot(point.getCount(), point.getSum(), point.getQuantiles(), point.getLabels().merge(labels), point.getExemplars(), point.getCreatedTimestampMillis(), point.getScrapeTimestampMillis())) + .collect(Collectors.toList()); + return new SummarySnapshot(getMetadata(), points); + } + @Override public List getDataPoints() { return (List) dataPoints; diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/UnknownSnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/UnknownSnapshot.java index b02228907..6195e29ed 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/UnknownSnapshot.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/UnknownSnapshot.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.stream.Collectors; /** * Immutable snapshot of an Unknown (Untyped) metric. @@ -21,6 +22,36 @@ public UnknownSnapshot(MetricMetadata metadata, Collection(); + result.addAll(this.getDataPoints()); + result.addAll(s.getDataPoints()); + return new UnknownSnapshot(getMetadata(), result); + } else { + throw new IllegalArgumentException("Unable to merge - invalid snapshot type"); + } + } + + + @Override + public UnknownSnapshot withNamePrefix(String prefix) { + return new UnknownSnapshot(getMetadata().withNamePrefix(prefix), getDataPoints()); + } + + /** Merge additional labels to all the data points. */ + public UnknownSnapshot withLabels(Labels labels) { + var points = getDataPoints() + .stream() + .map(point -> new UnknownSnapshot.UnknownDataPointSnapshot(point.value, point.getLabels().merge(labels), point.exemplar, point.getScrapeTimestampMillis())) + .collect(Collectors.toList()); + return new UnknownSnapshot(getMetadata(), points); + } + @Override public List getDataPoints() { return (List) dataPoints; diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/CollectorTransformationsTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/CollectorTransformationsTest.java new file mode 100644 index 000000000..e4ecaf829 --- /dev/null +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/CollectorTransformationsTest.java @@ -0,0 +1,54 @@ +package io.prometheus.metrics.model.registry; + +import io.prometheus.metrics.model.snapshots.CounterSnapshot; +import io.prometheus.metrics.model.snapshots.CounterSnapshot.CounterDataPointSnapshot; +import io.prometheus.metrics.model.snapshots.Labels; +import org.junit.Assert; +import org.junit.Test; + +public class CollectorTransformationsTest { + + @Test + public void testNamePrefix() { + var metrics = CollectorBuilder.fromMetric(() -> CounterSnapshot.builder() + .name("counter1") + .dataPoint(CounterDataPointSnapshot.builder() + .labels(Labels.of("path", "/hello")) + .value(1.0) + .build() + ) + .build() + ); + + var prefixed = CollectorBuilder.withNamePrefix(metrics, "my_"); + var labeled = CollectorBuilder.withLabels(metrics, Labels.of("l", "v")); + + Assert.assertEquals(metrics.collect(MetricNameFilter.ALLOW_ALL, null).get(0).getMetadata().getPrometheusName(), "counter1"); + Assert.assertEquals(prefixed.collect(MetricNameFilter.ALLOW_ALL, null).get(0).getMetadata().getPrometheusName(), "my_counter1"); + Assert.assertEquals(prefixed.collect(name -> name.equals("counter1"), null).size(), 0); + Assert.assertEquals(prefixed.collect(name -> name.equals("my_counter1"), null).size(), 1); + + Assert.assertEquals(metrics.collect(MetricNameFilter.ALLOW_ALL, null).get(0).getDataPoints().get(0).getLabels().size(), 1); + Assert.assertEquals(labeled.collect(MetricNameFilter.ALLOW_ALL, null).get(0).getDataPoints().get(0).getLabels().size(), 2); + Assert.assertTrue(labeled.collect(MetricNameFilter.ALLOW_ALL, null).get(0).getDataPoints().get(0).getLabels().contains("l")); + } + + @Test + public void testLabels() { + var metrics = CollectorBuilder.fromMetric(() -> CounterSnapshot.builder() + .name("counter1") + .dataPoint(CounterDataPointSnapshot.builder() + .labels(Labels.of("path", "/hello")) + .value(1.0) + .build() + ) + .build() + ); + + var labeled = CollectorBuilder.withLabels(metrics, Labels.of("l", "v")); + Assert.assertEquals(metrics.collect(MetricNameFilter.ALLOW_ALL, null).get(0).getDataPoints().get(0).getLabels().size(), 1); + Assert.assertEquals(labeled.collect(MetricNameFilter.ALLOW_ALL, null).get(0).getDataPoints().get(0).getLabels().size(), 2); + Assert.assertTrue(labeled.collect(MetricNameFilter.ALLOW_ALL, null).get(0).getDataPoints().get(0).getLabels().contains("l")); + } + +} diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/MetricNameFilterTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/MetricNameFilterTest.java index 91bcf74fc..92f815252 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/MetricNameFilterTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/MetricNameFilterTest.java @@ -2,18 +2,12 @@ import io.prometheus.metrics.model.snapshots.CounterSnapshot; import io.prometheus.metrics.model.snapshots.CounterSnapshot.CounterDataPointSnapshot; -import io.prometheus.metrics.model.snapshots.GaugeSnapshot; -import io.prometheus.metrics.model.snapshots.GaugeSnapshot.GaugeDataPointSnapshot; import io.prometheus.metrics.model.snapshots.Labels; import io.prometheus.metrics.model.snapshots.MetricSnapshots; import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Predicate; public class MetricNameFilterTest { @@ -27,30 +21,33 @@ public void setUp() { @Test public void testCounter() { - registry.register(() -> CounterSnapshot.builder() - .name("counter1") - .help("test counter 1") - .dataPoint(CounterDataPointSnapshot.builder() - .labels(Labels.of("path", "/hello")) - .value(1.0) + registry.register( + CollectorBuilder.fromMetric(() -> CounterSnapshot.builder() + .name("counter1") + .help("test counter 1") + .dataPoint(CounterDataPointSnapshot.builder() + .labels(Labels.of("path", "/hello")) + .value(1.0) + .build() + ) + .dataPoint(CounterDataPointSnapshot.builder() + .labels(Labels.of("path", "/goodbye")) + .value(2.0) + .build() + ) .build() - ) - .dataPoint(CounterDataPointSnapshot.builder() - .labels(Labels.of("path", "/goodbye")) - .value(2.0) - .build() - ) - .build() - ); - registry.register(() -> CounterSnapshot.builder() - .name("counter2") - .help("test counter 2") - .dataPoint(CounterDataPointSnapshot.builder() - .value(1.0) + )); + + registry.register( + CollectorBuilder.fromMetric(() -> CounterSnapshot.builder() + .name("counter2") + .help("test counter 2") + .dataPoint(CounterDataPointSnapshot.builder() + .value(1.0) + .build() + ) .build() - ) - .build() - ); + )); MetricNameFilter filter = MetricNameFilter.builder().build(); Assert.assertEquals(2, registry.scrape(filter).size()); diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/MultiCollectorNameFilterTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/MultiCollectorNameFilterTest.java index b36d58aa6..723541f24 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/MultiCollectorNameFilterTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/MultiCollectorNameFilterTest.java @@ -9,92 +9,58 @@ import org.junit.Before; import org.junit.Test; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; import java.util.function.Predicate; public class MultiCollectorNameFilterTest { private PrometheusRegistry registry; - private boolean[] collectCalled = {false}; private Predicate includedNames = null; - List prometheusNames = new ArrayList<>(); @Before public void setUp() { registry = new PrometheusRegistry(); - collectCalled[0] = false; - includedNames = null; - prometheusNames = Collections.emptyList(); - registry.register(new MultiCollector() { - @Override - public MetricSnapshots collect() { - collectCalled[0] = true; - return MetricSnapshots.builder() - .metricSnapshot(CounterSnapshot.builder() - .name("counter_1") - .dataPoint(CounterDataPointSnapshot.builder().value(1.0).build()) - .build() - ) - .metricSnapshot(GaugeSnapshot.builder() - .name("gauge_2") - .dataPoint(GaugeDataPointSnapshot.builder().value(1.0).build()) - .build() - ) - .build(); - } - - @Override - public List getPrometheusNames() { - return prometheusNames; - } - }); + registry.register(CollectorBuilder.fromMetrics(() -> MetricSnapshots.builder() + .metricSnapshot(CounterSnapshot.builder() + .name("counter_1") + .dataPoint(CounterDataPointSnapshot.builder().value(1.0).build()) + .build() + ) + .metricSnapshot(GaugeSnapshot.builder() + .name("gauge_2") + .dataPoint(GaugeDataPointSnapshot.builder().value(1.0).build()) + .build() + ) + .build())); } @Test - public void testPartialFilter() { - + public void filterAvoidsScrape() { includedNames = name -> name.equals("counter_1"); - MetricSnapshots snapshots = registry.scrape(includedNames); - Assert.assertTrue(collectCalled[0]); Assert.assertEquals(1, snapshots.size()); Assert.assertEquals("counter_1", snapshots.get(0).getMetadata().getName()); } @Test public void testPartialFilterWithPrometheusNames() { - includedNames = name -> name.equals("counter_1"); - prometheusNames = Arrays.asList("counter_1", "gauge_2"); - MetricSnapshots snapshots = registry.scrape(includedNames); - Assert.assertTrue(collectCalled[0]); Assert.assertEquals(1, snapshots.size()); Assert.assertEquals("counter_1", snapshots.get(0).getMetadata().getName()); } @Test public void testCompleteFilter_CollectCalled() { - includedNames = name -> !name.equals("counter_1") && !name.equals("gauge_2"); - MetricSnapshots snapshots = registry.scrape(includedNames); - Assert.assertTrue(collectCalled[0]); Assert.assertEquals(0, snapshots.size()); } @Test public void testCompleteFilter_CollectNotCalled() { - includedNames = name -> !name.equals("counter_1") && !name.equals("gauge_2"); - prometheusNames = Arrays.asList("counter_1", "gauge_2"); - MetricSnapshots snapshots = registry.scrape(includedNames); - Assert.assertFalse(collectCalled[0]); Assert.assertEquals(0, snapshots.size()); } } diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/PrometheusRegistryTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/PrometheusRegistryTest.java index 498d2354b..026eb154e 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/PrometheusRegistryTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/PrometheusRegistryTest.java @@ -2,82 +2,54 @@ import io.prometheus.metrics.model.snapshots.CounterSnapshot; import io.prometheus.metrics.model.snapshots.GaugeSnapshot; -import io.prometheus.metrics.model.snapshots.MetricSnapshot; import io.prometheus.metrics.model.snapshots.MetricSnapshots; import org.junit.Assert; import org.junit.Test; public class PrometheusRegistryTest { - Collector noName = () -> GaugeSnapshot.builder() - .name("no_name_gauge") - .build(); - - Collector counterA1 = new Collector() { - @Override - public MetricSnapshot collect() { - return CounterSnapshot.builder().name("counter_a").build(); - } - - @Override - public String getPrometheusName() { - return "counter_a"; - } - }; - - Collector counterA2 = new Collector() { - @Override - public MetricSnapshot collect() { - return CounterSnapshot.builder().name("counter.a").build(); - } - - @Override - public String getPrometheusName() { - return "counter_a"; - } - }; - - Collector counterB = new Collector() { - @Override - public MetricSnapshot collect() { - return CounterSnapshot.builder().name("counter_b").build(); - } - - @Override - public String getPrometheusName() { - return "counter_b"; - } - }; - - Collector gaugeA = new Collector() { - @Override - public MetricSnapshot collect() { - return GaugeSnapshot.builder().name("gauge_a").build(); - } - - @Override - public String getPrometheusName() { - return "gauge_a"; - } - }; + Collector counterA1 = CollectorBuilder.fromMetric(() -> CounterSnapshot.builder().name("metric_a").build()); + Collector counterA2 = CollectorBuilder.fromMetric(() -> CounterSnapshot.builder().name("metric_a") + .dataPoint(CounterSnapshot.CounterDataPointSnapshot.builder().value(1.0).build()).build()); + Collector counterA3 = CollectorBuilder.fromMetric(() -> CounterSnapshot.builder().name("metric_a") + .dataPoint(CounterSnapshot.CounterDataPointSnapshot.builder().value(2.0).build()).build()); + Collector counterB = CollectorBuilder.fromMetric(() -> CounterSnapshot.builder().name("metric_b").build()); + Collector gaugeA = CollectorBuilder.fromMetric(() -> GaugeSnapshot.builder().name("metric_a").build()); @Test - public void registerNoName() { + public void scrapeFailsOnTypeMismatch() { PrometheusRegistry registry = new PrometheusRegistry(); - // If the collector does not have a name at registration time, there is no conflict during registration. - registry.register(noName); - registry.register(noName); + registry.register(counterA1); + registry.register(gaugeA); // However, at scrape time the collector has to provide a metric name, and then we'll get a duplicat name error. try { registry.scrape(); - } catch (IllegalStateException e) { - Assert.assertTrue(e.getMessage().contains("duplicate") && e.getMessage().contains("no_name_gauge")); + } catch (IllegalArgumentException e) { + Assert.assertTrue(e.getMessage().contains("invalid type") && e.getMessage().contains("metric_a")); return; } Assert.fail("Expected duplicate name exception"); } - @Test(expected = IllegalStateException.class) + @Test + public void scrapeFailsOnDuplicateLabels() { + PrometheusRegistry registry = new PrometheusRegistry(); + registry.register(counterA2); + registry.register(counterA3); + try { + registry.scrape(); + } catch (IllegalArgumentException e) { + Assert.assertTrue(e.getMessage(), e.getMessage().contains("Duplicate labels")); + return; + } + Assert.fail("Expected duplicate labels exception"); + } + + + /** It is allowed to register collectors with duplicate names. + * Scrape will fail if metadata or metric types do not match. + */ + @Test public void registerDuplicateName() { PrometheusRegistry registry = new PrometheusRegistry(); registry.register(counterA1); @@ -89,16 +61,15 @@ public void registerOk() { PrometheusRegistry registry = new PrometheusRegistry(); registry.register(counterA1); registry.register(counterB); - registry.register(gaugeA); MetricSnapshots snapshots = registry.scrape(); - Assert.assertEquals(3, snapshots.size()); + Assert.assertEquals(2, snapshots.size()); registry.unregister(counterB); snapshots = registry.scrape(); - Assert.assertEquals(2, snapshots.size()); + Assert.assertEquals(1, snapshots.size()); registry.register(counterB); snapshots = registry.scrape(); - Assert.assertEquals(3, snapshots.size()); + Assert.assertEquals(2, snapshots.size()); } } diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricSnapshotsTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricSnapshotsTest.java index 61b54cb3a..489830602 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricSnapshotsTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricSnapshotsTest.java @@ -27,15 +27,44 @@ public void testSort() { .name("counter3") .dataPoint(CounterSnapshot.CounterDataPointSnapshot.builder().value(1.0).build()) .build(); - MetricSnapshots snapshots = new MetricSnapshots(c2, c3, c1); + MetricSnapshots snapshots = MetricSnapshots.of(c2, c3, c1); Assert.assertEquals(3, snapshots.size()); Assert.assertEquals("counter1", snapshots.get(0).getMetadata().getName()); Assert.assertEquals("counter2", snapshots.get(1).getMetadata().getName()); Assert.assertEquals("counter3", snapshots.get(2).getMetadata().getName()); } - @Test(expected = IllegalArgumentException.class) + /** It is legal to have duplicate names as long as labels are different. */ + @Test public void testDuplicateName() { + CounterSnapshot c1 = CounterSnapshot.builder() + .name("my_metric") + .dataPoint(CounterSnapshot.CounterDataPointSnapshot.builder().value(1.0).labels(Labels.of("name", "c1")).build()) + .build(); + CounterSnapshot c2 = CounterSnapshot.builder() + .name("my_metric") + .dataPoint(CounterSnapshot.CounterDataPointSnapshot.builder().value(1.0).labels(Labels.of("name", "c2")).build()) + .build(); + MetricSnapshots.of(c1, c2); + } + + + @Test(expected = IllegalArgumentException.class) + public void testDuplicateNameSameLabels() { + CounterSnapshot c1 = CounterSnapshot.builder() + .name("my_metric") + .dataPoint(CounterSnapshot.CounterDataPointSnapshot.builder().value(1.0).build()) + .build(); + CounterSnapshot c2 = CounterSnapshot.builder() + .name("my_metric") + .dataPoint(CounterSnapshot.CounterDataPointSnapshot.builder().value(1.0).build()) + .build(); + MetricSnapshots.of(c1, c2); + } + + + @Test(expected = IllegalArgumentException.class) + public void testDuplicateNameDifferentTypes() { // Q: What if you have a counter named "foo" and a gauge named "foo"? // A: Great question. You might think this is a valid scenario, because the counter will produce // the values "foo_total" and "foo_created" while the gauge will produce the value "foo". @@ -49,19 +78,7 @@ public void testDuplicateName() { .name("my_metric") .dataPoint(GaugeSnapshot.GaugeDataPointSnapshot.builder().value(1.0).build()) .build(); - new MetricSnapshots(c, g); - } - - @Test - public void testBuilder() { - CounterSnapshot counter = CounterSnapshot.builder() - .name("my_metric") - .dataPoint(CounterSnapshot.CounterDataPointSnapshot.builder().value(1.0).build()) - .build(); - MetricSnapshots.Builder builder = MetricSnapshots.builder(); - Assert.assertFalse(builder.containsMetricName("my_metric")); - builder.metricSnapshot(counter); - Assert.assertTrue(builder.containsMetricName("my_metric")); + MetricSnapshots.of(c, g); } @Test(expected = UnsupportedOperationException.class) diff --git a/prometheus-metrics-simpleclient-bridge/src/main/java/io/prometheus/metrics/simpleclient/bridge/SimpleclientCollector.java b/prometheus-metrics-simpleclient-bridge/src/main/java/io/prometheus/metrics/simpleclient/bridge/SimpleclientCollector.java index a775b2f55..4aebcfe96 100644 --- a/prometheus-metrics-simpleclient-bridge/src/main/java/io/prometheus/metrics/simpleclient/bridge/SimpleclientCollector.java +++ b/prometheus-metrics-simpleclient-bridge/src/main/java/io/prometheus/metrics/simpleclient/bridge/SimpleclientCollector.java @@ -1,10 +1,10 @@ package io.prometheus.metrics.simpleclient.bridge; -import io.prometheus.client.Collector; import io.prometheus.client.CollectorRegistry; import io.prometheus.metrics.config.PrometheusProperties; -import io.prometheus.metrics.model.registry.MultiCollector; +import io.prometheus.metrics.model.registry.Collector; import io.prometheus.metrics.model.registry.PrometheusRegistry; +import io.prometheus.metrics.model.registry.PrometheusScrapeRequest; import io.prometheus.metrics.model.snapshots.ClassicHistogramBuckets; import io.prometheus.metrics.model.snapshots.CounterSnapshot; import io.prometheus.metrics.model.snapshots.Exemplar; @@ -48,7 +48,7 @@ * .register(prometheusRegistry); * } */ -public class SimpleclientCollector implements MultiCollector { +public class SimpleclientCollector implements Collector { private final CollectorRegistry simpleclientRegistry; @@ -57,14 +57,20 @@ private SimpleclientCollector(CollectorRegistry simpleclientRegistry) { } @Override - public MetricSnapshots collect() { - return convert(simpleclientRegistry.metricFamilySamples()); + public MetricSnapshots collect(Predicate nameFilter, PrometheusScrapeRequest scrapeRequest) { + io.prometheus.client.Predicate filter = new io.prometheus.client.Predicate() { + @Override + public boolean test(String s) { + return nameFilter.test(s); + } + }; + return convert(simpleclientRegistry.filteredMetricFamilySamples(filter)); } - private MetricSnapshots convert(Enumeration samples) { + private MetricSnapshots convert(Enumeration samples) { MetricSnapshots.Builder result = MetricSnapshots.builder(); while (samples.hasMoreElements()) { - Collector.MetricFamilySamples sample = samples.nextElement(); + io.prometheus.client.Collector.MetricFamilySamples sample = samples.nextElement(); switch (sample.type) { case COUNTER: result.metricSnapshot(convertCounter(sample)); @@ -97,13 +103,13 @@ private MetricSnapshots convert(Enumeration sampl return result.build(); } - private MetricSnapshot convertCounter(Collector.MetricFamilySamples samples) { + private MetricSnapshot convertCounter(io.prometheus.client.Collector.MetricFamilySamples samples) { CounterSnapshot.Builder counter = CounterSnapshot.builder() .name(stripSuffix(samples.name, "_total")) .help(samples.help) .unit(convertUnit(samples)); Map dataPoints = new HashMap<>(); - for (Collector.MetricFamilySamples.Sample sample : samples.samples) { + for (io.prometheus.client.Collector.MetricFamilySamples.Sample sample : samples.samples) { Labels labels = Labels.of(sample.labelNames, sample.labelValues); CounterSnapshot.CounterDataPointSnapshot.Builder dataPoint = dataPoints.computeIfAbsent(labels, l -> CounterSnapshot.CounterDataPointSnapshot.builder().labels(labels)); if (sample.name.endsWith("_created")) { @@ -121,12 +127,12 @@ private MetricSnapshot convertCounter(Collector.MetricFamilySamples samples) { return counter.build(); } - private MetricSnapshot convertGauge(Collector.MetricFamilySamples samples) { + private MetricSnapshot convertGauge(io.prometheus.client.Collector.MetricFamilySamples samples) { GaugeSnapshot.Builder gauge = GaugeSnapshot.builder() .name(samples.name) .help(samples.help) .unit(convertUnit(samples)); - for (Collector.MetricFamilySamples.Sample sample : samples.samples) { + for (io.prometheus.client.Collector.MetricFamilySamples.Sample sample : samples.samples) { GaugeSnapshot.GaugeDataPointSnapshot.Builder dataPoint = GaugeSnapshot.GaugeDataPointSnapshot.builder() .value(sample.value) .labels(Labels.of(sample.labelNames, sample.labelValues)) @@ -139,7 +145,7 @@ private MetricSnapshot convertGauge(Collector.MetricFamilySamples samples) { return gauge.build(); } - private MetricSnapshot convertHistogram(Collector.MetricFamilySamples samples, boolean isGaugeHistogram) { + private MetricSnapshot convertHistogram(io.prometheus.client.Collector.MetricFamilySamples samples, boolean isGaugeHistogram) { HistogramSnapshot.Builder histogram = HistogramSnapshot.builder() .name(samples.name) .help(samples.help) @@ -148,7 +154,7 @@ private MetricSnapshot convertHistogram(Collector.MetricFamilySamples samples, b Map dataPoints = new HashMap<>(); Map> cumulativeBuckets = new HashMap<>(); Map exemplars = new HashMap<>(); - for (Collector.MetricFamilySamples.Sample sample : samples.samples) { + for (io.prometheus.client.Collector.MetricFamilySamples.Sample sample : samples.samples) { Labels labels = labelsWithout(sample, "le"); dataPoints.computeIfAbsent(labels, l -> HistogramSnapshot.HistogramDataPointSnapshot.builder() .labels(labels)); @@ -179,7 +185,7 @@ private MetricSnapshot convertHistogram(Collector.MetricFamilySamples samples, b return histogram.build(); } - private MetricSnapshot convertSummary(Collector.MetricFamilySamples samples) { + private MetricSnapshot convertSummary(io.prometheus.client.Collector.MetricFamilySamples samples) { SummarySnapshot.Builder summary = SummarySnapshot.builder() .name(samples.name) .help(samples.help) @@ -187,7 +193,7 @@ private MetricSnapshot convertSummary(Collector.MetricFamilySamples samples) { Map dataPoints = new HashMap<>(); Map quantiles = new HashMap<>(); Map exemplars = new HashMap<>(); - for (Collector.MetricFamilySamples.Sample sample : samples.samples) { + for (io.prometheus.client.Collector.MetricFamilySamples.Sample sample : samples.samples) { Labels labels = labelsWithout(sample, "quantile"); dataPoints.computeIfAbsent(labels, l -> SummarySnapshot.SummaryDataPointSnapshot.builder() .labels(labels)); @@ -223,12 +229,12 @@ private MetricSnapshot convertSummary(Collector.MetricFamilySamples samples) { return summary.build(); } - private MetricSnapshot convertStateSet(Collector.MetricFamilySamples samples) { + private MetricSnapshot convertStateSet(io.prometheus.client.Collector.MetricFamilySamples samples) { StateSetSnapshot.Builder stateSet = StateSetSnapshot.builder() .name(samples.name) .help(samples.help); Map dataPoints = new HashMap<>(); - for (Collector.MetricFamilySamples.Sample sample : samples.samples) { + for (io.prometheus.client.Collector.MetricFamilySamples.Sample sample : samples.samples) { Labels labels = labelsWithout(sample, sample.name); dataPoints.computeIfAbsent(labels, l -> StateSetSnapshot.StateSetDataPointSnapshot.builder().labels(labels)); String stateName = null; @@ -252,12 +258,12 @@ private MetricSnapshot convertStateSet(Collector.MetricFamilySamples samples) { return stateSet.build(); } - private MetricSnapshot convertUnknown(Collector.MetricFamilySamples samples) { + private MetricSnapshot convertUnknown(io.prometheus.client.Collector.MetricFamilySamples samples) { UnknownSnapshot.Builder unknown = UnknownSnapshot.builder() .name(samples.name) .help(samples.help) .unit(convertUnit(samples)); - for (Collector.MetricFamilySamples.Sample sample : samples.samples) { + for (io.prometheus.client.Collector.MetricFamilySamples.Sample sample : samples.samples) { UnknownSnapshot.UnknownDataPointSnapshot.Builder dataPoint = UnknownSnapshot.UnknownDataPointSnapshot.builder() .value(sample.value) .labels(Labels.of(sample.labelNames, sample.labelValues)) @@ -278,7 +284,7 @@ private String stripSuffix(String name, String suffix) { } } - private Unit convertUnit(Collector.MetricFamilySamples samples) { + private Unit convertUnit(io.prometheus.client.Collector.MetricFamilySamples samples) { if (samples.unit != null && !samples.unit.isEmpty()) { return new Unit(samples.unit); } else { @@ -300,7 +306,7 @@ private ClassicHistogramBuckets makeBuckets(Map cumulativeBuckets) return result.build(); } - private void addBucket(Map buckets, Collector.MetricFamilySamples.Sample sample) { + private void addBucket(Map buckets, io.prometheus.client.Collector.MetricFamilySamples.Sample sample) { for (int i = 0; i < sample.labelNames.size(); i++) { if (sample.labelNames.get(i).equals("le")) { double upperBound; @@ -322,7 +328,7 @@ private void addBucket(Map buckets, Collector.MetricFamilySamples. } - private Labels labelsWithout(Collector.MetricFamilySamples.Sample sample, String excludedLabelName) { + private Labels labelsWithout(io.prometheus.client.Collector.MetricFamilySamples.Sample sample, String excludedLabelName) { Labels.Builder labels = Labels.builder(); for (int i = 0; i < sample.labelNames.size(); i++) { if (!sample.labelNames.get(i).equals(excludedLabelName)) { @@ -332,11 +338,11 @@ private Labels labelsWithout(Collector.MetricFamilySamples.Sample sample, String return labels.build(); } - private MetricSnapshot convertInfo(Collector.MetricFamilySamples samples) { + private MetricSnapshot convertInfo(io.prometheus.client.Collector.MetricFamilySamples samples) { InfoSnapshot.Builder info = InfoSnapshot.builder() .name(stripSuffix(samples.name, "_info")) .help(samples.help); - for (Collector.MetricFamilySamples.Sample sample : samples.samples) { + for (io.prometheus.client.Collector.MetricFamilySamples.Sample sample : samples.samples) { info.dataPoint(InfoSnapshot.InfoDataPointSnapshot.builder() .labels(Labels.of(sample.labelNames, sample.labelValues)) .build());