Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support multi-target pattern #873

Merged
merged 7 commits into from Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
50 changes: 50 additions & 0 deletions docs/content/getting-started/multi-target.md
@@ -0,0 +1,50 @@
---
title: Multi-Target Pattern
weight: 7
---

ganzuoni marked this conversation as resolved.
Show resolved Hide resolved
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);
}

```

ganzuoni marked this conversation as resolved.
Show resolved Hide resolved
@@ -0,0 +1,25 @@
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 {
ganzuoni marked this conversation as resolved.
Show resolved Hide resolved

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

System.out.println("HTTPServer listening on port http://localhost:" + server.getPort() + "/metrics");
}
}
@@ -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 {
ganzuoni marked this conversation as resolved.
Show resolved Hide resolved

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

}
@@ -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) {
ganzuoni marked this conversation as resolved.
Show resolved Hide resolved
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<MetricSnapshot> snaps = new ArrayList<MetricSnapshot>();
snaps.add(sampleCounter.collect());
snaps.add(sampleGauge.collect());
MetricSnapshots msnaps = new MetricSnapshots(snaps);
return msnaps;
}

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

}
Expand Up @@ -7,6 +7,11 @@

public interface PrometheusHttpRequest {

/**
* Return the absolute path of a Http Request
*/
String getRequestPath();
ganzuoni marked this conversation as resolved.
Show resolved Hide resolved

/**
* See {@code jakarta.servlet.http.HttpServletRequest.getQueryString()}
*/
Expand Down
@@ -0,0 +1,24 @@
package io.prometheus.metrics.exporter.common;

import io.prometheus.metrics.model.registry.PrometheusScrapeRequest;

public class PrometheusHttpScrapeRequest implements PrometheusScrapeRequest {
ganzuoni marked this conversation as resolved.
Show resolved Hide resolved

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

}
Expand Up @@ -104,11 +104,13 @@ private Predicate<String> makeNameFilter(ExporterFilterProperties props) {
}

private MetricSnapshots scrape(PrometheusHttpRequest request) {
PrometheusHttpScrapeRequest scrapeRequest = new PrometheusHttpScrapeRequest(request);

Predicate<String> 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);
}
}

Expand Down
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,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();
}
Expand Down
Expand Up @@ -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();
}
Expand Down
Expand Up @@ -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();
ganzuoni marked this conversation as resolved.
Show resolved Hide resolved
return result;
}

/**
* Like {@link #collect()}, but returns {@code null} if {@code includedNames.test(name)} is {@code false}.
* <p>
Expand All @@ -33,6 +42,21 @@ default MetricSnapshot collect(Predicate<String> includedNames) {
}
}

/**
* Like {@link #collect(includedNames)}, but with support for multi-target pattern.
ganzuoni marked this conversation as resolved.
Show resolved Hide resolved
* <p>
* Override this if there is a more efficient way than first collecting the snapshot and then discarding it.
*/
default MetricSnapshot collect(Predicate<String> 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.
Expand Down
@@ -0,0 +1,30 @@
package io.prometheus.metrics.model.registry;

import io.prometheus.metrics.model.snapshots.MetricSnapshot;

public abstract class ExtendedCollector implements Collector {
ganzuoni marked this conversation as resolved.
Show resolved Hide resolved

@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;
}


}
@@ -0,0 +1,25 @@
package io.prometheus.metrics.model.registry;

import io.prometheus.metrics.model.snapshots.MetricSnapshots;

public abstract class ExtendedMultiCollector implements MultiCollector {
ganzuoni marked this conversation as resolved.
Show resolved Hide resolved

@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);


}