@cryptotaxi247 / netdata-1 / commits / 408c57b53

better debugging and logging for SocketService modules; redis enhancements

Costa Tsaousis (ktsaou) committed Nov 11, 2016 at 22:26 UTC 408c57b532c3f880e227823e541be453b1a20539
4 files changed +99 -56
python.d/memcached.chart.py
+28 -22
@@ -33,7 +33,7 @@ CHARTS = {
33 'options': [None, 'Network', 'kilobytes/s', 'Network', 'memcached.net', 'line'],
34 'lines': [
35 ['bytes_read', 'read', 'incremental', 1, 1024],
36 - ['bytes_written', 'written', 'incremental', 1, 1024]
36 + ['bytes_written', 'write', 'incremental', 1, 1024]
37 ]},
38 'connections': {
39 'options': [None, 'Connections', 'connections/s', 'Cluster', 'memcached.connections', 'line'],
@@ -125,46 +125,52 @@ class Service(SocketService):
125 Get data from socket
126 :return: dict
127 """
128 + response = self._get_raw_data()
129 + if response is None:
130 + # error has already been logged
131 + return None
132 +
133 + if response.startswith('ERROR'):
134 + self.error("received ERROR")
135 + return None
136 +
137 try:
129 - raw = self._get_raw_data().split("\n")
138 + parsed = response.split("\n")
139 except AttributeError:
131 - self.error("no data received")
132 - return None
133 - if raw[0].startswith('ERROR'):
134 - self.error("memcached returned ERROR")
140 + self.error("response is invalid/empty")
141 return None
142 +
143 + # split the response
144 data = {}
137 - for line in raw:
145 + for line in parsed:
146 if line.startswith('STAT'):
147 try:
148 t = line[5:].split(' ')
141 - data[t[0]] = int(t[1])
149 + data[t[0]] = t[1]
150 except (IndexError, ValueError):
151 + self.debug("invalid line received: " + str(line))
152 pass
144 - try:
145 - data['hit_rate'] = int((data['keyspace_hits'] / float(data['keyspace_hits'] + data['keyspace_misses'])) * 100)
146 - except:
147 - data['hit_rate'] = 0
153
154 + if len(data) == 0:
155 + self.error("received data doesn't have any records")
156 + return None
157 +
158 + # custom calculations
159 try:
160 data['avail'] = int(data['limit_maxbytes']) - int(data['bytes'])
151 - data['used'] = data['bytes']
161 + data['used'] = int(data['bytes'])
162 except:
163 pass
164
155 - if len(data) == 0:
156 - self.error("received data doesn't have needed records")
157 - return None
158 - else:
159 - return data
165 + return data
166
167 def _check_raw_data(self, data):
168 if data.endswith('END\r\n'):
163 - self.debug("received response")
169 + self.debug("received full response from memcached")
170 return True
165 - else:
166 - self.debug("waiting response")
167 - return False
171 +
172 + self.debug("waiting more data from memcached")
173 + return False
174
175 def check(self):
176 """
python.d/python_modules/base.py
+6 -3
@@ -506,6 +506,7 @@ class SocketService(SimpleService):
506 self.unix_socket = None
507 self.request = ""
508 self.__socket_config = None
509 + self.__empty_request = "".encode()
510 SimpleService.__init__(self, configuration=configuration, name=name)
511
512 def _socketerror(self, msg=None):
@@ -614,7 +615,7 @@ class SocketService(SimpleService):
615 """
616 if self._sock is not None:
617 try:
617 - self.error("closing socket")
618 + self.debug("closing socket")
619 self._sock.shutdown(2) # 0 - read, 1 - write, 2 - all
620 self._sock.close()
621 except Exception:
@@ -627,9 +628,9 @@ class SocketService(SimpleService):
628 :return: boolean
629 """
630 # Send request if it is needed
630 - if self.request != "".encode():
631 + if self.request != self.__empty_request:
632 try:
632 - self.debug("sending request")
633 + self.debug("sending request:", str(self.request))
634 self._sock.send(self.request)
635 except Exception as e:
636 self._socketerror("error sending request:" + str(e))
@@ -657,10 +658,12 @@ class SocketService(SimpleService):
658 self._disconnect()
659 break
660
661 + self.debug("received data:", str(buf))
662 data += buf.decode(errors='ignore')
663 if self._check_raw_data(data):
664 break
665
666 + self.debug("final response:", str(data))
667 return data
668
669 def _get_raw_data(self):
python.d/redis.chart.py
+51 -26
@@ -19,40 +19,52 @@ retries = 60
19 # 'unix_socket': None
20 # }}
21
22 -ORDER = ['operations', 'hit_rate', 'memory', 'keys', 'clients', 'slaves']
22 +ORDER = ['operations', 'hit_rate', 'memory', 'keys', 'connections', 'clients', 'slaves', 'persistence']
23
24 CHARTS = {
25 'operations': {
26 - 'options': [None, 'Operations', 'operations/s', 'Statistics', 'redis.operations', 'line'],
26 + 'options': [None, 'Operations', 'operations/s', 'operations', 'redis.operations', 'line'],
27 'lines': [
28 + ['total_commands_processed', 'commands', 'incremental'],
29 ['instantaneous_ops_per_sec', 'operations', 'absolute']
30 ]},
31 'hit_rate': {
31 - 'options': [None, 'Hit rate', 'percent', 'Statistics', 'redis.hit_rate', 'line'],
32 + 'options': [None, 'Hit rate', 'percent', 'hits', 'redis.hit_rate', 'line'],
33 'lines': [
34 ['hit_rate', 'rate', 'absolute']
35 ]},
36 'memory': {
36 - 'options': [None, 'Memory utilization', 'kilobytes', 'Memory', 'redis.memory', 'line'],
37 + 'options': [None, 'Memory utilization', 'kilobytes', 'memory', 'redis.memory', 'line'],
38 'lines': [
39 ['used_memory', 'total', 'absolute', 1, 1024],
40 ['used_memory_lua', 'lua', 'absolute', 1, 1024]
41 ]},
42 'keys': {
42 - 'options': [None, 'Database keys', 'keys', 'Keys', 'redis.keys', 'line'],
43 + 'options': [None, 'Database keys', 'keys', 'keys', 'redis.keys', 'line'],
44 'lines': [
45 # lines are created dynamically in `check()` method
46 ]},
47 + 'connections': {
48 + 'options': [None, 'Connections', 'connections/s', 'connections', 'redis.connections', 'line'],
49 + 'lines': [
50 + ['total_connections_received', 'received', 'incremental'],
51 + ['rejected_connections', 'rejected', 'incremental']
52 + ]},
53 'clients': {
47 - 'options': [None, 'Clients', 'clients', 'Clients', 'redis.clients', 'line'],
54 + 'options': [None, 'Clients', 'clients', 'connections', 'redis.clients', 'line'],
55 'lines': [
56 ['connected_clients', 'connected', 'absolute'],
57 ['blocked_clients', 'blocked', 'absolute']
58 ]},
59 'slaves': {
53 - 'options': [None, 'Slaves', 'slaves', 'Replication', 'redis.slaves', 'line'],
60 + 'options': [None, 'Slaves', 'slaves', 'replication', 'redis.slaves', 'line'],
61 'lines': [
62 ['connected_slaves', 'connected', 'absolute']
63 + ]},
64 + 'persistence': {
65 + 'options': [None, 'Changes since last save', 'changes', 'persistence', 'redis.rdb_changes', 'line'],
66 + 'lines': [
67 + ['rdb_changes_since_last_save', 'changes', 'absolute']
68 ]}
69 }
70
@@ -74,33 +86,45 @@ class Service(SocketService):
86 Get data from socket
87 :return: dict
88 """
89 + response = self._get_raw_data()
90 + if response is None:
91 + # error has already been logged
92 + return None
93 +
94 try:
78 - raw = self._get_raw_data().split("\n")
95 + parsed = response.split("\n")
96 except AttributeError:
80 - self.error("no data received")
97 + self.error("response is invalid/empty")
98 return None
99 +
100 data = {}
83 - for line in raw:
84 - if line.startswith(('instantaneous', 'keyspace', 'used_memory', 'connected', 'blocked')):
85 - try:
86 - t = line.split(':')
87 - data[t[0]] = int(t[1])
88 - except (IndexError, ValueError):
89 - pass
90 - elif line.startswith('db'):
101 + for line in parsed:
102 + if len(line) < 5 or line[0] == '$' or line[0] == '#':
103 + continue
104 +
105 + if line.startswith('db'):
106 tmp = line.split(',')[0].replace('keys=', '')
107 record = tmp.split(':')
93 - data[record[0]] = int(record[1])
108 + data[record[0]] = record[1]
109 + continue
110 +
111 + try:
112 + t = line.split(':')
113 + data[t[0]] = t[1]
114 + except (IndexError, ValueError):
115 + self.debug("invalid line received: " + str(line))
116 + pass
117 +
118 + if len(data) == 0:
119 + self.error("received data doesn't have any records")
120 + return None
121 +
122 try:
95 - data['hit_rate'] = int((data['keyspace_hits'] / float(data['keyspace_hits'] + data['keyspace_misses'])) * 100)
123 + data['hit_rate'] = (int(data['keyspace_hits']) * 100) / (int(data['keyspace_hits']) + int(data['keyspace_misses']))
124 except:
125 data['hit_rate'] = 0
126
99 - if len(data) == 0:
100 - self.error("received data doesn't have needed records")
101 - return None
102 - else:
103 - return data
127 + return data
128
129 def _check_raw_data(self, data):
130 """
@@ -113,11 +137,12 @@ class Service(SocketService):
137 supposed = data.split('\n')[0][1:]
138 offset = len(supposed) + 4 # 1 dollar sing, 1 new line character + 1 ending sequence '\r\n'
139 supposed = int(supposed)
140 +
141 if length - offset >= supposed:
142 + self.debug("received full response from redis")
143 return True
118 - else:
119 - return False
144
145 + self.debug("waiting more data from redis")
146 return False
147
148 def check(self):
python.d/squid.chart.py
+14 -5
@@ -58,20 +58,25 @@ class Service(SocketService):
58 Get data via http request
59 :return: dict
60 """
61 + response = self._get_raw_data()
62 +
63 data = {}
64 try:
65 raw = ""
64 - for tmp in self._get_raw_data().split('\r\n'):
66 + for tmp in response.split('\r\n'):
67 if tmp.startswith("sample_time"):
68 raw = tmp
69 break
70 +
71 if raw.startswith('<'):
72 self.error("invalid data received")
73 return None
74 +
75 for row in raw.split('\n'):
76 if row.startswith(("client", "server.all")):
77 tmp = row.split("=")
78 data[tmp[0].replace('.', '_').strip(' ')] = int(tmp[1])
79 +
80 except (ValueError, AttributeError, TypeError):
81 self.error("invalid data received")
82 return None
@@ -83,15 +88,19 @@ class Service(SocketService):
88 return data
89
90 def _check_raw_data(self, data):
86 - if "Connection: keep-alive" in data[:1024]:
91 + header = data[:1024].lower()
92 +
93 + if "connection: keep-alive" in header:
94 self._keep_alive = True
95 else:
96 self._keep_alive = False
97
91 - if data[-7:] == "\r\n0\r\n\r\n" and "Transfer-Encoding: chunked" in data[:1024]: # HTTP/1.1 response
98 + if data[-7:] == "\r\n0\r\n\r\n" and "transfer-encoding: chunked" in header: # HTTP/1.1 response
99 + self.debug("received full response from squid")
100 return True
93 - else:
94 - return False
101 +
102 + self.debug("waiting more data from squid")
103 + return False
104
105 def check(self):
106 """