#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import print_function, absolute_import, division
__docformat__ = "restructuredtext en"
#: The statsd metric kind for Gauges
OBSERVATION_KIND_GAUGE = 'g'
#: The statsd metric kind for Counters
OBSERVATION_KIND_COUNTER = 'c'
#: The statsd metric kind for Sets
OBSERVATION_KIND_SET = 's'
#: The statsd metric kind for Timers
OBSERVATION_KIND_TIMER = 'ms'
def _parse_sampling_data(data):
"""
Parses sampling rate from the provided packet part *data*.
Raises a `ValueError` if the packet part is invalid sampling data
"""
if not data.startswith('@'):
raise ValueError('Expected "@" in sampling data. %s' % data)
return float(data[1:])
def _as_metric(metric_data):
"""
Parses a single metric packet, *metric_data*, in to a `Metric`.
Metrics take the form of <name>:<value>|<type>(|@<sampling_rate>)
A `ValueError` is raised for invalid data
"""
sampling = None
name = None
value = None
kind = None
parts = metric_data.split('|')
if len(parts) < 2 or len(parts) > 3:
raise ValueError('Unexpected metric data %s. Wrong number of parts' % metric_data)
if len(parts) == 3:
sampling_data = parts.pop(-1)
sampling = _parse_sampling_data(sampling_data)
kind = parts[1]
name, value = parts[0].split(':')
return Observation(name, value, kind, sampling_rate=sampling)
def _as_metrics(data):
"""
Parses the statsd *data* packet to a _list_ of metrics.
.. seealso:: https://github.com/etsy/statsd/blob/master/docs/metric_types.md
"""
metrics = []
# Multi metric packets are seperated by newlines
for metric_data in data.split('\n'):
metrics.append(_as_metric(metric_data))
return metrics
[docs]class Observation(object):
"""
The representation of a single statsd metric.
"""
#: The metric name
name = None
#: The value provided for the metric
value = None
#: The statsd code for the type of metric. e.g. one of the ``METRIC_*_KIND`` constants
kind = None
#: The rate with which this event has been sampled from (optional)
sampling_rate = None
def __init__(self, name, value, kind, sampling_rate=None):
self.name = name
self.value = value
self.sampling_rate = sampling_rate
self.kind = kind
[docs] @classmethod
def make(cls, packet):
"""
Creates a metric from the provided statsd *packet*.
:raises ValueError: if *packet* is a multi metric packet or
otherwise invalid.
"""
metrics = cls.make_all(packet)
if len(metrics) != 1:
raise ValueError('Must supply a single metric packet. %s supplied' % packet)
return metrics[0]
[docs] @classmethod
def make_all(cls, packet):
"""
Makes a list of metrics from the provided statsd *packet*.
Like `make` but supports multi metric packets
"""
return _as_metrics(packet)
def __str__(self):
sampling_string = '|@%g' % self.sampling_rate if self.sampling_rate is not None else ''
return '%s:%s|%s%s' % (self.name, self.value, self.kind, sampling_string)
def __repr__(self):
return "%s(name=%r, value=%r, kind=%r, sampling_rate=%r)" % (
self.__class__.__name__,
self.name, self.value, self.kind, self.sampling_rate
)