311 lines
9.2 KiB
Python
Executable File
311 lines
9.2 KiB
Python
Executable File
#!/usr/bin/python
|
|
|
|
"""
|
|
udpbwgraph: Plot network bandwidth over time
|
|
|
|
Bob Lantz
|
|
3/27/10
|
|
"""
|
|
|
|
import re
|
|
|
|
from Tkinter import Frame, Label, Button, Scrollbar, OptionMenu, Canvas
|
|
from Tkinter import StringVar
|
|
|
|
from mininet.log import setLogLevel
|
|
from mininet.net import Mininet
|
|
from mininet.node import KernelSwitch, UserSwitch, OVSKernelSwitch
|
|
from mininet.node import Controller, NOX
|
|
from mininet.topolib import TreeTopo
|
|
from mininet.util import quietRun
|
|
|
|
# bwtest support
|
|
|
|
class Graph( Frame ):
|
|
|
|
"Graph that we can add bars to over time."
|
|
|
|
def __init__( self, master=None,
|
|
bg = 'white',
|
|
gheight=200, gwidth=500,
|
|
barwidth=10,
|
|
ymax=3.5,):
|
|
|
|
Frame.__init__( self, master )
|
|
|
|
self.bg = bg
|
|
self.gheight = gheight
|
|
self.gwidth = gwidth
|
|
self.barwidth = barwidth
|
|
self.ymax = float( ymax )
|
|
self.xpos = 0
|
|
|
|
# Create everything
|
|
self.title, self.graph, self.scale = self.createWidgets()
|
|
self.updateScrollRegions()
|
|
self.yview( 'moveto', '1.0' )
|
|
|
|
def createScale( self ):
|
|
"Create a and return a new canvas with scale markers."
|
|
height = float( self.gheight )
|
|
width = 25
|
|
ymax = self.ymax
|
|
scale = Canvas( self, width=width, height=height, background=self.bg )
|
|
fill = 'red'
|
|
# Draw scale line
|
|
scale.create_line( width - 1, height, width - 1, 0, fill=fill )
|
|
# Draw ticks and numbers
|
|
for y in range( 0, int( ymax + 1 ) ):
|
|
ypos = height * ( 1 - float( y ) / ymax )
|
|
scale.create_line( width, ypos, width - 10, ypos, fill=fill )
|
|
scale.create_text( 10, ypos, text=str( y ), fill=fill )
|
|
|
|
return scale
|
|
|
|
def updateScrollRegions( self ):
|
|
"Update graph and scale scroll regions."
|
|
ofs = 20
|
|
height = self.gheight + ofs
|
|
self.graph.configure( scrollregion=( 0, -ofs,
|
|
self.xpos * self.barwidth, height ) )
|
|
self.scale.configure( scrollregion=( 0, -ofs, 0, height ) )
|
|
|
|
def yview( self, *args ):
|
|
"Scroll both scale and graph."
|
|
self.graph.yview( *args )
|
|
self.scale.yview( *args )
|
|
|
|
def createWidgets( self ):
|
|
"Create initial widget set."
|
|
|
|
# Objects
|
|
title = Label( self, text="Bandwidth (Mb/s)", bg=self.bg )
|
|
width = self.gwidth
|
|
height = self.gheight
|
|
scale = self.createScale()
|
|
graph = Canvas( self, width=width, height=height, background=self.bg)
|
|
xbar = Scrollbar( self, orient='horizontal', command=graph.xview )
|
|
ybar = Scrollbar( self, orient='vertical', command=self.yview )
|
|
graph.configure( xscrollcommand=xbar.set, yscrollcommand=ybar.set,
|
|
scrollregion=(0, 0, width, height ) )
|
|
scale.configure( yscrollcommand=ybar.set )
|
|
|
|
# Layout
|
|
title.grid( row=0, columnspan=3, sticky='new')
|
|
scale.grid( row=1, column=0, sticky='nsew' )
|
|
graph.grid( row=1, column=1, sticky='nsew' )
|
|
ybar.grid( row=1, column=2, sticky='ns' )
|
|
xbar.grid( row=2, column=0, columnspan=2, sticky='ew' )
|
|
self.rowconfigure( 1, weight=1 )
|
|
self.columnconfigure( 1, weight=1 )
|
|
return title, graph, scale
|
|
|
|
def addBar( self, yval ):
|
|
"Add a new bar to our graph."
|
|
percent = yval / self.ymax
|
|
c = self.graph
|
|
x0 = self.xpos * self.barwidth
|
|
x1 = x0 + self.barwidth
|
|
y0 = self.gheight
|
|
y1 = ( 1 - percent ) * self.gheight
|
|
c.create_rectangle( x0 , y0, x1, y1, fill='green' )
|
|
self.xpos += 1
|
|
self.updateScrollRegions()
|
|
self.graph.xview( 'moveto', '1.0' )
|
|
|
|
def test( self ):
|
|
"Add a bar for testing purposes."
|
|
ms = 1000
|
|
if self.xpos < 10:
|
|
self.addBar( self.xpos / 10 * self.ymax )
|
|
self.after( ms, self.test )
|
|
|
|
def setTitle( self, text ):
|
|
"Set graph title"
|
|
self.title.configure( text=text, font='Helvetica 9 bold' )
|
|
|
|
|
|
class Controls( Frame ):
|
|
|
|
"Handy controls for configuring test."
|
|
|
|
switches = {
|
|
'Kernel Switch': KernelSwitch,
|
|
'User Switch': UserSwitch,
|
|
'Open vSwitch': OVSKernelSwitch
|
|
}
|
|
|
|
controllers = {
|
|
'Reference Controller': Controller,
|
|
'NOX': NOX
|
|
}
|
|
|
|
def __init__( self, master, startFn, stopFn, quitFn ):
|
|
|
|
Frame.__init__( self, master )
|
|
|
|
# Option menus
|
|
opts = { 'font': 'Geneva 7 bold' }
|
|
self.switch = self.optionMenu( self.switches,
|
|
KernelSwitch, opts )
|
|
self.controller = self.optionMenu( self.controllers,
|
|
Controller, opts)
|
|
|
|
# Spacer
|
|
pk = { 'fill': 'x' }
|
|
Label( self, **opts ).pack( **pk )
|
|
|
|
# Buttons
|
|
self.start = Button( self, text='Start', command=startFn, **opts )
|
|
self.stop = Button( self, text='Stop', command=stopFn, **opts )
|
|
self.quit = Button( self, text='Quit', command=quitFn, **opts )
|
|
for button in ( self.start, self.stop, self.quit ):
|
|
button.pack( **pk )
|
|
|
|
def optionMenu( self, menuItems, initval, opts ):
|
|
"Add a new option menu. Returns function to get value."
|
|
var = StringVar()
|
|
var.set( findKey( menuItems, initval ) )
|
|
menu = OptionMenu( self, var, *menuItems )
|
|
menu.config( **opts )
|
|
menu.pack( fill='x' )
|
|
return lambda: menuItems[ var.get() ]
|
|
|
|
def parsebwtest( line,
|
|
r=re.compile( r'(\d+) s: in ([\d\.]+) MB/s, out ([\d\.]+) MB/s' ) ):
|
|
"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
|
|
|
|
|
|
class UdpBwTest( Frame ):
|
|
"Test and plot UDP bandwidth over time"
|
|
|
|
def __init__( self, topo, master=None ):
|
|
"Start up and monitor udpbwtest on each of our hosts."
|
|
|
|
Frame.__init__( self, master )
|
|
|
|
self.controls = Controls( self, self.start, self.stop, self.quit )
|
|
self.graph = Graph( self )
|
|
|
|
# Layout
|
|
self.controls.pack( side='left', expand=False, fill='y' )
|
|
self.graph.pack( side='right', expand=True, fill='both' )
|
|
self.pack( expand=True, fill='both' )
|
|
|
|
self.topo = topo
|
|
self.net = None
|
|
self.hosts = []
|
|
self.hostCount = 0
|
|
self.output = None
|
|
self.results = {}
|
|
self.running = False
|
|
|
|
def start( self ):
|
|
"Start test."
|
|
|
|
if self.running:
|
|
return
|
|
|
|
switch = self.controls.switch()
|
|
controller = self.controls.controller()
|
|
|
|
self.net = Mininet( self.topo, switch=switch,
|
|
controller=controller )
|
|
self.hosts = self.net.hosts
|
|
self.hostCount = len( self.hosts )
|
|
|
|
print "*** Starting network"
|
|
self.net.start()
|
|
|
|
print "*** Starting udpbwtest on hosts"
|
|
hosts = self.hosts
|
|
for host in hosts:
|
|
ips = [ h.IP() for h in hosts if h != host ]
|
|
host.cmdPrint( './udpbwtest ' + ' '.join( ips ) + ' &' )
|
|
|
|
print "*** Monitoring hosts"
|
|
self.output = self.net.monitor( hosts, timeoutms=1 )
|
|
self.results = {}
|
|
self.running = True
|
|
self.updateGraph()
|
|
|
|
# Pylint isn't smart enough to understand iterator.next()
|
|
# pylint: disable-msg=E1101
|
|
|
|
def updateGraph( self ):
|
|
"Graph input bandwidth."
|
|
|
|
print "updateGraph"
|
|
|
|
if not self.running:
|
|
return
|
|
|
|
while True:
|
|
host, line = self.output.next()
|
|
if host is None or len( line ) == 0:
|
|
break
|
|
seconds, inbw, outbw = parsebwtest( line )
|
|
if seconds is None:
|
|
break
|
|
result = self.results.get( seconds, [] ) + [ ( host, inbw, outbw ) ]
|
|
self.results[ seconds ] = result
|
|
if len( result ) == self.hostCount:
|
|
# Calculate total and update graph
|
|
# We report input bandwidth, i.e. packets that made it
|
|
totalin = 0
|
|
for host, inbw, outbw in result:
|
|
totalin += inbw
|
|
self.graph.addBar( totalin * 8.0/1000.0 )
|
|
print totalin
|
|
# Fileevent might be better, but for now we just poll every 500ms
|
|
self.graph.after( 500, self.updateGraph )
|
|
|
|
def stop( self ):
|
|
"Stop test."
|
|
|
|
print "*** Stopping udpbwtest processes"
|
|
# We *really* don't want these things hanging around!
|
|
quietRun( 'killall -9 udpbwtest' )
|
|
|
|
if not self.running:
|
|
return
|
|
|
|
print "*** Stopping network"
|
|
self.running = False
|
|
self.net.stop()
|
|
|
|
def quit( self ):
|
|
"Quit app."
|
|
self.stop()
|
|
Frame.quit( self )
|
|
|
|
# pylint: enable-msg=E1101
|
|
|
|
# Useful utilities
|
|
|
|
def findKey( d, value ):
|
|
"Find some key where d[ key ] == value."
|
|
return [ key for key, val in d.items() if val == value ][ 0 ]
|
|
|
|
def assign( obj, **kwargs):
|
|
"Set a bunch of fields in an object."
|
|
for name, value in kwargs.items():
|
|
setattr( obj, name, value )
|
|
|
|
class Object( object ):
|
|
"Generic object you can stuff junk into."
|
|
def __init__( self, **kwargs ):
|
|
assign( self, **kwargs )
|
|
|
|
if __name__ == '__main__':
|
|
setLogLevel( 'info' )
|
|
app = UdpBwTest( topo=TreeTopo( depth=2, fanout=2 ) )
|
|
app.mainloop()
|
|
|
|
|