Skip to content

Commit

Permalink
Support multi-target pattern (#873)
Browse files Browse the repository at this point in the history
Add multi-target pattern support

Signed-off-by: Guido Anzuoni <ganzuoni@gmail.com>
  • Loading branch information
ganzuoni authored Oct 30, 2023
1 parent ee99bb8 commit cff40dd
Show file tree
Hide file tree
Showing 14 changed files with 503 additions and 97 deletions.
114 changes: 114 additions & 0 deletions docs/content/getting-started/multi-target.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
---
title: Multi-Target Pattern
weight: 7
---

{{< 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 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<MetricSnapshot> snaps = new ArrayList<MetricSnapshot>();
snaps.add(counterBuilder.build());
snaps.add(gaugeBuilder.build());
MetricSnapshots msnaps = new MetricSnapshots(snaps);
return msnaps;
}

public List<String> getPrometheusNames() {
List<String> names = new ArrayList<String>();
names.add("x_calls_total");
names.add("x_load");
return names;
}

}

```
`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);
}

```


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")
33 changes: 33 additions & 0 deletions examples/example-exporter-multi-target/README.md
Original file line number Diff line number Diff line change
@@ -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.
76 changes: 76 additions & 0 deletions examples/example-exporter-multi-target/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>io.prometheus</groupId>
<artifactId>examples</artifactId>
<version>1.1.0-SNAPSHOT</version>
</parent>

<artifactId>example-exporter-multi-target</artifactId>

<name>Example - HTTPServer Exporter Multi Target</name>
<description>
Prometheus Metrics Example for multi-target pattern implementation
</description>

<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>

<developers>
<developer>
<id>fstab</id>
<name>Guido Anzuoni</name>
<email>ganzuoni@gmail.com</email>
</developer>
</developers>

<dependencies>
<dependency>
<groupId>io.prometheus</groupId>
<artifactId>prometheus-metrics-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.prometheus</groupId>
<artifactId>prometheus-metrics-instrumentation-jvm</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.prometheus</groupId>
<artifactId>prometheus-metrics-exporter-httpserver</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>

<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>io.prometheus.metrics.examples.multitarget.Main</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.prometheus.metrics.examples.multitarget;

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 Main {

public static void main(String[] args) throws IOException, InterruptedException {

SampleMultiCollector xmc = new SampleMultiCollector();
PrometheusRegistry.defaultRegistry.register(xmc);
HTTPServer server = HTTPServer.builder()
.port(9401)
.buildAndStart();

System.out.println("HTTPServer listening on port http://localhost:" + server.getPort() + "/metrics");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package io.prometheus.metrics.examples.multitarget;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

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;
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();
}

@Override
public MetricSnapshots collect(PrometheusScrapeRequest scrapeRequest) {
return collectMetricSnapshots(scrapeRequest);
}

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<MetricSnapshot> snaps = new ArrayList<MetricSnapshot>();
snaps.add(counterBuilder.build());
snaps.add(gaugeBuilder.build());
MetricSnapshots msnaps = new MetricSnapshots(snaps);
return msnaps;
}

public List<String> getPrometheusNames() {
List<String> names = new ArrayList<String>();
names.add("x_calls_total");
names.add("x_load");
return names;
}

}
1 change: 1 addition & 0 deletions examples/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
<module>example-exemplars-tail-sampling</module>
<module>example-exporter-servlet-tomcat</module>
<module>example-exporter-httpserver</module>
<module>example-exporter-multi-target</module>
<module>example-exporter-opentelemetry</module>
<module>example-simpleclient-bridge</module>
<module>example-native-histogram</module>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
import java.util.ArrayList;
import java.util.Enumeration;

public interface PrometheusHttpRequest {
import io.prometheus.metrics.model.registry.PrometheusScrapeRequest;

public interface PrometheusHttpRequest extends PrometheusScrapeRequest {

/**
* See {@code jakarta.servlet.http.HttpServletRequest.getQueryString()}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,12 @@ private Predicate<String> makeNameFilter(ExporterFilterProperties props) {
}

private MetricSnapshots scrape(PrometheusHttpRequest request) {

Predicate<String> filter = makeNameFilter(request.getParameterValues("name[]"));
if (filter != null) {
return registry.scrape(filter);
return registry.scrape(filter, request);
} else {
return registry.scrape();
return registry.scrape(request);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -29,7 +30,7 @@ public HttpExchangeAdapter(HttpExchange httpExchange) {

public class HttpRequest implements PrometheusHttpRequest {

@Override
@Override
public String getQueryString() {
return httpExchange.getRequestURI().getRawQuery();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public Request(HttpServletRequest request) {
this.request = request;
}

@Override
@Override
public String getQueryString() {
return request.getQueryString();
}
Expand Down
Loading

0 comments on commit cff40dd

Please sign in to comment.