├── LICENSE ├── README.md ├── debian ├── changelog ├── checkversions.sh ├── compat ├── control ├── copyright ├── dirs ├── install └── rules ├── setup.py ├── src └── graphiteudp.py └── test └── example.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2012-2022 derpston 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | python-graphiteudp 2 | ================== 3 | 4 | Python module for sending metrics to Graphite over UDP. 5 | 6 | Features 7 | -------- 8 | * Simple module-level interface, can be configured by one module and that configuration will be reused across an application importing it 9 | * Uses python-socketcache to minimise DNS lookups and creating new socket objects for minimum performance impact on the application 10 | * Supports a debug mode where metric messages are logged and not sent 11 | * Logs messages for network errors instead of blowing up 12 | 13 | Examples 14 | ------- 15 | ```python 16 | import graphiteudp 17 | 18 | # By default, sends metrics to localhost:2003 with no prefix. 19 | graphiteudp.init() 20 | graphiteudp.send("foo", 1) 21 | 22 | # Send to a specific host and add a prefix. 23 | graphiteudp.init("graphite.example.com", prefix = "myapp") 24 | graphiteudp.send("bar.monkey", 123) 25 | 26 | # The above will send "myapp.bar.monkey 123.000000 1354307985\n" to Graphite. 27 | 28 | graphiteudp.send("bar.monkey", 123, False) 29 | 30 | # The above will send "myapp.bar.monkey 123.000000\n" to Graphite, allowing 31 | # the server to use the arrival time of the packet instead of specifying 32 | # a timestamp. Useful for embedded systems which may not keep accurate time. 33 | ``` 34 | 35 | #### Debug logging 36 | If you'd like to just log a message for testing without sending anything, you can add the ```debug=True``` parameter to `init()`: 37 | 38 | ```python 39 | graphiteudp.init("graphite.example.com", prefix="myapp", debug=True) 40 | 41 | graphiteudp.send("bar.monkey", 123) 42 | 43 | # With debug=True set above, calling .send will only log the message, like this: 44 | # DEBUG 'myapp.bar.monkey 123.000000 1354307985\n' -> ('graphite.example.com', 2003) 45 | ``` 46 | 47 | #### For multiple modules in the same application, graphiteudp.init() only needs to be called once. 48 | ##### foo.py 49 | ```python 50 | import graphiteudp 51 | graphiteudp.init(...) 52 | import bar 53 | ``` 54 | 55 | ##### bar.py 56 | ```python 57 | import graphiteudp 58 | graphiteudp.send("bar.things", 1) 59 | ``` 60 | 61 | #### Multiple client objects can be maintained if you need to. 62 | ```python 63 | import graphiteudp 64 | 65 | g = graphiteudp.GraphiteUDPClient(...) 66 | g.send("foo", 1) 67 | ``` 68 | 69 | #### As a context manager 70 | ```python 71 | import graphiteudp 72 | graphiteudp.init(...) 73 | 74 | with graphiteudp.measure("sleep.time"): 75 | time.sleep(5) 76 | ``` 77 | 78 | ```python 79 | import graphiteudp 80 | graphiteudp.init(...) 81 | 82 | with graphiteudp.measure("sleep.time") as g: 83 | g.send("foo", 1) 84 | time.sleep(5) 85 | ``` 86 | 87 | #### As a decorator 88 | ```python 89 | import time 90 | import graphiteudp 91 | 92 | @graphiteudp.measure('slow.function') 93 | def slow_function(): 94 | time.sleep(5) 95 | 96 | slow_function() 97 | ``` 98 | 99 | BUGS 100 | ---- 101 | Unknown. 102 | 103 | TODO 104 | ---- 105 | * Should use the adns module for non-blocking DNS where available. 106 | * Should probably not depend on python-socketcache, but should use it where available. 107 | * Tests 108 | 109 | Contributing 110 | ------------ 111 | Contributions welcome! 112 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | python-graphiteudp (0.0.6) unstable; urgency=low 2 | 3 | * Adding support for optional timestamp. 4 | 5 | -- Derp Ston Mon, 5 Aug 2019 13:00:00 +0000 6 | 7 | python-graphiteudp (0.0.5) unstable; urgency=low 8 | 9 | * Removing deprecated python-support deps for ubuntu 16.04 10 | 11 | -- Derp Ston Tue, 13 Mar 2018 15:11:00 +0000 12 | 13 | python-graphiteudp (0.0.2) unstable; urgency=low 14 | 15 | * Using python-socketcache to manage socket creation, DNS, etc, to avoid 16 | needing a local caching resolver. 17 | 18 | -- Derp Ston Tue, 20 Nov 2011 05:00:00 +0000 19 | python-graphiteudp (0.0.1) unstable; urgency=low 20 | 21 | * Initial release. 22 | 23 | -- Derp Ston Sat, 17 Nov 2011 15:00:00 +0000 24 | -------------------------------------------------------------------------------- /debian/checkversions.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | cd src 5 | pymodver=$(python -c 'import graphiteudp; print graphiteudp.__version__') 6 | cd - >> /dev/null 7 | 8 | packagever=$(head -1 debian/changelog | sed 's/[a-zA-Z0-9-]\+ (\([0-9.]\+\)).*/\1/g') 9 | 10 | if [ "$pymodver" != "$packagever" ] 11 | then 12 | echo "Version mismatch between debian/changelog ($packagever) and src/graphiteudp.py ($pymodver)" 13 | exit 1 14 | fi 15 | 16 | 17 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 6 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: python-graphiteudp 2 | Section: python 3 | Priority: optional 4 | Maintainer: Derp Ston 5 | Build-Depends: 6 | debhelper (>= 6.0.0), 7 | python-socketcache, 8 | Standards-Version: 3.9.1 9 | 10 | Package: python-graphiteudp 11 | Architecture: all 12 | Depends: 13 | ${python:Depends}, 14 | ${misc:Depends}, 15 | Description: Python module for sending UDP packets to Graphite 16 | Supports using a cache of socket objects to ensure minimal 17 | impact on application performance, supports a debug mode where 18 | metrics are dumped to logs instead, and handles network 19 | errors by logging exceptions. 20 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Name: python-graphiteudp 2 | Copyright: Derpston 2012 3 | -------------------------------------------------------------------------------- /debian/dirs: -------------------------------------------------------------------------------- 1 | /usr/share/pyshared 2 | -------------------------------------------------------------------------------- /debian/install: -------------------------------------------------------------------------------- 1 | src/graphiteudp.py /usr/share/pyshared/ 2 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # -*- makefile -*- 3 | 4 | # Uncomment this to turn on verbose mode. 5 | #export DH_VERBOSE=1 6 | #export DH_OPTIONS=-v 7 | 8 | build: 9 | debian/checkversions.sh 10 | dh build 11 | 12 | %: 13 | echo "target" $@ 14 | dh $@ 15 | 16 | 17 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from distutils.core import setup 4 | 5 | setup( 6 | name = 'graphiteudp' 7 | , version = '0.0.6' 8 | , description = 'A clean interface for sending metrics to Graphite over UDP' 9 | , long_description = """Uses a cache of socket objects to minimise DNS lookups and \ 10 | performance impact on the application. Supports a debug mode that logs metric messages, \ 11 | and handles network errors by logging an exception.""" 12 | , author = 'Derp Ston' 13 | , author_email = 'derpston+pypi@sleepygeek.org' 14 | , url = 'https://github.com/derpston/python-graphiteudp' 15 | , packages = [''] 16 | , package_dir = {'': 'src'} 17 | , install_requires = ['socketcache', 'contextlib2'] 18 | ) 19 | -------------------------------------------------------------------------------- /src/graphiteudp.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.0.6" 2 | 3 | import time 4 | import logging 5 | import warnings 6 | import socketcache 7 | from contextlib2 import contextmanager 8 | logger = logging.getLogger("graphiteudp") 9 | 10 | # If the user wants to use the module-level interface, this will hold a 11 | # reference to a Client object. Otherwise, the user will use the Client 12 | # object directly. 13 | _module_client = None 14 | 15 | class GraphiteUDPClient: 16 | def __init__(self, host = "localhost", port = 2003, prefix = None, debug = False): 17 | self._host = socketcache.UDPSocketCache(host, port) 18 | self._addr = (host, port) 19 | self._prefix = prefix 20 | self._debug = debug 21 | 22 | def send(self, metric, value, timestamp = None): 23 | if timestamp is False: 24 | # Omit the timestamp and allow the Graphite server to use the time 25 | # of arrival instead. 26 | message = "%s %f\n" % (metric, value) 27 | else: 28 | if timestamp is None: 29 | timestamp = int(time.time()) 30 | 31 | message = "%s %f %d\n" % (metric, value, timestamp) 32 | 33 | if self._prefix is not None: 34 | message = self._prefix + "." + message 35 | 36 | if self._debug: 37 | logger.debug("%s -> %s" % (repr(message), repr(self._addr))) 38 | else: 39 | try: 40 | (sock, addr) = self._host.get() 41 | sock.sendto(message.encode(), addr) 42 | except Exception as ex: 43 | logging.info("Failed to send graphite UDP message: %s" % ex) 44 | 45 | def init(*args, **kwargs): 46 | global _module_client 47 | _module_client = GraphiteUDPClient(*args, **kwargs) 48 | 49 | def send(*args, **kwargs): 50 | if _module_client is not None: 51 | _module_client.send(*args, **kwargs) 52 | else: 53 | warnings.warn("graphiteudp.send called before graphiteudp.init, metrics will be dropped.", RuntimeWarning, 2) 54 | 55 | 56 | @contextmanager 57 | def measure(metric, measure_func=time.time, reverse=False): 58 | """ 59 | Function to allow usage of graphiteudp as both a context manager 60 | and a decorator. Takes an optional measure_func argument which is 61 | called before and after to obtain the `value` thats passed to 62 | graphiteudp.send. Defaults to time. 63 | 64 | Args: 65 | metric: The metric path to use. 66 | measure_func: The function thats called on entry and exit of the 67 | context or before and after a decorated function when used as 68 | a decorator. 69 | reverse: Reverse the order of arguments used when obtainning 70 | the difference. 71 | 72 | Examples: 73 | 74 | with graphite_measure('my.example.metric'): 75 | time.sleep(5) 76 | 77 | @graphite_measure('slow_function.time') 78 | def slow_function(): 79 | time.sleep(5) 80 | 81 | def get_memory(): 82 | return resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024 83 | 84 | with graphite_measure('memory_usage', get_memory): 85 | _ = [x for x in range(100000)] 86 | """ 87 | try: 88 | before = measure_func() 89 | yield _module_client 90 | 91 | finally: 92 | if reverse: 93 | value = before - measure_func() 94 | else: 95 | value = measure_func() - before 96 | 97 | send(metric, value) 98 | -------------------------------------------------------------------------------- /test/example.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import logging 3 | sys.path.insert(0, "../src/") 4 | import graphiteudp 5 | 6 | graphiteudp.send("foo", 1.2) 7 | 8 | logger = logging.getLogger() 9 | logger.setLevel(logging.DEBUG) 10 | handler = logging.StreamHandler() # Writes to stderr by default 11 | handler.setFormatter(logging.Formatter('%(process)d %(name)s %(levelname)s %(message)s')) 12 | logger.addHandler(handler) 13 | 14 | graphiteudp.init(debug = True) 15 | graphiteudp.send("foo2", 1.2) 16 | 17 | graphiteudp.init("example.com") 18 | graphiteudp.send("foo3", 1.2) 19 | 20 | graphiteudp.init("example.com", 2005) 21 | graphiteudp.send("foo3", 1.2) 22 | 23 | graphiteudp.init("example.com", 2005, prefix = "bzrt") 24 | graphiteudp.send("foo4", 1.2, 100) 25 | 26 | graphiteudp.init("example.com", 2005, prefix = "bzrt") 27 | graphiteudp.send("foo5", 1.2, False) 28 | 29 | --------------------------------------------------------------------------------