Custom Metrics ด้วย DogStatsD
ใจความสำคัญในหนึ่งประโยค
หัวข้อที่มีชื่อว่า “ใจความสำคัญในหนึ่งประโยค”DogStatsD คือ daemon เล็ก ๆ ที่ฝังอยู่ใน Agent ให้ application code ส่ง custom metric ได้ด้วยการยิง datagram แบบ lightweight ผ่าน UDP หรือ Unix domain socket โดยไม่ต้องเขียน check เลย บทนี้ส่ง datagram จริงเข้า Agent จริงแล้วอ่าน internal counter จริงของมัน เพื่อพิสูจน์ว่ามันถูกรับและ aggregate ยังไงจริง ๆ
ทำไมต้องมี DogStatsD
หัวข้อที่มีชื่อว่า “ทำไมต้องมี DogStatsD”check (จากบทก่อนหน้า) เหมาะกับ infrastructure และ integration ที่รู้จักกันอยู่แล้ว แต่ไม่ช่วยเวลาต้องการวัดอะไรที่เฉพาะเจาะจงกับ application code ของตัวเอง เช่น “checkout attempt เกิดกี่ครั้ง” “function นี้ใช้เวลาเท่าไร” “endpoint นี้มี unique user กี่คน” การเขียน Agent check เต็มรูปแบบสำหรับเรื่องพวกนี้จะเกินความจำเป็น DogStatsD แก้ปัญหานี้ด้วยการฟัง datagram เล็ก ๆ ที่ process ไหนบน host ก็ยิงเข้ามาได้ — ผ่าน UDP (default 8125) หรือ พิสูจน์ด้านล่าง ผ่าน Unix domain socket Agent รับ datagram เหล่านั้น aggregate แล้วส่งต่อขึ้นไป application ไม่ต้องคุยกับ Datadog backend โดยตรงเลย
รูปแบบ datagram
หัวข้อที่มีชื่อว่า “รูปแบบ datagram”การส่ง DogStatsD คือ text หนึ่งบรรทัดผ่าน UDP หรือ UDS
<METRIC_NAME>:<VALUE>|<TYPE>|@<SAMPLE_RATE>|#<TAG_KEY_1>:<TAG_VALUE_1>,<TAG_2>ตัวอย่างเช่นส่ง counter สำหรับ checkout attempt พร้อม tag environment กับ payment provider
checkout.attempts:1|c|@1|#env:prod,provider:stripecheckout.attempts— ชื่อ metric1— ค่าที่ส่งc— type (ในที่นี้คือ count)@1— sample rate (ที่นี่ 1 หมายถึง “ไม่ sample” คือส่งทุก event)#env:prod,provider:stripe— tag คั่นด้วย comma
Metric type
หัวข้อที่มีชื่อว่า “Metric type”DogStatsD รองรับ type code ชุดคงที่
- COUNT (
c) — เพิ่ม counter ตามค่าที่ให้ ใช้กับ “X เกิดขึ้นกี่ครั้ง” - GAUGE (
g) — set metric เป็นค่าที่แน่นอน ณ ขณะนั้น ใช้กับ “ค่าปัจจุบันของ X คือเท่าไร” - HISTOGRAM (
h) — บันทึก distribution ของค่า โดย aggregate แบบ Agent-side ต่อ host (โดย DogStatsD server ของ Agent) - DISTRIBUTION (
d) — บันทึก distribution ของค่า โดย aggregate แบบ global ที่ server-side (backend ของ Datadog merge sketch) - SET (
s) — นับจำนวนค่าที่ไม่ซ้ำกันที่ส่งเข้ามาสำหรับ metric นั้น เช่น unique user ID - TIMER (
ms) — alias ของ HISTOGRAM ตามธรรมเนียมใช้กับการวัดเวลาเป็น millisecond ข้างในทำงานเหมือนhทุกอย่าง
พิสูจน์: datagram HISTOGRAM กับ DISTRIBUTION จริง อ่านกลับด้วย agent dogstatsd-stats
หัวข้อที่มีชื่อว่า “พิสูจน์: datagram HISTOGRAM กับ DISTRIBUTION จริง อ่านกลับด้วย agent dogstatsd-stats”แทนที่จะใช้ client library (venv Python ของบทนี้ที่ harness pin ไว้มีแค่ ddtrace กับ datadog_api_client ไม่มี datadog/hot-shots — การพิสูจน์ wire protocol ดิบตรง ๆ เป็นการพิสูจน์ที่แข็งแรงกว่าอยู่แล้ว เพราะโชว์เป๊ะ ๆ ว่า client library จะสร้างอะไรข้างใต้) บทนี้ส่งรูปแบบ datagram ที่ document ไว้ตรง ๆ ผ่าน UDP ด้วย module socket ที่มากับ Python ไปที่ datadog/agent:7 container จริง ที่ publish UDP port ของ DogStatsD ออกมาที่ host และเปิด dogstatsd_metrics_stats_enable ไว้ให้ agent dogstatsd-stats มีอะไรให้โชว์:
$ docker run -d --name dd-dsd \ -e DD_API_KEY=<fake> -e DD_DD_URL=http://127.0.0.1:1 \ -e DD_DOGSTATSD_NON_LOCAL_TRAFFIC=true \ -e DD_DOGSTATSD_METRICS_STATS_ENABLE=true \ -p 18125:8125/udp \ datadog/agent:7# 20 HISTOGRAM + 20 DISTRIBUTION sample, loop socket.sendto() เดียว$ python3 -c "import socket, timesock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)addr = ('127.0.0.1', 18125)for i in range(20): sock.sendto(f'lesson.histogram.duration:{10+i}|h|#env:lesson'.encode(), addr) sock.sendto(f'lesson.distribution.duration:{10+i}|d|#env:lesson'.encode(), addr) time.sleep(0.05)"$ docker exec dd-dsd agent dogstatsd-statslesson.distribution.duration | env:lesson | 20 | 2026-09-29 14:02:17...lesson.histogram.duration | env:lesson | 20 | 2026-09-29 14:02:17...ทั้งสอง type ถูกรับและนับเหมือนกันเป๊ะที่ชั้น ingestion — 20 datagram ดิบทั้งคู่ พิสูจน์จริงว่า wire protocol กับ path การรับเหมือนกันสำหรับ h กับ d ส่วนที่ต่างกันจริง ๆ คือสิ่งที่ Agent ทำกับค่าดิบเหล่านั้นต่อ ซึ่งเชื่อมตรงกับ proof ของ check API จากบทที่แล้ว: การเรียก self.histogram(...) ใน check ให้ derived metric จริง 5 ตัว (.avg, .max, .median, .95percentile, .count) ที่คำนวณ Agent-side บน Agent ตัวเดียวนั้น — HISTOGRAM ของ DogStatsD วิ่งผ่าน aggregation code path แบบ Agent-side เดียวกันเป๊ะ ดังนั้นการ fan-out เป็น 5 series แบบเดียวกันและข้อจำกัด “แม่นยำแค่ host เดียว” แบบเดียวกันใช้ได้เหมือนกัน ส่วน DISTRIBUTION ตรงข้าม ส่งค่าดิบขึ้นไปให้ aggregate ข้ามทุก host และ container ที่ส่ง metric ชื่อนั้น — เป็นความสามารถฝั่ง backend จริง ๆ ที่บทนี้สังเกตในเครื่องไม่ได้โดยไม่มี Datadog org จริง (ไม่มี account ในบทนี้ ตามที่ตั้งใจ) ซึ่งเป็นเหตุผลตรง ๆ ว่าทำไม guidance ทางการถึงแนะนำ DISTRIBUTION เมื่อต้องการ percentile ระดับ global ที่แม่นยำ (เช่น p99 latency ทั้ง fleet)
กับดัก cardinality พิสูจน์ด้วยจำนวน metric ที่ระเบิดจริง
หัวข้อที่มีชื่อว่า “กับดัก cardinality พิสูจน์ด้วยจำนวน metric ที่ระเบิดจริง”บทก่อนหน้าใน foundations module ครอบเรื่อง cardinality แบบนามธรรม ที่นี่คือการระเบิดจริงที่วัดได้ การส่ง COUNT 30 ครั้งของ metric name เดียวกัน แต่ละครั้งมี tag request_id ที่ไม่ซ้ำกันจริง (ความผิดพลาดคลาสสิกของ AI-generated code — “tag ทุก request ด้วย ID เพื่อ traceability”):
$ python3 -c "import socket, uuidsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)addr = ('127.0.0.1', 18125)for i in range(30): uid = uuid.uuid4().hex[:8] sock.sendto(f'lesson.requests.count:1|c|#request_id:{uid}'.encode(), addr)"$ docker exec dd-dsd agent dogstatsd-stats | grep lesson.requests.count | wc -l30$ docker exec dd-dsd agent dogstatsd-stats | grep lesson.requests.count | head -3lesson.requests.count | request_id:ef2343e3 | 1 | 2026-09-29 14:02:42...lesson.requests.count | request_id:056a91e3 | 1 | 2026-09-29 14:02:42...lesson.requests.count | request_id:f02cdb4b | 1 | 2026-09-29 14:02:42...metric name เดียว, 30 ครั้งที่ส่ง, 30 แถวที่แยกจากกัน ใน dogstatsd-stats — เพราะ dogstatsd-stats track context ของ metric-name-บวก-tag-combination ที่ไม่ซ้ำกัน ซึ่งเป็นหน่วยเดียวกับที่ Datadog คิดเงิน custom metric ตาม ในระบบ production จริงที่ยิง pattern นี้ต่อเนื่อง ทุก request เดี่ยว ๆ สร้าง time series ถาวรที่ไม่เคยถูกใช้ซ้ำ — นี่คือเวอร์ชันจริง ที่ลงมือทำเอง ของ “custom-metrics bill ระเบิด” ที่บท Metrics without Limits ถัดไปจะครอบ
UDP เทียบกับ Unix domain socket (UDS)
หัวข้อที่มีชื่อว่า “UDP เทียบกับ Unix domain socket (UDS)”DogStatsD ฟังทั้งสอง transport พร้อมกันโดย default — พิสูจน์จริง ไฟล์ dsd.socket ของ container เดียวกันมีอยู่แล้วโดยไม่ต้อง config เพิ่ม:
$ docker exec dd-dsd ls -la /var/run/datadog/srwx-w--w- 1 root root 0 ... apm.socketsrwx-w--w- 1 root root 0 ... dsd.socketส่ง datagram อีก 5 ตัวผ่าน Unix socket โดยเฉพาะ (จากข้างในตัว container เอง, AF_UNIX/SOCK_DGRAM connect ไปที่ /var/run/datadog/dsd.socket) แล้วเช็ค agent status อีกครั้งโชว์ว่า Agent track สอง transport เป็น counter แยกกันจริง:
$ docker exec dd-dsd agent status | grep -A2 -B2 "Udp Packets\|Uds Packets" Udp Packets: 74 # ไม่เปลี่ยน -- 5 ตัวนี้ไปทาง UDS ไม่ใช่ UDP Uds Bytes: 140 Uds Packets: 6 # จาก 1 เป็น 6 -- ตรงกับ UDS datagram ใหม่ 5 ตัวเป๊ะUDP ง่ายกว่า (ภาษาไหนก็ใช้ plain socket API ได้ ไม่ต้อง mount/share filesystem path) แต่ connectionless และไม่ authenticate — อะไรก็ตามที่ reach port ได้ ส่ง metric ได้ รวมถึงจาก container หรือ namespace อื่นถ้า dogstatsd_non_local_traffic เปิดอยู่ แบบที่บทนี้เปิดไว้ตั้งใจเพื่อให้เข้าถึง container จาก host ได้เลย Unix domain socket ถูก scope ด้วย filesystem (แค่ process ที่เห็น mount เดียวกันเขียนเข้าไปได้) และเป็นสิ่งที่ทำให้ origin detection เป็นไปได้เลย — ตาม config template จริงของ Agent version นี้เอง doc comment ของ dogstatsd_origin_detection ชัดเจนว่าทำงานได้แค่ “When using Unix Socket” — DogStatsD tag metric ด้วย container metadata (image, container ID) ให้อัตโนมัติได้ แต่ enrichment นั้นไม่มี UDP เทียบเท่าเลย เพราะ UDP packet ไม่มีข้อมูล sender-identity ที่เชื่อถือได้ให้ Agent เอาไปใช้เลย
flowchart LR A[App process] -->|UDP or UDS datagram| B[DogStatsD in Agent] B -->|HISTOGRAM: aggregate per host| C[.avg/.max/.median/.95percentile as GAUGE, .count as RATE] B -->|DISTRIBUTION: send raw values| D[Datadog backend] D -->|aggregate globally| E[Accurate percentiles across all hosts] C --> F[Datadog backend]
Reading AI-generated code
หัวข้อที่มีชื่อว่า “Reading AI-generated code”🐛 หา bug ในโค้ดนี้
# AI comment: "Track checkout latency per request for detailed debugging"import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
def track_checkout(request_id: str, duration_ms: float): tag = f"request_id:{request_id}" sock.sendto(f"checkout.duration:{duration_ms}|h|#{tag}".encode(), ("127.0.0.1", 8125))เฉลย
ดูเป็น instrumentation ที่สมเหตุสมผลและตั้งใจดี — รูปแบบ datagram จริง, type code HISTOGRAM จริง, tag ที่ดูมีประโยชน์สำหรับ “detailed debugging” bug ที่พิสูจน์ด้วย Agent จริงและ 30 submission จริงด้านบน: การ tag metric ด้วย request_id แปลว่าทุกการเรียกสร้าง time-series context ใหม่ถาวร — พิสูจน์ด้านบน dogstatsd-stats โชว์แถวเดียวต่อ tag value ที่ไม่ซ้ำกันพอดี ไม่มีการใช้ซ้ำเลย นี่คือ custom-metrics cardinality explosion เป๊ะ ๆ ที่บทถัดไป (Metrics without Limits) มีไว้ให้กู้คืนได้ทีหลัง แต่วิธีแก้ที่ถูกต้องตรงนี้ไม่ใช่การส่ง tag cardinality สูงเป็น metric tag เลย — ใส่ request_id ใน log line หรือ trace span attribute แทน (ค้นหาได้ ไม่ถูกคิดเงินต่อ unique value แบบที่ indexed metric tag โดน) แล้ว tag metric ด้วยแค่สิ่งที่คุ้มค่าจะ graph และ group ด้วยจริง อย่าง route หรือ provider
Mental Model
หัวข้อที่มีชื่อว่า “Mental Model”ลองนึกภาพ DogStatsD เป็น ช่องรับจดหมายสองช่อง — UDP คือช่องข้างนอกที่ใครเดินผ่านก็โพสต์เข้ามาได้ (เร็ว ไม่เช็ค ID และพิสูจน์ด้านบนว่าต้องเปิด dogstatsd_non_local_traffic ชัดเจนถึงจะรับจดหมายจากนอกตึกได้เลย); Unix socket คือช่องข้างใน ที่คนที่อยู่ในตึกเดียวกันเท่านั้นเข้าถึงได้ (scope ด้วย filesystem) และเพราะพนักงานของตึกเองรับรองได้ว่าใครส่งจดหมายผ่านมัน Agent เลยแปะ sticky note “ส่งโดย container X” ได้ (origin detection) — สิ่งที่ช่องข้างนอกทำไม่ได้เชิง structure เลย พอค่าลงในช่องใดช่องหนึ่งแล้ว HISTOGRAM คือเสมียนที่คำนวณเลขเองตรงนั้นที่โต๊ะเดียว (Agent-side, host เดียว); DISTRIBUTION คือเสมียนที่รวมค่าดิบแล้วส่งต่อไปสำนักงานใหญ่ ที่คำนวณครั้งเดียวด้วยตัวเลขของทุกคนรวมกัน (server-side, global)
Trade-offs
หัวข้อที่มีชื่อว่า “Trade-offs”- UDP เทียบกับ Unix domain socket — UDP ไม่ต้องมี filesystem path ร่วมกันและทำงานจาก container/network namespace ไหนก็ได้ที่ reach port ได้ (แลกกับต้องเปิด
dogstatsd_non_local_trafficพิสูจน์ด้านบน ซึ่งแลก isolation บางส่วนกับความเข้าถึงได้); UDS scope ด้วย filesystem และเปิด origin detection ได้ แต่ทำงานได้แค่เมื่อผู้ส่งกับ Agent share mount กัน (ปกติคือ Pod เดียวกันใน Kubernetes) - HISTOGRAM เทียบกับ DISTRIBUTION — พิสูจน์ด้านบน ทั้งคู่ถูกรับเหมือนกันเป๊ะที่ระดับ wire; HISTOGRAM ถูกกว่า (aggregate ลงเหลือ 5 series โดย Agent ก่อนออกจาก host เลย) แต่แม่นยำแค่ระดับ host เดียว; DISTRIBUTION แพงกว่า (ส่งค่าดิบขึ้นไป) แต่ให้ percentile ระดับ global ที่แม่นยำจริง — ตัวเลือกที่ถูกต้องขึ้นอยู่กับว่า “แม่นยำทั้ง fleet” เป็น requirement หรือแค่ nice-to-have
- Tag แบบกว้างขวาง เทียบกับ tag เพื่อ cardinality — พิสูจน์ด้านบน tag ที่ cardinality สูงตัวเดียว (request_id) ทำให้ metric name เดียวกลายเป็น time series ถาวรที่ถูกคิดเงินไม่จำกัดจำนวน วิธีแก้ที่บทถัดไป (Metrics without Limits) ครอบไว้ให้ส่งแบบกว้างขวางต่อไปได้และคุม cost ที่ชั้น indexing แทน แต่นั่นคือการบรรเทา ไม่ใช่เหตุผลที่จะข้ามการคิดเรื่อง cardinality ตอน instrument
Common Misconceptions
หัวข้อที่มีชื่อว่า “Common Misconceptions”- “DogStatsD ฟังแค่ UDP เท่านั้น” ผิด — พิสูจน์ด้านบน: ไฟล์ Unix socket
dsd.socketของ container เดียวกันมีอยู่โดย default และ datagram จริงที่ส่งผ่านมันถูกนับด้วย counterUds Packetsจริงแยกต่างหากในagent status - “HISTOGRAM กับ DISTRIBUTION เป็นแค่สองชื่อของสิ่งเดียวกัน เลือกตามรสนิยม” ผิด — พิสูจน์ด้านบน (และใน proof ของ check API บทก่อนหน้า): derived metric ห้าตัวของ HISTOGRAM คำนวณ Agent-side ต่อ host; DISTRIBUTION ส่งค่าดิบไปให้ aggregate แบบ global จริง — pipeline ต่างกันเชิง structure ไม่ใช่ความชอบเรื่องชื่อ
- “origin detection ทำงานผ่าน UDP ได้ถ้าตั้ง DD_DOGSTATSD_ORIGIN_DETECTION=true” ผิด — ยกมาตรงจาก config template จริงของ Agent version นี้: origin detection document ไว้ว่าเป็น feature เฉพาะ Unix-socket เท่านั้น ไม่มี UDP เทียบเท่า เพราะ UDP packet ไม่มี sender identity ที่ verify ได้เลย
- “tag ID ที่ไม่ซ้ำกันบน custom metric เป็นแค่ทางเลือก style เล็กน้อย ไม่ใช่ปัญหา cost จริง” ผิด — พิสูจน์ด้านบนด้วย 30 submission จริงที่ให้ time-series context ถาวรแยกกัน 30 ตัวใน stats จริงของ Agent: นี่คือตัวขับเคลื่อน cost ของ custom-metrics ที่วัดได้ตรง ๆ ไม่ใช่แค่สมมติฐาน