1c3baa1ac4
Guestperf tool does not cover the multifd-enabled migration currently, it is worth supporting so that developers can analysis the migration performance with all kinds of migration. To request that multifd is enabled, with 4 channels: $ ./tests/migration/guestperf.py \ --multifd --multifd-channels 4 --output output.json To run the entire standardized set of multifd-enabled comparisons, with unix migration: $ ./tests/migration/guestperf-batch.py \ --dst-host localhost --transport unix \ --filter compr-multifd* --output outputdir Signed-off-by: Hyman Huang(黄勇) <huangy81@chinatelecom.cn> Message-Id: <cfeeb04d17ad932c42a9871294058b77429ad1b7.1616171924.git.huangy81@chinatelecom.cn> Reviewed-by: Daniel P. Berrangé <berrange@redhat.com> Signed-off-by: Dr. David Alan Gilbert <dgilbert@redhat.com>
277 lines
11 KiB
Python
277 lines
11 KiB
Python
#
|
|
# 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)
|