Skip to content
This repository was archived by the owner on Jul 18, 2025. It is now read-only.

Commit dcaed45

Browse files
Constant tags; compatibility with non-datadog recordExecutionTime
- Add support for initialization-time constant tag list (similar use case to constant prefix). - Convert millisecond times to floating-point histogram type for compatibility with upstream semantics.
1 parent c5a0ef4 commit dcaed45

File tree

3 files changed

+92
-17
lines changed

3 files changed

+92
-17
lines changed

README.md

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,26 @@ import com.timgroup.statsd.StatsDClient;
2626
import com.timgroup.statsd.NonBlockingStatsDClient;
2727

2828
public class Foo {
29-
private static final StatsDClient statsd = new NonBlockingStatsDClient("my.prefix", "statsd-host", 8125);
29+
30+
private static final StatsDClient statsd = new NonBlockingStatsDClient(
31+
"my.prefix", /* prefix to any stats; may be null or empty string */
32+
"statsd-host", /* common case: localhost */
33+
8125, /* port */
34+
new String[] {"tag:value"} /* DataDog extension: Constant tags, always applied */
35+
);
3036

3137
public static final void main(String[] args) {
3238
statsd.incrementCounter("foo");
3339
statsd.recordGaugeValue("bar", 100);
3440
statsd.recordGaugeValue("baz", 0.01); /* DataDog extension: support for floating-point gauges */
41+
statsd.recordHistogram("qux", 15) /* DataDog extension: histograms */
42+
statsd.recordHistogram("qux", 15.5) /* ...also floating-point */
43+
44+
/* Compatibility note: Unlike upstream statsd, DataDog expects execution times to be a
45+
* floating-point value in seconds, not a millisecond value. This library
46+
* does the conversion from ms to fractional seconds.
47+
*/
3548
statsd.recordExecutionTime("bag", 25, "cluster:foo"); /* DataDog extension: cluster tag */
36-
statsd.recordHistogram("qux", 15) /* DataDog extension: histograms */
37-
statsd.recordHistogram("qux", 15.5) /* ...also floating-point */
3849
}
3950
}
4051
```

src/main/java/com/timgroup/statsd/NonBlockingStatsDClient.java

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ public final class NonBlockingStatsDClient implements StatsDClient {
5252
private final String prefix;
5353
private final DatagramSocket clientSocket;
5454
private final StatsDClientErrorHandler handler;
55+
private final String[] constantTags;
5556

5657
private final ExecutorService executor = Executors.newSingleThreadExecutor(new ThreadFactory() {
5758
final ThreadFactory delegate = Executors.defaultThreadFactory();
@@ -71,7 +72,7 @@ public final class NonBlockingStatsDClient implements StatsDClient {
7172
* be established. Once a client has been instantiated in this way, all
7273
* exceptions thrown during subsequent usage are consumed, guaranteeing
7374
* that failures in metrics will not affect normal code execution.
74-
*
75+
*
7576
* @param prefix
7677
* the prefix to apply to keys sent via this client
7778
* @param hostname
@@ -82,9 +83,34 @@ public final class NonBlockingStatsDClient implements StatsDClient {
8283
* if the client could not be started
8384
*/
8485
public NonBlockingStatsDClient(String prefix, String hostname, int port) throws StatsDClientException {
85-
this(prefix, hostname, port, NO_OP_HANDLER);
86+
this(prefix, hostname, port, null, NO_OP_HANDLER);
87+
}
88+
89+
/**
90+
* Create a new StatsD client communicating with a StatsD instance on the
91+
* specified host and port. All messages send via this client will have
92+
* their keys prefixed with the specified string. The new client will
93+
* attempt to open a connection to the StatsD server immediately upon
94+
* instantiation, and may throw an exception if that a connection cannot
95+
* be established. Once a client has been instantiated in this way, all
96+
* exceptions thrown during subsequent usage are consumed, guaranteeing
97+
* that failures in metrics will not affect normal code execution.
98+
*
99+
* @param prefix
100+
* the prefix to apply to keys sent via this client
101+
* @param hostname
102+
* the host name of the targeted StatsD server
103+
* @param port
104+
* the port of the targeted StatsD server
105+
* @param constantTags
106+
* tags to be added to all content sent
107+
* @throws StatsDClientException
108+
* if the client could not be started
109+
*/
110+
public NonBlockingStatsDClient(String prefix, String hostname, int port, String[] constantTags) throws StatsDClientException {
111+
this(prefix, hostname, port, constantTags, NO_OP_HANDLER);
86112
}
87-
113+
88114
/**
89115
* Create a new StatsD client communicating with a StatsD instance on the
90116
* specified host and port. All messages send via this client will have
@@ -102,19 +128,25 @@ public NonBlockingStatsDClient(String prefix, String hostname, int port) throws
102128
* the host name of the targeted StatsD server
103129
* @param port
104130
* the port of the targeted StatsD server
131+
* @param constantTags
132+
* tags to be added to all content sent
105133
* @param errorHandler
106134
* handler to use when an exception occurs during usage
107135
* @throws StatsDClientException
108136
* if the client could not be started
109137
*/
110-
public NonBlockingStatsDClient(String prefix, String hostname, int port, StatsDClientErrorHandler errorHandler) throws StatsDClientException {
138+
public NonBlockingStatsDClient(String prefix, String hostname, int port, String[] constantTags, StatsDClientErrorHandler errorHandler) throws StatsDClientException {
111139
if(prefix != null && prefix.length() > 0) {
112140
this.prefix = String.format("%s.", prefix);
113141
} else {
114142
this.prefix = "";
115143
}
116144
this.handler = errorHandler;
117-
145+
if(constantTags != null && constantTags.length == 0) {
146+
constantTags = null;
147+
}
148+
this.constantTags = constantTags;
149+
118150
try {
119151
this.clientSocket = new DatagramSocket();
120152
this.clientSocket.connect(new InetSocketAddress(hostname, port));
@@ -147,14 +179,26 @@ public void stop() {
147179
* Generate a suffix conveying the given tag list to the client
148180
*/
149181
String tagString(String[] tags) {
150-
if(tags == null || tags.length == 0) {
182+
boolean have_call_tags = (tags != null && tags.length > 0);
183+
boolean have_constant_tags = (constantTags != null && constantTags.length > 0);
184+
if(!have_call_tags && !have_constant_tags) {
151185
return "";
152186
}
153187
StringBuilder sb = new StringBuilder("|#");
154-
for(int n=tags.length - 1; n>=0; n--) {
155-
sb.append(tags[n]);
156-
if(n > 0) {
157-
sb.append(",");
188+
if(have_constant_tags) {
189+
for(int n=constantTags.length - 1; n>=0; n--) {
190+
sb.append(constantTags[n]);
191+
if(n > 0 || have_call_tags) {
192+
sb.append(",");
193+
}
194+
}
195+
}
196+
if (have_call_tags) {
197+
for(int n=tags.length - 1; n>=0; n--) {
198+
sb.append(tags[n]);
199+
if(n > 0) {
200+
sb.append(",");
201+
}
158202
}
159203
}
160204
return sb.toString();
@@ -290,7 +334,7 @@ public void gauge(String aspect, int value, String... tags) {
290334
*/
291335
@Override
292336
public void recordExecutionTime(String aspect, long timeInMs, String... tags) {
293-
send(String.format("%s%s:%d|ms%s", prefix, aspect, timeInMs, tagString(tags)));
337+
recordHistogramValue(aspect, (timeInMs * 0.001), tags);
294338
}
295339

296340
/**

src/test/java/com/timgroup/statsd/NonBlockingStatsDClientTest.java

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public void stop() throws Exception {
3636
sends_counter_value_to_statsd_with_null_tags() throws Exception {
3737
final DummyStatsDServer server = new DummyStatsDServer(STATSD_SERVER_PORT);
3838

39-
client.count("mycount", 24, null);
39+
client.count("mycount", 24, (java.lang.String[]) null);
4040
server.waitForMessage();
4141

4242
assertThat(server.messagesReceived(), contains("my.prefix.mycount:24|c"));
@@ -209,7 +209,7 @@ public void stop() throws Exception {
209209
client.recordExecutionTime("mytime", 123);
210210
server.waitForMessage();
211211

212-
assertThat(server.messagesReceived(), contains("my.prefix.mytime:123|ms"));
212+
assertThat(server.messagesReceived(), contains("my.prefix.mytime:0.123|h"));
213213
}
214214

215215
@Test(timeout=5000L) public void
@@ -219,10 +219,30 @@ public void stop() throws Exception {
219219
client.recordExecutionTime("mytime", 123, "foo:bar", "baz");
220220
server.waitForMessage();
221221

222-
assertThat(server.messagesReceived(), contains("my.prefix.mytime:123|ms|#baz,foo:bar"));
222+
assertThat(server.messagesReceived(), contains("my.prefix.mytime:0.123|h|#baz,foo:bar"));
223223
}
224224

225225

226+
@Test(timeout=5000L) public void
227+
sends_gauge_mixed_tags() throws Exception {
228+
final DummyStatsDServer server = new DummyStatsDServer(STATSD_SERVER_PORT);
229+
final NonBlockingStatsDClient empty_prefix_client = new NonBlockingStatsDClient("my.prefix", "localhost", STATSD_SERVER_PORT, new String[] {"instance:foo", "app:bar"});
230+
empty_prefix_client.gauge("value", 423, "baz");
231+
server.waitForMessage();
232+
233+
assertThat(server.messagesReceived(), contains("my.prefix.value:423|g|#app:bar,instance:foo,baz"));
234+
}
235+
236+
@Test(timeout=5000L) public void
237+
sends_gauge_constant_tags_only() throws Exception {
238+
final DummyStatsDServer server = new DummyStatsDServer(STATSD_SERVER_PORT);
239+
final NonBlockingStatsDClient empty_prefix_client = new NonBlockingStatsDClient("my.prefix", "localhost", STATSD_SERVER_PORT, new String[] {"instance:foo", "app:bar"});
240+
empty_prefix_client.gauge("value", 423);
241+
server.waitForMessage();
242+
243+
assertThat(server.messagesReceived(), contains("my.prefix.value:423|g|#app:bar,instance:foo"));
244+
}
245+
226246
@Test(timeout=5000L) public void
227247
sends_gauge_empty_prefix() throws Exception {
228248
final DummyStatsDServer server = new DummyStatsDServer(STATSD_SERVER_PORT);

0 commit comments

Comments
 (0)