changeset 14:2debc3fb4372

Update for VDSL modem (TP-Link W9970). Log maximum as well as current rates. Remove trailing whitespace. Keep 5 years of data.
author Daniel O'Connor <darius@dons.net.au>
date Fri, 20 May 2016 15:06:43 +0930
parents bf46efd061d7
children 7dbe86981f6b
files adslstats.py
diffstat 1 files changed, 73 insertions(+), 57 deletions(-) [+]
line wrap: on
line diff
--- a/adslstats.py	Thu Jan 08 11:28:28 2015 +1030
+++ b/adslstats.py	Fri May 20 15:06:43 2016 +0930
@@ -1,7 +1,7 @@
 #!/usr/bin/env python2
 ############################################################################
 #
-# Parse ADSL link stats for Billion 7300G & generate RRD archives & graphs
+# Parse ADSL link stats for TP-Link W9970 & generate RRD archives & graphs
 #
 ############################################################################
 #
@@ -30,10 +30,12 @@
 #
 ############################################################################
 
+import base64
 import ConfigParser
 import optparse
 import os
 import re
+import requests
 import rrdtool
 import sys
 import time
@@ -44,7 +46,7 @@
 conf.add_section('global')
 conf.set('global', 'username', 'admin')
 conf.set('global', 'password', 'admin')
-conf.set('global', 'name', 'dsl.dons.net.au')
+conf.set('global', 'name', '10.0.2.13')
 
 conflist = ['adslstats.ini']
 if ('HOME' in os.environ):
@@ -86,60 +88,71 @@
 
 class ADSLStats(object):
     def __str__(self):
-        return """Line Rate - Up: %d kbits, Down %d kbits
-Noise Margin - Up: %.1f dB, Down %.1f dB
-Attenuation - Up: %.1f dB, Down %.1f dB""" % (self.upstream, self.downstream,
-                                              self.nmup, self.nmdown,
+        s = "Line Rate - Up: %d kbits, Down %d kbits\n" % (self.upstream, self.downstream)
+        if hasattr(self, 'upstreammax'):
+            s += "Maximum Rate - Up: %d kbit, Down %s kbit\n" % (self.upstreammax, self.downstreammax)
+        s += """Noise Margin - Up: %.1f dB, Down %.1f dB
+Attenuation - Up: %.1f dB, Down %.1f dB""" % (self.nmup, self.nmdown,
                                               self.attenup, self.attendown)
-def getstats(f):
-    s = BeautifulSoup(f)
-    a = s.findAll('tr')
-    
-    for i in statsdict:
-        assert a[i].td.contents[0] == statsdict[i]
+        return s
 
+def getstats():
     stats = ADSLStats()
-    
-    # Check if the modem is offline
-    if a[3].td.findNext('td').contents[0] != 'Up':
-	return None
-
-    # dB
-    stats.nmdown = float(a[8].td.findNext('td').contents[0]) / 10.0
-    stats.nmup = float(a[8].td.findNext('td').findNext('td').contents[0]) / 10.0
-    stats.attendown = float(a[9].td.findNext('td').contents[0]) / 10.0
-    stats.attenup = float(a[9].td.findNext('td').findNext('td').contents[0]) / 10.0
-    # kBit
-    stats.downstream = float(a[14].td.findNext('td').contents[0])
-    stats.upstream = float(a[14].td.findNext('td').findNext('td').contents[0])
+    parser = ConfigParser.ConfigParser()
+    base = 'http://%s' % (conf.get('global', 'name'))
+    # Gunk extracted from Chrome (what the page is requesting). Note it's sensitive to line ending type...
+    # We could get more data, eg error rates..
+    data = '[WAN_DSL_INTF_CFG#1,0,0,0,0,0#0,0,0,0,0,0]0,12\r\nstatus\r\nmodulationType\r\nX_TP_AdslModulationCfg\r\nupstreamCurrRate\r\ndownstreamCurrRate\r\nX_TP_AnnexType\r\nupstreamMaxRate\r\ndownstreamMaxRate\r\nupstreamNoiseMargin\r\ndownstreamNoiseMargin\r\nupstreamAttenuation\r\ndownstreamAttenuation\r\n[WAN_DSL_INTF_STATS_TOTAL#1,0,0,0,0,0#0,0,0,0,0,0]1,8\r\nATUCCRCErrors\r\nCRCErrors\r\nATUCFECErrors\r\nFECErrors\r\nSeverelyErroredSecs\r\nX_TP_US_SeverelyErroredSecs\r\nerroredSecs\r\nX_TP_US_ErroredSecs\r\n'
+    cookies = {'Authorization' : 'Basic ' + base64.standard_b64encode(conf.get('global', 'username') + ':' + conf.get('global', 'password'))}
+    headers = {'Referer' : base}
+    r = requests.post(base + '/cgi?1&5' , data = data, headers = headers, cookies = cookies, stream = True)
+    parser.readfp(r.raw)
+    res = {}
+    tmp = '1,0,0,0,0,0'
+    if parser.get(tmp, 'status') == 'Up':
+        stats.linkup = True
+    else:
+        stats.linkup = False
+    stats.upstream = float(parser.get(tmp, 'upstreamCurrRate'))
+    stats.downstream = float(parser.get(tmp, 'downstreamCurrRate'))
+    stats.upstreammax = float(parser.get(tmp, 'upstreamMaxRate'))
+    stats.downstreammax = float(parser.get(tmp, 'downstreamMaxRate'))
+    stats.nmup = float(parser.get(tmp, 'upstreamNoiseMargin')) / 10.0
+    stats.nmdown = float(parser.get(tmp, 'downstreamNoiseMargin')) / 10.0
+    stats.attenup = float(parser.get(tmp, 'upstreamAttenuation')) / 10.0
+    stats.attendown = float(parser.get(tmp, 'downstreamAttenuation')) / 10.0
 
     return stats
 
 # Setup RRD
 # We expect data to be logged every 5 minutes
 # Average 12 5 minute points -> hourly stats (keep 168 - a weeks worth)
-# Average 288 5 minute points -> daily stats (keep 365 - a years worth)
+# Average 288 5 minute points -> daily stats (keep 1825 - 5 years worth)
 # Detemine  minimum & maximum for an hour and keep a weeks worth.
 def makerrd(filename):
     rrdtool.create(filename,
                    '--step', '300',
-                   'DS:upstream:GAUGE:3600:32:25000',   # Upstream (kbits) - 24mbit is ADSL2+ max
-                   'DS:downstream:GAUGE:3600:32:25000', # Downstream (kbits)
+                   'DS:upstream:GAUGE:3600:32:150000',   # Upstream (kbits)
+                   'DS:downstream:GAUGE:3600:32:150000', # Downstream (kbits)
+                   'DS:upstreammax:GAUGE:3600:32:150000',   # Upstream maximum (kbits)
+                   'DS:downstreammax:GAUGE:3600:32:150000', # Downstream maximum (kbits)
                    'DS:nmup:GAUGE:3600:0:100',          # Upstream Noise margin (dB)
                    'DS:nmdown:GAUGE:3600:0:100',        # Downstream Noise margin (dB)
                    'DS:attenup:GAUGE:3600:0:100',       # Upstream Attenuation (dB)
                    'DS:attendown:GAUGE:3600:0:100',     # Downstream Attenuation (dB)
                    'RRA:AVERAGE:0.1:12:168',
-                   'RRA:AVERAGE:0.1:288:365',
+                   'RRA:AVERAGE:0.1:288:1825',
                    'RRA:MIN:0.1:12:168',
                    'RRA:MAX:0.1:12:168')
 
 # Update the RRD (format stats as expected)
 def updaterrd(filename, tstamp, stats):
     rrdtool.update(filename,
-                   '%d:%d:%d:%f:%f:%f:%f' % (tstamp,
+                   '%d:%d:%d:%d:%d:%f:%f:%f:%f' % (tstamp,
                                              stats.upstream,
                                              stats.downstream,
+                                             stats.upstreammax,
+                                             stats.downstreammax,
                                              stats.nmup,
                                              stats.nmdown,
                                              stats.attenup,
@@ -147,13 +160,7 @@
 
 # Open the URL and call the parser
 def getdata():
-    opener = urllib.FancyURLopener()
-    opener.prompt_user_passwd = lambda host, realm: (options.authname, options.password)
-    f = opener.open(statsurl)
-    #f = open("adsl.html")
-    stats = getstats(f)
-    if stats == None:
-	return None
+    stats = getstats()
     return stats
 
 # Generate a graph
@@ -162,27 +169,35 @@
     linkargs = (
         '-a', 'SVG',
         '-X', '0',
+        '-l', '0',
         '--vertical-label', 'kbit/sec',
         '--slope-mode',
 
         'DEF:upstream=%s:upstream:AVERAGE' % rrdname,
         'DEF:upstreammin=%s:upstream:MIN' % rrdname,
         'DEF:upstreammax=%s:upstream:MAX' % rrdname,
-        
+
         'DEF:downstream=%s:downstream:AVERAGE' % rrdname,
         'DEF:downstreammin=%s:downstream:MIN' % rrdname,
         'DEF:downstreammax=%s:downstream:MAX' % rrdname,
 
         'CDEF:upstreamdif=upstreammax,upstreammin,-',
         'CDEF:downstreamdif=downstreammax,downstreammin,-',
-        
+
+        'DEF:maxupstream=%s:upstreammax:AVERAGE' % rrdname,
+        'DEF:maxdownstream=%s:downstreammax:AVERAGE' % rrdname,
+
         'LINE0:upstreammin#000000:',
         'AREA:upstreamdif#00dc76::STACK',
         'LINE1:upstream#00ff00:Upstream',
-        
+
         'LINE0:downstreammin#000000:',
         'AREA:downstreamdif#ff8686::STACK',
-        'LINE1:downstream#ff0000:Downstream')
+        'LINE1:downstream#ff0000:Downstream',
+
+        'LINE1:maxupstream#0000ff:Upstream (maximum)',
+        'LINE1:maxdownstream#000000:Downstream (maximum)'
+    )
 
     signalargs = (
         '-a', 'SVG',
@@ -191,53 +206,53 @@
 
         'DEF:upstream=%s:upstream:AVERAGE' % rrdname,
         'DEF:downstream=%s:downstream:AVERAGE' % rrdname,
-        
+
         'DEF:nmup_=%s:nmup:AVERAGE' % rrdname,
         'DEF:nmupmin_=%s:nmup:MIN' % rrdname,
         'DEF:nmupmax_=%s:nmup:MAX' % rrdname,
-        
+
         'DEF:nmdown_=%s:nmdown:AVERAGE' % rrdname,
         'DEF:nmdownmin_=%s:nmdown:MIN' % rrdname,
         'DEF:nmdownmax_=%s:nmdown:MAX' % rrdname,
-        
+
         'DEF:attenup=%s:attenup:AVERAGE' % rrdname,
         'DEF:attenupmin=%s:attenup:MIN' % rrdname,
         'DEF:attenupmax=%s:attenup:MAX' % rrdname,
-        
+
         'DEF:attendown=%s:attendown:AVERAGE' % rrdname,
         'DEF:attendownmin=%s:attendown:MIN' % rrdname,
         'DEF:attendownmax=%s:attendown:MAX' % rrdname,
-        
+
         'CDEF:nmup=nmup_,10,*',
         'CDEF:nmupmin=nmupmin_,10,*',
         'CDEF:nmupmax=nmupmax_,10,*',
         'CDEF:nmupdif=nmupmax,nmupmin,-',
-        
+
         'CDEF:nmdown=nmdown_,10,*',
         'CDEF:nmdownmin=nmdownmin_,10,*',
         'CDEF:nmdownmax=nmdownmax_,10,*',
         'CDEF:nmdowndif=nmdownmax,nmdownmin,-',
-        
+
         'CDEF:attenupdif=attenupmax,attenupmin,-',
-        
+
         'CDEF:attendowndif=attendownmax,attendownmin,-',
-        
+
         'LINE0:nmupmin#000000:',
         'AREA:nmupdif#5c5cff::STACK',
         'LINE1:nmup#0000ff:Noise Margin - Up (1/10 dB)',
-        
+
         'LINE0:nmdownmin#000000:',
         'AREA:nmdowndif#009a00::STACK',
         'LINE1:nmdown#00ff00:Noise Margin - Down (1/10 dB)',
-        
+
         'LINE0:attenupmin#000000:',
         'AREA:attenupdif#f98100::STACK',
         'LINE1:attenup#ff0000:Attenuation - Up',
-        
+
         'LINE0:attendownmin#000000:',
         'AREA:attendowndif#aaaaaa::STACK',
         'LINE1:attendown#000000:Attenuation - Down')
-    
+
     rrdtool.graph("%s-hour-link.svg" % (graphbasename),
                   '--width', '768',
                   '--height', '256',
@@ -306,10 +321,11 @@
             sys.exit(0)
     if options.update or options.munin:
         stats = getdata()
-        if stats == None:
-            if options.verbose:
+        if options.verbose:
+            if stats == None:
                 print "Modem is offline"
-
+            else:
+                print stats
     if (options.update or options.munin != None) and stats != None:
         if options.update:
             try: