1from __future__ import print_function 2# 3# Migration test command line shell integration 4# 5# Copyright (c) 2016 Red Hat, Inc. 6# 7# This library is free software; you can redistribute it and/or 8# modify it under the terms of the GNU Lesser General Public 9# License as published by the Free Software Foundation; either 10# version 2 of the License, or (at your option) any later version. 11# 12# This library is distributed in the hope that it will be useful, 13# but WITHOUT ANY WARRANTY; without even the implied warranty of 14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15# Lesser General Public License for more details. 16# 17# You should have received a copy of the GNU Lesser General Public 18# License along with this library; if not, see <http://www.gnu.org/licenses/>. 19# 20 21 22import argparse 23import fnmatch 24import os 25import os.path 26import platform 27import sys 28import logging 29 30from guestperf.hardware import Hardware 31from guestperf.engine import Engine 32from guestperf.scenario import Scenario 33from guestperf.comparison import COMPARISONS 34from guestperf.plot import Plot 35from guestperf.report import Report 36 37 38class BaseShell(object): 39 40 def __init__(self): 41 parser = argparse.ArgumentParser(description="Migration Test Tool") 42 43 # Test args 44 parser.add_argument("--debug", dest="debug", default=False, action="store_true") 45 parser.add_argument("--verbose", dest="verbose", default=False, action="store_true") 46 parser.add_argument("--sleep", dest="sleep", default=15, type=int) 47 parser.add_argument("--binary", dest="binary", default="/usr/bin/qemu-system-x86_64") 48 parser.add_argument("--dst-host", dest="dst_host", default="localhost") 49 parser.add_argument("--kernel", dest="kernel", default="/boot/vmlinuz-%s" % platform.release()) 50 parser.add_argument("--initrd", dest="initrd", default="tests/migration/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 65 self._parser = parser 66 67 def get_engine(self, args): 68 return Engine(binary=args.binary, 69 dst_host=args.dst_host, 70 kernel=args.kernel, 71 initrd=args.initrd, 72 transport=args.transport, 73 sleep=args.sleep, 74 debug=args.debug, 75 verbose=args.verbose) 76 77 def get_hardware(self, args): 78 def split_map(value): 79 if value == "": 80 return [] 81 return value.split(",") 82 83 return Hardware(cpus=args.cpus, 84 mem=args.mem, 85 86 src_cpu_bind=split_map(args.src_cpu_bind), 87 src_mem_bind=split_map(args.src_mem_bind), 88 dst_cpu_bind=split_map(args.dst_cpu_bind), 89 dst_mem_bind=split_map(args.dst_mem_bind), 90 91 locked_pages=args.locked_pages, 92 huge_pages=args.huge_pages, 93 prealloc_pages=args.prealloc_pages) 94 95 96class Shell(BaseShell): 97 98 def __init__(self): 99 super(Shell, self).__init__() 100 101 parser = self._parser 102 103 parser.add_argument("--output", dest="output", default=None) 104 105 # Scenario args 106 parser.add_argument("--max-iters", dest="max_iters", default=30, type=int) 107 parser.add_argument("--max-time", dest="max_time", default=300, type=int) 108 parser.add_argument("--bandwidth", dest="bandwidth", default=125000, type=int) 109 parser.add_argument("--downtime", dest="downtime", default=500, type=int) 110 111 parser.add_argument("--pause", dest="pause", default=False, action="store_true") 112 parser.add_argument("--pause-iters", dest="pause_iters", default=5, type=int) 113 114 parser.add_argument("--post-copy", dest="post_copy", default=False, action="store_true") 115 parser.add_argument("--post-copy-iters", dest="post_copy_iters", default=5, type=int) 116 117 parser.add_argument("--auto-converge", dest="auto_converge", default=False, action="store_true") 118 parser.add_argument("--auto-converge-step", dest="auto_converge_step", default=10, type=int) 119 120 parser.add_argument("--compression-mt", dest="compression_mt", default=False, action="store_true") 121 parser.add_argument("--compression-mt-threads", dest="compression_mt_threads", default=1, type=int) 122 123 parser.add_argument("--compression-xbzrle", dest="compression_xbzrle", default=False, action="store_true") 124 parser.add_argument("--compression-xbzrle-cache", dest="compression_xbzrle_cache", default=10, type=int) 125 126 def get_scenario(self, args): 127 return Scenario(name="perfreport", 128 downtime=args.downtime, 129 bandwidth=args.bandwidth, 130 max_iters=args.max_iters, 131 max_time=args.max_time, 132 133 pause=args.pause, 134 pause_iters=args.pause_iters, 135 136 post_copy=args.post_copy, 137 post_copy_iters=args.post_copy_iters, 138 139 auto_converge=args.auto_converge, 140 auto_converge_step=args.auto_converge_step, 141 142 compression_mt=args.compression_mt, 143 compression_mt_threads=args.compression_mt_threads, 144 145 compression_xbzrle=args.compression_xbzrle, 146 compression_xbzrle_cache=args.compression_xbzrle_cache) 147 148 def run(self, argv): 149 args = self._parser.parse_args(argv) 150 logging.basicConfig(level=(logging.DEBUG if args.debug else 151 logging.INFO if args.verbose else 152 logging.WARN)) 153 154 155 engine = self.get_engine(args) 156 hardware = self.get_hardware(args) 157 scenario = self.get_scenario(args) 158 159 try: 160 report = engine.run(hardware, scenario) 161 if args.output is None: 162 print(report.to_json()) 163 else: 164 with open(args.output, "w") as fh: 165 print(report.to_json(), file=fh) 166 return 0 167 except Exception as e: 168 print("Error: %s" % str(e), file=sys.stderr) 169 if args.debug: 170 raise 171 return 1 172 173 174class BatchShell(BaseShell): 175 176 def __init__(self): 177 super(BatchShell, self).__init__() 178 179 parser = self._parser 180 181 parser.add_argument("--filter", dest="filter", default="*") 182 parser.add_argument("--output", dest="output", default=os.getcwd()) 183 184 def run(self, argv): 185 args = self._parser.parse_args(argv) 186 logging.basicConfig(level=(logging.DEBUG if args.debug else 187 logging.INFO if args.verbose else 188 logging.WARN)) 189 190 191 engine = self.get_engine(args) 192 hardware = self.get_hardware(args) 193 194 try: 195 for comparison in COMPARISONS: 196 compdir = os.path.join(args.output, comparison._name) 197 for scenario in comparison._scenarios: 198 name = os.path.join(comparison._name, scenario._name) 199 if not fnmatch.fnmatch(name, args.filter): 200 if args.verbose: 201 print("Skipping %s" % name) 202 continue 203 204 if args.verbose: 205 print("Running %s" % name) 206 207 dirname = os.path.join(args.output, comparison._name) 208 filename = os.path.join(dirname, scenario._name + ".json") 209 if not os.path.exists(dirname): 210 os.makedirs(dirname) 211 report = engine.run(hardware, scenario) 212 with open(filename, "w") as fh: 213 print(report.to_json(), file=fh) 214 except Exception as e: 215 print("Error: %s" % str(e), file=sys.stderr) 216 if args.debug: 217 raise 218 219 220class PlotShell(object): 221 222 def __init__(self): 223 super(PlotShell, self).__init__() 224 225 self._parser = argparse.ArgumentParser(description="Migration Test Tool") 226 227 self._parser.add_argument("--output", dest="output", default=None) 228 229 self._parser.add_argument("--debug", dest="debug", default=False, action="store_true") 230 self._parser.add_argument("--verbose", dest="verbose", default=False, action="store_true") 231 232 self._parser.add_argument("--migration-iters", dest="migration_iters", default=False, action="store_true") 233 self._parser.add_argument("--total-guest-cpu", dest="total_guest_cpu", default=False, action="store_true") 234 self._parser.add_argument("--split-guest-cpu", dest="split_guest_cpu", default=False, action="store_true") 235 self._parser.add_argument("--qemu-cpu", dest="qemu_cpu", default=False, action="store_true") 236 self._parser.add_argument("--vcpu-cpu", dest="vcpu_cpu", default=False, action="store_true") 237 238 self._parser.add_argument("reports", nargs='*') 239 240 def run(self, argv): 241 args = self._parser.parse_args(argv) 242 logging.basicConfig(level=(logging.DEBUG if args.debug else 243 logging.INFO if args.verbose else 244 logging.WARN)) 245 246 247 if len(args.reports) == 0: 248 print("At least one report required", file=sys.stderr) 249 return 1 250 251 if not (args.qemu_cpu or 252 args.vcpu_cpu or 253 args.total_guest_cpu or 254 args.split_guest_cpu): 255 print("At least one chart type is required", file=sys.stderr) 256 return 1 257 258 reports = [] 259 for report in args.reports: 260 reports.append(Report.from_json_file(report)) 261 262 plot = Plot(reports, 263 args.migration_iters, 264 args.total_guest_cpu, 265 args.split_guest_cpu, 266 args.qemu_cpu, 267 args.vcpu_cpu) 268 269 plot.generate(args.output) 270