Use iperf CSV output in Mininet.iperf() (#1165)
+ identify client and server (different on Ubuntu 20 vs. 22)
+ add util.{unitScale,fmtBps} to help with output format
- internal function _parseIperfOutput has been removed
This commit is contained in:
+39
-31
@@ -98,14 +98,14 @@ from itertools import chain, groupby
|
|||||||
from math import ceil
|
from math import ceil
|
||||||
|
|
||||||
from mininet.cli import CLI
|
from mininet.cli import CLI
|
||||||
from mininet.log import info, error, debug, output, warn
|
from mininet.log import info, error, output, warn
|
||||||
from mininet.node import ( Node, Host, OVSKernelSwitch, DefaultController,
|
from mininet.node import ( Node, Host, OVSKernelSwitch, DefaultController,
|
||||||
Controller )
|
Controller )
|
||||||
from mininet.nodelib import NAT
|
from mininet.nodelib import NAT
|
||||||
from mininet.link import Link, Intf
|
from mininet.link import Link, Intf
|
||||||
from mininet.util import ( quietRun, fixLimits, numCores, ensureRoot,
|
from mininet.util import ( quietRun, fixLimits, numCores, ensureRoot,
|
||||||
macColonHex, ipStr, ipParse, netParse, ipAdd,
|
macColonHex, ipStr, ipParse, netParse, ipAdd,
|
||||||
waitListening, BaseString )
|
waitListening, BaseString, fmtBps )
|
||||||
from mininet.term import cleanUpScreens, makeTerms
|
from mininet.term import cleanUpScreens, makeTerms
|
||||||
|
|
||||||
# Mininet version: should be consistent with README and LICENSE
|
# Mininet version: should be consistent with README and LICENSE
|
||||||
@@ -782,18 +782,25 @@ class Mininet( object ):
|
|||||||
return self.pingFull( hosts=hosts )
|
return self.pingFull( hosts=hosts )
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parseIperf( iperfOutput ):
|
def _iperfVals( iperfcsv, serverip, _l4Type='TCP' ):
|
||||||
"""Parse iperf output and return bandwidth.
|
"""Return iperf CSV as dict
|
||||||
iperfOutput: string
|
iperfcsv: iperf -y C output
|
||||||
returns: result string"""
|
serverip: iperf server IP address
|
||||||
r = r'([\d\.]+ \w+/sec)'
|
l4Type: TCP|UDP
|
||||||
m = re.findall( r, iperfOutput )
|
"""
|
||||||
if m:
|
fields = 'date cip cport sip sport ipver interval sent rate'
|
||||||
return m[-1]
|
lines = iperfcsv.strip().split('\n')
|
||||||
else:
|
if lines:
|
||||||
# was: raise Exception(...)
|
line = lines[ -1 ].split( ',' )
|
||||||
error( 'could not parse iperf output: ' + iperfOutput )
|
svals = dict( zip( fields.split(), line ) )
|
||||||
return ''
|
# Return client in cip:cport, server in sip:sport
|
||||||
|
if svals[ 'cip' ] == serverip:
|
||||||
|
svals[ 'cip' ], svals[ 'sip' ] = (
|
||||||
|
svals[ 'sip' ], svals[ 'cip' ] )
|
||||||
|
svals[ 'cport' ], svals[ 'sport' ] = (
|
||||||
|
svals[ 'sport' ], svals[ 'cport' ] )
|
||||||
|
return svals
|
||||||
|
return {}
|
||||||
|
|
||||||
# XXX This should be cleaned up
|
# XXX This should be cleaned up
|
||||||
|
|
||||||
@@ -803,7 +810,7 @@ class Mininet( object ):
|
|||||||
hosts: list of hosts; if None, uses first and last hosts
|
hosts: list of hosts; if None, uses first and last hosts
|
||||||
l4Type: string, one of [ TCP, UDP ]
|
l4Type: string, one of [ TCP, UDP ]
|
||||||
udpBw: bandwidth target for UDP test
|
udpBw: bandwidth target for UDP test
|
||||||
fmt: iperf format argument if any
|
fmt: scale/format argument (e.g. m/M for Mbps)
|
||||||
seconds: iperf time to transmit
|
seconds: iperf time to transmit
|
||||||
port: iperf port
|
port: iperf port
|
||||||
returns: two-element array of [ server, client ] speeds
|
returns: two-element array of [ server, client ] speeds
|
||||||
@@ -816,33 +823,34 @@ class Mininet( object ):
|
|||||||
output( '*** Iperf: testing', l4Type, 'bandwidth between',
|
output( '*** Iperf: testing', l4Type, 'bandwidth between',
|
||||||
client, 'and', server, '\n' )
|
client, 'and', server, '\n' )
|
||||||
server.cmd( 'killall -9 iperf' )
|
server.cmd( 'killall -9 iperf' )
|
||||||
iperfArgs = 'iperf -p %d ' % port
|
# Note: CSV mode
|
||||||
|
iperfArgs = 'iperf -y C -p %d ' % port
|
||||||
bwArgs = ''
|
bwArgs = ''
|
||||||
if l4Type == 'UDP':
|
if l4Type == 'UDP':
|
||||||
iperfArgs += '-u '
|
iperfArgs += '-u '
|
||||||
bwArgs = '-b ' + udpBw + ' '
|
bwArgs = '-b ' + udpBw + ' '
|
||||||
elif l4Type != 'TCP':
|
|
||||||
raise Exception( 'Unexpected l4 type: %s' % l4Type )
|
|
||||||
if fmt:
|
|
||||||
iperfArgs += '-f %s ' % fmt
|
|
||||||
server.sendCmd( iperfArgs + '-s' )
|
server.sendCmd( iperfArgs + '-s' )
|
||||||
|
serverip = server.IP()
|
||||||
if l4Type == 'TCP':
|
if l4Type == 'TCP':
|
||||||
if not waitListening( client, server.IP(), port ):
|
if not waitListening( client, serverip, port ):
|
||||||
raise Exception( 'Could not connect to iperf on port %d'
|
raise Exception( 'Could not connect to iperf on port %d'
|
||||||
% port )
|
% port )
|
||||||
cliout = client.cmd( iperfArgs + '-t %d -c ' % seconds +
|
cliout = client.cmd( iperfArgs + '-t %d -c ' % seconds +
|
||||||
server.IP() + ' ' + bwArgs )
|
server.IP() + ' ' + bwArgs )
|
||||||
debug( 'Client output: %s\n' % cliout )
|
cvals = self._iperfVals( cliout, serverip )
|
||||||
servout = ''
|
serverout = ''
|
||||||
# We want the last *b/sec from the iperf server output
|
# Wait for output from the client session
|
||||||
# for TCP, there are two of them because of waitListening
|
while True:
|
||||||
count = 2 if l4Type == 'TCP' else 1
|
serverout += server.monitor( timeoutms=5000 )
|
||||||
while len( re.findall( '/sec', servout ) ) < count:
|
svals = self._iperfVals( serverout, serverip )
|
||||||
servout += server.monitor( timeoutms=5000 )
|
# Check for the client's source/output port
|
||||||
|
if ( svals and cvals[ 'sport' ] == svals[ 'sport' ]
|
||||||
|
and int( svals[ 'rate' ] ) > 0 ):
|
||||||
|
break
|
||||||
server.sendInt()
|
server.sendInt()
|
||||||
servout += server.waitOutput()
|
serverout += server.waitOutput()
|
||||||
debug( 'Server output: %s\n' % servout )
|
result = [ fmtBps( svals[ 'rate'], fmt ),
|
||||||
result = [ self._parseIperf( servout ), self._parseIperf( cliout ) ]
|
fmtBps( cvals[ 'rate' ], fmt ) ]
|
||||||
if l4Type == 'UDP':
|
if l4Type == 'UDP':
|
||||||
result.insert( 0, udpBw )
|
result.insert( 0, udpBw )
|
||||||
output( '*** Results: %s\n' % result )
|
output( '*** Results: %s\n' % result )
|
||||||
|
|||||||
@@ -705,3 +705,26 @@ def waitListening( client=None, server='127.0.0.1', port=80, timeout=None ):
|
|||||||
time += .5
|
time += .5
|
||||||
result = runCmd( cmd )
|
result = runCmd( cmd )
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def unitScale( num, prefix='' ):
|
||||||
|
"Return unit scale prefix and factor"
|
||||||
|
scale = 'kMGTP'
|
||||||
|
if prefix:
|
||||||
|
pos = scale.lower().index( prefix.lower() )
|
||||||
|
return prefix, float( 10**(3*(pos+1)) )
|
||||||
|
num, prefix, factor = float( num ), '', 1
|
||||||
|
for i, c in enumerate(scale, start=1):
|
||||||
|
f = 10**(3*i)
|
||||||
|
if num < f:
|
||||||
|
break
|
||||||
|
prefix, factor = c, f
|
||||||
|
return prefix, float( factor )
|
||||||
|
|
||||||
|
def fmtBps( bps, prefix='', fmt='%.1f %sbits/sec' ):
|
||||||
|
"""Return bps as iperf-style formatted rate string
|
||||||
|
prefix: lock to specific prefix (k, M, G, ...)
|
||||||
|
fmt: default format string for bps, prefix"""
|
||||||
|
bps = float( bps )
|
||||||
|
prefix, factor = unitScale( bps, prefix )
|
||||||
|
bps /= factor
|
||||||
|
return fmt % ( bps, prefix)
|
||||||
|
|||||||
Reference in New Issue
Block a user