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