| # |
| # Migration test command line shell integration |
| # |
| # Copyright (c) 2016 Red Hat, Inc. |
| # |
| # This library is free software; you can redistribute it and/or |
| # modify it under the terms of the GNU Lesser General Public |
| # License as published by the Free Software Foundation; either |
| # version 2.1 of the License, or (at your option) any later version. |
| # |
| # This library is distributed in the hope that it will be useful, |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| # Lesser General Public License for more details. |
| # |
| # You should have received a copy of the GNU Lesser General Public |
| # License along with this library; if not, see <http://www.gnu.org/licenses/>. |
| # |
| |
| |
| import argparse |
| import fnmatch |
| import os |
| import os.path |
| import platform |
| import sys |
| import logging |
| |
| from guestperf.hardware import Hardware |
| from guestperf.engine import Engine |
| from guestperf.scenario import Scenario |
| from guestperf.comparison import COMPARISONS |
| from guestperf.plot import Plot |
| from guestperf.report import Report |
| |
| |
| class BaseShell(object): |
| |
| def __init__(self): |
| parser = argparse.ArgumentParser(description="Migration Test Tool") |
| |
| # Test args |
| parser.add_argument("--debug", dest="debug", default=False, action="store_true") |
| parser.add_argument("--verbose", dest="verbose", default=False, action="store_true") |
| parser.add_argument("--sleep", dest="sleep", default=15, type=int) |
| parser.add_argument("--binary", dest="binary", default="/usr/bin/qemu-system-x86_64") |
| parser.add_argument("--dst-host", dest="dst_host", default="localhost") |
| parser.add_argument("--kernel", dest="kernel", default="/boot/vmlinuz-%s" % platform.release()) |
| parser.add_argument("--initrd", dest="initrd", default="tests/migration/initrd-stress.img") |
| parser.add_argument("--transport", dest="transport", default="unix") |
| |
| |
| # Hardware args |
| parser.add_argument("--cpus", dest="cpus", default=1, type=int) |
| parser.add_argument("--mem", dest="mem", default=1, type=int) |
| parser.add_argument("--src-cpu-bind", dest="src_cpu_bind", default="") |
| parser.add_argument("--src-mem-bind", dest="src_mem_bind", default="") |
| parser.add_argument("--dst-cpu-bind", dest="dst_cpu_bind", default="") |
| parser.add_argument("--dst-mem-bind", dest="dst_mem_bind", default="") |
| parser.add_argument("--prealloc-pages", dest="prealloc_pages", default=False) |
| parser.add_argument("--huge-pages", dest="huge_pages", default=False) |
| parser.add_argument("--locked-pages", dest="locked_pages", default=False) |
| |
| self._parser = parser |
| |
| def get_engine(self, args): |
| return Engine(binary=args.binary, |
| dst_host=args.dst_host, |
| kernel=args.kernel, |
| initrd=args.initrd, |
| transport=args.transport, |
| sleep=args.sleep, |
| debug=args.debug, |
| verbose=args.verbose) |
| |
| def get_hardware(self, args): |
| def split_map(value): |
| if value == "": |
| return [] |
| return value.split(",") |
| |
| return Hardware(cpus=args.cpus, |
| mem=args.mem, |
| |
| src_cpu_bind=split_map(args.src_cpu_bind), |
| src_mem_bind=split_map(args.src_mem_bind), |
| dst_cpu_bind=split_map(args.dst_cpu_bind), |
| dst_mem_bind=split_map(args.dst_mem_bind), |
| |
| locked_pages=args.locked_pages, |
| huge_pages=args.huge_pages, |
| prealloc_pages=args.prealloc_pages) |
| |
| |
| class Shell(BaseShell): |
| |
| def __init__(self): |
| super(Shell, self).__init__() |
| |
| parser = self._parser |
| |
| parser.add_argument("--output", dest="output", default=None) |
| |
| # Scenario args |
| parser.add_argument("--max-iters", dest="max_iters", default=30, type=int) |
| parser.add_argument("--max-time", dest="max_time", default=300, type=int) |
| parser.add_argument("--bandwidth", dest="bandwidth", default=125000, type=int) |
| parser.add_argument("--downtime", dest="downtime", default=500, type=int) |
| |
| parser.add_argument("--pause", dest="pause", default=False, action="store_true") |
| parser.add_argument("--pause-iters", dest="pause_iters", default=5, type=int) |
| |
| parser.add_argument("--post-copy", dest="post_copy", default=False, action="store_true") |
| parser.add_argument("--post-copy-iters", dest="post_copy_iters", default=5, type=int) |
| |
| parser.add_argument("--auto-converge", dest="auto_converge", default=False, action="store_true") |
| parser.add_argument("--auto-converge-step", dest="auto_converge_step", default=10, type=int) |
| |
| parser.add_argument("--compression-mt", dest="compression_mt", default=False, action="store_true") |
| parser.add_argument("--compression-mt-threads", dest="compression_mt_threads", default=1, type=int) |
| |
| parser.add_argument("--compression-xbzrle", dest="compression_xbzrle", default=False, action="store_true") |
| parser.add_argument("--compression-xbzrle-cache", dest="compression_xbzrle_cache", default=10, type=int) |
| |
| parser.add_argument("--multifd", dest="multifd", default=False, |
| action="store_true") |
| parser.add_argument("--multifd-channels", dest="multifd_channels", |
| default=2, type=int) |
| |
| def get_scenario(self, args): |
| return Scenario(name="perfreport", |
| downtime=args.downtime, |
| bandwidth=args.bandwidth, |
| max_iters=args.max_iters, |
| max_time=args.max_time, |
| |
| pause=args.pause, |
| pause_iters=args.pause_iters, |
| |
| post_copy=args.post_copy, |
| post_copy_iters=args.post_copy_iters, |
| |
| auto_converge=args.auto_converge, |
| auto_converge_step=args.auto_converge_step, |
| |
| compression_mt=args.compression_mt, |
| compression_mt_threads=args.compression_mt_threads, |
| |
| compression_xbzrle=args.compression_xbzrle, |
| compression_xbzrle_cache=args.compression_xbzrle_cache, |
| |
| multifd=args.multifd, |
| multifd_channels=args.multifd_channels) |
| |
| def run(self, argv): |
| args = self._parser.parse_args(argv) |
| logging.basicConfig(level=(logging.DEBUG if args.debug else |
| logging.INFO if args.verbose else |
| logging.WARN)) |
| |
| |
| engine = self.get_engine(args) |
| hardware = self.get_hardware(args) |
| scenario = self.get_scenario(args) |
| |
| try: |
| report = engine.run(hardware, scenario) |
| if args.output is None: |
| print(report.to_json()) |
| else: |
| with open(args.output, "w") as fh: |
| print(report.to_json(), file=fh) |
| return 0 |
| except Exception as e: |
| print("Error: %s" % str(e), file=sys.stderr) |
| if args.debug: |
| raise |
| return 1 |
| |
| |
| class BatchShell(BaseShell): |
| |
| def __init__(self): |
| super(BatchShell, self).__init__() |
| |
| parser = self._parser |
| |
| parser.add_argument("--filter", dest="filter", default="*") |
| parser.add_argument("--output", dest="output", default=os.getcwd()) |
| |
| def run(self, argv): |
| args = self._parser.parse_args(argv) |
| logging.basicConfig(level=(logging.DEBUG if args.debug else |
| logging.INFO if args.verbose else |
| logging.WARN)) |
| |
| |
| engine = self.get_engine(args) |
| hardware = self.get_hardware(args) |
| |
| try: |
| for comparison in COMPARISONS: |
| compdir = os.path.join(args.output, comparison._name) |
| for scenario in comparison._scenarios: |
| name = os.path.join(comparison._name, scenario._name) |
| if not fnmatch.fnmatch(name, args.filter): |
| if args.verbose: |
| print("Skipping %s" % name) |
| continue |
| |
| if args.verbose: |
| print("Running %s" % name) |
| |
| dirname = os.path.join(args.output, comparison._name) |
| filename = os.path.join(dirname, scenario._name + ".json") |
| if not os.path.exists(dirname): |
| os.makedirs(dirname) |
| report = engine.run(hardware, scenario) |
| with open(filename, "w") as fh: |
| print(report.to_json(), file=fh) |
| except Exception as e: |
| print("Error: %s" % str(e), file=sys.stderr) |
| if args.debug: |
| raise |
| |
| |
| class PlotShell(object): |
| |
| def __init__(self): |
| super(PlotShell, self).__init__() |
| |
| self._parser = argparse.ArgumentParser(description="Migration Test Tool") |
| |
| self._parser.add_argument("--output", dest="output", default=None) |
| |
| self._parser.add_argument("--debug", dest="debug", default=False, action="store_true") |
| self._parser.add_argument("--verbose", dest="verbose", default=False, action="store_true") |
| |
| self._parser.add_argument("--migration-iters", dest="migration_iters", default=False, action="store_true") |
| self._parser.add_argument("--total-guest-cpu", dest="total_guest_cpu", default=False, action="store_true") |
| self._parser.add_argument("--split-guest-cpu", dest="split_guest_cpu", default=False, action="store_true") |
| self._parser.add_argument("--qemu-cpu", dest="qemu_cpu", default=False, action="store_true") |
| self._parser.add_argument("--vcpu-cpu", dest="vcpu_cpu", default=False, action="store_true") |
| |
| self._parser.add_argument("reports", nargs='*') |
| |
| def run(self, argv): |
| args = self._parser.parse_args(argv) |
| logging.basicConfig(level=(logging.DEBUG if args.debug else |
| logging.INFO if args.verbose else |
| logging.WARN)) |
| |
| |
| if len(args.reports) == 0: |
| print("At least one report required", file=sys.stderr) |
| return 1 |
| |
| if not (args.qemu_cpu or |
| args.vcpu_cpu or |
| args.total_guest_cpu or |
| args.split_guest_cpu): |
| print("At least one chart type is required", file=sys.stderr) |
| return 1 |
| |
| reports = [] |
| for report in args.reports: |
| reports.append(Report.from_json_file(report)) |
| |
| plot = Plot(reports, |
| args.migration_iters, |
| args.total_guest_cpu, |
| args.split_guest_cpu, |
| args.qemu_cpu, |
| args.vcpu_cpu) |
| |
| plot.generate(args.output) |