This is an automated email from the ASF dual-hosted git repository.
shreemaan-abhishek pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git
The following commit(s) were added to refs/heads/master by this push:
new 7b9038835 perf(datadog): reuse one UDP socket per batch and coalesce
metrics (#13653)
7b9038835 is described below
commit 7b9038835c0e0a6f39378a62bea4279e3a088ed2
Author: Shreemaan Abhishek <[email protected]>
AuthorDate: Mon Jul 6 19:09:33 2026 +0800
perf(datadog): reuse one UDP socket per batch and coalesce metrics (#13653)
* perf(datadog): reuse one UDP socket per batch and coalesce metrics
The datadog plugin opened a fresh UDP socket for every entry in a
flushed batch and sent 6 separate datagrams per entry (request.counter,
request.latency, upstream.latency, apisix.latency, ingress.size,
egress.size). For a batch of N entries this cost N socket setups and
6N sendto syscalls.
Reuse a single socket across the whole batch and coalesce each entry's
metrics into one newline-delimited DogStatsD datagram, cutting the
per-batch work to 1 socket setup and N sends. Newline-delimited
multi-metric packets are standard DogStatsD and parsed by the agent.
The test mock now splits received datagrams on newlines, as a real
DogStatsD server does, so existing metric assertions still hold.
Signed-off-by: Abhishek Choudhary <[email protected]>
* refactor(datadog): single checked socket close on the batch send path
Collapse the send loop to a single exit so there is one checked
sock:close() instead of an unchecked close on the failure path plus a
checked one at the end, matching the existing udp-logger idiom.
Signed-off-by: Abhishek Choudhary <[email protected]>
---------
Signed-off-by: Abhishek Choudhary <[email protected]>
---
apisix/plugins/datadog.lua | 121 +++++++++++++++++----------------------------
t/lib/mock_layer4.lua | 6 ++-
2 files changed, 49 insertions(+), 78 deletions(-)
diff --git a/apisix/plugins/datadog.lua b/apisix/plugins/datadog.lua
index c720f4216..b39f5718c 100644
--- a/apisix/plugins/datadog.lua
+++ b/apisix/plugins/datadog.lua
@@ -142,17 +142,8 @@ local function generate_tag(entry, const_tags)
end
-local function send_metric_over_udp(entry, metadata)
- local err_msg
- local sock = udp()
- local host, port = metadata.value.host, metadata.value.port
-
- local ok, err = sock:setpeername(host, port)
- if not ok then
- return false, "failed to connect to UDP server: host[" .. host
- .. "] port[" .. tostring(port) .. "] err: " .. err
- end
-
+-- Build all DogStatsD metrics for one entry as a single newline-delimited
packet.
+local function build_metrics(entry, metadata)
-- Generate prefix & suffix according dogstatsd udp data format.
local suffix = generate_tag(entry, metadata.value.constant_tags)
local prefix = metadata.value.namespace
@@ -160,72 +151,24 @@ local function send_metric_over_udp(entry, metadata)
prefix = prefix .. "."
end
- -- request counter
- ok, err = sock:send(format("%s:%s|%s%s", prefix .. "request.counter", 1,
"c", suffix))
- if not ok then
- err_msg = "error sending request.counter: " .. err
- core.log.error("failed to report request count to dogstatsd server:
host[" .. host
- .. "] port[" .. tostring(port) .. "] err: " .. err)
- end
-
- -- request latency histogram
- ok, err = sock:send(format("%s:%s|%s%s", prefix .. "request.latency",
- entry.latency, "h", suffix))
- if not ok then
- err_msg = "error sending request.latency: " .. err
- core.log.error("failed to report request latency to dogstatsd server:
host["
- .. host .. "] port[" .. tostring(port) .. "] err: " ..
err)
- end
+ local metrics = {
+ format("%s:%s|%s%s", prefix .. "request.counter", 1, "c", suffix),
+ format("%s:%s|%s%s", prefix .. "request.latency", entry.latency, "h",
suffix),
+ }
- -- upstream latency
if entry.upstream_latency then
- ok, err = sock:send(format("%s:%s|%s%s", prefix .. "upstream.latency",
- entry.upstream_latency, "h", suffix))
- if not ok then
- err_msg = "error sending upstream.latency: " .. err
- core.log.error("failed to report upstream latency to dogstatsd
server: host["
- .. host .. "] port[" .. tostring(port) .. "] err: "
.. err)
- end
- end
-
- -- apisix_latency
- ok, err = sock:send(format("%s:%s|%s%s", prefix .. "apisix.latency",
- entry.apisix_latency, "h", suffix))
- if not ok then
- err_msg = "error sending apisix.latency: " .. err
- core.log.error("failed to report apisix latency to dogstatsd server:
host[" .. host
- .. "] port[" .. tostring(port) .. "] err: " .. err)
+ core.table.insert(metrics, format("%s:%s|%s%s", prefix ..
"upstream.latency",
+ entry.upstream_latency, "h",
suffix))
end
- -- request body size timer
- ok, err = sock:send(format("%s:%s|%s%s", prefix .. "ingress.size",
- entry.request.size, "ms", suffix))
- if not ok then
- err_msg = "error sending ingress.size: " .. err
- core.log.error("failed to report req body size to dogstatsd server:
host[" .. host
- .. "] port[" .. tostring(port) .. "] err: " .. err)
- end
-
- -- response body size timer
- ok, err = sock:send(format("%s:%s|%s%s", prefix .. "egress.size",
- entry.response.size, "ms", suffix))
- if not ok then
- err_msg = "error sending egress.size: " .. err
- core.log.error("failed to report response body size to dogstatsd
server: host["
- .. host .. "] port[" .. tostring(port) .. "] err: " ..
err)
- end
+ core.table.insert(metrics, format("%s:%s|%s%s", prefix .. "apisix.latency",
+ entry.apisix_latency, "h", suffix))
+ core.table.insert(metrics, format("%s:%s|%s%s", prefix .. "ingress.size",
+ entry.request.size, "ms", suffix))
+ core.table.insert(metrics, format("%s:%s|%s%s", prefix .. "egress.size",
+ entry.response.size, "ms", suffix))
- ok, err = sock:close()
- if not ok then
- core.log.error("failed to close the UDP connection, host[",
- host, "] port[", port, "] ", err)
- end
-
- if not err_msg then
- return true
- end
-
- return false, err_msg
+ return concat(metrics, "\n")
end
@@ -240,16 +183,40 @@ local function push_metrics(entries)
metadata = {}
metadata.value = defaults
end
- core.log.info("sending batch metrics to dogstatsd: ", metadata.value.host,
- ":", metadata.value.port)
+ local host, port = metadata.value.host, metadata.value.port
+ core.log.info("sending batch metrics to dogstatsd: ", host, ":", port)
+
+ -- reuse a single socket for the whole batch instead of one per entry
+ local sock = udp()
+ local ok, err = sock:setpeername(host, port)
+ if not ok then
+ return false, "failed to connect to UDP server: host[" .. host
+ .. "] port[" .. tostring(port) .. "] err: " .. err
+ end
+
+ local err_msg, first_fail
for i = 1, #entries do
- local ok, err = send_metric_over_udp(entries[i], metadata)
- if not ok then
- return false, err, i
+ -- coalesce the per-request metrics into one datagram
+ local send_ok, send_err = sock:send(build_metrics(entries[i],
metadata))
+ if not send_ok then
+ err_msg = "failed to send metrics to dogstatsd server: host[" ..
host
+ .. "] port[" .. tostring(port) .. "] err: " .. send_err
+ first_fail = i
+ break
end
end
+ ok, err = sock:close()
+ if not ok then
+ core.log.error("failed to close the UDP connection, host[",
+ host, "] port[", port, "] ", err)
+ end
+
+ if err_msg then
+ return false, err_msg, first_fail
+ end
+
return true
end
diff --git a/t/lib/mock_layer4.lua b/t/lib/mock_layer4.lua
index cc15bb04f..b4d4e1031 100644
--- a/t/lib/mock_layer4.lua
+++ b/t/lib/mock_layer4.lua
@@ -38,7 +38,11 @@ function _M.dogstatsd()
end
return
end
- core.log.warn("message received: ", data)
+
+ -- a datagram may carry several newline-delimited metrics
+ for line in data:gmatch("[^\n]+") do
+ core.log.warn("message received: ", line)
+ end
end
end