Skip to content

Commit eae8816

Browse files
author
Cory G Watson
committed
It works.
0 parents  commit eae8816

File tree

2 files changed

+151
-0
lines changed

2 files changed

+151
-0
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# redis-statsd
2+
3+
Redis statsd is a small, dependency free Python program for periodically fetching stats via Redis' INFO
4+
command and emitting them to a local StatsD.

redis-statsd.py

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
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

Comments
 (0)