Compare commits
No commits in common. "master" and "debian" have entirely different histories.
|
@ -1,5 +0,0 @@
|
|||
*.pyc
|
||||
*.egg-info
|
||||
.coverage
|
||||
build
|
||||
|
203
LICENSE
203
LICENSE
|
@ -1,203 +0,0 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
98
README.md
98
README.md
|
@ -1,98 +0,0 @@
|
|||
|
||||
gstatsd - A statsd service implementation in Python + gevent.
|
||||
|
||||
If you are unfamiliar with statsd, you can read [why statsd exists][etsy post],
|
||||
or look at the [NodeJS statsd implementation][etsy repo].
|
||||
|
||||
License: [Apache 2.0][license]
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
* [Python][python] - I'm testing on 2.6/2.7 at the moment.
|
||||
* [gevent][gevent] - A libevent wrapper.
|
||||
* [distribute][distribute] - (or setuptools) for builds.
|
||||
|
||||
|
||||
Using gstatsd
|
||||
-------------
|
||||
|
||||
Show gstatsd help:
|
||||
|
||||
% gstatsd -h
|
||||
|
||||
Options:
|
||||
|
||||
Usage: gstatsd [options]
|
||||
|
||||
A statsd service in Python + gevent.
|
||||
|
||||
Options:
|
||||
--version show program's version number and exit
|
||||
-b BIND_ADDR, --bind=BIND_ADDR
|
||||
bind [host]:port (host defaults to '')
|
||||
-s SINK, --sink=SINK a graphite service to which stats are sent
|
||||
([host]:port).
|
||||
-v increase verbosity (currently used for debugging)
|
||||
-f INTERVAL, --flush=INTERVAL
|
||||
flush interval, in seconds (default 10)
|
||||
-p PERCENT, --percent=PERCENT
|
||||
percent threshold (default 90)
|
||||
-D, --daemonize daemonize the service
|
||||
-h, --help
|
||||
|
||||
Start gstatsd listening on the default port 8125, and send stats to graphite
|
||||
server on port 2003 every 5 seconds:
|
||||
|
||||
% gstatsd -s 2003 -f 5
|
||||
|
||||
Bind listener to host 'foo' port 8126, and send stats to the Graphite server
|
||||
on host 'bar' port 2003 every 20 seconds:
|
||||
|
||||
% gstatsd -b foo:8126 -s bar:2003 -f 20
|
||||
|
||||
To send the stats to multiple graphite servers, specify '-s' multiple times:
|
||||
|
||||
% gstatsd -b :8125 -s stats1:2003 -s stats2:2004
|
||||
|
||||
|
||||
Using the client
|
||||
----------------
|
||||
|
||||
The code example below demonstrates using the low-level client interface:
|
||||
|
||||
from gstatsd import client
|
||||
|
||||
# location of the statsd server
|
||||
hostport = ('', 8125)
|
||||
|
||||
raw = client.StatsClient(hostport)
|
||||
|
||||
# add 1 to the 'foo' bucket
|
||||
raw.increment('foo')
|
||||
|
||||
# timer 'bar' took 25ms to complete
|
||||
raw.timer('bar', 25)
|
||||
|
||||
|
||||
You may prefer to use the stateful client:
|
||||
|
||||
# wraps the raw client
|
||||
cli = client.Stats(raw)
|
||||
|
||||
timer = cli.get_timer('foo')
|
||||
timer.start()
|
||||
|
||||
... do some work ..
|
||||
|
||||
# when .stop() is called, the stat is sent to the server
|
||||
timer.stop()
|
||||
|
||||
|
||||
[python]: http://www.python.org/
|
||||
[gevent]: http://www.gevent.org/
|
||||
[license]: http://www.apache.org/licenses/LICENSE-2.0
|
||||
[distribute]: http://pypi.python.org/pypi/distribute
|
||||
[etsy repo]: https://github.com/etsy/statsd
|
||||
[etsy post]: http://codeascraft.etsy.com/2011/02/15/measure-anything-measure-everything/
|
||||
|
109
README.txt
109
README.txt
|
@ -1,109 +0,0 @@
|
|||
gstatsd - A statsd service implementation in Python + gevent.
|
||||
|
||||
If you are unfamiliar with statsd, you can read
|
||||
`why statsd exists <http://codeascraft.etsy.com/2011/02/15/measure-anything-measure-everything/>`_,
|
||||
or look at the
|
||||
`NodeJS statsd implementation <https://github.com/etsy/statsd>`_.
|
||||
|
||||
License:
|
||||
`Apache 2.0 <http://www.apache.org/licenses/LICENSE-2.0>`_
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
|
||||
- `Python <http://www.python.org/>`_ - I'm testing on 2.6/2.7 at
|
||||
the moment.
|
||||
- `gevent <http://www.gevent.org/>`_ - A libevent wrapper.
|
||||
- `distribute <http://pypi.python.org/pypi/distribute>`_ - (or
|
||||
setuptools) for builds.
|
||||
|
||||
Using gstatsd
|
||||
-------------
|
||||
|
||||
Show gstatsd help:
|
||||
|
||||
::
|
||||
|
||||
% gstatsd -h
|
||||
|
||||
Options:
|
||||
|
||||
::
|
||||
|
||||
Usage: gstatsd [options]
|
||||
|
||||
A statsd service in Python + gevent.
|
||||
|
||||
Options:
|
||||
--version show program's version number and exit
|
||||
-b BIND_ADDR, --bind=BIND_ADDR
|
||||
bind [host]:port (host defaults to '')
|
||||
-s SINK, --sink=SINK a graphite service to which stats are sent
|
||||
([host]:port).
|
||||
-v increase verbosity (currently used for debugging)
|
||||
-f INTERVAL, --flush=INTERVAL
|
||||
flush interval, in seconds (default 10)
|
||||
-p PERCENT, --percent=PERCENT
|
||||
percent threshold (default 90)
|
||||
-D, --daemonize daemonize the service
|
||||
-h, --help
|
||||
|
||||
Start gstatsd listening on the default port 8125, and send stats to
|
||||
graphite server on port 2003 every 5 seconds:
|
||||
|
||||
::
|
||||
|
||||
% gstatsd -s 2003 -f 5
|
||||
|
||||
Bind listener to host 'foo' port 8126, and send stats to the
|
||||
Graphite server on host 'bar' port 2003 every 20 seconds:
|
||||
|
||||
::
|
||||
|
||||
% gstatsd -b foo:8126 -s bar:2003 -f 20
|
||||
|
||||
To send the stats to multiple graphite servers, specify '-s'
|
||||
multiple times:
|
||||
|
||||
::
|
||||
|
||||
% gstatsd -b :8125 -s stats1:2003 -s stats2:2004
|
||||
|
||||
Using the client
|
||||
----------------
|
||||
|
||||
The code example below demonstrates using the low-level client
|
||||
interface:
|
||||
|
||||
::
|
||||
|
||||
from gstatsd import client
|
||||
|
||||
# location of the statsd server
|
||||
hostport = ('', 8125)
|
||||
|
||||
raw = client.StatsClient(hostport)
|
||||
|
||||
# add 1 to the 'foo' bucket
|
||||
raw.increment('foo')
|
||||
|
||||
# timer 'bar' took 25ms to complete
|
||||
raw.timer('bar', 25)
|
||||
|
||||
You may prefer to use the stateful client:
|
||||
|
||||
::
|
||||
|
||||
# wraps the raw client
|
||||
cli = client.Stats(raw)
|
||||
|
||||
timer = cli.get_timer('foo')
|
||||
timer.start()
|
||||
|
||||
... do some work ..
|
||||
|
||||
# when .stop() is called, the stat is sent to the server
|
||||
timer.stop()
|
||||
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
/etc/init.d/gstatsd
|
||||
/etc/default/gstatsd
|
|
@ -0,0 +1,11 @@
|
|||
# Flush buffers every 10 seconds
|
||||
FLUSH=10
|
||||
|
||||
# Percent threshold
|
||||
PERCENT_THRESHOLD=90
|
||||
|
||||
# Default Graphite receiver
|
||||
SINK=":2003"
|
||||
|
||||
# Daemon arguments
|
||||
# DAEMON_ARGS=${DAEMON_ARGS:-"-f $FLUSH -s $SINK -p $PERCENT_THRESHOLD"}
|
|
@ -0,0 +1,141 @@
|
|||
#!/bin/sh
|
||||
### BEGIN INIT INFO
|
||||
# Provides: gstatsd
|
||||
# Required-Start: $network $local_fs
|
||||
# Required-Stop:
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: StatsD daemon using gevent
|
||||
### END INIT INFO
|
||||
|
||||
# Author: Benjamin Dauvergne <bdauvergne@entrouvert.com>
|
||||
|
||||
PATH=/sbin:/usr/sbin:/bin:/usr/bin
|
||||
DESC=GStatsD
|
||||
NAME=gstatsd
|
||||
DAEMON=/usr/bin/gstatsd
|
||||
RUN_DIR=/var/run/$NAME
|
||||
PIDFILE=$RUN_DIR/$NAME.pid
|
||||
LOG_DIR=/var/log/$NAME
|
||||
SCRIPTNAME=/etc/init.d/$NAME
|
||||
MANAGE_SCRIPT=/usr/lib/$NAME/manage.py
|
||||
FLUSH=10
|
||||
PERCENT_THRESHOLD=90
|
||||
|
||||
USER=$NAME
|
||||
GROUP=$NAME
|
||||
|
||||
# Exit if the package is not installed
|
||||
[ -x $DAEMON ] || exit 0
|
||||
|
||||
# Read configuration variable file if it is present
|
||||
[ -r /etc/default/$NAME ] && . /etc/default/$NAME
|
||||
|
||||
# Load the VERBOSE setting and other rcS variables
|
||||
. /lib/init/vars.sh
|
||||
|
||||
# Define LSB log_* functions.
|
||||
# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
|
||||
. /lib/lsb/init-functions
|
||||
|
||||
# Verify that a sink has been defined
|
||||
[ -z "$SINK" ] && log_warning_msg "SINK is not defined in /etc/default/$NAME" && exit 0
|
||||
|
||||
# Build daemon arguments
|
||||
DAEMON_ARGS=${DAEMON_ARGS:-"-f $FLUSH -s $SINK -p $PERCENT_THRESHOLD"}
|
||||
|
||||
# Create /run directory
|
||||
if [ ! -d $RUN_DIR ]; then
|
||||
install -d -m 755 -o $USER -g $GROUP $RUN_DIR
|
||||
fi
|
||||
|
||||
#
|
||||
# Function that starts the daemon/service
|
||||
#
|
||||
do_start()
|
||||
{
|
||||
# Return
|
||||
# 0 if daemon has been started
|
||||
# 1 if daemon was already running
|
||||
# 2 if daemon could not be started
|
||||
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
|
||||
|| return 1
|
||||
start-stop-daemon --start --quiet --pidfile $PIDFILE --background --make-pidfile --chuid $USER:$GROUP --exec $DAEMON -- \
|
||||
$DAEMON_ARGS \
|
||||
|| return 2
|
||||
}
|
||||
|
||||
#
|
||||
# Function that stops the daemon/service
|
||||
#
|
||||
do_stop()
|
||||
{
|
||||
# Return
|
||||
# 0 if daemon has been stopped
|
||||
# 1 if daemon was already stopped
|
||||
# 2 if daemon could not be stopped
|
||||
# other if a failure occurred
|
||||
start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE
|
||||
RETVAL="$?"
|
||||
[ "$RETVAL" = 2 ] && return 2
|
||||
# Wait for children to finish too if this is a daemon that forks
|
||||
# and if the daemon is only ever run from this initscript.
|
||||
# If the above conditions are not satisfied then add some other code
|
||||
# that waits for the process to drop all resources that could be
|
||||
# needed by services started subsequently. A last resort is to
|
||||
# sleep for some time.
|
||||
start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
|
||||
[ "$?" = 2 ] && return 2
|
||||
# Many daemons don't delete their pidfiles when they exit.
|
||||
rm -f $PIDFILE
|
||||
return "$RETVAL"
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
log_daemon_msg "Starting $DESC " "$NAME"
|
||||
do_start
|
||||
case "$?" in
|
||||
0|1) log_end_msg 0 ;;
|
||||
2) log_end_msg 1 ;;
|
||||
esac
|
||||
;;
|
||||
stop)
|
||||
log_daemon_msg "Stopping $DESC" "$NAME"
|
||||
do_stop
|
||||
case "$?" in
|
||||
0|1) log_end_msg 0 ;;
|
||||
2) log_end_msg 1 ;;
|
||||
esac
|
||||
;;
|
||||
status)
|
||||
status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
|
||||
;;
|
||||
restart|force-reload)
|
||||
#
|
||||
# If the "reload" option is implemented then remove the
|
||||
# 'force-reload' alias
|
||||
#
|
||||
log_daemon_msg "Restarting $DESC" "$NAME"
|
||||
do_stop
|
||||
case "$?" in
|
||||
0|1)
|
||||
do_start
|
||||
case "$?" in
|
||||
0) log_end_msg 0 ;;
|
||||
1) log_end_msg 1 ;; # Old process is still running
|
||||
*) log_end_msg 1 ;; # Failed to start
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
# Failed to stop
|
||||
log_end_msg 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
|
||||
exit 3
|
||||
;;
|
||||
esac
|
||||
|
|
@ -0,0 +1 @@
|
|||
debian/etc/default/gstatsd /etc/default
|
|
@ -0,0 +1,27 @@
|
|||
#! /bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
case "$1" in
|
||||
configure)
|
||||
if ! getent passwd gstatsd >/dev/null; then
|
||||
adduser --disabled-password --quiet --system \
|
||||
--no-create-home --home / \
|
||||
--gecos "gStatsD software user" --group gstatsd
|
||||
fi
|
||||
;;
|
||||
reconfigure)
|
||||
;;
|
||||
|
||||
abort-upgrade|abort-remove|abort-deconfigure)
|
||||
;;
|
||||
*)
|
||||
echo "postinst called with unknown argument \`$1'" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
#DEBHELPER#
|
||||
|
||||
exit 0
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
|
||||
from core import __version__
|
||||
|
||||
|
|
@ -1,118 +0,0 @@
|
|||
|
||||
|
||||
# standard
|
||||
import random
|
||||
import socket
|
||||
import time
|
||||
|
||||
|
||||
E_NOSTART = 'you must call start() before stop(). ignoring.'
|
||||
|
||||
|
||||
def _format_float(val):
|
||||
return ('%f' % val).rstrip('0').rstrip('.')
|
||||
|
||||
|
||||
class StatsClient(object):
|
||||
|
||||
"Simple client to exercise the statsd server."
|
||||
|
||||
HOSTPORT = ('', 8125)
|
||||
|
||||
def __init__(self, hostport=None):
|
||||
if hostport is None:
|
||||
hostport = StatsClient.HOSTPORT
|
||||
self._hostport = hostport
|
||||
self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
|
||||
def timer(self, key, timestamp, sample_rate=1):
|
||||
self._send('%s:%d|ms' % (key, round(timestamp)), sample_rate)
|
||||
|
||||
def gauge(self, key, value, sample_rate=1):
|
||||
self._send('%s:%s|g' % (key, _format_float(value)), sample_rate)
|
||||
|
||||
def increment(self, key, sample_rate=1):
|
||||
return self.counter(key, 1, sample_rate)
|
||||
|
||||
def decrement(self, key, sample_rate=1):
|
||||
return self.counter(key, -1, sample_rate)
|
||||
|
||||
def counter(self, keys, magnitude=1, sample_rate=1):
|
||||
if not isinstance(keys, (list, tuple)):
|
||||
keys = [keys]
|
||||
for key in keys:
|
||||
self._send('%s:%d|c' % (key, round(magnitude)), sample_rate)
|
||||
|
||||
def _send(self, data, sample_rate=1):
|
||||
packet = None
|
||||
if sample_rate < 1.0:
|
||||
if random.random() < sample_rate:
|
||||
packet = data + '|@%s' % sample_rate
|
||||
else:
|
||||
packet = data
|
||||
if packet:
|
||||
self._sock.sendto(packet, self._hostport)
|
||||
|
||||
|
||||
class StatsCounter(object):
|
||||
|
||||
def __init__(self, client, key, sample_rate=1):
|
||||
self._client = client
|
||||
self._key = key
|
||||
self._sample_rate = sample_rate
|
||||
|
||||
def increment(self):
|
||||
self._client.increment(self._key, self._sample_rate)
|
||||
|
||||
def decrement(self):
|
||||
self._client.decrement(self._key, self._sample_rate)
|
||||
|
||||
def add(self, val):
|
||||
self._client.counter(self._key, val, self._sample_rate)
|
||||
|
||||
|
||||
class StatsTimer(object):
|
||||
|
||||
def __init__(self, client, key):
|
||||
self._client = client
|
||||
self._key = key
|
||||
self._started = 0
|
||||
self._timestamp = 0
|
||||
|
||||
def start(self):
|
||||
self._started = 1
|
||||
self._timestamp = time.time()
|
||||
|
||||
def stop(self):
|
||||
if not self._started:
|
||||
raise UserWarning(E_NOSTART)
|
||||
return
|
||||
elapsed = time.time() - self._timestamp
|
||||
self._client.timer(self._key, int(elapsed * 1000.0))
|
||||
self._started = 0
|
||||
|
||||
|
||||
class StatsGauge(object):
|
||||
|
||||
def __init__(self, client, key, sample_rate=1):
|
||||
self._client = client
|
||||
self._key = key
|
||||
self._sample_rate = sample_rate
|
||||
|
||||
def set(self, value):
|
||||
self._client.gauge(self._key, value, self._sample_rate)
|
||||
|
||||
|
||||
class Stats(object):
|
||||
|
||||
def __init__(self, client):
|
||||
self._client = client
|
||||
|
||||
def get_counter(self, key, sample_rate=1):
|
||||
return StatsCounter(self._client, key, sample_rate)
|
||||
|
||||
def get_timer(self, key):
|
||||
return StatsTimer(self._client, key)
|
||||
|
||||
def get_gauge(self, key):
|
||||
return StatsGauge(self._client, key)
|
|
@ -1,96 +0,0 @@
|
|||
|
||||
# standard
|
||||
import unittest
|
||||
|
||||
# local
|
||||
from gstatsd import client
|
||||
|
||||
|
||||
class StatsDummyClient(client.StatsClient):
|
||||
|
||||
def __init__(self, hostport=None):
|
||||
client.StatsClient.__init__(self, hostport)
|
||||
self.packets = []
|
||||
|
||||
def _send(self, data, sample_rate=1):
|
||||
self.packets.append((data, sample_rate))
|
||||
|
||||
|
||||
class StatsClientTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self._cli = StatsDummyClient()
|
||||
|
||||
def test_timer(self):
|
||||
self._cli.timer('foo', 15, 1)
|
||||
self.assertEquals(self._cli.packets[-1], ('foo:15|ms', 1))
|
||||
self._cli.timer('bar.baz', 1.35, 1)
|
||||
self.assertEquals(self._cli.packets[-1], ('bar.baz:1|ms', 1))
|
||||
self._cli.timer('x', 1.99, 1)
|
||||
self.assertEquals(self._cli.packets[-1], ('x:2|ms', 1))
|
||||
self._cli.timer('x', 1, 0.5)
|
||||
self.assertEquals(self._cli.packets[-1], ('x:1|ms', 0.5))
|
||||
|
||||
def test_increment(self):
|
||||
self._cli.increment('foo')
|
||||
self.assertEquals(self._cli.packets[-1], ('foo:1|c', 1))
|
||||
self._cli.increment('x', 0.5)
|
||||
self.assertEquals(self._cli.packets[-1], ('x:1|c', 0.5))
|
||||
|
||||
def test_decrement(self):
|
||||
self._cli.decrement('foo')
|
||||
self.assertEquals(self._cli.packets[-1], ('foo:-1|c', 1))
|
||||
self._cli.decrement('x', 0.2)
|
||||
self.assertEquals(self._cli.packets[-1], ('x:-1|c', 0.2))
|
||||
|
||||
def test_counter(self):
|
||||
self._cli.counter('foo', 5)
|
||||
self.assertEquals(self._cli.packets[-1], ('foo:5|c', 1))
|
||||
self._cli.counter('foo', -50)
|
||||
self.assertEquals(self._cli.packets[-1], ('foo:-50|c', 1))
|
||||
self._cli.counter('foo', 5.9)
|
||||
self.assertEquals(self._cli.packets[-1], ('foo:6|c', 1))
|
||||
self._cli.counter('foo', 1, 0.2)
|
||||
self.assertEquals(self._cli.packets[-1], ('foo:1|c', 0.2))
|
||||
|
||||
def test_gauge(self):
|
||||
self._cli.gauge('foo', 5)
|
||||
self.assertEquals(self._cli.packets[-1], ('foo:5|g', 1))
|
||||
self._cli.gauge('foo', -50)
|
||||
self.assertEquals(self._cli.packets[-1], ('foo:-50|g', 1))
|
||||
self._cli.gauge('foo', 5.9)
|
||||
self.assertEquals(self._cli.packets[-1], ('foo:5.9|g', 1))
|
||||
|
||||
|
||||
class StatsTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self._cli = StatsDummyClient()
|
||||
self._stat = client.Stats(self._cli)
|
||||
|
||||
def test_timer(self):
|
||||
timer = self._stat.get_timer('foo')
|
||||
timer.start()
|
||||
timer.stop()
|
||||
data, sr = self._cli.packets[-1]
|
||||
pkt = data.split(':')
|
||||
self.assertEquals(pkt[0], 'foo')
|
||||
|
||||
# ensure warning is raised for mismatched start/stop
|
||||
timer = self._stat.get_timer('foo')
|
||||
self.assertRaises(UserWarning, timer.stop)
|
||||
|
||||
def test_counter(self):
|
||||
count = self._stat.get_counter('foo')
|
||||
count.increment()
|
||||
count.decrement()
|
||||
count.add(5)
|
||||
self.assertEquals(self._cli.packets[-1], ('foo:5|c', 1))
|
||||
|
||||
|
||||
def main():
|
||||
unittest.main()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,3 +0,0 @@
|
|||
|
||||
__version__ = '0.6'
|
||||
|
|
@ -1,262 +0,0 @@
|
|||
|
||||
# standard
|
||||
import cStringIO
|
||||
import optparse
|
||||
import os
|
||||
import resource
|
||||
import signal
|
||||
import string
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
from collections import defaultdict
|
||||
|
||||
# local
|
||||
import sink
|
||||
from core import __version__
|
||||
|
||||
# vendor
|
||||
import gevent, gevent.socket
|
||||
socket = gevent.socket
|
||||
# protect stats
|
||||
from gevent.thread import allocate_lock as Lock
|
||||
stats_lock = Lock()
|
||||
|
||||
# constants
|
||||
INTERVAL = 10.0
|
||||
PERCENT = 90.0
|
||||
MAX_PACKET = 2048
|
||||
|
||||
DESCRIPTION = '''
|
||||
A statsd service in Python + gevent.
|
||||
'''
|
||||
EPILOG = '''
|
||||
|
||||
'''
|
||||
|
||||
# table to remove invalid characters from keys
|
||||
ALL_ASCII = set(chr(c) for c in range(256))
|
||||
KEY_VALID = string.ascii_letters + string.digits + '_-.'
|
||||
KEY_TABLE = string.maketrans(KEY_VALID + '/', KEY_VALID + '_')
|
||||
KEY_DELETIONS = ''.join(ALL_ASCII.difference(KEY_VALID + '/'))
|
||||
|
||||
# error messages
|
||||
E_BADADDR = 'invalid bind address specified %r'
|
||||
E_NOSINKS = 'you must specify at least one stats sink'
|
||||
|
||||
|
||||
class Stats(object):
|
||||
|
||||
def __init__(self):
|
||||
self.timers = defaultdict(list)
|
||||
self.counts = defaultdict(float)
|
||||
self.gauges = defaultdict(float)
|
||||
self.percent = PERCENT
|
||||
self.interval = INTERVAL
|
||||
|
||||
|
||||
def daemonize(umask=0027):
|
||||
if gevent.fork():
|
||||
os._exit(0)
|
||||
os.setsid()
|
||||
if gevent.fork():
|
||||
os._exit(0)
|
||||
os.umask(umask)
|
||||
fd_limit = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
|
||||
if fd_limit == resource.RLIM_INFINITY:
|
||||
fd_limit = 1024
|
||||
for fd in xrange(0, fd_limit):
|
||||
try:
|
||||
os.close(fd)
|
||||
except:
|
||||
pass
|
||||
os.open(os.devnull, os.O_RDWR)
|
||||
os.dup2(0, 1)
|
||||
os.dup2(0, 2)
|
||||
gevent.reinit()
|
||||
|
||||
|
||||
def parse_addr(text):
|
||||
"Parse a 1- to 3-part address spec."
|
||||
if text:
|
||||
parts = text.split(':')
|
||||
length = len(parts)
|
||||
if length== 3:
|
||||
return parts[0], parts[1], int(parts[2])
|
||||
elif length == 2:
|
||||
return None, parts[0], int(parts[1])
|
||||
elif length == 1:
|
||||
return None, '', int(parts[0])
|
||||
return None, None, None
|
||||
|
||||
|
||||
class StatsDaemon(object):
|
||||
|
||||
"""
|
||||
A statsd service implementation in Python + gevent.
|
||||
"""
|
||||
|
||||
def __init__(self, bindaddr, sinkspecs, interval, percent, debug=0,
|
||||
key_prefix=''):
|
||||
_, host, port = parse_addr(bindaddr)
|
||||
if port is None:
|
||||
self.exit(E_BADADDR % bindaddr)
|
||||
self._bindaddr = (host, port)
|
||||
|
||||
# TODO: generalize to support more than one sink type. currently
|
||||
# only the graphite backend is present, but we may want to write
|
||||
# stats to hbase, redis, etc. - ph
|
||||
|
||||
# construct the sink and add hosts to it
|
||||
if not sinkspecs:
|
||||
self.exit(E_NOSINKS)
|
||||
self._sink = sink.GraphiteSink()
|
||||
errors = []
|
||||
for spec in sinkspecs:
|
||||
try:
|
||||
self._sink.add(spec)
|
||||
except ValueError, ex:
|
||||
errors.append(ex)
|
||||
if errors:
|
||||
for err in errors:
|
||||
self.error(str(err))
|
||||
self.exit('exiting.')
|
||||
|
||||
self._percent = float(percent)
|
||||
self._interval = float(interval)
|
||||
self._debug = debug
|
||||
self._sock = None
|
||||
self._flush_task = None
|
||||
self._key_prefix = key_prefix
|
||||
|
||||
self._reset_stats()
|
||||
|
||||
def _reset_stats(self):
|
||||
with stats_lock:
|
||||
self._stats = Stats()
|
||||
self._stats.percent = self._percent
|
||||
self._stats.interval = self._interval
|
||||
|
||||
def exit(self, msg, code=1):
|
||||
self.error(msg)
|
||||
sys.exit(code)
|
||||
|
||||
def error(self, msg):
|
||||
sys.stderr.write(msg + '\n')
|
||||
|
||||
def start(self):
|
||||
"Start the service"
|
||||
# register signals
|
||||
gevent.signal(signal.SIGINT, self._shutdown)
|
||||
|
||||
# spawn the flush trigger
|
||||
def _flush_impl():
|
||||
while 1:
|
||||
gevent.sleep(self._stats.interval)
|
||||
|
||||
# rotate stats
|
||||
stats = self._stats
|
||||
self._reset_stats()
|
||||
|
||||
# send the stats to the sink which in turn broadcasts
|
||||
# the stats packet to one or more hosts.
|
||||
try:
|
||||
self._sink.send(stats)
|
||||
except Exception, ex:
|
||||
trace = traceback.format_tb(sys.exc_info()[-1])
|
||||
self.error(''.join(trace))
|
||||
|
||||
self._flush_task = gevent.spawn(_flush_impl)
|
||||
|
||||
# start accepting connections
|
||||
self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM,
|
||||
socket.IPPROTO_UDP)
|
||||
self._sock.bind(self._bindaddr)
|
||||
while 1:
|
||||
try:
|
||||
data, _ = self._sock.recvfrom(MAX_PACKET)
|
||||
for p in data.split('\n'):
|
||||
if p:
|
||||
self._process(p)
|
||||
except Exception, ex:
|
||||
self.error(str(ex))
|
||||
|
||||
def _shutdown(self):
|
||||
"Shutdown the server"
|
||||
self.exit("service exiting", code=0)
|
||||
|
||||
def _process(self, data):
|
||||
"Process a single packet and update the internal tables."
|
||||
parts = data.split(':')
|
||||
if self._debug:
|
||||
self.error('packet: %r' % data)
|
||||
if not parts:
|
||||
return
|
||||
|
||||
# interpret the packet and update stats
|
||||
stats = self._stats
|
||||
key = parts[0].translate(KEY_TABLE, KEY_DELETIONS)
|
||||
if self._key_prefix:
|
||||
key = '.'.join([self._key_prefix, key])
|
||||
for part in parts[1:]:
|
||||
srate = 1.0
|
||||
fields = part.split('|')
|
||||
length = len(fields)
|
||||
if length < 2:
|
||||
continue
|
||||
value = fields[0]
|
||||
stype = fields[1].strip()
|
||||
|
||||
with stats_lock:
|
||||
# timer (milliseconds)
|
||||
if stype == 'ms':
|
||||
stats.timers[key].append(float(value if value else 0))
|
||||
|
||||
# counter with optional sample rate
|
||||
elif stype == 'c':
|
||||
if length == 3 and fields[2].startswith('@'):
|
||||
srate = float(fields[2][1:])
|
||||
value = float(value if value else 1) * (1 / srate)
|
||||
stats.counts[key] += value
|
||||
elif stype == 'g':
|
||||
value = float(value if value else 1)
|
||||
stats.gauges[key] = value
|
||||
|
||||
|
||||
def main():
|
||||
opts = optparse.OptionParser(description=DESCRIPTION, version=__version__,
|
||||
add_help_option=False)
|
||||
opts.add_option('-b', '--bind', dest='bind_addr', default=':8125',
|
||||
help="bind [host]:port (host defaults to '')")
|
||||
opts.add_option('-s', '--sink', dest='sink', action='append', default=[],
|
||||
help="a graphite service to which stats are sent ([host]:port).")
|
||||
opts.add_option('-v', dest='verbose', action='count', default=0,
|
||||
help="increase verbosity (currently used for debugging)")
|
||||
opts.add_option('-f', '--flush', dest='interval', default=INTERVAL,
|
||||
help="flush interval, in seconds (default 10)")
|
||||
opts.add_option('-x', '--prefix', dest='key_prefix', default='',
|
||||
help="key prefix added to all keys (default None)")
|
||||
opts.add_option('-p', '--percent', dest='percent', default=PERCENT,
|
||||
help="percent threshold (default 90)")
|
||||
opts.add_option('-D', '--daemonize', dest='daemonize', action='store_true',
|
||||
help='daemonize the service')
|
||||
opts.add_option('-h', '--help', dest='usage', action='store_true')
|
||||
|
||||
(options, args) = opts.parse_args()
|
||||
|
||||
if options.usage:
|
||||
# TODO: write epilog. usage is manually output since optparse will
|
||||
# wrap the epilog and we want pre-formatted output. - ph
|
||||
print(opts.format_help())
|
||||
sys.exit()
|
||||
|
||||
if options.daemonize:
|
||||
daemonize()
|
||||
|
||||
sd = StatsDaemon(options.bind_addr, options.sink, options.interval,
|
||||
options.percent, options.verbose, options.key_prefix)
|
||||
sd.start()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,78 +0,0 @@
|
|||
|
||||
# standard
|
||||
import unittest
|
||||
|
||||
# local
|
||||
from gstatsd import service
|
||||
|
||||
|
||||
class StatsServiceTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
args = (':8125', [':2003'], 5, 90, 0)
|
||||
self.svc = service.StatsDaemon(*args)
|
||||
self.stats = self.svc._stats
|
||||
|
||||
def test_construct(self):
|
||||
svc = service.StatsDaemon('8125', ['2003'], 5, 90, 0)
|
||||
stats = svc._stats
|
||||
self.assertEquals(svc._bindaddr, ('', 8125))
|
||||
self.assertEquals(svc._interval, 5.0)
|
||||
self.assertEquals(svc._debug, 0)
|
||||
self.assertEquals(stats.percent, 90.0)
|
||||
self.assertEquals(svc._sink._hosts, [('', 2003)])
|
||||
|
||||
svc = service.StatsDaemon('bar:8125', ['foo:2003'], 5, 90, 1)
|
||||
self.assertEquals(svc._bindaddr, ('bar', 8125))
|
||||
self.assertEquals(svc._sink._hosts, [('foo', 2003)])
|
||||
self.assertEquals(svc._debug, 1)
|
||||
|
||||
def test_backend(self):
|
||||
service.StatsDaemon._send_foo = lambda self, x, y: None
|
||||
svc = service.StatsDaemon('8125', ['bar:2003'], 5, 90, 0)
|
||||
self.assertEquals(svc._sink._hosts, [('bar', 2003)])
|
||||
|
||||
def test_counters(self):
|
||||
pkt = 'foo:1|c'
|
||||
self.svc._process(pkt)
|
||||
self.assertEquals(self.stats.counts, {'foo': 1})
|
||||
self.svc._process(pkt)
|
||||
self.assertEquals(self.stats.counts, {'foo': 2})
|
||||
pkt = 'foo:-1|c'
|
||||
self.svc._process(pkt)
|
||||
self.assertEquals(self.stats.counts, {'foo': 1})
|
||||
|
||||
def test_counters_sampled(self):
|
||||
pkt = 'foo:1|c|@.5'
|
||||
self.svc._process(pkt)
|
||||
self.assertEquals(self.stats.counts, {'foo': 2})
|
||||
|
||||
def test_timers(self):
|
||||
pkt = 'foo:20|ms'
|
||||
self.svc._process(pkt)
|
||||
self.assertEquals(self.stats.timers, {'foo': [20.0]})
|
||||
pkt = 'foo:10|ms'
|
||||
self.svc._process(pkt)
|
||||
self.assertEquals(self.stats.timers, {'foo': [20.0, 10.0]})
|
||||
|
||||
def test_key_sanitize(self):
|
||||
pkt = '\t\n#! foo . bar \0 ^:1|c'
|
||||
self.svc._process(pkt)
|
||||
self.assertEquals(self.stats.counts, {'foo.bar': 1})
|
||||
|
||||
def test_key_prefix(self):
|
||||
args = (':8125', [':2003'], 5, 90, 0, 'pfx')
|
||||
svc = service.StatsDaemon(*args)
|
||||
pkt = 'foo:1|c'
|
||||
svc._process(pkt)
|
||||
self.assertEquals(svc._stats.counts, {'pfx.foo': 1})
|
||||
|
||||
|
||||
def main():
|
||||
unittest.main()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
|
107
gstatsd/sink.py
107
gstatsd/sink.py
|
@ -1,107 +0,0 @@
|
|||
|
||||
# standard
|
||||
import cStringIO
|
||||
import sys
|
||||
import time
|
||||
|
||||
# vendor
|
||||
from gevent import socket
|
||||
|
||||
E_BADSPEC = "bad sink spec %r: %s"
|
||||
E_SENDFAIL = 'failed to send stats to %s %s: %s'
|
||||
|
||||
|
||||
class Sink(object):
|
||||
|
||||
"""
|
||||
A resource to which stats will be sent.
|
||||
"""
|
||||
|
||||
def error(self, msg):
|
||||
sys.stderr.write(msg + '\n')
|
||||
|
||||
def _parse_hostport(self, spec):
|
||||
try:
|
||||
parts = spec.split(':')
|
||||
if len(parts) == 2:
|
||||
return (parts[0], int(parts[1]))
|
||||
if len(parts) == 1:
|
||||
return ('', int(parts[0]))
|
||||
except ValueError, ex:
|
||||
raise ValueError(E_BADSPEC % (spec, ex))
|
||||
raise ValueError("expected '[host]:port' but got %r" % spec)
|
||||
|
||||
|
||||
class GraphiteSink(Sink):
|
||||
|
||||
"""
|
||||
Sends stats to one or more Graphite servers.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._hosts = []
|
||||
|
||||
def add(self, spec):
|
||||
self._hosts.append(self._parse_hostport(spec))
|
||||
|
||||
def send(self, stats):
|
||||
"Format stats and send to one or more Graphite hosts"
|
||||
buf = cStringIO.StringIO()
|
||||
now = int(time.time())
|
||||
num_stats = 0
|
||||
|
||||
# timer stats
|
||||
pct = stats.percent
|
||||
timers = stats.timers
|
||||
for key, vals in timers.iteritems():
|
||||
if not vals:
|
||||
continue
|
||||
|
||||
# compute statistics
|
||||
num = len(vals)
|
||||
vals = sorted(vals)
|
||||
vmin = vals[0]
|
||||
vmax = vals[-1]
|
||||
mean = vmin
|
||||
max_at_thresh = vmax
|
||||
if num > 1:
|
||||
idx = round((pct / 100.0) * num)
|
||||
tmp = vals[:int(idx)]
|
||||
if tmp:
|
||||
max_at_thresh = tmp[-1]
|
||||
mean = sum(tmp) / idx
|
||||
|
||||
key = 'stats.timers.%s' % key
|
||||
buf.write('%s.mean %f %d\n' % (key, mean, now))
|
||||
buf.write('%s.upper %f %d\n' % (key, vmax, now))
|
||||
buf.write('%s.upper_%d %f %d\n' % (key, pct, max_at_thresh, now))
|
||||
buf.write('%s.lower %f %d\n' % (key, vmin, now))
|
||||
buf.write('%s.count %d %d\n' % (key, num, now))
|
||||
num_stats += 1
|
||||
|
||||
# counter stats
|
||||
counts = stats.counts
|
||||
for key, val in counts.iteritems():
|
||||
buf.write('stats.%s %f %d\n' % (key, val / stats.interval, now))
|
||||
buf.write('stats_counts.%s %f %d\n' % (key, val, now))
|
||||
num_stats += 1
|
||||
|
||||
# counter stats
|
||||
gauges = stats.gauges
|
||||
for key, val in gauges.iteritems():
|
||||
buf.write('stats.%s %f %d\n' % (key, val, now))
|
||||
buf.write('stats_counts.%s %f %d\n' % (key, val, now))
|
||||
num_stats += 1
|
||||
|
||||
buf.write('statsd.numStats %d %d\n' % (num_stats, now))
|
||||
|
||||
# TODO: add support for N retries
|
||||
|
||||
for host in self._hosts:
|
||||
# flush stats to graphite
|
||||
try:
|
||||
sock = socket.create_connection(host)
|
||||
sock.sendall(buf.getvalue())
|
||||
sock.close()
|
||||
except Exception, ex:
|
||||
self.error(E_SENDFAIL % ('graphite', host, ex))
|
|
@ -1,4 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
pandoc -f markdown -t rst -o README.txt README.md
|
||||
|
44
setup.py
44
setup.py
|
@ -1,44 +0,0 @@
|
|||
|
||||
import os
|
||||
from setuptools import setup
|
||||
|
||||
from gstatsd import __version__
|
||||
|
||||
|
||||
def main():
|
||||
cwd = os.path.dirname(os.path.abspath(__file__))
|
||||
path = os.path.join(cwd, 'README.txt')
|
||||
readme = open(path, 'rb').read()
|
||||
|
||||
setup(
|
||||
name = 'gstatsd',
|
||||
version = __version__,
|
||||
description = 'A statsd service and client in Python + gevent',
|
||||
license = 'Apache 2.0',
|
||||
author = 'Patrick Hensley',
|
||||
author_email = 'spaceboy@indirect.com',
|
||||
keywords = ['stats', 'graphite', 'statsd', 'gevent'],
|
||||
url = 'http://github.com/phensley/gstatsd',
|
||||
packages = ['gstatsd'],
|
||||
entry_points = {
|
||||
'console_scripts': ['gstatsd=gstatsd.service:main']
|
||||
},
|
||||
classifiers = [
|
||||
"Development Status :: 3 - Alpha",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: Apache Software License",
|
||||
"Operating System :: MacOS :: MacOS X",
|
||||
"Operating System :: POSIX :: Linux",
|
||||
"Operating System :: Unix",
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 2.6",
|
||||
"Programming Language :: Python :: 2.7",
|
||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||
],
|
||||
long_description = readme
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
Loading…
Reference in New Issue