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