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 Davydov│ build/qemu-system-x86_64 │ build/qemu-exp │ build/qemu-exp │ 284*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