diff --git a/integration_tests/example_application/src/main/java/io/prometheus/client/smoketest/Server.java b/integration_tests/example_application/src/main/java/io/prometheus/client/smoketest/Server.java index 1a5f481f0..5630ac63b 100644 --- a/integration_tests/example_application/src/main/java/io/prometheus/client/smoketest/Server.java +++ b/integration_tests/example_application/src/main/java/io/prometheus/client/smoketest/Server.java @@ -1,5 +1,6 @@ package io.prometheus.client.smoketest; +import io.prometheus.client.CollectorRegistry; import io.prometheus.client.Counter; import io.prometheus.client.exporter.HTTPServer; import io.prometheus.client.hotspot.DefaultExports; @@ -19,6 +20,7 @@ public static void main(String[] args) throws IOException, InterruptedException .labelNames("path") .register(); counter.labels("/hello-world").inc(); + CollectorRegistry.defaultRegistry.metricFamilySamples(); new HTTPServer(9000); Thread.currentThread().join(); // sleep forever } diff --git a/pom.xml b/pom.xml index 2b4a05bde..3d0b571aa 100644 --- a/pom.xml +++ b/pom.xml @@ -62,6 +62,7 @@ simpleclient_bom benchmark integration_tests + simpleclient_nightingale_bridge @@ -224,8 +225,8 @@ org.apache.maven.plugins maven-compiler-plugin - 1.6 - 1.6 + 1.8 + 1.8 diff --git a/simpleclient/src/main/java/io/prometheus/client/CollectorRegistry.java b/simpleclient/src/main/java/io/prometheus/client/CollectorRegistry.java index 0d37cca27..98ddd5de8 100644 --- a/simpleclient/src/main/java/io/prometheus/client/CollectorRegistry.java +++ b/simpleclient/src/main/java/io/prometheus/client/CollectorRegistry.java @@ -31,6 +31,15 @@ public class CollectorRegistry { private final Map> collectorsToNames = new HashMap>(); private final Map namesToCollectors = new HashMap(); + public Map getGlobalTags() { + return globalTags; + } + + public void setGlobalTags(Map globalTags) { + this.globalTags = globalTags; + } + + private Map globalTags; private final boolean autoDescribe; public CollectorRegistry() { diff --git a/simpleclient_common/src/main/java/io/prometheus/client/exporter/common/TextFormat.java b/simpleclient_common/src/main/java/io/prometheus/client/exporter/common/TextFormat.java index db2de3a5b..d99a64187 100644 --- a/simpleclient_common/src/main/java/io/prometheus/client/exporter/common/TextFormat.java +++ b/simpleclient_common/src/main/java/io/prometheus/client/exporter/common/TextFormat.java @@ -12,292 +12,382 @@ import io.prometheus.client.Collector; public class TextFormat { - /** - * Content-type for Prometheus text version 0.0.4. - */ - public final static String CONTENT_TYPE_004 = "text/plain; version=0.0.4; charset=utf-8"; + /** + * Content-type for Prometheus text version 0.0.4. + */ + public final static String CONTENT_TYPE_004 = "text/plain; version=0.0.4; charset=utf-8"; - /** - * Content-type for Openmetrics text version 1.0.0. - * - * @since 0.10.0 - */ - public final static String CONTENT_TYPE_OPENMETRICS_100 = "application/openmetrics-text; version=1.0.0; charset=utf-8"; + /** + * Content-type for Openmetrics text version 1.0.0. + * + * @since 0.10.0 + */ + public final static String CONTENT_TYPE_OPENMETRICS_100 = "application/openmetrics-text; version=1.0.0; charset=utf-8"; - /** - * Return the content type that should be used for a given Accept HTTP header. - * - * @since 0.10.0 - */ - public static String chooseContentType(String acceptHeader) { - if (acceptHeader == null) { - return CONTENT_TYPE_004; - } + /** + * Return the content type that should be used for a given Accept HTTP header. + * + * @since 0.10.0 + */ + public static String chooseContentType(String acceptHeader) { + if (acceptHeader == null) { + return CONTENT_TYPE_004; + } - for (String accepts : acceptHeader.split(",")) { - if ("application/openmetrics-text".equals(accepts.split(";")[0].trim())) { - return CONTENT_TYPE_OPENMETRICS_100; - } - } + for (String accepts : acceptHeader.split(",")) { + if ("application/openmetrics-text".equals(accepts.split(";")[0].trim())) { + return CONTENT_TYPE_OPENMETRICS_100; + } + } - return CONTENT_TYPE_004; - } + return CONTENT_TYPE_004; + } - /** - * Write out the given MetricFamilySamples in a format per the contentType. - * - * @since 0.10.0 - */ - public static void writeFormat(String contentType, Writer writer, Enumeration mfs) throws IOException { - if (CONTENT_TYPE_004.equals(contentType)) { - write004(writer, mfs); - return; + /** + * Write out the given MetricFamilySamples in a format per the contentType. + * + * @since 0.10.0 + */ + public static void writeFormat(String contentType, Writer writer, Enumeration mfs) throws IOException { + if (CONTENT_TYPE_004.equals(contentType)) { + write004(writer, mfs); + return; + } + if (CONTENT_TYPE_OPENMETRICS_100.equals(contentType)) { + writeOpenMetrics100(writer, mfs); + return; + } + throw new IllegalArgumentException("Unknown contentType " + contentType); } - if (CONTENT_TYPE_OPENMETRICS_100.equals(contentType)) { - writeOpenMetrics100(writer, mfs); - return; + + public static void writeFormat(String contentType, Writer writer, Enumeration mfs, String globalTagsString) throws IOException { + if (CONTENT_TYPE_004.equals(contentType)) { + write004(writer, mfs, globalTagsString); + return; + } + if (CONTENT_TYPE_OPENMETRICS_100.equals(contentType)) { + writeOpenMetrics100(writer, mfs); + return; + } + throw new IllegalArgumentException("Unknown contentType " + contentType); } - throw new IllegalArgumentException("Unknown contentType " + contentType); - } - /** - * Write out the text version 0.0.4 of the given MetricFamilySamples. - */ - public static void write004(Writer writer, Enumeration mfs) throws IOException { - Map omFamilies = new TreeMap(); - /* See http://prometheus.io/docs/instrumenting/exposition_formats/ - * for the output format specification. */ - while(mfs.hasMoreElements()) { - Collector.MetricFamilySamples metricFamilySamples = mfs.nextElement(); - String name = metricFamilySamples.name; - writer.write("# HELP "); - writer.write(name); - if (metricFamilySamples.type == Collector.Type.COUNTER) { - writer.write("_total"); - } - if (metricFamilySamples.type == Collector.Type.INFO) { - writer.write("_info"); - } - writer.write(' '); - writeEscapedHelp(writer, metricFamilySamples.help); - writer.write('\n'); + /** + * Write out the text version 0.0.4 of the given MetricFamilySamples. + */ + public static void write004(Writer writer, Enumeration mfs) throws IOException { + Map omFamilies = new TreeMap(); + /* See http://prometheus.io/docs/instrumenting/exposition_formats/ + * for the output format specification. */ + while (mfs.hasMoreElements()) { + Collector.MetricFamilySamples metricFamilySamples = mfs.nextElement(); + String name = metricFamilySamples.name; + writer.write("# HELP "); + writer.write(name); + if (metricFamilySamples.type == Collector.Type.COUNTER) { + writer.write("_total"); + } + if (metricFamilySamples.type == Collector.Type.INFO) { + writer.write("_info"); + } + writer.write(' '); + writeEscapedHelp(writer, metricFamilySamples.help); + writer.write('\n'); - writer.write("# TYPE "); - writer.write(name); - if (metricFamilySamples.type == Collector.Type.COUNTER) { - writer.write("_total"); - } - if (metricFamilySamples.type == Collector.Type.INFO) { - writer.write("_info"); - } - writer.write(' '); - writer.write(typeString(metricFamilySamples.type)); - writer.write('\n'); + writer.write("# TYPE "); + writer.write(name); + if (metricFamilySamples.type == Collector.Type.COUNTER) { + writer.write("_total"); + } + if (metricFamilySamples.type == Collector.Type.INFO) { + writer.write("_info"); + } + writer.write(' '); + writer.write(typeString(metricFamilySamples.type)); + writer.write('\n'); - String createdName = name + "_created"; - String gcountName = name + "_gcount"; - String gsumName = name + "_gsum"; - for (Collector.MetricFamilySamples.Sample sample: metricFamilySamples.samples) { - /* OpenMetrics specific sample, put in a gauge at the end. */ - if (sample.name.equals(createdName) - || sample.name.equals(gcountName) - || sample.name.equals(gsumName)) { - Collector.MetricFamilySamples omFamily = omFamilies.get(sample.name); - if (omFamily == null) { - omFamily = new Collector.MetricFamilySamples(sample.name, Collector.Type.GAUGE, metricFamilySamples.help, new ArrayList()); - omFamilies.put(sample.name, omFamily); - } - omFamily.samples.add(sample); - continue; - } - writer.write(sample.name); - if (sample.labelNames.size() > 0) { - writer.write('{'); - for (int i = 0; i < sample.labelNames.size(); ++i) { - writer.write(sample.labelNames.get(i)); - writer.write("=\""); - writeEscapedLabelValue(writer, sample.labelValues.get(i)); - writer.write("\","); - } - writer.write('}'); + String createdName = name + "_created"; + String gcountName = name + "_gcount"; + String gsumName = name + "_gsum"; + for (Collector.MetricFamilySamples.Sample sample : metricFamilySamples.samples) { + /* OpenMetrics specific sample, put in a gauge at the end. */ + if (sample.name.equals(createdName) + || sample.name.equals(gcountName) + || sample.name.equals(gsumName)) { + Collector.MetricFamilySamples omFamily = omFamilies.get(sample.name); + if (omFamily == null) { + omFamily = new Collector.MetricFamilySamples(sample.name, Collector.Type.GAUGE, metricFamilySamples.help, new ArrayList()); + omFamilies.put(sample.name, omFamily); + } + omFamily.samples.add(sample); + continue; + } + writer.write(sample.name); + if (sample.labelNames.size() > 0) { + writer.write('{'); + for (int i = 0; i < sample.labelNames.size(); ++i) { + writer.write(sample.labelNames.get(i)); + writer.write("=\""); + writeEscapedLabelValue(writer, sample.labelValues.get(i)); + writer.write("\","); + } + writer.write('}'); + } + writer.write(' '); + writer.write(Collector.doubleToGoString(sample.value)); + if (sample.timestampMs != null) { + writer.write(' '); + writer.write(sample.timestampMs.toString()); + } + writer.write('\n'); + } } - writer.write(' '); - writer.write(Collector.doubleToGoString(sample.value)); - if (sample.timestampMs != null){ - writer.write(' '); - writer.write(sample.timestampMs.toString()); + // Write out any OM-specific samples. + if (!omFamilies.isEmpty()) { + write004(writer, Collections.enumeration(omFamilies.values())); } - writer.write('\n'); - } - } - // Write out any OM-specific samples. - if (!omFamilies.isEmpty()) { - write004(writer, Collections.enumeration(omFamilies.values())); } - } - private static void writeEscapedHelp(Writer writer, String s) throws IOException { - for (int i = 0; i < s.length(); i++) { - char c = s.charAt(i); - switch (c) { - case '\\': - writer.append("\\\\"); - break; - case '\n': - writer.append("\\n"); - break; - default: - writer.append(c); - } + public static void write004(Writer writer, Enumeration mfs, String tags) throws IOException { + Map omFamilies = new TreeMap(); + /* See http://prometheus.io/docs/instrumenting/exposition_formats/ + * for the output format specification. */ + while (mfs.hasMoreElements()) { + Collector.MetricFamilySamples metricFamilySamples = mfs.nextElement(); + String name = metricFamilySamples.name; + writer.write("# HELP "); + writer.write(name); + if (metricFamilySamples.type == Collector.Type.COUNTER) { + writer.write("_total"); + } + if (metricFamilySamples.type == Collector.Type.INFO) { + writer.write("_info"); + } + writer.write(' '); + writeEscapedHelp(writer, metricFamilySamples.help); + writer.write('\n'); + + writer.write("# TYPE "); + writer.write(name); + if (metricFamilySamples.type == Collector.Type.COUNTER) { + writer.write("_total"); + } + if (metricFamilySamples.type == Collector.Type.INFO) { + writer.write("_info"); + } + writer.write(' '); + writer.write(typeString(metricFamilySamples.type)); + writer.write('\n'); + + String createdName = name + "_created"; + String gcountName = name + "_gcount"; + String gsumName = name + "_gsum"; + for (Collector.MetricFamilySamples.Sample sample : metricFamilySamples.samples) { + /* OpenMetrics specific sample, put in a gauge at the end. */ + if (sample.name.equals(createdName) + || sample.name.equals(gcountName) + || sample.name.equals(gsumName)) { + Collector.MetricFamilySamples omFamily = omFamilies.get(sample.name); + if (omFamily == null) { + omFamily = new Collector.MetricFamilySamples(sample.name, Collector.Type.GAUGE, metricFamilySamples.help, new ArrayList()); + omFamilies.put(sample.name, omFamily); + } + omFamily.samples.add(sample); + continue; + } + writer.write(sample.name); + if (sample.labelNames.size() > 0) { + writer.write('{'); + writer.write(""); + if (tags != null) { + writer.write(tags); + writer.write(','); + } + for (int i = 0; i < sample.labelNames.size(); ++i) { + writer.write(sample.labelNames.get(i)); + writer.write("=\""); + writeEscapedLabelValue(writer, sample.labelValues.get(i)); + writer.write("\","); + } + writer.write('}'); + } + writer.write(' '); + writer.write(Collector.doubleToGoString(sample.value)); + if (sample.timestampMs != null) { + writer.write(' '); + writer.write(sample.timestampMs.toString()); + } + writer.write('\n'); + } + } + // Write out any OM-specific samples. + if (!omFamilies.isEmpty()) { + write004(writer, Collections.enumeration(omFamilies.values())); + } } - } - private static void writeEscapedLabelValue(Writer writer, String s) throws IOException { - for (int i = 0; i < s.length(); i++) { - char c = s.charAt(i); - switch (c) { - case '\\': - writer.append("\\\\"); - break; - case '\"': - writer.append("\\\""); - break; - case '\n': - writer.append("\\n"); - break; - default: - writer.append(c); - } + private static void writeEscapedHelp(Writer writer, String s) throws IOException { + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + switch (c) { + case '\\': + writer.append("\\\\"); + break; + case '\n': + writer.append("\\n"); + break; + default: + writer.append(c); + } + } } - } - private static String typeString(Collector.Type t) { - switch (t) { - case GAUGE: - return "gauge"; - case COUNTER: - return "counter"; - case SUMMARY: - return "summary"; - case HISTOGRAM: - return "histogram"; - case GAUGE_HISTOGRAM: - return "histogram"; - case STATE_SET: - return "gauge"; - case INFO: - return "gauge"; - default: - return "untyped"; + private static void writeEscapedLabelValue(Writer writer, String s) throws IOException { + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + switch (c) { + case '\\': + writer.append("\\\\"); + break; + case '\"': + writer.append("\\\""); + break; + case '\n': + writer.append("\\n"); + break; + default: + writer.append(c); + } + } } - } - /** - * Write out the OpenMetrics text version 1.0.0 of the given MetricFamilySamples. - * - * @since 0.10.0 - */ - public static void writeOpenMetrics100(Writer writer, Enumeration mfs) throws IOException { - while(mfs.hasMoreElements()) { - Collector.MetricFamilySamples metricFamilySamples = mfs.nextElement(); - String name = metricFamilySamples.name; + private static String typeString(Collector.Type t) { + switch (t) { + case GAUGE: + return "gauge"; + case COUNTER: + return "counter"; + case SUMMARY: + return "summary"; + case HISTOGRAM: + return "histogram"; + case GAUGE_HISTOGRAM: + return "histogram"; + case STATE_SET: + return "gauge"; + case INFO: + return "gauge"; + default: + return "untyped"; + } + } - writer.write("# TYPE "); - writer.write(name); - writer.write(' '); - writer.write(omTypeString(metricFamilySamples.type)); - writer.write('\n'); + /** + * Write out the OpenMetrics text version 1.0.0 of the given MetricFamilySamples. + * + * @since 0.10.0 + */ + public static void writeOpenMetrics100(Writer writer, Enumeration mfs) throws IOException { + while (mfs.hasMoreElements()) { + Collector.MetricFamilySamples metricFamilySamples = mfs.nextElement(); + String name = metricFamilySamples.name; - if (!metricFamilySamples.unit.isEmpty()) { - writer.write("# UNIT "); - writer.write(name); - writer.write(' '); - writer.write(metricFamilySamples.unit); - writer.write('\n'); - } + writer.write("# TYPE "); + writer.write(name); + writer.write(' '); + writer.write(omTypeString(metricFamilySamples.type)); + writer.write('\n'); - writer.write("# HELP "); - writer.write(name); - writer.write(' '); - writeEscapedLabelValue(writer, metricFamilySamples.help); - writer.write('\n'); - - for (Collector.MetricFamilySamples.Sample sample: metricFamilySamples.samples) { - writer.write(sample.name); - if (sample.labelNames.size() > 0) { - writer.write('{'); - for (int i = 0; i < sample.labelNames.size(); ++i) { - if (i > 0) { - writer.write(","); - } - writer.write(sample.labelNames.get(i)); - writer.write("=\""); - writeEscapedLabelValue(writer, sample.labelValues.get(i)); - writer.write("\""); - } - writer.write('}'); - } - writer.write(' '); - writer.write(Collector.doubleToGoString(sample.value)); - if (sample.timestampMs != null){ - writer.write(' '); - omWriteTimestamp(writer, sample.timestampMs); - } - if (sample.exemplar != null) { - writer.write(" # {"); - for (int i=0; i 0) { - writer.write(","); + if (!metricFamilySamples.unit.isEmpty()) { + writer.write("# UNIT "); + writer.write(name); + writer.write(' '); + writer.write(metricFamilySamples.unit); + writer.write('\n'); } - writer.write(sample.exemplar.getLabelName(i)); - writer.write("=\""); - writeEscapedLabelValue(writer, sample.exemplar.getLabelValue(i)); - writer.write("\""); - } - writer.write("} "); - writer.write(Collector.doubleToGoString(sample.exemplar.getValue())); - if (sample.exemplar.getTimestampMs() != null) { + + writer.write("# HELP "); + writer.write(name); writer.write(' '); - omWriteTimestamp(writer, sample.exemplar.getTimestampMs()); - } + writeEscapedLabelValue(writer, metricFamilySamples.help); + writer.write('\n'); + + for (Collector.MetricFamilySamples.Sample sample : metricFamilySamples.samples) { + writer.write(sample.name); + if (sample.labelNames.size() > 0) { + writer.write('{'); + for (int i = 0; i < sample.labelNames.size(); ++i) { + if (i > 0) { + writer.write(","); + } + writer.write(sample.labelNames.get(i)); + writer.write("=\""); + writeEscapedLabelValue(writer, sample.labelValues.get(i)); + writer.write("\""); + } + writer.write('}'); + } + writer.write(' '); + writer.write(Collector.doubleToGoString(sample.value)); + if (sample.timestampMs != null) { + writer.write(' '); + omWriteTimestamp(writer, sample.timestampMs); + } + if (sample.exemplar != null) { + writer.write(" # {"); + for (int i = 0; i < sample.exemplar.getNumberOfLabels(); i++) { + if (i > 0) { + writer.write(","); + } + writer.write(sample.exemplar.getLabelName(i)); + writer.write("=\""); + writeEscapedLabelValue(writer, sample.exemplar.getLabelValue(i)); + writer.write("\""); + } + writer.write("} "); + writer.write(Collector.doubleToGoString(sample.exemplar.getValue())); + if (sample.exemplar.getTimestampMs() != null) { + writer.write(' '); + omWriteTimestamp(writer, sample.exemplar.getTimestampMs()); + } + } + writer.write('\n'); + } } - writer.write('\n'); - } + writer.write("# EOF\n"); } - writer.write("# EOF\n"); - } - static void omWriteTimestamp(Writer writer, long timestampMs) throws IOException { - writer.write(Long.toString(timestampMs / 1000L)); - writer.write("."); - long ms = timestampMs % 1000; - if (ms < 100) { - writer.write("0"); - } - if (ms < 10) { - writer.write("0"); + static void omWriteTimestamp(Writer writer, long timestampMs) throws IOException { + writer.write(Long.toString(timestampMs / 1000L)); + writer.write("."); + long ms = timestampMs % 1000; + if (ms < 100) { + writer.write("0"); + } + if (ms < 10) { + writer.write("0"); + } + writer.write(Long.toString(timestampMs % 1000)); } - writer.write(Long.toString(timestampMs % 1000)); - } - private static String omTypeString(Collector.Type t) { - switch (t) { - case GAUGE: - return "gauge"; - case COUNTER: - return "counter"; - case SUMMARY: - return "summary"; - case HISTOGRAM: - return "histogram"; - case GAUGE_HISTOGRAM: - return "gaugehistogram"; - case STATE_SET: - return "stateset"; - case INFO: - return "info"; - default: - return "unknown"; + private static String omTypeString(Collector.Type t) { + switch (t) { + case GAUGE: + return "gauge"; + case COUNTER: + return "counter"; + case SUMMARY: + return "summary"; + case HISTOGRAM: + return "histogram"; + case GAUGE_HISTOGRAM: + return "gaugehistogram"; + case STATE_SET: + return "stateset"; + case INFO: + return "info"; + default: + return "unknown"; + } } - } } diff --git a/simpleclient_graphite_bridge/src/main/java/io/prometheus/client/bridge/Graphite.java b/simpleclient_graphite_bridge/src/main/java/io/prometheus/client/sdk/Graphite.java similarity index 98% rename from simpleclient_graphite_bridge/src/main/java/io/prometheus/client/bridge/Graphite.java rename to simpleclient_graphite_bridge/src/main/java/io/prometheus/client/sdk/Graphite.java index 5e292800c..120fa416f 100644 --- a/simpleclient_graphite_bridge/src/main/java/io/prometheus/client/bridge/Graphite.java +++ b/simpleclient_graphite_bridge/src/main/java/io/prometheus/client/sdk/Graphite.java @@ -1,4 +1,4 @@ -package io.prometheus.client.bridge; +package io.prometheus.client.sdk; import io.prometheus.client.Collector; import io.prometheus.client.CollectorRegistry; diff --git a/simpleclient_graphite_bridge/src/test/java/io/prometheus/client/bridge/GraphiteTest.java b/simpleclient_graphite_bridge/src/test/java/io/prometheus/client/sdk/GraphiteTest.java similarity index 95% rename from simpleclient_graphite_bridge/src/test/java/io/prometheus/client/bridge/GraphiteTest.java rename to simpleclient_graphite_bridge/src/test/java/io/prometheus/client/sdk/GraphiteTest.java index dbe6099ef..4a2fc3246 100644 --- a/simpleclient_graphite_bridge/src/test/java/io/prometheus/client/bridge/GraphiteTest.java +++ b/simpleclient_graphite_bridge/src/test/java/io/prometheus/client/sdk/GraphiteTest.java @@ -1,4 +1,4 @@ -package io.prometheus.client.bridge; +package io.prometheus.client.sdk; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -9,7 +9,6 @@ import io.prometheus.client.Gauge; import java.io.BufferedReader; -import java.io.IOException; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; diff --git a/simpleclient_hotspot/src/main/java/io/prometheus/client/hotspot/DefaultExports.java b/simpleclient_hotspot/src/main/java/io/prometheus/client/hotspot/DefaultExports.java index 79cd6ed5e..737978c6d 100644 --- a/simpleclient_hotspot/src/main/java/io/prometheus/client/hotspot/DefaultExports.java +++ b/simpleclient_hotspot/src/main/java/io/prometheus/client/hotspot/DefaultExports.java @@ -30,6 +30,8 @@ public static synchronized void initialize() { } } + + /** * Register the default Hotspot collectors with the given registry. */ diff --git a/simpleclient_httpserver/src/main/java/io/prometheus/client/exporter/HTTPServer.java b/simpleclient_httpserver/src/main/java/io/prometheus/client/exporter/HTTPServer.java index d6afdcd69..a08775b49 100644 --- a/simpleclient_httpserver/src/main/java/io/prometheus/client/exporter/HTTPServer.java +++ b/simpleclient_httpserver/src/main/java/io/prometheus/client/exporter/HTTPServer.java @@ -12,6 +12,7 @@ import java.nio.charset.Charset; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -19,6 +20,7 @@ import java.util.concurrent.FutureTask; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; import java.util.zip.GZIPOutputStream; import com.sun.net.httpserver.HttpExchange; @@ -34,7 +36,7 @@ * HTTPServer server = new HTTPServer(1234); * } * - * */ + */ public class HTTPServer { static { @@ -49,8 +51,7 @@ public class HTTPServer { private static class LocalByteArray extends ThreadLocal { @Override - protected ByteArrayOutputStream initialValue() - { + protected ByteArrayOutputStream initialValue() { return new ByteArrayOutputStream(1 << 20); } } @@ -62,9 +63,16 @@ public static class HTTPMetricHandler implements HttpHandler { private final CollectorRegistry registry; private final LocalByteArray response = new LocalByteArray(); private final static String HEALTHY_RESPONSE = "Exporter is Healthy."; + private String tagsString; HTTPMetricHandler(CollectorRegistry registry) { - this.registry = registry; + this.registry = registry; + Map tags = registry.getGlobalTags(); + if (tags != null && tags.size() > 0) { + tagsString = tags.entrySet().stream().map((e) -> + e.getKey() + "=\"" + e.getValue()+"\"" + ).collect(Collectors.joining(",")); + } } @Override @@ -81,7 +89,7 @@ public void handle(HttpExchange t) throws IOException { String contentType = TextFormat.chooseContentType(t.getRequestHeaders().getFirst("Accept")); t.getResponseHeaders().set("Content-Type", contentType); TextFormat.writeFormat(contentType, osw, - registry.filteredMetricFamilySamples(parseQuery(query))); + registry.filteredMetricFamilySamples(parseQuery(query)),tagsString); } osw.close(); diff --git a/simpleclient_sdk/pom.xml b/simpleclient_sdk/pom.xml new file mode 100644 index 000000000..2362298ad --- /dev/null +++ b/simpleclient_sdk/pom.xml @@ -0,0 +1,68 @@ + + + 4.0.0 + + + io.prometheus + parent + 0.11.1-SNAPSHOT + + + io.prometheus + simpleclient_nightingale_bridge + bundle + + Prometheus Java Simpleclient Nightingale Bridge + + Nightingale bridge for the simpleclient. + + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + + brian-brazil + Brian Brazil + brian.brazil@robustperception.io + + + + + + + io.prometheus + simpleclient + 0.11.1-SNAPSHOT + + + io.prometheus + simpleclient_httpserver + 0.11.1-SNAPSHOT + + + + com.alibaba + fastjson + 1.2.76 + + + + org.apache.httpcomponents + httpclient + 4.5.13 + + + + junit + junit + 4.13.2 + test + + + diff --git a/simpleclient_sdk/src/main/java/io/prometheus/client/sdk/Encoder.java b/simpleclient_sdk/src/main/java/io/prometheus/client/sdk/Encoder.java new file mode 100644 index 000000000..5fc105ee7 --- /dev/null +++ b/simpleclient_sdk/src/main/java/io/prometheus/client/sdk/Encoder.java @@ -0,0 +1,147 @@ +package io.prometheus.client.sdk; + +import io.prometheus.client.Collector; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +import static io.prometheus.client.sdk.StringEscapeUtils.escapeJson; + +public class Encoder { + private final HttpClient client; + private ArrayList samples; + private static final Logger logger = Logger.getLogger(Encoder.class.getName()); + private final String url; + private final int batchSize; + private final long stepS; + private final String nid; + private StringBuilder builder = new StringBuilder(); + private final Map tags; + private String tagStr; + + Encoder(String url, int batchSize, long stepS, String nid, Map tags) { + this.stepS = stepS; + this.nid = nid; + this.url = url; + this.client = HttpClients.createDefault(); + this.batchSize = batchSize; + this.samples = new ArrayList<>(batchSize); + this.tags = tags; + if (this.tags!=null){ + this.tagStr = this.tags.entrySet().stream().map((e) -> { + return escapeJson(e.getKey()) + "=" + escapeJson(e.getValue()); + }).collect(Collectors.joining(",")); + } + + + + } + + + void add(Collector.MetricFamilySamples.Sample sample) { + this.samples.add(sample); + } + + + protected Long generateTimestamp() { + return System.currentTimeMillis() / 1000; + } + + void writeMessage(StringBuilder sb, Collector.MetricFamilySamples.Sample sample,Long timestamp) { + if (sample.labelNames.size() != sample.labelValues.size()) { + return; + } + String type = "GAUGE"; + String name = sample.name; + sb.append("{\"").append("timestamp").append("\":").append(timestamp) + .append(",\"metric\":\"").append(escapeJson(name)).append('"') + .append(",\"counterType\":\"").append(type).append('"') + .append(",\"step\":").append(stepS); + sb.append(",\"nid\":\"").append(nid).append('"'); + writeTags(sb, sample); + sb.append(",\"value\":"); + sb.append(sample.value); + sb.append("}"); + } + + void writeTags(StringBuilder sb, Collector.MetricFamilySamples.Sample sample) { + if (sample.labelNames.size() == 0) { + return; + } + sb.append(",\"tags\":\""); + sb.append(this.tagStr); + for (int i = 0; i < sample.labelNames.size(); ++i) { + sb.append(',').append(escapeJson(sample.labelNames.get(i))).append("=") + .append(escapeJson(sample.labelValues.get(i).replace(" ", "-"))); + } + sb.append('"'); + } + + void end() { + send(); + } + + String marshalBatch(Long timestamp) { + + builder.append("["); + int len = this.samples.size(); + for (int i = 0; i < len; i++) { + if (i != 0) { + builder.append(",\n"); + } + writeMessage(builder, this.samples.get(i),timestamp); + } + builder.append("]"); + String body = builder.toString(); + builder.setLength(0); + this.samples.clear(); + return body; + } + + private void send() { + Long timestamp = generateTimestamp(); + if (this.samples.size() > 0) { + String body = marshalBatch(timestamp); + HttpPost post = new HttpPost(this.url); + StringEntity requestEntity = new StringEntity( + body, ContentType.APPLICATION_JSON); + post.setEntity(requestEntity); + try { + HttpResponse response = client.execute(post); + final HttpEntity entity = response.getEntity(); + String resp = EntityUtils.toString(entity); + if (!resp.startsWith("{\"dat\":\"ok\"")) { + logger.log(Level.WARNING, "nightingale get response" + resp + " from " + this.url); + } + if (entity != null) { + try (final InputStream inStream = entity.getContent()) { + //inStream.read(); + + // do something useful with the response + } catch (final IOException ex) { + logger.log(Level.WARNING, "Exception " + ex + " get response from " + this.url, ex); + + // In case of an IOException the connection will be released + // back to the connection manager automatically + } + } + } catch (Exception e) { + logger.log(Level.WARNING, "Exception " + e + " pushing to " + this.url, e); + } + + } + } +} diff --git a/simpleclient_sdk/src/main/java/io/prometheus/client/sdk/Metrics.java b/simpleclient_sdk/src/main/java/io/prometheus/client/sdk/Metrics.java new file mode 100644 index 000000000..5dfbbf67d --- /dev/null +++ b/simpleclient_sdk/src/main/java/io/prometheus/client/sdk/Metrics.java @@ -0,0 +1,60 @@ +package io.prometheus.client.sdk; + +import io.prometheus.client.CollectorRegistry; +import io.prometheus.client.exporter.HTTPServer; + +import java.io.IOException; +import java.lang.management.ManagementFactory; +import java.net.InetSocketAddress; +import java.util.HashMap; +import java.util.Map; + +public class Metrics { + private CollectorRegistry registry; + private HashMap tags; + + public Metrics(String bu, String project, String app, Map globalTags) { + this(bu, project, app, globalTags, CollectorRegistry.defaultRegistry); + + } + + public Metrics(String bu, String project, String app) { + this(bu, project, app, null); + + } + + public Metrics(String bu, String project, String app, Map globalTags, CollectorRegistry registry) { + this.registry = registry; + this.tags = new HashMap<>(); + if (globalTags != null) { + globalTags.forEach((k, v) -> { + this.tags.put(k, v); + }); + } + this.tags.put("bu", bu); + this.tags.put("project", project); + this.tags.put("app", app); + String podName = System.getenv("MY_POD_NAME"); + if (podName == null) { + podName = ManagementFactory.getRuntimeMXBean().getName(); + } + this.tags.put("pid", podName); + this.registry.setGlobalTags(this.tags); + } + + public void StartPushLoop(int interval) { + this.StartPushLoop(interval, Nightingale.defaultUrl); + } + + public void StartPushLoop(int interval, String url) { + Nightingale nightingale = new Nightingale(url, interval, this.tags); + nightingale.start(this.registry, interval); + } + + public HTTPServer StartPullHttpServer(int port) throws IOException { + HTTPServer server = new HTTPServer(new InetSocketAddress(port), this.registry, false); + return server; + } + + +} diff --git a/simpleclient_sdk/src/main/java/io/prometheus/client/sdk/Nightingale.java b/simpleclient_sdk/src/main/java/io/prometheus/client/sdk/Nightingale.java new file mode 100644 index 000000000..feb6553fe --- /dev/null +++ b/simpleclient_sdk/src/main/java/io/prometheus/client/sdk/Nightingale.java @@ -0,0 +1,80 @@ +package io.prometheus.client.sdk; + +import io.prometheus.client.Collector; +import io.prometheus.client.CollectorRegistry; + +import java.util.Collections; +import java.util.Map; +import java.util.logging.Logger; + + +public class Nightingale { + private static final Logger logger = Logger.getLogger(Nightingale.class.getName()); + static final String defaultUrl = "http://localhost:2080/v1/push"; + private final Encoder encoder; + + public Nightingale(String url, int batchSize, int interval, String tenantId, Map globalTags) { + this.encoder = new Encoder(url, 100, interval, tenantId,globalTags); + } + + public Nightingale(String url,int interval, Map globalTags) { + this(url, 1000, interval, "1", globalTags); + } + + + /** + * Push samples from the given registry to Graphite. + */ + public void push(CollectorRegistry registry) { + for (Collector.MetricFamilySamples metricFamilySamples : Collections.list(registry.metricFamilySamples())) { + for (Collector.MetricFamilySamples.Sample sample : metricFamilySamples.samples) { + this.encoder.add(sample); + } + } + this.encoder.end(); + } + + /** + * Push samples from the given registry to Graphite every minute. + */ + public Thread start(CollectorRegistry registry) { + return start(registry, 60); + } + + /** + * Push samples from the given registry to Graphite at the given interval. + */ + public Thread start(CollectorRegistry registry, int intervalSeconds) { + Thread thread = new PushThread(registry, intervalSeconds); + thread.setDaemon(true); + thread.start(); + return thread; + } + + private class PushThread extends Thread { + private final CollectorRegistry registry; + private final int intervalSeconds; + + PushThread(CollectorRegistry registry, int intervalSeconds) { + this.registry = registry; + this.intervalSeconds = intervalSeconds; + } + + public void run() { + long waitUntil = System.currentTimeMillis(); + while (true) { + push(registry); + long now = System.currentTimeMillis(); + // We may skip some pushes if we're falling behind. + while (now >= waitUntil) { + waitUntil += intervalSeconds * 1000; + } + try { + Thread.sleep(waitUntil - now); + } catch (InterruptedException e) { + return; + } + } + } + } +} diff --git a/simpleclient_sdk/src/main/java/io/prometheus/client/sdk/StringEscapeUtils.java b/simpleclient_sdk/src/main/java/io/prometheus/client/sdk/StringEscapeUtils.java new file mode 100644 index 000000000..75fe55918 --- /dev/null +++ b/simpleclient_sdk/src/main/java/io/prometheus/client/sdk/StringEscapeUtils.java @@ -0,0 +1,88 @@ +// +// Source code recreated from a .class file by IntelliJ IDEA +// (powered by FernFlower decompiler) +// + +package io.prometheus.client.sdk; + + +public final class StringEscapeUtils { + private static final String[] REPLACEMENT_CHARS = new String[128]; + private static final String U2028 = "\\u2028"; + private static final String U2029 = "\\u2029"; + + public static String escapeJson(String v) { + if (v == null) { + return ""; + } else { + int length = v.length(); + if (length == 0) { + return v; + } else { + int afterReplacement = 0; + StringBuilder builder = null; + + for(int i = 0; i < length; ++i) { + char c = v.charAt(i); + String replacement; + if (c < 128) { + replacement = REPLACEMENT_CHARS[c]; + if (replacement == null) { + continue; + } + } else if (c == 8232) { + replacement = "\\u2028"; + } else { + if (c != 8233) { + continue; + } + + replacement = "\\u2029"; + } + + if (afterReplacement < i) { + if (builder == null) { + builder = new StringBuilder(length); + } + + builder.append(v, afterReplacement, i); + } + + if (builder == null) { + builder = new StringBuilder(length); + } + + builder.append(replacement); + afterReplacement = i + 1; + } + + if (builder == null) { + return v; + } else { + if (afterReplacement < length) { + builder.append(v, afterReplacement, length); + } + + return builder.toString(); + } + } + } + } + + private StringEscapeUtils() { + } + + static { + for(int i = 0; i <= 31; ++i) { + REPLACEMENT_CHARS[i] = String.format("\\u%04x", i); + } + + REPLACEMENT_CHARS[34] = "\\\""; + REPLACEMENT_CHARS[92] = "\\\\"; + REPLACEMENT_CHARS[9] = "\\t"; + REPLACEMENT_CHARS[8] = "\\b"; + REPLACEMENT_CHARS[10] = "\\n"; + REPLACEMENT_CHARS[13] = "\\r"; + REPLACEMENT_CHARS[12] = "\\f"; + } +} diff --git a/simpleclient_sdk/src/test/java/io/prometheus/client/sdk/NightingaleTest.java b/simpleclient_sdk/src/test/java/io/prometheus/client/sdk/NightingaleTest.java new file mode 100644 index 000000000..ab473f52f --- /dev/null +++ b/simpleclient_sdk/src/test/java/io/prometheus/client/sdk/NightingaleTest.java @@ -0,0 +1,78 @@ +package io.prometheus.client.sdk; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import io.prometheus.client.*; +import org.junit.Test; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public class NightingaleTest { + @Test + public void testMarshalBatch(){ + CollectorRegistry registry = new CollectorRegistry(); + + for (int i=0;i<1;i++){ + Gauge labels = Gauge.build().name("labels"+i).help("help").labelNames("l").register(registry); + labels.labels("fo*o").inc(); + } + Encoder encoder =new Encoder("dsadf",100,1000,"11",null); + for (Collector.MetricFamilySamples metricFamilySamples : Collections.list(registry.metricFamilySamples())) { + for (Collector.MetricFamilySamples.Sample sample : metricFamilySamples.samples) { + encoder.add(sample); + } + } + String body=encoder.marshalBatch(2212111l); + System.out.println(body); + } + @Test + public void testPush() throws Exception { + // Create a metric. + CollectorRegistry registry = new CollectorRegistry(); + Gauge labels = Gauge.build().name("test_test").help("help").labelNames("l").register(registry); + labels.labels("fo*o").inc(); + // Push. + Nightingale g = new Nightingale("http://n9e.example.com/api/transfer/push",10,null); + g.push(registry); + } + @Test + public void testPushLoop(){ + Metrics metric= new Metrics("infra","metrics","test"); + Gauge labels = Gauge.build().name("test_test3").help("help").labelNames("l").register(); + labels.labels("foo").inc(); + labels.labels("foo").inc(); + labels.labels("foo").inc(); + Histogram s= Histogram.build().name("histogram1").help("help").labelNames("sl").register(); + s.labels("l1").observe(0.88); + s.labels("l1").observe(0.77); + s.labels("l2").observe(0.66); + Gauge labels2 = Gauge.build().name("test_test4").help("help").labelNames("l").register(); + labels2.labels("foo").inc(); + metric.StartPushLoop(1,"http://10.110.20.100:8080/api/transfer/push"); + try { + Thread.sleep(1000*60); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + @Test + public void testExport() throws IOException { + Metrics metric= new Metrics("infra","metrics","test"); + Gauge labels = Gauge.build().name("test_test2").help("help").labelNames("l","l2").register(); + labels.labels("foo","f2").inc(); + Histogram s= Histogram.build().name("histogram1").help("help").labelNames("sl").register(); + s.labels("l1").observe(0.88); + s.labels("l1").observe(0.77); + s.labels("l2").observe(0.66); + metric.StartPullHttpServer(9000); + try { + Thread.sleep(1000*60); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } +}