 22b7cb2c79
			
		
	
	
		22b7cb2c79
		
	
	
	
	
		
			
			Currently, guestperf does not cover the dirty-limit
migration, support this feature.
Note that dirty-limit requires 'dirty-ring-size' set.
To enable dirty-limit, setting x-vcpu-dirty-limit-period
as 500ms and x-vcpu-dirty-limit as 10MB/s:
$ ./tests/migration/guestperf.py \
    --dirty-ring-size 4096 \
    --dirty-limit --x-vcpu-dirty-limit-period 500 \
    --vcpu-dirty-limit 10 --output output.json \
To run the entire standardized set of dirty-limit-enabled
comparisons, with unix migration:
$ ./tests/migration/guestperf-batch.py \
    --dirty-ring-size 4096 \
    --dst-host localhost --transport unix \
    --filter compr-dirty-limit* --output outputdir
Signed-off-by: Hyman Huang <yong.huang@smartx.com>
Reviewed-by: Fabiano Rosas <farosas@suse.de>
Message-Id: <516e7a55dfc6e33d33510be37eb24223de5dc072.1697815117.git.yong.huang@smartx.com>
Message-ID: <e1283565b00b34b0377bbd27bee4bb8fc7c255a8.1698847223.git.yong.huang@smartx.com>
Signed-off-by: Juan Quintela <quintela@redhat.com>
		
	
			
		
			
				
	
	
		
			297 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			297 lines
		
	
	
		
			12 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)
 | |
|         parser.add_argument("--dirty-ring-size", dest="dirty_ring_size",
 | |
|                             default=0, type=int)
 | |
| 
 | |
|         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,
 | |
| 
 | |
|                         dirty_ring_size=args.dirty_ring_size)
 | |
| 
 | |
| 
 | |
| 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)
 | |
| 
 | |
|         parser.add_argument("--dirty-limit", dest="dirty_limit", default=False,
 | |
|                             action="store_true")
 | |
| 
 | |
|         parser.add_argument("--x-vcpu-dirty-limit-period",
 | |
|                             dest="x_vcpu_dirty_limit_period",
 | |
|                             default=500, type=int)
 | |
| 
 | |
|         parser.add_argument("--vcpu-dirty-limit",
 | |
|                             dest="vcpu_dirty_limit",
 | |
|                             default=1, 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,
 | |
| 
 | |
|                         dirty_limit=args.dirty_limit,
 | |
|                         x_vcpu_dirty_limit_period=\
 | |
|                             args.x_vcpu_dirty_limit_period,
 | |
|                         vcpu_dirty_limit=args.vcpu_dirty_limit)
 | |
| 
 | |
|     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)
 |