diff --git a/examples/consoles.py b/examples/consoles.py index 1347738..13b4453 100755 --- a/examples/consoles.py +++ b/examples/consoles.py @@ -5,12 +5,30 @@ consoles.py: bring up a bunch of miniature consoles on a virtual network This demo shows how to monitor a set of nodes by using Node's monitor() and Tkinter's createfilehandler(). + +We monitor nodes in a couple of ways: + +- First, each individual node is monitored, and its output is added + to its console window + +- Second, each time a console window gets iperf output, it is parsed + and accumulated. Once we have output for all consoles, a bar is + added to the bandwidth graph. + +The consoles also support limited interaction: + +- Pressing "return" in a console will send a command to it + +- Pressing the console's title button will open up an xterm + +Bob Lantz, April 2010 + """ import re from Tkinter import * -from mininet.log import setLogLevel +from mininet.log import setLogLevel,info from mininet.topolib import TreeNet from mininet.term import makeTerms, cleanUpScreens from mininet.util import quietRun @@ -45,7 +63,9 @@ class Console( Frame ): self.text = self.makeWidgets( ) self.bindEvents() self.sendCmd( 'export TERM=dumb' ) - + + self.outputHook = None + def makeWidgets( self ): "Make a label, a text area, and a scroll bar." @@ -85,7 +105,9 @@ class Console( Frame ): self.text.insert( 'end', text ) self.text.mark_set( 'insert', 'end' ) self.text.see( 'insert' ) - + if self.outputHook: + self.outputHook( self, text ) + def handleKey( self, event ): "If it's an interactive command, send it to the node." char = event.char @@ -122,7 +144,11 @@ class Console( Frame ): if not self.node.waiting: # Print prompt self.append( self.prompt ) - + + def waiting( self ): + "Are we waiting for output?" + return self.node.waiting + def waitOutput( self ): "Wait for any remaining output." while self.node.waiting: @@ -133,7 +159,124 @@ class Console( Frame ): def clear( self ): "Clear all of our text." self.text.delete( '1.0', 'end' ) + + +class Graph( Frame ): + + "Graph that we can add bars to over time." + + def __init__( self, parent=None, + bg = 'white', + gheight=200, gwidth=500, + barwidth=10, + ymax=3.5,): + Frame.__init__( self, parent ) + + 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 = None + self.createWidgets() + self.updateScrollRegions() + self.yview( 'moveto', '1.0' ) + + + def scale( 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 (Gb/s)", bg=self.bg ) + width = self.gwidth + height = self.gheight + scale = self.scale() + 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=N+E+W) + scale.grid( row=1, column=0, sticky=N+S+E+W ) + graph.grid( row=1, column=1, sticky=N+S+E+W ) + ybar.grid( row=1, column=2, sticky=N+S ) + xbar.grid( row=2, column=0, columnspan=2, sticky=E+W ) + self.rowconfigure( 1, weight=1 ) + self.columnconfigure( 1, weight=1 ) + # Save for future reference + self.title = title + self.scale = scale + self.graph = graph + return graph + + def addBar( self, yval ): + "Add a new bar to our graph." + percent = yval / self.ymax + height = percent * self.gheight + 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 clear( self ): + "Clear graph contents." + self.graph.delete( 'all' ) + self.xpos = 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 ConsoleApp( Frame ): menuStyle = { 'font': 'Geneva 7 bold' } @@ -159,11 +302,39 @@ class ConsoleApp( Frame ): self.selected = None self.select( 'hosts' ) self.cframe.pack( expand=True, fill='both' ) - self.pack( expand=True, fill='both' ) cleanUpScreens() # Close window gracefully Wm.wm_protocol( self.top, name='WM_DELETE_WINDOW', func=self.quit ) + # Initialize graph + graph = Graph( cframe ) + self.consoles[ 'graph' ] = Object( frame=graph, consoles=[ graph ] ) + self.graph = graph + self.graphVisible = False + self.updates = 0 + self.hostCount = len( self.consoles[ 'hosts' ].consoles ) + self.bw = 0 + + self.pack( expand=True, fill='both' ) + + def updateGraph( self, console, output ): + "Update our graph." + m = re.search( r'(\d+) Mbits/sec', output ) + if not m: + return + self.updates += 1 + self.bw += .001 * float( m.group( 1 ) ) + if self.updates >= self.hostCount: + self.graph.addBar( self.bw ) + self.bw = 0 + self.updates = 0 + + def setOutputHook( self, fn=None, consoles=None ): + if consoles is None: + consoles = self.consoles[ 'hosts' ].consoles + for console in consoles: + console.outputHook = fn + def createConsoles( self, parent, nodes, width, title ): "Create a grid of consoles in a frame." f = Frame( parent ) @@ -194,7 +365,8 @@ class ConsoleApp( Frame ): buttons = [ ( 'Hosts', lambda: self.select( 'hosts' ) ), ( 'Switches', lambda: self.select( 'switches' ) ), - ( 'Controllers', lambda: self.select( 'controllers' ) ), + ( 'Controllers', lambda: self.select( 'controllers' ) ), + ( 'Graph', lambda: self.select( 'graph' ) ), ( 'Ping', self.ping ), ( 'Iperf', self.iperf ), ( 'Interrupt', self.stop ), @@ -208,13 +380,24 @@ class ConsoleApp( Frame ): return f def clear( self ): - "Clear all consoles." + "Clear selection." for console in self.selected.consoles: console.clear() - + + def waiting( self, consoles=None ): + "Are any of our hosts waiting for output?" + if consoles is None: + consoles = self.consoles[ 'hosts' ].consoles + for console in consoles: + if console.waiting(): + return True + return False + def ping( self ): "Tell each host to ping the next one." consoles = self.consoles[ 'hosts' ].consoles + if self.waiting( consoles ): + return count = len( consoles ) i = 0 for console in consoles: @@ -225,7 +408,10 @@ class ConsoleApp( Frame ): def iperf( self ): "Tell each host to iperf to the next one." consoles = self.consoles[ 'hosts' ].consoles + if self.waiting( consoles ): + return count = len( consoles ) + self.setOutputHook( self.updateGraph ) for console in consoles: console.node.cmd( 'iperf -sD' ) i = 0 @@ -242,27 +428,32 @@ class ConsoleApp( Frame ): if wait: for console in consoles: console.waitOutput() + self.setOutputHook( None ) # Shut down any iperfs that might still be running quietRun( 'killall -9 iperf' ) def quit( self ): "Stope everything and quit." - print "Quit" self.stop( wait=False) Frame.quit( self ) - def reallyQuit( self ): - print "ReallyQuit" - + +# Make it easier to construct and assign objects + +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 ): - for name, value in kwargs.items(): - setattr( self, name, value ) - + assign( self, **kwargs ) + + if __name__ == '__main__': setLogLevel( 'info' ) - net = TreeNet( depth=2, fanout=2 ) + net = TreeNet( depth=2, fanout=4 ) net.start() app = ConsoleApp( net, width=4 ) app.mainloop()