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