xref: /qemu/tests/migration-stress/guestperf/shell.py (revision 45f34156e4d9c3f4215402b34d7da32f00073066)
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.1 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",
50                            default="tests/migration-stress/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        parser.add_argument("--dirty-ring-size", dest="dirty_ring_size",
65                            default=0, type=int)
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                        dirty_ring_size=args.dirty_ring_size)
98
99
100class Shell(BaseShell):
101
102    def __init__(self):
103        super(Shell, self).__init__()
104
105        parser = self._parser
106
107        parser.add_argument("--output", dest="output", default=None)
108
109        # Scenario args
110        parser.add_argument("--max-iters", dest="max_iters", default=30, type=int)
111        parser.add_argument("--max-time", dest="max_time", default=300, type=int)
112        parser.add_argument("--bandwidth", dest="bandwidth", default=125000, type=int)
113        parser.add_argument("--downtime", dest="downtime", default=500, type=int)
114
115        parser.add_argument("--pause", dest="pause", default=False, action="store_true")
116        parser.add_argument("--pause-iters", dest="pause_iters", default=5, type=int)
117
118        parser.add_argument("--post-copy", dest="post_copy", default=False, action="store_true")
119        parser.add_argument("--post-copy-iters", dest="post_copy_iters", default=5, type=int)
120
121        parser.add_argument("--auto-converge", dest="auto_converge", default=False, action="store_true")
122        parser.add_argument("--auto-converge-step", dest="auto_converge_step", default=10, type=int)
123
124        parser.add_argument("--compression-mt", dest="compression_mt", default=False, action="store_true")
125        parser.add_argument("--compression-mt-threads", dest="compression_mt_threads", default=1, type=int)
126
127        parser.add_argument("--compression-xbzrle", dest="compression_xbzrle", default=False, action="store_true")
128        parser.add_argument("--compression-xbzrle-cache", dest="compression_xbzrle_cache", default=10, type=int)
129
130        parser.add_argument("--multifd", dest="multifd", default=False,
131                            action="store_true")
132        parser.add_argument("--multifd-channels", dest="multifd_channels",
133                            default=2, type=int)
134        parser.add_argument("--multifd-compression", dest="multifd_compression",
135                            default="")
136
137        parser.add_argument("--dirty-limit", dest="dirty_limit", default=False,
138                            action="store_true")
139
140        parser.add_argument("--x-vcpu-dirty-limit-period",
141                            dest="x_vcpu_dirty_limit_period",
142                            default=500, type=int)
143
144        parser.add_argument("--vcpu-dirty-limit",
145                            dest="vcpu_dirty_limit",
146                            default=1, type=int)
147
148    def get_scenario(self, args):
149        return Scenario(name="perfreport",
150                        downtime=args.downtime,
151                        bandwidth=args.bandwidth,
152                        max_iters=args.max_iters,
153                        max_time=args.max_time,
154
155                        pause=args.pause,
156                        pause_iters=args.pause_iters,
157
158                        post_copy=args.post_copy,
159                        post_copy_iters=args.post_copy_iters,
160
161                        auto_converge=args.auto_converge,
162                        auto_converge_step=args.auto_converge_step,
163
164                        compression_mt=args.compression_mt,
165                        compression_mt_threads=args.compression_mt_threads,
166
167                        compression_xbzrle=args.compression_xbzrle,
168                        compression_xbzrle_cache=args.compression_xbzrle_cache,
169
170                        multifd=args.multifd,
171                        multifd_channels=args.multifd_channels,
172                        multifd_compression=args.multifd_compression,
173
174                        dirty_limit=args.dirty_limit,
175                        x_vcpu_dirty_limit_period=\
176                            args.x_vcpu_dirty_limit_period,
177                        vcpu_dirty_limit=args.vcpu_dirty_limit)
178
179    def run(self, argv):
180        args = self._parser.parse_args(argv)
181        logging.basicConfig(level=(logging.DEBUG if args.debug else
182                                   logging.INFO if args.verbose else
183                                   logging.WARN))
184
185
186        engine = self.get_engine(args)
187        hardware = self.get_hardware(args)
188        scenario = self.get_scenario(args)
189
190        try:
191            report = engine.run(hardware, scenario)
192            if args.output is None:
193                print(report.to_json())
194            else:
195                with open(args.output, "w") as fh:
196                    print(report.to_json(), file=fh)
197            return 0
198        except Exception as e:
199            print("Error: %s" % str(e), file=sys.stderr)
200            if args.debug:
201                raise
202            return 1
203
204
205class BatchShell(BaseShell):
206
207    def __init__(self):
208        super(BatchShell, self).__init__()
209
210        parser = self._parser
211
212        parser.add_argument("--filter", dest="filter", default="*")
213        parser.add_argument("--output", dest="output", default=os.getcwd())
214
215    def run(self, argv):
216        args = self._parser.parse_args(argv)
217        logging.basicConfig(level=(logging.DEBUG if args.debug else
218                                   logging.INFO if args.verbose else
219                                   logging.WARN))
220
221
222        engine = self.get_engine(args)
223        hardware = self.get_hardware(args)
224
225        try:
226            for comparison in COMPARISONS:
227                compdir = os.path.join(args.output, comparison._name)
228                for scenario in comparison._scenarios:
229                    name = os.path.join(comparison._name, scenario._name)
230                    if not fnmatch.fnmatch(name, args.filter):
231                        if args.verbose:
232                            print("Skipping %s" % name)
233                        continue
234
235                    if args.verbose:
236                        print("Running %s" % name)
237
238                    dirname = os.path.join(args.output, comparison._name)
239                    filename = os.path.join(dirname, scenario._name + ".json")
240                    if not os.path.exists(dirname):
241                        os.makedirs(dirname)
242                    report = engine.run(hardware, scenario)
243                    with open(filename, "w") as fh:
244                        print(report.to_json(), file=fh)
245        except Exception as e:
246            print("Error: %s" % str(e), file=sys.stderr)
247            if args.debug:
248                raise
249
250
251class PlotShell(object):
252
253    def __init__(self):
254        super(PlotShell, self).__init__()
255
256        self._parser = argparse.ArgumentParser(description="Migration Test Tool")
257
258        self._parser.add_argument("--output", dest="output", default=None)
259
260        self._parser.add_argument("--debug", dest="debug", default=False, action="store_true")
261        self._parser.add_argument("--verbose", dest="verbose", default=False, action="store_true")
262
263        self._parser.add_argument("--migration-iters", dest="migration_iters", default=False, action="store_true")
264        self._parser.add_argument("--total-guest-cpu", dest="total_guest_cpu", default=False, action="store_true")
265        self._parser.add_argument("--split-guest-cpu", dest="split_guest_cpu", default=False, action="store_true")
266        self._parser.add_argument("--qemu-cpu", dest="qemu_cpu", default=False, action="store_true")
267        self._parser.add_argument("--vcpu-cpu", dest="vcpu_cpu", default=False, action="store_true")
268
269        self._parser.add_argument("reports", nargs='*')
270
271    def run(self, argv):
272        args = self._parser.parse_args(argv)
273        logging.basicConfig(level=(logging.DEBUG if args.debug else
274                                   logging.INFO if args.verbose else
275                                   logging.WARN))
276
277
278        if len(args.reports) == 0:
279            print("At least one report required", file=sys.stderr)
280            return 1
281
282        if not (args.qemu_cpu or
283                args.vcpu_cpu or
284                args.total_guest_cpu or
285                args.split_guest_cpu):
286            print("At least one chart type is required", file=sys.stderr)
287            return 1
288
289        reports = []
290        for report in args.reports:
291            reports.append(Report.from_json_file(report))
292
293        plot = Plot(reports,
294                    args.migration_iters,
295                    args.total_guest_cpu,
296                    args.split_guest_cpu,
297                    args.qemu_cpu,
298                    args.vcpu_cpu)
299
300        plot.generate(args.output)
301