diff --git a/dogstatsd-http/forwarder/pom.xml b/dogstatsd-http/forwarder/pom.xml index c278e1c0..40bed66e 100644 --- a/dogstatsd-http/forwarder/pom.xml +++ b/dogstatsd-http/forwarder/pom.xml @@ -15,11 +15,6 @@ HTTP forwarder for DogStatsD metrics. - - com.datadoghq - dogstatsd-http-core - 1.0.0-SNAPSHOT - junit junit diff --git a/dogstatsd-http/forwarder/src/main/java/com/datadoghq/dogstatsd/http/forwarder/Telemetry.java b/dogstatsd-http/forwarder/src/main/java/com/datadoghq/dogstatsd/http/forwarder/Telemetry.java index 99fbd72f..574cdfa8 100644 --- a/dogstatsd-http/forwarder/src/main/java/com/datadoghq/dogstatsd/http/forwarder/Telemetry.java +++ b/dogstatsd-http/forwarder/src/main/java/com/datadoghq/dogstatsd/http/forwarder/Telemetry.java @@ -7,11 +7,8 @@ package com.datadoghq.dogstatsd.http.forwarder; -import com.datadoghq.dogstatsd.http.serializer.PayloadBuilder; import java.time.Clock; -import java.util.Collections; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.function.LongSupplier; @@ -56,50 +53,63 @@ public static final class Snapshot { this.intervalStartMillis = intervalStartMillis; } + /** Destination for the metrics of an encoded snapshot. */ + public interface Encoder { + /** + * Emits a gauge. + * + * @param name Full metric name. + * @param value Metric value. + */ + void gauge(String name, double value); + + /** + * Emits a count. + * + * @param name Full metric name. + * @param value Metric value. + * @param tags Tags to attach, or {@code null} for none. + */ + void count(String name, double value, String[] tags); + } + /** - * Encodes this snapshot into {@code pb} using the default metric. + * Encodes this snapshot into {@code enc} using the default metric name prefix. * - * @param pb Builder to append metrics to. + * @param enc Encoder to emit metrics to. */ - public void encodeTo(PayloadBuilder pb) { - encodeTo(DEFAULT_PREFIX, pb); + public void encodeTo(Encoder enc) { + encodeTo(DEFAULT_PREFIX, enc); } /** - * Encodes this snapshot into {@code pb}. + * Encodes this snapshot into {@code enc}. + * + *

Metrics carry no timestamp — the encoder decides how to timestamp them. * - * @param pb Builder to append metrics to. * @param prefix Metric name prefix. + * @param enc Encoder to emit metrics to. */ - public void encodeTo(String prefix, PayloadBuilder pb) { - long ts = intervalStartMillis / 1000; - - pb.count(prefix + ".enqueued_payloads").addPoint(ts, enqueuedPayloads).close(); - pb.count(prefix + ".enqueued_bytes").addPoint(ts, enqueuedBytes).close(); - pb.count(prefix + ".delivered_payloads").addPoint(ts, deliveredPayloads).close(); - pb.count(prefix + ".delivered_bytes").addPoint(ts, deliveredBytes).close(); - pb.count(prefix + ".dropped_payloads").addPoint(ts, droppedPayloads).close(); - pb.count(prefix + ".dropped_bytes").addPoint(ts, droppedBytes).close(); - - pb.gauge(prefix + ".queue_payloads").addPoint(ts, queuePayloads).close(); - pb.gauge(prefix + ".queue_bytes").addPoint(ts, queueBytes).close(); - pb.gauge(prefix + ".queue_max_bytes").addPoint(ts, queueMaxBytes).close(); - - pb.gauge(prefix + ".oldest_enqueued_age_seconds") - .addPoint(ts, oldestEnqueuedAgeNanos / 1e9) - .close(); - pb.gauge(prefix + ".last_success_age_seconds") - .addPoint(ts, lastSuccessAgeNanos / 1e9) - .close(); + public void encodeTo(String prefix, Encoder enc) { + enc.count(prefix + ".enqueued_payloads", (double) enqueuedPayloads, null); + enc.count(prefix + ".enqueued_bytes", (double) enqueuedBytes, null); + enc.count(prefix + ".delivered_payloads", (double) deliveredPayloads, null); + enc.count(prefix + ".delivered_bytes", (double) deliveredBytes, null); + enc.count(prefix + ".dropped_payloads", (double) droppedPayloads, null); + enc.count(prefix + ".dropped_bytes", (double) droppedBytes, null); + + enc.gauge(prefix + ".queue_payloads", (double) queuePayloads); + enc.gauge(prefix + ".queue_bytes", (double) queueBytes); + enc.gauge(prefix + ".queue_max_bytes", (double) queueMaxBytes); + + enc.gauge(prefix + ".oldest_enqueued_age_seconds", oldestEnqueuedAgeNanos / 1e9); + enc.gauge(prefix + ".last_success_age_seconds", lastSuccessAgeNanos / 1e9); for (Map.Entry e : byCode.entrySet()) { - List tags = Collections.singletonList("code:" + e.getKey()); + String[] tags = {"code:" + e.getKey()}; CodeCounters c = e.getValue(); - pb.count(prefix + ".response_payloads") - .setTags(tags) - .addPoint(ts, c.payloads) - .close(); - pb.count(prefix + ".response_bytes").setTags(tags).addPoint(ts, c.bytes).close(); + enc.count(prefix + ".response_payloads", (double) c.payloads, tags); + enc.count(prefix + ".response_bytes", (double) c.bytes, tags); } } diff --git a/dogstatsd-http/forwarder/src/test/java/com/datadoghq/dogstatsd/http/forwarder/TelemetryTest.java b/dogstatsd-http/forwarder/src/test/java/com/datadoghq/dogstatsd/http/forwarder/TelemetryTest.java index edb692ea..190dfc90 100644 --- a/dogstatsd-http/forwarder/src/test/java/com/datadoghq/dogstatsd/http/forwarder/TelemetryTest.java +++ b/dogstatsd-http/forwarder/src/test/java/com/datadoghq/dogstatsd/http/forwarder/TelemetryTest.java @@ -12,12 +12,12 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import com.datadoghq.dogstatsd.http.serializer.PayloadBuilder; -import java.io.ByteArrayOutputStream; import java.net.URI; -import java.nio.charset.StandardCharsets; import java.time.Clock; import java.time.Instant; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; import org.junit.Test; public class TelemetryTest { @@ -174,6 +174,28 @@ public void lastSuccessAge() { assertEquals(0L, t.snapshot(null).lastSuccessAgeNanos); } + /** Encoder that records every emitted metric, keyed by name and tags. */ + private static final class Recorder implements Telemetry.Snapshot.Encoder { + final Map gauges = new HashMap<>(); + final Map counts = new HashMap<>(); + + @Override + public void gauge(String name, double value) { + gauges.put(name, value); + } + + @Override + public void count(String name, double value, String[] tags) { + counts.put(tags == null ? name : name + Arrays.toString(tags), value); + } + } + + private static void assertMetric(String name, double expected, Map metrics) { + Double actual = metrics.get(name); + assertNotNull("missing " + name, actual); + assertEquals(name, expected, actual, 0); + } + @Test public void encodeTo() { Telemetry t = new Telemetry(); @@ -182,49 +204,28 @@ public void encodeTo() { t.onResponse(503, 7, false); t.onDrop(1, 25); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - PayloadBuilder pb = new PayloadBuilder(out::writeBytes); - t.snapshot(null).encodeTo(pb); - pb.close(); - byte[] p = out.toByteArray(); + Recorder rec = new Recorder(); + t.snapshot(null).encodeTo(rec); String prefix = "datadog.dogstatsd_http.client"; - for (String suffix : - new String[] { - ".enqueued_payloads", - ".enqueued_bytes", - ".delivered_payloads", - ".delivered_bytes", - ".dropped_payloads", - ".dropped_bytes", - ".queue_payloads", - ".queue_bytes", - ".queue_max_bytes", - ".oldest_enqueued_age_seconds", - ".last_success_age_seconds", - ".response_payloads", - ".response_bytes", - }) { - assertTrue("missing " + suffix, contains(p, prefix + suffix)); - } + assertMetric(prefix + ".enqueued_payloads", 1, rec.counts); + assertMetric(prefix + ".enqueued_bytes", 10, rec.counts); + assertMetric(prefix + ".delivered_payloads", 1, rec.counts); + assertMetric(prefix + ".delivered_bytes", 5, rec.counts); + assertMetric(prefix + ".dropped_payloads", 1, rec.counts); + assertMetric(prefix + ".dropped_bytes", 25, rec.counts); + + assertMetric(prefix + ".queue_payloads", 0, rec.gauges); + assertMetric(prefix + ".queue_bytes", 0, rec.gauges); + assertMetric(prefix + ".queue_max_bytes", 0, rec.gauges); + // Ages come off the real clock here; only their presence is deterministic. + assertNotNull(rec.gauges.get(prefix + ".oldest_enqueued_age_seconds")); + assertNotNull(rec.gauges.get(prefix + ".last_success_age_seconds")); // Per-code totals are tagged with the HTTP code. - assertTrue(contains(p, "code:200")); - assertTrue(contains(p, "code:503")); - } - - /** True if {@code needle} appears verbatim (as UTF-8) anywhere in {@code haystack}. */ - private static boolean contains(byte[] haystack, String needle) { - byte[] n = needle.getBytes(StandardCharsets.UTF_8); - outer: - for (int i = 0; i + n.length <= haystack.length; i++) { - for (int j = 0; j < n.length; j++) { - if (haystack[i + j] != n[j]) { - continue outer; - } - } - return true; - } - return false; + assertMetric(prefix + ".response_payloads[code:200]", 1, rec.counts); + assertMetric(prefix + ".response_bytes[code:200]", 5, rec.counts); + assertMetric(prefix + ".response_payloads[code:503]", 1, rec.counts); + assertMetric(prefix + ".response_bytes[code:503]", 7, rec.counts); } }