1##########################################################################
2# Copyright (c) 2009, ETH Zurich.
3# All rights reserved.
4#
5# This file is distributed under the terms in the attached LICENSE file.
6# If you do not find this file, copies can be found by writing to:
7# ETH Zurich D-INFK, Haldeneggsteig 4, CH-8092 Zurich. Attn: Systems Group.
8##########################################################################
9
10import re, os, subprocess, select, time, datetime
11import tests, debug, siteconfig
12from common import TestCommon, TimeoutError, select_timeout
13from results import RowResults
14
15# TODO: this test needs a control loop like the httperf test to
16# ramp up the load until it saturates. currently it just runs N
17# iterations of a pre-configured set of parameters.
18
19IPBENCH_TEST = 'latency'
20IPBENCH_TEST_ARGS = ['Mbps=400', 'size=1300'] # per daemon
21IPBENCH_NDAEMONS = 3
22IPBENCH_ITERATIONS = 4
23IPBENCH_TIMEOUT = datetime.timedelta(seconds=60) # for a single ipbench run
24IPBENCH_SLEEPTIME = 10 # seconds between iterations
25
26LOGFILENAME = 'testlog.txt'
27
28class EchoTestCommon(TestCommon):
29    def setup(self, build, machine, testdir):
30        super(EchoTestCommon, self).setup(build, machine, testdir)
31        self.testdir = testdir
32        self.finished = False
33
34    def get_modules(self, build, machine):
35        cardName = "e1000"
36        modules = super(EchoTestCommon, self).get_modules(build, machine)
37        modules.add_module("e1000n", ["auto"])
38        modules.add_module("NGD_mng", ["auto"])
39        modules.add_module("netd", ["auto"])
40        modules.add_module("echoserver",["core=%d"%machine.get_coreids()[3],
41                                         "cardname=%s"%cardName])
42        return modules
43
44    def process_line(self, line):
45        m = re.match(r'Interface up! IP address (\d+\.\d+\.\d+\.\d+)', line)
46        if m:
47            self.run_test(m.group(1))
48            self.finished = True
49
50    def is_finished(self, line):
51        return self.finished or super(EchoTestCommon, self).is_finished(line)
52
53    def get_ipbench_test(self):
54        return (IPBENCH_TEST, IPBENCH_TEST_ARGS)
55
56    def _run_ipbenchd(self, user, host):
57        ssh_dest = '%s@%s' % (user, host)
58        remotecmd = siteconfig.get('IPBENCHD_PATH')
59        cmd = ['ssh'] + siteconfig.get('SSH_ARGS').split() + [ssh_dest, remotecmd]
60        debug.verbose('spawning ipbenchd on %s' % host)
61        return subprocess.Popen(cmd)
62
63    def _cleanup_ipbenchd(self, user, host):
64        # run a remote killall to get rid of ipbenchd
65        ssh_dest = '%s@%s' % (user, host)
66        remotecmd = 'killall -q python'
67        cmd = ['ssh'] + siteconfig.get('SSH_ARGS').split() + [ssh_dest, remotecmd]
68        debug.verbose('killing ipbenchd on %s' % host)
69        retcode = subprocess.call(cmd)
70        if retcode != 0:
71            debug.warning('failed to killall python on %s!' % host)
72
73    def _run_ipbench(self, args, logfile):
74        cmd = [siteconfig.get('IPBENCH_PATH')] + args
75        firstrun = True
76        for _ in range(IPBENCH_ITERATIONS):
77            if firstrun:
78                firstrun = False
79            else:
80                # sleep a moment to let things settle down between runs
81                debug.verbose('sleeping between ipbench runs')
82                time.sleep(IPBENCH_SLEEPTIME)
83
84            debug.verbose('running ipbench: %s' % ' '.join(cmd))
85            child = subprocess.Popen(cmd, stdout=subprocess.PIPE)
86            timeout = datetime.datetime.now() + IPBENCH_TIMEOUT
87            while True:
88                # wait for some output
89                (rlist, _, _) = select_timeout(timeout, [child.stdout])
90                if not rlist:
91                    debug.warning('ipbench run timed out')
92                    child.terminate()
93                    child.wait()
94                    raise TimeoutError('waiting for ipbench')
95                # read one char at a time to avoid blocking
96                c = child.stdout.read(1)
97                if c == '':
98                    break # EOF
99                logfile.write(c)
100            child.wait()
101            assert(child.returncode == 0) # check for successful exit
102
103    def run_test(self, targetip):
104        ipbenchds = []
105        ipbenchd_hosts = []
106        logfile = open(os.path.join(self.testdir, LOGFILENAME), 'w')
107        try:
108            # spawn ipbenchds
109            for _ in range(IPBENCH_NDAEMONS):
110                user, host = siteconfig.site.get_load_generator()
111                # can't run multiple ipbenchds on the same host
112                assert(host not in [h for (_,h) in ipbenchd_hosts])
113                ipbenchd_hosts.append((user, host))
114                ipbenchds.append(self._run_ipbenchd(user, host))
115
116            # give them a moment to start
117            time.sleep(1)
118
119            # construct command-line args to ipbench, and run it
120            test, testargs = self.get_ipbench_test()
121            args = (['--test=%s' % test, '--test-args=%s' % ','.join(testargs),
122                     '--test-target=%s' % targetip]
123                    + ['--client=%s' % h for (_, h) in ipbenchd_hosts])
124            self._run_ipbench(args, logfile)
125
126        finally:
127            logfile.close()
128
129            # terminate ipbenchds
130            for child in ipbenchds:
131                if child.poll() is None:
132                    child.terminate()
133                    child.wait()
134            for (user, host) in ipbenchd_hosts:
135                self._cleanup_ipbenchd(user, host)
136
137    def process_data(self, testdir, raw_iter):
138        cols = ('Requested Throughput,Achieved Throughput,Sent Throughput,'
139                'Packet Size,Min,Avg,Max,Standard Deviation,Median')
140        results = RowResults(cols.split(','))
141        with open(os.path.join(testdir, LOGFILENAME), 'r') as logfile:
142            for line in logfile:
143                m = re.match('(\d+),(\d+),(\d+),(\d+),(\d+),(\d+),(\d+),'
144                             '(\d+\.\d+),(\d+)', line)
145                assert(m) # line must match, otherwise we have junk output
146                vals = [float(s) if '.' in s else int(s) for s in m.groups()]
147                results.add_row(vals)
148        return results
149
150@tests.add_test
151class UDPEchoTest(EchoTestCommon):
152    '''UDP echo throughput'''
153    name = "udp_echo"
154
155    def get_ipbench_test(self):
156        (test, args) = super(UDPEchoTest, self).get_ipbench_test()
157        args.append('socktype=udp')
158        return (test, args)
159
160@tests.add_test
161class TCPEchoTest(EchoTestCommon):
162    '''TCP echo throughput'''
163    name = "tcp_echo"
164
165    def get_ipbench_test(self):
166        (test, args) = super(TCPEchoTest, self).get_ipbench_test()
167        args.append('socktype=tcp')
168        return (test, args)
169