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