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