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) 269