Skip to content

Commit 37fa7fb

Browse files
committed
Add support for Info metrics
Signed-off-by: Brian Brazil <[email protected]>
1 parent 92d34e3 commit 37fa7fb

File tree

7 files changed

+306
-1
lines changed

7 files changed

+306
-1
lines changed

simpleclient/src/main/java/io/prometheus/client/CollectorRegistry.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,10 @@ private List<String> collectorNames(Collector m) {
129129
names.add(family.name + "_bucket");
130130
names.add(family.name);
131131
break;
132+
case INFO:
133+
names.add(family.name + "_info");
134+
names.add(family.name);
135+
break;
132136
default:
133137
names.add(family.name);
134138
}
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
package io.prometheus.client;
2+
3+
import io.prometheus.client.CKMSQuantiles.Quantile;
4+
5+
import java.io.Closeable;
6+
import java.util.ArrayList;
7+
import java.util.Arrays;
8+
import java.util.Collections;
9+
import java.util.List;
10+
import java.util.Map;
11+
import java.util.TreeMap;
12+
import java.util.concurrent.Callable;
13+
import java.util.concurrent.TimeUnit;
14+
15+
/**
16+
* Info metric, key-value pairs.
17+
*
18+
* Examples of Info include, build information, version information, and potential target metadata,
19+
* The first provided state will be the default.
20+
*
21+
* <p>
22+
* Example enumeration:
23+
* <pre>
24+
* {@code
25+
* class YourClass {
26+
* static final Info buildInfo = Info.build()
27+
* .name("your_build_info").help("Build information.")
28+
* .register();
29+
*
30+
* void func() {
31+
* // Your code here.
32+
* buildInfo.info("branch", "HEAD", "version", "1.2.3", "revision", "e0704b");
33+
* }
34+
* }
35+
* }
36+
* </pre>
37+
*/
38+
public class Info extends SimpleCollector<Info.Child> implements Counter.Describable {
39+
40+
Info(Builder b) {
41+
super(b);
42+
}
43+
44+
public static class Builder extends SimpleCollector.Builder<Builder, Info> {
45+
@Override
46+
public Info create() {
47+
if (!unit.isEmpty()) {
48+
throw new IllegalStateException("Info metrics cannot have a unit.");
49+
}
50+
return new Info(this);
51+
}
52+
}
53+
54+
/**
55+
* Return a Builder to allow configuration of a new Info. Ensures required fields are provided.
56+
*
57+
* @param name The name of the metric
58+
* @param help The help string of the metric
59+
*/
60+
public static Builder build(String name, String help) {
61+
return new Builder().name(name).help(help);
62+
}
63+
64+
/**
65+
* Return a Builder to allow configuration of a new Info.
66+
*/
67+
public static Builder build() {
68+
return new Builder();
69+
}
70+
71+
@Override
72+
protected Child newChild() {
73+
return new Child(labelNames);
74+
}
75+
76+
77+
/**
78+
* The value of a single Info.
79+
* <p>
80+
* <em>Warning:</em> References to a Child become invalid after using
81+
* {@link SimpleCollector#remove} or {@link SimpleCollector#clear}.
82+
*/
83+
public static class Child {
84+
85+
private Map<String, String> value = Collections.emptyMap();
86+
private List<String> labelNames;
87+
88+
private Child(List<String> labelNames) {
89+
this.labelNames = labelNames;
90+
}
91+
92+
/**
93+
* Set the info.
94+
*/
95+
public void info(Map<String, String> v) {
96+
for (String key : v.keySet()) {
97+
checkMetricLabelName(key);
98+
}
99+
for (String label : labelNames) {
100+
if (v.containsKey(label)) {
101+
throw new IllegalArgumentException("Info and its value cannot have the same label name.");
102+
}
103+
}
104+
this.value = v;
105+
}
106+
/**
107+
* Set the info.
108+
*
109+
* @param v labels as pairs of key values
110+
*/
111+
public void info(String... v) {
112+
if (v.length % 2 != 0) {
113+
throw new IllegalArgumentException("An even number of arguments must be passed");
114+
}
115+
Map<String, String> m = new TreeMap<String, String>();
116+
for (int i = 0; i < v.length; i+=2) {
117+
m.put(v[i], v[i+1]);
118+
}
119+
info(m);
120+
}
121+
122+
/**
123+
* Get the info.
124+
*/
125+
public Map<String, String> get() {
126+
return value;
127+
}
128+
}
129+
130+
// Convenience methods.
131+
/**
132+
* Set the info on the info with no labels.
133+
*/
134+
public void info(String... v) {
135+
noLabelsChild.info(v);
136+
}
137+
138+
/**
139+
* Set the info on the info with no labels.
140+
*
141+
* @param v labels as pairs of key values
142+
*/
143+
public void info(Map<String, String> v) {
144+
noLabelsChild.info(v);
145+
}
146+
147+
/**
148+
* Get the the info.
149+
*/
150+
public Map<String, String> get() {
151+
return noLabelsChild.get();
152+
}
153+
154+
@Override
155+
public List<MetricFamilySamples> collect() {
156+
List<MetricFamilySamples.Sample> samples = new ArrayList<MetricFamilySamples.Sample>();
157+
for(Map.Entry<List<String>, Child> c: children.entrySet()) {
158+
Map<String, String> v = c.getValue().get();
159+
List<String> names = new ArrayList<String>(labelNames);
160+
List<String> values = new ArrayList<String>(c.getKey());
161+
for(Map.Entry<String, String> l: v.entrySet()) {
162+
names.add(l.getKey());
163+
values.add(l.getValue());
164+
}
165+
samples.add(new MetricFamilySamples.Sample(fullname + "_info", names, values, 1.0));
166+
}
167+
168+
return familySamplesList(Type.INFO, samples);
169+
}
170+
171+
@Override
172+
public List<MetricFamilySamples> describe() {
173+
return Collections.singletonList(
174+
new MetricFamilySamples(fullname, Type.INFO, help, Collections.<MetricFamilySamples.Sample>emptyList()));
175+
}
176+
177+
}

simpleclient/src/test/java/io/prometheus/client/EnumerationTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public void testLabels() {
6565
assertEquals(1.0, getLabeledState("b", "bar"), .001);
6666
}
6767

68-
public enum myEnum {
68+
enum myEnum {
6969
FOO,
7070
BAR,
7171
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package io.prometheus.client;
2+
3+
import org.junit.After;
4+
import org.junit.Before;
5+
import org.junit.Test;
6+
7+
import java.util.ArrayList;
8+
import java.util.List;
9+
10+
import static java.util.Arrays.asList;
11+
import static org.junit.Assert.assertEquals;
12+
import static org.junit.Assert.assertTrue;
13+
14+
15+
public class InfoTest {
16+
17+
CollectorRegistry registry;
18+
Info noLabels, labels;
19+
20+
@Before
21+
public void setUp() {
22+
registry = new CollectorRegistry();
23+
noLabels = Info.build().name("nolabels").help("help").register(registry);
24+
labels = Info.build().name("labels").help("help").labelNames("l").register(registry);
25+
}
26+
27+
private Double getInfo(String metric, String... labels) {
28+
String[] names = new String[labels.length / 2];
29+
String[] values = new String[labels.length / 2];
30+
for (int i = 0; i < labels.length; i+=2) {
31+
names[i/2] = labels[i];
32+
values[i/2] = labels[i+1];
33+
}
34+
return registry.getSampleValue(metric + "_info", names, values);
35+
}
36+
37+
@Test
38+
public void testInfo() {
39+
assertEquals(null, getInfo("nolabels", "foo", "bar"));
40+
noLabels.info("foo", "bar");
41+
assertEquals(1.0, getInfo("nolabels", "foo", "bar"), .001);
42+
noLabels.info("foo", "bar", "baz", "meh");
43+
assertEquals(null, getInfo("nolabels", "foo", "bar"));
44+
assertEquals(1.0, getInfo("nolabels", "baz", "meh", "foo", "bar"), .001);
45+
}
46+
47+
@Test
48+
public void testDefaultValue() {
49+
assertEquals(1.0, getInfo("nolabels"), .001);
50+
}
51+
52+
@Test
53+
public void testLabels() {
54+
assertEquals(null, getInfo("labels", "l", "a", "foo", "bar"));
55+
assertEquals(null, getInfo("labels", "l", "b", "baz", "meh"));
56+
labels.labels("a").info("foo", "bar");
57+
assertEquals(1.0, getInfo("labels", "l", "a", "foo", "bar"), .001);
58+
assertEquals(null, getInfo("labels", "l", "b", "baz", "meh"));
59+
labels.labels("b").info("baz", "meh");
60+
assertEquals(1.0, getInfo("labels", "l", "a", "foo", "bar"), .001);
61+
assertEquals(1.0, getInfo("labels", "l", "b", "baz", "meh"), .001);
62+
63+
assertEquals(null, getInfo("nolabels", "l", "a"));
64+
assertEquals(null, getInfo("nolabels", "l", "b"));
65+
}
66+
67+
@Test(expected=IllegalArgumentException.class)
68+
public void testDuplicateNameLabelThrows() {
69+
Info i = Info.build().name("labels").help("help").labelNames("l").create();
70+
i.labels("a").info("l", "bar");
71+
}
72+
73+
@Test(expected=IllegalArgumentException.class)
74+
public void testOddInfoThrows() {
75+
Info i = Info.build().name("labels").help("help").create();
76+
i.info("odd");
77+
}
78+
79+
@Test(expected=IllegalStateException.class)
80+
public void testUnitThrows() {
81+
Info.build().unit("seconds").name("nolabels").help("help").create();
82+
}
83+
84+
@Test
85+
public void testCollect() {
86+
labels.labels("a").info("foo", "bar", "baz", "meh");
87+
List<Collector.MetricFamilySamples> mfs = labels.collect();
88+
89+
ArrayList<Collector.MetricFamilySamples.Sample> samples = new ArrayList<Collector.MetricFamilySamples.Sample>();
90+
samples.add(new Collector.MetricFamilySamples.Sample("labels_info", asList("l", "baz", "foo"), asList("a", "meh", "bar"), 1.0));
91+
Collector.MetricFamilySamples mfsFixture = new Collector.MetricFamilySamples("labels", Collector.Type.INFO, "help", samples);
92+
93+
assertEquals(1, mfs.size());
94+
assertEquals(mfsFixture, mfs.get(0));
95+
}
96+
}

simpleclient_common/src/main/java/io/prometheus/client/exporter/common/TextFormat.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ public static void write004(Writer writer, Enumeration<Collector.MetricFamilySam
6969
if (metricFamilySamples.type == Collector.Type.COUNTER) {
7070
writer.write("_total");
7171
}
72+
if (metricFamilySamples.type == Collector.Type.INFO) {
73+
writer.write("_info");
74+
}
7275
writer.write(' ');
7376
writeEscapedHelp(writer, metricFamilySamples.help);
7477
writer.write('\n');
@@ -78,6 +81,9 @@ public static void write004(Writer writer, Enumeration<Collector.MetricFamilySam
7881
if (metricFamilySamples.type == Collector.Type.COUNTER) {
7982
writer.write("_total");
8083
}
84+
if (metricFamilySamples.type == Collector.Type.INFO) {
85+
writer.write("_info");
86+
}
8187
writer.write(' ');
8288
writer.write(typeString(metricFamilySamples.type));
8389
writer.write('\n');

simpleclient_common/src/test/java/io/prometheus/client/exporter/common/TextFormatOpenMetricsTest.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import io.prometheus.client.CollectorRegistry;
1515
import io.prometheus.client.Counter;
1616
import io.prometheus.client.Gauge;
17+
import io.prometheus.client.Info;
1718
import io.prometheus.client.Summary;
1819

1920

@@ -61,6 +62,17 @@ public void testCounterOutput() throws IOException {
6162
+ "# EOF\n", writer.toString().replaceAll("_created [0-9E.]+", "_created 1234.0"));
6263
}
6364

65+
@Test
66+
public void testInfoOutput() throws IOException {
67+
Info noLabels = Info.build().name("nolabels").help("help").register(registry);
68+
noLabels.info("foo", "bar");
69+
TextFormat.writeOpenMetrics100(writer, registry.metricFamilySamples());
70+
assertEquals("# TYPE nolabels info\n"
71+
+ "# HELP nolabels help\n"
72+
+ "nolabels_info{foo=\"bar\"} 1.0\n"
73+
+ "# EOF\n", writer.toString());
74+
}
75+
6476
@Test
6577
public void testCounterSamplesMissingTotal() throws IOException {
6678

simpleclient_common/src/test/java/io/prometheus/client/exporter/common/TextFormatTest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import io.prometheus.client.CollectorRegistry;
1616
import io.prometheus.client.Counter;
1717
import io.prometheus.client.Gauge;
18+
import io.prometheus.client.Info;
1819
import io.prometheus.client.Summary;
1920

2021

@@ -76,6 +77,15 @@ public void testCounterWithTotalOutput() throws IOException {
7677
writer.toString().replaceAll("_created [0-9E.]+", "_created 1234.0"));
7778
}
7879

80+
@Test
81+
public void testInfoOutput() throws IOException {
82+
Info noLabels = Info.build().name("nolabels").help("help").register(registry);
83+
noLabels.info("foo", "bar");
84+
TextFormat.write004(writer, registry.metricFamilySamples());
85+
assertEquals("# HELP nolabels_info help\n"
86+
+ "# TYPE nolabels_info gauge\n"
87+
+ "nolabels_info{foo=\"bar\",} 1.0\n", writer.toString());
88+
}
7989

8090
@Test
8191
public void testCounterSamplesMissingTotal() throws IOException {

0 commit comments

Comments
 (0)