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 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 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 181class 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 227class 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) 277