Skip to content

Commit d720fea

Browse files
alex859brian-brazil
authored andcommitted
Mapping Dropwizard metric to Prometheus labelled metric (#435)
* Add ability to translate specific Dropwizard dot separated metrics to Prometheus metrics with custom name and labels Signed-off-by: Alessandro Ciccimarra <[email protected]>
1 parent df64303 commit d720fea

File tree

13 files changed

+1136
-128
lines changed

13 files changed

+1136
-128
lines changed

README.md

Lines changed: 89 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Prometheus JVM Client
22
It supports Java, Clojure, Scala, JRuby, and anything else that runs on the JVM.
3-
3+
44
[![Build Status](https://travis-ci.org/prometheus/client_java.png?branch=master)](https://travis-ci.org/prometheus/client_java)
55

66
Table of Contents
@@ -85,7 +85,7 @@ import io.prometheus.client.Counter;
8585
class YourClass {
8686
static final Counter requests = Counter.build()
8787
.name("requests_total").help("Total requests.").register();
88-
88+
8989
void processRequest() {
9090
requests.inc();
9191
// Your code here.
@@ -101,7 +101,7 @@ Gauges can go up and down.
101101
class YourClass {
102102
static final Gauge inprogressRequests = Gauge.build()
103103
.name("inprogress_requests").help("Inprogress requests.").register();
104-
104+
105105
void processRequest() {
106106
inprogressRequests.inc();
107107
// Your code here.
@@ -116,9 +116,9 @@ There are utilities for common use cases:
116116
gauge.setToCurrentTime(); // Set to current unixtime.
117117
```
118118

119-
As an advanced use case, a `Gauge` can also take its value from a callback by using the
120-
[setChild()](https://prometheus.io/client_java/io/prometheus/client/SimpleCollector.html#setChild-Child-java.lang.String...-)
121-
method. Keep in mind that the default `inc()`, `dec()` and `set()` methods on Gauge take care of thread safety, so
119+
As an advanced use case, a `Gauge` can also take its value from a callback by using the
120+
[setChild()](https://prometheus.io/client_java/io/prometheus/client/SimpleCollector.html#setChild-Child-java.lang.String...-)
121+
method. Keep in mind that the default `inc()`, `dec()` and `set()` methods on Gauge take care of thread safety, so
122122
when using this approach ensure the value you are reporting accounts for concurrency.
123123

124124

@@ -132,7 +132,7 @@ class YourClass {
132132
.name("requests_size_bytes").help("Request size in bytes.").register();
133133
static final Summary requestLatency = Summary.build()
134134
.name("requests_latency_seconds").help("Request latency in seconds.").register();
135-
135+
136136
void processRequest(Request req) {
137137
Summary.Timer requestTimer = requestLatency.startTimer();
138138
try {
@@ -154,16 +154,16 @@ class YourClass {
154154
.quantile(0.5, 0.05) // Add 50th percentile (= median) with 5% tolerated error
155155
.quantile(0.9, 0.01) // Add 90th percentile with 1% tolerated error
156156
.name("requests_latency_seconds").help("Request latency in seconds.").register();
157-
157+
158158
void processRequest(Request req) {
159159
requestLatency.time(new Runnable() {
160160
public abstract void run() {
161-
// Your code here.
161+
// Your code here.
162162
}
163-
});
164-
165-
166-
// Or the Java 8 lambda equivalent
163+
});
164+
165+
166+
// Or the Java 8 lambda equivalent
167167
requestLatency.time(() -> {
168168
// Your code here.
169169
});
@@ -201,18 +201,18 @@ There are utilities for timing code:
201201
class YourClass {
202202
static final Histogram requestLatency = Histogram.build()
203203
.name("requests_latency_seconds").help("Request latency in seconds.").register();
204-
204+
205205
void processRequest(Request req) {
206206
requestLatency.time(new Runnable() {
207207
public abstract void run() {
208-
// Your code here.
208+
// Your code here.
209209
}
210-
});
211-
212-
213-
// Or the Java 8 lambda equivalent
210+
});
211+
212+
213+
// Or the Java 8 lambda equivalent
214214
requestLatency.time(() -> {
215-
// Your code here.
215+
// Your code here.
216216
});
217217
}
218218
}
@@ -232,7 +232,7 @@ class YourClass {
232232
static final Counter requests = Counter.build()
233233
.name("my_library_requests_total").help("Total requests.")
234234
.labelNames("method").register();
235-
235+
236236
void processGetRequest() {
237237
requests.labels("get").inc();
238238
// Your code here.
@@ -248,10 +248,10 @@ The best way to register a metric is via a `static final` class variable as is c
248248
static final Counter requests = Counter.build()
249249
.name("my_library_requests_total").help("Total requests.").labelNames("path").register();
250250
```
251-
252-
Using the default registry with variables that are `static` is ideal since registering a metric with the same name
253-
is not allowed and the default registry is also itself static. You can think of registering a metric, more like
254-
registering a definition (as in the `TYPE` and `HELP` sections). The metric 'definition' internally holds the samples
251+
252+
Using the default registry with variables that are `static` is ideal since registering a metric with the same name
253+
is not allowed and the default registry is also itself static. You can think of registering a metric, more like
254+
registering a definition (as in the `TYPE` and `HELP` sections). The metric 'definition' internally holds the samples
255255
that are reported and pulled out by Prometheus. Here is an example of registering a metric that has no labels.
256256

257257
```java
@@ -260,19 +260,19 @@ class YourClass {
260260
.name("my_library_transactions_active")
261261
.help("Active transactions.")
262262
.register();
263-
263+
264264
void processThatCalculates(String key) {
265265
activeTransactions.inc();
266266
try {
267-
// Perform work.
267+
// Perform work.
268268
} finally{
269269
activeTransactions.dec();
270270
}
271271
}
272272
}
273273
```
274274

275-
To create timeseries with labels, include `labelNames()` with the builder. The `labels()` method looks up or creates
275+
To create timeseries with labels, include `labelNames()` with the builder. The `labels()` method looks up or creates
276276
the corresponding labelled timeseries. You might also consider storing the labelled timeseries as an instance variable if it is
277277
appropriate. It is thread safe and can be used multiple times, which can help performance.
278278

@@ -282,7 +282,7 @@ class YourClass {
282282
static final Counter calculationsCounter = Counter.build()
283283
.name("my_library_calculations_total").help("Total calls.")
284284
.labelNames("key").register();
285-
285+
286286
void processThatCalculates(String key) {
287287
calculationsCounter.labels(key).inc();
288288
// Run calculations.
@@ -294,7 +294,7 @@ class YourClass {
294294
## Included Collectors
295295

296296
The Java client includes collectors for garbage collection, memory pools, JMX, classloading, and thread counts.
297-
These can be added individually or just use the `DefaultExports` to conveniently register them.
297+
These can be added individually or just use the `DefaultExports` to conveniently register them.
298298

299299
```java
300300
DefaultExports.initialize();
@@ -353,7 +353,7 @@ To register the log4j2 collector at root level:
353353
### Caches
354354

355355
To register the Guava cache collector, be certain to add `recordStats()` when building
356-
the cache and adding it to the registered collector.
356+
the cache and adding it to the registered collector.
357357

358358
```java
359359
CacheMetricsCollector cacheMetrics = new CacheMetricsCollector().register();
@@ -374,8 +374,8 @@ cacheMetrics.addCache("myCacheLabel", cache);
374374

375375
### Hibernate
376376

377-
There is a collector for Hibernate which allows to collect metrics from one or more
378-
`SessionFactory` instances.
377+
There is a collector for Hibernate which allows to collect metrics from one or more
378+
`SessionFactory` instances.
379379

380380
If you want to collect metrics from a single `SessionFactory`, you can register
381381
the collector like this:
@@ -415,7 +415,7 @@ new JettyStatisticsCollector(stats).register();
415415
```
416416

417417
Also, you can collect `QueuedThreadPool` metrics. If there is a single `QueuedThreadPool`
418-
to keep track of, use the following:
418+
to keep track of, use the following:
419419

420420
```java
421421
new QueuedThreadPoolStatisticsCollector(queuedThreadPool, "myapp").register();
@@ -522,7 +522,7 @@ The simplest of these is the HTTPServer:
522522
HTTPServer server = new HTTPServer(1234);
523523
```
524524

525-
To add Prometheus exposition to an existing HTTP server using servlets, see the `MetricsServlet`.
525+
To add Prometheus exposition to an existing HTTP server using servlets, see the `MetricsServlet`.
526526
It also serves as a simple example of how to write a custom endpoint.
527527

528528
To expose the metrics used in your code, you would add the Prometheus servlet to your Jetty server:
@@ -554,7 +554,7 @@ void executeBatchJob() throws Exception {
554554
Gauge.Timer durationTimer = duration.startTimer();
555555
try {
556556
// Your code here.
557-
557+
558558
// This is only added to the registry after success,
559559
// so that a previous success in the Pushgateway isn't overwritten on failure.
560560
Gauge lastSuccess = Gauge.build()
@@ -568,8 +568,8 @@ void executeBatchJob() throws Exception {
568568
}
569569
```
570570

571-
A separate registry is used, as the default registry may contain other metrics
572-
such as those from the Process Collector. See the
571+
A separate registry is used, as the default registry may contain other metrics
572+
such as those from the Process Collector. See the
573573
[Pushgateway documentation](https://github.com/prometheus/pushgateway/blob/master/README.md)
574574
for more information.
575575

@@ -593,7 +593,7 @@ class MyHttpConnectionFactory implements HttpConnectionFactory {
593593
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
594594
// add any connection preparation logic you need
595595
return connection;
596-
}
596+
}
597597
}
598598
```
599599

@@ -659,6 +659,56 @@ registration time instead of `describe`. If this could cause problems, either
659659
implement a proper `describe`, or if that's not practical have `describe`
660660
return an empty list.
661661

662+
### DropwizardExports Collector
663+
664+
DropwizardExports collector is available to proxy metrics from Dropwizard.
665+
666+
```java
667+
// Dropwizard MetricRegistry
668+
MetricRegistry metricRegistry = new MetricRegistry();
669+
new DropwizardExports(metricRegistry).register();
670+
```
671+
672+
By default Dropwizard metrics are translated to Prometheus sample sanitizing their names, i.e. replacing unsupported chars with `_`, for example:
673+
```
674+
Dropwizard metric name:
675+
org.company.controller.save.status.400
676+
Prometheus metric:
677+
org_company_controller_save_status_400
678+
```
679+
680+
It is also possible add custom labels and name to newly created `Sample`s by using a `CustomMappingSampleBuilder` with custom `MapperConfig`s:
681+
682+
```java
683+
// Dropwizard MetricRegistry
684+
MetricRegistry metricRegistry = new MetricRegistry();
685+
MapperConfig config = new MapperConfig();
686+
// The match field in MapperConfig is a simplified glob expression that only allows * wildcard.
687+
config.setMatch("org.company.controller.*.status.*");
688+
// The new Sample's template name.
689+
config.setName("org.company.controller");
690+
Map<String, String> labels = new HashMap<String,String>();
691+
// ... more configs
692+
// Labels to be extracted from the metric. Key=label name. Value=label template
693+
labels.put("name", "${0}");
694+
labels.put("status", "${1}");
695+
config.setLabels(labels);
696+
697+
SampleBuilder sampleBuilder = new CustomMappingSampleBuilder(Arrays.asList(config));
698+
new DropwizardExports(metricRegistry, sampleBuilder).register();
699+
```
700+
701+
When a new metric comes to the collector, `MapperConfig`s are scanned to find the first one that matches the incoming metric name. The name set in the configuration will
702+
be used and labels will be extracted. Using the `CustomMappingSampleBuilder` in the previous example leads to the following result:
703+
```
704+
Dropwizard metric name
705+
org.company.controller.save.status.400
706+
Prometheus metric
707+
org_company_controller{name="save",status="400"}
708+
```
709+
710+
Template with placeholders can be used both as names and label values. Placeholders are in the `${n}` format where n is the zero based index of the Dropwizard metric name wildcard group we want to extract.
711+
662712
## Contact
663713
The [Prometheus Users Mailinglist](https://groups.google.com/forum/?fromgroups#!forum/prometheus-users) is the best place to ask questions.
664714

simpleclient_dropwizard/pom.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,5 +49,17 @@
4949
<version>4.11</version>
5050
<scope>test</scope>
5151
</dependency>
52+
<dependency>
53+
<groupId>org.assertj</groupId>
54+
<artifactId>assertj-core</artifactId>
55+
<version>2.9.1</version>
56+
<scope>test</scope>
57+
</dependency>
58+
<dependency>
59+
<groupId>org.mockito</groupId>
60+
<artifactId>mockito-core</artifactId>
61+
<version>2.10.0</version>
62+
<scope>test</scope>
63+
</dependency>
5264
</dependencies>
5365
</project>

0 commit comments

Comments
 (0)