1#
2# Migration test command line shell integration
3#
4# Copyright (c) 2016 Red Hat, Inc.
5#
6# This library is free software; you can redistribute it and/or
7# modify it under the terms of the GNU Lesser General Public
8# License as published by the Free Software Foundation; either
9# version 2 of the License, or (at your option) any later version.
10#
11# This library is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14# Lesser General Public License for more details.
15#
16# You should have received a copy of the GNU Lesser General Public
17# License along with this library; if not, see <http://www.gnu.org/licenses/>.
18#
19
20
21import os
22import os.path
23import sys
24sys.path.append(os.path.join(os.path.dirname(__file__),
25                             '..', '..', '..', 'scripts'))
26import argparse
27import fnmatch
28import platform
29
30from guestperf.hardware import Hardware
31from guestperf.engine import Engine
32from guestperf.scenario import Scenario
33from guestperf.comparison import COMPARISONS
34from guestperf.plot import Plot
35from guestperf.report import Report
36
37
38class BaseShell(object):
39
40    def __init__(self):
41        parser = argparse.ArgumentParser(description="Migration Test Tool")
42
43        # Test args
44        parser.add_argument("--debug", dest="debug", default=False, action="store_true")
45        parser.add_argument("--verbose", dest="verbose", default=False, action="store_true")
46        parser.add_argument("--sleep", dest="sleep", default=15, type=int)
47        parser.add_argument("--binary", dest="binary", default="/usr/bin/qemu-system-x86_64")
48        parser.add_argument("--dst-host", dest="dst_host", default="localhost")
49        parser.add_argument("--kernel", dest="kernel", default="/boot/vmlinuz-%s" % platform.release())
50        parser.add_argument("--initrd", dest="initrd", default="tests/migration/initrd-stress.img")
51        parser.add_argument("--transport", dest="transport", default="unix")
52
53
54        # Hardware args
55        parser.add_argument("--cpus", dest="cpus", default=1, type=int)
56        parser.add_argument("--mem", dest="mem", default=1, type=int)
57        parser.add_argument("--src-cpu-bind", dest="src_cpu_bind", default="")
58        parser.add_argument("--src-mem-bind", dest="src_mem_bind", default="")
59        parser.add_argument("--dst-cpu-bind", dest="dst_cpu_bind", default="")
60        parser.add_argument("--dst-mem-bind", dest="dst_mem_bind", default="")
61        parser.add_argument("--prealloc-pages", dest="prealloc_pages", default=False)
62        parser.add_argument("--huge-pages", dest="huge_pages", default=False)
63        parser.add_argument("--locked-pages", dest="locked_pages", default=False)
64
65        self._parser = parser
66
67    def get_engine(self, args):
68        return Engine(binary=args.binary,
69                      dst_host=args.dst_host,
70                      kernel=args.kernel,
71                      initrd=args.initrd,
72                      transport=args.transport,
73                      sleep=args.sleep,
74                      debug=args.debug,
75                      verbose=args.verbose)
76
77    def get_hardware(self, args):
78        def split_map(value):
79            if value == "":
80                return []
81            return value.split(",")
82
83        return Hardware(cpus=args.cpus,
84                        mem=args.mem,
85
86                        src_cpu_bind=split_map(args.src_cpu_bind),
87                        src_mem_bind=split_map(args.src_mem_bind),
88                        dst_cpu_bind=split_map(args.dst_cpu_bind),
89                        dst_mem_bind=split_map(args.dst_mem_bind),
90
91                        locked_pages=args.locked_pages,
92                        huge_pages=args.huge_pages,
93                        prealloc_pages=args.prealloc_pages)
94
95
96class Shell(BaseShell):
97
98    def __init__(self):
99        super(Shell, self).__init__()
100
101        parser = self._parser
102
103        parser.add_argument("--output", dest="output", default=None)
104
105        # Scenario args
106        parser.add_argument("--max-iters", dest="max_iters", default=30, type=int)
107        parser.add_argument("--max-time", dest="max_time", default=300, type=int)
108        parser.add_argument("--bandwidth", dest="bandwidth", default=125000, type=int)
109        parser.add_argument("--downtime", dest="downtime", default=500, type=int)
110
111        parser.add_argument("--pause", dest="pause", default=False, action="store_true")
112        parser.add_argument("--pause-iters", dest="pause_iters", default=5, type=int)
113
114        parser.add_argument("--post-copy", dest="post_copy", default=False, action="store_true")
115        parser.add_argument("--post-copy-iters", dest="post_copy_iters", default=5, type=int)
116
117        parser.add_argument("--auto-converge", dest="auto_converge", default=False, action="store_true")
118        parser.add_argument("--auto-converge-step", dest="auto_converge_step", default=10, type=int)
119
120        parser.add_argument("--compression-mt", dest="compression_mt", default=False, action="store_true")
121        parser.add_argument("--compression-mt-threads", dest="compression_mt_threads", default=1, type=int)
122
123        parser.add_argument("--compression-xbzrle", dest="compression_xbzrle", default=False, action="store_true")
124        parser.add_argument("--compression-xbzrle-cache", dest="compression_xbzrle_cache", default=10, type=int)
125
126    def get_scenario(self, args):
127        return Scenario(name="perfreport",
128                        downtime=args.downtime,
129                        bandwidth=args.bandwidth,
130                        max_iters=args.max_iters,
131                        max_time=args.max_time,
132
133                        pause=args.pause,
134                        pause_iters=args.pause_iters,
135
136                        post_copy=args.post_copy,
137                        post_copy_iters=args.post_copy_iters,
138
139                        auto_converge=args.auto_converge,
140                        auto_converge_step=args.auto_converge_step,
141
142                        compression_mt=args.compression_mt,
143                        compression_mt_threads=args.compression_mt_threads,
144
145                        compression_xbzrle=args.compression_xbzrle,
146                        compression_xbzrle_cache=args.compression_xbzrle_cache)
147
148    def run(self, argv):
149        args = self._parser.parse_args(argv)
150
151        engine = self.get_engine(args)
152        hardware = self.get_hardware(args)
153        scenario = self.get_scenario(args)
154
155        try:
156            report = engine.run(hardware, scenario)
157            if args.output is None:
158                print report.to_json()
159            else:
160                with open(args.output, "w") as fh:
161                    print >>fh, report.to_json()
162            return 0
163        except Exception as e:
164            print >>sys.stderr, "Error: %s" % str(e)
165            if args.debug:
166                raise
167            return 1
168
169
170class BatchShell(BaseShell):
171
172    def __init__(self):
173        super(BatchShell, self).__init__()
174
175        parser = self._parser
176
177        parser.add_argument("--filter", dest="filter", default="*")
178        parser.add_argument("--output", dest="output", default=os.getcwd())
179
180    def run(self, argv):
181        args = self._parser.parse_args(argv)
182
183        engine = self.get_engine(args)
184        hardware = self.get_hardware(args)
185
186        try:
187            for comparison in COMPARISONS:
188                compdir = os.path.join(args.output, comparison._name)
189                for scenario in comparison._scenarios:
190                    name = os.path.join(comparison._name, scenario._name)
191                    if not fnmatch.fnmatch(name, args.filter):
192                        if args.verbose:
193                            print "Skipping %s" % name
194                        continue
195
196                    if args.verbose:
197                        print "Running %s" % name
198
199                    dirname = os.path.join(args.output, comparison._name)
200                    filename = os.path.join(dirname, scenario._name + ".json")
201                    if not os.path.exists(dirname):
202                        os.makedirs(dirname)
203                    report = engine.run(hardware, scenario)
204                    with open(filename, "w") as fh:
205                        print >>fh, report.to_json()
206        except Exception as e:
207            print >>sys.stderr, "Error: %s" % str(e)
208            if args.debug:
209                raise
210
211
212class PlotShell(object):
213
214    def __init__(self):
215        super(PlotShell, self).__init__()
216
217        self._parser = argparse.ArgumentParser(description="Migration Test Tool")
218
219        self._parser.add_argument("--output", dest="output", default=None)
220
221        self._parser.add_argument("--debug", dest="debug", default=False, action="store_true")
222        self._parser.add_argument("--verbose", dest="verbose", default=False, action="store_true")
223
224        self._parser.add_argument("--migration-iters", dest="migration_iters", default=False, action="store_true")
225        self._parser.add_argument("--total-guest-cpu", dest="total_guest_cpu", default=False, action="store_true")
226        self._parser.add_argument("--split-guest-cpu", dest="split_guest_cpu", default=False, action="store_true")
227        self._parser.add_argument("--qemu-cpu", dest="qemu_cpu", default=False, action="store_true")
228        self._parser.add_argument("--vcpu-cpu", dest="vcpu_cpu", default=False, action="store_true")
229
230        self._parser.add_argument("reports", nargs='*')
231
232    def run(self, argv):
233        args = self._parser.parse_args(argv)
234
235        if len(args.reports) == 0:
236            print >>sys.stderr, "At least one report required"
237            return 1
238
239        if not (args.qemu_cpu or
240                args.vcpu_cpu or
241                args.total_guest_cpu or
242                args.split_guest_cpu):
243            print >>sys.stderr, "At least one chart type is required"
244            return 1
245
246        reports = []
247        for report in args.reports:
248            reports.append(Report.from_json_file(report))
249
250        plot = Plot(reports,
251                    args.migration_iters,
252                    args.total_guest_cpu,
253                    args.split_guest_cpu,
254                    args.qemu_cpu,
255                    args.vcpu_cpu)
256
257        plot.generate(args.output)
258