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
"""