package io.prometheus.client.exporter;
import io.prometheus.client.Collector;
import io.prometheus.client.CollectorRegistry;
import io.prometheus.client.exporter.common.TextFormat;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
/**
* Export metrics via the Prometheus Pushgateway.
* <p>
* The Prometheus Pushgateway exists to allow ephemeral and batch jobs to expose their metrics to Prometheus.
* Since these kinds of jobs may not exist long enough to be scraped, they can instead push their metrics
* to a Pushgateway. This class allows pushing the contents of a {@link CollectorRegistry} to
* a Pushgateway.
* <p>
* Example usage:
* <pre>
* {@code
* void executeBatchJob() throws Exception {
* CollectorRegistry registry = new CollectorRegistry();
* Gauge duration = Gauge.build()
* .name("my_batch_job_duration_seconds").help("Duration of my batch job in seconds.").register(registry);
* Gauge.Timer durationTimer = duration.startTimer();
* try {
* // Your code here.
*
* // This is only added to the registry after success,
* // so that a previous success in the Pushgateway isn't overwritten on failure.
* Gauge lastSuccess = Gauge.build()
* .name("my_batch_job_last_success").help("Last time my batch job succeeded, in unixtime.").register(registry);
* lastSuccess.setToCurrentTime();
* } finally {
* durationTimer.setDuration();
* PushGateway pg = new PushGateway("127.0.0.1:9091");
* pg.pushAdd(registry, "my_batch_job");
* }
* }
* }
* </pre>
* <p>
* See <a href="https://github.com/prometheus/pushgateway">https://github.com/prometheus/pushgateway</a>
*/
public class PushGateway {
private final String address;
private static final int SECONDS_PER_MILLISECOND = 1000;
/**
* Construct a Pushgateway, with the given address.
* <p>
* @param address host:port or ip:port of the Pushgateway.
*/
public PushGateway(String address) {
this.address = address;
}
/**
* Pushes all metrics in a registry, replacing all those with the same job and no grouping key.
* <p>
* This uses the PUT HTTP method.
*/
public void push(CollectorRegistry registry, String job) throws IOException {
doRequest(registry, job, null, "PUT");
}
/**
* Pushes all metrics in a Collector, replacing all those with the same job and no grouping key.
* <p>
* This is useful for pushing a single Gauge.
* <p>
* This uses the PUT HTTP method.
*/
public void push(Collector collector, String job) throws IOException {
CollectorRegistry registry = new CollectorRegistry();
collector.register(registry);
push(registry, job);
}
/**
* Pushes all metrics in a registry, replacing all those with the same job and grouping key.
* <p>
* This uses the PUT HTTP method.
*/
public void push(CollectorRegistry registry, String job, Map<String, String> groupingKey) throws IOException {
doRequest(registry, job, groupingKey, "PUT");
}
/**
* Pushes all metrics in a Collector, replacing all those with the same job and grouping key.
* <p>
* This is useful for pushing a single Gauge.
* <p>
* This uses the PUT HTTP method.
*/
public void push(Collector collector, String job, Map<String, String> groupingKey) throws IOException {
CollectorRegistry registry = new CollectorRegistry();
collector.register(registry);
push(registry, job, groupingKey);
}
/**
* Pushes all metrics in a registry, replacing only previously pushed metrics of the same name and job and no grouping key.
* <p>
* This uses the POST HTTP method.
*/
public void pushAdd(CollectorRegistry registry, String job) throws IOException {
doRequest(registry, job, null, "POST");
}
/**
* Pushes all metrics in a Collector, replacing only previously pushed metrics of the same name and job and no grouping key.
* <p>
* This is useful for pushing a single Gauge.
* <p>
* This uses the POST HTTP method.
*/
public void pushAdd(Collector collector, String job) throws IOException {
CollectorRegistry registry = new CollectorRegistry();
collector.register(registry);
pushAdd(registry, job);
}
/**
* Pushes all metrics in a registry, replacing only previously pushed metrics of the same name, job and grouping key.
* <p>
* This uses the POST HTTP method.
*/
public void pushAdd(CollectorRegistry registry, String job, Map<String, String> groupingKey) throws IOException {
doRequest(registry, job, groupingKey, "POST");
}
/**
* Pushes all metrics in a Collector, replacing only previously pushed metrics of the same name, job and grouping key.
* <p>
* This is useful for pushing a single Gauge.
* <p>
* This uses the POST HTTP method.
*/
public void pushAdd(Collector collector, String job, Map<String, String> groupingKey) throws IOException {
CollectorRegistry registry = new CollectorRegistry();
collector.register(registry);
pushAdd(registry, job, groupingKey);
}
/**
* Deletes metrics from the Pushgateway.
* <p>
* Deletes metrics with no grouping key and the provided job.
* This uses the DELETE HTTP method.
*/
public void delete(String job) throws IOException {
doRequest(null, job, null, "DELETE");
}
/**
* Deletes metrics from the Pushgateway.
* <p>
* Deletes metrics with the provided job and grouping key.
* This uses the DELETE HTTP method.
*/
public void delete(String job, Map<String, String> groupingKey) throws IOException {
doRequest(null, job, groupingKey, "DELETE");
}
/**
* Pushes all metrics in a registry, replacing all those with the same job and instance.
* <p>
* This uses the PUT HTTP method.
* @deprecated use {@link #push(CollectorRegistry, String, Map)}
*/
@Deprecated
public void push(CollectorRegistry registry, String job, String instance) throws IOException {
push(registry, job, Collections.singletonMap("instance", instance));
}
/**
* Pushes all metrics in a Collector, replacing all those with the same job and instance.
* <p>
* This is useful for pushing a single Gauge.
* <p>
* This uses the PUT HTTP method.
* @deprecated use {@link #push(Collector, String, Map)}
*/
@Deprecated
public void push(Collector collector, String job, String instance) throws IOException {
push(collector, job, Collections.singletonMap("instance", instance));
}
/**
* Pushes all metrics in a registry, replacing only previously pushed metrics of the same name.
* <p>
* This uses the POST HTTP method.
* @deprecated use {@link #pushAdd(CollectorRegistry, String, Map)}
*/
@Deprecated
public void pushAdd(CollectorRegistry registry, String job, String instance) throws IOException {
pushAdd(registry, job, Collections.singletonMap("instance", instance));
}
/**
* Pushes all metrics in a Collector, replacing only previously pushed metrics of the same name.
* <p>
* This is useful for pushing a single Gauge.
* <p>
* This uses the POST HTTP method.
* @deprecated use {@link #pushAdd(Collector, String, Map)}
*/
@Deprecated
public void pushAdd(Collector collector, String job, String instance) throws IOException {
pushAdd(collector, job, Collections.singletonMap("instance", instance));
}
/**
* Deletes metrics from the Pushgateway.
* <p>
* This uses the DELETE HTTP method.
* @deprecated use {@link #delete(String, Map)}
*/
@Deprecated
public void delete(String job, String instance) throws IOException {
delete(job, Collections.singletonMap("instance", instance));
}
void doRequest(CollectorRegistry registry, String job, Map<String, String> groupingKey, String method) throws IOException {
String url = "http://" + address + "/metrics/job/" + URLEncoder.encode(job, "UTF-8");
if (groupingKey != null) {
for (Map.Entry<String, String> entry: groupingKey.entrySet()) {
url += "/" + entry.getKey() + "/" + URLEncoder.encode(entry.getValue(), "UTF-8");
}
}
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
connection.setRequestProperty("Content-Type", TextFormat.CONTENT_TYPE_004);
if (!method.equals("DELETE")) {
connection.setDoOutput(true);
}
connection.setRequestMethod(method);
connection.setConnectTimeout(10 * SECONDS_PER_MILLISECOND);
connection.setReadTimeout(10 * SECONDS_PER_MILLISECOND);
connection.connect();
try {
if (!method.equals("DELETE")) {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(connection.getOutputStream(), "UTF-8"));
TextFormat.write004(writer, registry.metricFamilySamples());
writer.flush();
writer.close();
}
int response = connection.getResponseCode();
if (response != HttpURLConnection.HTTP_ACCEPTED) {
throw new IOException("Response code from " + url + " was " + response);
}
} finally {
connection.disconnect();
}
}
/**
* Returns a grouping key with the instance label set to the machine's IP address.
* <p>
* This is a convenience function, and should only be used where you want to
* push per-instance metrics rather than cluster/job level metrics.
*/
public static Map<String, String> instanceIPGroupingKey() throws UnknownHostException {
Map<String, String> groupingKey = new HashMap<String, String>();
groupingKey.put("instance", InetAddress.getLocalHost().getHostAddress());
return groupingKey;
}
}