| """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 |
| |
| |