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