Skip to content

Commit

Permalink
Merge branch 'master' of github.com:jsocol/pystatsd
Browse files Browse the repository at this point in the history
  • Loading branch information
James Socol committed Sep 6, 2012
2 parents d87ede8 + 55e495b commit eb7ad47
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 21 deletions.
4 changes: 2 additions & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@
# built documents.
#
# The short X.Y version.
version = '0.5.0'
version = '1.0'
# The full version, including alpha/beta/rc tags.
release = '0.5.0'
release = '1.0.0'

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
Expand Down
46 changes: 42 additions & 4 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,45 @@
Welcome to Python StatsD's documentation!
=========================================

Contents:
statsd_ is a friendly front-end to Graphite_. This is a Python client for the
statsd daemon.

Quickly, to use::

>>> import statsd
>>> c = statsd.StatsClient('localhost', 8125)
>>> c.incr('foo') # Increment the 'foo' counter.
>>> c.timing('stats.timed', 320) # Record a 320ms 'stats.timed'.

You can also add a prefix to all your stats::

>>> import statsd
>>> c = statsd.StatsClient('localhost', 8125, prefix='foo')
>>> c.incr('bar') # Will be 'foo.bar' in statsd/graphite.


Installing
----------

The easiest way to install statsd is with pip!

You can install from PyPI::

$ pip install statsd

Or GitHub::

$ pip install -e git+https://github.com/jsocol/pystatsd#egg=statsd

Or from source::

$ git clone https://github.com/jsocol/pystatsd
$ cd statsd
$ python setup.py install


Contents
--------

.. toctree::
:maxdepth: 2
Expand All @@ -18,9 +56,9 @@ Contents:


Indices and tables
==================
------------------

* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

.. _statsd: https://github.com/etsy/statsd
.. _Graphite: http://graphite.wikidot.com/
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

setup(
name='statsd',
version='0.5.1',
version='1.0.0',
description='A simple statsd client.',
long_description=open('README.rst').read(),
author='James Socol',
Expand All @@ -13,7 +13,7 @@
include_package_data=True,
package_data={'': ['README.rst']},
classifiers=[
'Development Status :: 4 - Beta',
'Development Status :: 5 - Production/Stable',
'Environment :: Web Environment',
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
Expand Down
2 changes: 1 addition & 1 deletion statsd/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

__all__ = ['StatsClient', 'statsd']

VERSION = (0, 5, 1)
VERSION = (1, 0, 0)
__version__ = '.'.join(map(str, VERSION))


Expand Down
25 changes: 18 additions & 7 deletions statsd/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,13 @@ def __exit__(self, typ, value, tb):
class StatsClient(object):
"""A client for statsd."""

def __init__(self, host='localhost', port=8125, prefix=None):
def __init__(self, host='localhost', port=8125, prefix=None, batch_len=1):
"""Create a new client."""
self._addr = (socket.gethostbyname(host), port)
self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self._prefix = prefix
self._batch_len = batch_len
self._stats = []

def timer(self, stat, rate=1):
return _Timer(self, stat, rate)
Expand All @@ -59,6 +61,17 @@ def gauge(self, stat, value, rate=1):
"""Set a gauge value."""
self._send(stat, '%s|g' % value, rate)

def flush(self):
"""Flush the stats batching buffer."""
if (0 < len(self._stats)):
data = '\n'.join(self._stats)
self._stats = []
try:
self._sock.sendto(data.encode('ascii'), self._addr)
except socket.error:
# No time for love, Dr. Jones!
pass

def _send(self, stat, value, rate=1):
"""Send data to statsd."""
if rate < 1:
Expand All @@ -70,9 +83,7 @@ def _send(self, stat, value, rate=1):
if self._prefix:
stat = '%s.%s' % (self._prefix, stat)

try:
txt = '%s:%s' % (stat, value)
self._sock.sendto(txt.encode('ascii'), self._addr)
except socket.error:
# No time for love, Dr. Jones!
pass
txt = '%s:%s' % (stat, value)
self._stats.append(txt)
if self._batch_len <= len(self._stats):
self.flush()
35 changes: 30 additions & 5 deletions statsd/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,19 @@
ADDR = (socket.gethostbyname('localhost'), 8125)


def _client(prefix=None):
sc = StatsClient(host=ADDR[0], port=ADDR[1], prefix=prefix)
def _client(prefix=None, batch_len=1):
sc = StatsClient(host=ADDR[0], port=ADDR[1], prefix=prefix, batch_len=batch_len)
sc._sock = mock.Mock()
return sc


def _sock_check(cl, count, val):
val = val.encode('ascii')
eq_(cl._sock.sendto.call_count, count)
eq_(cl._sock.sendto.call_args, ((val, ADDR), {}))

if val:
val = val.encode('ascii')
eq_(cl._sock.sendto.call_args, ((val, ADDR), {}))
else:
eq_(cl._sock.sendto.call_args, None)

@mock.patch.object(random, 'random', lambda: -1)
def test_incr():
Expand Down Expand Up @@ -85,6 +87,29 @@ def test_timing():
_sock_check(sc, 3, 'foo:100|ms|@0.5')


@mock.patch.object(random, 'random', lambda: -1)
def test_batch():
sc = _client(None, 2)

sc.incr('foo')
_sock_check(sc, 0, '')

sc.incr('bar')
_sock_check(sc, 1, 'foo:1|c\nbar:1|c')

@mock.patch.object(random, 'random', lambda: -1)
def test_batch_flush():
sc = _client(None, 10)

sc.incr('foo')
_sock_check(sc, 0, '')

sc.incr('bar')
_sock_check(sc, 0, '')

sc.flush()
_sock_check(sc, 1, 'foo:1|c\nbar:1|c')

def test_prefix():
sc = _client('foo')

Expand Down

0 comments on commit eb7ad47

Please sign in to comment.