Cable modem stats

A while back now, I had the idea to write a tool to observe the status of my cable modem and came up with the following python script. It should work with the Scientific Atlanta cable modem, given to my by Unitymedia. It provides a system page, where no login credentials are required. The values read from the status page are stored in a rrd database and allows you to create nice graphs using rrdtool. I also prepared a little demo graph, where you can see, that I have been experiencing connection problems, by looking at the SNR1). You can also see, when they start fixing my port2). Modem stats

#!/usr/bin/env python
 
import urllib2
from BeautifulSoup import BeautifulSoup
from lxml import etree
from StringIO import StringIO
import sys, time
import rrdtool
import signal
 
# URL to the sytem status frame
URL = 'http://192.168.100.1/system.asp'
is_looping = False
 
def rrdNew(filename):
    ret = rrdtool.create(filename, "--step", "300", "--start", '0',
                     "DS:rpl:GAUGE:600:U:U",
                     "DS:tpl:GAUGE:600:U:U",
                     "DS:snr:GAUGE:600:U:U",
                     "RRA:MIN:0.5:1:600",
                     "RRA:MIN:0.5:6:700",
                     "RRA:MIN:0.5:24:775",
                     "RRA:MIN:0.5:288:797",
                     "RRA:AVERAGE:0.5:1:600",
                     "RRA:AVERAGE:0.5:6:700",
                     "RRA:AVERAGE:0.5:24:775",
                     "RRA:AVERAGE:0.5:288:797",
                     "RRA:MAX:0.5:1:600",
                     "RRA:MAX:0.5:6:700",
                     "RRA:MAX:0.5:24:775",
                     "RRA:MAX:0.5:444:797")
    if ret:
        print 'rrdNew:', rrdtool.error()
 
def rrdGraph(imgfile, rrdfile):
    print 're-generating graph...'
    rrdtool.graph(imgfile, "--start", "-1d", "--vertical-label=dBmV\dB",
              "--width", "600",
              "DEF:receive=%s:rpl:AVERAGE" %rrdfile,
              "DEF:transmit=%s:tpl:AVERAGE" %rrdfile,
              "DEF:signal=%s:snr:AVERAGE" %rrdfile,
              "AREA:receive#00FF00:Receive Power Level",
              "LINE1:transmit#0000FF:Transmit Power Level",
              "LINE1:signal#FF0000:Signal to Noise Ratio\\r",
              "CDEF:rpldbmv=receive,1,*",
              "CDEF:tpldbmv=transmit,1,*",
              "CDEF:snrdb=signal,1,*",
              "COMMENT:\\n",
              "GPRINT:rpldbmv:AVERAGE:Avg Receive Power Level\: %6.2lf dBmV",
              "GPRINT:rpldbmv:MAX:Max Receive Power Level\: %6.2lf dBmV\\r",
              "GPRINT:rpldbmv:MIN:Min Receive Power Level\: %6.2lf dBmV\\r",
              "COMMENT:\\n",
              "GPRINT:tpldbmv:AVERAGE:Avg Transmit Power Level\: %6.2lf dBmV",
              "GPRINT:tpldbmv:MAX:Max Transmit Power Level\: %6.2lf dBmV\\r",
              "GPRINT:tpldbmv:MIN:Min Transmit Power Level\: %6.2lf dBmV\\r",
              "COMMENT:\\n",
              "GPRINT:snrdb:AVERAGE:Avg Signal to Noise Level\: %6.2lf dB",
              "GPRINT:snrdb:MAX:Max Avg Signal to Noise Level\: %6.2lf dB\\r",
              "GPRINT:snrdb:MIN:Min Avg Signal to Noise Level\: %6.2lf dB\\r")
 
def rrdUpdate(rrdfile, data):
    print 'update data:', data
    ret = rrdtool.update(rrdfile, data)
    if ret:
        print 'rrdUpdate:', rrdtool.error()
 
def refresh():
    print 'refreshing...'
    try:
	page = urllib2.urlopen(URL)
    except:
        # Poor mans exception; no infos, just skip the processing
	print 'refresh aborted.'
	return
    soup = BeautifulSoup(page)
    # Remove   markups, because they break the XML parser.
    f = StringIO(str(soup.findAll('tbody')[0]).replace(' ', ''))
    dom = etree.parse(f)
    nodes = dom.xpath('//font')
    # We take every value we can get, but do not use them, yet
    modemvalues = []
    for i in xrange(0, len(nodes), 2):
        modemvalues.append((nodes[i].text, nodes[i+1].text.strip()))
    # Prepare the values and cut of the string at the end
    rpl = float(modemvalues[4][1].split(' ')[0]) # dBmV
    tpl = float(modemvalues[5][1].split(' ')[0]) # dBmV
    snr = float(modemvalues[6][1].split(' ')[0]) # dB
    rrdUpdate('modem.rrd', 'N:%f:%f:%f' %(rpl, tpl, snr))
 
def handler(signum, frame):
    global is_looping
    if signum == signal.SIGHUP:
        rrdGraph('modem.png', 'modem.rrd')
    elif signum == signal.SIGQUIT:
        print 'Graceful shutdown received. Please wait...'
        is_looping = False
    elif signum == signal.SIGUSR1:
        print 'Premature status update...'
        refresh()
 
if '-n' in sys.argv:
    print 'resetting database...'
    rrdNew('modem.rrd')
    sys.exit(0)
if '-g' in sys.argv:
    rrdGraph('modem.png', 'modem.rrd')
    sys.exit(0)
if '-l' in sys.argv:
    signal.signal(signal.SIGHUP, handler)
    signal.signal(signal.SIGQUIT, handler)
    signal.signal(signal.SIGUSR1, handler)
    print 'SIGHUP  handler registered: Will draw the graph when signaled.'
    print 'SIGQUIT handler registered: Graceful shutdown.'
    print 'SIGUSR1 handler registered: Force premature status update.'
    is_looping = True
    while is_looping:
        refresh()
        print 'Sleeping for 300 seconds...'
        time.sleep(300)
else:
    refresh()

Commandline switches:

./modemstatd.py -<Switch>

  Switch    Description
  ========= =================================================================
  -n        Create a new "modem.rrd" file. Overwrites the old one!
  -g        Render the graph and stores the image as "modem.png".
  -l        The script will loop and update the data every 300 seconds.

Signals (when looping):

kill -<SIGNAL> <pid>

  SIGNAL    Description
  ========= =================================================================
  HUP       Draws the graph and stores it as "modem.png".
  QUIT      Graceful shutdown, terminate the loop after the sleep finished.
  USR1      Premature update of modem status.

1) Signal to Noise Ratio
2) The gap where the modem was unresponsive

Comments




If you can't read the letters on the image, download this .wav file to get them read to you.
Posted 2011/03/10 11:36 · Julian Knauer
blog/2011/03/10.cable.modem.stats.txt · Last modified: 2011/03/10 11:38 by jpk
CC Attribution-Noncommercial-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0