Source code for aiostats

# statsd.py
# Based on example from https://github.com/etsy/statsd/

# Key format: apps.<app>.<server>.<app-key>
# e.g.: apps.tetra-app.bacon.request
import random
import asyncio
import logging

from micro import di


log = logging.getLogger(__name__)


class StatsdProtocol(asyncio.DatagramProtocol):

    def __init__(self, prefix):
        self.transport = None
        """ :type : asyncio.DatagramTransport """
        self.prefix = prefix

    def connection_made(self, transport):
        self.transport = transport

    def connection_lost(self, exc):
        self.transport = False

    def send(self, data, sample_rate=1):
        """Squirt the metrics over UDP"""
        sampled_data = {}

        if sample_rate < 1:
            if random.random() <= sample_rate:
                for stat in data.keys():
                    value = data[stat]
                    sampled_data[stat] = '%s|@%s' % (value, sample_rate)
        else:
            sampled_data = data

        try:
            for stat in sampled_data.keys():
                value = data[stat]
                send_data = '%s.%s:%s' % (self.prefix, stat, value)
                self.transport.sendto(send_data.encode('utf-8'))
        except Exception as e:
            log.error('Statsd error %r', e)


[docs]class StatsD(metaclass=di.Component): """Sends statistics to the stats daemon over UDP""" def __init__(self, loop, hostname, host, port, prefix, app_prefix="app"): self.loop = loop self.host = host self.port = port self.prefix = 'apps.%s.%s' % (prefix, hostname) self.protocol = None """ :type protocol: StatsdProtocol """ self.app_prefix = app_prefix @asyncio.coroutine def start(self): log.info('Creating StastD connection to %s:%s with prefix=%s', self.host, self.port, self.prefix) _, self.protocol = yield from self.loop.create_datagram_endpoint(lambda: StatsdProtocol(self.prefix), remote_addr=(self.host, self.port)) def stop(self): if self.protocol: self.protocol.transport.close()
[docs] def timing(self, stats, time, sample_rate=1): """Log timing information >>> # noinspection PyUnresolvedReferences >>> timing('some.time', '500') """ self.update_stats(stats, time, sample_rate, 'ms')
[docs] def increment(self, stats, sample_rate=1): """ Increments one or more stats counters >>> # noinspection PyUnresolvedReferences >>> increment('some.int') >>> # noinspection PyUnresolvedReferences >>> increment('some.int', 0.5) """ self.update_stats(stats, 1, sample_rate)
[docs] def decrement(self, stats, sample_rate=1): """Decrements one or more stats counters >>> # noinspection PyUnresolvedReferences >>> decrement('some.int') """ self.update_stats(stats, -1, sample_rate)
[docs] def gauge(self, stats, value, sample_rate=1): """Sets one or more gauges to a value >>> # noinspection PyUnresolvedReferences >>> gauge('some.int', 'some_value') """ self.update_stats(stats, value, sample_rate, 'g')
[docs] def update_stats(self, stats, delta=1, sample_rate=1, metric='c'): """Updates one or more stats counters by arbitrary amounts >>> # noinspection PyUnresolvedReferences >>> update_stats('some.int', 10) """ if type(stats) is not list: stats = [stats] data = {} for stat in stats: data[stat] = '%s|%s' % (delta, metric) self.protocol.send(data, sample_rate)
@di.has_deps
[docs]class WrappedStatsD: """Sends statistics to the stats daemon over UDP""" delegate = di.Dep(StatsD) def __init__(self, key_pattern): """ :param key_pattern: The key pattern. Must contain {stats} """ self.key = key_pattern pass
[docs] def timing(self, stats, time, sample_rate=1): """Log timing information >>> # noinspection PyUnresolvedReferences >>> timing('some.time', '500') """ self.update_stats(stats, time, sample_rate, 'ms')
[docs] def increment(self, stats, sample_rate=1): """ Increments one or more stats counters >>> # noinspection PyUnresolvedReferences >>> increment('some.int') >>> # noinspection PyUnresolvedReferences >>> increment('some.int', 0.5) """ self.update_stats(stats, 1, sample_rate)
[docs] def update_stats(self, stats, delta=1, sample_rate=1, metric='c'): """Updates one or more stats counters by arbitrary amounts >>> # noinspection PyUnresolvedReferences >>> update_stats('some.int', 10) """ key = self.key.format(stats=stats) if self.delegate: self.delegate.update_stats(key, delta, sample_rate, metric) else: log.debug("Stats: {key} {delta}".format(key=key, delta=delta)) # pragma: no cover