1. changed arguments from decode() in base.py, so they can run under CentOS 6.8 with Python 2.6 2. added pass parameters to redis for AUTH command 3. optimized nginx.chart.py and phpfpm.chart.py webpages submenu name 4. optimized phpfpm.chart.py average display
1. changed arguments from decode() in base.py, so they can run under CentOS 6.8 with Python 2.6 2. added pass parameters to redis for AUTH command 3. optimized nginx.chart.py and phpfpm.chart.py webpages submenu name 4. optimized phpfpm.chart.py average display
liu lei committed
Nov 17, 2016 at 03:11 UTC
7d5c114516ae6cce23803807d5893d7a617ae243
4 files changed
+270
-148
python.d/nginx.chart.py
+4
-4
@@ -22,24 +22,24 @@ ORDER = ['connections', 'requests', 'connection_status', 'connect_rate']
22
23
CHARTS = {
24
'connections': {
25
- 'options': [None, 'nginx Active Connections', 'connections', 'Active Connections', 'nginx.connections', 'line'],
25
+ 'options': [None, 'nginx Active Connections', 'connections', 'active connections', 'nginx.connections', 'line'],
26
'lines': [
27
["active"]
28
]},
29
'requests': {
30
- 'options': [None, 'nginx Requests', 'requests/s', 'Requests', 'nginx.requests', 'line'],
30
+ 'options': [None, 'nginx Requests', 'requests/s', 'requests', 'nginx.requests', 'line'],
31
'lines': [
32
["requests", None, 'incremental']
33
]},
34
'connection_status': {
35
- 'options': [None, 'nginx Active Connections by Status', 'connections', 'Active Connection Status', 'nginx.connection_status', 'line'],
35
+ 'options': [None, 'nginx Active Connections by Status', 'connections', 'status', 'nginx.connection_status', 'line'],
36
'lines': [
37
["reading"],
38
["writing"],
39
["waiting", "idle"]
40
]},
41
'connect_rate': {
42
- 'options': [None, 'nginx Connections Rate', 'connections/s', 'Connections Rate', 'nginx.connect_rate', 'line'],
42
+ 'options': [None, 'nginx Connections Rate', 'connections/s', 'connections rate', 'nginx.connect_rate', 'line'],
43
'lines': [
44
["accepts", "accepted", "incremental"],
45
["handled", None, "incremental"]
python.d/phpfpm.chart.py
+6
-9
@@ -23,37 +23,37 @@ ORDER = ['connections', 'requests', 'performance', 'request_duration', 'request_
23
24
CHARTS = {
25
'connections': {
26
- 'options': [None, 'PHP-FPM Active Connections', 'connections', 'Active Connections', 'phpfpm.connections', 'line'],
26
+ 'options': [None, 'PHP-FPM Active Connections', 'connections', 'active connections', 'phpfpm.connections', 'line'],
27
'lines': [
28
["active"],
29
["maxActive", 'max active'],
30
["idle"]
31
]},
32
'requests': {
33
- 'options': [None, 'PHP-FPM Requests', 'requests/s', 'Requests', 'phpfpm.requests', 'line'],
33
+ 'options': [None, 'PHP-FPM Requests', 'requests/s', 'requests', 'phpfpm.requests', 'line'],
34
'lines': [
35
["requests", None, "incremental"]
36
]},
37
'performance': {
38
- 'options': [None, 'PHP-FPM Performance', 'status', 'Performance', 'phpfpm.performance', 'line'],
38
+ 'options': [None, 'PHP-FPM Performance', 'status', 'performance', 'phpfpm.performance', 'line'],
39
'lines': [
40
["reached", 'max children reached'],
41
["slow", 'slow requests']
42
]},
43
'request_duration': {
44
- 'options': [None, 'PHP-FPM Request Duration', 'milliseconds', 'Request Duration', 'phpfpm.request_duration', 'line'],
44
+ 'options': [None, 'PHP-FPM Request Duration', 'milliseconds', 'request duration', 'phpfpm.request_duration', 'line'],
45
'lines': [
46
["maxReqDur", 'max request duration'],
47
["avgReqDur", 'average request duration']
48
]},
49
'request_cpu': {
50
- 'options': [None, 'PHP-FPM Request CPU', 'percent', 'Request CPU', 'phpfpm.request_cpu', 'line'],
50
+ 'options': [None, 'PHP-FPM Request CPU', 'percent', 'request CPU', 'phpfpm.request_cpu', 'line'],
51
'lines': [
52
["maxReqCPU", 'max request cpu'],
53
["avgReqCPU", 'average request cpu']
54
]},
55
'request_mem': {
56
- 'options': [None, 'PHP-FPM Request Memory', 'kilobytes', 'Request Memory', 'phpfpm.request_mem', 'line'],
56
+ 'options': [None, 'PHP-FPM Request Memory', 'kilobytes', 'request memory', 'phpfpm.request_mem', 'line'],
57
'lines': [
58
["maxReqMem", 'max request memory'],
59
["avgReqMem", 'average request memory']
@@ -137,6 +137,3 @@ class Service(UrlService):
137
if len(data) == 0:
138
return None
139
return data
140
-
141
- def check(self):
142
- return UrlService.check(self)
python.d/python_modules/base.py
+183
-94
@@ -116,33 +116,21 @@ class SimpleService(threading.Thread):
116
Return value presents exit status of update()
117
:return: boolean
118
"""
119
- t_start = time.time()
120
- timetable = self.timetable
119
+ t_start = float(time.time())
120
chart_name = self.chart_name
122
- # check if it is time to execute job update() function
123
- if timetable['next'] > t_start:
124
- self.debug(chart_name, "will be run in", str(int((timetable['next'] - t_start) * 1000)), "ms")
125
- return True
121
127
- since_last = int((t_start - timetable['last']) * 1000000)
128
- self.debug(chart_name,
129
- "ready to run, after", str(int((t_start - timetable['last']) * 1000)),
130
- "ms (update_every:", str(timetable['freq'] * 1000),
131
- "ms, latency:", str(int((t_start - timetable['next']) * 1000)), "ms")
122
+ since_last = int((t_start - self.timetable['last']) * 1000000)
123
if self.__first_run:
124
since_last = 0
125
+
126
if not self.update(since_last):
127
self.error("update function failed.")
128
return False
137
- t_end = time.time()
138
- self.timetable['next'] = t_end - (t_end % timetable['freq']) + timetable['freq']
129
+
130
# draw performance graph
140
- run_time = str(int((t_end - t_start) * 1000))
141
- # noinspection SqlNoDataSourceInspection
131
+ run_time = int((time.time() - t_start) * 1000)
132
print("BEGIN netdata.plugin_pythond_%s %s\nSET run_time = %s\nEND\n" %
143
- (self.chart_name, str(since_last), run_time))
144
- # sys.stdout.write("BEGIN netdata.plugin_pythond_%s %s\nSET run_time = %s\nEND\n" %
145
- # (self.chart_name, str(since_last), run_time))
133
+ (self.chart_name, str(since_last), str(run_time)))
134
135
self.debug(chart_name, "updated in", str(run_time), "ms")
136
self.timetable['last'] = t_start
@@ -155,23 +143,49 @@ class SimpleService(threading.Thread):
143
Exits when job failed or timed out.
144
:return: None
145
"""
158
- self.timetable['last'] = time.time()
146
+ step = float(self.timetable['freq'])
147
+ penalty = 0
148
+ self.timetable['last'] = float(time.time() - step)
149
+ self.debug("starting data collection - update frequency:", str(step), " retries allowed:", str(self.retries))
150
while True: # run forever, unless something is wrong
151
+ now = float(time.time())
152
+ next = self.timetable['next'] = now - (now % step) + step + penalty
153
+
154
+ # it is important to do this in a loop
155
+ # sleep() is interruptable
156
+ while now < next:
157
+ self.debug("sleeping for", str(next - now), "secs to reach frequency of", str(step), "secs, now:", str(now), " next:", str(next), " penalty:", str(penalty))
158
+ time.sleep(next - now)
159
+ now = float(time.time())
160
+
161
+ # do the job
162
try:
163
status = self._run_once()
164
except Exception as e:
163
- self.error("Something wrong: ", str(e))
165
+ self.alert("internal error - aborting data collection: " + str(e))
166
return
165
- if status: # handle retries if update failed
166
- time.sleep(self.timetable['next'] - time.time())
167
+
168
+ if status:
169
+ # it is good
170
self.retries_left = self.retries
171
+ penalty = 0
172
else:
173
+ # it failed
174
self.retries_left -= 1
175
if self.retries_left <= 0:
171
- self.error("no more retries. Exiting")
172
- return
176
+ if penalty == 0:
177
+ penalty = float(self.retries * step) / 2
178
+ else:
179
+ penalty *= 1.5
180
+
181
+ if penalty > 600:
182
+ penalty = 600
183
+
184
+ self.retries_left = self.retries
185
+ self.alert("failed to collect data for " + str(self.retries) + " times - increasing penalty to " + str(penalty) + " sec and trying again")
186
+
187
else:
174
- time.sleep(self.timetable['freq'])
188
+ self.error("failed to collect data - " + str(self.retries_left) + " retries left - penalty: " + str(penalty) + " sec")
189
190
# --- CHART ---
191
@@ -318,6 +332,12 @@ class SimpleService(threading.Thread):
332
"""
333
msg.error(self.chart_name, *params)
334
335
+ def alert(self, *params):
336
+ """
337
+ Show error message on stderr
338
+ """
339
+ msg.alert(self.chart_name, *params)
340
+
341
def debug(self, *params):
342
"""
343
Show debug message on stderr
@@ -345,10 +365,18 @@ class SimpleService(threading.Thread):
365
:return: boolean
366
"""
367
self.debug("Module", str(self.__module__), "doesn't implement check() function. Using default.")
348
- if self._get_data() is None or len(self._get_data()) == 0:
368
+ data = self._get_data()
369
+
370
+ if data is None:
371
+ self.debug("failed to receive data during check().")
372
return False
350
- else:
351
- return True
373
+
374
+ if len(data) == 0:
375
+ self.debug("empty data during check().")
376
+ return False
377
+
378
+ self.debug("successfully received data during check(): '" + str(data) + "'")
379
+ return True
380
381
def create(self):
382
"""
@@ -357,6 +385,7 @@ class SimpleService(threading.Thread):
385
"""
386
data = self._get_data()
387
if data is None:
388
+ self.debug("failed to receive data during create().")
389
return False
390
391
idx = 0
@@ -380,7 +409,7 @@ class SimpleService(threading.Thread):
409
"""
410
data = self._get_data()
411
if data is None:
383
- self.debug("_get_data() returned no data")
412
+ self.debug("failed to receive data during update().")
413
return False
414
415
updated = False
@@ -506,8 +535,83 @@ class SocketService(SimpleService):
535
self.unix_socket = None
536
self.request = ""
537
self.__socket_config = None
538
+ self.__empty_request = "".encode()
539
SimpleService.__init__(self, configuration=configuration, name=name)
540
541
+ def _socketerror(self, message=None):
542
+ if self.unix_socket is not None:
543
+ self.error("unix socket '" + self.unix_socket + "':", message)
544
+ else:
545
+ if self.__socket_config is not None:
546
+ af, socktype, proto, canonname, sa = self.__socket_config
547
+ self.error("socket to '" + str(sa[0]) + "' port " + str(sa[1]) + ":", message)
548
+ else:
549
+ self.error("unknown socket:", message)
550
+
551
+ def _connect2socket(self, res=None):
552
+ """
553
+ Connect to a socket, passing the result of getaddrinfo()
554
+ :return: boolean
555
+ """
556
+ if res is None:
557
+ res = self.__socket_config
558
+ if res is None:
559
+ self.error("Cannot create socket to 'None':")
560
+ return False
561
+
562
+ af, socktype, proto, canonname, sa = res
563
+ try:
564
+ self.debug("creating socket to '" + str(sa[0]) + "', port " + str(sa[1]))
565
+ self._sock = socket.socket(af, socktype, proto)
566
+ except socket.error as e:
567
+ self.error("Failed to create socket to '" + str(sa[0]) + "', port " + str(sa[1]) + ":", str(e))
568
+ self._sock = None
569
+ self.__socket_config = None
570
+ return False
571
+
572
+ try:
573
+ self.debug("connecting socket to '" + str(sa[0]) + "', port " + str(sa[1]))
574
+ self._sock.connect(sa)
575
+ except socket.error as e:
576
+ self.error("Failed to connect to '" + str(sa[0]) + "', port " + str(sa[1]) + ":", str(e))
577
+ self._disconnect()
578
+ self.__socket_config = None
579
+ return False
580
+
581
+ self.debug("connected to '" + str(sa[0]) + "', port " + str(sa[1]))
582
+ self.__socket_config = res
583
+ return True
584
+
585
+ def _connect2unixsocket(self):
586
+ """
587
+ Connect to a unix socket, given its filename
588
+ :return: boolean
589
+ """
590
+ if self.unix_socket is None:
591
+ self.error("cannot connect to unix socket 'None'")
592
+ return False
593
+
594
+ try:
595
+ self.debug("attempting DGRAM unix socket '" + str(self.unix_socket) + "'")
596
+ self._sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
597
+ self._sock.connect(self.unix_socket)
598
+ self.debug("connected DGRAM unix socket '" + str(self.unix_socket) + "'")
599
+ return True
600
+ except socket.error as e:
601
+ self.debug("Failed to connect DGRAM unix socket '" + str(self.unix_socket) + "':", str(e))
602
+
603
+ try:
604
+ self.debug("attempting STREAM unix socket '" + str(self.unix_socket) + "'")
605
+ self._sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
606
+ self._sock.connect(self.unix_socket)
607
+ self.debug("connected STREAM unix socket '" + str(self.unix_socket) + "'")
608
+ return True
609
+ except socket.error as e:
610
+ self.debug("Failed to connect STREAM unix socket '" + str(self.unix_socket) + "':", str(e))
611
+ self.error("Failed to connect to unix socket '" + str(self.unix_socket) + "':", str(e))
612
+ self._sock = None
613
+ return False
614
+
615
def _connect(self):
616
"""
617
Recreate socket and connect to it since sockets cannot be reused after closing
@@ -515,63 +619,38 @@ class SocketService(SimpleService):
619
:return:
620
"""
621
try:
518
- if self.unix_socket is None:
519
- if self.__socket_config is None:
520
- # establish ipv6 or ipv4 connection.
521
- for res in socket.getaddrinfo(self.host, self.port, socket.AF_UNSPEC, socket.SOCK_STREAM):
522
- try:
523
- # noinspection SpellCheckingInspection
524
- af, socktype, proto, canonname, sa = res
525
- self._sock = socket.socket(af, socktype, proto)
526
- except socket.error as e:
527
- self.debug("Cannot create socket:", str(e))
528
- self._sock = None
529
- continue
530
- try:
531
- self._sock.connect(sa)
532
- except socket.error as e:
533
- self.debug("Cannot connect to socket:", str(e))
534
- self._disconnect()
535
- continue
536
- self.__socket_config = res
537
- break
538
- else:
539
- # connect to socket with previously established configuration
540
- try:
541
- af, socktype, proto, canonname, sa = self.__socket_config
542
- self._sock = socket.socket(af, socktype, proto)
543
- self._sock.connect(sa)
544
- except socket.error as e:
545
- self.debug("Cannot create or connect to socket:", str(e))
546
- self._disconnect()
622
+ if self.unix_socket is not None:
623
+ self._connect2unixsocket()
624
+
625
else:
548
- # connect to unix socket
549
- try:
550
- self._sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
551
- self._sock.connect(self.unix_socket)
552
- except socket.error:
553
- self._sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
554
- self._sock.connect(self.unix_socket)
626
+ if self.__socket_config is not None:
627
+ self._connect2socket()
628
+ else:
629
+ for res in socket.getaddrinfo(self.host, self.port, socket.AF_UNSPEC, socket.SOCK_STREAM):
630
+ if self._connect2socket(res): break
631
632
except Exception as e:
557
- self.error(str(e),
558
- "Cannot create socket with following configuration: host:", str(self.host),
559
- "port:", str(self.port),
560
- "socket:", str(self.unix_socket))
633
self._sock = None
562
- self._sock.setblocking(0)
634
+ self.__socket_config = None
635
+
636
+ if self._sock is not None:
637
+ self._sock.setblocking(0)
638
+ self._sock.settimeout(5)
639
+ self.debug("set socket timeout to: " + str(self._sock.gettimeout()))
640
641
def _disconnect(self):
642
"""
643
Close socket connection
644
:return:
645
"""
569
- try:
570
- self._sock.shutdown(2) # 0 - read, 1 - write, 2 - all
571
- self._sock.close()
572
- except Exception:
573
- pass
574
- self._sock = None
646
+ if self._sock is not None:
647
+ try:
648
+ self.debug("closing socket")
649
+ self._sock.shutdown(2) # 0 - read, 1 - write, 2 - all
650
+ self._sock.close()
651
+ except Exception:
652
+ pass
653
+ self._sock = None
654
655
def _send(self):
656
"""
@@ -579,15 +658,13 @@ class SocketService(SimpleService):
658
:return: boolean
659
"""
660
# Send request if it is needed
582
- if self.request != "".encode():
661
+ if self.request != self.__empty_request:
662
try:
663
+ self.debug("sending request:", str(self.request))
664
self._sock.send(self.request)
665
except Exception as e:
666
+ self._socketerror("error sending request:" + str(e))
667
self._disconnect()
587
- self.error(str(e),
588
- "used configuration: host:", str(self.host),
589
- "port:", str(self.port),
590
- "socket:", str(self.unix_socket))
668
return False
669
return True
670
@@ -598,24 +675,28 @@ class SocketService(SimpleService):
675
"""
676
data = ""
677
while True:
678
+ self.debug("receiving response")
679
try:
602
- ready_to_read, _, in_error = select.select([self._sock], [], [], 5)
680
+ buf = self._sock.recv(4096)
681
except Exception as e:
604
- self.debug("SELECT", str(e))
682
+ self._socketerror("failed to receive response:" + str(e))
683
self._disconnect()
684
break
607
- if len(ready_to_read) > 0:
608
- buf = self._sock.recv(4096)
609
- if len(buf) == 0 or buf is None: # handle server disconnect
610
- break
611
- data += buf.decode('utf-8', 'ignore')
612
- if self._check_raw_data(data):
613
- break
614
- else:
615
- self.error("Socket timed out.")
685
+
686
+ if buf is None or len(buf) == 0: # handle server disconnect
687
+ if data == "":
688
+ self._socketerror("unexpectedly disconnected")
689
+ else:
690
+ self.debug("server closed the connection")
691
self._disconnect()
692
break
693
694
+ self.debug("received data:", str(buf))
695
+ data += buf.decode('utf-8', 'ignore')
696
+ if self._check_raw_data(data):
697
+ break
698
+
699
+ self.debug("final response:", str(data))
700
return data
701
702
def _get_raw_data(self):
@@ -625,6 +706,8 @@ class SocketService(SimpleService):
706
"""
707
if self._sock is None:
708
self._connect()
709
+ if self._sock is None:
710
+ return None
711
712
# Send request if it is needed
713
if not self._send():
@@ -654,6 +737,7 @@ class SocketService(SimpleService):
737
self.name = ""
738
else:
739
self.name = str(self.name)
740
+
741
try:
742
self.unix_socket = str(self.configuration['socket'])
743
except (KeyError, TypeError):
@@ -667,10 +751,12 @@ class SocketService(SimpleService):
751
self.port = int(self.configuration['port'])
752
except (KeyError, TypeError):
753
self.debug("No port specified. Using: '" + str(self.port) + "'")
754
+
755
try:
756
self.request = str(self.configuration['request'])
757
except (KeyError, TypeError):
758
self.debug("No request specified. Using: '" + str(self.request) + "'")
759
+
760
self.request = self.request.encode()
761
762
def check(self):
@@ -724,7 +810,7 @@ class LogService(SimpleService):
810
try:
811
self.log_path = str(self.configuration['path'])
812
except (KeyError, TypeError):
727
- self.error("No path to log specified. Using: '" + self.log_path + "'")
813
+ self.info("No path to log specified. Using: '" + self.log_path + "'")
814
815
if os.access(self.log_path, os.R_OK):
816
return True
@@ -779,13 +865,14 @@ class ExecutableService(SimpleService):
865
try:
866
self.command = str(self.configuration['command'])
867
except (KeyError, TypeError):
782
- self.error("No command specified. Using: '" + self.command + "'")
868
+ self.info("No command specified. Using: '" + self.command + "'")
869
command = self.command.split(' ')
870
871
for arg in command[1:]:
872
if any(st in arg for st in self.bad_substrings):
873
self.error("Bad command argument:" + " ".join(self.command[1:]))
874
return False
875
+
876
# test command and search for it in /usr/sbin or /sbin when failed
877
base = command[0].split('/')[-1]
878
if self._get_raw_data() is None:
@@ -793,8 +880,10 @@ class ExecutableService(SimpleService):
880
command[0] = prefix + base
881
if os.path.isfile(command[0]):
882
break
883
+
884
self.command = command
885
if self._get_data() is None or len(self._get_data()) == 0:
886
self.error("Command", self.command, "returned no data")
887
return False
888
+
889
return True
python.d/redis.chart.py
+77
-41
@@ -19,40 +19,58 @@ retries = 60
19
# 'unix_socket': None
20
# }}
21
22
-ORDER = ['operations', 'hit_rate', 'memory', 'keys', 'clients', 'slaves']
22
+ORDER = ['operations', 'hit_rate', 'memory', 'keys', 'net', 'connections', 'clients', 'slaves', 'persistence']
23
24
CHARTS = {
25
'operations': {
26
- 'options': [None, 'Operations', 'operations/s', 'Statistics', 'redis.operations', 'line'],
26
+ 'options': [None, 'Redis 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, 'Redis 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, 'Redis 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
+ 'net': {
43
+ 'options': [None, 'Redis Bandwidth', 'kilobits/s', 'network', 'redis.net', 'area'],
44
+ 'lines': [
45
+ ['total_net_input_bytes', 'in', 'incremental', 8, 1024],
46
+ ['total_net_output_bytes', 'out', 'incremental', -8, 1024]
47
+ ]},
48
'keys': {
42
- 'options': [None, 'Database keys', 'keys', 'Keys', 'redis.keys', 'line'],
49
+ 'options': [None, 'Redis Keys per Database', 'keys', 'keys', 'redis.keys', 'line'],
50
'lines': [
51
# lines are created dynamically in `check()` method
52
]},
53
+ 'connections': {
54
+ 'options': [None, 'Redis Connections', 'connections/s', 'connections', 'redis.connections', 'line'],
55
+ 'lines': [
56
+ ['total_connections_received', 'received', 'incremental', 1],
57
+ ['rejected_connections', 'rejected', 'incremental', -1]
58
+ ]},
59
'clients': {
47
- 'options': [None, 'Clients', 'clients', 'Clients', 'redis.clients', 'line'],
60
+ 'options': [None, 'Redis Clients', 'clients', 'connections', 'redis.clients', 'line'],
61
'lines': [
49
- ['connected_clients', 'connected', 'absolute'],
50
- ['blocked_clients', 'blocked', 'absolute']
62
+ ['connected_clients', 'connected', 'absolute', 1],
63
+ ['blocked_clients', 'blocked', 'absolute', -1]
64
]},
65
'slaves': {
53
- 'options': [None, 'Slaves', 'slaves', 'Replication', 'redis.slaves', 'line'],
66
+ 'options': [None, 'Redis Slaves', 'slaves', 'replication', 'redis.slaves', 'line'],
67
'lines': [
68
['connected_slaves', 'connected', 'absolute']
69
+ ]},
70
+ 'persistence': {
71
+ 'options': [None, 'Redis Persistence Changes Since Last Save', 'changes', 'persistence', 'redis.rdb_changes', 'line'],
72
+ 'lines': [
73
+ ['rdb_changes_since_last_save', 'changes', 'absolute']
74
]}
75
}
76
@@ -61,57 +79,74 @@ class Service(SocketService):
79
def __init__(self, configuration=None, name=None):
80
SocketService.__init__(self, configuration=configuration, name=name)
81
self.request = "INFO\r\n"
64
- self.host = "localhost"
65
- self.port = 6379
66
- self.unix_socket = None
82
self.order = ORDER
83
self.definitions = CHARTS
84
self._keep_alive = True
85
self.chart_name = ""
71
- self.passwd = None
86
+ self.passwd = None
87
+ self.port = 6379
88
+ if 'port' in configuration:
89
+ self.port = configuration['port']
90
if 'pass' in configuration:
91
self.passwd = configuration['pass']
92
+ if 'host' in configuration:
93
+ self.host = configuration['host']
94
+ if 'socket' in configuration:
95
+ self.unix_socket = configuration['socket']
96
97
def _get_data(self):
98
"""
99
Get data from socket
100
:return: dict
101
"""
102
+ if self.passwd:
103
+ info_request = self.request
104
+ self.request = "AUTH " + self.passwd + "\r\n"
105
+ raw = self._get_raw_data().strip()
106
+ if raw != "+OK":
107
+ self.error("invalid password")
108
+ return None
109
+ self.request = info_request
110
+
111
+ response = self._get_raw_data()
112
+ if response is None:
113
+ # error has already been logged
114
+ return None
115
+
116
try:
81
- if self.passwd:
82
- info_request = self.request
83
- self.request = "AUTH " + self.passwd + "\r\n"
84
- raw = self._get_raw_data().strip()
85
- if raw != "+OK":
86
- self.error("invalid password")
87
- return None
88
- self.request = info_request
89
- raw = self._get_raw_data().split("\n")
117
+ parsed = response.split("\n")
118
except AttributeError:
91
- self.error("no data received")
119
+ self.error("response is invalid/empty")
120
return None
121
+
122
data = {}
94
- for line in raw:
95
- if line.startswith(('instantaneous', 'keyspace', 'used_memory', 'connected', 'blocked')):
96
- try:
97
- t = line.split(':')
98
- data[t[0]] = int(t[1])
99
- except (IndexError, ValueError):
100
- pass
101
- elif line.startswith('db'):
123
+ for line in parsed:
124
+ if len(line) < 5 or line[0] == '$' or line[0] == '#':
125
+ continue
126
+
127
+ if line.startswith('db'):
128
tmp = line.split(',')[0].replace('keys=', '')
129
record = tmp.split(':')
104
- data[record[0]] = int(record[1])
130
+ data[record[0]] = record[1]
131
+ continue
132
+
133
+ try:
134
+ t = line.split(':')
135
+ data[t[0]] = t[1]
136
+ except (IndexError, ValueError):
137
+ self.debug("invalid line received: " + str(line))
138
+ pass
139
+
140
+ if len(data) == 0:
141
+ self.error("received data doesn't have any records")
142
+ return None
143
+
144
try:
106
- data['hit_rate'] = int((data['keyspace_hits'] / float(data['keyspace_hits'] + data['keyspace_misses'])) * 100)
145
+ data['hit_rate'] = (int(data['keyspace_hits']) * 100) / (int(data['keyspace_hits']) + int(data['keyspace_misses']))
146
except:
147
data['hit_rate'] = 0
148
110
- if len(data) == 0:
111
- self.error("received data doesn't have needed records")
112
- return None
113
- else:
114
- return data
149
+ return data
150
151
def _check_raw_data(self, data):
152
"""
@@ -123,14 +158,15 @@ class Service(SocketService):
158
length = len(data)
159
supposed = data.split('\n')[0][1:]
160
offset = len(supposed) + 4 # 1 dollar sing, 1 new line character + 1 ending sequence '\r\n'
126
- if (not supposed.isdigit()) :
161
+ if not supposed.isdigit():
162
return True
163
supposed = int(supposed)
164
+
165
if length - offset >= supposed:
166
+ self.debug("received full response from redis")
167
return True
131
- else:
132
- return False
168
169
+ self.debug("waiting more data from redis")
170
return False
171
172
def check(self):