#!/usr/bin/python

"""
Create a tree network and run udpbwtest.c on it, attempting to
saturate global bandwidth by sending constant all-to-all
udp traffic. This should be something of a stress test.

We should also make a tcp version. :D

In addition to trying to saturate global bandwidth in
various Mininet configurations, this example:

- uses a topology, TreeTopo, from mininet.topolib
- starts up a custom test program, udpbwtest, on each host
- dynamically monitors the output of a set of hosts

"""

import os
import re
import sys
from time import time

flush = sys.stdout.flush

from mininet.log import lg
from mininet.net import init, Mininet
from mininet.node import KernelSwitch
from mininet.topolib import TreeTopo
from mininet.util import quietRun

# bwtest support

def parsebwtest( line,
    r=re.compile( r'(\d+) s: in ([\d\.]+) Mbps, out ([\d\.]+) Mbps' ) ):
    "Parse udpbwtest.c output, returning seconds, inbw, outbw."
    match = r.match( line )
    if match:
        seconds, inbw, outbw = match.group( 1, 2, 3 )
        return int( seconds ), float( inbw ), float( outbw )
    return None, None, None

def printTotalHeader():
    "Print header for bandwidth stats."
    print
    print "time(s)\thosts\ttotal in/out (Mbps)\tavg in/out (Mbps)"

# Annoyingly, pylint isn't smart enough to notice
# when an unused variable is an iteration tuple
# pylint: disable-msg=W0612

def printTotal( seconds=None, result=None ):
    "Compute and print total bandwidth for given results set."
    intotal = outtotal = 0.0
    count = len( result )
    for host, inbw, outbw in result:
        intotal += inbw
        outtotal += outbw
    inavg = intotal / count if count > 0 else 0
    outavg = outtotal / count if count > 0 else 0
    print '%d\t%d\t%.2f/%.2f\t\t%.2f/%.2f' % ( seconds, count,
        intotal, outtotal, inavg, outavg )

# pylint: enable-msg=W0612

# Pylint also isn't smart enough to understand iterator.next()
# pylint: disable-msg=E1101

def udpbwtest( net, seconds ):
    "Start up and monitor udpbwtest on each of our hosts."
    hosts = net.hosts
    hostCount = len( hosts )
    print "*** Starting udpbwtest on hosts"
    for host in hosts:
        ips = [ h.IP() for h in hosts if h != host ]
        print host.name,
        flush()
        host.cmd( './udpbwtest ' + ' '.join( ips ) + ' &' )
    print
    results = {}
    print "*** Monitoring hosts"
    output = net.monitor( hosts )
    quitTime = time() + seconds
    while time() < quitTime:
        host, line = output.next()
        if host is None:
            break
        seconds, inbw, outbw = parsebwtest( line )
        if seconds is not None:
            result = results.get( seconds, [] ) + [ ( host, inbw, outbw ) ]
            if len( result ) == hostCount:
                printTotal( seconds, result )
            results[ seconds ] = result
    print "*** Stopping udpbwtest processes"
    # We *really* don't want these things hanging around!
    quietRun( 'killall -9 udpbwtest' )
    print
    print "*** Results:"
    printTotalHeader()
    times = sorted( results.keys() )
    for t in times:
        printTotal( t - times[ 0 ] , results[ t ] )
    print

# pylint: enable-msg=E1101

if __name__ == '__main__':
    lg.setLogLevel( 'info' )
    if not os.path.exists( './udpbwtest' ):
        raise Exception( 'Could not find udpbwtest in current directory.' )
    network = Mininet( TreeTopo( depth=2, fanout=2 ), switch=KernelSwitch )
    network.start()
    udpbwtest( network, seconds=10 )
    network.stop()