From f5db8e6a40924e7623498975bd847d1afb816c0b Mon Sep 17 00:00:00 2001 From: guido Date: Tue, 10 Oct 2023 14:33:30 +0200 Subject: [PATCH 1/6] Added multi-target pattern support Signed-off-by: Guido Anzuoni --- .../examples/httpserver/MainMultiTarget.java | 23 ++++++++ .../httpserver/SampleExtendedCollector.java | 35 ++++++++++++ .../common/PrometheusHttpRequest.java | 5 ++ .../common/PrometheusHttpScrapeRequest.java | 24 ++++++++ .../common/PrometheusScrapeHandler.java | 6 +- .../httpserver/HttpExchangeAdapter.java | 14 +++++ .../servlet/jakarta/HttpExchangeAdapter.java | 18 ++++++ .../metrics/model/registry/Collector.java | 24 ++++++++ .../model/registry/ExtendedCollector.java | 30 ++++++++++ .../registry/ExtendedMultiCollector.java | 25 ++++++++ .../model/registry/MultiCollector.java | 27 +++++++++ .../model/registry/PrometheusRegistry.java | 57 +++++++++++++++++++ .../registry/PrometheusScrapeRequest.java | 13 +++++ 13 files changed, 299 insertions(+), 2 deletions(-) create mode 100644 examples/example-exporter-httpserver/src/main/java/io/prometheus/metrics/examples/httpserver/MainMultiTarget.java create mode 100644 examples/example-exporter-httpserver/src/main/java/io/prometheus/metrics/examples/httpserver/SampleExtendedCollector.java create mode 100644 prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusHttpScrapeRequest.java create mode 100644 prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/ExtendedCollector.java create mode 100644 prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/ExtendedMultiCollector.java create mode 100644 prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/PrometheusScrapeRequest.java diff --git a/examples/example-exporter-httpserver/src/main/java/io/prometheus/metrics/examples/httpserver/MainMultiTarget.java b/examples/example-exporter-httpserver/src/main/java/io/prometheus/metrics/examples/httpserver/MainMultiTarget.java new file mode 100644 index 000000000..8a89f1785 --- /dev/null +++ b/examples/example-exporter-httpserver/src/main/java/io/prometheus/metrics/examples/httpserver/MainMultiTarget.java @@ -0,0 +1,23 @@ +package io.prometheus.metrics.examples.httpserver; + +import java.io.IOException; + +import io.prometheus.metrics.exporter.httpserver.HTTPServer; +import io.prometheus.metrics.model.registry.PrometheusRegistry; + +/** + * Simple example of an application exposing metrics via Prometheus' built-in HTTPServer. + */ +public class MainMultiTarget { + + public static void main(String[] args) throws IOException, InterruptedException { + + SampleExtendedCollector xc = new SampleExtendedCollector(); + PrometheusRegistry.defaultRegistry.register(xc); + HTTPServer server = HTTPServer.builder() + .port(9400) + .buildAndStart(); + + System.out.println("HTTPServer listening on port http://localhost:" + server.getPort() + "/metrics"); + } +} diff --git a/examples/example-exporter-httpserver/src/main/java/io/prometheus/metrics/examples/httpserver/SampleExtendedCollector.java b/examples/example-exporter-httpserver/src/main/java/io/prometheus/metrics/examples/httpserver/SampleExtendedCollector.java new file mode 100644 index 000000000..b5ae7f195 --- /dev/null +++ b/examples/example-exporter-httpserver/src/main/java/io/prometheus/metrics/examples/httpserver/SampleExtendedCollector.java @@ -0,0 +1,35 @@ +package io.prometheus.metrics.examples.httpserver; + +import io.prometheus.metrics.core.metrics.Counter; +import io.prometheus.metrics.model.registry.ExtendedCollector; +import io.prometheus.metrics.model.registry.PrometheusScrapeRequest; +import io.prometheus.metrics.model.snapshots.MetricSnapshot; + +public class SampleExtendedCollector extends ExtendedCollector { + + public SampleExtendedCollector() { + super(); + } + + @Override + protected MetricSnapshot collectMetricSnapshot(PrometheusScrapeRequest scrapeRequest) { + Counter sampleCounter; + sampleCounter = Counter.builder().name("x_counter_total").labelNames("target", "proc").build(); + String[] targetName = scrapeRequest.getParameterValues("target"); + String[] procs = scrapeRequest.getParameterValues("proc"); + if (targetName == null || targetName.length == 0) { + sampleCounter.labelValues("defaultTarget", "defaultProc").inc(); + } else { + if (procs == null || procs.length == 0) { + sampleCounter.labelValues(targetName[0], "defaultProc").inc(Math.random()); + } else { + for (int i = 0; i < procs.length; i++) { + sampleCounter.labelValues(targetName[0], procs[i]).inc(Math.random()); + } + + } + } + return sampleCounter.collect(); + } + +} diff --git a/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusHttpRequest.java b/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusHttpRequest.java index a5502003c..8b4a846a7 100644 --- a/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusHttpRequest.java +++ b/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusHttpRequest.java @@ -7,6 +7,11 @@ public interface PrometheusHttpRequest { + /** + * Return the absolute path of a Http Request + */ + String getRequestPath(); + /** * See {@code jakarta.servlet.http.HttpServletRequest.getQueryString()} */ diff --git a/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusHttpScrapeRequest.java b/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusHttpScrapeRequest.java new file mode 100644 index 000000000..c80a7bd99 --- /dev/null +++ b/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusHttpScrapeRequest.java @@ -0,0 +1,24 @@ +package io.prometheus.metrics.exporter.common; + +import io.prometheus.metrics.model.registry.PrometheusScrapeRequest; + +public class PrometheusHttpScrapeRequest implements PrometheusScrapeRequest { + + protected PrometheusHttpRequest prometheusHttpRequest; + + public PrometheusHttpScrapeRequest(PrometheusHttpRequest prometheusHttpRequest) { + super(); + this.prometheusHttpRequest = prometheusHttpRequest; + } + + @Override + public String getRequestURI() { + return prometheusHttpRequest.getRequestPath(); + } + + @Override + public String[] getParameterValues(String name) { + return prometheusHttpRequest.getParameterValues(name); + } + +} 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 b095007e0..13e8322c8 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 @@ -104,11 +104,13 @@ private Predicate makeNameFilter(ExporterFilterProperties props) { } private MetricSnapshots scrape(PrometheusHttpRequest request) { + PrometheusHttpScrapeRequest scrapeRequest = new PrometheusHttpScrapeRequest(request); + Predicate filter = makeNameFilter(request.getParameterValues("name[]")); if (filter != null) { - return registry.scrape(filter); + return registry.scrape(filter, scrapeRequest); } else { - return registry.scrape(); + return registry.scrape(scrapeRequest); } } diff --git a/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/HttpExchangeAdapter.java b/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/HttpExchangeAdapter.java index 4f1b4f63a..c0422c461 100644 --- a/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/HttpExchangeAdapter.java +++ b/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/HttpExchangeAdapter.java @@ -9,6 +9,7 @@ import java.io.OutputStream; import java.io.PrintWriter; import java.io.StringWriter; +import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.Enumeration; @@ -29,7 +30,20 @@ public HttpExchangeAdapter(HttpExchange httpExchange) { public class HttpRequest implements PrometheusHttpRequest { + + @Override + public String getRequestPath() { + URI requestURI = httpExchange.getRequestURI(); + String uri = requestURI.toString(); + int qx = uri.indexOf('?'); + if (qx != -1) { + uri = uri.substring(0, qx); + } + return uri; + } + + @Override public String getQueryString() { return httpExchange.getRequestURI().getRawQuery(); } diff --git a/prometheus-metrics-exporter-servlet-jakarta/src/main/java/io/prometheus/metrics/exporter/servlet/jakarta/HttpExchangeAdapter.java b/prometheus-metrics-exporter-servlet-jakarta/src/main/java/io/prometheus/metrics/exporter/servlet/jakarta/HttpExchangeAdapter.java index 656d661eb..ece3d1519 100644 --- a/prometheus-metrics-exporter-servlet-jakarta/src/main/java/io/prometheus/metrics/exporter/servlet/jakarta/HttpExchangeAdapter.java +++ b/prometheus-metrics-exporter-servlet-jakarta/src/main/java/io/prometheus/metrics/exporter/servlet/jakarta/HttpExchangeAdapter.java @@ -54,6 +54,24 @@ public Request(HttpServletRequest request) { } @Override + public String getRequestPath() { + StringBuffer uri = new StringBuffer(); + String contextPath = request.getContextPath(); + if (contextPath.startsWith("/")) { + uri.append(contextPath); + } + String servletPath = request.getServletPath(); + if (servletPath.startsWith("/")) { + uri.append(servletPath); + } + String pathInfo = request.getPathInfo(); + if (pathInfo != null) { + uri.append(pathInfo); + } + return uri.toString(); + } + + @Override public String getQueryString() { return request.getQueryString(); } 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 f66d00b67..867c6bf57 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 @@ -19,6 +19,15 @@ public interface Collector { */ 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) { + MetricSnapshot result = collect(); + return result; + } + /** * Like {@link #collect()}, but returns {@code null} if {@code includedNames.test(name)} is {@code false}. *

@@ -33,6 +42,21 @@ default MetricSnapshot collect(Predicate includedNames) { } } + /** + * Like {@link #collect(includedNames)}, 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; + } + } + + /** * Override this and return {@code null} if a collector does not have a constant name, * or if you don't want this library to call {@link #collect()} during registration of this collector. diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/ExtendedCollector.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/ExtendedCollector.java new file mode 100644 index 000000000..42b314620 --- /dev/null +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/ExtendedCollector.java @@ -0,0 +1,30 @@ +package io.prometheus.metrics.model.registry; + +import io.prometheus.metrics.model.snapshots.MetricSnapshot; + +public abstract class ExtendedCollector implements Collector { + + @Override + public MetricSnapshot collect() { + return null; + } + + @Override + public MetricSnapshot collect(PrometheusScrapeRequest scrapeRequest) { + return collectMetricSnapshot(scrapeRequest); + } + + /** + * Override to implement multi-target exporter pattern + * @param scrapeRequest + * the (http) request context triggering metrics collection + */ + protected abstract MetricSnapshot collectMetricSnapshot(PrometheusScrapeRequest scrapeRequest); + + @Override + public String getPrometheusName() { + return null; + } + + +} diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/ExtendedMultiCollector.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/ExtendedMultiCollector.java new file mode 100644 index 000000000..79fda7e1f --- /dev/null +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/ExtendedMultiCollector.java @@ -0,0 +1,25 @@ +package io.prometheus.metrics.model.registry; + +import io.prometheus.metrics.model.snapshots.MetricSnapshots; + +public abstract class ExtendedMultiCollector implements MultiCollector { + + @Override + public MetricSnapshots collect() { + return new MetricSnapshots(); + } + + @Override + public MetricSnapshots collect(PrometheusScrapeRequest scrapeRequest) { + return collectMetricSnapshots(scrapeRequest); + } + + /** + * Override to implement multi-target exporter pattern + * @param scrapeRequest + * the (http) request context triggering metrics collection + */ + protected abstract MetricSnapshots collectMetricSnapshots(PrometheusScrapeRequest scrapeRequest); + + +} 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 index 92fcf9012..28b32f666 100644 --- 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 @@ -18,6 +18,16 @@ public interface MultiCollector { */ 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) { + MetricSnapshots result = collect(); + return result; + } + + /** * Like {@link #collect()}, but returns only the snapshots where {@code includedNames.test(name)} is {@code true}. *

@@ -34,6 +44,23 @@ default MetricSnapshots collect(Predicate includedNames) { return result.build(); } + /** + * Like {@link #collect(includedNames)}, 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 = collect(scrapeRequest); + MetricSnapshots.Builder result = MetricSnapshots.builder(); + for (MetricSnapshot snapshot : allSnapshots) { + if (includedNames.test(snapshot.getMetadata().getPrometheusName())) { + result.metricSnapshot(snapshot); + } + } + return result.build(); + } + + /** * Override this and return an empty list if the MultiCollector does not return a constant list of names * (names may be added / removed between scrapes). 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 21665499d..c08e6296c 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 @@ -72,6 +72,29 @@ public MetricSnapshots scrape() { return result.build(); } + public MetricSnapshots scrape(PrometheusScrapeRequest scrapeRequest) { + MetricSnapshots.Builder result = MetricSnapshots.builder(); + for (Collector collector : collectors) { + MetricSnapshot snapshot = 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) { + for (MetricSnapshot snapshot : collector.collect(scrapeRequest)) { + if (result.containsMetricName(snapshot.getMetadata().getName())) { + throw new IllegalStateException(snapshot.getMetadata().getPrometheusName() + ": duplicate metric name."); + } + result.metricSnapshot(snapshot); + } + } + return result.build(); + } + + public MetricSnapshots scrape(Predicate includedNames) { if (includedNames == null) { return scrape(); @@ -106,4 +129,38 @@ public MetricSnapshots scrape(Predicate includedNames) { return result.build(); } + public MetricSnapshots scrape(Predicate includedNames, PrometheusScrapeRequest scrapeRequest) { + if (includedNames == null) { + return scrape(scrapeRequest); + } + MetricSnapshots.Builder result = MetricSnapshots.builder(); + for (Collector collector : collectors) { + String prometheusName = collector.getPrometheusName(); + if (prometheusName == null || includedNames.test(prometheusName)) { + MetricSnapshot snapshot = collector.collect(includedNames, scrapeRequest); + if (snapshot != null) { + result.metricSnapshot(snapshot); + } + } + } + for (MultiCollector collector : multiCollectors) { + List prometheusNames = collector.getPrometheusNames(); + boolean excluded = true; // the multi-collector is excluded unless at least one name matches + for (String prometheusName : prometheusNames) { + if (includedNames.test(prometheusName)) { + excluded = false; + break; + } + } + if (!excluded) { + for (MetricSnapshot snapshot : collector.collect(includedNames, scrapeRequest)) { + if (snapshot != null) { + result.metricSnapshot(snapshot); + } + } + } + } + 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 new file mode 100644 index 000000000..71aaf31d2 --- /dev/null +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/PrometheusScrapeRequest.java @@ -0,0 +1,13 @@ +package io.prometheus.metrics.model.registry; + +/** + * Infos extracted from the request received by the endpoint + * + */ +public interface PrometheusScrapeRequest { + + String getRequestURI(); + + String[] getParameterValues(String name); + +} From 9a5e488c9fddc74c9439de4ee63b26636cecefb0 Mon Sep 17 00:00:00 2001 From: guido Date: Tue, 10 Oct 2023 14:51:08 +0200 Subject: [PATCH 2/6] Added documentation for custom Collector for multi-target pattern Signed-off-by: Guido Anzuoni --- docs/content/getting-started/multi-target.md | 50 ++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 docs/content/getting-started/multi-target.md diff --git a/docs/content/getting-started/multi-target.md b/docs/content/getting-started/multi-target.md new file mode 100644 index 000000000..fec623cb5 --- /dev/null +++ b/docs/content/getting-started/multi-target.md @@ -0,0 +1,50 @@ +--- +title: Multi-Target Pattern +weight: 7 +--- + +To support multi-target pattern you can create a custom collector overriding the purposed internal method in ExtendedCollector +see SampleExtendedCollector in io.prometheus.metrics.examples.httpserver + +```java +public class SampleExtendedCollector extends ExtendedCollector { + + public SampleExtendedCollector() { + super(); + } + + @Override + protected MetricSnapshot collectMetricSnapshot(PrometheusScrapeRequest scrapeRequest) { + Counter sampleCounter; + sampleCounter = Counter.builder().name("x_counter_total").labelNames("target", "proc").build(); + String[] targetName = scrapeRequest.getParameterValues("target"); + String[] procs = scrapeRequest.getParameterValues("proc"); + if (targetName == null || targetName.length == 0) { + sampleCounter.labelValues("defaultTarget", "defaultProc").inc(); + } else { + if (procs == null || procs.length == 0) { + sampleCounter.labelValues(targetName[0], "defaultProc").inc(Math.random()); + } else { + for (int i = 0; i < procs.length; i++) { + sampleCounter.labelValues(targetName[0], procs[i]).inc(Math.random()); + } + + } + } + return sampleCounter.collect(); + } + +} + +``` +`PrometheusScrapeRequest` provides methods to access http-related infos from the request originally received by the endpoint + +```java +public interface PrometheusScrapeRequest { + String getRequestURI(); + + String[] getParameterValues(String name); +} + +``` + From 96f14124a9033d1c8fc7343cb1aa8fcfa196e6b1 Mon Sep 17 00:00:00 2001 From: guido Date: Thu, 12 Oct 2023 09:40:12 +0200 Subject: [PATCH 3/6] Added MultiCollector example Signed-off-by: Guido Anzuoni --- .../examples/httpserver/MainMultiTarget.java | 2 + .../SampleExtendedMultiCollector.java | 56 +++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 examples/example-exporter-httpserver/src/main/java/io/prometheus/metrics/examples/httpserver/SampleExtendedMultiCollector.java diff --git a/examples/example-exporter-httpserver/src/main/java/io/prometheus/metrics/examples/httpserver/MainMultiTarget.java b/examples/example-exporter-httpserver/src/main/java/io/prometheus/metrics/examples/httpserver/MainMultiTarget.java index 8a89f1785..affa038c2 100644 --- a/examples/example-exporter-httpserver/src/main/java/io/prometheus/metrics/examples/httpserver/MainMultiTarget.java +++ b/examples/example-exporter-httpserver/src/main/java/io/prometheus/metrics/examples/httpserver/MainMultiTarget.java @@ -14,6 +14,8 @@ public static void main(String[] args) throws IOException, InterruptedException SampleExtendedCollector xc = new SampleExtendedCollector(); PrometheusRegistry.defaultRegistry.register(xc); + SampleExtendedMultiCollector xmc = new SampleExtendedMultiCollector(); + PrometheusRegistry.defaultRegistry.register(xmc); HTTPServer server = HTTPServer.builder() .port(9400) .buildAndStart(); diff --git a/examples/example-exporter-httpserver/src/main/java/io/prometheus/metrics/examples/httpserver/SampleExtendedMultiCollector.java b/examples/example-exporter-httpserver/src/main/java/io/prometheus/metrics/examples/httpserver/SampleExtendedMultiCollector.java new file mode 100644 index 000000000..8b325b17e --- /dev/null +++ b/examples/example-exporter-httpserver/src/main/java/io/prometheus/metrics/examples/httpserver/SampleExtendedMultiCollector.java @@ -0,0 +1,56 @@ +package io.prometheus.metrics.examples.httpserver; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import io.prometheus.metrics.core.metrics.Counter; +import io.prometheus.metrics.core.metrics.Gauge; +import io.prometheus.metrics.model.registry.ExtendedMultiCollector; +import io.prometheus.metrics.model.registry.PrometheusScrapeRequest; +import io.prometheus.metrics.model.snapshots.MetricSnapshot; +import io.prometheus.metrics.model.snapshots.MetricSnapshots; + +public class SampleExtendedMultiCollector extends ExtendedMultiCollector { + + public SampleExtendedMultiCollector() { + super(); + } + + @Override + protected MetricSnapshots collectMetricSnapshots(PrometheusScrapeRequest scrapeRequest) { + Counter sampleCounter; + Gauge sampleGauge; + sampleCounter = Counter.builder().name("x_calls_total").labelNames("target", "proc").build(); + sampleGauge = Gauge.builder().name("x_load").labelNames("target", "proc").build(); + String[] targetName = scrapeRequest.getParameterValues("target"); + String[] procs = scrapeRequest.getParameterValues("proc"); + if (targetName == null || targetName.length == 0) { + sampleCounter.labelValues("defaultTarget", "defaultProc").inc(); + sampleGauge.labelValues("defaultTarget", "defaultProc").set(Math.random()); + } else { + if (procs == null || procs.length == 0) { + sampleCounter.labelValues(targetName[0], "defaultProc").inc(Math.random()); + sampleGauge.labelValues(targetName[0], "defaultProc").set(Math.random()); + } else { + for (int i = 0; i < procs.length; i++) { + sampleCounter.labelValues(targetName[0], procs[i]).inc(Math.random()); + sampleGauge.labelValues(targetName[0], procs[i]).set(Math.random()); + } + + } + } + Collection snaps = new ArrayList(); + snaps.add(sampleCounter.collect()); + snaps.add(sampleGauge.collect()); + MetricSnapshots msnaps = new MetricSnapshots(snaps); + return msnaps; + } + + public List getPrometheusNames() { + List names = new ArrayList(); + names.add("Multi"); + return names ; + } + +} From fe4619f25a70478d23c094d0d4fb3d1487813f4d Mon Sep 17 00:00:00 2001 From: Guido Anzuoni Date: Mon, 16 Oct 2023 16:31:37 +0200 Subject: [PATCH 4/6] =?UTF-8?q?Adopted=20(partially)=20Fabian=20St=C3=A4be?= =?UTF-8?q?r=20comments=20to=20the=20pull=20request?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Guido Anzuoni --- docs/content/getting-started/multi-target.md | 98 +++++-- .../httpserver/SampleExtendedCollector.java | 35 --- .../SampleExtendedMultiCollector.java | 56 ---- .../example-exporter-multi-target/README.md | 33 +++ .../example-exporter-multi-target/pom.xml | 76 ++++++ .../metrics/examples/multitarget/Main.java} | 8 +- .../SampleExtendedMultiCollector.java | 79 ++++++ examples/pom.xml | 1 + .../metrics/model/registry/Collector.java | 5 +- .../model/registry/ExtendedCollector.java | 30 --- .../model/registry/MultiCollector.java | 16 +- .../model/registry/PrometheusRegistry.java | 249 ++++++++---------- 12 files changed, 382 insertions(+), 304 deletions(-) delete mode 100644 examples/example-exporter-httpserver/src/main/java/io/prometheus/metrics/examples/httpserver/SampleExtendedCollector.java delete mode 100644 examples/example-exporter-httpserver/src/main/java/io/prometheus/metrics/examples/httpserver/SampleExtendedMultiCollector.java create mode 100644 examples/example-exporter-multi-target/README.md create mode 100644 examples/example-exporter-multi-target/pom.xml rename examples/{example-exporter-httpserver/src/main/java/io/prometheus/metrics/examples/httpserver/MainMultiTarget.java => example-exporter-multi-target/src/main/java/io/prometheus/metrics/examples/multitarget/Main.java} (74%) create mode 100644 examples/example-exporter-multi-target/src/main/java/io/prometheus/metrics/examples/multitarget/SampleExtendedMultiCollector.java delete mode 100644 prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/ExtendedCollector.java diff --git a/docs/content/getting-started/multi-target.md b/docs/content/getting-started/multi-target.md index fec623cb5..6a1c0412c 100644 --- a/docs/content/getting-started/multi-target.md +++ b/docs/content/getting-started/multi-target.md @@ -3,35 +3,74 @@ title: Multi-Target Pattern weight: 7 --- -To support multi-target pattern you can create a custom collector overriding the purposed internal method in ExtendedCollector -see SampleExtendedCollector in io.prometheus.metrics.examples.httpserver +{{< hint type=note >}} +This is for the upcoming release 1.1.0. +{{< /hint >}} + +To support multi-target pattern you can create a custom collector overriding the purposed internal method in ExtendedMultiCollector +see SampleExtendedMultiCollector in io.prometheus.metrics.examples.httpserver ```java -public class SampleExtendedCollector extends ExtendedCollector { +public class SampleExtendedMultiCollector extends ExtendedMultiCollector { - public SampleExtendedCollector() { + public SampleExtendedMultiCollector() { super(); } @Override - protected MetricSnapshot collectMetricSnapshot(PrometheusScrapeRequest scrapeRequest) { - Counter sampleCounter; - sampleCounter = Counter.builder().name("x_counter_total").labelNames("target", "proc").build(); - String[] targetName = scrapeRequest.getParameterValues("target"); + protected MetricSnapshots collectMetricSnapshots(PrometheusScrapeRequest scrapeRequest) { + + GaugeSnapshot.Builder gaugeBuilder = GaugeSnapshot.builder(); + gaugeBuilder.name("x_load").help("process load"); + + CounterSnapshot.Builder counterBuilder = CounterSnapshot.builder(); + counterBuilder.name(PrometheusNaming.sanitizeMetricName("x_calls_total")).help("invocations"); + + String[] targetNames = scrapeRequest.getParameterValues("target"); + String targetName; String[] procs = scrapeRequest.getParameterValues("proc"); - if (targetName == null || targetName.length == 0) { - sampleCounter.labelValues("defaultTarget", "defaultProc").inc(); + if (targetNames == null || targetNames.length == 0) { + targetName = "defaultTarget"; + procs = null; //ignore procs param + } else { + targetName = targetNames[0]; + } + Builder counterDataPointBuilder = CounterSnapshot.CounterDataPointSnapshot.builder(); + io.prometheus.metrics.model.snapshots.GaugeSnapshot.GaugeDataPointSnapshot.Builder gaugeDataPointBuilder = GaugeSnapshot.GaugeDataPointSnapshot.builder(); + Labels lbls = Labels.of("target", targetName); + + if (procs == null || procs.length == 0) { + counterDataPointBuilder.labels(lbls.merge(Labels.of("proc", "defaultProc"))); + gaugeDataPointBuilder.labels(lbls.merge(Labels.of("proc", "defaultProc"))); + counterDataPointBuilder.value(70); + gaugeDataPointBuilder.value(Math.random()); + + counterBuilder.dataPoint(counterDataPointBuilder.build()); + gaugeBuilder.dataPoint(gaugeDataPointBuilder.build()); + } else { - if (procs == null || procs.length == 0) { - sampleCounter.labelValues(targetName[0], "defaultProc").inc(Math.random()); - } else { - for (int i = 0; i < procs.length; i++) { - sampleCounter.labelValues(targetName[0], procs[i]).inc(Math.random()); - } + for (int i = 0; i < procs.length; i++) { + counterDataPointBuilder.labels(lbls.merge(Labels.of("proc", procs[i]))); + gaugeDataPointBuilder.labels(lbls.merge(Labels.of("proc", procs[i]))); + counterDataPointBuilder.value(Math.random()); + gaugeDataPointBuilder.value(Math.random()); + counterBuilder.dataPoint(counterDataPointBuilder.build()); + gaugeBuilder.dataPoint(gaugeDataPointBuilder.build()); } } - return sampleCounter.collect(); + Collection snaps = new ArrayList(); + snaps.add(counterBuilder.build()); + snaps.add(gaugeBuilder.build()); + MetricSnapshots msnaps = new MetricSnapshots(snaps); + return msnaps; + } + + public List getPrometheusNames() { + List names = new ArrayList(); + names.add("x_calls_total"); + names.add("x_load"); + return names; } } @@ -48,3 +87,28 @@ public interface PrometheusScrapeRequest { ``` + +Sample Prometheus scrape_config + +``` + - job_name: "multi-target" + + # metrics_path defaults to '/metrics' + # scheme defaults to 'http'. + params: + proc: [proc1, proc2] + relabel_configs: + - source_labels: [__address__] + target_label: __param_target + - source_labels: [__param_target] + target_label: instance + - target_label: __address__ + replacement: localhost:9401 + static_configs: + - targets: ["target1", "target2"] +``` +It's up to the specific MultiCollector implementation how to interpret the _target_ parameter. +It might be an explicit real target (i.e. via host name/ip address) or as an alias in some internal configuration. +The latter is more suitable when the MultiCollector implementation is a proxy (see https://github.com/prometheus/snmp_exporter) +In this case, invoking real target might require extra parameters (e.g. credentials) that might be complex to manage in Prometheus configuration +(not considering the case where the proxy might become an "open relay") \ No newline at end of file diff --git a/examples/example-exporter-httpserver/src/main/java/io/prometheus/metrics/examples/httpserver/SampleExtendedCollector.java b/examples/example-exporter-httpserver/src/main/java/io/prometheus/metrics/examples/httpserver/SampleExtendedCollector.java deleted file mode 100644 index b5ae7f195..000000000 --- a/examples/example-exporter-httpserver/src/main/java/io/prometheus/metrics/examples/httpserver/SampleExtendedCollector.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.prometheus.metrics.examples.httpserver; - -import io.prometheus.metrics.core.metrics.Counter; -import io.prometheus.metrics.model.registry.ExtendedCollector; -import io.prometheus.metrics.model.registry.PrometheusScrapeRequest; -import io.prometheus.metrics.model.snapshots.MetricSnapshot; - -public class SampleExtendedCollector extends ExtendedCollector { - - public SampleExtendedCollector() { - super(); - } - - @Override - protected MetricSnapshot collectMetricSnapshot(PrometheusScrapeRequest scrapeRequest) { - Counter sampleCounter; - sampleCounter = Counter.builder().name("x_counter_total").labelNames("target", "proc").build(); - String[] targetName = scrapeRequest.getParameterValues("target"); - String[] procs = scrapeRequest.getParameterValues("proc"); - if (targetName == null || targetName.length == 0) { - sampleCounter.labelValues("defaultTarget", "defaultProc").inc(); - } else { - if (procs == null || procs.length == 0) { - sampleCounter.labelValues(targetName[0], "defaultProc").inc(Math.random()); - } else { - for (int i = 0; i < procs.length; i++) { - sampleCounter.labelValues(targetName[0], procs[i]).inc(Math.random()); - } - - } - } - return sampleCounter.collect(); - } - -} diff --git a/examples/example-exporter-httpserver/src/main/java/io/prometheus/metrics/examples/httpserver/SampleExtendedMultiCollector.java b/examples/example-exporter-httpserver/src/main/java/io/prometheus/metrics/examples/httpserver/SampleExtendedMultiCollector.java deleted file mode 100644 index 8b325b17e..000000000 --- a/examples/example-exporter-httpserver/src/main/java/io/prometheus/metrics/examples/httpserver/SampleExtendedMultiCollector.java +++ /dev/null @@ -1,56 +0,0 @@ -package io.prometheus.metrics.examples.httpserver; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import io.prometheus.metrics.core.metrics.Counter; -import io.prometheus.metrics.core.metrics.Gauge; -import io.prometheus.metrics.model.registry.ExtendedMultiCollector; -import io.prometheus.metrics.model.registry.PrometheusScrapeRequest; -import io.prometheus.metrics.model.snapshots.MetricSnapshot; -import io.prometheus.metrics.model.snapshots.MetricSnapshots; - -public class SampleExtendedMultiCollector extends ExtendedMultiCollector { - - public SampleExtendedMultiCollector() { - super(); - } - - @Override - protected MetricSnapshots collectMetricSnapshots(PrometheusScrapeRequest scrapeRequest) { - Counter sampleCounter; - Gauge sampleGauge; - sampleCounter = Counter.builder().name("x_calls_total").labelNames("target", "proc").build(); - sampleGauge = Gauge.builder().name("x_load").labelNames("target", "proc").build(); - String[] targetName = scrapeRequest.getParameterValues("target"); - String[] procs = scrapeRequest.getParameterValues("proc"); - if (targetName == null || targetName.length == 0) { - sampleCounter.labelValues("defaultTarget", "defaultProc").inc(); - sampleGauge.labelValues("defaultTarget", "defaultProc").set(Math.random()); - } else { - if (procs == null || procs.length == 0) { - sampleCounter.labelValues(targetName[0], "defaultProc").inc(Math.random()); - sampleGauge.labelValues(targetName[0], "defaultProc").set(Math.random()); - } else { - for (int i = 0; i < procs.length; i++) { - sampleCounter.labelValues(targetName[0], procs[i]).inc(Math.random()); - sampleGauge.labelValues(targetName[0], procs[i]).set(Math.random()); - } - - } - } - Collection snaps = new ArrayList(); - snaps.add(sampleCounter.collect()); - snaps.add(sampleGauge.collect()); - MetricSnapshots msnaps = new MetricSnapshots(snaps); - return msnaps; - } - - public List getPrometheusNames() { - List names = new ArrayList(); - names.add("Multi"); - return names ; - } - -} diff --git a/examples/example-exporter-multi-target/README.md b/examples/example-exporter-multi-target/README.md new file mode 100644 index 000000000..b21f9ce9e --- /dev/null +++ b/examples/example-exporter-multi-target/README.md @@ -0,0 +1,33 @@ +# Multi-Target pattern example + +## Build + +This example is built as part of the `client_java` project. + +``` +./mvnw package +``` + +## Run + +The build creates a JAR file with the example application in `./examples/example-exporter-multi-target/target/`. + +``` +java -jar ./examples/example-exporter-multi-target/target/example-exporter-multi-target.jar +``` + +## Manually testing the Metrics Endpoint + +Accessing [http://localhost:9400/metrics](http://localhost:9400/metrics) with a Web browser should yield an example of a counter metric. + +``` +# HELP uptime_seconds_total total number of seconds since this application was started +# TYPE uptime_seconds_total counter +uptime_seconds_total 301.0 +``` + +The exporter supports a `debug` URL parameter to quickly view other formats in your Web browser: + +* [http://localhost:9400/metrics?debug=text](http://localhost:9400/metrics?debug=text): Prometheus text format, same as without the `debug` option. +* [http://localhost:9400/metrics?debug=openmetrics](http://localhost:9400/metrics?debug=openmetrics): OpenMetrics text format. +* [http://localhost:9400/metrics?debug=prometheus-protobuf](http://localhost:9400/metrics?debug=prometheus-protobuf): Text representation of the Prometheus protobuf format. diff --git a/examples/example-exporter-multi-target/pom.xml b/examples/example-exporter-multi-target/pom.xml new file mode 100644 index 000000000..fa9d1a94a --- /dev/null +++ b/examples/example-exporter-multi-target/pom.xml @@ -0,0 +1,76 @@ + + + 4.0.0 + + + io.prometheus + examples + 1.1.0-SNAPSHOT + + + example-exporter-multi-target + + Example - HTTPServer Exporter Multi Target + + Prometheus Metrics Example for multi-target pattern implementation + + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + + fstab + Guido Anzuoni + ganzuoni@gmail.com + + + + + + io.prometheus + prometheus-metrics-core + ${project.version} + + + io.prometheus + prometheus-metrics-instrumentation-jvm + ${project.version} + + + io.prometheus + prometheus-metrics-exporter-httpserver + ${project.version} + + + + + ${project.artifactId} + + + org.apache.maven.plugins + maven-shade-plugin + + + package + + shade + + + + + io.prometheus.metrics.examples.multitarget.Main + + + + + + + + + diff --git a/examples/example-exporter-httpserver/src/main/java/io/prometheus/metrics/examples/httpserver/MainMultiTarget.java b/examples/example-exporter-multi-target/src/main/java/io/prometheus/metrics/examples/multitarget/Main.java similarity index 74% rename from examples/example-exporter-httpserver/src/main/java/io/prometheus/metrics/examples/httpserver/MainMultiTarget.java rename to examples/example-exporter-multi-target/src/main/java/io/prometheus/metrics/examples/multitarget/Main.java index affa038c2..bb96fac0f 100644 --- a/examples/example-exporter-httpserver/src/main/java/io/prometheus/metrics/examples/httpserver/MainMultiTarget.java +++ b/examples/example-exporter-multi-target/src/main/java/io/prometheus/metrics/examples/multitarget/Main.java @@ -1,4 +1,4 @@ -package io.prometheus.metrics.examples.httpserver; +package io.prometheus.metrics.examples.multitarget; import java.io.IOException; @@ -8,16 +8,14 @@ /** * Simple example of an application exposing metrics via Prometheus' built-in HTTPServer. */ -public class MainMultiTarget { +public class Main { public static void main(String[] args) throws IOException, InterruptedException { - SampleExtendedCollector xc = new SampleExtendedCollector(); - PrometheusRegistry.defaultRegistry.register(xc); SampleExtendedMultiCollector xmc = new SampleExtendedMultiCollector(); PrometheusRegistry.defaultRegistry.register(xmc); HTTPServer server = HTTPServer.builder() - .port(9400) + .port(9401) .buildAndStart(); System.out.println("HTTPServer listening on port http://localhost:" + server.getPort() + "/metrics"); diff --git a/examples/example-exporter-multi-target/src/main/java/io/prometheus/metrics/examples/multitarget/SampleExtendedMultiCollector.java b/examples/example-exporter-multi-target/src/main/java/io/prometheus/metrics/examples/multitarget/SampleExtendedMultiCollector.java new file mode 100644 index 000000000..ff36c65a4 --- /dev/null +++ b/examples/example-exporter-multi-target/src/main/java/io/prometheus/metrics/examples/multitarget/SampleExtendedMultiCollector.java @@ -0,0 +1,79 @@ +package io.prometheus.metrics.examples.multitarget; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import io.prometheus.metrics.model.registry.ExtendedMultiCollector; +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 SampleExtendedMultiCollector extends ExtendedMultiCollector { + + public SampleExtendedMultiCollector() { + super(); + } + + @Override + protected MetricSnapshots collectMetricSnapshots(PrometheusScrapeRequest scrapeRequest) { + + GaugeSnapshot.Builder gaugeBuilder = GaugeSnapshot.builder(); + gaugeBuilder.name("x_load").help("process load"); + + CounterSnapshot.Builder counterBuilder = CounterSnapshot.builder(); + counterBuilder.name(PrometheusNaming.sanitizeMetricName("x_calls_total")).help("invocations"); + + String[] targetNames = scrapeRequest.getParameterValues("target"); + String targetName; + String[] procs = scrapeRequest.getParameterValues("proc"); + if (targetNames == null || targetNames.length == 0) { + targetName = "defaultTarget"; + procs = null; //ignore procs param + } else { + targetName = targetNames[0]; + } + Builder counterDataPointBuilder = CounterSnapshot.CounterDataPointSnapshot.builder(); + io.prometheus.metrics.model.snapshots.GaugeSnapshot.GaugeDataPointSnapshot.Builder gaugeDataPointBuilder = GaugeSnapshot.GaugeDataPointSnapshot.builder(); + Labels lbls = Labels.of("target", targetName); + + if (procs == null || procs.length == 0) { + counterDataPointBuilder.labels(lbls.merge(Labels.of("proc", "defaultProc"))); + gaugeDataPointBuilder.labels(lbls.merge(Labels.of("proc", "defaultProc"))); + counterDataPointBuilder.value(70); + gaugeDataPointBuilder.value(Math.random()); + + counterBuilder.dataPoint(counterDataPointBuilder.build()); + gaugeBuilder.dataPoint(gaugeDataPointBuilder.build()); + + } else { + for (int i = 0; i < procs.length; i++) { + counterDataPointBuilder.labels(lbls.merge(Labels.of("proc", procs[i]))); + gaugeDataPointBuilder.labels(lbls.merge(Labels.of("proc", procs[i]))); + counterDataPointBuilder.value(Math.random()); + gaugeDataPointBuilder.value(Math.random()); + + counterBuilder.dataPoint(counterDataPointBuilder.build()); + gaugeBuilder.dataPoint(gaugeDataPointBuilder.build()); + } + } + Collection snaps = new ArrayList(); + snaps.add(counterBuilder.build()); + snaps.add(gaugeBuilder.build()); + MetricSnapshots msnaps = new MetricSnapshots(snaps); + return msnaps; + } + + public List getPrometheusNames() { + List names = new ArrayList(); + names.add("x_calls_total"); + names.add("x_load"); + return names; + } + +} diff --git a/examples/pom.xml b/examples/pom.xml index 0b293cedc..61db27a97 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -36,6 +36,7 @@ example-exemplars-tail-sampling example-exporter-servlet-tomcat example-exporter-httpserver + example-exporter-multi-target example-exporter-opentelemetry example-simpleclient-bridge example-native-histogram 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 4d22a940a..0c69a89a9 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 @@ -24,8 +24,7 @@ public interface Collector { * Override to implement request dependent logic to provide MetricSnapshot */ default MetricSnapshot collect(PrometheusScrapeRequest scrapeRequest) { - MetricSnapshot result = collect(); - return result; + return collect(); } /** @@ -43,7 +42,7 @@ default MetricSnapshot collect(Predicate includedNames) { } /** - * Like {@link #collect(includedNames)}, but with support for multi-target pattern. + * 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. */ diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/ExtendedCollector.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/ExtendedCollector.java deleted file mode 100644 index 42b314620..000000000 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/ExtendedCollector.java +++ /dev/null @@ -1,30 +0,0 @@ -package io.prometheus.metrics.model.registry; - -import io.prometheus.metrics.model.snapshots.MetricSnapshot; - -public abstract class ExtendedCollector implements Collector { - - @Override - public MetricSnapshot collect() { - return null; - } - - @Override - public MetricSnapshot collect(PrometheusScrapeRequest scrapeRequest) { - return collectMetricSnapshot(scrapeRequest); - } - - /** - * Override to implement multi-target exporter pattern - * @param scrapeRequest - * the (http) request context triggering metrics collection - */ - protected abstract MetricSnapshot collectMetricSnapshot(PrometheusScrapeRequest scrapeRequest); - - @Override - public String getPrometheusName() { - return null; - } - - -} 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 index 4280ef3a1..5434c0ec0 100644 --- 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 @@ -23,8 +23,7 @@ public interface MultiCollector { * Override to implement request dependent logic to provide MetricSnapshot */ default MetricSnapshots collect(PrometheusScrapeRequest scrapeRequest) { - MetricSnapshots result = collect(); - return result; + return collect(); } @@ -34,23 +33,16 @@ default MetricSnapshots collect(PrometheusScrapeRequest scrapeRequest) { * 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) { - MetricSnapshots allSnapshots = collect(); - MetricSnapshots.Builder result = MetricSnapshots.builder(); - for (MetricSnapshot snapshot : allSnapshots) { - if (includedNames.test(snapshot.getMetadata().getPrometheusName())) { - result.metricSnapshot(snapshot); - } - } - return result.build(); + return collect(includedNames, null); } /** - * Like {@link #collect(includedNames)}, but with support for multi-target pattern. + * 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 = collect(scrapeRequest); + MetricSnapshots allSnapshots = scrapeRequest == null ? collect(): collect(scrapeRequest); MetricSnapshots.Builder result = MetricSnapshots.builder(); for (MetricSnapshot snapshot : allSnapshots) { if (includedNames.test(snapshot.getMetadata().getPrometheusName())) { 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 0aab165a3..a8ab5a762 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,7 +1,6 @@ package io.prometheus.metrics.model.registry; -import io.prometheus.metrics.model.snapshots.MetricSnapshot; -import io.prometheus.metrics.model.snapshots.MetricSnapshots; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.prometheusName; import java.util.List; import java.util.Set; @@ -9,161 +8,119 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Predicate; -import static io.prometheus.metrics.model.snapshots.PrometheusNaming.prometheusName; +import io.prometheus.metrics.model.snapshots.MetricSnapshot; +import io.prometheus.metrics.model.snapshots.MetricSnapshots; public class PrometheusRegistry { - public static final PrometheusRegistry defaultRegistry = new PrometheusRegistry(); + 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<>(); + 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(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 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(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)); - } - } + public void unregister(MultiCollector collector) { + multiCollectors.remove(collector); + for (String prometheusName : collector.getPrometheusNames()) { + prometheusNames.remove(prometheusName(prometheusName)); + } + } - public MetricSnapshots scrape() { - MetricSnapshots.Builder result = MetricSnapshots.builder(); - for (Collector collector : collectors) { - MetricSnapshot snapshot = collector.collect(); - 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) { - for (MetricSnapshot snapshot : collector.collect()) { - if (result.containsMetricName(snapshot.getMetadata().getName())) { - throw new IllegalStateException(snapshot.getMetadata().getPrometheusName() + ": duplicate metric name."); - } - result.metricSnapshot(snapshot); - } - } - return result.build(); - } + public MetricSnapshots scrape() { + return scrape((PrometheusScrapeRequest) null); + } - public MetricSnapshots scrape(PrometheusScrapeRequest scrapeRequest) { - MetricSnapshots.Builder result = MetricSnapshots.builder(); - for (Collector collector : collectors) { - MetricSnapshot snapshot = 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) { - for (MetricSnapshot snapshot : collector.collect(scrapeRequest)) { - if (result.containsMetricName(snapshot.getMetadata().getName())) { - throw new IllegalStateException(snapshot.getMetadata().getPrometheusName() + ": duplicate metric name."); - } - result.metricSnapshot(snapshot); - } - } - return result.build(); - } + 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(); + } - - public MetricSnapshots scrape(Predicate includedNames) { - if (includedNames == null) { - return scrape(); - } - MetricSnapshots.Builder result = MetricSnapshots.builder(); - for (Collector collector : collectors) { - String prometheusName = collector.getPrometheusName(); - if (prometheusName == null || includedNames.test(prometheusName)) { - MetricSnapshot snapshot = collector.collect(includedNames); - if (snapshot != null) { - result.metricSnapshot(snapshot); - } - } - } - for (MultiCollector collector : multiCollectors) { - List prometheusNames = collector.getPrometheusNames(); - boolean excluded = prometheusNames.size() > 0; // the multi-collector is excluded unless at least one name matches - for (String prometheusName : prometheusNames) { - if (includedNames.test(prometheusName)) { - excluded = false; - break; - } - } - if (!excluded) { - for (MetricSnapshot snapshot : collector.collect(includedNames)) { - if (snapshot != null) { - result.metricSnapshot(snapshot); - } - } - } - } - return result.build(); - } + public MetricSnapshots scrape(Predicate includedNames) { + if (includedNames == null) { + return scrape(); + } + return scrape(includedNames, null); + } - public MetricSnapshots scrape(Predicate includedNames, PrometheusScrapeRequest scrapeRequest) { - if (includedNames == null) { - return scrape(scrapeRequest); - } - MetricSnapshots.Builder result = MetricSnapshots.builder(); - for (Collector collector : collectors) { - String prometheusName = collector.getPrometheusName(); - if (prometheusName == null || includedNames.test(prometheusName)) { - MetricSnapshot snapshot = collector.collect(includedNames, scrapeRequest); - if (snapshot != null) { - result.metricSnapshot(snapshot); - } - } - } - for (MultiCollector collector : multiCollectors) { - List prometheusNames = collector.getPrometheusNames(); - boolean excluded = true; // the multi-collector is excluded unless at least one name matches - for (String prometheusName : prometheusNames) { - if (includedNames.test(prometheusName)) { - excluded = false; - break; - } - } - if (!excluded) { - for (MetricSnapshot snapshot : collector.collect(includedNames, scrapeRequest)) { - if (snapshot != null) { - result.metricSnapshot(snapshot); - } - } - } - } - return result.build(); - } + public MetricSnapshots scrape(Predicate includedNames, PrometheusScrapeRequest scrapeRequest) { + if (includedNames == null) { + return scrape(scrapeRequest); + } + MetricSnapshots.Builder result = MetricSnapshots.builder(); + for (Collector collector : collectors) { + String prometheusName = collector.getPrometheusName(); + 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(); + boolean excluded = true; // the multi-collector is excluded unless + // at least one name matches + for (String prometheusName : prometheusNames) { + if (includedNames.test(prometheusName)) { + excluded = false; + break; + } + } + if (!excluded) { + MetricSnapshots snaphots = scrapeRequest == null ? collector.collect(includedNames) : collector.collect(includedNames, scrapeRequest); + for (MetricSnapshot snapshot : snaphots) { + if (snapshot != null) { + result.metricSnapshot(snapshot); + } + } + } + } + return result.build(); + } } From c871a04fdd465e6624edeb5fc43e60c43efac504 Mon Sep 17 00:00:00 2001 From: Guido Anzuoni Date: Thu, 26 Oct 2023 15:10:41 +0200 Subject: [PATCH 5/6] Refactoring to let PrometheusHttpRequest extends PrometheusScrapeRequest Signed-off-by: Guido Anzuoni --- .../common/PrometheusHttpRequest.java | 9 +++---- .../common/PrometheusHttpScrapeRequest.java | 24 ------------------- .../common/PrometheusScrapeHandler.java | 5 ++-- .../httpserver/HttpExchangeAdapter.java | 13 ---------- .../servlet/jakarta/HttpExchangeAdapter.java | 18 -------------- .../registry/PrometheusScrapeRequest.java | 2 -- 6 files changed, 5 insertions(+), 66 deletions(-) delete mode 100644 prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusHttpScrapeRequest.java diff --git a/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusHttpRequest.java b/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusHttpRequest.java index 3f5fd5378..f7b5346a5 100644 --- a/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusHttpRequest.java +++ b/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusHttpRequest.java @@ -5,13 +5,10 @@ import java.util.ArrayList; import java.util.Enumeration; -public interface PrometheusHttpRequest { +import io.prometheus.metrics.model.registry.PrometheusScrapeRequest; + +public interface PrometheusHttpRequest extends PrometheusScrapeRequest { - /** - * Return the absolute path of a Http Request - */ - String getRequestPath(); - /** * See {@code jakarta.servlet.http.HttpServletRequest.getQueryString()} */ diff --git a/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusHttpScrapeRequest.java b/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusHttpScrapeRequest.java deleted file mode 100644 index c80a7bd99..000000000 --- a/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusHttpScrapeRequest.java +++ /dev/null @@ -1,24 +0,0 @@ -package io.prometheus.metrics.exporter.common; - -import io.prometheus.metrics.model.registry.PrometheusScrapeRequest; - -public class PrometheusHttpScrapeRequest implements PrometheusScrapeRequest { - - protected PrometheusHttpRequest prometheusHttpRequest; - - public PrometheusHttpScrapeRequest(PrometheusHttpRequest prometheusHttpRequest) { - super(); - this.prometheusHttpRequest = prometheusHttpRequest; - } - - @Override - public String getRequestURI() { - return prometheusHttpRequest.getRequestPath(); - } - - @Override - public String[] getParameterValues(String name) { - return prometheusHttpRequest.getParameterValues(name); - } - -} 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 0f7e1e1e1..5155457df 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 @@ -104,13 +104,12 @@ private Predicate makeNameFilter(ExporterFilterProperties props) { } private MetricSnapshots scrape(PrometheusHttpRequest request) { - PrometheusHttpScrapeRequest scrapeRequest = new PrometheusHttpScrapeRequest(request); Predicate filter = makeNameFilter(request.getParameterValues("name[]")); if (filter != null) { - return registry.scrape(filter, scrapeRequest); + return registry.scrape(filter, request); } else { - return registry.scrape(scrapeRequest); + return registry.scrape(request); } } diff --git a/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/HttpExchangeAdapter.java b/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/HttpExchangeAdapter.java index c0422c461..daaeb2dbd 100644 --- a/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/HttpExchangeAdapter.java +++ b/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/HttpExchangeAdapter.java @@ -30,19 +30,6 @@ public HttpExchangeAdapter(HttpExchange httpExchange) { public class HttpRequest implements PrometheusHttpRequest { - - - @Override - public String getRequestPath() { - URI requestURI = httpExchange.getRequestURI(); - String uri = requestURI.toString(); - int qx = uri.indexOf('?'); - if (qx != -1) { - uri = uri.substring(0, qx); - } - return uri; - } - @Override public String getQueryString() { return httpExchange.getRequestURI().getRawQuery(); diff --git a/prometheus-metrics-exporter-servlet-jakarta/src/main/java/io/prometheus/metrics/exporter/servlet/jakarta/HttpExchangeAdapter.java b/prometheus-metrics-exporter-servlet-jakarta/src/main/java/io/prometheus/metrics/exporter/servlet/jakarta/HttpExchangeAdapter.java index ece3d1519..c55eb7967 100644 --- a/prometheus-metrics-exporter-servlet-jakarta/src/main/java/io/prometheus/metrics/exporter/servlet/jakarta/HttpExchangeAdapter.java +++ b/prometheus-metrics-exporter-servlet-jakarta/src/main/java/io/prometheus/metrics/exporter/servlet/jakarta/HttpExchangeAdapter.java @@ -53,24 +53,6 @@ public Request(HttpServletRequest request) { this.request = request; } - @Override - public String getRequestPath() { - StringBuffer uri = new StringBuffer(); - String contextPath = request.getContextPath(); - if (contextPath.startsWith("/")) { - uri.append(contextPath); - } - String servletPath = request.getServletPath(); - if (servletPath.startsWith("/")) { - uri.append(servletPath); - } - String pathInfo = request.getPathInfo(); - if (pathInfo != null) { - uri.append(pathInfo); - } - return uri.toString(); - } - @Override public String getQueryString() { return request.getQueryString(); 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 71aaf31d2..268a326e1 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 @@ -6,8 +6,6 @@ */ public interface PrometheusScrapeRequest { - String getRequestURI(); - String[] getParameterValues(String name); } From f4c3f890f04ca924f42ec250fdbc8fd06bcc55ae Mon Sep 17 00:00:00 2001 From: Guido Anzuoni Date: Mon, 30 Oct 2023 09:00:15 +0100 Subject: [PATCH 6/6] Remove abstract class to keep the model simple Signed-off-by: Guido Anzuoni --- .../metrics/examples/multitarget/Main.java | 2 +- ...llector.java => SampleMultiCollector.java} | 15 ++++++++--- .../registry/ExtendedMultiCollector.java | 25 ------------------- 3 files changed, 13 insertions(+), 29 deletions(-) rename examples/example-exporter-multi-target/src/main/java/io/prometheus/metrics/examples/multitarget/{SampleExtendedMultiCollector.java => SampleMultiCollector.java} (88%) delete mode 100644 prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/ExtendedMultiCollector.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 bb96fac0f..da36346b9 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 { - SampleExtendedMultiCollector xmc = new SampleExtendedMultiCollector(); + SampleMultiCollector xmc = new SampleMultiCollector(); 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/SampleExtendedMultiCollector.java b/examples/example-exporter-multi-target/src/main/java/io/prometheus/metrics/examples/multitarget/SampleMultiCollector.java similarity index 88% rename from examples/example-exporter-multi-target/src/main/java/io/prometheus/metrics/examples/multitarget/SampleExtendedMultiCollector.java rename to examples/example-exporter-multi-target/src/main/java/io/prometheus/metrics/examples/multitarget/SampleMultiCollector.java index ff36c65a4..819bb3028 100644 --- a/examples/example-exporter-multi-target/src/main/java/io/prometheus/metrics/examples/multitarget/SampleExtendedMultiCollector.java +++ b/examples/example-exporter-multi-target/src/main/java/io/prometheus/metrics/examples/multitarget/SampleMultiCollector.java @@ -4,7 +4,7 @@ import java.util.Collection; import java.util.List; -import io.prometheus.metrics.model.registry.ExtendedMultiCollector; +import io.prometheus.metrics.model.registry.MultiCollector; import io.prometheus.metrics.model.registry.PrometheusScrapeRequest; import io.prometheus.metrics.model.snapshots.CounterSnapshot; import io.prometheus.metrics.model.snapshots.CounterSnapshot.CounterDataPointSnapshot.Builder; @@ -14,13 +14,22 @@ import io.prometheus.metrics.model.snapshots.MetricSnapshots; import io.prometheus.metrics.model.snapshots.PrometheusNaming; -public class SampleExtendedMultiCollector extends ExtendedMultiCollector { +public class SampleMultiCollector implements MultiCollector { - public SampleExtendedMultiCollector() { + public SampleMultiCollector() { super(); } + + @Override + public MetricSnapshots collect() { + return new MetricSnapshots(); + } @Override + public MetricSnapshots collect(PrometheusScrapeRequest scrapeRequest) { + return collectMetricSnapshots(scrapeRequest); + } + protected MetricSnapshots collectMetricSnapshots(PrometheusScrapeRequest scrapeRequest) { GaugeSnapshot.Builder gaugeBuilder = GaugeSnapshot.builder(); diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/ExtendedMultiCollector.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/ExtendedMultiCollector.java deleted file mode 100644 index 79fda7e1f..000000000 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/ExtendedMultiCollector.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.prometheus.metrics.model.registry; - -import io.prometheus.metrics.model.snapshots.MetricSnapshots; - -public abstract class ExtendedMultiCollector implements MultiCollector { - - @Override - public MetricSnapshots collect() { - return new MetricSnapshots(); - } - - @Override - public MetricSnapshots collect(PrometheusScrapeRequest scrapeRequest) { - return collectMetricSnapshots(scrapeRequest); - } - - /** - * Override to implement multi-target exporter pattern - * @param scrapeRequest - * the (http) request context triggering metrics collection - */ - protected abstract MetricSnapshots collectMetricSnapshots(PrometheusScrapeRequest scrapeRequest); - - -}