12f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley#!/usr/bin/env python
22f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley
3c0beca55d290fe0b1c96d78cbbcf94b05c23f5a5Peter Qiu#
4c0beca55d290fe0b1c96d78cbbcf94b05c23f5a5Peter Qiu# Copyright (C) 2010 The Android Open Source Project
5c0beca55d290fe0b1c96d78cbbcf94b05c23f5a5Peter Qiu#
6c0beca55d290fe0b1c96d78cbbcf94b05c23f5a5Peter Qiu# Licensed under the Apache License, Version 2.0 (the "License");
7c0beca55d290fe0b1c96d78cbbcf94b05c23f5a5Peter Qiu# you may not use this file except in compliance with the License.
8c0beca55d290fe0b1c96d78cbbcf94b05c23f5a5Peter Qiu# You may obtain a copy of the License at
9c0beca55d290fe0b1c96d78cbbcf94b05c23f5a5Peter Qiu#
10c0beca55d290fe0b1c96d78cbbcf94b05c23f5a5Peter Qiu#      http://www.apache.org/licenses/LICENSE-2.0
11c0beca55d290fe0b1c96d78cbbcf94b05c23f5a5Peter Qiu#
12c0beca55d290fe0b1c96d78cbbcf94b05c23f5a5Peter Qiu# Unless required by applicable law or agreed to in writing, software
13c0beca55d290fe0b1c96d78cbbcf94b05c23f5a5Peter Qiu# distributed under the License is distributed on an "AS IS" BASIS,
14c0beca55d290fe0b1c96d78cbbcf94b05c23f5a5Peter Qiu# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15c0beca55d290fe0b1c96d78cbbcf94b05c23f5a5Peter Qiu# See the License for the specific language governing permissions and
16c0beca55d290fe0b1c96d78cbbcf94b05c23f5a5Peter Qiu# limitations under the License.
17c0beca55d290fe0b1c96d78cbbcf94b05c23f5a5Peter Qiu#
182f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley
192f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wileyimport math
202f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wileyimport optparse
212f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wileyimport sched
222f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wileyimport subprocess
232f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wileyimport sys
242f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wileyimport time
252f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley
262f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley
272f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wileydef fxrange(start, finish, increment=1.0):
282f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley    """Like xrange, but with float arguments."""
292f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley    steps = int(math.ceil(float(finish - start) / increment))
302f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley
312f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley    if steps < 0:
322f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley        raise ValueError
332f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley
342f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley    for i in xrange(steps):
352f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley        yield start + i * increment
362f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley
372f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley
382f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wileydef hms(seconds):
392f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley    hours = int(seconds / (60 * 60))
402f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley    seconds -= hours * 60 * 60
412f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley    minutes = int(seconds / 60)
422f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley    seconds -= minutes * 60
432f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley    return '%d:%02d:%02d' % (hours, minutes, seconds)
442f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley
452f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley
462f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wileyclass PeriodicExperiment(object):
472f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley    """Uses the scheduler to run the specified function repeatedly."""
482f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley    def __init__(self,
492f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley                 scheduler=None,
502f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley                 total_duration=8 * 60 * 60,
512f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley                 test_interval=60,
522f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley                 test_function=None):
532f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley        self._scheduler = scheduler
542f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley        self._total_duration = total_duration
552f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley        self._test_interval = test_interval
562f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley        self._test_function = test_function
572f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley        self._start = self._scheduler.timefunc()
582f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley        self._finish = self._start + self._total_duration
592f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley
602f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley    def Run(self):
612f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley        for start_one in fxrange(self._start,
622f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley                                 self._finish,
632f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley                                 self._test_interval):
642f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley            time_remaining = self._finish - start_one
652f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley            self._scheduler.enterabs(start_one,
662f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley                                     1,     # Priority
672f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley                                     self._test_function,
682f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley                                     [time_remaining])
692f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley        self._scheduler.run()
702f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley
712f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley
722f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wileyclass ManualExperiment(object):
732f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley    """Runs the experiment repeatedly, prompting for input each time."""
742f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley    def __init__(self, test_function):
752f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley        self._test_function = test_function
762f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley
772f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley    def Run(self):
782f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley        try:
792f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley            while True:
802f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley                self._test_function(0)    # Pass in a fake time remaining
812f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley                _ = raw_input('Press return to run the test again.  '
822f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley                              'Control-c to exit.')
832f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley        except KeyboardInterrupt:
842f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley            return
852f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley
862f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wileyclass IperfTest(object):
872f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley    def __init__(self, filename, servername, individual_length):
882f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley        self._file = file(filename, 'a')
892f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley        self._servername = servername
902f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley        self._individual_length = individual_length
912f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley
922f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley    def Run(self, remaining):
932f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley        """Run iperf, log output to file, and print."""
942f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley        iperf = ['iperf',
952f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley                 '--client', self._servername,
962f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley                 # Transfer time in seconds.
972f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley                 '--time', str(self._individual_length),
982f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley                 '--reportstyle', 'c' # CSV output
992f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley                 ]
1002f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley        print '%s remaining.  Running %s' % (hms(remaining), ' '.join(iperf))
1012f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley        result = subprocess.Popen(iperf,
1022f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley                                  stdout=subprocess.PIPE).communicate()[0]
1032f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley        print result.rstrip()
1042f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley        sys.stdout.flush()
1052f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley        self._file.write(result)
1062f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley        self._file.flush()
1072f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley
1082f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley    def teardown(self):
1092f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley        self._file.close()
1102f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley
1112f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wileydef main():
1122f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley    default_output = 'stability-' + time.strftime('%Y-%m-%d-%H-%M-%S')
1132f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley
1142f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley    parser = optparse.OptionParser()
1152f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley    parser.add_option('--server', default=None,
1162f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley                      help='Machine running the iperf server')
1172f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley    parser.add_option('--test_interval', default=60 * 5, type='int',
1182f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley                      help='Interval (in seconds) between tests')
1192f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley    parser.add_option('--individual_length', default=10, type='int',
1202f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley                      help='length (in seconds) of each individual test')
1212f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley    parser.add_option('--total_duration', default=8 * 60 * 60, type='int',
1222f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley                      help='length (in seconds) for entire test')
1232f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley    parser.add_option('--output', default=default_output,
1242f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley                      help='Output file')
1252f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley    parser.add_option('--manual', default=False, action='store_true',
1262f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley                      help='Manual mode; wait for input between every test')
1272f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley
1282f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley    (options, _) = parser.parse_args()
1292f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley
1302f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley    if not options.server:
1312f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley        print 'No server specified.  Specify a server with --server=SERVER.'
1322f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley        exit(2)
1332f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley
1342f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley    if options.individual_length > options.test_interval:
1352f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley        print ('The length of a given bandwidth test must be lower than the '
1362f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley               'interval between tests')
1372f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley        exit(2)
1382f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley
1392f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley    s = sched.scheduler(time.time, time.sleep)
1402f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley
1412f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley    iperf = IperfTest(filename=options.output,
1422f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley                      servername=options.server,
1432f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley                      individual_length=options.individual_length)
1442f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley
1452f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley    if options.manual:
1462f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley        e = ManualExperiment(test_function=iperf.Run)
1472f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley    else:
1482f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley        e = PeriodicExperiment(scheduler=s,
1492f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley                               total_duration=options.total_duration,
1502f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley                               test_interval=options.test_interval,
1512f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley                               test_function=iperf.Run)
1522f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley    e.Run()
1532f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley    iperf.teardown()
1542f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley
1552f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wileyif __name__ == '__main__':
1562f48d9572459c2c90d68e8b017b86eb843fe2a74Christopher Wiley    main()
157