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 os 22import os.path 23import sys 24sys.path.append(os.path.join(os.path.dirname(__file__), 25 '..', '..', '..', 'scripts')) 26import argparse 27import fnmatch 28import platform 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 151 engine = self.get_engine(args) 152 hardware = self.get_hardware(args) 153 scenario = self.get_scenario(args) 154 155 try: 156 report = engine.run(hardware, scenario) 157 if args.output is None: 158 print report.to_json() 159 else: 160 with open(args.output, "w") as fh: 161 print >>fh, report.to_json() 162 return 0 163 except Exception as e: 164 print >>sys.stderr, "Error: %s" % str(e) 165 if args.debug: 166 raise 167 return 1 168 169 170class BatchShell(BaseShell): 171 172 def __init__(self): 173 super(BatchShell, self).__init__() 174 175 parser = self._parser 176 177 parser.add_argument("--filter", dest="filter", default="*") 178 parser.add_argument("--output", dest="output", default=os.getcwd()) 179 180 def run(self, argv): 181 args = self._parser.parse_args(argv) 182 183 engine = self.get_engine(args) 184 hardware = self.get_hardware(args) 185 186 try: 187 for comparison in COMPARISONS: 188 compdir = os.path.join(args.output, comparison._name) 189 for scenario in comparison._scenarios: 190 name = os.path.join(comparison._name, scenario._name) 191 if not fnmatch.fnmatch(name, args.filter): 192 if args.verbose: 193 print "Skipping %s" % name 194 continue 195 196 if args.verbose: 197 print "Running %s" % name 198 199 dirname = os.path.join(args.output, comparison._name) 200 filename = os.path.join(dirname, scenario._name + ".json") 201 if not os.path.exists(dirname): 202 os.makedirs(dirname) 203 report = engine.run(hardware, scenario) 204 with open(filename, "w") as fh: 205 print >>fh, report.to_json() 206 except Exception as e: 207 print >>sys.stderr, "Error: %s" % str(e) 208 if args.debug: 209 raise 210 211 212class PlotShell(object): 213 214 def __init__(self): 215 super(PlotShell, self).__init__() 216 217 self._parser = argparse.ArgumentParser(description="Migration Test Tool") 218 219 self._parser.add_argument("--output", dest="output", default=None) 220 221 self._parser.add_argument("--debug", dest="debug", default=False, action="store_true") 222 self._parser.add_argument("--verbose", dest="verbose", default=False, action="store_true") 223 224 self._parser.add_argument("--migration-iters", dest="migration_iters", default=False, action="store_true") 225 self._parser.add_argument("--total-guest-cpu", dest="total_guest_cpu", default=False, action="store_true") 226 self._parser.add_argument("--split-guest-cpu", dest="split_guest_cpu", default=False, action="store_true") 227 self._parser.add_argument("--qemu-cpu", dest="qemu_cpu", default=False, action="store_true") 228 self._parser.add_argument("--vcpu-cpu", dest="vcpu_cpu", default=False, action="store_true") 229 230 self._parser.add_argument("reports", nargs='*') 231 232 def run(self, argv): 233 args = self._parser.parse_args(argv) 234 235 if len(args.reports) == 0: 236 print >>sys.stderr, "At least one report required" 237 return 1 238 239 if not (args.qemu_cpu or 240 args.vcpu_cpu or 241 args.total_guest_cpu or 242 args.split_guest_cpu): 243 print >>sys.stderr, "At least one chart type is required" 244 return 1 245 246 reports = [] 247 for report in args.reports: 248 reports.append(Report.from_json_file(report)) 249 250 plot = Plot(reports, 251 args.migration_iters, 252 args.total_guest_cpu, 253 args.split_guest_cpu, 254 args.qemu_cpu, 255 args.vcpu_cpu) 256 257 plot.generate(args.output) 258