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 os 23import os.path 24import sys 25sys.path.append(os.path.join(os.path.dirname(__file__), 26 '..', '..', '..', 'scripts')) 27import argparse 28import fnmatch 29import platform 30import logging 31 32from guestperf.hardware import Hardware 33from guestperf.engine import Engine 34from guestperf.scenario import Scenario 35from guestperf.comparison import COMPARISONS 36from guestperf.plot import Plot 37from guestperf.report import Report 38 39 40class BaseShell(object): 41 42 def __init__(self): 43 parser = argparse.ArgumentParser(description="Migration Test Tool") 44 45 # Test args 46 parser.add_argument("--debug", dest="debug", default=False, action="store_true") 47 parser.add_argument("--verbose", dest="verbose", default=False, action="store_true") 48 parser.add_argument("--sleep", dest="sleep", default=15, type=int) 49 parser.add_argument("--binary", dest="binary", default="/usr/bin/qemu-system-x86_64") 50 parser.add_argument("--dst-host", dest="dst_host", default="localhost") 51 parser.add_argument("--kernel", dest="kernel", default="/boot/vmlinuz-%s" % platform.release()) 52 parser.add_argument("--initrd", dest="initrd", default="tests/migration/initrd-stress.img") 53 parser.add_argument("--transport", dest="transport", default="unix") 54 55 56 # Hardware args 57 parser.add_argument("--cpus", dest="cpus", default=1, type=int) 58 parser.add_argument("--mem", dest="mem", default=1, type=int) 59 parser.add_argument("--src-cpu-bind", dest="src_cpu_bind", default="") 60 parser.add_argument("--src-mem-bind", dest="src_mem_bind", default="") 61 parser.add_argument("--dst-cpu-bind", dest="dst_cpu_bind", default="") 62 parser.add_argument("--dst-mem-bind", dest="dst_mem_bind", default="") 63 parser.add_argument("--prealloc-pages", dest="prealloc_pages", default=False) 64 parser.add_argument("--huge-pages", dest="huge_pages", default=False) 65 parser.add_argument("--locked-pages", dest="locked_pages", default=False) 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 98class Shell(BaseShell): 99 100 def __init__(self): 101 super(Shell, self).__init__() 102 103 parser = self._parser 104 105 parser.add_argument("--output", dest="output", default=None) 106 107 # Scenario args 108 parser.add_argument("--max-iters", dest="max_iters", default=30, type=int) 109 parser.add_argument("--max-time", dest="max_time", default=300, type=int) 110 parser.add_argument("--bandwidth", dest="bandwidth", default=125000, type=int) 111 parser.add_argument("--downtime", dest="downtime", default=500, type=int) 112 113 parser.add_argument("--pause", dest="pause", default=False, action="store_true") 114 parser.add_argument("--pause-iters", dest="pause_iters", default=5, type=int) 115 116 parser.add_argument("--post-copy", dest="post_copy", default=False, action="store_true") 117 parser.add_argument("--post-copy-iters", dest="post_copy_iters", default=5, type=int) 118 119 parser.add_argument("--auto-converge", dest="auto_converge", default=False, action="store_true") 120 parser.add_argument("--auto-converge-step", dest="auto_converge_step", default=10, type=int) 121 122 parser.add_argument("--compression-mt", dest="compression_mt", default=False, action="store_true") 123 parser.add_argument("--compression-mt-threads", dest="compression_mt_threads", default=1, type=int) 124 125 parser.add_argument("--compression-xbzrle", dest="compression_xbzrle", default=False, action="store_true") 126 parser.add_argument("--compression-xbzrle-cache", dest="compression_xbzrle_cache", default=10, type=int) 127 128 def get_scenario(self, args): 129 return Scenario(name="perfreport", 130 downtime=args.downtime, 131 bandwidth=args.bandwidth, 132 max_iters=args.max_iters, 133 max_time=args.max_time, 134 135 pause=args.pause, 136 pause_iters=args.pause_iters, 137 138 post_copy=args.post_copy, 139 post_copy_iters=args.post_copy_iters, 140 141 auto_converge=args.auto_converge, 142 auto_converge_step=args.auto_converge_step, 143 144 compression_mt=args.compression_mt, 145 compression_mt_threads=args.compression_mt_threads, 146 147 compression_xbzrle=args.compression_xbzrle, 148 compression_xbzrle_cache=args.compression_xbzrle_cache) 149 150 def run(self, argv): 151 args = self._parser.parse_args(argv) 152 logging.basicConfig(level=(logging.DEBUG if args.debug else 153 logging.INFO if args.verbose else 154 logging.WARN)) 155 156 157 engine = self.get_engine(args) 158 hardware = self.get_hardware(args) 159 scenario = self.get_scenario(args) 160 161 try: 162 report = engine.run(hardware, scenario) 163 if args.output is None: 164 print(report.to_json()) 165 else: 166 with open(args.output, "w") as fh: 167 print(report.to_json(), file=fh) 168 return 0 169 except Exception as e: 170 print("Error: %s" % str(e), file=sys.stderr) 171 if args.debug: 172 raise 173 return 1 174 175 176class BatchShell(BaseShell): 177 178 def __init__(self): 179 super(BatchShell, self).__init__() 180 181 parser = self._parser 182 183 parser.add_argument("--filter", dest="filter", default="*") 184 parser.add_argument("--output", dest="output", default=os.getcwd()) 185 186 def run(self, argv): 187 args = self._parser.parse_args(argv) 188 logging.basicConfig(level=(logging.DEBUG if args.debug else 189 logging.INFO if args.verbose else 190 logging.WARN)) 191 192 193 engine = self.get_engine(args) 194 hardware = self.get_hardware(args) 195 196 try: 197 for comparison in COMPARISONS: 198 compdir = os.path.join(args.output, comparison._name) 199 for scenario in comparison._scenarios: 200 name = os.path.join(comparison._name, scenario._name) 201 if not fnmatch.fnmatch(name, args.filter): 202 if args.verbose: 203 print("Skipping %s" % name) 204 continue 205 206 if args.verbose: 207 print("Running %s" % name) 208 209 dirname = os.path.join(args.output, comparison._name) 210 filename = os.path.join(dirname, scenario._name + ".json") 211 if not os.path.exists(dirname): 212 os.makedirs(dirname) 213 report = engine.run(hardware, scenario) 214 with open(filename, "w") as fh: 215 print(report.to_json(), file=fh) 216 except Exception as e: 217 print("Error: %s" % str(e), file=sys.stderr) 218 if args.debug: 219 raise 220 221 222class PlotShell(object): 223 224 def __init__(self): 225 super(PlotShell, self).__init__() 226 227 self._parser = argparse.ArgumentParser(description="Migration Test Tool") 228 229 self._parser.add_argument("--output", dest="output", default=None) 230 231 self._parser.add_argument("--debug", dest="debug", default=False, action="store_true") 232 self._parser.add_argument("--verbose", dest="verbose", default=False, action="store_true") 233 234 self._parser.add_argument("--migration-iters", dest="migration_iters", default=False, action="store_true") 235 self._parser.add_argument("--total-guest-cpu", dest="total_guest_cpu", default=False, action="store_true") 236 self._parser.add_argument("--split-guest-cpu", dest="split_guest_cpu", default=False, action="store_true") 237 self._parser.add_argument("--qemu-cpu", dest="qemu_cpu", default=False, action="store_true") 238 self._parser.add_argument("--vcpu-cpu", dest="vcpu_cpu", default=False, action="store_true") 239 240 self._parser.add_argument("reports", nargs='*') 241 242 def run(self, argv): 243 args = self._parser.parse_args(argv) 244 logging.basicConfig(level=(logging.DEBUG if args.debug else 245 logging.INFO if args.verbose else 246 logging.WARN)) 247 248 249 if len(args.reports) == 0: 250 print("At least one report required", file=sys.stderr) 251 return 1 252 253 if not (args.qemu_cpu or 254 args.vcpu_cpu or 255 args.total_guest_cpu or 256 args.split_guest_cpu): 257 print("At least one chart type is required", file=sys.stderr) 258 return 1 259 260 reports = [] 261 for report in args.reports: 262 reports.append(Report.from_json_file(report)) 263 264 plot = Plot(reports, 265 args.migration_iters, 266 args.total_guest_cpu, 267 args.split_guest_cpu, 268 args.qemu_cpu, 269 args.vcpu_cpu) 270 271 plot.generate(args.output) 272