forked from reddit/diamond-memcached-slab-collector
-
Notifications
You must be signed in to change notification settings - Fork 0
/
memcached_slab.py
132 lines (112 loc) · 3.77 KB
/
memcached_slab.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
"""
Collect memcached slab stats
#### Example Configuration
[[MemcachedSlabCollector]]
enabled = True
host = localhost # optional
port = 11211 # optional
"""
from collections import defaultdict
import socket
import diamond.collector
from diamond.metric import Metric
def parse_slab_stats(slab_stats):
"""Convert output from memcached's `stats slabs` into a Python dict.
Newlines are returned by memcached along with carriage returns
(i.e. '\r\n').
>>> parse_slab_stats("STAT 1:chunk_size 96\r\nSTAT 1:chunks_per_page 10922\r\nSTAT active_slabs 1\r\nSTAT total_malloced 1048512\r\nEND\r\n")
{
'slabs': {
1: {
'chunk_size': 96,
'chunks_per_page': 10922,
# ...
},
},
'active_slabs': 1,
'total_malloced': 1048512,
}
"""
stats_dict = defaultdict(lambda: {})
for line in slab_stats.splitlines():
if line == 'END':
continue
# e.g.: "STAT 1:chunks_per_page 10922"
cmd, key, value = line.split(' ')
if cmd != 'STAT':
continue
# e.g.: "STAT active_slabs 1"
if ":" not in key:
stats_dict[key] = int(value)
else:
indexes = key.split(':')
if indexes[0].isdigit():
temp_dict = stats_dict['slabs']
else:
temp_dict = stats_dict
for key in indexes[:-1]:
if not key in temp_dict:
temp_dict[key] = defaultdict(lambda: {})
temp_dict = temp_dict[key]
temp_dict[indexes[-1]] = int(value)
return stats_dict
def dict_to_paths(dict_):
"""Convert a dict to metric paths.
>>> dict_to_paths({'foo': {'bar': 1}, 'baz': 2})
{
'foo.bar': 1,
'baz': 2,
}
"""
metrics = {}
for k, v in dict_.iteritems():
if isinstance(v, dict):
submetrics = dict_to_paths(v)
for subk, subv in submetrics.iteritems():
metrics['.'.join([str(k), str(subk)])] = subv
else:
metrics[k] = v
return metrics
class MemcachedSlabCollector(diamond.collector.Collector):
def process_config(self):
super(MemcachedSlabCollector, self).process_config()
self.host = self.config['host']
self.port = int(self.config['port'])
def get_default_config(self):
config = super(MemcachedSlabCollector, self).get_default_config()
# Output stats in the format:
# 'servers.cache-main-01.memcached_slab.slabs.1.chunk_size'
config.update({
'interval': 60,
'path_prefix': 'servers',
'path': 'memcached_slab',
'host': 'localhost',
'port': 11211,
})
return config
def get_slab_stats(self):
"""Retrieve slab stats from memcached."""
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((self.host, self.port))
data = []
try:
for statcmd in ['stats slabs\n', 'stats items\n']:
s.send(statcmd)
while True:
buf = s.recv(4096)
data.append(buf)
if buf.endswith('END\r\n'):
break
finally:
s.close()
return ''.join(data)
def collect(self):
unparsed_slab_stats = self.get_slab_stats()
slab_stats = parse_slab_stats(unparsed_slab_stats)
paths = dict_to_paths(slab_stats)
for path, value in paths.iteritems():
# Add path and prefix to metric (e.g.
# 'servers.cache-main-01.memchached_slab')
full_path = self.get_metric_path(path)
metric = Metric(full_path, value)
self.publish_metric(metric)