Mercurial > ~darius > hgwebdir.cgi > adslstats
comparison adslstats.py @ 9:8b5958404f81
Add support for running as a munin plugin and a simple wrapper script.
author | Daniel O'Connor <darius@dons.net.au> |
---|---|
date | Thu, 08 Jan 2015 11:17:40 +1030 |
parents | e69ad89061dc |
children | 88cf0a88c826 |
comparison
equal
deleted
inserted
replaced
8:e69ad89061dc | 9:8b5958404f81 |
---|---|
1 #!/usr/bin/env python | 1 #!/usr/bin/env python2 |
2 ############################################################################ | 2 ############################################################################ |
3 # | 3 # |
4 # Parse ADSL link stats for Billion 7300G & generate RRD archives & graphs | 4 # Parse ADSL link stats for Billion 7300G & generate RRD archives & graphs |
5 # | 5 # |
6 ############################################################################ | 6 ############################################################################ |
7 # | 7 # |
8 # Copyright (C) 2013 Daniel O'Connor. All rights reserved. | 8 # Copyright (C) 2015 Daniel O'Connor. All rights reserved. |
9 # | 9 # |
10 # Redistribution and use in source and binary forms, with or without | 10 # Redistribution and use in source and binary forms, with or without |
11 # modification, are permitted provided that the following conditions | 11 # modification, are permitted provided that the following conditions |
12 # are met: | 12 # are met: |
13 # 1. Redistributions of source code must retain the above copyright | 13 # 1. Redistributions of source code must retain the above copyright |
33 import ConfigParser | 33 import ConfigParser |
34 import optparse | 34 import optparse |
35 import os | 35 import os |
36 import re | 36 import re |
37 import rrdtool | 37 import rrdtool |
38 import sys | |
38 import time | 39 import time |
39 import urllib | 40 import urllib |
40 from bs4 import BeautifulSoup | 41 from bs4 import BeautifulSoup |
41 | 42 |
42 conf = ConfigParser.ConfigParser() | 43 conf = ConfigParser.ConfigParser() |
55 opts.add_option('-v', '--verbose', action="store_true", default=False, | 56 opts.add_option('-v', '--verbose', action="store_true", default=False, |
56 help="Enable debug output") | 57 help="Enable debug output") |
57 opts.add_option('-g', '--graph', action="store_true", default=False, | 58 opts.add_option('-g', '--graph', action="store_true", default=False, |
58 help="Generate a graph") | 59 help="Generate a graph") |
59 opts.add_option('-u', '--update', action="store_true", default=False, | 60 opts.add_option('-u', '--update', action="store_true", default=False, |
60 help="Update RRD") | 61 help="Update RRD (implies -d)") |
62 opts.add_option('-m', '--munin', action="store", default=None, | |
63 help="Output munin data for ARG") | |
61 opts.add_option('-a', '--authname', action="store", default=conf.get('global', 'username'), | 64 opts.add_option('-a', '--authname', action="store", default=conf.get('global', 'username'), |
62 help="Username to login to modem") | 65 help="Username to login to modem") |
63 opts.add_option('-p', '--password', action="store", default=conf.get('global', 'password'), | 66 opts.add_option('-p', '--password', action="store", default=conf.get('global', 'password'), |
64 help="Password to login to modem") | 67 help="Password to login to modem") |
65 opts.add_option('-n', '--name', action="store", default=conf.get('global', 'name'), | 68 opts.add_option('-n', '--name', action="store", default=conf.get('global', 'name'), |
86 return """Line Rate - Up: %d kbits, Down %d kbits | 89 return """Line Rate - Up: %d kbits, Down %d kbits |
87 Noise Margin - Up: %.1f dB, Down %.1f dB | 90 Noise Margin - Up: %.1f dB, Down %.1f dB |
88 Attenuation - Up: %.1f dB, Down %.1f dB""" % (self.upstream, self.downstream, | 91 Attenuation - Up: %.1f dB, Down %.1f dB""" % (self.upstream, self.downstream, |
89 self.nmup, self.nmdown, | 92 self.nmup, self.nmdown, |
90 self.attenup, self.attendown) | 93 self.attenup, self.attendown) |
91 | |
92 def getstats(f): | 94 def getstats(f): |
93 s = BeautifulSoup(f) | 95 s = BeautifulSoup(f) |
94 a = s.findAll('tr') | 96 a = s.findAll('tr') |
95 | 97 |
96 for i in statsdict: | 98 for i in statsdict: |
115 | 117 |
116 # Setup RRD | 118 # Setup RRD |
117 # We expect data to be logged every 5 minutes | 119 # We expect data to be logged every 5 minutes |
118 # Average 12 5 minute points -> hourly stats (keep 168 - a weeks worth) | 120 # Average 12 5 minute points -> hourly stats (keep 168 - a weeks worth) |
119 # Average 288 5 minute points -> daily stats (keep 365 - a years worth) | 121 # Average 288 5 minute points -> daily stats (keep 365 - a years worth) |
120 # Detemine minimum & maximum for an hour and keep a weeks worth. | 122 # Detemine minimum & maximum for an hour and keep a weeks worth. |
121 def makerrd(filename): | 123 def makerrd(filename): |
122 rrdtool.create(filename, | 124 rrdtool.create(filename, |
123 '--step', '300', | 125 '--step', '300', |
124 'DS:upstream:GAUGE:3600:32:25000', # Upstream (kbits) - 24mbit is ADSL2+ max | 126 'DS:upstream:GAUGE:3600:32:25000', # Upstream (kbits) - 24mbit is ADSL2+ max |
125 'DS:downstream:GAUGE:3600:32:25000', # Downstream (kbits) | 127 'DS:downstream:GAUGE:3600:32:25000', # Downstream (kbits) |
141 stats.nmup, | 143 stats.nmup, |
142 stats.nmdown, | 144 stats.nmdown, |
143 stats.attenup, | 145 stats.attenup, |
144 stats.attendown)) | 146 stats.attendown)) |
145 | 147 |
146 # Open the URL and call the parser, the update the RRD | 148 # Open the URL and call the parser |
147 def doupdate(): | 149 def getdata(): |
148 opener = urllib.FancyURLopener() | 150 opener = urllib.FancyURLopener() |
149 opener.prompt_user_passwd = lambda host, realm: (options.authname, options.password) | 151 opener.prompt_user_passwd = lambda host, realm: (options.authname, options.password) |
150 f = opener.open(statsurl) | 152 f = opener.open(statsurl) |
151 #f = open("adsl.html") | 153 #f = open("adsl.html") |
152 stats = getstats(f) | 154 stats = getstats(f) |
153 if stats == None: | 155 if stats == None: |
154 if options.verbose: | 156 return None |
155 print "Modem is offline" | 157 return stats |
156 return | |
157 if options.verbose: | |
158 print str(stats) | |
159 updaterrd(rrdname, int(time.time()), stats) | |
160 | 158 |
161 # Generate a graph | 159 # Generate a graph |
162 def gengraph(): | 160 def gengraph(): |
163 | 161 |
164 linkargs = ( | 162 linkargs = ( |
267 '--height', '256', | 265 '--height', '256', |
268 '--start', 'end - 365d', | 266 '--start', 'end - 365d', |
269 '--end', 'now', | 267 '--end', 'now', |
270 *signalargs) | 268 *signalargs) |
271 | 269 |
272 | |
273 if __name__ == "__main__": | 270 if __name__ == "__main__": |
274 if options.update: | 271 names = ['Noise Margin (up)', 'Noise Margin (down)', 'Attenuation (up)', 'Attenuation (down)'] |
275 try: | 272 if options.munin != None: |
276 os.stat(rrdname) | 273 # Handle the wrapper passing us $0 directly |
277 except OSError, e: | 274 tmp = options.munin.split('_') |
278 if e.errno == 2: | 275 if len(tmp) > 1: |
279 print "rrd not found, creating.." | 276 options.munin = tmp[-1] |
280 makerrd(rrdname) | 277 if options.munin not in ['signal', 'sync']: |
281 | 278 print "Unknown data type" |
282 doupdate() | 279 sys.exit(1) |
280 if len(args) > 0: | |
281 if args[0] == 'config': | |
282 if options.munin == 'signal': | |
283 print '''graph_category adsl | |
284 graph_title ADSL Signal Quality | |
285 graph_args --base 1000 -l 0 | |
286 graph_vlabel dB''' | |
287 for n in names: | |
288 name = n.translate(None, ' ()').lower() | |
289 print '''%s.label %s | |
290 %s.type GAUGE | |
291 %s.max 100 | |
292 %s.min 0''' % (name, n, name, name, name) | |
293 elif options.munin == 'sync': | |
294 print '''graph_category adsl | |
295 graph_title ADSL Sync Speed | |
296 graph_args --base 1024 -l 0 | |
297 graph_vlabel kbit/sec | |
298 up.label Up | |
299 up.type GAUGE | |
300 up.max 24000 | |
301 up.min 0 | |
302 up.label Down | |
303 up.type GAUGE | |
304 up.max 24000 | |
305 up.min 0''' | |
306 sys.exit(0) | |
307 if options.update or options.munin: | |
308 stats = getdata() | |
309 if stats == None: | |
310 if options.verbose: | |
311 print "Modem is offline" | |
312 | |
313 if stats != None: | |
314 if options.update: | |
315 try: | |
316 os.stat(rrdname) | |
317 except OSError, e: | |
318 if e.errno == 2: | |
319 print "rrd not found, creating.." | |
320 makerrd(rrdname) | |
321 | |
322 print '%d:%d:%d:%f:%f:%f:%f' % (int(time.time()), | |
323 stats.upstream, | |
324 stats.downstream, | |
325 stats.nmup, | |
326 stats.nmdown, | |
327 stats.attenup, | |
328 stats.attendown) | |
329 if options.munin != None: | |
330 if options.munin == 'signal': | |
331 print '''noisemarginup.value %.1f | |
332 noisemargindown.value %.1f | |
333 attenuationup.value %.1f | |
334 attenuationdown.value %.1f''' % (stats.nmup, stats.nmdown, stats.attenup, stats.attendown) | |
335 elif options.munin == 'sync': | |
336 print '''up.value %.1f | |
337 down.value %.1f''' % (stats.upstream, stats.downstream) | |
283 | 338 |
284 if options.graph: | 339 if options.graph: |
285 gengraph() | 340 gengraph() |
286 | 341 |