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