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