blob: efe6781e2cec55514ddbd51c7fbe1d0880fcd62d [file] [log] [blame]
"""iperf: run a series of iperf tests over a wireless network."""
import os
import pipes
import re
import sys
DEVNULL = open(os.devnull, 'wb')
class Iperf(object):
"""Iperf collects `iperf` measurements across a wireless network."""
def __init__(self, runner=None, bind=None):
self.run = runner
self.bind = bind
self.cache = {}
def Ping(self, host):
line = 'ping -c 1 {}'.format(pipes.quote(host))
return self.run(line).return_code
def _Iperf(self, host, udp=False, bandwidth=20):
"""Run iperf against host and return string containing stdout from run."""
line = 'iperf -c {}'
args = [host]
if udp:
line += ' -u -b {}'
args += [str(bandwidth * 1000000)]
if self.bind:
line += ' -B {}'
args += [self.bind]
return self.run(line.format(*[pipes.quote(arg) for arg in args]))
def IperfTCP(self, host='127.0.0.1'):
return self._Iperf(host)
def IperfUDP(self, host='127.0.0.1', bandwidth=20):
return self._Iperf(host, udp=True, bandwidth=bandwidth)
def RunTestSeries(self, host):
"""RunTestSeries runs iperf tests and returns their results.
Args:
host: string containing the hostname to run tests against
Returns:
a list of files; each file contains output for one test
"""
outputs = {}
status = self.Ping(host)
if not status:
it = self.IperfTCP(host)
outputs['iperf'] = it
# Empirically about 1.25x more packets make it through in UDP than TCP.
# Try to saturate the channel by sending a bit more than that over UDP.
bandwidth = ParseIperfTCP(it).get('bandwidth', 0.01)
outputs['iperfu'] = self.IperfUDP(host, bandwidth=bandwidth * 1.5)
else:
print >> sys.stderr, ('Could not ping destination host {0}; '
'skipping performance tests').format(host)
return outputs
def _ParseIperf(text, udp=False):
"""Parse summary line written by an `iperf` run into a Python dict."""
pattern = (r'\[(.{3})\]\s+(?P<interval>.*?sec)\s+'
r'(?P<transfer>.*?Bytes|bits)'
r'\s+(?P<bandwidth>.*?/sec)')
if udp:
pattern += r'\s+(?P<jitter>.*?s)\s+(?P<datagrams>.*)'
iperf_re = re.compile(pattern)
for line in text.splitlines():
match = iperf_re.match(line)
if match:
iperf = match.groupdict()
bval, bunit = iperf['bandwidth'].split()
iperf['bandwidth'] = float(bval)
iperf['bandwidth_unit'] = bunit
tval, tunit = iperf['transfer'].split()
iperf['transfer'] = float(tval)
iperf['transfer_unit'] = tunit
return iperf
return {}
def ParseIperfTCP(text):
# sample line: [ 4] 0.0-10.0 sec 245 MBytes 206 Mbits/sec
return _ParseIperf(text)
def ParseIperfUDP(text):
# pylint: disable=line-too-long
# sample line: [ 5] 0.0-10.0 sec 1.25 MBytes 1.05 Mbits/sec 0.593 ms 0/ 893 (0%)
return _ParseIperf(text, udp=True)
def Restore(report_dir):
"""Restores an `Iperf` cache from data on the filesystem."""
cache = {}
for name in ['iperf', 'iperfu']:
ipath = os.path.join(report_dir, name)
if os.path.exists(ipath):
with open(ipath) as infile:
cache[name] = infile.read()
else:
cache[name] = ''
return cache