|
| 1 | +#!/usr/bin/env python |
| 2 | + |
| 3 | +import argparse |
| 4 | +import socket |
| 5 | +import sys |
| 6 | +import time |
| 7 | + |
| 8 | +parser = argparse.ArgumentParser(description='Collect metrics from Redis and emit to StatsD') |
| 9 | +parser.add_argument('--period', dest='period', type=int, default=20, help='The period at which to collect and emit metrics') |
| 10 | +parser.add_argument('--prefix', dest='prefix', type=str, default='redis', help='The prefix to use for metric names') |
| 11 | +parser.add_argument('--redis-host', dest='redis_host', type=str, default='localhost', help='The address of the Redis host to connect to') |
| 12 | +parser.add_argument('--redis-port', dest='redis_port', type=int, default=6379, help='The port of the Redis host to connect to') |
| 13 | +parser.add_argument('--statsd-host', dest='statsd_host', type=str, default='localhost', help='The port of the StatsD host to connect to') |
| 14 | +parser.add_argument('--statsd-port', dest='statsd_port', type=int, default=8125, help='The port of the Redis port to connect to') |
| 15 | +parser.add_argument('--no-tags', dest='tags', action='store_false', help='Disable tags for use with DogStatsD') |
| 16 | + |
| 17 | +args = parser.parse_args() |
| 18 | + |
| 19 | +GAUGES = { |
| 20 | + 'blocked_clients': 'blocked_clients', |
| 21 | + 'connected_clients': 'connected_clients', |
| 22 | + 'mem_fragmentation_ratio': 'mem_fragmentation_ratio', |
| 23 | + 'uptime_in_seconds': 'uptime_in_seconds', |
| 24 | + 'used_memory': 'used_memory', |
| 25 | + 'used_memory_lua': 'used_memory_lua', |
| 26 | + 'used_memory_peak': 'used_memory_peak', |
| 27 | + 'used_memory_rss': 'used_memory_rss' |
| 28 | +} |
| 29 | +COUNTERS = { |
| 30 | + 'evicted_keys': 'evicted_keys', |
| 31 | + 'expired_keys': 'expired_keys', |
| 32 | + 'instantaneous_ops_per_sec': 'instantaneous_ops_per_sec', |
| 33 | + 'keyspace_hits': 'keyspace_hits', |
| 34 | + 'keyspace_misses': 'keyspace_misses', |
| 35 | + 'latest_fork_usec': 'latest_fork_usec', |
| 36 | + 'migrate_cached_sockets': 'migrate_cached_sockets', |
| 37 | + 'pubsub_channels': 'pubsub_channels', |
| 38 | + 'pubsub_patterns': 'pubsub_patterns', |
| 39 | + 'rejected_connections': 'rejected_connections', |
| 40 | + 'sync_full': 'sync_full', |
| 41 | + 'sync_partial_err': 'sync_partial_err', |
| 42 | + 'sync_partial_ok': 'sync_partial_ok', |
| 43 | + 'total_commands_processed': 'total_commands_processed', |
| 44 | + 'total_connections_received': 'total_connections_received' |
| 45 | +} |
| 46 | +KEYSPACE_COUNTERS = { |
| 47 | + 'expires': 'expires', |
| 48 | + 'keys': 'keys' |
| 49 | +} |
| 50 | +KEYSPACE_GAUGES = { |
| 51 | + 'avg_ttl': 'avg_ttl' |
| 52 | +} |
| 53 | + |
| 54 | +last_seens = {} |
| 55 | + |
| 56 | +def send_metric(name, mtype, value, tags=None): |
| 57 | + tagstring = '' |
| 58 | + finalvalue = value |
| 59 | + if tags is not None and args.tags: |
| 60 | + tagstring = '#{}'.format(','.join(tags)) |
| 61 | + if mtype == 'c': |
| 62 | + # For counters we will calculate our own deltas. |
| 63 | + mkey = '{}:{}'.format(name, tagstring) |
| 64 | + if mkey in last_seens: |
| 65 | + # global finalvalue |
| 66 | + # calculate our deltas and don't go below 0 |
| 67 | + finalvalue = max(0, value - last_seens[mkey]) |
| 68 | + else: |
| 69 | + # We'll default to 0, since we don't want our first counter |
| 70 | + # to be some huge number. |
| 71 | + finalvalue = 0 |
| 72 | + last_seens[mkey] = value |
| 73 | + |
| 74 | + met = '{}:{}|{}{}'.format(name, finalvalue, mtype, tagstring) |
| 75 | + out_sock.sendto(met, (args.statsd_host, args.statsd_port)) |
| 76 | + |
| 77 | +def linesplit(socket): |
| 78 | + buffer = socket.recv(4096) |
| 79 | + buffering = True |
| 80 | + while buffering: |
| 81 | + if "\n" in buffer: |
| 82 | + (line, buffer) = buffer.split("\n", 1) |
| 83 | + if(line == "\r"): |
| 84 | + buffering = False |
| 85 | + yield line |
| 86 | + else: |
| 87 | + more = socket.recv(4096) |
| 88 | + if not more: |
| 89 | + buffering = False |
| 90 | + else: |
| 91 | + buffer += more |
| 92 | + if buffer: |
| 93 | + yield buffer |
| 94 | + |
| 95 | +while True: |
| 96 | + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
| 97 | + s.connect((args.redis_host, args.redis_port)) |
| 98 | + s.send("INFO\n") |
| 99 | + |
| 100 | + stats = {} |
| 101 | + stats['keyspaces'] = {} |
| 102 | + |
| 103 | + for line in linesplit(s): |
| 104 | + if '# Clients' in line: |
| 105 | + for l in line.split("\n"): |
| 106 | + if ':keys' in l: |
| 107 | + (keyspace, kstats) = l.split(':') |
| 108 | + if keyspace not in stats['keyspaces']: |
| 109 | + stats['keyspaces'][keyspace] = {} |
| 110 | + for ks in kstats.split(','): |
| 111 | + (n, v) = ks.split('=') |
| 112 | + stats['keyspaces'][keyspace][n] = v.rstrip() |
| 113 | + |
| 114 | + elif ':' in l: |
| 115 | + (name, value) = l.split(':') |
| 116 | + stats[name] = value.rstrip() |
| 117 | + |
| 118 | + s.close() |
| 119 | + |
| 120 | + out_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) |
| 121 | + |
| 122 | + for g in GAUGES: |
| 123 | + if g in stats: |
| 124 | + send_metric('{}.{}'.format(args.prefix, g), 'g', float(stats[g])) |
| 125 | + |
| 126 | + for c in COUNTERS: |
| 127 | + if c in stats: |
| 128 | + send_metric('{}.{}'.format(args.prefix, c), 'c', float(stats[c])) |
| 129 | + |
| 130 | + for ks in stats['keyspaces']: |
| 131 | + for kc in KEYSPACE_COUNTERS: |
| 132 | + if kc in stats['keyspaces'][ks]: |
| 133 | + send_metric('{}.keyspace.{}'.format( |
| 134 | + args.prefix, kc), 'c', |
| 135 | + float(stats['keyspaces'][ks][kc]), ['keyspace={}'.format(ks)] |
| 136 | + ) |
| 137 | + |
| 138 | + for kg in KEYSPACE_GAUGES: |
| 139 | + if kg in stats['keyspaces'][ks]: |
| 140 | + send_metric('{}.keyspace.{}'.format( |
| 141 | + args.prefix, kg), 'g', |
| 142 | + float(stats['keyspaces'][ks][kg]), ['keyspace={}'.format(ks)] |
| 143 | + ) |
| 144 | + |
| 145 | + out_sock.close() |
| 146 | + time.sleep(10) |
| 147 | + |
0 commit comments