diff --git a/src/benchmark/java/com/timgroup/statsd/FullPrecisionBenchmark.java b/src/benchmark/java/com/timgroup/statsd/FullPrecisionBenchmark.java new file mode 100644 index 00000000..fdfed7c1 --- /dev/null +++ b/src/benchmark/java/com/timgroup/statsd/FullPrecisionBenchmark.java @@ -0,0 +1,41 @@ +package com.timgroup.statsd; + +import java.util.concurrent.TimeUnit; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@Fork(2) +@State(Scope.Thread) +public class FullPrecisionBenchmark { + + @Param({"3.141592653589793", "3.3E20", "42.0", "0.423", "243.5"}) + public double value; + + private final StringBuilder builder = new StringBuilder(64); + + @Benchmark + public int format_default() { + builder.setLength(0); + builder.append(NonBlockingStatsDClient.format(NonBlockingStatsDClient.NUMBER_FORMATTER, value)); + return builder.length(); + } + + @Benchmark + public int format_full_precision() { + builder.setLength(0); + builder.append(value); + return builder.length(); + } +} diff --git a/src/main/java/com/timgroup/statsd/NonBlockingStatsDClient.java b/src/main/java/com/timgroup/statsd/NonBlockingStatsDClient.java index 4ea7591c..85b9462a 100644 --- a/src/main/java/com/timgroup/statsd/NonBlockingStatsDClient.java +++ b/src/main/java/com/timgroup/statsd/NonBlockingStatsDClient.java @@ -182,6 +182,7 @@ protected static String format(ThreadLocal formatter, Number value private final String containerID; private final String externalEnv; final TagsCardinality clientTagsCardinality; + private final boolean fullPrecision; /** * Create a new StatsD client communicating with a StatsD instance on the host and port @@ -207,6 +208,7 @@ public NonBlockingStatsDClient(final NonBlockingStatsDClientBuilder builder) } blocking = builder.blocking; + fullPrecision = builder.fullPrecision; maxPacketSizeBytes = builder.maxPacketSizeBytes; clientTagsCardinality = builder.tagsCardinality; @@ -625,7 +627,11 @@ private void send( tags) { @Override protected void writeValue(StringBuilder builder) { - builder.append(format(NUMBER_FORMATTER, this.value)); + if (fullPrecision) { + builder.append(this.value); + } else { + builder.append(format(NUMBER_FORMATTER, this.value)); + } } }); } diff --git a/src/main/java/com/timgroup/statsd/NonBlockingStatsDClientBuilder.java b/src/main/java/com/timgroup/statsd/NonBlockingStatsDClientBuilder.java index 289b16c2..39dfd368 100644 --- a/src/main/java/com/timgroup/statsd/NonBlockingStatsDClientBuilder.java +++ b/src/main/java/com/timgroup/statsd/NonBlockingStatsDClientBuilder.java @@ -52,6 +52,9 @@ public class NonBlockingStatsDClientBuilder implements Cloneable { public boolean enableAggregation = NonBlockingStatsDClient.DEFAULT_ENABLE_AGGREGATION; + /** Use full precision when formatting numeric metric values. */ + public boolean fullPrecision = true; + /** Telemetry flush interval, in milliseconds. */ public int telemetryFlushInterval = Telemetry.DEFAULT_FLUSH_INTERVAL; @@ -270,6 +273,12 @@ public NonBlockingStatsDClientBuilder enableAggregation(boolean val) { return this; } + /** Use full precision when formatting numeric metric values. */ + public NonBlockingStatsDClientBuilder fullPrecision(boolean val) { + fullPrecision = val; + return this; + } + /** Telemetry flush interval, in milliseconds. */ public NonBlockingStatsDClientBuilder telemetryFlushInterval(int val) { telemetryFlushInterval = val; diff --git a/src/test/java/com/timgroup/statsd/NonBlockingStatsDClientTest.java b/src/test/java/com/timgroup/statsd/NonBlockingStatsDClientTest.java index 8ed7a030..0e803793 100644 --- a/src/test/java/com/timgroup/statsd/NonBlockingStatsDClientTest.java +++ b/src/test/java/com/timgroup/statsd/NonBlockingStatsDClientTest.java @@ -43,6 +43,7 @@ public class NonBlockingStatsDClientTest { private static final int STATSD_SERVER_PORT = 17254; private NonBlockingStatsDClient client; private NonBlockingStatsDClient clientUnaggregated; + private NonBlockingStatsDClient clientFullPrecision; private static UDPDummyStatsDServer server; private static Logger log = Logger.getLogger("NonBlockingStatsDClientTest"); @@ -111,8 +112,20 @@ public void start() throws IOException { .originDetectionEnabled(originDetectionEnabled) .aggregationFlushInterval(100) .containerID(containerID) + .fullPrecision(false) .build(); clientUnaggregated = + new NonBlockingStatsDClientBuilder() + .prefix("my.prefix") + .hostname("localhost") + .port(server.getPort()) + .enableTelemetry(false) + .enableAggregation(false) + .originDetectionEnabled(originDetectionEnabled) + .containerID(containerID) + .fullPrecision(false) + .build(); + clientFullPrecision = new NonBlockingStatsDClientBuilder() .prefix("my.prefix") .hostname("localhost") @@ -128,6 +141,7 @@ public void start() throws IOException { public void stop() throws IOException { client.stop(); clientUnaggregated.stop(); + clientFullPrecision.stop(); server.close(); } @@ -236,6 +250,22 @@ public void sends_double_counter_value_with_incorrect_timestamp() throws Excepti assertPayload("my.prefix.mycount:42.5|c|T1|#baz,foo:bar"); } + @Test(timeout = 5000L) + public void sends_large_long_counter_to_statsd() throws Exception { + clientUnaggregated.count("mycount", 1L << 62); + server.waitForMessage("my.prefix"); + + assertPayload("my.prefix.mycount:4611686018427387904|c"); + } + + @Test(timeout = 5000L) + public void sends_large_long_counter_to_statsd_with_full_precision() throws Exception { + clientFullPrecision.count("mycount", 1L << 62); + server.waitForMessage("my.prefix"); + + assertPayload("my.prefix.mycount:4611686018427387904|c"); + } + @Test(timeout = 5000L) public void sends_counter_increment_to_statsd() throws Exception { @@ -354,10 +384,19 @@ public void sends_gauge_with_sample_rate_to_statsd() throws Exception { @Test(timeout = 5000L) public void sends_large_double_gauge_to_statsd() throws Exception { - client.recordGaugeValue("mygauge", 123456789012345.67890); + client.recordGaugeValue("mygauge", 3.3e20); + server.waitForMessage("my.prefix"); + + assertPayload("my.prefix.mygauge:330000000000000000000|g"); + } + + @Test(timeout = 5000L) + public void sends_large_double_gauge_to_statsd_with_full_precision() throws Exception { + + clientFullPrecision.recordGaugeValue("mygauge", 3.3e20); server.waitForMessage("my.prefix"); - assertPayload("my.prefix.mygauge:123456789012345.67|g"); + assertPayload("my.prefix.mygauge:3.3E20|g"); } @Test(timeout = 5000L) @@ -396,6 +435,20 @@ public void sends_gauge_with_sample_rate_to_statsd_with_tags() throws Exception assertPayload("my.prefix.mygauge:423|g|@1.000000|#baz,foo:bar"); } + @Test(timeout = 5000L) + public void sends_gauge_with_full_precision_to_statsd() throws Exception { + clientFullPrecision.recordGaugeValue("mygauge", Math.PI); + server.waitForMessage("my.prefix"); + assertPayload("my.prefix.mygauge:3.141592653589793|g"); + } + + @Test(timeout = 5000L) + public void sends_gauge_with_full_precision_integer_value_to_statsd() throws Exception { + clientFullPrecision.recordGaugeValue("mygauge", 42.0); + server.waitForMessage("my.prefix"); + assertPayload("my.prefix.mygauge:42.0|g"); + } + @Test(timeout = 5000L) public void sends_long_gauge_with_timestamp() throws Exception { clientUnaggregated.gaugeWithTimestamp("mygauge", 234l, 1205794800, "foo:bar", "baz");