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