@cryptotaxi247 / netdata-1 / commits / 3ee7e26b5

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