1- #!/usr/bin/python
1+ #!/usr/bin/env python
22
3- import urllib2 , base64 , re , struct , time , socket , sys , datetime , os .path
3+ from __future__ import absolute_import
4+ from __future__ import division
5+ from __future__ import print_function
6+ from __future__ import unicode_literals
7+
8+ import sys
9+ is_python_3 = sys .version_info > (3 , 0 )
10+
11+ import argparse , base64 , re , struct , time , socket , datetime , os .path
12+
13+ if is_python_3 :
14+ import urllib .request as urllib # Python 3
15+ import codecs
16+ else :
17+ import urllib2 as urllib # Python 2
418
519try :
620 import json
721except :
822 import simplejson as json
923
10- zabbix_host = '127.0.0.1' # Zabbix server IP
11- zabbix_port = 10051 # Zabbix server port
12- hostname = 'Zabbix Agent' # Name of monitored host, like it shows in zabbix web ui
13- time_delta = 1 # grep interval in minutes
14-
15- # URL to nginx stat (http_stub_status_module)
16- stat_url = 'https://nginx.server/nginx_stat'
17-
18- # Nginx log file path
19- nginx_log_file_path = '/var/log/nginx/access.log'
20-
21- # Optional Basic Auth
22- username = 'user'
23- password = 'pass'
24+ parser = argparse .ArgumentParser ()
25+ parser .add_argument ('--use-ssl' , default = False , action = "store_true" , help = 'Enable SSL' )
26+ parser .add_argument ('--ssl-cafile' , default = '/etc/zabbix/certs/cacert.pem' , type = str , help = 'CA PEM file path' )
27+ parser .add_argument ('--ssl-certfile' , default = '/etc/zabbix/certs/agent.crt' , type = str , help = 'Client/Agent certificate file path' )
28+ parser .add_argument ('--ssl-keyfile' , default = '/etc/zabbix/certs/agent.key' , type = str , help = 'Client/Agent private key file path' )
29+ parser .add_argument ('--zabbix-host' , default = '127.0.0.1' , type = str , help = 'Zabbix server host' )
30+ parser .add_argument ('--zabbix-port' , default = 10051 , type = int , help = 'Zabbix server port' )
31+ parser .add_argument ('--monitored-hostname' , default = 'Zabbix agent' , type = str , help = 'Name of monitored host, like it shows in zabbix web ui' )
32+ parser .add_argument ('--nginx-auth-username' , default = None , type = str , help = 'Nginx authentication username' )
33+ parser .add_argument ('--nginx-auth-password' , default = None , type = str , help = 'Nginx authentication password' )
34+ parser .add_argument ('--nginx-status-module-url' , default = 'http://127.0.0.1:55777/nginx-status' , type = str , help = 'URL to retrieve Nginx status (http_stub_status_module)' )
35+ parser .add_argument ('--nginx-accesslog' , default = '/var/log/nginx/access.log' , type = str , help = 'Nginx access log file path' )
36+ parser .add_argument ('--print-metric' , default = - 1 , type = int , help = 'Print an specific metric' )
37+ parser .add_argument ('--skip-nginx-accesslog' , default = False , action = "store_true" , help = 'Don\' t gather data from the Nginx access log' )
38+ parser .add_argument ('--skip-nginx-status-module' , default = False , action = "store_true" , help = 'Don\' t gather data from the Nginx status module (http_stub_status_module)' )
39+
40+ args = parser .parse_args ()
41+
42+ # grep interval in minutes
43+ time_delta = 1
2444
2545# Temp file, with log file cursor position
2646seek_file = '/tmp/nginx_log_stat'
2747
28-
2948class Metric (object ):
3049 def __init__ (self , host , key , value , clock = None ):
3150 self .host = host
@@ -38,81 +57,106 @@ def __repr__(self):
3857 return 'Metric(%r, %r, %r)' % (self .host , self .key , self .value )
3958 return 'Metric(%r, %r, %r, %r)' % (self .host , self .key , self .value , self .clock )
4059
41- def send_to_zabbix (metrics , zabbix_host = '127.0.0.1' , zabbix_port = 10051 ):
42-
60+ def send_to_zabbix (metrics , zabbix_host , zabbix_port ):
4361 j = json .dumps
4462 metrics_data = []
4563 for m in metrics :
4664 clock = m .clock or ('%d' % time .time ())
47- metrics_data .append (('{"host":%s,"key":%s,"value":%s,"clock":%s}' ) % (j (m .host ), j (m .key ), j (m .value ), j (clock )))
48- json_data = ('{"request":"sender data","data":[%s]}' ) % (',' .join (metrics_data ))
49- data_len = struct .pack ('<Q' , len (json_data ))
50- packet = 'ZBXD\x01 ' + data_len + json_data
65+ #print('%s, %s, %s, %s' % (m.host, m.key, m.value, clock))
66+ metrics_data .append ('{"host":%s,"key":%s,"value":%s,"clock":%s}' % (j (m .host ), j (m .key ), j (m .value ), j (clock )))
5167
52- #print packet
53- #print ':'.join(x.encode('hex') for x in packet)
68+ # Zabbix 3.0 protocol - https://www.zabbix.com/documentation/3.0/manual/appendix/items/activepassive
69+ data = ('{"request":"sender data","data":[%s]}' % (',' .join (metrics_data )))
70+ data_length = len (data )
71+ data_header = struct .pack ('<Q' , data_length ) # 8 bytes
72+ data_to_send = b'ZBXD\x01 ' + data_header + data .encode ('utf-8' )
73+ #print(repr(data_to_send))
5474
5575 try :
5676 zabbix = socket .socket ()
57- zabbix .connect ((zabbix_host , zabbix_port ))
58- zabbix .sendall (packet )
59- resp_hdr = _recv_all (zabbix , 13 )
60- if not resp_hdr .startswith ('ZBXD\x01 ' ) or len (resp_hdr ) != 13 :
61- print 'Wrong zabbix response'
77+ sock = zabbix
78+ if args .use_ssl :
79+ import ssl
80+ context = ssl .SSLContext (ssl .PROTOCOL_TLSv1_2 )
81+ context .options |= ssl .OP_NO_SSLv2
82+ context .options |= ssl .OP_NO_SSLv3
83+ context .verify_mode = ssl .CERT_REQUIRED
84+ context .load_verify_locations (cafile = args .ssl_cafile )
85+ context .load_cert_chain (certfile = args .ssl_certfile , keyfile = args .ssl_keyfile )
86+
87+ sock = context .wrap_socket (zabbix , server_hostname = zabbix_host )
88+
89+ sock .connect ((zabbix_host , zabbix_port ))
90+ sock .sendall (data_to_send )
91+
92+ resp_hdr = _recv_all (sock , 13 )
93+ if not resp_hdr .decode ('ascii' ).startswith ('ZBXD\x01 ' ) or len (resp_hdr ) != 13 :
94+ print ('Wrong zabbix response' )
6295 return False
96+
6397 resp_body_len = struct .unpack ('<Q' , resp_hdr [5 :])[0 ]
64- resp_body = zabbix .recv (resp_body_len )
65- zabbix .close ()
98+ resp_body = sock .recv (resp_body_len ).decode ('ascii' )
99+
100+ #sock.shutdown(socket.SHUT_RDWR)
101+ sock .close ()
66102
67103 resp = json .loads (resp_body )
68- #print resp
104+ #print( resp)
69105 if resp .get ('response' ) != 'success' :
70- print 'Got error from Zabbix: %s' % resp
106+ print ( 'Got error from Zabbix: %s' % resp )
71107 return False
72108 return True
73- except :
74- print 'Error while sending data to Zabbix'
109+ except Exception as e :
110+ print ('Error while sending data to Zabbix %s:' % e )
111+ #sock.shutdown(socket.SHUT_RDWR)
112+ sock .close ()
75113 return False
76114
77-
78115def _recv_all (sock , count ):
79- buf = ''
116+ buf = b ''
80117 while len (buf )< count :
81118 chunk = sock .recv (count - len (buf ))
82119 if not chunk :
83120 return buf
84121 buf += chunk
85- return buf
86-
122+ return buf # type(buf) == bytes
87123
88124def get (url , login , passwd ):
89- req = urllib2 .Request (url )
90- if login and passwd :
91- base64string = base64 .encodestring ('%s:%s' % (login , passwd )).replace ('\n ' , '' )
92- req .add_header ("Authorization" , "Basic %s" % base64string )
93- q = urllib2 .urlopen (req )
94- res = q .read ()
95- q .close ()
96- return res
125+ req = urllib .Request (url )
126+ if login and passwd :
127+ base64string = base64 .encodestring ('%s:%s' % (login , passwd )).replace ('\n ' , '' )
128+ req .add_header ("Authorization" , "Basic %s" % base64string )
129+ q = urllib .urlopen (req )
130+ if is_python_3 :
131+ res = codecs .decode (q .read (), 'utf-8' )
132+ else :
133+ res = q .read ().decode ('utf-8' )
134+ q .close ()
135+ return res
97136
98137def parse_nginx_stat (data ):
99- a = {}
100- # Active connections
101- a ['active_connections' ] = re .match (r'(.*):\s(\d*)' , data [0 ], re .M | re .I ).group (2 )
102- # Accepts
103- a ['accepted_connections' ] = re .match (r'\s(\d*)\s(\d*)\s(\d*)' , data [2 ], re .M | re .I ).group (1 )
104- # Handled
105- a ['handled_connections' ] = re .match (r'\s(\d*)\s(\d*)\s(\d*)' , data [2 ], re .M | re .I ).group (2 )
106- # Requests
107- a ['handled_requests' ] = re .match (r'\s(\d*)\s(\d*)\s(\d*)' , data [2 ], re .M | re .I ).group (3 )
108- # Reading
109- a ['header_reading' ] = re .match (r'(.*):\s(\d*)(.*):\s(\d*)(.*):\s(\d*)' , data [3 ], re .M | re .I ).group (2 )
110- # Writing
111- a ['body_reading' ] = re .match (r'(.*):\s(\d*)(.*):\s(\d*)(.*):\s(\d*)' , data [3 ], re .M | re .I ).group (4 )
112- # Waiting
113- a ['keepalive_connections' ] = re .match (r'(.*):\s(\d*)(.*):\s(\d*)(.*):\s(\d*)' , data [3 ], re .M | re .I ).group (6 )
114- return a
138+ a = {}
139+
140+ # Active connections
141+ a ['active_connections' ] = re .match (r'(.*):\s(\d*)' , data [0 ], re .M | re .I ).group (2 )
142+ # Accepts
143+ a ['accepted_connections' ] = re .match (r'\s(\d*)\s(\d*)\s(\d*)' , data [2 ], re .M | re .I ).group (1 )
144+ # Handled
145+ a ['handled_connections' ] = re .match (r'\s(\d*)\s(\d*)\s(\d*)' , data [2 ], re .M | re .I ).group (2 )
146+ # Requests
147+ a ['handled_requests' ] = re .match (r'\s(\d*)\s(\d*)\s(\d*)' , data [2 ], re .M | re .I ).group (3 )
148+ # Reading
149+ a ['header_reading' ] = re .match (r'(.*):\s(\d*)(.*):\s(\d*)(.*):\s(\d*)' , data [3 ], re .M | re .I ).group (2 )
150+ # Writing
151+ a ['body_reading' ] = re .match (r'(.*):\s(\d*)(.*):\s(\d*)(.*):\s(\d*)' , data [3 ], re .M | re .I ).group (4 )
152+ # Waiting
153+ a ['keepalive_connections' ] = re .match (r'(.*):\s(\d*)(.*):\s(\d*)(.*):\s(\d*)' , data [3 ], re .M | re .I ).group (6 )
154+
155+ if not is_python_3 :
156+ for key , value in a .iteritems ():
157+ a [key ] = value .decode ('utf-8' )
115158
159+ return a
116160
117161def read_seek (file ):
118162 if os .path .isfile (file ):
@@ -131,66 +175,68 @@ def write_seek(file, value):
131175 f .write (value )
132176 f .close ()
133177
178+ #print('[12/Mar/2014:03:21:13 +0400]')
134179
135- #print '[12/Mar/2014:03:21:13 +0400]'
136-
137- d = datetime .datetime .now ()- datetime .timedelta (minutes = time_delta )
138- minute = int (time .mktime (d .timetuple ()) / 60 )* 60
139- d = d .strftime ('%d/%b/%Y:%H:%M' )
140-
141- total_rps = 0
142- rps = [0 ]* 60
143- tps = [0 ]* 60
144- res_code = {}
180+ if not args .skip_nginx_accesslog :
181+ d = datetime .datetime .now ()- datetime .timedelta (minutes = time_delta )
182+ minute = int (time .mktime (d .timetuple ()) / 60 )* 60
183+ d = d .strftime ('%d/%b/%Y:%H:%M' )
145184
146- nf = open (nginx_log_file_path , 'r' )
185+ total_rps = 0
186+ rps = [0 ]* 60
187+ tps = [0 ]* 60
188+ res_code = {}
147189
148- new_seek = seek = read_seek ( seek_file )
190+ nf = open ( args . nginx_accesslog , 'r' )
149191
150- # if new log file, don't do seek
151- if os .path .getsize (nginx_log_file_path ) > seek :
152- nf .seek (seek )
192+ new_seek = seek = read_seek (seek_file )
153193
154- line = nf .readline ()
155- while line :
156- if d in line :
157- new_seek = nf .tell ()
158- total_rps += 1
159- sec = int (re .match ('(.*):(\d+):(\d+):(\d+)\s' , line ).group (4 ))
160- code = re .match (r'(.*)"\s(\d*)\s' , line ).group (2 )
161- if code in res_code :
162- res_code [code ] += 1
163- else :
164- res_code [code ] = 1
194+ # if new log file, don't do seek
195+ if os .path .getsize (args .nginx_accesslog ) > seek :
196+ nf .seek (seek )
165197
166- rps [sec ] += 1
167198 line = nf .readline ()
168-
169- if total_rps != 0 :
170- write_seek (seek_file , str (new_seek ))
171-
172- nf .close ()
173-
174- metric = (len (sys .argv ) >= 2 ) and re .match (r'nginx\[(.*)\]' , sys .argv [1 ], re .M | re .I ).group (1 ) or False
175- data = get (stat_url , username , password ).split ('\n ' )
199+ while line :
200+ if d in line :
201+ new_seek = nf .tell ()
202+ total_rps += 1
203+ sec = int (re .match ('(.*):(\d+):(\d+):(\d+)\s' , line ).group (4 ))
204+ code = re .match (r'(.*)"\s(\d*)\s' , line ).group (2 )
205+ if code in res_code :
206+ res_code [code ] += 1
207+ else :
208+ res_code [code ] = 1
209+
210+ rps [sec ] += 1
211+ line = nf .readline ()
212+
213+ if total_rps != 0 :
214+ write_seek (seek_file , str (new_seek ))
215+
216+ nf .close ()
217+
218+ should_print_metric = (args .print_metric != - 1 ) and re .match (r'nginx\[(.*)\]' , args .debug_metric , re .M | re .I ).group (1 ) or False
219+ data = get (args .nginx_status_module_url , args .nginx_auth_username , args .nginx_auth_password ).split ('\n ' )
176220data = parse_nginx_stat (data )
177221
178222data_to_send = []
179223
180224# Adding the metrics to response
181- if not metric :
225+ if should_print_metric :
226+ print (data [metric ])
227+
228+ if not args .skip_nginx_status_module :
182229 for i in data :
183- data_to_send .append (Metric (hostname , ('nginx[%s]' % i ), data [i ]))
184- else :
185- print data [metric ]
230+ data_to_send .append (Metric (args .monitored_hostname , ('nginx[%s]' % i ), data [i ]))
186231
187- # Adding the request per seconds to response
188- for t in range (0 ,60 ):
189- data_to_send .append (Metric (hostname , 'nginx[rps]' , rps [t ], minute + t ))
232+ if not args .skip_nginx_accesslog :
233+ # Add the request per seconds to response
234+ for t in range (0 ,60 ):
235+ data_to_send .append (Metric (args .monitored_hostname , 'nginx[rps]' , rps [t ], minute + t ))
190236
191- # Adding the response codes stats to respons
192- for t in res_code :
193- data_to_send .append (Metric (hostname , ('nginx[%s]' % t ), res_code [t ]))
237+ # Add the response codes stats to response
238+ for t in res_code :
239+ data_to_send .append (Metric (args . monitored_hostname , ('nginx[%s]' % t ), res_code [t ]))
194240
195241
196- send_to_zabbix (data_to_send , zabbix_host , zabbix_port )
242+ send_to_zabbix (data_to_send , args . zabbix_host , args . zabbix_port )
0 commit comments