1*b928505dSMaksim Davydov#!/usr/bin/env python3
2*b928505dSMaksim Davydov#
3*b928505dSMaksim Davydov# Script to compare machine type compatible properties (include/hw/boards.h).
4*b928505dSMaksim Davydov# compat_props are applied to the driver during initialization to change
5*b928505dSMaksim Davydov# default values, for instance, to maintain compatibility.
6*b928505dSMaksim Davydov# This script constructs table with machines and values of their compat_props
7*b928505dSMaksim Davydov# to compare and to find places for improvements or places with bugs. If
8*b928505dSMaksim Davydov# during the comparison, some machine type doesn't have a property (it is in
9*b928505dSMaksim Davydov# the comparison table because another machine type has it), then the
10*b928505dSMaksim Davydov# appropriate method will be used to obtain the default value of this driver
11*b928505dSMaksim Davydov# property via qmp command (e.g. query-cpu-model-expansion for x86_64-cpu).
12*b928505dSMaksim Davydov# These methods are defined below in qemu_property_methods.
13*b928505dSMaksim Davydov#
14*b928505dSMaksim Davydov# Copyright (c) Yandex Technologies LLC, 2023
15*b928505dSMaksim Davydov#
16*b928505dSMaksim Davydov# This program is free software; you can redistribute it and/or modify
17*b928505dSMaksim Davydov# it under the terms of the GNU General Public License as published by
18*b928505dSMaksim Davydov# the Free Software Foundation; either version 2 of the License, or
19*b928505dSMaksim Davydov# (at your option) any later version.
20*b928505dSMaksim Davydov#
21*b928505dSMaksim Davydov# This program is distributed in the hope that it will be useful,
22*b928505dSMaksim Davydov# but WITHOUT ANY WARRANTY; without even the implied warranty of
23*b928505dSMaksim Davydov# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24*b928505dSMaksim Davydov# GNU General Public License for more details.
25*b928505dSMaksim Davydov#
26*b928505dSMaksim Davydov# You should have received a copy of the GNU General Public License
27*b928505dSMaksim Davydov# along with this program; if not, see <http://www.gnu.org/licenses/>.
28*b928505dSMaksim Davydov
29*b928505dSMaksim Davydovimport sys
30*b928505dSMaksim Davydovfrom os import path
31*b928505dSMaksim Davydovfrom argparse import ArgumentParser, RawTextHelpFormatter, Namespace
32*b928505dSMaksim Davydovimport pandas as pd
33*b928505dSMaksim Davydovfrom contextlib import ExitStack
34*b928505dSMaksim Davydovfrom typing import Optional, List, Dict, Generator, Tuple, Union, Any, Set
35*b928505dSMaksim Davydov
36*b928505dSMaksim Davydovtry:
37*b928505dSMaksim Davydov    qemu_dir = path.abspath(path.dirname(path.dirname(__file__)))
38*b928505dSMaksim Davydov    sys.path.append(path.join(qemu_dir, 'python'))
39*b928505dSMaksim Davydov    from qemu.machine import QEMUMachine
40*b928505dSMaksim Davydovexcept ModuleNotFoundError as exc:
41*b928505dSMaksim Davydov    print(f"Module '{exc.name}' not found.")
42*b928505dSMaksim Davydov    print("Try export PYTHONPATH=top-qemu-dir/python or run from top-qemu-dir")
43*b928505dSMaksim Davydov    sys.exit(1)
44*b928505dSMaksim Davydov
45*b928505dSMaksim Davydov
46*b928505dSMaksim Davydovdefault_qemu_args = '-enable-kvm -machine none'
47*b928505dSMaksim Davydovdefault_qemu_binary = 'build/qemu-system-x86_64'
48*b928505dSMaksim Davydov
49*b928505dSMaksim Davydov
50*b928505dSMaksim Davydov# Methods for gettig the right values of drivers properties
51*b928505dSMaksim Davydov#
52*b928505dSMaksim Davydov# Use these methods as a 'whitelist' and add entries only if necessary. It's
53*b928505dSMaksim Davydov# important to be stable and predictable in analysis and tests.
54*b928505dSMaksim Davydov# Be careful:
55*b928505dSMaksim Davydov# * Class must be inherited from 'QEMUObject' and used in new_driver()
56*b928505dSMaksim Davydov# * Class has to implement get_prop method in order to get values
57*b928505dSMaksim Davydov# * Specialization always wins (with the given classes for 'device' and
58*b928505dSMaksim Davydov#   'x86_64-cpu', method of 'x86_64-cpu' will be used for '486-x86_64-cpu')
59*b928505dSMaksim Davydov
60*b928505dSMaksim Davydovclass Driver():
61*b928505dSMaksim Davydov    def __init__(self, vm: QEMUMachine, name: str, abstract: bool) -> None:
62*b928505dSMaksim Davydov        self.vm = vm
63*b928505dSMaksim Davydov        self.name = name
64*b928505dSMaksim Davydov        self.abstract = abstract
65*b928505dSMaksim Davydov        self.parent: Optional[Driver] = None
66*b928505dSMaksim Davydov        self.property_getter: Optional[Driver] = None
67*b928505dSMaksim Davydov
68*b928505dSMaksim Davydov    def get_prop(self, driver: str, prop: str) -> str:
69*b928505dSMaksim Davydov        if self.property_getter:
70*b928505dSMaksim Davydov            return self.property_getter.get_prop(driver, prop)
71*b928505dSMaksim Davydov        else:
72*b928505dSMaksim Davydov            return 'Unavailable method'
73*b928505dSMaksim Davydov
74*b928505dSMaksim Davydov    def is_child_of(self, parent: 'Driver') -> bool:
75*b928505dSMaksim Davydov        """Checks whether self is (recursive) child of @parent"""
76*b928505dSMaksim Davydov        cur_parent = self.parent
77*b928505dSMaksim Davydov        while cur_parent:
78*b928505dSMaksim Davydov            if cur_parent is parent:
79*b928505dSMaksim Davydov                return True
80*b928505dSMaksim Davydov            cur_parent = cur_parent.parent
81*b928505dSMaksim Davydov
82*b928505dSMaksim Davydov        return False
83*b928505dSMaksim Davydov
84*b928505dSMaksim Davydov    def set_implementations(self, implementations: List['Driver']) -> None:
85*b928505dSMaksim Davydov        self.implementations = implementations
86*b928505dSMaksim Davydov
87*b928505dSMaksim Davydov
88*b928505dSMaksim Davydovclass QEMUObject(Driver):
89*b928505dSMaksim Davydov    def __init__(self, vm: QEMUMachine, name: str) -> None:
90*b928505dSMaksim Davydov        super().__init__(vm, name, True)
91*b928505dSMaksim Davydov
92*b928505dSMaksim Davydov    def set_implementations(self, implementations: List[Driver]) -> None:
93*b928505dSMaksim Davydov        self.implementations = implementations
94*b928505dSMaksim Davydov
95*b928505dSMaksim Davydov        # each implementation of the abstract driver has to use property getter
96*b928505dSMaksim Davydov        # of this abstract driver unless it has specialization. (e.g. having
97*b928505dSMaksim Davydov        # 'device' and 'x86_64-cpu', property getter of 'x86_64-cpu' will be
98*b928505dSMaksim Davydov        # used for '486-x86_64-cpu')
99*b928505dSMaksim Davydov        for impl in implementations:
100*b928505dSMaksim Davydov            if not impl.property_getter or\
101*b928505dSMaksim Davydov                    self.is_child_of(impl.property_getter):
102*b928505dSMaksim Davydov                impl.property_getter = self
103*b928505dSMaksim Davydov
104*b928505dSMaksim Davydov
105*b928505dSMaksim Davydovclass QEMUDevice(QEMUObject):
106*b928505dSMaksim Davydov    def __init__(self, vm: QEMUMachine) -> None:
107*b928505dSMaksim Davydov        super().__init__(vm, 'device')
108*b928505dSMaksim Davydov        self.cached: Dict[str, List[Dict[str, Any]]] = {}
109*b928505dSMaksim Davydov
110*b928505dSMaksim Davydov    def get_prop(self, driver: str, prop_name: str) -> str:
111*b928505dSMaksim Davydov        if driver not in self.cached:
112*b928505dSMaksim Davydov            self.cached[driver] = self.vm.cmd('device-list-properties',
113*b928505dSMaksim Davydov                                              typename=driver)
114*b928505dSMaksim Davydov        for prop in self.cached[driver]:
115*b928505dSMaksim Davydov            if prop['name'] == prop_name:
116*b928505dSMaksim Davydov                return str(prop.get('default-value', 'No default value'))
117*b928505dSMaksim Davydov
118*b928505dSMaksim Davydov        return 'Unknown property'
119*b928505dSMaksim Davydov
120*b928505dSMaksim Davydov
121*b928505dSMaksim Davydovclass QEMUx86CPU(QEMUObject):
122*b928505dSMaksim Davydov    def __init__(self, vm: QEMUMachine) -> None:
123*b928505dSMaksim Davydov        super().__init__(vm, 'x86_64-cpu')
124*b928505dSMaksim Davydov        self.cached: Dict[str, Dict[str, Any]] = {}
125*b928505dSMaksim Davydov
126*b928505dSMaksim Davydov    def get_prop(self, driver: str, prop_name: str) -> str:
127*b928505dSMaksim Davydov        if not driver.endswith('-x86_64-cpu'):
128*b928505dSMaksim Davydov            return 'Wrong x86_64-cpu name'
129*b928505dSMaksim Davydov
130*b928505dSMaksim Davydov        # crop last 11 chars '-x86_64-cpu'
131*b928505dSMaksim Davydov        name = driver[:-11]
132*b928505dSMaksim Davydov        if name not in self.cached:
133*b928505dSMaksim Davydov            self.cached[name] = self.vm.cmd(
134*b928505dSMaksim Davydov                'query-cpu-model-expansion', type='full',
135*b928505dSMaksim Davydov                model={'name': name})['model']['props']
136*b928505dSMaksim Davydov        return str(self.cached[name].get(prop_name, 'Unknown property'))
137*b928505dSMaksim Davydov
138*b928505dSMaksim Davydov
139*b928505dSMaksim Davydov# Now it's stub, because all memory_backend types don't have default values
140*b928505dSMaksim Davydov# but this behaviour can be changed
141*b928505dSMaksim Davydovclass QEMUMemoryBackend(QEMUObject):
142*b928505dSMaksim Davydov    def __init__(self, vm: QEMUMachine) -> None:
143*b928505dSMaksim Davydov        super().__init__(vm, 'memory-backend')
144*b928505dSMaksim Davydov        self.cached: Dict[str, List[Dict[str, Any]]] = {}
145*b928505dSMaksim Davydov
146*b928505dSMaksim Davydov    def get_prop(self, driver: str, prop_name: str) -> str:
147*b928505dSMaksim Davydov        if driver not in self.cached:
148*b928505dSMaksim Davydov            self.cached[driver] = self.vm.cmd('qom-list-properties',
149*b928505dSMaksim Davydov                                              typename=driver)
150*b928505dSMaksim Davydov        for prop in self.cached[driver]:
151*b928505dSMaksim Davydov            if prop['name'] == prop_name:
152*b928505dSMaksim Davydov                return str(prop.get('default-value', 'No default value'))
153*b928505dSMaksim Davydov
154*b928505dSMaksim Davydov        return 'Unknown property'
155*b928505dSMaksim Davydov
156*b928505dSMaksim Davydov
157*b928505dSMaksim Davydovdef new_driver(vm: QEMUMachine, name: str, is_abstr: bool) -> Driver:
158*b928505dSMaksim Davydov    if name == 'object':
159*b928505dSMaksim Davydov        return QEMUObject(vm, 'object')
160*b928505dSMaksim Davydov    elif name == 'device':
161*b928505dSMaksim Davydov        return QEMUDevice(vm)
162*b928505dSMaksim Davydov    elif name == 'x86_64-cpu':
163*b928505dSMaksim Davydov        return QEMUx86CPU(vm)
164*b928505dSMaksim Davydov    elif name == 'memory-backend':
165*b928505dSMaksim Davydov        return QEMUMemoryBackend(vm)
166*b928505dSMaksim Davydov    else:
167*b928505dSMaksim Davydov        return Driver(vm, name, is_abstr)
168*b928505dSMaksim Davydov# End of methods definition
169*b928505dSMaksim Davydov
170*b928505dSMaksim Davydov
171*b928505dSMaksim Davydovclass VMPropertyGetter:
172*b928505dSMaksim Davydov    """It implements the relationship between drivers and how to get their
173*b928505dSMaksim Davydov    properties"""
174*b928505dSMaksim Davydov    def __init__(self, vm: QEMUMachine) -> None:
175*b928505dSMaksim Davydov        self.drivers: Dict[str, Driver] = {}
176*b928505dSMaksim Davydov
177*b928505dSMaksim Davydov        qom_all_types = vm.cmd('qom-list-types', abstract=True)
178*b928505dSMaksim Davydov        self.drivers = {t['name']: new_driver(vm, t['name'],
179*b928505dSMaksim Davydov                                              t.get('abstract', False))
180*b928505dSMaksim Davydov                        for t in qom_all_types}
181*b928505dSMaksim Davydov
182*b928505dSMaksim Davydov        for t in qom_all_types:
183*b928505dSMaksim Davydov            drv = self.drivers[t['name']]
184*b928505dSMaksim Davydov            if 'parent' in t:
185*b928505dSMaksim Davydov                drv.parent = self.drivers[t['parent']]
186*b928505dSMaksim Davydov
187*b928505dSMaksim Davydov        for drv in self.drivers.values():
188*b928505dSMaksim Davydov            imps = vm.cmd('qom-list-types', implements=drv.name)
189*b928505dSMaksim Davydov            # only implementations inherit property getter
190*b928505dSMaksim Davydov            drv.set_implementations([self.drivers[imp['name']]
191*b928505dSMaksim Davydov                                     for imp in imps])
192*b928505dSMaksim Davydov
193*b928505dSMaksim Davydov    def get_prop(self, driver: str, prop: str) -> str:
194*b928505dSMaksim Davydov        # wrong driver name or disabled in config driver
195*b928505dSMaksim Davydov        try:
196*b928505dSMaksim Davydov            drv = self.drivers[driver]
197*b928505dSMaksim Davydov        except KeyError:
198*b928505dSMaksim Davydov            return 'Unavailable driver'
199*b928505dSMaksim Davydov
200*b928505dSMaksim Davydov        assert not drv.abstract
201*b928505dSMaksim Davydov
202*b928505dSMaksim Davydov        return drv.get_prop(driver, prop)
203*b928505dSMaksim Davydov
204*b928505dSMaksim Davydov    def get_implementations(self, driver: str) -> List[str]:
205*b928505dSMaksim Davydov        return [impl.name for impl in self.drivers[driver].implementations]
206*b928505dSMaksim Davydov
207*b928505dSMaksim Davydov
208*b928505dSMaksim Davydovclass Machine:
209*b928505dSMaksim Davydov    """A short QEMU machine type description. It contains only processed
210*b928505dSMaksim Davydov    compat_props (properties of abstract classes are applied to its
211*b928505dSMaksim Davydov    implementations)
212*b928505dSMaksim Davydov    """
213*b928505dSMaksim Davydov    # raw_mt_dict - dict produced by `query-machines`
214*b928505dSMaksim Davydov    def __init__(self, raw_mt_dict: Dict[str, Any],
215*b928505dSMaksim Davydov                 qemu_drivers: VMPropertyGetter) -> None:
216*b928505dSMaksim Davydov        self.name = raw_mt_dict['name']
217*b928505dSMaksim Davydov        self.compat_props: Dict[str, Any] = {}
218*b928505dSMaksim Davydov        # properties are applied sequentially and can rewrite values like in
219*b928505dSMaksim Davydov        # QEMU. Also it has to resolve class relationships to apply appropriate
220*b928505dSMaksim Davydov        # values from abstract class to all implementations
221*b928505dSMaksim Davydov        for prop in raw_mt_dict['compat-props']:
222*b928505dSMaksim Davydov            driver = prop['qom-type']
223*b928505dSMaksim Davydov            try:
224*b928505dSMaksim Davydov                # implementation adds only itself, abstract class adds
225*b928505dSMaksim Davydov                #  lementation (abstract classes are uninterestiong)
226*b928505dSMaksim Davydov                impls = qemu_drivers.get_implementations(driver)
227*b928505dSMaksim Davydov                for impl in impls:
228*b928505dSMaksim Davydov                    if impl not in self.compat_props:
229*b928505dSMaksim Davydov                        self.compat_props[impl] = {}
230*b928505dSMaksim Davydov                    self.compat_props[impl][prop['property']] = prop['value']
231*b928505dSMaksim Davydov            except KeyError:
232*b928505dSMaksim Davydov                # QEMU doesn't know this driver thus it has to be saved
233*b928505dSMaksim Davydov                if driver not in self.compat_props:
234*b928505dSMaksim Davydov                    self.compat_props[driver] = {}
235*b928505dSMaksim Davydov                self.compat_props[driver][prop['property']] = prop['value']
236*b928505dSMaksim Davydov
237*b928505dSMaksim Davydov
238*b928505dSMaksim Davydovclass Configuration():
239*b928505dSMaksim Davydov    """Class contains all necessary components to generate table and is used
240*b928505dSMaksim Davydov    to compare different binaries"""
241*b928505dSMaksim Davydov    def __init__(self, vm: QEMUMachine,
242*b928505dSMaksim Davydov                 req_mt: List[str], all_mt: bool) -> None:
243*b928505dSMaksim Davydov        self._vm = vm
244*b928505dSMaksim Davydov        self._binary = vm.binary
245*b928505dSMaksim Davydov        self._qemu_args = args.qemu_args.split(' ')
246*b928505dSMaksim Davydov
247*b928505dSMaksim Davydov        self._qemu_drivers = VMPropertyGetter(vm)
248*b928505dSMaksim Davydov        self.req_mt = get_req_mt(self._qemu_drivers, vm, req_mt, all_mt)
249*b928505dSMaksim Davydov
250*b928505dSMaksim Davydov    def get_implementations(self, driver_name: str) -> List[str]:
251*b928505dSMaksim Davydov        return self._qemu_drivers.get_implementations(driver_name)
252*b928505dSMaksim Davydov
253*b928505dSMaksim Davydov    def get_table(self, req_props: List[Tuple[str, str]]) -> pd.DataFrame:
254*b928505dSMaksim Davydov        table: List[pd.DataFrame] = []
255*b928505dSMaksim Davydov        for mt in self.req_mt:
256*b928505dSMaksim Davydov            name = f'{self._binary}\n{mt.name}'
257*b928505dSMaksim Davydov            column = []
258*b928505dSMaksim Davydov            for driver, prop in req_props:
259*b928505dSMaksim Davydov                try:
260*b928505dSMaksim Davydov                    # values from QEMU machine type definitions
261*b928505dSMaksim Davydov                    column.append(mt.compat_props[driver][prop])
262*b928505dSMaksim Davydov                except KeyError:
263*b928505dSMaksim Davydov                    # values from QEMU type definitions
264*b928505dSMaksim Davydov                    column.append(self._qemu_drivers.get_prop(driver, prop))
265*b928505dSMaksim Davydov            table.append(pd.DataFrame({name: column}))
266*b928505dSMaksim Davydov
267*b928505dSMaksim Davydov        return pd.concat(table, axis=1)
268*b928505dSMaksim Davydov
269*b928505dSMaksim Davydov
270*b928505dSMaksim Davydovscript_desc = """Script to compare machine types (their compat_props).
271*b928505dSMaksim Davydov
272*b928505dSMaksim DavydovExamples:
273*b928505dSMaksim Davydov* save info about all machines:  ./scripts/compare-machine-types.py --all \
274*b928505dSMaksim Davydov--format csv --raw > table.csv
275*b928505dSMaksim Davydov* compare machines: ./scripts/compare-machine-types.py --mt pc-q35-2.12 \
276*b928505dSMaksim Davydovpc-q35-3.0
277*b928505dSMaksim Davydov* compare binaries and machines: ./scripts/compare-machine-types.py \
278*b928505dSMaksim Davydov--mt pc-q35-6.2 pc-q35-7.0 --qemu-binary build/qemu-system-x86_64 \
279*b928505dSMaksim Davydovbuild/qemu-exp
280*b928505dSMaksim Davydov  ╒════════════╤══════════════════════════╤════════════════════════════\
281*b928505dSMaksim Davydov╤════════════════════════════╤══════════════════╤══════════════════╕
282*b928505dSMaksim Davydov  │   Driver   │         Property         │  build/qemu-system-x86_64  \
283*b928505dSMaksim Davydovbuild/qemu-system-x86_64build/qemu-expbuild/qemu-exp284*b928505dSMaksim Davydov  │            │                          │         pc-q35-6.2         \
285*b928505dSMaksim Davydov│         pc-q35-7.0         │    pc-q35-6.2    │    pc-q35-7.0    │
286*b928505dSMaksim Davydov  ╞════════════╪══════════════════════════╪════════════════════════════\
287*b928505dSMaksim Davydov╪════════════════════════════╪══════════════════╪══════════════════╡
288*b928505dSMaksim Davydov  │  PIIX4_PM  │ x-not-migrate-acpi-index │            True            \
289*b928505dSMaksim Davydov│           False            │      False       │      False       │
290*b928505dSMaksim Davydov  ├────────────┼──────────────────────────┼────────────────────────────\
291*b928505dSMaksim Davydov┼────────────────────────────┼──────────────────┼──────────────────┤
292*b928505dSMaksim Davydov  │ virtio-mem │  unplugged-inaccessible  │           False            \
293*b928505dSMaksim Davydov│            auto            │      False       │       auto       │
294*b928505dSMaksim Davydov  ╘════════════╧══════════════════════════╧════════════════════════════\
295*b928505dSMaksim Davydov╧════════════════════════════╧══════════════════╧══════════════════╛
296*b928505dSMaksim Davydov
297*b928505dSMaksim DavydovIf a property from QEMU machine defintion applies to an abstract class (e.g. \
298*b928505dSMaksim Davydovx86_64-cpu) this script will compare all implementations of this class.
299*b928505dSMaksim Davydov
300*b928505dSMaksim Davydov"Unavailable method" - means that this script doesn't know how to get \
301*b928505dSMaksim Davydovdefault values of the driver. To add method use the construction described \
302*b928505dSMaksim Davydovat the top of the script.
303*b928505dSMaksim Davydov"Unavailable driver" - means that this script doesn't know this driver. \
304*b928505dSMaksim DavydovFor instance, this can happen if you configure QEMU without this device or \
305*b928505dSMaksim Davydovif machine type definition has error.
306*b928505dSMaksim Davydov"No default value" - means that the appropriate method can't get the default \
307*b928505dSMaksim Davydovvalue and most likely that this property doesn't have it.
308*b928505dSMaksim Davydov"Unknown property" - means that the appropriate method can't find property \
309*b928505dSMaksim Davydovwith this name."""
310*b928505dSMaksim Davydov
311*b928505dSMaksim Davydov
312*b928505dSMaksim Davydovdef parse_args() -> Namespace:
313*b928505dSMaksim Davydov    parser = ArgumentParser(formatter_class=RawTextHelpFormatter,
314*b928505dSMaksim Davydov                            description=script_desc)
315*b928505dSMaksim Davydov    parser.add_argument('--format', choices=['human-readable', 'json', 'csv'],
316*b928505dSMaksim Davydov                        default='human-readable',
317*b928505dSMaksim Davydov                        help='returns table in json format')
318*b928505dSMaksim Davydov    parser.add_argument('--raw', action='store_true',
319*b928505dSMaksim Davydov                        help='prints ALL defined properties without value '
320*b928505dSMaksim Davydov                             'transformation. By default, only rows '
321*b928505dSMaksim Davydov                             'with different values will be printed and '
322*b928505dSMaksim Davydov                             'values will be transformed(e.g. "on" -> True)')
323*b928505dSMaksim Davydov    parser.add_argument('--qemu-args', default=default_qemu_args,
324*b928505dSMaksim Davydov                        help='command line to start qemu. '
325*b928505dSMaksim Davydov                             f'Default: "{default_qemu_args}"')
326*b928505dSMaksim Davydov    parser.add_argument('--qemu-binary', nargs="*", type=str,
327*b928505dSMaksim Davydov                        default=[default_qemu_binary],
328*b928505dSMaksim Davydov                        help='list of qemu binaries that will be compared. '
329*b928505dSMaksim Davydov                             f'Deafult: {default_qemu_binary}')
330*b928505dSMaksim Davydov
331*b928505dSMaksim Davydov    mt_args_group = parser.add_mutually_exclusive_group()
332*b928505dSMaksim Davydov    mt_args_group.add_argument('--all', action='store_true',
333*b928505dSMaksim Davydov                               help='prints all available machine types (list '
334*b928505dSMaksim Davydov                                    'of machine types will be ignored)')
335*b928505dSMaksim Davydov    mt_args_group.add_argument('--mt', nargs="*", type=str,
336*b928505dSMaksim Davydov                               help='list of Machine Types '
337*b928505dSMaksim Davydov                                    'that will be compared')
338*b928505dSMaksim Davydov
339*b928505dSMaksim Davydov    return parser.parse_args()
340*b928505dSMaksim Davydov
341*b928505dSMaksim Davydov
342*b928505dSMaksim Davydovdef mt_comp(mt: Machine) -> Tuple[str, int, int, int]:
343*b928505dSMaksim Davydov    """Function to compare and sort machine by names.
344*b928505dSMaksim Davydov    It returns socket_name, major version, minor version, revision"""
345*b928505dSMaksim Davydov    # none, microvm, x-remote and etc.
346*b928505dSMaksim Davydov    if '-' not in mt.name or '.' not in mt.name:
347*b928505dSMaksim Davydov        return mt.name, 0, 0, 0
348*b928505dSMaksim Davydov
349*b928505dSMaksim Davydov    socket, ver = mt.name.rsplit('-', 1)
350*b928505dSMaksim Davydov    ver_list = list(map(int, ver.split('.', 2)))
351*b928505dSMaksim Davydov    ver_list += [0] * (3 - len(ver_list))
352*b928505dSMaksim Davydov    return socket, ver_list[0], ver_list[1], ver_list[2]
353*b928505dSMaksim Davydov
354*b928505dSMaksim Davydov
355*b928505dSMaksim Davydovdef get_mt_definitions(qemu_drivers: VMPropertyGetter,
356*b928505dSMaksim Davydov                       vm: QEMUMachine) -> List[Machine]:
357*b928505dSMaksim Davydov    """Constructs list of machine definitions (primarily compat_props) via
358*b928505dSMaksim Davydov    info from QEMU"""
359*b928505dSMaksim Davydov    raw_mt_defs = vm.cmd('query-machines', compat_props=True)
360*b928505dSMaksim Davydov    mt_defs = []
361*b928505dSMaksim Davydov    for raw_mt in raw_mt_defs:
362*b928505dSMaksim Davydov        mt_defs.append(Machine(raw_mt, qemu_drivers))
363*b928505dSMaksim Davydov
364*b928505dSMaksim Davydov    mt_defs.sort(key=mt_comp)
365*b928505dSMaksim Davydov    return mt_defs
366*b928505dSMaksim Davydov
367*b928505dSMaksim Davydov
368*b928505dSMaksim Davydovdef get_req_mt(qemu_drivers: VMPropertyGetter, vm: QEMUMachine,
369*b928505dSMaksim Davydov               req_mt: Optional[List[str]], all_mt: bool) -> List[Machine]:
370*b928505dSMaksim Davydov    """Returns list of requested by user machines"""
371*b928505dSMaksim Davydov    mt_defs = get_mt_definitions(qemu_drivers, vm)
372*b928505dSMaksim Davydov    if all_mt:
373*b928505dSMaksim Davydov        return mt_defs
374*b928505dSMaksim Davydov
375*b928505dSMaksim Davydov    if req_mt is None:
376*b928505dSMaksim Davydov        print('Enter machine types for comparision')
377*b928505dSMaksim Davydov        exit(0)
378*b928505dSMaksim Davydov
379*b928505dSMaksim Davydov    matched_mt = []
380*b928505dSMaksim Davydov    for mt in mt_defs:
381*b928505dSMaksim Davydov        if mt.name in req_mt:
382*b928505dSMaksim Davydov            matched_mt.append(mt)
383*b928505dSMaksim Davydov
384*b928505dSMaksim Davydov    return matched_mt
385*b928505dSMaksim Davydov
386*b928505dSMaksim Davydov
387*b928505dSMaksim Davydovdef get_affected_props(configs: List[Configuration]) -> Generator[Tuple[str,
388*b928505dSMaksim Davydov                                                                        str],
389*b928505dSMaksim Davydov                                                                  None, None]:
390*b928505dSMaksim Davydov    """Helps to go through all affected in machine definitions drivers
391*b928505dSMaksim Davydov    and properties"""
392*b928505dSMaksim Davydov    driver_props: Dict[str, Set[Any]] = {}
393*b928505dSMaksim Davydov    for config in configs:
394*b928505dSMaksim Davydov        for mt in config.req_mt:
395*b928505dSMaksim Davydov            compat_props = mt.compat_props
396*b928505dSMaksim Davydov            for driver, prop in compat_props.items():
397*b928505dSMaksim Davydov                if driver not in driver_props:
398*b928505dSMaksim Davydov                    driver_props[driver] = set()
399*b928505dSMaksim Davydov                driver_props[driver].update(prop.keys())
400*b928505dSMaksim Davydov
401*b928505dSMaksim Davydov    for driver, props in sorted(driver_props.items()):
402*b928505dSMaksim Davydov        for prop in sorted(props):
403*b928505dSMaksim Davydov            yield driver, prop
404*b928505dSMaksim Davydov
405*b928505dSMaksim Davydov
406*b928505dSMaksim Davydovdef transform_value(value: str) -> Union[str, bool]:
407*b928505dSMaksim Davydov    true_list = ['true', 'on']
408*b928505dSMaksim Davydov    false_list = ['false', 'off']
409*b928505dSMaksim Davydov
410*b928505dSMaksim Davydov    out = value.lower()
411*b928505dSMaksim Davydov
412*b928505dSMaksim Davydov    if out in true_list:
413*b928505dSMaksim Davydov        return True
414*b928505dSMaksim Davydov
415*b928505dSMaksim Davydov    if out in false_list:
416*b928505dSMaksim Davydov        return False
417*b928505dSMaksim Davydov
418*b928505dSMaksim Davydov    return value
419*b928505dSMaksim Davydov
420*b928505dSMaksim Davydov
421*b928505dSMaksim Davydovdef simplify_table(table: pd.DataFrame) -> pd.DataFrame:
422*b928505dSMaksim Davydov    """transforms values to make it easier to compare it and drops rows
423*b928505dSMaksim Davydov    with the same values for all columns"""
424*b928505dSMaksim Davydov
425*b928505dSMaksim Davydov    table = table.map(transform_value)
426*b928505dSMaksim Davydov
427*b928505dSMaksim Davydov    return table[~table.iloc[:, 3:].eq(table.iloc[:, 2], axis=0).all(axis=1)]
428*b928505dSMaksim Davydov
429*b928505dSMaksim Davydov
430*b928505dSMaksim Davydov# constructs table in the format:
431*b928505dSMaksim Davydov#
432*b928505dSMaksim Davydov# Driver  | Property  | binary1  | binary1  | ...
433*b928505dSMaksim Davydov#         |           | machine1 | machine2 | ...
434*b928505dSMaksim Davydov# ------------------------------------------------------ ...
435*b928505dSMaksim Davydov# driver1 | property1 |  value1  |  value2  | ...
436*b928505dSMaksim Davydov# driver1 | property2 |  value3  |  value4  | ...
437*b928505dSMaksim Davydov# driver2 | property3 |  value5  |  value6  | ...
438*b928505dSMaksim Davydov#   ...   |    ...    |   ...    |   ...    | ...
439*b928505dSMaksim Davydov#
440*b928505dSMaksim Davydovdef fill_prop_table(configs: List[Configuration],
441*b928505dSMaksim Davydov                    is_raw: bool) -> pd.DataFrame:
442*b928505dSMaksim Davydov    req_props = list(get_affected_props(configs))
443*b928505dSMaksim Davydov    if not req_props:
444*b928505dSMaksim Davydov        print('No drivers to compare. Check machine names')
445*b928505dSMaksim Davydov        exit(0)
446*b928505dSMaksim Davydov
447*b928505dSMaksim Davydov    driver_col, prop_col = tuple(zip(*req_props))
448*b928505dSMaksim Davydov    table = [pd.DataFrame({'Driver': driver_col}),
449*b928505dSMaksim Davydov             pd.DataFrame({'Property': prop_col})]
450*b928505dSMaksim Davydov
451*b928505dSMaksim Davydov    table.extend([config.get_table(req_props) for config in configs])
452*b928505dSMaksim Davydov
453*b928505dSMaksim Davydov    df_table = pd.concat(table, axis=1)
454*b928505dSMaksim Davydov
455*b928505dSMaksim Davydov    if is_raw:
456*b928505dSMaksim Davydov        return df_table
457*b928505dSMaksim Davydov
458*b928505dSMaksim Davydov    return simplify_table(df_table)
459*b928505dSMaksim Davydov
460*b928505dSMaksim Davydov
461*b928505dSMaksim Davydovdef print_table(table: pd.DataFrame, table_format: str) -> None:
462*b928505dSMaksim Davydov    if table_format == 'json':
463*b928505dSMaksim Davydov        print(comp_table.to_json())
464*b928505dSMaksim Davydov    elif table_format == 'csv':
465*b928505dSMaksim Davydov        print(comp_table.to_csv())
466*b928505dSMaksim Davydov    else:
467*b928505dSMaksim Davydov        print(comp_table.to_markdown(index=False, stralign='center',
468*b928505dSMaksim Davydov                                     colalign=('center',), headers='keys',
469*b928505dSMaksim Davydov                                     tablefmt='fancy_grid',
470*b928505dSMaksim Davydov                                     disable_numparse=True))
471*b928505dSMaksim Davydov
472*b928505dSMaksim Davydov
473*b928505dSMaksim Davydovif __name__ == '__main__':
474*b928505dSMaksim Davydov    args = parse_args()
475*b928505dSMaksim Davydov    with ExitStack() as stack:
476*b928505dSMaksim Davydov        vms = [stack.enter_context(QEMUMachine(binary=binary, qmp_timer=15,
477*b928505dSMaksim Davydov               args=args.qemu_args.split(' '))) for binary in args.qemu_binary]
478*b928505dSMaksim Davydov
479*b928505dSMaksim Davydov        configurations = []
480*b928505dSMaksim Davydov        for vm in vms:
481*b928505dSMaksim Davydov            vm.launch()
482*b928505dSMaksim Davydov            configurations.append(Configuration(vm, args.mt, args.all))
483*b928505dSMaksim Davydov
484*b928505dSMaksim Davydov        comp_table = fill_prop_table(configurations, args.raw)
485*b928505dSMaksim Davydov        if not comp_table.empty:
486*b928505dSMaksim Davydov            print_table(comp_table, args.format)
487