diff --git a/dogstatsd-http/forwarder/pom.xml b/dogstatsd-http/forwarder/pom.xml
index 6a72a802..ee1a2514 100644
--- a/dogstatsd-http/forwarder/pom.xml
+++ b/dogstatsd-http/forwarder/pom.xml
@@ -15,6 +15,11 @@
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 f56f65cb..99fbd72f 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,8 +7,11 @@
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;
@@ -46,10 +49,60 @@ public static final class Snapshot {
/** Totals keyed by HTTP code. */
public Map byCode = new HashMap<>();
+ /** Default metric name prefix used when none is supplied to {@link Snapshot#encode}. */
+ static final String DEFAULT_PREFIX = "datadog.dogstatsd_http.client";
+
Snapshot(long intervalStartMillis) {
this.intervalStartMillis = intervalStartMillis;
}
+ /**
+ * Encodes this snapshot into {@code pb} using the default metric.
+ *
+ * @param pb Builder to append metrics to.
+ */
+ public void encodeTo(PayloadBuilder pb) {
+ encodeTo(DEFAULT_PREFIX, pb);
+ }
+
+ /**
+ * Encodes this snapshot into {@code pb}.
+ *
+ * @param pb Builder to append metrics to.
+ * @param prefix Metric name prefix.
+ */
+ 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();
+
+ for (Map.Entry e : byCode.entrySet()) {
+ List tags = Collections.singletonList("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();
+ }
+ }
+
/** Per-code totals within a snapshot's window. */
public static final class CodeCounters {
public long payloads;
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 7b88c94d..edb692ea 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,7 +12,10 @@
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 org.junit.Test;
@@ -170,4 +173,58 @@ public void lastSuccessAge() {
t.onResponse(200, 1, true);
assertEquals(0L, t.snapshot(null).lastSuccessAgeNanos);
}
+
+ @Test
+ public void encodeTo() {
+ Telemetry t = new Telemetry();
+ t.onEnqueue(10);
+ t.onResponse(200, 5, true);
+ 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();
+
+ 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));
+ }
+
+ // 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;
+ }
}