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 argparse
22import fnmatch
23import os
24import os.path
25import platform
26import sys
27import logging
28
29from guestperf.hardware import Hardware
30from guestperf.engine import Engine
31from guestperf.scenario import Scenario
32from guestperf.comparison import COMPARISONS
33from guestperf.plot import Plot
34from guestperf.report import Report
35
36
37class BaseShell(object):
38
39    def __init__(self):
40        parser = argparse.ArgumentParser(description="Migration Test Tool")
41
42        # Test args
43        parser.add_argument("--debug", dest="debug", default=False, action="store_true")
44        parser.add_argument("--verbose", dest="verbose", default=False, action="store_true")
45        parser.add_argument("--sleep", dest="sleep", default=15, type=int)
46        parser.add_argument("--binary", dest="binary", default="/usr/bin/qemu-system-x86_64")
47        parser.add_argument("--dst-host", dest="dst_host", default="localhost")
48        parser.add_argument("--kernel", dest="kernel", default="/boot/vmlinuz-%s" % platform.release())
49        parser.add_argument("--initrd", dest="initrd", default="tests/migration/initrd-stress.img")
50        parser.add_argument("--transport", dest="transport", default="unix")
51
52
53        # Hardware args
54        parser.add_argument("--cpus", dest="cpus", default=1, type=int)
55        parser.add_argument("--mem", dest="mem", default=1, type=int)
56        parser.add_argument("--src-cpu-bind", dest="src_cpu_bind", default="")
57        parser.add_argument("--src-mem-bind", dest="src_mem_bind", default="")
58        parser.add_argument("--dst-cpu-bind", dest="dst_cpu_bind", default="")
59        parser.add_argument("--dst-mem-bind", dest="dst_mem_bind", default="")
60        parser.add_argument("--prealloc-pages", dest="prealloc_pages", default=False)
61        parser.add_argument("--huge-pages", dest="huge_pages", default=False)
62        parser.add_argument("--locked-pages", dest="locked_pages", default=False)
63
64        self._parser = parser
65
66    def get_engine(self, args):
67        return Engine(binary=args.binary,
68                      dst_host=args.dst_host,
69                      kernel=args.kernel,
70                      initrd=args.initrd,
71                      transport=args.transport,
72                      sleep=args.sleep,
73                      debug=args.debug,
74                      verbose=args.verbose)
75
76    def get_hardware(self, args):
77        def split_map(value):
78            if value == "":
79                return []
80            return value.split(",")
81
82        return Hardware(cpus=args.cpus,
83                        mem=args.mem,
84
85                        src_cpu_bind=split_map(args.src_cpu_bind),
86                        src_mem_bind=split_map(args.src_mem_bind),
87                        dst_cpu_bind=split_map(args.dst_cpu_bind),
88                        dst_mem_bind=split_map(args.dst_mem_bind),
89
90                        locked_pages=args.locked_pages,
91                        huge_pages=args.huge_pages,
92                        prealloc_pages=args.prealloc_pages)
93
94
95class Shell(BaseShell):
96
97    def __init__(self):
98        super(Shell, self).__init__()
99
100        parser = self._parser
101
102        parser.add_argument("--output", dest="output", default=None)
103
104        # Scenario args
105        parser.add_argument("--max-iters", dest="max_iters", default=30, type=int)
106        parser.add_argument("--max-time", dest="max_time", default=300, type=int)
107        parser.add_argument("--bandwidth", dest="bandwidth", default=125000, type=int)
108        parser.add_argument("--downtime", dest="downtime", default=500, type=int)
109
110        parser.add_argument("--pause", dest="pause", default=False, action="store_true")
111        parser.add_argument("--pause-iters", dest="pause_iters", default=5, type=int)
112
113        parser.add_argument("--post-copy", dest="post_copy", default=False, action="store_true")
114        parser.add_argument("--post-copy-iters", dest="post_copy_iters", default=5, type=int)
115
116        parser.add_argument("--auto-converge", dest="auto_converge", default=False, action="store_true")
117        parser.add_argument("--auto-converge-step", dest="auto_converge_step", default=10, type=int)
118
119        parser.add_argument("--compression-mt", dest="compression_mt", default=False, action="store_true")
120        parser.add_argument("--compression-mt-threads", dest="compression_mt_threads", default=1, type=int)
121
122        parser.add_argument("--compression-xbzrle", dest="compression_xbzrle", default=False, action="store_true")
123        parser.add_argument("--compression-xbzrle-cache", dest="compression_xbzrle_cache", default=10, type=int)
124
125    def get_scenario(self, args):
126        return Scenario(name="perfreport",
127                        downtime=args.downtime,
128                        bandwidth=args.bandwidth,
129                        max_iters=args.max_iters,
130                        max_time=args.max_time,
131
132                        pause=args.pause,
133                        pause_iters=args.pause_iters,
134
135                        post_copy=args.post_copy,
136                        post_copy_iters=args.post_copy_iters,
137
138                        auto_converge=args.auto_converge,
139                        auto_converge_step=args.auto_converge_step,
140
141                        compression_mt=args.compression_mt,
142                        compression_mt_threads=args.compression_mt_threads,
143
144                        compression_xbzrle=args.compression_xbzrle,
145                        compression_xbzrle_cache=args.compression_xbzrle_cache)
146
147    def run(self, argv):
148        args = self._parser.parse_args(argv)
149        logging.basicConfig(level=(logging.DEBUG if args.debug else
150                                   logging.INFO if args.verbose else
151                                   logging.WARN))
152
153
154        engine = self.get_engine(args)
155        hardware = self.get_hardware(args)
156        scenario = self.get_scenario(args)
157
158        try:
159            report = engine.run(hardware, scenario)
160            if args.output is None:
161                print(report.to_json())
162            else:
163                with open(args.output, "w") as fh:
164                    print(report.to_json(), file=fh)
165            return 0
166        except Exception as e:
167            print("Error: %s" % str(e), file=sys.stderr)
168            if args.debug:
169                raise
170            return 1
171
172
173class BatchShell(BaseShell):
174
175    def __init__(self):
176        super(BatchShell, self).__init__()
177
178        parser = self._parser
179
180        parser.add_argument("--filter", dest="filter", default="*")
181        parser.add_argument("--output", dest="output", default=os.getcwd())
182
183    def run(self, argv):
184        args = self._parser.parse_args(argv)
185        logging.basicConfig(level=(logging.DEBUG if args.debug else
186                                   logging.INFO if args.verbose else
187                                   logging.WARN))
188
189
190        engine = self.get_engine(args)
191        hardware = self.get_hardware(args)
192
193        try:
194            for comparison in COMPARISONS:
195                compdir = os.path.join(args.output, comparison._name)
196                for scenario in comparison._scenarios:
197                    name = os.path.join(comparison._name, scenario._name)
198                    if not fnmatch.fnmatch(name, args.filter):
199                        if args.verbose:
200                            print("Skipping %s" % name)
201                        continue
202
203                    if args.verbose:
204                        print("Running %s" % name)
205
206                    dirname = os.path.join(args.output, comparison._name)
207                    filename = os.path.join(dirname, scenario._name + ".json")
208                    if not os.path.exists(dirname):
209                        os.makedirs(dirname)
210                    report = engine.run(hardware, scenario)
211                    with open(filename, "w") as fh:
212                        print(report.to_json(), file=fh)
213        except Exception as e:
214            print("Error: %s" % str(e), file=sys.stderr)
215            if args.debug:
216                raise
217
218
219class PlotShell(object):
220
221    def __init__(self):
222        super(PlotShell, self).__init__()
223
224        self._parser = argparse.ArgumentParser(description="Migration Test Tool")
225
226        self._parser.add_argument("--output", dest="output", default=None)
227
228        self._parser.add_argument("--debug", dest="debug", default=False, action="store_true")
229        self._parser.add_argument("--verbose", dest="verbose", default=False, action="store_true")
230
231        self._parser.add_argument("--migration-iters", dest="migration_iters", default=False, action="store_true")
232        self._parser.add_argument("--total-guest-cpu", dest="total_guest_cpu", default=False, action="store_true")
233        self._parser.add_argument("--split-guest-cpu", dest="split_guest_cpu", default=False, action="store_true")
234        self._parser.add_argument("--qemu-cpu", dest="qemu_cpu", default=False, action="store_true")
235        self._parser.add_argument("--vcpu-cpu", dest="vcpu_cpu", default=False, action="store_true")
236
237        self._parser.add_argument("reports", nargs='*')
238
239    def run(self, argv):
240        args = self._parser.parse_args(argv)
241        logging.basicConfig(level=(logging.DEBUG if args.debug else
242                                   logging.INFO if args.verbose else
243                                   logging.WARN))
244
245
246        if len(args.reports) == 0:
247            print("At least one report required", file=sys.stderr)
248            return 1
249
250        if not (args.qemu_cpu or
251                args.vcpu_cpu or
252                args.total_guest_cpu or
253                args.split_guest_cpu):
254            print("At least one chart type is required", file=sys.stderr)
255            return 1
256
257        reports = []
258        for report in args.reports:
259            reports.append(Report.from_json_file(report))
260
261        plot = Plot(reports,
262                    args.migration_iters,
263                    args.total_guest_cpu,
264                    args.split_guest_cpu,
265                    args.qemu_cpu,
266                    args.vcpu_cpu)
267
268        plot.generate(args.output)
269