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", 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 99class 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 201class 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 247class 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