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