..

asyncstatsd an asynchronous statsd client library

I’ve just released asyncstatsd, a python statsd client library, fully asynchronous and based on asyncio. It’s a simple library that allows to send metrics to statsd server in a non-blocking way. For years, I was ok using jsocol/pystatsd: A Python client for statsd, but it does not support DataDog tagged metrics as their docs state:

Tagged metrics—such as those used by Datadog and Telegraf—are explicitly outside the scope of this library. Alternatives exist and are recommended. This document lays out the reasons to avoid support for tags. ~ [^1]

Unfortunately, author of alternative implementation decided to delete github repository. PyPI package is still available, but I did not want to rely on something that is not maintained anymore.

There is library provided by DataDog, but it’s not asynchronous by default. Though it has support for UDP transport nevertheless it forces you to install requests, a synchronous HTTP client library. It uses thread, locks and queues - in general it’s too bloated for my taste.

Anyway, I’ve decided to write my own implementation. It’s targeted asyncio python and for now support only UDP transport via asyncio.DatagramProtocol. Pure statsd implementation is based on statsd specification and supports all basic metric types:

import asyncio
from asyncstatsd.client import StatsdClient


async def bar(statsd):
   statsd.incr('some.counter')
   statsd.timing('some.timer', 320)
   with statsd.timer('some.timer'):
       await asyncio.sleep(1)

async def main():
    client = StatsdClient('localhost', 8125)
    await client.connect()
    foo(client)
    await bar(client)

If you need to send tagged metrics to DataDog, you can use DatadogClient. Implementation is informed by Getting Started with Tags

import asyncio
from asyncstatsd.client import DatadogClient


def foo(statsd):
    statsd.incr('some.counter', tags=dict(tag1='value1', tag2='value2'))
    statsd.timing('some.timer', 320, tags=dict(tag1='value1', tag2='value2'))


async def bar(statsd):
   statsd.incr('some.counter', tags=dict(tag1='value1', tag2='value2'))
   statsd.timing('some.timer', 320, tags=dict(tag1='value1', tag2='value2')
   with statsd.timer('some.timer', tags=dict(tag1='value1', tag2='value2'):
       await asyncio.sleep(1)


async def main():
    client = DatadogClient('localhost', 8125)
    await client.connect()
    foo(client)
    await bar(client)

For now DatadogClient supports only pure statsd metric types with tags. I’m planning to add support for DataDog specific metrics like histogram and distribution in the future. Whole idea was to keep it simple and easy to extend so adding histogram support should be as easy as:

@dataclass
class DatadogHistogramMetric(StatsdMetric):
    value: float
    unit: str = "h"

class DatadogClient(AbstractStatsdClient, StatsdClientBase):
    ...
    def histogram(self, stat, value, rate=1, *, tags: Dict = {}):
        self.send(DatadogHistogramMetric(stat, value, rate=rate, tags=tags))

Check it out on github: b1r3k/asyncstatsd: easy extendable asyncio-based client for sending metric to StatsD

References

  1. https://statsd.readthedocs.io/en/stable/tags.html