Skip to content

Commit dff4405

Browse files
committed
Support metrics with Datadog
[#151844595] Fixes #102
1 parent 758c029 commit dff4405

File tree

8 files changed

+308
-12
lines changed

8 files changed

+308
-12
lines changed

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,7 @@ run: compile ## Run PerfTest, pass exec arguments via ARGS, e.g. ARGS="-x 1 -y 1
3333
signed-binary: clean ## Build a GPG signed binary
3434
@mvnw package -P assemblies
3535

36+
doc: clean ## Generate PerfTest documentation
37+
@mvnw asciidoctor:process-asciidoc
38+
3639
.PHONY: binary help clean compile jar run signed-binary

pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,11 @@
125125
<artifactId>micrometer-registry-prometheus</artifactId>
126126
<version>${micrometer.version}</version>
127127
</dependency>
128+
<dependency>
129+
<groupId>io.micrometer</groupId>
130+
<artifactId>micrometer-registry-datadog</artifactId>
131+
<version>${micrometer.version}</version>
132+
</dependency>
128133
<dependency>
129134
<groupId>ch.qos.logback</groupId>
130135
<artifactId>logback-classic</artifactId>

src/docs/asciidoc/monitoring.adoc

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,27 @@ for more information about tags and dimensions.
5555
=== Supported Monitoring Systems
5656

5757
PerfTest builds on top https://micrometer.io[Micrometer] to report gathered metrics to various monitoring systems.
58-
Nevertheless, not all systems supported by Micrometer are actually supported by PerfTest. The only metrics
59-
system currently supported is https://prometheus.io/[Prometheus]. Don't hesitate to
58+
Nevertheless, not all systems supported by Micrometer are actually supported by PerfTest.
59+
PerfTest currently supports https://www.datadoghq.com/[Datadog] and https://prometheus.io/[Prometheus].
60+
Don't hesitate to
6061
https://github.com/rabbitmq/rabbitmq-perf-test/issues[request support for other monitoring systems].
6162

63+
==== Datadog
64+
65+
The API key is the only required option to send metrics to Datadog:
66+
67+
```
68+
./runjava com.rabbitmq.perf.PerfTest --metrics-datadog-api-key YOUR_API_KEY
69+
```
70+
71+
Another useful option is the step size or reporting frequency. The default value is
72+
10 seconds.
73+
74+
```
75+
./runjava com.rabbitmq.perf.PerfTest --metrics-datadog-api-key YOUR_API_KEY \
76+
--metrics-datadog-step-size 20
77+
```
78+
6279
==== Prometheus
6380

6481
Use the `-mpr` or `--metrics-prometheus` flag to enable metrics reporting to Prometheus:
@@ -76,5 +93,3 @@ can be changed:
7693
--metrics-prometheus-port 8090 --metrics-prometheus-endpoint perf-test-metrics
7794
```
7895

79-
80-

src/main/java/com/rabbitmq/perf/CompositeMetrics.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public class CompositeMetrics implements Metrics {
3838
public CompositeMetrics() {
3939
metrics.add(new BaseMetrics());
4040
metrics.add(new PrometheusMetrics());
41+
metrics.add(new DatadogMetrics());
4142
}
4243

4344
@Override
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Copyright (c) 2018 Pivotal Software, Inc. All rights reserved.
2+
//
3+
// This software, the RabbitMQ Java client library, is triple-licensed under the
4+
// Mozilla Public License 1.1 ("MPL"), the GNU General Public License version 2
5+
// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see
6+
// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL,
7+
// please see LICENSE-APACHE2.
8+
//
9+
// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND,
10+
// either express or implied. See the LICENSE file for specific language governing
11+
// rights and limitations of this software.
12+
//
13+
// If you have any questions regarding licensing, please contact us at
14+
15+
16+
package com.rabbitmq.perf;
17+
18+
import com.rabbitmq.client.ConnectionFactory;
19+
import io.micrometer.core.instrument.Clock;
20+
import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
21+
import io.micrometer.datadog.DatadogConfig;
22+
import io.micrometer.datadog.DatadogMeterRegistry;
23+
import org.apache.commons.cli.Option;
24+
import org.apache.commons.cli.Options;
25+
26+
import java.time.Duration;
27+
import java.util.HashMap;
28+
import java.util.Map;
29+
30+
import static com.rabbitmq.perf.PerfTest.hasOption;
31+
import static com.rabbitmq.perf.PerfTest.strArg;
32+
import static java.lang.Boolean.valueOf;
33+
34+
/**
35+
*
36+
*/
37+
public class DatadogMetrics implements Metrics {
38+
39+
private volatile DatadogMeterRegistry registry;
40+
41+
public Options options() {
42+
Options options = new Options();
43+
options.addOption(new Option("mda", "metrics-datadog", false, "enable Datadog metrics"));
44+
options.addOption(new Option("mdk", "metrics-datadog-api-key", true, "Datadog API key"));
45+
options.addOption(new Option("mds", "metrics-datadog-step-size", true, "step size (reporting frequency) to use "
46+
+ "in seconds, default is 10 seconds"));
47+
options.addOption(new Option("mdak", "metrics-datadog-application-key", true, "Datadog application key"));
48+
options.addOption(new Option("mdh", "metrics-datadog-host-tag", true, "tag that will be mapped to \"host\" when shipping metrics to datadog"));
49+
options.addOption(new Option("mdd", "metrics-datadog-descriptions", false, "if meter descriptions should be sent to Datadog"));
50+
options.addOption(new Option("mdu", "metrics-datadog-uri", true, "URI to ship metrics, useful when using "
51+
+ "a proxy, default is https://app.datadoghq.com"));
52+
return options;
53+
}
54+
55+
public void configure(CommandLineProxy cmd, CompositeMeterRegistry meterRegistry, ConnectionFactory factory) throws Exception {
56+
if (isEnabled(cmd)) {
57+
Map<String, String> dataCfg = new HashMap<>();
58+
dataCfg.put("datadog.apiKey", strArg(cmd, "mdk", null));
59+
dataCfg.put("datadog.step", strArg(cmd, "mds", "10"));
60+
dataCfg.put("datadog.applicationKey", strArg(cmd, "mdak", null));
61+
dataCfg.put("datadog.hostTag", strArg(cmd, "mdh", null));
62+
dataCfg.put("datadog.descriptions", valueOf(hasOption(cmd, "mdd")).toString());
63+
dataCfg.put("datadog.uri", strArg(cmd, "mdu", null));
64+
65+
DatadogConfig config = new DatadogConfig() {
66+
@Override
67+
public Duration step() {
68+
return Duration.ofSeconds(Integer.valueOf(dataCfg.get("datadog.step")));
69+
}
70+
71+
@Override
72+
public String get(String k) {
73+
return dataCfg.get(k);
74+
}
75+
};
76+
registry = new DatadogMeterRegistry(
77+
config,
78+
Clock.SYSTEM,
79+
new NamedThreadFactory("perf-test-metrics-datadog-")
80+
);
81+
meterRegistry.add(registry);
82+
}
83+
}
84+
85+
public void close() {
86+
registry.close();
87+
}
88+
89+
@Override
90+
public String toString() {
91+
return "Datadog Metrics";
92+
}
93+
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
// Copyright (c) 2018 Pivotal Software, Inc. All rights reserved.
2+
//
3+
// This software, the RabbitMQ Java client library, is triple-licensed under the
4+
// Mozilla Public License 1.1 ("MPL"), the GNU General Public License version 2
5+
// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see
6+
// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL,
7+
// please see LICENSE-APACHE2.
8+
//
9+
// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND,
10+
// either express or implied. See the LICENSE file for specific language governing
11+
// rights and limitations of this software.
12+
//
13+
// If you have any questions regarding licensing, please contact us at
14+
15+
16+
package com.rabbitmq.perf;
17+
18+
import io.micrometer.core.instrument.Gauge;
19+
import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
20+
import org.apache.commons.cli.CommandLine;
21+
import org.apache.commons.cli.CommandLineParser;
22+
import org.apache.commons.cli.GnuParser;
23+
import org.apache.commons.cli.Options;
24+
import org.eclipse.jetty.server.Connector;
25+
import org.eclipse.jetty.server.Request;
26+
import org.eclipse.jetty.server.Server;
27+
import org.eclipse.jetty.server.ServerConnector;
28+
import org.eclipse.jetty.server.handler.AbstractHandler;
29+
import org.eclipse.jetty.server.handler.ContextHandler;
30+
import org.eclipse.jetty.util.thread.QueuedThreadPool;
31+
import org.junit.jupiter.api.AfterEach;
32+
import org.junit.jupiter.api.BeforeEach;
33+
import org.junit.jupiter.api.Test;
34+
35+
import javax.servlet.http.HttpServletRequest;
36+
import javax.servlet.http.HttpServletResponse;
37+
import java.io.IOException;
38+
import java.util.concurrent.CountDownLatch;
39+
import java.util.concurrent.TimeUnit;
40+
import java.util.concurrent.atomic.AtomicInteger;
41+
import java.util.concurrent.atomic.AtomicReference;
42+
import java.util.stream.Collectors;
43+
44+
import static org.junit.jupiter.api.Assertions.assertEquals;
45+
import static org.junit.jupiter.api.Assertions.assertTrue;
46+
47+
/**
48+
*
49+
*/
50+
public class DatadogMetricsTest {
51+
52+
static final int NB_REQUESTS = 5;
53+
final AtomicReference<String> apiKey = new AtomicReference<>();
54+
final AtomicReference<String> appKey = new AtomicReference<>();
55+
final AtomicReference<String> content = new AtomicReference<>();
56+
final AtomicReference<String> description = new AtomicReference<>();
57+
final CountDownLatch latch = new CountDownLatch(NB_REQUESTS);
58+
int port;
59+
Server server;
60+
DatadogMetrics metrics;
61+
62+
@BeforeEach
63+
public void init() throws Exception {
64+
port = TestUtils.randomNetworkPort();
65+
server = startMockDatadogService();
66+
}
67+
68+
@AfterEach
69+
public void tearDown() throws Exception {
70+
if (metrics != null) {
71+
metrics.close();
72+
}
73+
if (server != null) {
74+
server.stop();
75+
}
76+
}
77+
78+
@Test
79+
public void datadog() throws Exception {
80+
DatadogMetrics metrics = new DatadogMetrics();
81+
Options options = metrics.options();
82+
83+
CommandLineParser parser = new GnuParser();
84+
CommandLine rawCmd = parser.parse(
85+
options,
86+
("--metrics-datadog-uri http://localhost:" + port + "/datadog "
87+
+ "--metrics-datadog-api-key APIKEY --metrics-datadog-application-key APPKEY "
88+
+ "--metrics-datadog-step-size 1 --metrics-datadog-host-tag host --metrics-datadog-descriptions")
89+
.split(" ")
90+
);
91+
CommandLineProxy cmd = new CommandLineProxy(options, rawCmd, name -> null);
92+
CompositeMeterRegistry registry = new CompositeMeterRegistry();
93+
registry.config().commonTags("host", "test");
94+
AtomicInteger gauge = new AtomicInteger(42);
95+
Gauge.builder("dummy", gauge, g -> g.doubleValue()).description("this is a dummy meter").register(registry);
96+
metrics.configure(cmd, registry, null);
97+
98+
assertTrue(latch.await(10, TimeUnit.SECONDS), NB_REQUESTS + " metrics requests should have been sent by now");
99+
100+
assertEquals("APIKEY", apiKey.get());
101+
assertEquals("APPKEY", appKey.get());
102+
assertTrue(description.get().contains("\"description\":\"this is a dummy meter\""));
103+
assertTrue(content.get().contains("\"metric\":\"dummy\""));
104+
assertTrue(content.get().contains("42.0"));
105+
assertTrue(content.get().contains("\"host\":\"test\""));
106+
107+
metrics.close();
108+
}
109+
110+
private Server startMockDatadogService() throws Exception {
111+
QueuedThreadPool threadPool = new QueuedThreadPool();
112+
// difference between those 2 should be high enough to avoid a warning
113+
threadPool.setMinThreads(2);
114+
threadPool.setMaxThreads(12);
115+
server = new Server(threadPool);
116+
ServerConnector connector = new ServerConnector(server);
117+
connector.setPort(port);
118+
server.setConnectors(new Connector[] { connector });
119+
120+
ContextHandler context = new ContextHandler();
121+
context.setContextPath("/datadog");
122+
context.setHandler(new AbstractHandler() {
123+
124+
@Override
125+
public void handle(String s, Request request, HttpServletRequest httpServletRequest, HttpServletResponse response)
126+
throws IOException {
127+
if (request.getParameter("api_key") != null) {
128+
apiKey.set(request.getParameter("api_key"));
129+
}
130+
if (request.getParameter("application_key") != null) {
131+
appKey.set(request.getParameter("application_key"));
132+
}
133+
134+
String body = request.getReader().lines().collect(Collectors.joining("\n"));
135+
DatadogMetricsTest.this.content.set(body);
136+
137+
if (body.contains("\"description\"")) {
138+
description.set(body);
139+
}
140+
141+
request.setHandled(true);
142+
latch.countDown();
143+
}
144+
});
145+
146+
server.setHandler(context);
147+
148+
server.setStopTimeout(1000);
149+
server.start();
150+
return server;
151+
}
152+
}

src/test/java/com/rabbitmq/perf/PrometheusMetricsTest.java

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import java.net.URL;
3131
import java.util.concurrent.atomic.AtomicInteger;
3232

33+
import static com.rabbitmq.perf.TestUtils.randomNetworkPort;
3334
import static org.junit.jupiter.api.Assertions.assertEquals;
3435
import static org.junit.jupiter.api.Assertions.assertTrue;
3536

@@ -38,14 +39,6 @@
3839
*/
3940
public class PrometheusMetricsTest {
4041

41-
static int randomNetworkPort() throws IOException {
42-
ServerSocket socket = new ServerSocket();
43-
socket.bind(null);
44-
int port = socket.getLocalPort();
45-
socket.close();
46-
return port;
47-
}
48-
4942
@Test
5043
public void prometheusHttpEndpointExposed() throws Exception {
5144
PrometheusMetrics metrics = new PrometheusMetrics();
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (c) 2018 Pivotal Software, Inc. All rights reserved.
2+
//
3+
// This software, the RabbitMQ Java client library, is triple-licensed under the
4+
// Mozilla Public License 1.1 ("MPL"), the GNU General Public License version 2
5+
// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see
6+
// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL,
7+
// please see LICENSE-APACHE2.
8+
//
9+
// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND,
10+
// either express or implied. See the LICENSE file for specific language governing
11+
// rights and limitations of this software.
12+
//
13+
// If you have any questions regarding licensing, please contact us at
14+
15+
16+
package com.rabbitmq.perf;
17+
18+
import java.io.IOException;
19+
import java.net.ServerSocket;
20+
21+
/**
22+
*
23+
*/
24+
public abstract class TestUtils {
25+
26+
static int randomNetworkPort() throws IOException {
27+
ServerSocket socket = new ServerSocket();
28+
socket.bind(null);
29+
int port = socket.getLocalPort();
30+
socket.close();
31+
return port;
32+
}
33+
34+
}

0 commit comments

Comments
 (0)