├── LICENSE ├── README.md ├── haproxy-statsd.conf.sample └── haproxy-statsd.py /LICENSE: -------------------------------------------------------------------------------- 1 | Unless otherwise noted, all files are released under the MIT license, 2 | exceptions contain licensing information in them. 3 | 4 | Copyright (C) 2013 SoftLayer Technologies, Inc. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | haproxy-statsd 2 | -------------- 3 | This script reports stats to statsd using [haproxy's](http://haproxy.1wt.eu/) stats interface. [See Stats Interface Demo](http://demo.1wt.eu/) ([CSV](http://demo.1wt.eu/;csv)). 4 | 5 | Supported with Python 2.6 and Python 2.7. 6 | 7 | Usage 8 | ----- 9 | ``` 10 | usage: report_haproxy.py [-h] [-c CONFIG] [-1] 11 | 12 | Report haproxy stats to statsd 13 | 14 | optional arguments: 15 | -h, --help show this help message and exit 16 | -c CONFIG, --config CONFIG 17 | Config file location 18 | -1, --once Run once and exit 19 | ``` 20 | Configuration via environment 21 | ------------------------------- 22 | ``` 23 | STATSD_HOST=127.0.0.1 \ 24 | STATSD_PORT=8125 \ 25 | STATSD_NAMESPACE=haproxy \ 26 | HAPROXY_HOST=127.0.0.1 \ 27 | HAPROXY_USER=stats \ 28 | HAPROXY_PASS=stats \ 29 | report_haproxy.py 30 | ``` 31 | 32 | Config file 33 | ----------- 34 | Default location is ./haproxy-statsd.conf. 35 | 36 | ``` 37 | [haproxy-statsd] 38 | haproxy_url = http://127.0.0.1:1936/;csv 39 | haproxy_user = 40 | haproxy_password = 41 | statsd_host = 127.0.0.1 42 | statsd_port = 8125 43 | statsd_namespace = haproxy.(HOSTNAME) 44 | interval = 5 45 | ``` 46 | 47 | Statsd Paths 48 | ------------ 49 | All metrics are reported with paths matching this format: 50 | 51 | stats.gauges.[namespace].[pxname].[svname].[statname] 52 | 53 | The following stats are monitored: scur, smax, ereq, econ, rate, bin, bout, hrsp_1xx, hrsp_2xx, hrsp_3xx, hrsp_4xx, hrsp_5xx, qtime, ctime, rtime, ttime. 54 | 55 | For more information about those metrics please refer to: [HAProxy' stats](http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#9.1) documentation. 56 | -------------------------------------------------------------------------------- /haproxy-statsd.conf.sample: -------------------------------------------------------------------------------- 1 | [haproxy-statsd] 2 | haproxy_url = http://127.0.0.1:1936/;csv 3 | haproxy_user = 4 | haproxy_password = 5 | statsd_host = 127.0.0.1 6 | statsd_port = 8125 7 | statsd_namespace = haproxy.(HOSTNAME) 8 | -------------------------------------------------------------------------------- /haproxy-statsd.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | """ 3 | usage: report_haproxy.py [-h] [-c CONFIG] [-1] 4 | 5 | Report haproxy stats to statsd 6 | 7 | optional arguments: 8 | -h, --help show this help message and exit 9 | -c CONFIG, --config CONFIG 10 | Config file location 11 | -1, --once Run once and exit 12 | 13 | Config file format 14 | ------------------ 15 | [haproxy-statsd] 16 | haproxy_url = http://127.0.0.1:1936/;csv 17 | haproxy_user = 18 | haproxy_password = 19 | statsd_host = 127.0.0.1 20 | statsd_port = 8125 21 | statsd_namespace = haproxy.(HOSTNAME) 22 | interval = 5 23 | """ 24 | 25 | import time 26 | import csv 27 | import socket 28 | import argparse 29 | import ConfigParser 30 | import os 31 | 32 | import requests 33 | from requests.auth import HTTPBasicAuth 34 | 35 | 36 | def get_haproxy_report(url, user=None, password=None): 37 | auth = None 38 | if user: 39 | auth = HTTPBasicAuth(user, password) 40 | r = requests.get(url, auth=auth) 41 | r.raise_for_status() 42 | data = r.content.lstrip('# ') 43 | return csv.DictReader(data.splitlines()) 44 | 45 | 46 | def report_to_statsd(stat_rows, 47 | host=os.getenv('STATSD_HOST', '127.0.0.1'), 48 | port=os.getenv('STATSD_PORT', 8125), 49 | namespace=os.getenv('STATSD_NAMESPACE', 'haproxy')): 50 | udp_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 51 | stat_count = 0 52 | 53 | # Report for each row 54 | for row in stat_rows: 55 | path = '.'.join([namespace, row['pxname'], row['svname']]) 56 | 57 | # Report each stat that we want in each row 58 | for stat in ['scur', 'smax', 'ereq', 'econ', 'rate', 'bin', 'bout', 'hrsp_1xx', 'hrsp_2xx', 'hrsp_3xx', 'hrsp_4xx', 'hrsp_5xx', 'qtime', 'ctime', 'rtime', 'ttime']: 59 | val = row.get(stat) or 0 60 | udp_sock.sendto( 61 | '%s.%s:%s|g' % (path, stat, val), (host, port)) 62 | stat_count += 1 63 | return stat_count 64 | 65 | 66 | if __name__ == '__main__': 67 | parser = argparse.ArgumentParser( 68 | description='Report haproxy stats to statsd') 69 | parser.add_argument('-c', '--config', 70 | help='Config file location', 71 | default='./haproxy-statsd.conf') 72 | parser.add_argument('-1', '--once', 73 | action='store_true', 74 | help='Run once and exit', 75 | default=False) 76 | 77 | args = parser.parse_args() 78 | config = ConfigParser.ConfigParser({ 79 | 'haproxy_url': os.getenv('HAPROXY_HOST', 'http://127.0.0.1:1936/;csv'), 80 | 'haproxy_user': os.getenv('HAPROXY_USER',''), 81 | 'haproxy_password': os.getenv('HAPROXY_PASS',''), 82 | 'statsd_namespace': os.getenv('STATSD_NAMESPACE', 'haproxy.(HOSTNAME)'), 83 | 'statsd_host': os.getenv('STATSD_HOST', '127.0.0.1'), 84 | 'statsd_port': os.getenv('STATSD_PORT', 8125), 85 | 'interval': '5', 86 | }) 87 | config.add_section('haproxy-statsd') 88 | config.read(args.config) 89 | 90 | # Generate statsd namespace 91 | namespace = config.get('haproxy-statsd', 'statsd_namespace') 92 | if '(HOSTNAME)' in namespace: 93 | namespace = namespace.replace('(HOSTNAME)', socket.gethostname()) 94 | 95 | interval = config.getfloat('haproxy-statsd', 'interval') 96 | 97 | try: 98 | while True: 99 | report_data = get_haproxy_report( 100 | config.get('haproxy-statsd', 'haproxy_url'), 101 | user=config.get('haproxy-statsd', 'haproxy_user'), 102 | password=config.get('haproxy-statsd', 'haproxy_password')) 103 | 104 | report_num = report_to_statsd( 105 | report_data, 106 | namespace=namespace, 107 | host=config.get('haproxy-statsd', 'statsd_host'), 108 | port=config.getint('haproxy-statsd', 'statsd_port')) 109 | 110 | print("Reported %s stats" % report_num) 111 | if args.once: 112 | exit(0) 113 | else: 114 | time.sleep(interval) 115 | except KeyboardInterrupt: 116 | exit(0) 117 | --------------------------------------------------------------------------------