add Pika support to redis module
lgz committed
Jun 16, 2018 at 16:36 UTC
3ee7e26b5d7a98242cf2daee7a322fe211dbda69
1 file changed
+119
-86
python.d/redis.chart.py
+119
-86
@@ -1,75 +1,97 @@
1
# -*- coding: utf-8 -*-
2
# Description: redis netdata python.d module
3
# Author: Pawel Krupa (paulfantom)
4
+# Author: Ilya Mashchenko (l2isbad)
5
# SPDX-License-Identifier: GPL-3.0+
6
6
-from bases.FrameworkServices.SocketService import SocketService
7
+import re
8
+
9
+from copy import deepcopy
10
8
-# default module values (can be overridden per job in `config`)
9
-priority = 60000
10
-retries = 60
11
+from bases.FrameworkServices.SocketService import SocketService
12
12
-# default job configuration (overridden by python.d.plugin)
13
-# config = {'local': {
14
-# 'update_every': update_every,
15
-# 'retries': retries,
16
-# 'priority': priority,
17
-# 'host': 'localhost',
18
-# 'port': 6379,
19
-# 'unix_socket': None
20
-# }}
13
+REDIS_ORDER = [
14
+ 'operations',
15
+ 'hit_rate',
16
+ 'memory',
17
+ 'keys_redis',
18
+ 'net',
19
+ 'connections',
20
+ 'clients',
21
+ 'slaves',
22
+ 'persistence',
23
+ 'bgsave_now',
24
+ 'bgsave_health',
25
+]
26
+
27
+PIKA_ORDER = [
28
+ 'operations',
29
+ 'hit_rate',
30
+ 'memory',
31
+ 'keys_pika',
32
+ 'connections',
33
+ 'clients',
34
+ 'slaves',
35
+]
36
22
-ORDER = ['operations', 'hit_rate', 'memory', 'keys', 'net', 'connections', 'clients', 'slaves', 'persistence',
23
- 'bgsave_now', 'bgsave_health']
37
38
CHARTS = {
39
'operations': {
27
- 'options': [None, 'Redis Operations', 'operations/s', 'operations', 'redis.operations', 'line'],
40
+ 'options': [None, 'Operations', 'operations/s', 'operations', 'redis.operations', 'line'],
41
'lines': [
42
['total_commands_processed', 'commands', 'incremental'],
43
['instantaneous_ops_per_sec', 'operations', 'absolute']
44
]},
45
'hit_rate': {
33
- 'options': [None, 'Redis Hit rate', 'percent', 'hits', 'redis.hit_rate', 'line'],
46
+ 'options': [None, 'Hit rate', 'percent', 'hits', 'redis.hit_rate', 'line'],
47
'lines': [
48
['hit_rate', 'rate', 'absolute']
49
]},
50
'memory': {
38
- 'options': [None, 'Redis Memory utilization', 'kilobytes', 'memory', 'redis.memory', 'line'],
51
+ 'options': [None, 'Memory utilization', 'kilobytes', 'memory', 'redis.memory', 'line'],
52
'lines': [
53
['used_memory', 'total', 'absolute', 1, 1024],
54
['used_memory_lua', 'lua', 'absolute', 1, 1024]
55
]},
56
'net': {
44
- 'options': [None, 'Redis Bandwidth', 'kilobits/s', 'network', 'redis.net', 'area'],
57
+ 'options': [None, 'Bandwidth', 'kilobits/s', 'network', 'redis.net', 'area'],
58
'lines': [
59
['total_net_input_bytes', 'in', 'incremental', 8, 1024],
60
['total_net_output_bytes', 'out', 'incremental', -8, 1024]
61
]},
49
- 'keys': {
50
- 'options': [None, 'Redis Keys per Database', 'keys', 'keys', 'redis.keys', 'line'],
62
+ 'keys_redis': {
63
+ 'options': [None, 'Keys per Database', 'keys', 'keys', 'redis.keys', 'line'],
64
'lines': [
65
# lines are created dynamically in `check()` method
66
]},
67
+ 'keys_pika': {
68
+ 'options': [None, 'Keys', 'keys', 'keys', 'redis.keys', 'line'],
69
+ 'lines': [
70
+ ['kv_keys', 'kv', 'absolute'],
71
+ ['hash_keys', 'hash', 'absolute'],
72
+ ['list_keys', 'list', 'absolute'],
73
+ ['zset_keys', 'zset', 'absolute'],
74
+ ['set_keys', 'set', 'absolute']
75
+ ]},
76
'connections': {
55
- 'options': [None, 'Redis Connections', 'connections/s', 'connections', 'redis.connections', 'line'],
77
+ 'options': [None, 'Connections', 'connections/s', 'connections', 'redis.connections', 'line'],
78
'lines': [
79
['total_connections_received', 'received', 'incremental', 1],
80
['rejected_connections', 'rejected', 'incremental', -1]
81
]},
82
'clients': {
61
- 'options': [None, 'Redis Clients', 'clients', 'connections', 'redis.clients', 'line'],
83
+ 'options': [None, 'Clients', 'clients', 'connections', 'redis.clients', 'line'],
84
'lines': [
85
['connected_clients', 'connected', 'absolute', 1],
86
['blocked_clients', 'blocked', 'absolute', -1]
87
]},
88
'slaves': {
67
- 'options': [None, 'Redis Slaves', 'slaves', 'replication', 'redis.slaves', 'line'],
89
+ 'options': [None, 'Slaves', 'slaves', 'replication', 'redis.slaves', 'line'],
90
'lines': [
91
['connected_slaves', 'connected', 'absolute']
92
]},
93
'persistence': {
72
- 'options': [None, 'Redis Persistence Changes Since Last Save', 'changes', 'persistence',
94
+ 'options': [None, 'Persistence Changes Since Last Save', 'changes', 'persistence',
95
'redis.rdb_changes', 'line'],
96
'lines': [
97
['rdb_changes_since_last_save', 'changes', 'absolute']
@@ -89,81 +111,106 @@ CHARTS = {
111
}
112
113
114
+def copy_chart(name):
115
+ return dict((k, deepcopy(v)) for k, v in CHARTS.items() if k == name)
116
+
117
+
118
+RE = re.compile(r'\n([a-z_0-9 ]+):(?:keys=)?([^,\r]+)')
119
+
120
+
121
class Service(SocketService):
122
def __init__(self, configuration=None, name=None):
123
SocketService.__init__(self, configuration=configuration, name=name)
95
- self.order = ORDER
96
- self.definitions = CHARTS
124
self._keep_alive = True
98
- self.chart_name = ""
125
+
126
+ self.order = list()
127
+ self.definitions = dict()
128
+
129
self.host = self.configuration.get('host', 'localhost')
130
self.port = self.configuration.get('port', 6379)
131
self.unix_socket = self.configuration.get('socket')
102
- password = self.configuration.get('pass', str())
132
+ p = self.configuration.get('pass')
133
+
134
+ self.auth_request = 'AUTH {0} \r\n'.format(p).encode() if p else None
135
+ self.request = 'INFO\r\n'.encode()
136
self.bgsave_time = 0
104
- self.requests = dict(request='INFO\r\n'.encode(),
105
- password=' '.join(['AUTH', password, '\r\n']).encode() if password else None)
106
- self.request = self.requests['request']
137
108
- def _get_data(self):
109
- """
110
- Get data from socket
111
- :return: dict
112
- """
113
- if self.requests['password']:
114
- self.request = self.requests['password']
115
- raw = self._get_raw_data().strip()
116
- if raw != "+OK":
117
- self.error("invalid password")
118
- return None
119
- self.request = self.requests['request']
120
- response = self._get_raw_data()
121
- if response is None:
122
- # error has already been logged
138
+ def do_auth(self):
139
+ resp = self._get_raw_data(request=self.auth_request)
140
+ if not resp or resp.strip() != '+OK':
141
+ self.error("invalid password")
142
+ return False
143
+ return True
144
+
145
+ def get_raw_and_parse(self):
146
+ if self.auth_request and not self.do_auth():
147
return None
148
125
- try:
126
- parsed = response.split("\n")
127
- except AttributeError:
128
- self.error("response is invalid/empty")
149
+ resp = self._get_raw_data()
150
+
151
+ if not resp:
152
return None
153
131
- data = dict()
132
- for line in parsed:
133
- if len(line) < 5 or line[0] == '$' or line[0] == '#':
134
- continue
154
+ parsed = RE.findall(resp)
155
136
- if line.startswith('db'):
137
- tmp = line.split(',')[0].replace('keys=', '')
138
- record = tmp.split(':')
139
- data[record[0]] = record[1]
140
- continue
156
+ if not parsed:
157
+ self.error("response is invalid/empty")
158
+ return None
159
142
- try:
143
- t = line.split(':')
144
- data[t[0]] = t[1]
145
- except (IndexError, ValueError):
146
- self.debug("invalid line received: " + str(line))
160
+ return dict((k.replace(' ', '_'), v) for k, v in parsed)
161
+
162
+ def get_data(self):
163
+ """
164
+ Get data from socket
165
+ :return: dict
166
+ """
167
+ data = self.get_raw_and_parse()
168
169
if not data:
149
- self.error("received data doesn't have any records")
170
return None
171
172
try:
153
- data['hit_rate'] = (int(data['keyspace_hits']) * 100) / (int(data['keyspace_hits'])
154
- + int(data['keyspace_misses']))
155
- except (KeyError, ZeroDivisionError, TypeError):
173
+ data['hit_rate'] = (
174
+ (int(data['keyspace_hits']) * 100) / (int(data['keyspace_hits']) + int(data['keyspace_misses']))
175
+ )
176
+ except (KeyError, ZeroDivisionError):
177
data['hit_rate'] = 0
178
158
- if data['rdb_bgsave_in_progress'] != '0\r':
179
+ if data.get('redis_version'):
180
+ self.get_data_redis_specific(data)
181
+
182
+ return data
183
+
184
+ def get_data_redis_specific(self, data):
185
+ if data['rdb_bgsave_in_progress'] != '0':
186
self.bgsave_time += self.update_every
187
else:
188
self.bgsave_time = 0
189
163
- data['rdb_last_bgsave_status'] = 0 if data['rdb_last_bgsave_status'] == 'ok\r' else 1
190
+ data['rdb_last_bgsave_status'] = 0 if data['rdb_last_bgsave_status'] == 'ok' else 1
191
data['rdb_bgsave_in_progress'] = self.bgsave_time
192
166
- return data
193
+ def check(self):
194
+ """
195
+ Parse configuration, check if redis is available, and dynamically create chart lines data
196
+ :return: boolean
197
+ """
198
+ data = self.get_raw_and_parse()
199
+
200
+ if not data:
201
+ return False
202
+
203
+ self.order = PIKA_ORDER if data.get("pika_version") else REDIS_ORDER
204
+
205
+ for n in self.order:
206
+ self.definitions.update(copy_chart(n))
207
+
208
+ if data.get("redis_version"):
209
+ for k in data:
210
+ if k.startswith('db'):
211
+ self.definitions['keys_redis']['lines'].append([k, None, 'absolute'])
212
+
213
+ return True
214
215
def _check_raw_data(self, data):
216
"""
@@ -185,17 +232,3 @@ class Service(SocketService):
232
233
self.debug("waiting more data from redis")
234
return False
188
-
189
- def check(self):
190
- """
191
- Parse configuration, check if redis is available, and dynamically create chart lines data
192
- :return: boolean
193
- """
194
- data = self._get_data()
195
- if data is None:
196
- return False
197
-
198
- for name in data:
199
- if name.startswith('db'):
200
- self.definitions['keys']['lines'].append([name, None, 'absolute'])
201
- return True