From 30a5fd0df4024ea8b73eaab14adef9b78aefce27 Mon Sep 17 00:00:00 2001 From: lantz Date: Fri, 21 Apr 2023 20:07:50 -0700 Subject: [PATCH] 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 --- mininet/net.py | 70 +++++++++++++++++++++++++++---------------------- mininet/util.py | 23 ++++++++++++++++ 2 files changed, 62 insertions(+), 31 deletions(-) diff --git a/mininet/net.py b/mininet/net.py index eccd32f..61c8d8b 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -98,14 +98,14 @@ from itertools import chain, groupby from math import ceil 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, Controller ) from mininet.nodelib import NAT from mininet.link import Link, Intf from mininet.util import ( quietRun, fixLimits, numCores, ensureRoot, macColonHex, ipStr, ipParse, netParse, ipAdd, - waitListening, BaseString ) + waitListening, BaseString, fmtBps ) from mininet.term import cleanUpScreens, makeTerms # Mininet version: should be consistent with README and LICENSE @@ -782,18 +782,25 @@ class Mininet( object ): return self.pingFull( hosts=hosts ) @staticmethod - def _parseIperf( iperfOutput ): - """Parse iperf output and return bandwidth. - iperfOutput: string - returns: result string""" - r = r'([\d\.]+ \w+/sec)' - m = re.findall( r, iperfOutput ) - if m: - return m[-1] - else: - # was: raise Exception(...) - error( 'could not parse iperf output: ' + iperfOutput ) - return '' + def _iperfVals( iperfcsv, serverip, _l4Type='TCP' ): + """Return iperf CSV as dict + iperfcsv: iperf -y C output + serverip: iperf server IP address + l4Type: TCP|UDP + """ + fields = 'date cip cport sip sport ipver interval sent rate' + lines = iperfcsv.strip().split('\n') + if lines: + line = lines[ -1 ].split( ',' ) + svals = dict( zip( fields.split(), line ) ) + # 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 @@ -803,7 +810,7 @@ class Mininet( object ): hosts: list of hosts; if None, uses first and last hosts l4Type: string, one of [ TCP, UDP ] 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 port: iperf port returns: two-element array of [ server, client ] speeds @@ -816,33 +823,34 @@ class Mininet( object ): output( '*** Iperf: testing', l4Type, 'bandwidth between', client, 'and', server, '\n' ) server.cmd( 'killall -9 iperf' ) - iperfArgs = 'iperf -p %d ' % port + # Note: CSV mode + iperfArgs = 'iperf -y C -p %d ' % port bwArgs = '' if l4Type == 'UDP': iperfArgs += '-u ' bwArgs = '-b ' + udpBw + ' ' - elif l4Type != 'TCP': - raise Exception( 'Unexpected l4 type: %s' % l4Type ) - if fmt: - iperfArgs += '-f %s ' % fmt server.sendCmd( iperfArgs + '-s' ) + serverip = server.IP() 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' % port ) cliout = client.cmd( iperfArgs + '-t %d -c ' % seconds + server.IP() + ' ' + bwArgs ) - debug( 'Client output: %s\n' % cliout ) - servout = '' - # We want the last *b/sec from the iperf server output - # for TCP, there are two of them because of waitListening - count = 2 if l4Type == 'TCP' else 1 - while len( re.findall( '/sec', servout ) ) < count: - servout += server.monitor( timeoutms=5000 ) + cvals = self._iperfVals( cliout, serverip ) + serverout = '' + # Wait for output from the client session + while True: + serverout += server.monitor( timeoutms=5000 ) + svals = self._iperfVals( serverout, serverip ) + # Check for the client's source/output port + if ( svals and cvals[ 'sport' ] == svals[ 'sport' ] + and int( svals[ 'rate' ] ) > 0 ): + break server.sendInt() - servout += server.waitOutput() - debug( 'Server output: %s\n' % servout ) - result = [ self._parseIperf( servout ), self._parseIperf( cliout ) ] + serverout += server.waitOutput() + result = [ fmtBps( svals[ 'rate'], fmt ), + fmtBps( cvals[ 'rate' ], fmt ) ] if l4Type == 'UDP': result.insert( 0, udpBw ) output( '*** Results: %s\n' % result ) diff --git a/mininet/util.py b/mininet/util.py index feffc48..8ccc567 100644 --- a/mininet/util.py +++ b/mininet/util.py @@ -705,3 +705,26 @@ def waitListening( client=None, server='127.0.0.1', port=80, timeout=None ): time += .5 result = runCmd( cmd ) 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)