blob: 89cfe2f5146a614f071336c2e1a3592607961ed6 [file] [log] [blame]
#!/usr/bin/env python
"""sample: measure wireless performance and write a report to the filesystem."""
import atexit
import functools
import os
import platform
import subprocess
import sys
import time
from fabric.api import env, execute, local, run
from fabric.network import disconnect_all
import ifstats
import iperf
import options
import tcpdump
optspec = """
sample [options...]
--
B,bind= interface IP to bind during iperf runs
d,destination= host to run tests against [192.168.1.143]
s,steps= number of steps test was run from [10]
j,journal= append to journal tracking a series of test runs
i,interface= wireless interface to use for outgoing connections
m,monitor= wireless monitor interface to use
r,remote= remote host to run tests on
"""
def main():
system = platform.system()
defaults = {
# on a modern MacBook, en0 is the AirPort and can monitor and send at once
'Darwin': {
'monitor': 'en0'
},
# on Linux, separate client and monitor interfaces are needed; and the
# monitor interface must be manually created
'Linux': {
'monitor': 'moni0' # from the `iw` documentation
},
}
o = options.Options(optspec)
(opt, _, extra) = o.parse(sys.argv[1:])
if extra:
o.fatal('did not understand supplied extra arguments.')
if not defaults.get(system):
raise OSError('Running on unsupported system {0}; '
'supported systems are {1}'.format(system,
' '.join(defaults.keys())))
# Pick the report name before we run it, so it can be consistent across
# multiple systems.
report_name = 'wifi-{}-{:04}'.format(time.time(), opt.steps)
if opt.journal:
with open(opt.journal, 'a') as journal:
print >> journal, report_name
dest_dir = os.path.join(os.path.dirname(opt.journal), report_name)
else:
dest_dir = report_name
print 'Report being written to', dest_dir
if not os.path.exists(dest_dir):
os.makedirs(dest_dir)
# we run diagnostics, write their output to files, and gather the files into
# a report that we present at the end of the run.
#
# entries collects Entry objects that have not been written to disk yet.
execute_args = {}
if opt.remote:
# update Fabric env for embedded systems
env.update({
'key_filename': os.path.expanduser('~/.ssh/bruno-sshkey'),
'user': 'root',
'shell': 'sh -l -c',
})
execute_args['host'] = opt.remote
ifsystem = 'Linux'
ifrun = run
atexit.register(disconnect_all)
else:
ifsystem = system
ifrun = functools.partial(local, capture=True)
ifs = ifstats.InterfaceStats(system=ifsystem,
runner=ifrun,
interface=opt.interface)
# since we're only executing over one host, ignore the return from `execute`
# that says which host it was for now.
cache = execute(ifs.Gather, **execute_args).values()[0]
results = ifstats.Parse(ifsystem, cache)
bssid = results['link']['BSSID']
addr = results.get('addr', {}).get(opt.interface, '')
# because ip addr usually includes a subnet mask, which will prevent iperf
# from binding to the address
mask = addr.find('/')
if mask > -1:
addr = addr[:mask]
if opt.monitor:
sudo_tcpdump, mcs_out, mcs_err = tcpdump.MCS(bssid, opt.monitor, dest_dir)
print 'Gathering tcpdump in background as', sudo_tcpdump.pid
ips = iperf.Iperf(runner=ifrun, bind=addr)
env.warn_only = True # `iperf` returns 56 if it can't reach the server,
# or 57 if it doesn't receive a final report from it
# on Linux; don't abort in these cases
cache.update(
execute(ips.RunTestSeries, opt.destination, **execute_args).values()[0]
)
if opt.monitor:
subprocess.check_call(['sudo', 'kill', str(sudo_tcpdump.pid)])
for stream in [mcs_out, mcs_err]:
stream.flush()
stream.close()
if opt.journal:
with open(opt.journal, 'a') as journal:
print >> journal, report_name
for name, value in cache.items():
with open(os.path.join(dest_dir, name), 'w+b') as outfile:
outfile.write(value)
if __name__ == '__main__':
main()