├── .gitignore
├── .travis.yml
├── vendor
├── buildlib
│ ├── junit-dep-4.10.jar
│ ├── hamcrest-core-1.3.0RC2.jar
│ └── hamcrest-library-1.3.0RC2.jar
└── src
│ └── junit-dep-4.10-sources.jar
├── .project
├── src
├── main
│ └── java
│ │ └── com
│ │ └── timgroup
│ │ └── statsd
│ │ ├── StatsDClientErrorHandler.java
│ │ ├── StatsDClientException.java
│ │ ├── NoOpStatsDClient.java
│ │ ├── NonBlockingUdpSender.java
│ │ ├── ConvenienceMethodProvidingStatsDClient.java
│ │ ├── StatsDClient.java
│ │ └── NonBlockingStatsDClient.java
└── test
│ └── java
│ └── com
│ └── timgroup
│ └── statsd
│ └── NonBlockingStatsDClientTest.java
├── .classpath
├── .settings
└── org.eclipse.jdt.core.prefs
├── LICENSE
├── java-statsd-client.pom
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | bin
2 | build
3 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 | jdk:
3 | - oraclejdk7
4 | - openjdk7
5 | - openjdk6
6 |
--------------------------------------------------------------------------------
/vendor/buildlib/junit-dep-4.10.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tim-group/java-statsd-client/HEAD/vendor/buildlib/junit-dep-4.10.jar
--------------------------------------------------------------------------------
/vendor/src/junit-dep-4.10-sources.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tim-group/java-statsd-client/HEAD/vendor/src/junit-dep-4.10-sources.jar
--------------------------------------------------------------------------------
/vendor/buildlib/hamcrest-core-1.3.0RC2.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tim-group/java-statsd-client/HEAD/vendor/buildlib/hamcrest-core-1.3.0RC2.jar
--------------------------------------------------------------------------------
/vendor/buildlib/hamcrest-library-1.3.0RC2.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tim-group/java-statsd-client/HEAD/vendor/buildlib/hamcrest-library-1.3.0RC2.jar
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
Four key methods are provided for the submission of data-points for the application under 8 | * scrutiny: 9 | *
This method is non-blocking and is guaranteed not to throw an exception.
31 | * 32 | * @param aspect 33 | * the name of the counter to adjust 34 | * @param delta 35 | * the amount to adjust the counter by 36 | */ 37 | void count(String aspect, long delta); 38 | 39 | /** 40 | * Adjusts the specified counter by a given delta. 41 | * 42 | *This method is non-blocking and is guaranteed not to throw an exception.
43 | * 44 | * @param aspect 45 | * the name of the counter to adjust 46 | * @param delta 47 | * the amount to adjust the counter by 48 | * @param sampleRate 49 | * the sampling rate being employed. For example, a rate of 0.1 would tell StatsD that this counter is being sent 50 | * sampled every 1/10th of the time. 51 | */ 52 | void count(String aspect, long delta, double sampleRate); 53 | 54 | /** 55 | * Increments the specified counter by one. 56 | * 57 | *This method is non-blocking and is guaranteed not to throw an exception.
58 | * 59 | * @param aspect 60 | * the name of the counter to increment 61 | */ 62 | void incrementCounter(String aspect); 63 | 64 | /** 65 | * Convenience method equivalent to {@link #incrementCounter(String)}. 66 | */ 67 | void increment(String aspect); 68 | 69 | /** 70 | * Decrements the specified counter by one. 71 | * 72 | *This method is non-blocking and is guaranteed not to throw an exception.
73 | * 74 | * @param aspect 75 | * the name of the counter to decrement 76 | */ 77 | void decrementCounter(String aspect); 78 | 79 | /** 80 | * Convenience method equivalent to {@link #decrementCounter(String)}. 81 | */ 82 | void decrement(String aspect); 83 | 84 | /** 85 | * Records the latest fixed value for the specified named gauge. 86 | * 87 | *This method is non-blocking and is guaranteed not to throw an exception.
88 | * 89 | * @param aspect 90 | * the name of the gauge 91 | * @param value 92 | * the new reading of the gauge 93 | */ 94 | void recordGaugeValue(String aspect, long value); 95 | 96 | /** 97 | * Convenience method equivalent to {@link #recordGaugeValue(String, long)} but for double values. 98 | */ 99 | void recordGaugeValue(String aspect, double value); 100 | 101 | /** 102 | * Records a change in the value of the specified named gauge. 103 | * 104 | *This method is non-blocking and is guaranteed not to throw an exception.
105 | * 106 | * @param aspect 107 | * the name of the gauge 108 | * @param delta 109 | * the +/- delta to apply to the gauge 110 | */ 111 | void recordGaugeDelta(String aspect, long delta); 112 | 113 | /** 114 | * Convenience method equivalent to {@link #recordGaugeDelta(String, long)} but for double deltas. 115 | */ 116 | void recordGaugeDelta(String aspect, double delta); 117 | 118 | /** 119 | * Convenience method equivalent to {@link #recordGaugeValue(String, long)}. 120 | */ 121 | void gauge(String aspect, long value); 122 | 123 | /** 124 | * Convenience method equivalent to {@link #recordGaugeValue(String, double)}. 125 | */ 126 | void gauge(String aspect, double value); 127 | 128 | /** 129 | * StatsD supports counting unique occurrences of events between flushes, Call this method to records an occurrence 130 | * of the specified named event. 131 | * 132 | *This method is non-blocking and is guaranteed not to throw an exception.
133 | * 134 | * @param aspect 135 | * the name of the set 136 | * @param eventName 137 | * the value to be added to the set 138 | */ 139 | void recordSetEvent(String aspect, String eventName); 140 | 141 | /** 142 | * Convenience method equivalent to {@link #recordSetEvent(String, String)}. 143 | */ 144 | void set(String aspect, String eventName); 145 | 146 | /** 147 | * Records an execution time in milliseconds for the specified named operation. 148 | * 149 | *This method is non-blocking and is guaranteed not to throw an exception.
150 | * 151 | * @param aspect 152 | * the name of the timed operation 153 | * @param timeInMs 154 | * the time in milliseconds 155 | */ 156 | void recordExecutionTime(String aspect, long timeInMs); 157 | 158 | /** 159 | * Adjusts the specified counter by a given delta. 160 | * 161 | *This method is non-blocking and is guaranteed not to throw an exception.
162 | * 163 | * @param aspect 164 | * the name of the counter to adjust 165 | * @param delta 166 | * the amount to adjust the counter by 167 | * @param sampleRate 168 | * the sampling rate being employed. For example, a rate of 0.1 would tell StatsD that this timer is being sent 169 | * sampled every 1/10th of the time, so that it updates its timer_counters appropriately. 170 | */ 171 | void recordExecutionTime(String aspect, long timeInMs, double sampleRate); 172 | 173 | /** 174 | * Records an execution time in milliseconds for the specified named operation. The execution 175 | * time is calculated as the delta between the specified start time and the current system 176 | * time (using {@link System#currentTimeMillis()}) 177 | * 178 | *This method is non-blocking and is guaranteed not to throw an exception.
179 | * 180 | * @param aspect 181 | * the name of the timed operation 182 | * @param timeInMs 183 | * the system time, in millis, at the start of the operation that has just completed 184 | */ 185 | void recordExecutionTimeToNow(String aspect, long systemTimeMillisAtStart); 186 | 187 | /** 188 | * Convenience method equivalent to {@link #recordExecutionTime(String, long)}. 189 | */ 190 | void time(String aspect, long value); 191 | 192 | } -------------------------------------------------------------------------------- /src/main/java/com/timgroup/statsd/NonBlockingStatsDClient.java: -------------------------------------------------------------------------------- 1 | package com.timgroup.statsd; 2 | 3 | import java.nio.charset.Charset; 4 | import java.text.NumberFormat; 5 | import java.util.Locale; 6 | 7 | /** 8 | * A simple StatsD client implementation facilitating metrics recording. 9 | * 10 | *Upon instantiation, this client will establish a socket connection to a StatsD instance 11 | * running on the specified host and port. Metrics are then sent over this connection as they are 12 | * received by the client. 13 | *
14 | * 15 | *Three key methods are provided for the submission of data-points for the application under 16 | * scrutiny: 17 | *
As part of a clean system shutdown, the {@link #stop()} method should be invoked 28 | * on any StatsD clients.
29 | * 30 | * @author Tom Denley 31 | * 32 | */ 33 | public final class NonBlockingStatsDClient extends ConvenienceMethodProvidingStatsDClient { 34 | 35 | private static final Charset STATS_D_ENCODING = Charset.forName("UTF-8"); 36 | 37 | private static final StatsDClientErrorHandler NO_OP_HANDLER = new StatsDClientErrorHandler() { 38 | @Override public void handle(Exception e) { /* No-op */ } 39 | }; 40 | 41 | private final String prefix; 42 | private final NonBlockingUdpSender sender; 43 | 44 | /** 45 | * Create a new StatsD client communicating with a StatsD instance on the 46 | * specified host and port. All messages send via this client will have 47 | * their keys prefixed with the specified string. The new client will 48 | * attempt to open a connection to the StatsD server immediately upon 49 | * instantiation, and may throw an exception if that a connection cannot 50 | * be established. Once a client has been instantiated in this way, all 51 | * exceptions thrown during subsequent usage are consumed, guaranteeing 52 | * that failures in metrics will not affect normal code execution. 53 | * 54 | * @param prefix 55 | * the prefix to apply to keys sent via this client (can be null or empty for no prefix) 56 | * @param hostname 57 | * the host name of the targeted StatsD server 58 | * @param port 59 | * the port of the targeted StatsD server 60 | * @throws StatsDClientException 61 | * if the client could not be started 62 | */ 63 | public NonBlockingStatsDClient(String prefix, String hostname, int port) throws StatsDClientException { 64 | this(prefix, hostname, port, NO_OP_HANDLER); 65 | } 66 | 67 | /** 68 | * Create a new StatsD client communicating with a StatsD instance on the 69 | * specified host and port. All messages send via this client will have 70 | * their keys prefixed with the specified string. The new client will 71 | * attempt to open a connection to the StatsD server immediately upon 72 | * instantiation, and may throw an exception if that a connection cannot 73 | * be established. Once a client has been instantiated in this way, all 74 | * exceptions thrown during subsequent usage are passed to the specified 75 | * handler and then consumed, guaranteeing that failures in metrics will 76 | * not affect normal code execution. 77 | * 78 | * @param prefix 79 | * the prefix to apply to keys sent via this client (can be null or empty for no prefix) 80 | * @param hostname 81 | * the host name of the targeted StatsD server 82 | * @param port 83 | * the port of the targeted StatsD server 84 | * @param errorHandler 85 | * handler to use when an exception occurs during usage 86 | * @throws StatsDClientException 87 | * if the client could not be started 88 | */ 89 | public NonBlockingStatsDClient(String prefix, String hostname, int port, StatsDClientErrorHandler errorHandler) throws StatsDClientException { 90 | this.prefix = (prefix == null || prefix.trim().isEmpty()) ? "" : (prefix.trim() + "."); 91 | 92 | try { 93 | this.sender = new NonBlockingUdpSender(hostname, port, STATS_D_ENCODING, errorHandler); 94 | } catch (Exception e) { 95 | throw new StatsDClientException("Failed to start StatsD client", e); 96 | } 97 | } 98 | 99 | /** 100 | * Cleanly shut down this StatsD client. This method may throw an exception if 101 | * the socket cannot be closed. 102 | */ 103 | @Override 104 | public void stop() { 105 | sender.stop(); 106 | } 107 | 108 | /** 109 | * Adjusts the specified counter by a given delta. 110 | * 111 | *This method is non-blocking and is guaranteed not to throw an exception.
112 | * 113 | * @param aspect 114 | * the name of the counter to adjust 115 | * @param delta 116 | * the amount to adjust the counter by 117 | * @param sampleRate 118 | * the sampling rate being employed. For example, a rate of 0.1 would tell StatsD that this counter is being sent 119 | * sampled every 1/10th of the time. 120 | */ 121 | @Override 122 | public void count(String aspect, long delta, double sampleRate) { 123 | send(messageFor(aspect, Long.toString(delta), "c", sampleRate)); 124 | } 125 | 126 | /** 127 | * Records the latest fixed value for the specified named gauge. 128 | * 129 | *This method is non-blocking and is guaranteed not to throw an exception.
130 | * 131 | * @param aspect 132 | * the name of the gauge 133 | * @param value 134 | * the new reading of the gauge 135 | */ 136 | @Override 137 | public void recordGaugeValue(String aspect, long value) { 138 | recordGaugeCommon(aspect, Long.toString(value), value < 0, false); 139 | } 140 | 141 | @Override 142 | public void recordGaugeValue(String aspect, double value) { 143 | recordGaugeCommon(aspect, stringValueOf(value), value < 0, false); 144 | } 145 | 146 | @Override 147 | public void recordGaugeDelta(String aspect, long value) { 148 | recordGaugeCommon(aspect, Long.toString(value), value < 0, true); 149 | } 150 | 151 | @Override 152 | public void recordGaugeDelta(String aspect, double value) { 153 | recordGaugeCommon(aspect, stringValueOf(value), value < 0, true); 154 | } 155 | 156 | private void recordGaugeCommon(String aspect, String value, boolean negative, boolean delta) { 157 | final StringBuilder message = new StringBuilder(); 158 | if (!delta && negative) { 159 | message.append(messageFor(aspect, "0", "g")).append('\n'); 160 | } 161 | message.append(messageFor(aspect, (delta && !negative) ? ("+" + value) : value, "g")); 162 | send(message.toString()); 163 | } 164 | 165 | /** 166 | * StatsD supports counting unique occurrences of events between flushes, Call this method to records an occurrence 167 | * of the specified named event. 168 | * 169 | *This method is non-blocking and is guaranteed not to throw an exception.
170 | * 171 | * @param aspect 172 | * the name of the set 173 | * @param eventName 174 | * the value to be added to the set 175 | */ 176 | @Override 177 | public void recordSetEvent(String aspect, String eventName) { 178 | send(messageFor(aspect, eventName, "s")); 179 | } 180 | 181 | /** 182 | * Records an execution time in milliseconds for the specified named operation. 183 | * 184 | *This method is non-blocking and is guaranteed not to throw an exception.
185 | * 186 | * @param aspect 187 | * the name of the timed operation 188 | * @param timeInMs 189 | * the time in milliseconds 190 | */ 191 | @Override 192 | public void recordExecutionTime(String aspect, long timeInMs, double sampleRate) { 193 | send(messageFor(aspect, Long.toString(timeInMs), "ms", sampleRate)); 194 | } 195 | 196 | private String messageFor(String aspect, String value, String type) { 197 | return messageFor(aspect, value, type, 1.0); 198 | } 199 | 200 | private String messageFor(String aspect, String value, String type, double sampleRate) { 201 | final String message = prefix + aspect + ':' + value + '|' + type; 202 | return (sampleRate == 1.0) 203 | ? message 204 | : (message + "|@" + stringValueOf(sampleRate)); 205 | } 206 | 207 | private void send(final String message) { 208 | sender.send(message); 209 | } 210 | 211 | private String stringValueOf(double value) { 212 | NumberFormat formatter = NumberFormat.getInstance(Locale.US); 213 | formatter.setGroupingUsed(false); 214 | formatter.setMaximumFractionDigits(19); 215 | return formatter.format(value); 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /src/test/java/com/timgroup/statsd/NonBlockingStatsDClientTest.java: -------------------------------------------------------------------------------- 1 | package com.timgroup.statsd; 2 | 3 | import static java.lang.Long.valueOf; 4 | import static junit.framework.Assert.assertTrue; 5 | import static org.hamcrest.MatcherAssert.assertThat; 6 | import static org.hamcrest.Matchers.contains; 7 | import static org.hamcrest.Matchers.startsWith; 8 | 9 | import java.net.DatagramPacket; 10 | import java.net.DatagramSocket; 11 | import java.net.SocketException; 12 | import java.nio.charset.Charset; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | import java.util.regex.Matcher; 16 | import java.util.regex.Pattern; 17 | 18 | import org.hamcrest.Matchers; 19 | import org.junit.After; 20 | import org.junit.Test; 21 | 22 | public final class NonBlockingStatsDClientTest { 23 | 24 | private static final int STATSD_SERVER_PORT = 17254; 25 | 26 | private final NonBlockingStatsDClient client = new NonBlockingStatsDClient("my.prefix", "localhost", STATSD_SERVER_PORT); 27 | private final DummyStatsDServer server = new DummyStatsDServer(STATSD_SERVER_PORT); 28 | 29 | @After 30 | public void stop() throws Exception { 31 | client.stop(); 32 | server.stop(); 33 | } 34 | 35 | @Test(timeout=5000L) public void 36 | sends_counter_value_to_statsd() throws Exception { 37 | client.count("mycount", Long.MAX_VALUE); 38 | server.waitForMessage(); 39 | 40 | assertThat(server.messagesReceived(), contains("my.prefix.mycount:9223372036854775807|c")); 41 | } 42 | 43 | @Test(timeout=5000L) public void 44 | sends_counter_value_with_rate_to_statsd() throws Exception { 45 | client.count("mycount", Long.MAX_VALUE, 0.00024); 46 | server.waitForMessage(); 47 | 48 | assertThat(server.messagesReceived(), contains("my.prefix.mycount:9223372036854775807|c|@0.00024")); 49 | } 50 | 51 | @Test(timeout=5000L) public void 52 | sends_counter_increment_to_statsd() throws Exception { 53 | client.incrementCounter("myinc"); 54 | server.waitForMessage(); 55 | 56 | assertThat(server.messagesReceived(), contains("my.prefix.myinc:1|c")); 57 | } 58 | 59 | @Test(timeout=5000L) public void 60 | sends_counter_decrement_to_statsd() throws Exception { 61 | client.decrementCounter("mydec"); 62 | server.waitForMessage(); 63 | 64 | assertThat(server.messagesReceived(), contains("my.prefix.mydec:-1|c")); 65 | } 66 | 67 | @Test(timeout=5000L) public void 68 | sends_gauge_to_statsd() throws Exception { 69 | client.recordGaugeValue("mygauge", Long.MAX_VALUE); 70 | server.waitForMessage(); 71 | 72 | assertThat(server.messagesReceived(), contains("my.prefix.mygauge:9223372036854775807|g")); 73 | } 74 | 75 | @Test(timeout=5000L) public void 76 | sends_fractional_gauge_to_statsd() throws Exception { 77 | client.recordGaugeValue("mygauge", 423.123456789d); 78 | server.waitForMessage(); 79 | 80 | assertThat(server.messagesReceived(), contains("my.prefix.mygauge:423.123456789|g")); 81 | } 82 | 83 | @Test(timeout=5000L) public void 84 | sends_large_fractional_gauge_to_statsd() throws Exception { 85 | client.recordGaugeValue("mygauge", 423423423.9d); 86 | server.waitForMessage(); 87 | 88 | assertThat(server.messagesReceived(), contains("my.prefix.mygauge:423423423.9|g")); 89 | } 90 | 91 | @Test(timeout=5000L) public void 92 | sends_zero_gauge_to_statsd() throws Exception { 93 | client.recordGaugeValue("mygauge", 0L); 94 | server.waitForMessage(); 95 | 96 | assertThat(server.messagesReceived(), contains("my.prefix.mygauge:0|g")); 97 | } 98 | 99 | @Test(timeout=5000L) public void 100 | sends_negagive_gauge_to_statsd_by_resetting_to_zero_first() throws Exception { 101 | client.recordGaugeValue("mygauge", -423L); 102 | server.waitForMessage(); 103 | 104 | assertThat(server.messagesReceived(), contains("my.prefix.mygauge:0|g\nmy.prefix.mygauge:-423|g")); 105 | } 106 | 107 | @Test(timeout=5000L) public void 108 | sends_gauge_positive_delta_to_statsd() throws Exception { 109 | client.recordGaugeDelta("mygauge", 423L); 110 | server.waitForMessage(); 111 | 112 | assertThat(server.messagesReceived(), contains("my.prefix.mygauge:+423|g")); 113 | } 114 | 115 | @Test(timeout=5000L) public void 116 | sends_gauge_negative_delta_to_statsd() throws Exception { 117 | client.recordGaugeDelta("mygauge", -423L); 118 | server.waitForMessage(); 119 | 120 | assertThat(server.messagesReceived(), contains("my.prefix.mygauge:-423|g")); 121 | } 122 | 123 | @Test(timeout=5000L) public void 124 | sends_gauge_zero_delta_to_statsd() throws Exception { 125 | client.recordGaugeDelta("mygauge", 0L); 126 | server.waitForMessage(); 127 | 128 | assertThat(server.messagesReceived(), contains("my.prefix.mygauge:+0|g")); 129 | } 130 | 131 | @Test(timeout=5000L) public void 132 | sends_set_to_statsd() throws Exception { 133 | client.recordSetEvent("myset", "test"); 134 | server.waitForMessage(); 135 | 136 | assertThat(server.messagesReceived(), contains("my.prefix.myset:test|s")); 137 | } 138 | 139 | @Test(timeout=5000L) public void 140 | sends_timer_to_statsd() throws Exception { 141 | client.recordExecutionTime("mytime", 123L); 142 | server.waitForMessage(); 143 | 144 | assertThat(server.messagesReceived(), contains("my.prefix.mytime:123|ms")); 145 | } 146 | 147 | @Test(timeout=5000L) public void 148 | sends_timer_with_rate_to_statsd() throws Exception { 149 | client.recordExecutionTime("mytime", 123L, 0.000123); 150 | server.waitForMessage(); 151 | 152 | assertThat(server.messagesReceived(), contains("my.prefix.mytime:123|ms|@0.000123")); 153 | } 154 | 155 | @Test(timeout=5000L) public void 156 | sends_timer_to_statsd_based_on_specified_start_time_to_now() throws Exception { 157 | final long startTime = System.currentTimeMillis() - 1000L; 158 | 159 | final long beforeCompletionTime = System.currentTimeMillis(); 160 | client.recordExecutionTimeToNow("mytime", startTime); 161 | final long afterCompletionTime = System.currentTimeMillis(); 162 | 163 | server.waitForMessage(); 164 | 165 | long maxExpectedValue = afterCompletionTime - startTime; 166 | long minExpectedValue = beforeCompletionTime - startTime; 167 | final String messageReceived = server.messagesReceived().get(0); 168 | final Matcher resultMatcher = Pattern.compile(".*:(\\d+)\\|ms").matcher(messageReceived); 169 | assertTrue(messageReceived, resultMatcher.matches()); 170 | assertThat(valueOf(resultMatcher.group(1)), Matchers.greaterThanOrEqualTo(minExpectedValue)); 171 | assertThat(valueOf(resultMatcher.group(1)), Matchers.lessThanOrEqualTo(maxExpectedValue)); 172 | } 173 | 174 | @Test(timeout=5000L) public void 175 | sends_timer_of_zero_to_statsd_based_on_specified_start_time_in_the_future() throws Exception { 176 | client.recordExecutionTimeToNow("mytime", System.currentTimeMillis() + 100000L); 177 | server.waitForMessage(); 178 | 179 | assertThat(server.messagesReceived(), contains("my.prefix.mytime:0|ms")); 180 | } 181 | 182 | @Test(timeout=5000L) public void 183 | allows_empty_prefix() { 184 | final NonBlockingStatsDClient emptyPrefixClient = new NonBlockingStatsDClient(" ", "localhost", STATSD_SERVER_PORT); 185 | try { 186 | emptyPrefixClient.count("mycount", 24L); 187 | server.waitForMessage(); 188 | } finally { 189 | emptyPrefixClient.stop(); 190 | } 191 | assertThat(server.messagesReceived(), contains(startsWith("mycount:"))); 192 | } 193 | 194 | @Test(timeout=5000L) public void 195 | allows_null_prefix() { 196 | final NonBlockingStatsDClient nullPrefixClient = new NonBlockingStatsDClient(null, "localhost", STATSD_SERVER_PORT); 197 | try { 198 | nullPrefixClient.count("mycount", 24L); 199 | server.waitForMessage(); 200 | } finally { 201 | nullPrefixClient.stop(); 202 | } 203 | assertThat(server.messagesReceived(), contains(startsWith("mycount:"))); 204 | } 205 | 206 | private static final class DummyStatsDServer { 207 | private final List