Added output hook and graph.
This commit is contained in:
+207
-16
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user