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