From 819a8576ccb159d4c3769ac49e97a59ae67858fc Mon Sep 17 00:00:00 2001 From: Brian Brazil Date: Wed, 25 Nov 2020 17:48:54 +0000 Subject: [PATCH 01/11] Add support for OM Units, and Counter naming. Signed-off-by: Brian Brazil --- .../java/io/prometheus/client/Collector.java | 31 +++++++++++++++---- .../prometheus/client/CollectorRegistry.java | 5 +++ .../java/io/prometheus/client/Counter.java | 6 +++- .../io/prometheus/client/SimpleCollector.java | 15 ++++++++- .../client/CollectorRegistryTest.java | 4 +-- .../io/prometheus/client/CounterTest.java | 22 ++++++++++--- .../client/SimpleCollectorTest.java | 9 ++++++ .../client/exporter/common/TextFormat.java | 11 +++++-- .../exporter/common/TextFormatTest.java | 29 +++++++++++++++-- 9 files changed, 112 insertions(+), 20 deletions(-) diff --git a/simpleclient/src/main/java/io/prometheus/client/Collector.java b/simpleclient/src/main/java/io/prometheus/client/Collector.java index 49c29e9b8..6855e3a51 100644 --- a/simpleclient/src/main/java/io/prometheus/client/Collector.java +++ b/simpleclient/src/main/java/io/prometheus/client/Collector.java @@ -19,11 +19,14 @@ public abstract class Collector { */ public abstract List collect(); public enum Type { + UNTYPED, // XXX This is Unknown in OpenMetrics. COUNTER, GAUGE, - SUMMARY, + STATE_SET, + INFO, HISTOGRAM, - UNTYPED, + GAUGE_HISTOGRAM, + SUMMARY, } /** @@ -31,17 +34,29 @@ public enum Type { */ static public class MetricFamilySamples { public final String name; + public final String unit; public final Type type; public final String help; public final List samples; - public MetricFamilySamples(String name, Type type, String help, List samples) { + public MetricFamilySamples(String name, String unit, Type type, String help, List samples) { + if (!unit.isEmpty() && !name.endsWith("_" + unit)) { + throw new IllegalArgumentException("Metric's unit is not the suffix of the metric name: " + name); + } + if ((type == Type.INFO || type == Type.STATE_SET) && !unit.isEmpty()) { + throw new IllegalArgumentException("Metric is of a type that cannot have a unit: " + name); + } this.name = name; + this.unit = unit; this.type = type; this.help = help; this.samples = samples; } + public MetricFamilySamples(String name, Type type, String help, List samples) { + this(name, "", type, help, samples); + } + @Override public boolean equals(Object obj) { if (!(obj instanceof MetricFamilySamples)) { @@ -49,14 +64,18 @@ public boolean equals(Object obj) { } MetricFamilySamples other = (MetricFamilySamples) obj; - return other.name.equals(name) && other.type.equals(type) - && other.help.equals(help) && other.samples.equals(samples) ; + return other.name.equals(name) + && other.unit.equals(unit) + && other.type.equals(type) + && other.help.equals(help) + && other.samples.equals(samples); } @Override public int hashCode() { int hash = 1; hash = 37 * hash + name.hashCode(); + hash = 37 * hash + unit.hashCode(); hash = 37 * hash + type.hashCode(); hash = 37 * hash + help.hashCode(); hash = 37 * hash + samples.hashCode(); @@ -65,7 +84,7 @@ public int hashCode() { @Override public String toString() { - return "Name: " + name + " Type: " + type + " Help: " + help + + return "Name: " + name + " Unit:" + unit + " Type: " + type + " Help: " + help + " Samples: " + samples; } diff --git a/simpleclient/src/main/java/io/prometheus/client/CollectorRegistry.java b/simpleclient/src/main/java/io/prometheus/client/CollectorRegistry.java index d2a12c95c..b7bc0284b 100644 --- a/simpleclient/src/main/java/io/prometheus/client/CollectorRegistry.java +++ b/simpleclient/src/main/java/io/prometheus/client/CollectorRegistry.java @@ -105,6 +105,11 @@ private List collectorNames(Collector m) { List names = new ArrayList(); for (Collector.MetricFamilySamples family : mfs) { switch (family.type) { + case COUNTER: + names.add(family.name + "_total"); + names.add(family.name + "_created"); + names.add(family.name); + break; case SUMMARY: names.add(family.name + "_count"); names.add(family.name + "_sum"); diff --git a/simpleclient/src/main/java/io/prometheus/client/Counter.java b/simpleclient/src/main/java/io/prometheus/client/Counter.java index 180a8d5c9..27a7d5627 100644 --- a/simpleclient/src/main/java/io/prometheus/client/Counter.java +++ b/simpleclient/src/main/java/io/prometheus/client/Counter.java @@ -74,6 +74,10 @@ public class Counter extends SimpleCollector implements Collector public static class Builder extends SimpleCollector.Builder { @Override public Counter create() { + // Gracefully handle pre-OpenMetrics counters. + if (name.endsWith("_total")) { + name = name.substring(0, name.length() - 6); + } return new Counter(this); } } @@ -158,7 +162,7 @@ public double get() { public List collect() { List samples = new ArrayList(children.size()); for(Map.Entry, Child> c: children.entrySet()) { - samples.add(new MetricFamilySamples.Sample(fullname, labelNames, c.getKey(), c.getValue().get())); + samples.add(new MetricFamilySamples.Sample(fullname + "_total", labelNames, c.getKey(), c.getValue().get())); } return familySamplesList(Type.COUNTER, samples); } diff --git a/simpleclient/src/main/java/io/prometheus/client/SimpleCollector.java b/simpleclient/src/main/java/io/prometheus/client/SimpleCollector.java index a70d5d4e7..6a0183961 100644 --- a/simpleclient/src/main/java/io/prometheus/client/SimpleCollector.java +++ b/simpleclient/src/main/java/io/prometheus/client/SimpleCollector.java @@ -49,6 +49,7 @@ public abstract class SimpleCollector extends Collector { protected final String fullname; protected final String help; + protected final String unit; protected final List labelNames; protected final ConcurrentMap, Child> children = new ConcurrentHashMap, Child>(); @@ -145,7 +146,7 @@ public T setChild(Child child, String... labelValues) { protected abstract Child newChild(); protected List familySamplesList(Collector.Type type, List samples) { - MetricFamilySamples mfs = new MetricFamilySamples(fullname, type, help, samples); + MetricFamilySamples mfs = new MetricFamilySamples(fullname, unit, type, help, samples); List mfsList = new ArrayList(1); mfsList.add(mfs); return mfsList; @@ -160,6 +161,10 @@ protected SimpleCollector(Builder b) { if (!b.namespace.isEmpty()) { name = b.namespace + '_' + name; } + unit = b.unit; + if (!unit.isEmpty() && !name.endsWith("_" + unit)) { + name += "_" + unit; + } fullname = name; checkMetricName(fullname); if (b.help != null && b.help.isEmpty()) throw new IllegalStateException("Help hasn't been set."); @@ -183,6 +188,7 @@ public abstract static class Builder, C extends SimpleCo String subsystem = ""; String name = ""; String fullname = ""; + String unit = ""; String help = ""; String[] labelNames = new String[]{}; // Some metrics require additional setup before the initialization can be done. @@ -209,6 +215,13 @@ public B namespace(String namespace) { this.namespace = namespace; return (B)this; } + /** + * Set the uit of the metric. Required. + */ + public B unit(String unit) { + this.unit = unit; + return (B)this; + } /** * Set the help string of the metric. Required. */ diff --git a/simpleclient/src/test/java/io/prometheus/client/CollectorRegistryTest.java b/simpleclient/src/test/java/io/prometheus/client/CollectorRegistryTest.java index 3f96dcbe3..4404f247a 100644 --- a/simpleclient/src/test/java/io/prometheus/client/CollectorRegistryTest.java +++ b/simpleclient/src/test/java/io/prometheus/client/CollectorRegistryTest.java @@ -83,7 +83,7 @@ public void testMetricFamilySamples_filterNames() { HashSet metrics = new HashSet(); HashSet series = new HashSet(); for (Collector.MetricFamilySamples metricFamilySamples : Collections.list(registry.filteredMetricFamilySamples( - new HashSet(Arrays.asList("", "s_sum", "c", "part_filter_a", "part_filter_c"))))) { + new HashSet(Arrays.asList("", "s_sum", "c_total", "part_filter_a", "part_filter_c"))))) { metrics.add(metricFamilySamples.name); for (Collector.MetricFamilySamples.Sample sample : metricFamilySamples.samples) { series.add(sample.name); @@ -93,7 +93,7 @@ public void testMetricFamilySamples_filterNames() { assertEquals(1, sr.collectCallCount); assertEquals(2, pfr.collectCallCount); assertEquals(new HashSet(Arrays.asList("s", "c", "part_filter_a", "part_filter_c")), metrics); - assertEquals(new HashSet(Arrays.asList("s_sum", "c", "part_filter_a", "part_filter_c")), series); + assertEquals(new HashSet(Arrays.asList("s_sum", "c_total", "part_filter_a", "part_filter_c")), series); } @Test diff --git a/simpleclient/src/test/java/io/prometheus/client/CounterTest.java b/simpleclient/src/test/java/io/prometheus/client/CounterTest.java index b7e9ac6ea..6dc089d6a 100644 --- a/simpleclient/src/test/java/io/prometheus/client/CounterTest.java +++ b/simpleclient/src/test/java/io/prometheus/client/CounterTest.java @@ -23,11 +23,11 @@ public class CounterTest { public void setUp() { registry = new CollectorRegistry(); noLabels = Counter.build().name("nolabels").help("help").register(registry); - labels = Counter.build().name("labels").help("help").labelNames("l").register(registry); + labels = Counter.build().name("labels").unit("seconds").help("help").labelNames("l").register(registry); } private double getValue() { - return registry.getSampleValue("nolabels").doubleValue(); + return registry.getSampleValue("nolabels_total").doubleValue(); } @Test @@ -59,7 +59,7 @@ public void noLabelsDefaultZeroValue() { } private Double getLabelsValue(String labelValue) { - return registry.getSampleValue("labels", new String[]{"l"}, new String[]{labelValue}); + return registry.getSampleValue("labels_seconds_total", new String[]{"l"}, new String[]{labelValue}); } @Test @@ -74,6 +74,18 @@ public void testLabels() { assertEquals(3.0, getLabelsValue("b").doubleValue(), .001); } + @Test + public void testTotalStrippedFromName() { + Counter c = Counter.build().name("foo_total").unit("seconds").help("h").create(); + assertEquals("foo_seconds", c.fullname); + + // This is not a good unit, but test it anyway. + c = Counter.build().name("foo_total").unit("total").help("h").create(); + assertEquals("foo_total", c.fullname); + c = Counter.build().name("foo").unit("total").help("h").create(); + assertEquals("foo_total", c.fullname); + } + @Test public void testCollect() { labels.labels("a").inc(); @@ -84,8 +96,8 @@ public void testCollect() { labelNames.add("l"); ArrayList labelValues = new ArrayList(); labelValues.add("a"); - samples.add(new Collector.MetricFamilySamples.Sample("labels", labelNames, labelValues, 1.0)); - Collector.MetricFamilySamples mfsFixture = new Collector.MetricFamilySamples("labels", Collector.Type.COUNTER, "help", samples); + samples.add(new Collector.MetricFamilySamples.Sample("labels_seconds_total", labelNames, labelValues, 1.0)); + Collector.MetricFamilySamples mfsFixture = new Collector.MetricFamilySamples("labels_seconds", "seconds", Collector.Type.COUNTER, "help", samples); assertEquals(1, mfs.size()); assertEquals(mfsFixture, mfs.get(0)); diff --git a/simpleclient/src/test/java/io/prometheus/client/SimpleCollectorTest.java b/simpleclient/src/test/java/io/prometheus/client/SimpleCollectorTest.java index e2b7d1a4e..74b01db52 100644 --- a/simpleclient/src/test/java/io/prometheus/client/SimpleCollectorTest.java +++ b/simpleclient/src/test/java/io/prometheus/client/SimpleCollectorTest.java @@ -145,6 +145,15 @@ public void testReservedLabelNameThrows() { Gauge.build().name("a").labelNames("__name__").help("h").create(); } + @Test + public void testUnitsAdded() { + Gauge g = Gauge.build().name("a").unit("seconds").help("h").create(); + assertEquals("a_seconds", g.fullname); + + Gauge g2 = Gauge.build().name("a_seconds").unit("seconds").help("h").create(); + assertEquals("a_seconds", g2.fullname); + } + @Test public void testSetChild() { metric.setChild(new Gauge.Child(){ 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 954edfda8..de6726ec3 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 @@ -20,20 +20,27 @@ public static void write004(Writer writer, Enumeration 0) { writer.write('{'); for (int i = 0; i < sample.labelNames.size(); ++i) { diff --git a/simpleclient_common/src/test/java/io/prometheus/client/exporter/common/TextFormatTest.java b/simpleclient_common/src/test/java/io/prometheus/client/exporter/common/TextFormatTest.java index 67a01b38e..f1886eeb7 100644 --- a/simpleclient_common/src/test/java/io/prometheus/client/exporter/common/TextFormatTest.java +++ b/simpleclient_common/src/test/java/io/prometheus/client/exporter/common/TextFormatTest.java @@ -52,9 +52,32 @@ public void testCounterOutput() throws IOException { Counter noLabels = Counter.build().name("nolabels").help("help").register(registry); noLabels.inc(); TextFormat.write004(writer, registry.metricFamilySamples()); - assertEquals("# HELP nolabels help\n" - + "# TYPE nolabels counter\n" - + "nolabels 1.0\n", writer.toString()); + assertEquals("# HELP nolabels_total help\n" + + "# TYPE nolabels_total counter\n" + + "nolabels_total 1.0\n", writer.toString()); + } + + @Test + public void testCounterSamplesMissingTotal() throws IOException { + + class CustomCollector extends Collector { + public List collect() { + List mfs = new ArrayList(); + ArrayList labelNames = new ArrayList(); + ArrayList labelValues = new ArrayList(); + ArrayList samples = new ArrayList(); + MetricFamilySamples.Sample sample = new MetricFamilySamples.Sample("nolabels", labelNames, labelValues, 1.0); + samples.add(sample); + mfs.add(new MetricFamilySamples("nolabels", Collector.Type.COUNTER, "help", samples)); + return mfs; + } + } + + new CustomCollector().register(registry); + TextFormat.write004(writer, registry.metricFamilySamples()); + assertEquals("# HELP nolabels_total help\n" + + "# TYPE nolabels_total counter\n" + + "nolabels_total 1.0\n", writer.toString()); } @Test From 6847f9fff1f0d6f79a4d8e646052da7554e56565 Mon Sep 17 00:00:00 2001 From: Brian Brazil Date: Thu, 14 Jan 2021 16:09:56 +0000 Subject: [PATCH 02/11] Get basic OM rendering working with HttpServer. Signed-off-by: Brian Brazil --- .../java/io/prometheus/client/Collector.java | 19 ++- .../client/exporter/common/TextFormat.java | 131 +++++++++++++++++- .../client/exporter/HTTPServer.java | 7 +- 3 files changed, 152 insertions(+), 5 deletions(-) diff --git a/simpleclient/src/main/java/io/prometheus/client/Collector.java b/simpleclient/src/main/java/io/prometheus/client/Collector.java index 6855e3a51..a6c52b302 100644 --- a/simpleclient/src/main/java/io/prometheus/client/Collector.java +++ b/simpleclient/src/main/java/io/prometheus/client/Collector.java @@ -1,6 +1,7 @@ package io.prometheus.client; +import java.util.ArrayList; import java.util.List; import java.util.regex.Pattern; @@ -46,11 +47,27 @@ public MetricFamilySamples(String name, String unit, Type type, String help, Lis if ((type == Type.INFO || type == Type.STATE_SET) && !unit.isEmpty()) { throw new IllegalArgumentException("Metric is of a type that cannot have a unit: " + name); } + List mungedSamples = samples; + // Deal with _total from pre-OM automatically. + if (type == Type.COUNTER) { + if (name.endsWith("_total")) { + name = name.substring(0, name.length() - 6); + } + String withTotal = name + "_total"; + mungedSamples = new ArrayList(samples.size()); + for (Sample s: samples) { + String n = s.name; + if (name.equals(n)) { + n = withTotal; + } + mungedSamples.add(new Sample(n, s.labelNames, s.labelValues, s.value, s.timestampMs)); + } + } this.name = name; this.unit = unit; this.type = type; this.help = help; - this.samples = samples; + this.samples = mungedSamples; } public MetricFamilySamples(String name, Type type, String help, List samples) { 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 de6726ec3..901454ca7 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 @@ -8,9 +8,46 @@ public class TextFormat { /** - * Content-type for text version 0.0.4. + * 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. + */ + 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. + */ + 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; + } + } + + return CONTENT_TYPE_004; + } + + /** + * Write out the given MetricFamilySamples in a format per the contentType. + */ + 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); + } /** * Write out the text version 0.0.4 of the given MetricFamilySamples. @@ -107,8 +144,100 @@ private static String typeString(Collector.Type t) { return "summary"; case HISTOGRAM: return "histogram"; + case GAUGE_HISTOGRAM: + return "histogram"; + case STATE_SET: + return "gauge"; + case INFO: + return "gauge"; default: return "untyped"; } } + + /** + * Write out the OpenMetrics text version 1.0.0 of the given MetricFamilySamples. + */ + public static void writeOpenMetrics100(Writer writer, Enumeration mfs) throws IOException { + while(mfs.hasMoreElements()) { + Collector.MetricFamilySamples metricFamilySamples = mfs.nextElement(); + String name = metricFamilySamples.name; + + writer.write("# TYPE "); + writer.write(name); + writer.write(' '); + writer.write(omTypeString(metricFamilySamples.type)); + writer.write('\n'); + + if (!metricFamilySamples.unit.isEmpty()) { + writer.write("# UNIT "); + writer.write(name); + writer.write(' '); + writer.write(metricFamilySamples.unit); + 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(' '); + long ts = sample.timestampMs.longValue(); + writer.write(Long.toString(ts / 1000)); + writer.write("."); + long ms = ts % 1000; + if (ms < 100) { + writer.write("0"); + } + if (ms < 10) { + writer.write("0"); + } + writer.write(Long.toString(ts % 1000)); + + } + writer.write('\n'); + } + } + } + + 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 "gauge_histogram"; + case STATE_SET: + return "stateset"; + case INFO: + return "info"; + default: + return "unknown"; + } + } } 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 10445a7c3..e016ab260 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 @@ -67,13 +67,14 @@ public void handle(HttpExchange t) throws IOException { if ("/-/healthy".equals(contextPath)) { osw.write(HEALTHY_RESPONSE); } else { - TextFormat.write004(osw, + String contentType = TextFormat.chooseContentType(t.getRequestHeaders().getFirst("Accept")); + t.getResponseHeaders().set("Content-Type", contentType); + TextFormat.writeFormat(contentType, osw, registry.filteredMetricFamilySamples(parseQuery(query))); } osw.close(); - t.getResponseHeaders().set("Content-Type", - TextFormat.CONTENT_TYPE_004); + if (shouldUseCompression(t)) { t.getResponseHeaders().set("Content-Encoding", "gzip"); t.sendResponseHeaders(HttpURLConnection.HTTP_OK, 0); From 99e209d234e40ddc4b10ae468e3ff0aff945bed7 Mon Sep 17 00:00:00 2001 From: Brian Brazil Date: Fri, 15 Jan 2021 12:31:07 +0000 Subject: [PATCH 03/11] Update and add tests and code for _total handling. Signed-off-by: Brian Brazil --- .../client/CounterMetricFamily.java | 4 +- .../io/prometheus/client/CollectorTest.java | 31 +++++++++++++++ .../client/CounterMetricFamilyTest.java | 38 ++++++++++++++++--- .../caffeine/CacheMetricsCollectorTest.java | 1 + .../dropwizard/DropwizardExportsTest.java | 8 ++-- .../spring/boot/PrometheusEndpointTest.java | 4 +- 6 files changed, 72 insertions(+), 14 deletions(-) diff --git a/simpleclient/src/main/java/io/prometheus/client/CounterMetricFamily.java b/simpleclient/src/main/java/io/prometheus/client/CounterMetricFamily.java index c477bba1b..8c6156b35 100644 --- a/simpleclient/src/main/java/io/prometheus/client/CounterMetricFamily.java +++ b/simpleclient/src/main/java/io/prometheus/client/CounterMetricFamily.java @@ -37,7 +37,7 @@ public CounterMetricFamily(String name, String help, double value) { labelNames = Collections.emptyList(); samples.add( new Sample( - name, + this.name + "_total", labelNames, Collections.emptyList(), value)); @@ -52,7 +52,7 @@ public CounterMetricFamily addMetric(List labelValues, double value) { if (labelValues.size() != labelNames.size()) { throw new IllegalArgumentException("Incorrect number of labels."); } - samples.add(new Sample(name, labelNames, labelValues, value)); + samples.add(new Sample(name + "_total", labelNames, labelValues, value)); return this; } } diff --git a/simpleclient/src/test/java/io/prometheus/client/CollectorTest.java b/simpleclient/src/test/java/io/prometheus/client/CollectorTest.java index 6c7fd358e..67840594e 100644 --- a/simpleclient/src/test/java/io/prometheus/client/CollectorTest.java +++ b/simpleclient/src/test/java/io/prometheus/client/CollectorTest.java @@ -1,5 +1,10 @@ package io.prometheus.client; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + import org.junit.Test; import static org.junit.Assert.*; @@ -11,4 +16,30 @@ public void sanitizeMetricName() throws Exception { assertEquals("foo_bar0", Collector.sanitizeMetricName("foo.bar0")); assertEquals(":baz::", Collector.sanitizeMetricName(":baz::")); } + + @Test + public void testTotalHandling() throws Exception { + class YourCustomCollector extends Collector { + public List collect() { + List emptyList = new ArrayList(); + return Arrays.asList( + new MetricFamilySamples("a_total", Type.COUNTER, "help", Arrays.asList( + new MetricFamilySamples.Sample("a_total", emptyList, emptyList, 1.0))), + new MetricFamilySamples("b", Type.COUNTER, "help", Arrays.asList( + new MetricFamilySamples.Sample("b", emptyList, emptyList, 2.0))), + new MetricFamilySamples("c_total", Type.COUNTER, "help", Arrays.asList( + new MetricFamilySamples.Sample("c", emptyList, emptyList, 3.0))), + new MetricFamilySamples("d", Type.COUNTER, "help", Arrays.asList( + new MetricFamilySamples.Sample("d_total", emptyList, emptyList, 4.0))) + ); + } + } + CollectorRegistry registry = new CollectorRegistry(); + new YourCustomCollector().register(registry); + + assertEquals(1.0, registry.getSampleValue("a_total").doubleValue(), .001); + assertEquals(2.0, registry.getSampleValue("b_total").doubleValue(), .001); + assertEquals(3.0, registry.getSampleValue("c_total").doubleValue(), .001); + assertEquals(4.0, registry.getSampleValue("d_total").doubleValue(), .001); + } } diff --git a/simpleclient/src/test/java/io/prometheus/client/CounterMetricFamilyTest.java b/simpleclient/src/test/java/io/prometheus/client/CounterMetricFamilyTest.java index f2b3e1b1a..ffa8396a4 100644 --- a/simpleclient/src/test/java/io/prometheus/client/CounterMetricFamilyTest.java +++ b/simpleclient/src/test/java/io/prometheus/client/CounterMetricFamilyTest.java @@ -35,10 +35,10 @@ public List collect() { } new YourCustomCollector().register(registry); - assertEquals(42.0, registry.getSampleValue("my_counter").doubleValue(), .001); - assertEquals(null, registry.getSampleValue("my_other_counter")); - assertEquals(4.0, registry.getSampleValue("my_other_counter", new String[]{"labelname"}, new String[]{"foo"}).doubleValue(), .001); - assertEquals(5.0, registry.getSampleValue("my_other_counter", new String[]{"labelname"}, new String[]{"bar"}).doubleValue(), .001); + assertEquals(42.0, registry.getSampleValue("my_counter_total").doubleValue(), .001); + assertEquals(null, registry.getSampleValue("my_other_counter_total")); + assertEquals(4.0, registry.getSampleValue("my_other_counter_total", new String[]{"labelname"}, new String[]{"foo"}).doubleValue(), .001); + assertEquals(5.0, registry.getSampleValue("my_other_counter_total", new String[]{"labelname"}, new String[]{"bar"}).doubleValue(), .001); } @Test @@ -55,11 +55,37 @@ public List collect() { new YourCustomCollector().register(registry); assertEquals(1.0, - registry.getSampleValue("my_metric", new String[]{"name"}, new String[]{"value1"}) + registry.getSampleValue("my_metric_total", new String[]{"name"}, new String[]{"value1"}) .doubleValue(), .001); assertEquals(2.0, - registry.getSampleValue("my_metric", new String[]{"name"}, new String[]{"value2"}) + registry.getSampleValue("my_metric_total", new String[]{"name"}, new String[]{"value2"}) .doubleValue(), .001); } + @Test + public void testTotalHandling() { + class YourCustomCollector extends Collector { + public List collect() { + return Arrays.asList( + new CounterMetricFamily("a_total", "help", Arrays.asList("name")) + .addMetric(Arrays.asList("value"), 1.0), + new CounterMetricFamily("b", "help", Arrays.asList("name")) + .addMetric(Arrays.asList("value"), 2.0), + new CounterMetricFamily("c_total", "help", 3.0), + new CounterMetricFamily("d_total", "help", 4.0) + ); + } + } + new YourCustomCollector().register(registry); + + assertEquals(1.0, + registry.getSampleValue("a_total", new String[]{"name"}, new String[]{"value"}) + .doubleValue(), .001); + assertEquals(2.0, + registry.getSampleValue("b_total", new String[]{"name"}, new String[]{"value"}) + .doubleValue(), .001); + assertEquals(3.0, registry.getSampleValue("c_total").doubleValue(), .001); + assertEquals(4.0, registry.getSampleValue("d_total").doubleValue(), .001); + } + } diff --git a/simpleclient_caffeine/src/test/java/io/prometheus/client/cache/caffeine/CacheMetricsCollectorTest.java b/simpleclient_caffeine/src/test/java/io/prometheus/client/cache/caffeine/CacheMetricsCollectorTest.java index 65429f337..75f198d1b 100644 --- a/simpleclient_caffeine/src/test/java/io/prometheus/client/cache/caffeine/CacheMetricsCollectorTest.java +++ b/simpleclient_caffeine/src/test/java/io/prometheus/client/cache/caffeine/CacheMetricsCollectorTest.java @@ -70,6 +70,7 @@ public void loadingCacheExposesMetricsForLoadsAndExceptions() throws Exception { } cache.get("user3"); + assertMetric(registry, "caffeine_cache_hit_total", "loadingusers", 1.0); assertMetric(registry, "caffeine_cache_miss_total", "loadingusers", 3.0); diff --git a/simpleclient_dropwizard/src/test/java/io/prometheus/client/dropwizard/DropwizardExportsTest.java b/simpleclient_dropwizard/src/test/java/io/prometheus/client/dropwizard/DropwizardExportsTest.java index da721f75c..409dfaa51 100644 --- a/simpleclient_dropwizard/src/test/java/io/prometheus/client/dropwizard/DropwizardExportsTest.java +++ b/simpleclient_dropwizard/src/test/java/io/prometheus/client/dropwizard/DropwizardExportsTest.java @@ -224,7 +224,7 @@ public void testThatMetricHelpUsesOriginalDropwizardName() { assertTrue(elements.keySet().contains("my_application_namedTimer1")); assertTrue(elements.keySet().contains("my_application_namedCounter1")); - assertTrue(elements.keySet().contains("my_application_namedMeter1_total")); + assertTrue(elements.keySet().contains("my_application_namedMeter1")); assertTrue(elements.keySet().contains("my_application_namedHistogram1")); assertTrue(elements.keySet().contains("my_application_namedGauge1")); @@ -234,7 +234,7 @@ public void testThatMetricHelpUsesOriginalDropwizardName() { assertThat(elements.get("my_application_namedCounter1").help, is("Generated from Dropwizard metric import (metric=my.application.namedCounter1, type=com.codahale.metrics.Counter)")); - assertThat(elements.get("my_application_namedMeter1_total").help, + assertThat(elements.get("my_application_namedMeter1").help, is("Generated from Dropwizard metric import (metric=my.application.namedMeter1, type=com.codahale.metrics.Meter)")); assertThat(elements.get("my_application_namedHistogram1").help, @@ -321,7 +321,7 @@ public void testThatMetricsMappedToSameNameAreGroupedInSameFamily() { assertTrue(namedCounter.samples.contains(namedCounter1)); assertTrue(namedCounter.samples.contains(namedCounter2)); - final Collector.MetricFamilySamples namedMeter = elements.get("my_application_namedMeter_total"); + final Collector.MetricFamilySamples namedMeter = elements.get("my_application_namedMeter"); assertNotNull(namedMeter); assertEquals(Collector.Type.COUNTER, namedMeter.type); assertEquals(2, namedMeter.samples.size()); @@ -349,4 +349,4 @@ public Double getValue() { return 0.0; } } -} \ No newline at end of file +} diff --git a/simpleclient_spring_boot/src/test/java/io/prometheus/client/spring/boot/PrometheusEndpointTest.java b/simpleclient_spring_boot/src/test/java/io/prometheus/client/spring/boot/PrometheusEndpointTest.java index 377034964..737788f76 100644 --- a/simpleclient_spring_boot/src/test/java/io/prometheus/client/spring/boot/PrometheusEndpointTest.java +++ b/simpleclient_spring_boot/src/test/java/io/prometheus/client/spring/boot/PrometheusEndpointTest.java @@ -55,7 +55,7 @@ public void testMetricsExportedThroughPrometheusEndpoint() { HttpHeaders headers = new HttpHeaders(); headers.set("Accept", "text/plain"); - ResponseEntity metricsResponse = template.exchange(getBaseUrl() + "/prometheus?name[]=foo_bar", HttpMethod.GET, new HttpEntity(headers), String.class); + ResponseEntity metricsResponse = template.exchange(getBaseUrl() + "/prometheus?name[]=foo_bar_total", HttpMethod.GET, new HttpEntity(headers), String.class); // then: assertEquals(HttpStatus.OK, metricsResponse.getStatusCode()); @@ -63,7 +63,7 @@ public void testMetricsExportedThroughPrometheusEndpoint() { List responseLines = Arrays.asList(metricsResponse.getBody().split("\n")); assertThat(responseLines, CustomMatchers.exactlyNItems(1, - matchesPattern("foo_bar\\{label1=\"val1\",label2=\"val2\",?\\} 3.0"))); + matchesPattern("foo_bar_total\\{label1=\"val1\",label2=\"val2\",?\\} 3.0"))); } private String getBaseUrl() { From d4f32a2c1467aabb19110ff524ac6c5af47faf3f Mon Sep 17 00:00:00 2001 From: Brian Brazil Date: Fri, 15 Jan 2021 13:17:08 +0000 Subject: [PATCH 04/11] Get all the http servers working with OM. Signed-off-by: Brian Brazil --- .../client/exporter/common/TextFormat.java | 1 + .../client/exporter/TestHTTPServer.java | 17 +++++++++++++++++ .../client/exporter/MetricsServlet.java | 5 +++-- .../client/exporter/MetricsServletTest.java | 19 +++++++++++++++++++ .../spring/boot/PrometheusEndpoint.java | 6 +++--- .../spring/boot/PrometheusMvcEndpoint.java | 9 ++++++--- .../boot/PrometheusMvcEndpointTest.java | 16 +++++++++++++++- .../client/vertx/MetricsHandler.java | 6 ++++-- 8 files changed, 68 insertions(+), 11 deletions(-) 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 901454ca7..ed3992f24 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 @@ -218,6 +218,7 @@ public static void writeOpenMetrics100(Writer writer, Enumeration { @Override public String invoke() { - return writeRegistry(Collections.emptySet()); + return writeRegistry(Collections.emptySet(), ""); } - public String writeRegistry(Set metricsToInclude) { + public String writeRegistry(Set metricsToInclude, String contentType) { try { Writer writer = new StringWriter(); - TextFormat.write004(writer, collectorRegistry.filteredMetricFamilySamples(metricsToInclude)); + TextFormat.writeFormat(contentType, writer, collectorRegistry.filteredMetricFamilySamples(metricsToInclude)); return writer.toString(); } catch (IOException e) { // This actually never happens since StringWriter::write() doesn't throw any IOException diff --git a/simpleclient_spring_boot/src/main/java/io/prometheus/client/spring/boot/PrometheusMvcEndpoint.java b/simpleclient_spring_boot/src/main/java/io/prometheus/client/spring/boot/PrometheusMvcEndpoint.java index 816fde7f3..fafd4dc69 100644 --- a/simpleclient_spring_boot/src/main/java/io/prometheus/client/spring/boot/PrometheusMvcEndpoint.java +++ b/simpleclient_spring_boot/src/main/java/io/prometheus/client/spring/boot/PrometheusMvcEndpoint.java @@ -4,6 +4,7 @@ import org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; @@ -29,16 +30,18 @@ public PrometheusMvcEndpoint(PrometheusEndpoint delegate) { ) @ResponseBody public ResponseEntity value( - @RequestParam(value = "name[]", required = false, defaultValue = "") Set name) { + @RequestParam(value = "name[]", required = false, defaultValue = "") Set name, + @RequestHeader(value = "Accept", required = false, defaultValue = "") String accept) { if (!getDelegate().isEnabled()) { // Shouldn't happen - MVC endpoint shouldn't be registered when delegate's // disabled return getDisabledResponse(); } - String result = delgate.writeRegistry(name); + String contentType = TextFormat.chooseContentType(accept); + String result = delgate.writeRegistry(name, contentType); return ResponseEntity.ok() - .header(CONTENT_TYPE, TextFormat.CONTENT_TYPE_004) + .header(CONTENT_TYPE, contentType) .body(result); } } diff --git a/simpleclient_spring_boot/src/test/java/io/prometheus/client/spring/boot/PrometheusMvcEndpointTest.java b/simpleclient_spring_boot/src/test/java/io/prometheus/client/spring/boot/PrometheusMvcEndpointTest.java index e0b1567f4..5f7eea102 100644 --- a/simpleclient_spring_boot/src/test/java/io/prometheus/client/spring/boot/PrometheusMvcEndpointTest.java +++ b/simpleclient_spring_boot/src/test/java/io/prometheus/client/spring/boot/PrometheusMvcEndpointTest.java @@ -17,6 +17,7 @@ import org.springframework.test.context.junit4.SpringRunner; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; @RunWith(SpringRunner.class) @@ -50,6 +51,19 @@ public void testAcceptPlainText() throws Exception { assertEquals(HttpStatus.OK, metricsResponse.getStatusCode()); } + @Test + public void testAcceptOpenMetrics() throws Exception { + + HttpHeaders headers = new HttpHeaders(); + headers.set("Accept", "application/openmetrics-text; version=0.0.1,text/plain;version=0.0.4;q=0.5,*/*;q=0.1"); + + ResponseEntity metricsResponse = template.exchange(getBaseUrl() + "/prometheus", HttpMethod.GET, new HttpEntity(headers), String.class); + + assertEquals(HttpStatus.OK, metricsResponse.getStatusCode()); + assertEquals(StringUtils.deleteWhitespace(TextFormat.CONTENT_TYPE_OPENMETRICS_100), metricsResponse.getHeaders().getContentType().toString().toLowerCase()); + assertTrue(metricsResponse.getBody().contains("# EOF")); + } + @Test public void testNameParamIsNotNull() { ResponseEntity metricsResponse = template.exchange(getBaseUrl() + "/prometheus?name[]=foo_bar", HttpMethod.GET, getEntity(), String.class); @@ -68,4 +82,4 @@ public HttpEntity getEntity() { private String getBaseUrl() { return "http://localhost:" + localServerPort; } -} \ No newline at end of file +} diff --git a/simpleclient_vertx/src/main/java/io/prometheus/client/vertx/MetricsHandler.java b/simpleclient_vertx/src/main/java/io/prometheus/client/vertx/MetricsHandler.java index 4f763a541..5f1cbfafd 100644 --- a/simpleclient_vertx/src/main/java/io/prometheus/client/vertx/MetricsHandler.java +++ b/simpleclient_vertx/src/main/java/io/prometheus/client/vertx/MetricsHandler.java @@ -73,10 +73,12 @@ public MetricsHandler(CollectorRegistry registry) { public void handle(RoutingContext ctx) { try { final BufferWriter writer = new BufferWriter(); - TextFormat.write004(writer, registry.filteredMetricFamilySamples(parse(ctx.request()))); + String contentType = TextFormat.chooseContentType(ctx.request().headers().get("Accept")); + + TextFormat.writeFormat(contentType, writer, registry.filteredMetricFamilySamples(parse(ctx.request()))); ctx.response() .setStatusCode(200) - .putHeader("Content-Type", TextFormat.CONTENT_TYPE_004) + .putHeader("Content-Type", contentType) .end(writer.getBuffer()); } catch (IOException e) { ctx.fail(e); From 706672af5d7266b9b94b576adb4f2eebc99e12de Mon Sep 17 00:00:00 2001 From: Brian Brazil Date: Fri, 15 Jan 2021 13:36:23 +0000 Subject: [PATCH 05/11] Add OM output tests. Signed-off-by: Brian Brazil --- .../common/TextFormatOpenMetricsTest.java | 173 ++++++++++++++++++ .../exporter/common/TextFormatTest.java | 10 + 2 files changed, 183 insertions(+) create mode 100644 simpleclient_common/src/test/java/io/prometheus/client/exporter/common/TextFormatOpenMetricsTest.java diff --git a/simpleclient_common/src/test/java/io/prometheus/client/exporter/common/TextFormatOpenMetricsTest.java b/simpleclient_common/src/test/java/io/prometheus/client/exporter/common/TextFormatOpenMetricsTest.java new file mode 100644 index 000000000..c8e6f4848 --- /dev/null +++ b/simpleclient_common/src/test/java/io/prometheus/client/exporter/common/TextFormatOpenMetricsTest.java @@ -0,0 +1,173 @@ +package io.prometheus.client.exporter.common; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; + +import io.prometheus.client.Collector; +import io.prometheus.client.CollectorRegistry; +import io.prometheus.client.Counter; +import io.prometheus.client.Gauge; +import io.prometheus.client.Summary; + + +public class TextFormatOpenMetricsTest { + CollectorRegistry registry; + StringWriter writer; + + @Before + public void setUp() { + registry = new CollectorRegistry(); + writer = new StringWriter(); + } + + @Test + public void testGaugeOutput() throws IOException { + Gauge noLabels = Gauge.build().name("nolabels").help("help").register(registry); + noLabels.inc(); + TextFormat.writeOpenMetrics100(writer, registry.metricFamilySamples()); + assertEquals("# TYPE nolabels gauge\n" + + "# HELP nolabels help\n" + + "nolabels 1.0\n" + + "# EOF\n", writer.toString()); + } + + @Test + public void testValueInfinity() throws IOException { + Gauge noLabels = Gauge.build().name("nolabels").help("help").register(registry); + noLabels.set(Double.POSITIVE_INFINITY); + TextFormat.writeOpenMetrics100(writer, registry.metricFamilySamples()); + assertEquals("# TYPE nolabels gauge\n" + + "# HELP nolabels help\n" + + "nolabels +Inf\n" + + "# EOF\n", writer.toString()); + } + + @Test + public void testCounterOutput() throws IOException { + Counter noLabels = Counter.build().name("nolabels").help("help").register(registry); + noLabels.inc(); + TextFormat.writeOpenMetrics100(writer, registry.metricFamilySamples()); + assertEquals("# TYPE nolabels counter\n" + + "# HELP nolabels help\n" + + "nolabels_total 1.0\n" + + "# EOF\n", writer.toString()); + } + + @Test + public void testCounterSamplesMissingTotal() throws IOException { + + class CustomCollector extends Collector { + public List collect() { + List mfs = new ArrayList(); + ArrayList labelNames = new ArrayList(); + ArrayList labelValues = new ArrayList(); + ArrayList samples = new ArrayList(); + MetricFamilySamples.Sample sample = new MetricFamilySamples.Sample("nolabels", labelNames, labelValues, 1.0); + samples.add(sample); + mfs.add(new MetricFamilySamples("nolabels", Collector.Type.COUNTER, "help", samples)); + return mfs; + } + } + + new CustomCollector().register(registry); + TextFormat.writeOpenMetrics100(writer, registry.metricFamilySamples()); + assertEquals("# TYPE nolabels counter\n" + + "# HELP nolabels help\n" + + "nolabels_total 1.0\n" + + "# EOF\n", writer.toString()); + } + + @Test + public void testMetricOutputWithTimestamp() throws IOException { + + class CustomCollector extends Collector { + public List collect() { + List mfs = new ArrayList(); + ArrayList labelNames = new ArrayList(); + ArrayList labelValues = new ArrayList(); + ArrayList samples = new ArrayList(); + MetricFamilySamples.Sample sample = new MetricFamilySamples.Sample("nolabels", labelNames, labelValues, 1.0, 1518123006L); + samples.add(sample); + mfs.add(new MetricFamilySamples("nolabels", Collector.Type.UNTYPED, "help", samples)); + return mfs; + } + } + + new CustomCollector().register(registry); + TextFormat.writeOpenMetrics100(writer, registry.metricFamilySamples()); + assertEquals("# TYPE nolabels unknown\n" + + "# HELP nolabels help\n" + + "nolabels 1.0 1518123.006\n" + + "# EOF\n", writer.toString()); + } + + @Test + public void testSummaryOutput() throws IOException { + Summary noLabels = Summary.build().name("nolabels").help("help").register(registry); + noLabels.observe(2); + TextFormat.writeOpenMetrics100(writer, registry.metricFamilySamples()); + assertEquals("# TYPE nolabels summary\n" + + "# HELP nolabels help\n" + + "nolabels_count 1.0\n" + + "nolabels_sum 2.0\n" + + "# EOF\n", writer.toString()); + } + + @Test + public void testSummaryOutputWithQuantiles() throws IOException { + Summary labelsAndQuantiles = Summary.build() + .quantile(0.5, 0.05).quantile(0.9, 0.01).quantile(0.99, 0.001) + .labelNames("l").name("labelsAndQuantiles").help("help").register(registry); + labelsAndQuantiles.labels("a").observe(2); + writer = new StringWriter(); + TextFormat.writeOpenMetrics100(writer, registry.metricFamilySamples()); + assertEquals("# TYPE labelsAndQuantiles summary\n" + + "# HELP labelsAndQuantiles help\n" + + "labelsAndQuantiles{l=\"a\",quantile=\"0.5\"} 2.0\n" + + "labelsAndQuantiles{l=\"a\",quantile=\"0.9\"} 2.0\n" + + "labelsAndQuantiles{l=\"a\",quantile=\"0.99\"} 2.0\n" + + "labelsAndQuantiles_count{l=\"a\"} 1.0\n" + + "labelsAndQuantiles_sum{l=\"a\"} 2.0\n" + + "# EOF\n", writer.toString()); + } + + @Test + public void testLabelsOutput() throws IOException { + Gauge labels = Gauge.build().name("labels").help("help").labelNames("l").register(registry); + labels.labels("a").inc(); + TextFormat.writeOpenMetrics100(writer, registry.metricFamilySamples()); + assertEquals("# TYPE labels gauge\n" + + "# HELP labels help\n" + + "labels{l=\"a\"} 1.0\n" + + "# EOF\n", writer.toString()); + } + + @Test + public void testLabelValuesEscaped() throws IOException { + Gauge labels = Gauge.build().name("labels").help("help").labelNames("l").register(registry); + labels.labels("ąćčęntěd a\nb\\c\"d").inc(); + TextFormat.writeOpenMetrics100(writer, registry.metricFamilySamples()); + assertEquals("# TYPE labels gauge\n" + + "# HELP labels help\n" + + "labels{l=\"ąćčęntěd a\\nb\\\\c\\\"d\"} 1.0\n" + + "# EOF\n", writer.toString()); + } + + @Test + public void testHelpEscaped() throws IOException { + Gauge noLabels = Gauge.build().name("nolabels").help("ąćčęntěd h\"e\\l\np").register(registry); + noLabels.inc(); + TextFormat.writeOpenMetrics100(writer, registry.metricFamilySamples()); + assertEquals("# TYPE nolabels gauge\n" + + "# HELP nolabels ąćčęntěd h\\\"e\\\\l\\np\n" + + "nolabels 1.0\n" + + "# EOF\n", writer.toString()); + } +} diff --git a/simpleclient_common/src/test/java/io/prometheus/client/exporter/common/TextFormatTest.java b/simpleclient_common/src/test/java/io/prometheus/client/exporter/common/TextFormatTest.java index f1886eeb7..b98972a26 100644 --- a/simpleclient_common/src/test/java/io/prometheus/client/exporter/common/TextFormatTest.java +++ b/simpleclient_common/src/test/java/io/prometheus/client/exporter/common/TextFormatTest.java @@ -160,4 +160,14 @@ public void testHelpEscaped() throws IOException { + "# TYPE nolabels gauge\n" + "nolabels 1.0\n", writer.toString()); } + + @Test + public void testChooseContentType() throws IOException { + assertEquals(TextFormat.CONTENT_TYPE_004, TextFormat.chooseContentType(null)); + assertEquals(TextFormat.CONTENT_TYPE_004, TextFormat.chooseContentType("")); + assertEquals(TextFormat.CONTENT_TYPE_004, TextFormat.chooseContentType("text/plain;version=0.0.4")); + assertEquals(TextFormat.CONTENT_TYPE_004, TextFormat.chooseContentType("foo")); + assertEquals(TextFormat.CONTENT_TYPE_OPENMETRICS_100, TextFormat.chooseContentType("application/openmetrics-text; version=0.0.1,text/plain;version=0.0.4;q=0.5,*/*;q=0.1")); + assertEquals(TextFormat.CONTENT_TYPE_OPENMETRICS_100, TextFormat.chooseContentType("application/openmetrics-text; version=1.0.0")); + } } From 801f91e05cbee4dfe99bd65724d6a7cfede8e94a Mon Sep 17 00:00:00 2001 From: Brian Brazil Date: Fri, 15 Jan 2021 14:22:35 +0000 Subject: [PATCH 06/11] Add handling for gsum/gcount/created in Prometheus exposition format. Signed-off-by: Brian Brazil --- .../client/exporter/common/TextFormat.java | 37 ++++++++++++--- .../exporter/common/TextFormatTest.java | 45 +++++++++++++++++++ 2 files changed, 76 insertions(+), 6 deletions(-) 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 ed3992f24..a16a57632 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 @@ -2,7 +2,12 @@ import java.io.IOException; import java.io.Writer; +import java.util.ArrayList; +import java.util.Collections; import java.util.Enumeration; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; import io.prometheus.client.Collector; @@ -53,31 +58,47 @@ public static void writeFormat(String contentType, 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; - if (metricFamilySamples.type == Collector.Type.COUNTER) { - name += "_total"; - } writer.write("# HELP "); writer.write(name); + if (metricFamilySamples.type == Collector.Type.COUNTER) { + writer.write("_total"); + } writer.write(' '); writeEscapedHelp(writer, metricFamilySamples.help); writer.write('\n'); writer.write("# TYPE "); writer.write(name); + if (metricFamilySamples.type == Collector.Type.COUNTER) { + writer.write("_total"); + } 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) { - writer.write(sample.name); - if (metricFamilySamples.type == Collector.Type.COUNTER && sample.name.equals(metricFamilySamples.name)) { - writer.write("_total"); + /* 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) { @@ -97,6 +118,10 @@ public static void write004(Writer writer, Enumeration collect() { + List mfs = new ArrayList(); + ArrayList labelNames = new ArrayList(); + ArrayList labelValues = new ArrayList(); + ArrayList samples = new ArrayList(); + samples.add(new MetricFamilySamples.Sample("nolabels_bucket", Arrays.asList("le"), Arrays.asList("+Inf"), 2.0)); + samples.add(new MetricFamilySamples.Sample("nolabels_gcount", labelNames, labelValues, 2.0)); + samples.add(new MetricFamilySamples.Sample("nolabels_gsum", labelNames, labelValues, 7.0)); + samples.add(new MetricFamilySamples.Sample("nolabels_created", labelNames, labelValues, 1234.0)); + mfs.add(new MetricFamilySamples("nolabels", Collector.Type.GAUGE_HISTOGRAM, "help", samples)); + return mfs; + } + } + new CustomCollector().register(registry); + writer = new StringWriter(); + TextFormat.write004(writer, registry.metricFamilySamples()); + assertEquals("# HELP nolabels help\n" + + "# TYPE nolabels histogram\n" + + "nolabels_bucket{le=\"+Inf\",} 2.0\n" + + "# HELP nolabels_created help\n" + + "# TYPE nolabels_created gauge\n" + + "nolabels_created 1234.0\n" + + "# HELP nolabels_gcount help\n" + + "# TYPE nolabels_gcount gauge\n" + + "nolabels_gcount 2.0\n" + + "# HELP nolabels_gsum help\n" + + "# TYPE nolabels_gsum gauge\n" + + "nolabels_gsum 7.0\n", writer.toString()); + } + @Test public void testLabelsOutput() throws IOException { Gauge labels = Gauge.build().name("labels").help("help").labelNames("l").register(registry); From 5c0d1e7ee22897b87f73358caee43d95dcd07408 Mon Sep 17 00:00:00 2001 From: Brian Brazil Date: Fri, 15 Jan 2021 14:47:38 +0000 Subject: [PATCH 07/11] Add _created for direct instrumentation. Signed-off-by: Brian Brazil --- .../prometheus/client/CollectorRegistry.java | 8 ++++++ .../java/io/prometheus/client/Counter.java | 8 ++++++ .../java/io/prometheus/client/Histogram.java | 8 ++++-- .../java/io/prometheus/client/Summary.java | 8 ++++-- .../io/prometheus/client/CounterTest.java | 1 + .../io/prometheus/client/HistogramTest.java | 2 ++ .../io/prometheus/client/SummaryTest.java | 2 ++ .../common/TextFormatOpenMetricsTest.java | 9 ++++--- .../exporter/common/TextFormatTest.java | 26 +++++++++++++++---- 9 files changed, 60 insertions(+), 12 deletions(-) diff --git a/simpleclient/src/main/java/io/prometheus/client/CollectorRegistry.java b/simpleclient/src/main/java/io/prometheus/client/CollectorRegistry.java index b7bc0284b..590beeba5 100644 --- a/simpleclient/src/main/java/io/prometheus/client/CollectorRegistry.java +++ b/simpleclient/src/main/java/io/prometheus/client/CollectorRegistry.java @@ -113,12 +113,20 @@ private List collectorNames(Collector m) { case SUMMARY: names.add(family.name + "_count"); names.add(family.name + "_sum"); + names.add(family.name + "_created"); names.add(family.name); break; case HISTOGRAM: names.add(family.name + "_count"); names.add(family.name + "_sum"); names.add(family.name + "_bucket"); + names.add(family.name + "_created"); + names.add(family.name); + break; + case GAUGE_HISTOGRAM: + names.add(family.name + "_gcount"); + names.add(family.name + "_gsum"); + names.add(family.name + "_bucket"); names.add(family.name); break; default: diff --git a/simpleclient/src/main/java/io/prometheus/client/Counter.java b/simpleclient/src/main/java/io/prometheus/client/Counter.java index 27a7d5627..900812b16 100644 --- a/simpleclient/src/main/java/io/prometheus/client/Counter.java +++ b/simpleclient/src/main/java/io/prometheus/client/Counter.java @@ -112,6 +112,7 @@ protected Child newChild() { */ public static class Child { private final DoubleAdder value = new DoubleAdder(); + private final long created = System.currentTimeMillis(); /** * Increment the counter by 1. */ @@ -134,6 +135,12 @@ public void inc(double amt) { public double get() { return value.sum(); } + /** + * Get the created time of the counter in milliseconds. + */ + public long created() { + return created; + } } // Convenience methods. @@ -163,6 +170,7 @@ public List collect() { List samples = new ArrayList(children.size()); for(Map.Entry, Child> c: children.entrySet()) { samples.add(new MetricFamilySamples.Sample(fullname + "_total", labelNames, c.getKey(), c.getValue().get())); + samples.add(new MetricFamilySamples.Sample(fullname + "_created", labelNames, c.getKey(), c.getValue().created() / 1000.0)); } return familySamplesList(Type.COUNTER, samples); } diff --git a/simpleclient/src/main/java/io/prometheus/client/Histogram.java b/simpleclient/src/main/java/io/prometheus/client/Histogram.java index 1d8b1a2fa..30595e187 100644 --- a/simpleclient/src/main/java/io/prometheus/client/Histogram.java +++ b/simpleclient/src/main/java/io/prometheus/client/Histogram.java @@ -231,10 +231,12 @@ public E time(Callable timeable) { public static class Value { public final double sum; public final double[] buckets; + public final long created; - public Value(double sum, double[] buckets) { + public Value(double sum, double[] buckets, long created) { this.sum = sum; this.buckets = buckets; + this.created = created; } } @@ -248,6 +250,7 @@ private Child(double[] buckets) { private final double[] upperBounds; private final DoubleAdder[] cumulativeCounts; private final DoubleAdder sum = new DoubleAdder(); + private final long created = System.currentTimeMillis(); /** @@ -283,7 +286,7 @@ public Value get() { acc += cumulativeCounts[i].sum(); buckets[i] = acc; } - return new Value(sum.sum(), buckets); + return new Value(sum.sum(), buckets, created); } } @@ -337,6 +340,7 @@ public List collect() { } samples.add(new MetricFamilySamples.Sample(fullname + "_count", labelNames, c.getKey(), v.buckets[buckets.length-1])); samples.add(new MetricFamilySamples.Sample(fullname + "_sum", labelNames, c.getKey(), v.sum)); + samples.add(new MetricFamilySamples.Sample(fullname + "_created", labelNames, c.getKey(), v.created / 1000.0)); } return familySamplesList(Type.HISTOGRAM, samples); diff --git a/simpleclient/src/main/java/io/prometheus/client/Summary.java b/simpleclient/src/main/java/io/prometheus/client/Summary.java index 4d79e558a..9d2aca3f8 100644 --- a/simpleclient/src/main/java/io/prometheus/client/Summary.java +++ b/simpleclient/src/main/java/io/prometheus/client/Summary.java @@ -239,11 +239,13 @@ public static class Value { public final double count; public final double sum; public final SortedMap quantiles; + public final long created; - private Value(double count, double sum, List quantiles, TimeWindowQuantiles quantileValues) { + private Value(double count, double sum, List quantiles, TimeWindowQuantiles quantileValues, long created) { this.count = count; this.sum = sum; this.quantiles = Collections.unmodifiableSortedMap(snapshot(quantiles, quantileValues)); + this.created = created; } private SortedMap snapshot(List quantiles, TimeWindowQuantiles quantileValues) { @@ -263,6 +265,7 @@ private SortedMap snapshot(List quantiles, TimeWindowQ private final DoubleAdder sum = new DoubleAdder(); private final List quantiles; private final TimeWindowQuantiles quantileValues; + private final long created = System.currentTimeMillis(); private Child(List quantiles, long maxAgeSeconds, int ageBuckets) { this.quantiles = quantiles; @@ -297,7 +300,7 @@ public Timer startTimer() { * Warning: The definition of {@link Value} is subject to change. */ public Value get() { - return new Value(count.sum(), sum.sum(), quantiles, quantileValues); + return new Value(count.sum(), sum.sum(), quantiles, quantileValues, created); } } @@ -360,6 +363,7 @@ public List collect() { } samples.add(new MetricFamilySamples.Sample(fullname + "_count", labelNames, c.getKey(), v.count)); samples.add(new MetricFamilySamples.Sample(fullname + "_sum", labelNames, c.getKey(), v.sum)); + samples.add(new MetricFamilySamples.Sample(fullname + "_created", labelNames, c.getKey(), v.created / 1000.0)); } return familySamplesList(Type.SUMMARY, samples); diff --git a/simpleclient/src/test/java/io/prometheus/client/CounterTest.java b/simpleclient/src/test/java/io/prometheus/client/CounterTest.java index 6dc089d6a..2d7ba0735 100644 --- a/simpleclient/src/test/java/io/prometheus/client/CounterTest.java +++ b/simpleclient/src/test/java/io/prometheus/client/CounterTest.java @@ -97,6 +97,7 @@ public void testCollect() { ArrayList labelValues = new ArrayList(); labelValues.add("a"); samples.add(new Collector.MetricFamilySamples.Sample("labels_seconds_total", labelNames, labelValues, 1.0)); + samples.add(new Collector.MetricFamilySamples.Sample("labels_seconds_created", labelNames, labelValues, labels.labels("a").created() / 1000.0)); Collector.MetricFamilySamples mfsFixture = new Collector.MetricFamilySamples("labels_seconds", "seconds", Collector.Type.COUNTER, "help", samples); assertEquals(1, mfs.size()); diff --git a/simpleclient/src/test/java/io/prometheus/client/HistogramTest.java b/simpleclient/src/test/java/io/prometheus/client/HistogramTest.java index 1a09a8939..15ac00a76 100644 --- a/simpleclient/src/test/java/io/prometheus/client/HistogramTest.java +++ b/simpleclient/src/test/java/io/prometheus/client/HistogramTest.java @@ -204,6 +204,8 @@ public void testCollect() { } samples.add(new Collector.MetricFamilySamples.Sample("labels_count", labelNames, labelValues, 1.0)); samples.add(new Collector.MetricFamilySamples.Sample("labels_sum", labelNames, labelValues, 2.0)); + samples.add(new Collector.MetricFamilySamples.Sample("labels_created", labelNames, labelValues, labels.labels("a").get().created / 1000.0)); + Collector.MetricFamilySamples mfsFixture = new Collector.MetricFamilySamples("labels", Collector.Type.HISTOGRAM, "help", samples); assertEquals(1, mfs.size()); diff --git a/simpleclient/src/test/java/io/prometheus/client/SummaryTest.java b/simpleclient/src/test/java/io/prometheus/client/SummaryTest.java index 06e1f9d9b..c6f70506d 100644 --- a/simpleclient/src/test/java/io/prometheus/client/SummaryTest.java +++ b/simpleclient/src/test/java/io/prometheus/client/SummaryTest.java @@ -183,6 +183,7 @@ public void testCollect() { labelValues.add("a"); samples.add(new Collector.MetricFamilySamples.Sample("labels_count", labelNames, labelValues, 1.0)); samples.add(new Collector.MetricFamilySamples.Sample("labels_sum", labelNames, labelValues, 2.0)); + samples.add(new Collector.MetricFamilySamples.Sample("labels_created", labelNames, labelValues, labels.labels("a").get().created / 1000.0)); Collector.MetricFamilySamples mfsFixture = new Collector.MetricFamilySamples("labels", Collector.Type.SUMMARY, "help", samples); assertEquals(1, mfs.size()); @@ -200,6 +201,7 @@ public void testCollectWithQuantiles() { samples.add(new Collector.MetricFamilySamples.Sample("labels_and_quantiles", asList("l", "quantile"), asList("a", "0.99"), 2.0)); samples.add(new Collector.MetricFamilySamples.Sample("labels_and_quantiles_count", asList("l"), asList("a"), 1.0)); samples.add(new Collector.MetricFamilySamples.Sample("labels_and_quantiles_sum", asList("l"), asList("a"), 2.0)); + samples.add(new Collector.MetricFamilySamples.Sample("labels_and_quantiles_created", asList("l"), asList("a"), labelsAndQuantiles.labels("a").get().created / 1000.0)); Collector.MetricFamilySamples mfsFixture = new Collector.MetricFamilySamples("labels_and_quantiles", Collector.Type.SUMMARY, "help", samples); assertEquals(1, mfs.size()); diff --git a/simpleclient_common/src/test/java/io/prometheus/client/exporter/common/TextFormatOpenMetricsTest.java b/simpleclient_common/src/test/java/io/prometheus/client/exporter/common/TextFormatOpenMetricsTest.java index c8e6f4848..3d013ee5c 100644 --- a/simpleclient_common/src/test/java/io/prometheus/client/exporter/common/TextFormatOpenMetricsTest.java +++ b/simpleclient_common/src/test/java/io/prometheus/client/exporter/common/TextFormatOpenMetricsTest.java @@ -57,7 +57,8 @@ public void testCounterOutput() throws IOException { assertEquals("# TYPE nolabels counter\n" + "# HELP nolabels help\n" + "nolabels_total 1.0\n" - + "# EOF\n", writer.toString()); + + "nolabels_created 1234.0\n" + + "# EOF\n", writer.toString().replaceAll("_created [0-9E.]+", "_created 1234.0")); } @Test @@ -117,7 +118,8 @@ public void testSummaryOutput() throws IOException { + "# HELP nolabels help\n" + "nolabels_count 1.0\n" + "nolabels_sum 2.0\n" - + "# EOF\n", writer.toString()); + + "nolabels_created 1234.0\n" + + "# EOF\n", writer.toString().replaceAll("_created [0-9E.]+", "_created 1234.0")); } @Test @@ -135,7 +137,8 @@ public void testSummaryOutputWithQuantiles() throws IOException { + "labelsAndQuantiles{l=\"a\",quantile=\"0.99\"} 2.0\n" + "labelsAndQuantiles_count{l=\"a\"} 1.0\n" + "labelsAndQuantiles_sum{l=\"a\"} 2.0\n" - + "# EOF\n", writer.toString()); + + "labelsAndQuantiles_created{l=\"a\"} 1234.0\n" + + "# EOF\n", writer.toString().replaceAll("(_created\\{.*\\}) [0-9E.]+", "$1 1234.0")); } @Test diff --git a/simpleclient_common/src/test/java/io/prometheus/client/exporter/common/TextFormatTest.java b/simpleclient_common/src/test/java/io/prometheus/client/exporter/common/TextFormatTest.java index a1b367f06..98fbabfb2 100644 --- a/simpleclient_common/src/test/java/io/prometheus/client/exporter/common/TextFormatTest.java +++ b/simpleclient_common/src/test/java/io/prometheus/client/exporter/common/TextFormatTest.java @@ -55,7 +55,11 @@ public void testCounterOutput() throws IOException { TextFormat.write004(writer, registry.metricFamilySamples()); assertEquals("# HELP nolabels_total help\n" + "# TYPE nolabels_total counter\n" - + "nolabels_total 1.0\n", writer.toString()); + + "nolabels_total 1.0\n" + + "# HELP nolabels_created help\n" + + "# TYPE nolabels_created gauge\n" + + "nolabels_created 1234.0\n", + writer.toString().replaceAll("_created [0-9E.]+", "_created 1234.0")); } @Test @@ -65,7 +69,11 @@ public void testCounterWithTotalOutput() throws IOException { TextFormat.write004(writer, registry.metricFamilySamples()); assertEquals("# HELP nolabels_total help\n" + "# TYPE nolabels_total counter\n" - + "nolabels_total 1.0\n", writer.toString()); + + "nolabels_total 1.0\n" + + "# HELP nolabels_created help\n" + + "# TYPE nolabels_created gauge\n" + + "nolabels_created 1234.0\n", + writer.toString().replaceAll("_created [0-9E.]+", "_created 1234.0")); } @@ -107,7 +115,7 @@ public List collect() { return mfs; } } - + new CustomCollector().register(registry); TextFormat.write004(writer, registry.metricFamilySamples()); assertEquals("# HELP nolabels help\n" @@ -123,7 +131,11 @@ public void testSummaryOutput() throws IOException { assertEquals("# HELP nolabels help\n" + "# TYPE nolabels summary\n" + "nolabels_count 1.0\n" - + "nolabels_sum 2.0\n", writer.toString()); + + "nolabels_sum 2.0\n" + + "# HELP nolabels_created help\n" + + "# TYPE nolabels_created gauge\n" + + "nolabels_created 1234.0\n", + writer.toString().replaceAll("_created [0-9E.]+", "_created 1234.0")); } @Test @@ -140,7 +152,11 @@ public void testSummaryOutputWithQuantiles() throws IOException { + "labelsAndQuantiles{l=\"a\",quantile=\"0.9\",} 2.0\n" + "labelsAndQuantiles{l=\"a\",quantile=\"0.99\",} 2.0\n" + "labelsAndQuantiles_count{l=\"a\",} 1.0\n" - + "labelsAndQuantiles_sum{l=\"a\",} 2.0\n", writer.toString()); + + "labelsAndQuantiles_sum{l=\"a\",} 2.0\n" + + "# HELP labelsAndQuantiles_created help\n" + + "# TYPE labelsAndQuantiles_created gauge\n" + + "labelsAndQuantiles_created{l=\"a\",} 1234.0\n", + writer.toString().replaceAll("(_created\\{.*\\}) [0-9E.]+", "$1 1234.0")); } @Test From 92d34e36ef1d75c8b99af652484003bc212e03dc Mon Sep 17 00:00:00 2001 From: Brian Brazil Date: Mon, 18 Jan 2021 12:47:14 +0000 Subject: [PATCH 08/11] Add support for Enumeration. Signed-off-by: Brian Brazil --- .../io/prometheus/client/Enumeration.java | 223 ++++++++++++++++++ .../io/prometheus/client/EnumerationTest.java | 114 +++++++++ 2 files changed, 337 insertions(+) create mode 100644 simpleclient/src/main/java/io/prometheus/client/Enumeration.java create mode 100644 simpleclient/src/test/java/io/prometheus/client/EnumerationTest.java diff --git a/simpleclient/src/main/java/io/prometheus/client/Enumeration.java b/simpleclient/src/main/java/io/prometheus/client/Enumeration.java new file mode 100644 index 000000000..900f1ec30 --- /dev/null +++ b/simpleclient/src/main/java/io/prometheus/client/Enumeration.java @@ -0,0 +1,223 @@ +package io.prometheus.client; + +import io.prometheus.client.CKMSQuantiles.Quantile; + +import java.io.Closeable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.LinkedHashSet; +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; + +/** + * Enumeration metric, to track which of a set of states something is in. + * + * The first provided state will be the default. + * + *

+ * Example enumeration: + *

+ * {@code
+ *   class YourClass {
+ *     static final Enumeration taskState = Enumeration.build()
+ *         .name("task_state").help("State of the task.")
+ *         .states("stopped", "starting", "running")
+ *         .register();
+ *
+ *     void stop() {
+ *          // Your code here.
+ *          taskState.state("stopped")
+ *     }
+ *   }
+ * }
+ * 
+ * + * You can also use a Java Enum: + *
+ *   class YourClass {
+ *     public enum yourEnum {
+ *       STOPPED,
+ *       STARTING,
+ *       RUNNING,
+ *     }
+ *     static final Enumeration taskState = Enumeration.build()
+ *         .name("task_state").help("State of the task.")
+ *         .states(yourEnum.class)
+ *         .register();
+ *
+ *     void stop() {
+ *          // Your code here.
+ *          taskState.state(yourEnum.STOPPED)
+ *     }
+ *   }
+ * }
+ * 
+ */ +public class Enumeration extends SimpleCollector implements Counter.Describable { + + private final Set states; + + Enumeration(Builder b) { + super(b); + for (String label : labelNames) { + if (label.equals(fullname)) { + throw new IllegalStateException("Enumeration cannot have a label named the same as its metric name."); + } + } + states = b.states; + initializeNoLabelsChild(); + } + + public static class Builder extends SimpleCollector.Builder { + + private Set states; + + public Builder states(String... s) { + if (s.length == 0) { + throw new IllegalArgumentException("There must be at least one state"); + } + // LinkedHashSet so we can know which was the first state. + states = new LinkedHashSet(); + states.addAll(Arrays.asList(s)); + return this; + } + + /** + * Take states from the names of the values in an Enum class. + */ + public Builder states(Class e) { + Object[] vals = e.getEnumConstants(); + String[] s = new String[vals.length]; + for(int i = 0; i < vals.length; i++) { + s[i] = ((Enum)vals[i]).name(); + } + return states(s); + } + + @Override + public Enumeration create() { + if (states == null) { + throw new IllegalStateException("Enumeration states must be specified."); + } + if (!unit.isEmpty()) { + throw new IllegalStateException("Enumeration metrics cannot have a unit."); + } + dontInitializeNoLabelsChild = true; + return new Enumeration(this); + } + } + + /** + * Return a Builder to allow configuration of a new Enumeration. Ensures required fields are provided. + * + * @param name The name of the metric + * @param help The help string of the metric + */ + public static Builder build(String name, String help) { + return new Builder().name(name).help(help); + } + + /** + * Return a Builder to allow configuration of a new Enumeration. + */ + public static Builder build() { + return new Builder(); + } + + @Override + protected Child newChild() { + return new Child(states); + } + + + /** + * The value of a single Enumeration. + *

+ * Warning: References to a Child become invalid after using + * {@link SimpleCollector#remove} or {@link SimpleCollector#clear}. + */ + public static class Child { + + private String value; + private final Set states; + + private Child(Set states) { + this.states = states; + value = states.iterator().next(); // Initialize with the first state. + } + + /** + * Set the state. + */ + public void state(String s) { + if (!states.contains(s)) { + throw new IllegalArgumentException("Unknown state " + s); + } + value = s; + } + + /** + * Set the state. + */ + public void state(Enum e) { + state(e.name()); + } + + /** + * Get the state. + */ + public String get() { + return value; + } + } + + // Convenience methods. + /** + * Set the state on the enum with no labels. + */ + public void state(String s) { + noLabelsChild.state(s); + } + + /** + * Set the state on the enum with no labels. + */ + public void state(Enum e) { + noLabelsChild.state(e); + } + + /** + * Get the value of the Enumeration. + */ + public String get() { + return noLabelsChild.get(); + } + + @Override + public List collect() { + List samples = new ArrayList(); + for(Map.Entry, Child> c: children.entrySet()) { + String v = c.getValue().get(); + List labelNamesWithState = new ArrayList(labelNames); + labelNamesWithState.add(fullname); + for(String s : states) { + List labelValuesWithState = new ArrayList(c.getKey()); + labelValuesWithState.add(s); + samples.add(new MetricFamilySamples.Sample(fullname, labelNamesWithState, labelValuesWithState, s.equals(v) ? 1.0 : 0.0)); + } + } + + return familySamplesList(Type.STATE_SET, samples); + } + + @Override + public List describe() { + return Collections.singletonList( + new MetricFamilySamples(fullname, Type.STATE_SET, help, Collections.emptyList())); + } + +} diff --git a/simpleclient/src/test/java/io/prometheus/client/EnumerationTest.java b/simpleclient/src/test/java/io/prometheus/client/EnumerationTest.java new file mode 100644 index 000000000..158036360 --- /dev/null +++ b/simpleclient/src/test/java/io/prometheus/client/EnumerationTest.java @@ -0,0 +1,114 @@ +package io.prometheus.client; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + + +public class EnumerationTest { + + CollectorRegistry registry; + Enumeration noLabels, labels; + + @Before + public void setUp() { + registry = new CollectorRegistry(); + noLabels = Enumeration.build().states("foo", "bar").name("nolabels").help("help").register(registry); + labels = Enumeration.build().states("foo", "bar").name("labels").help("help").labelNames("l").register(registry); + } + + private Double getNoLabelState(String s) { + return registry.getSampleValue("nolabels", new String[]{"nolabels"}, new String[]{s}); + } + private Double getLabeledState(String labelValue, String s) { + return registry.getSampleValue("labels", new String[]{"l", "labels"}, new String[]{labelValue, s}); + } + + @Test + public void testState() { + noLabels.state("bar"); + assertEquals(0.0, getNoLabelState("foo"), .001); + assertEquals(1.0, getNoLabelState("bar"), .001); + noLabels.state("foo"); + assertEquals(1.0, getNoLabelState("foo"), .001); + assertEquals(0.0, getNoLabelState("bar"), .001); + } + + @Test + public void testDefaultValue() { + assertEquals(1.0, getNoLabelState("foo"), .001); + assertEquals(0.0, getNoLabelState("bar"), .001); + } + + @Test + public void testLabels() { + assertEquals(null, getLabeledState("a", "foo")); + assertEquals(null, getLabeledState("a", "bar")); + assertEquals(null, getLabeledState("b", "foo")); + assertEquals(null, getLabeledState("b", "bar")); + labels.labels("a").state("foo"); + assertEquals(1.0, getLabeledState("a", "foo"), .001); + assertEquals(0.0, getLabeledState("a", "bar"), .001); + assertEquals(null, getLabeledState("b", "foo")); + assertEquals(null, getLabeledState("b", "bar")); + labels.labels("b").state("bar"); + assertEquals(1.0, getLabeledState("a", "foo"), .001); + assertEquals(0.0, getLabeledState("a", "bar"), .001); + assertEquals(0.0, getLabeledState("b", "foo"), .001); + assertEquals(1.0, getLabeledState("b", "bar"), .001); + } + + public enum myEnum { + FOO, + BAR, + } + + @Test + public void testJavaEnum() { + Enumeration metric = Enumeration.build().states(myEnum.class).name("enum").help("help").register(registry); + metric.state(myEnum.BAR); + assertEquals(0.0, registry.getSampleValue("enum", new String[]{"enum"}, new String[]{"FOO"}), .001); + assertEquals(1.0, registry.getSampleValue("enum", new String[]{"enum"}, new String[]{"BAR"}), .001); + } + + @Test(expected=IllegalStateException.class) + public void testDuplicateNameLabelThrows() { + Enumeration.build().states("foo", "bar").name("labels").help("help").labelNames("labels").create(); + } + + @Test(expected=IllegalStateException.class) + public void testNoStatesThrows() { + Enumeration.build().name("nolabels").help("help").create(); + } + + @Test(expected=IllegalArgumentException.class) + public void testEmptyStatesThrows() { + Enumeration.build().states().name("nolabels").help("help").create(); + } + + @Test(expected=IllegalStateException.class) + public void testUnitThrows() { + Enumeration.build().states("foo", "bar").unit("seconds").name("nolabels").help("help").create(); + } + + @Test + public void testCollect() { + labels.labels("a").state("bar"); + List mfs = labels.collect(); + + ArrayList samples = new ArrayList(); + samples.add(new Collector.MetricFamilySamples.Sample("labels", asList("l", "labels"), asList("a", "foo"), 0.0)); + samples.add(new Collector.MetricFamilySamples.Sample("labels", asList("l", "labels"), asList("a", "bar"), 1.0)); + Collector.MetricFamilySamples mfsFixture = new Collector.MetricFamilySamples("labels", Collector.Type.STATE_SET, "help", samples); + + assertEquals(1, mfs.size()); + assertEquals(mfsFixture, mfs.get(0)); + } +} From 37fa7fb33c49cfbb9d2620ee59e350e23af8d2fc Mon Sep 17 00:00:00 2001 From: Brian Brazil Date: Mon, 18 Jan 2021 13:57:02 +0000 Subject: [PATCH 09/11] Add support for Info metrics Signed-off-by: Brian Brazil --- .../prometheus/client/CollectorRegistry.java | 4 + .../main/java/io/prometheus/client/Info.java | 177 ++++++++++++++++++ .../io/prometheus/client/EnumerationTest.java | 2 +- .../java/io/prometheus/client/InfoTest.java | 96 ++++++++++ .../client/exporter/common/TextFormat.java | 6 + .../common/TextFormatOpenMetricsTest.java | 12 ++ .../exporter/common/TextFormatTest.java | 10 + 7 files changed, 306 insertions(+), 1 deletion(-) create mode 100644 simpleclient/src/main/java/io/prometheus/client/Info.java create mode 100644 simpleclient/src/test/java/io/prometheus/client/InfoTest.java diff --git a/simpleclient/src/main/java/io/prometheus/client/CollectorRegistry.java b/simpleclient/src/main/java/io/prometheus/client/CollectorRegistry.java index 590beeba5..55d33287d 100644 --- a/simpleclient/src/main/java/io/prometheus/client/CollectorRegistry.java +++ b/simpleclient/src/main/java/io/prometheus/client/CollectorRegistry.java @@ -129,6 +129,10 @@ private List collectorNames(Collector m) { names.add(family.name + "_bucket"); names.add(family.name); break; + case INFO: + names.add(family.name + "_info"); + names.add(family.name); + break; default: names.add(family.name); } diff --git a/simpleclient/src/main/java/io/prometheus/client/Info.java b/simpleclient/src/main/java/io/prometheus/client/Info.java new file mode 100644 index 000000000..e878ae386 --- /dev/null +++ b/simpleclient/src/main/java/io/prometheus/client/Info.java @@ -0,0 +1,177 @@ +package io.prometheus.client; + +import io.prometheus.client.CKMSQuantiles.Quantile; + +import java.io.Closeable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; + +/** + * Info metric, key-value pairs. + * + * Examples of Info include, build information, version information, and potential target metadata, + * The first provided state will be the default. + * + *

+ * Example enumeration: + *

+ * {@code
+ *   class YourClass {
+ *     static final Info buildInfo = Info.build()
+ *         .name("your_build_info").help("Build information.")
+ *         .register();
+ *
+ *     void func() {
+ *          // Your code here.
+ *         buildInfo.info("branch", "HEAD", "version", "1.2.3", "revision", "e0704b");
+ *     }
+ *   }
+ * }
+ * 
+ */ +public class Info extends SimpleCollector implements Counter.Describable { + + Info(Builder b) { + super(b); + } + + public static class Builder extends SimpleCollector.Builder { + @Override + public Info create() { + if (!unit.isEmpty()) { + throw new IllegalStateException("Info metrics cannot have a unit."); + } + return new Info(this); + } + } + + /** + * Return a Builder to allow configuration of a new Info. Ensures required fields are provided. + * + * @param name The name of the metric + * @param help The help string of the metric + */ + public static Builder build(String name, String help) { + return new Builder().name(name).help(help); + } + + /** + * Return a Builder to allow configuration of a new Info. + */ + public static Builder build() { + return new Builder(); + } + + @Override + protected Child newChild() { + return new Child(labelNames); + } + + + /** + * The value of a single Info. + *

+ * Warning: References to a Child become invalid after using + * {@link SimpleCollector#remove} or {@link SimpleCollector#clear}. + */ + public static class Child { + + private Map value = Collections.emptyMap(); + private List labelNames; + + private Child(List labelNames) { + this.labelNames = labelNames; + } + + /** + * Set the info. + */ + public void info(Map v) { + for (String key : v.keySet()) { + checkMetricLabelName(key); + } + for (String label : labelNames) { + if (v.containsKey(label)) { + throw new IllegalArgumentException("Info and its value cannot have the same label name."); + } + } + this.value = v; + } + /** + * Set the info. + * + * @param v labels as pairs of key values + */ + public void info(String... v) { + if (v.length % 2 != 0) { + throw new IllegalArgumentException("An even number of arguments must be passed"); + } + Map m = new TreeMap(); + for (int i = 0; i < v.length; i+=2) { + m.put(v[i], v[i+1]); + } + info(m); + } + + /** + * Get the info. + */ + public Map get() { + return value; + } + } + + // Convenience methods. + /** + * Set the info on the info with no labels. + */ + public void info(String... v) { + noLabelsChild.info(v); + } + + /** + * Set the info on the info with no labels. + * + * @param v labels as pairs of key values + */ + public void info(Map v) { + noLabelsChild.info(v); + } + + /** + * Get the the info. + */ + public Map get() { + return noLabelsChild.get(); + } + + @Override + public List collect() { + List samples = new ArrayList(); + for(Map.Entry, Child> c: children.entrySet()) { + Map v = c.getValue().get(); + List names = new ArrayList(labelNames); + List values = new ArrayList(c.getKey()); + for(Map.Entry l: v.entrySet()) { + names.add(l.getKey()); + values.add(l.getValue()); + } + samples.add(new MetricFamilySamples.Sample(fullname + "_info", names, values, 1.0)); + } + + return familySamplesList(Type.INFO, samples); + } + + @Override + public List describe() { + return Collections.singletonList( + new MetricFamilySamples(fullname, Type.INFO, help, Collections.emptyList())); + } + +} diff --git a/simpleclient/src/test/java/io/prometheus/client/EnumerationTest.java b/simpleclient/src/test/java/io/prometheus/client/EnumerationTest.java index 158036360..4c8fd893a 100644 --- a/simpleclient/src/test/java/io/prometheus/client/EnumerationTest.java +++ b/simpleclient/src/test/java/io/prometheus/client/EnumerationTest.java @@ -65,7 +65,7 @@ public void testLabels() { assertEquals(1.0, getLabeledState("b", "bar"), .001); } - public enum myEnum { + enum myEnum { FOO, BAR, } diff --git a/simpleclient/src/test/java/io/prometheus/client/InfoTest.java b/simpleclient/src/test/java/io/prometheus/client/InfoTest.java new file mode 100644 index 000000000..02b1dbbe8 --- /dev/null +++ b/simpleclient/src/test/java/io/prometheus/client/InfoTest.java @@ -0,0 +1,96 @@ +package io.prometheus.client; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + + +public class InfoTest { + + CollectorRegistry registry; + Info noLabels, labels; + + @Before + public void setUp() { + registry = new CollectorRegistry(); + noLabels = Info.build().name("nolabels").help("help").register(registry); + labels = Info.build().name("labels").help("help").labelNames("l").register(registry); + } + + private Double getInfo(String metric, String... labels) { + String[] names = new String[labels.length / 2]; + String[] values = new String[labels.length / 2]; + for (int i = 0; i < labels.length; i+=2) { + names[i/2] = labels[i]; + values[i/2] = labels[i+1]; + } + return registry.getSampleValue(metric + "_info", names, values); + } + + @Test + public void testInfo() { + assertEquals(null, getInfo("nolabels", "foo", "bar")); + noLabels.info("foo", "bar"); + assertEquals(1.0, getInfo("nolabels", "foo", "bar"), .001); + noLabels.info("foo", "bar", "baz", "meh"); + assertEquals(null, getInfo("nolabels", "foo", "bar")); + assertEquals(1.0, getInfo("nolabels", "baz", "meh", "foo", "bar"), .001); + } + + @Test + public void testDefaultValue() { + assertEquals(1.0, getInfo("nolabels"), .001); + } + + @Test + public void testLabels() { + assertEquals(null, getInfo("labels", "l", "a", "foo", "bar")); + assertEquals(null, getInfo("labels", "l", "b", "baz", "meh")); + labels.labels("a").info("foo", "bar"); + assertEquals(1.0, getInfo("labels", "l", "a", "foo", "bar"), .001); + assertEquals(null, getInfo("labels", "l", "b", "baz", "meh")); + labels.labels("b").info("baz", "meh"); + assertEquals(1.0, getInfo("labels", "l", "a", "foo", "bar"), .001); + assertEquals(1.0, getInfo("labels", "l", "b", "baz", "meh"), .001); + + assertEquals(null, getInfo("nolabels", "l", "a")); + assertEquals(null, getInfo("nolabels", "l", "b")); + } + + @Test(expected=IllegalArgumentException.class) + public void testDuplicateNameLabelThrows() { + Info i = Info.build().name("labels").help("help").labelNames("l").create(); + i.labels("a").info("l", "bar"); + } + + @Test(expected=IllegalArgumentException.class) + public void testOddInfoThrows() { + Info i = Info.build().name("labels").help("help").create(); + i.info("odd"); + } + + @Test(expected=IllegalStateException.class) + public void testUnitThrows() { + Info.build().unit("seconds").name("nolabels").help("help").create(); + } + + @Test + public void testCollect() { + labels.labels("a").info("foo", "bar", "baz", "meh"); + List mfs = labels.collect(); + + ArrayList samples = new ArrayList(); + samples.add(new Collector.MetricFamilySamples.Sample("labels_info", asList("l", "baz", "foo"), asList("a", "meh", "bar"), 1.0)); + Collector.MetricFamilySamples mfsFixture = new Collector.MetricFamilySamples("labels", Collector.Type.INFO, "help", samples); + + assertEquals(1, mfs.size()); + assertEquals(mfsFixture, mfs.get(0)); + } +} 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 a16a57632..7e4aaa4a2 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 @@ -69,6 +69,9 @@ public static void write004(Writer writer, Enumeration Date: Mon, 18 Jan 2021 14:15:23 +0000 Subject: [PATCH 10/11] Make jvm_info use an Info. UNTYPED -> UNKNOWN. Signed-off-by: Brian Brazil --- .../java/io/prometheus/client/Collector.java | 2 +- .../io/prometheus/client/Enumeration.java | 4 ++-- .../main/java/io/prometheus/client/Info.java | 2 +- .../java/io/prometheus/client/InfoTest.java | 2 +- .../client/exporter/common/TextFormat.java | 8 +++---- .../common/TextFormatOpenMetricsTest.java | 4 ++-- .../exporter/common/TextFormatTest.java | 2 +- .../client/hotspot/VersionInfoExports.java | 24 +++++++------------ .../hotspot/VersionInfoExportsTest.java | 6 ++++- .../client/exporter/TestHTTPServer.java | 1 - 10 files changed, 25 insertions(+), 30 deletions(-) diff --git a/simpleclient/src/main/java/io/prometheus/client/Collector.java b/simpleclient/src/main/java/io/prometheus/client/Collector.java index a6c52b302..327b9b01b 100644 --- a/simpleclient/src/main/java/io/prometheus/client/Collector.java +++ b/simpleclient/src/main/java/io/prometheus/client/Collector.java @@ -20,7 +20,7 @@ public abstract class Collector { */ public abstract List collect(); public enum Type { - UNTYPED, // XXX This is Unknown in OpenMetrics. + UNKNOWN, // This is untyped in Prometheus text format. COUNTER, GAUGE, STATE_SET, diff --git a/simpleclient/src/main/java/io/prometheus/client/Enumeration.java b/simpleclient/src/main/java/io/prometheus/client/Enumeration.java index 900f1ec30..93c7265ec 100644 --- a/simpleclient/src/main/java/io/prometheus/client/Enumeration.java +++ b/simpleclient/src/main/java/io/prometheus/client/Enumeration.java @@ -58,7 +58,7 @@ * */ public class Enumeration extends SimpleCollector implements Counter.Describable { - + private final Set states; Enumeration(Builder b) { @@ -166,7 +166,7 @@ public void state(String s) { public void state(Enum e) { state(e.name()); } - + /** * Get the state. */ diff --git a/simpleclient/src/main/java/io/prometheus/client/Info.java b/simpleclient/src/main/java/io/prometheus/client/Info.java index e878ae386..c240d5b0b 100644 --- a/simpleclient/src/main/java/io/prometheus/client/Info.java +++ b/simpleclient/src/main/java/io/prometheus/client/Info.java @@ -36,7 +36,7 @@ * */ public class Info extends SimpleCollector implements Counter.Describable { - + Info(Builder b) { super(b); } diff --git a/simpleclient/src/test/java/io/prometheus/client/InfoTest.java b/simpleclient/src/test/java/io/prometheus/client/InfoTest.java index 02b1dbbe8..f17673286 100644 --- a/simpleclient/src/test/java/io/prometheus/client/InfoTest.java +++ b/simpleclient/src/test/java/io/prometheus/client/InfoTest.java @@ -59,7 +59,7 @@ public void testLabels() { labels.labels("b").info("baz", "meh"); assertEquals(1.0, getInfo("labels", "l", "a", "foo", "bar"), .001); assertEquals(1.0, getInfo("labels", "l", "b", "baz", "meh"), .001); - + assertEquals(null, getInfo("nolabels", "l", "a")); assertEquals(null, getInfo("nolabels", "l", "b")); } 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 7e4aaa4a2..2b81635ef 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 @@ -16,7 +16,7 @@ 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 Openmetrics text version 1.0.0. */ @@ -29,7 +29,7 @@ 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; @@ -193,13 +193,13 @@ public static void writeOpenMetrics100(Writer writer, Enumeration collect() { ArrayList samples = new ArrayList(); MetricFamilySamples.Sample sample = new MetricFamilySamples.Sample("nolabels", labelNames, labelValues, 1.0, 1518123006L); samples.add(sample); - mfs.add(new MetricFamilySamples("nolabels", Collector.Type.UNTYPED, "help", samples)); + mfs.add(new MetricFamilySamples("nolabels", Collector.Type.UNKNOWN, "help", samples)); return mfs; } } - + new CustomCollector().register(registry); TextFormat.writeOpenMetrics100(writer, registry.metricFamilySamples()); assertEquals("# TYPE nolabels unknown\n" diff --git a/simpleclient_common/src/test/java/io/prometheus/client/exporter/common/TextFormatTest.java b/simpleclient_common/src/test/java/io/prometheus/client/exporter/common/TextFormatTest.java index 2c8877bb4..7ecac1a76 100644 --- a/simpleclient_common/src/test/java/io/prometheus/client/exporter/common/TextFormatTest.java +++ b/simpleclient_common/src/test/java/io/prometheus/client/exporter/common/TextFormatTest.java @@ -121,7 +121,7 @@ public List collect() { ArrayList samples = new ArrayList(); MetricFamilySamples.Sample sample = new MetricFamilySamples.Sample("nolabels", labelNames, labelValues, 1.0, 1518123456L); samples.add(sample); - mfs.add(new MetricFamilySamples("nolabels", Collector.Type.UNTYPED, "help", samples)); + mfs.add(new MetricFamilySamples("nolabels", Collector.Type.UNKNOWN, "help", samples)); return mfs; } } diff --git a/simpleclient_hotspot/src/main/java/io/prometheus/client/hotspot/VersionInfoExports.java b/simpleclient_hotspot/src/main/java/io/prometheus/client/hotspot/VersionInfoExports.java index f50d79f21..8dd711080 100644 --- a/simpleclient_hotspot/src/main/java/io/prometheus/client/hotspot/VersionInfoExports.java +++ b/simpleclient_hotspot/src/main/java/io/prometheus/client/hotspot/VersionInfoExports.java @@ -1,7 +1,7 @@ package io.prometheus.client.hotspot; import io.prometheus.client.Collector; -import io.prometheus.client.GaugeMetricFamily; +import io.prometheus.client.Info; import java.util.ArrayList; import java.util.Arrays; @@ -26,20 +26,12 @@ public class VersionInfoExports extends Collector { public List collect() { - List mfs = new ArrayList(); - - GaugeMetricFamily jvmInfo = new GaugeMetricFamily( - "jvm_info", - "JVM version info", - Arrays.asList("version", "vendor", "runtime")); - jvmInfo.addMetric( - Arrays.asList( - System.getProperty("java.runtime.version", "unknown"), - System.getProperty("java.vm.vendor", "unknown"), - System.getProperty("java.runtime.name", "unknown")), - 1L); - mfs.add(jvmInfo); - - return mfs; + Info i = Info.build().name("jvm").help("VM version info").create(); + i.info( + "version", System.getProperty("java.runtime.version", "unknown"), + "vendor", System.getProperty("java.vm.vendor", "unknown"), + "runtime", System.getProperty("java.runtime.name", "unknown") + ); + return i.collect(); } } diff --git a/simpleclient_hotspot/src/test/java/io/prometheus/client/hotspot/VersionInfoExportsTest.java b/simpleclient_hotspot/src/test/java/io/prometheus/client/hotspot/VersionInfoExportsTest.java index 036105e05..718ceecd5 100644 --- a/simpleclient_hotspot/src/test/java/io/prometheus/client/hotspot/VersionInfoExportsTest.java +++ b/simpleclient_hotspot/src/test/java/io/prometheus/client/hotspot/VersionInfoExportsTest.java @@ -21,7 +21,11 @@ public void testVersionInfo() { assertEquals( 1L, registry.getSampleValue( - "jvm_info", new String[]{"version", "vendor", "runtime"}, new String[]{System.getProperty("java.runtime.version", "unknown"), System.getProperty("java.vm.vendor", "unknown"), System.getProperty("java.runtime.name", "unknown")}), + "jvm_info", new String[]{"runtime", "vendor", "version"}, + new String[]{ + System.getProperty("java.runtime.name", "unknown"), + System.getProperty("java.vm.vendor", "unknown"), + System.getProperty("java.runtime.version", "unknown")}), .0000001); } } diff --git a/simpleclient_httpserver/src/test/java/io/prometheus/client/exporter/TestHTTPServer.java b/simpleclient_httpserver/src/test/java/io/prometheus/client/exporter/TestHTTPServer.java index 78521136b..c9f6beed6 100644 --- a/simpleclient_httpserver/src/test/java/io/prometheus/client/exporter/TestHTTPServer.java +++ b/simpleclient_httpserver/src/test/java/io/prometheus/client/exporter/TestHTTPServer.java @@ -75,7 +75,6 @@ String requestWithAccept(String accept) throws IOException { return s.hasNext() ? s.next() : ""; } - @Test @Test(expected = IllegalArgumentException.class) public void testRefuseUsingUnbound() throws IOException { CollectorRegistry registry = new CollectorRegistry(); From cf018d19b321fccb760cdf7fb89ce47bd50dbd63 Mon Sep 17 00:00:00 2001 From: Brian Brazil Date: Thu, 21 Jan 2021 09:22:24 +0000 Subject: [PATCH 11/11] Address code review comments. Signed-off-by: Brian Brazil --- .../src/main/java/io/prometheus/client/Counter.java | 6 ++++++ .../src/main/java/io/prometheus/client/SimpleCollector.java | 2 +- .../client/cache/caffeine/CacheMetricsCollectorTest.java | 1 - 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/simpleclient/src/main/java/io/prometheus/client/Counter.java b/simpleclient/src/main/java/io/prometheus/client/Counter.java index 900812b16..bcff43eb2 100644 --- a/simpleclient/src/main/java/io/prometheus/client/Counter.java +++ b/simpleclient/src/main/java/io/prometheus/client/Counter.java @@ -64,6 +64,12 @@ * * These can be aggregated and processed together much more easily in the Prometheus * server than individual metrics for each labelset. + * + * If there is a suffix of _total on the metric name, it will be + * removed. When exposing the time series for counter value, a + * _total suffix will be added. This is for compatibility between + * OpenMetrics and the Prometheus text format, as OpenMetrics requires the + * _total suffix. */ public class Counter extends SimpleCollector implements Collector.Describable { diff --git a/simpleclient/src/main/java/io/prometheus/client/SimpleCollector.java b/simpleclient/src/main/java/io/prometheus/client/SimpleCollector.java index 6a0183961..a228c6851 100644 --- a/simpleclient/src/main/java/io/prometheus/client/SimpleCollector.java +++ b/simpleclient/src/main/java/io/prometheus/client/SimpleCollector.java @@ -216,7 +216,7 @@ public B namespace(String namespace) { return (B)this; } /** - * Set the uit of the metric. Required. + * Set the unit of the metric. Required. */ public B unit(String unit) { this.unit = unit; diff --git a/simpleclient_caffeine/src/test/java/io/prometheus/client/cache/caffeine/CacheMetricsCollectorTest.java b/simpleclient_caffeine/src/test/java/io/prometheus/client/cache/caffeine/CacheMetricsCollectorTest.java index 75f198d1b..65429f337 100644 --- a/simpleclient_caffeine/src/test/java/io/prometheus/client/cache/caffeine/CacheMetricsCollectorTest.java +++ b/simpleclient_caffeine/src/test/java/io/prometheus/client/cache/caffeine/CacheMetricsCollectorTest.java @@ -70,7 +70,6 @@ public void loadingCacheExposesMetricsForLoadsAndExceptions() throws Exception { } cache.get("user3"); - assertMetric(registry, "caffeine_cache_hit_total", "loadingusers", 1.0); assertMetric(registry, "caffeine_cache_miss_total", "loadingusers", 3.0);