All of lore.kernel.org
 help / color / mirror / Atom feed
From: Vladimir Sementsov-Ogievskiy <v.sementsov-og@mail.ru>
To: Maxim Davydov <maxim.davydov@openvz.org>, qemu-devel@nongnu.org
Cc: den@openvz.org, eduardo@habkost.net, marcel.apfelbaum@gmail.com,
	f4bug@amsat.org, wangyanan55@huawei.com, eblake@redhat.com,
	armbru@redhat.com, mst@redhat.com, pbonzini@redhat.com,
	xiaoguangrong.eric@gmail.com, imammedo@redhat.com,
	ani@anisinha.ca, marcandre.lureau@redhat.com,
	chen.zhang@intel.com, lizhijian@fujitsu.com, berrange@redhat.com,
	jsnow@redhat.com, crosa@redhat.com
Subject: Re: [PATCH v1 9/9] scripts: printing machine type compat properties
Date: Wed, 30 Mar 2022 18:55:15 +0300	[thread overview]
Message-ID: <00246e6f-85a4-487f-7025-fe616a65ebe6@mail.ru> (raw)
In-Reply-To: <20220328211539.90170-10-maxim.davydov@openvz.org>

29.03.2022 00:15, Maxim Davydov wrote:
> This script makes the information that can be obtained from
> query-init-properties and query-machines easy to read.
> 
> Note: some init values from the devices can't be available like properties
> from virtio-9p when configure has --disable-virtfs. Such values are
> replaced by "DEFAULT". Another exception is properties of abstract
> classes. The default value of the abstract class property is common
> value of all child classes. But if the values of the child classes are
> different the default value will be "BASED_ON_CHILD" (for example, old
> x86_64-cpu can have unsupported feature).
> 
> Example:
> 
>      1) virsh qemu-monitor-command VM --pretty \
>         '{"execute" : "query-init-properties"}' > init_props.json
> 
>      2) virsh qemu-monitor-command VM --pretty \
>         '{"execute" : "query-machines", "arguments" : {"is-full" : true}}' \
>         > compat_props.json
> 
>      3) scripts/print_MT.py --MT_compat_props compat_props.json\
>          --init_props init_props.json --mt pc-q35-7.0 pc-q35-6.1
> 
> Output:
> ╒═══════════════════════════════════╤══════════════╤══════════════╕
> │           property_name           │  pc-q35-7.0  │  pc-q35-6.1  │
> ╞═══════════════════════════════════╪══════════════╪══════════════╡
> │   ICH9-LPC-x-keep-pci-slot-hpc    │     True     │    False     │
> ├───────────────────────────────────┼──────────────┼──────────────┤
> │          nvme-ns-shared           │     True     │     off      │
> ├───────────────────────────────────┼──────────────┼──────────────┤
> │ vhost-user-vsock-device-seqpacket │     auto     │     off      │
> ├───────────────────────────────────┼──────────────┼──────────────┤
> │ virtio-mem-unplugged-inaccessible │     auto     │     off      │
> ├───────────────────────────────────┼──────────────┼──────────────┤
> │  x86_64-cpu-hv-version-id-build   │    14393     │    0x1bbc    │
> ├───────────────────────────────────┼──────────────┼──────────────┤
> │  x86_64-cpu-hv-version-id-major   │      10      │    0x0006    │
> ├───────────────────────────────────┼──────────────┼──────────────┤
> │  x86_64-cpu-hv-version-id-minor   │      0       │    0x0001    │
> ╘═══════════════════════════════════╧══════════════╧══════════════╛
> 
> Signed-off-by: Maxim Davydov <maxim.davydov@openvz.org>
> ---
>   scripts/print_MT.py | 274 ++++++++++++++++++++++++++++++++++++++++++++
>   1 file changed, 274 insertions(+)
>   create mode 100755 scripts/print_MT.py
> 
> diff --git a/scripts/print_MT.py b/scripts/print_MT.py
> new file mode 100755
> index 0000000000..8be13be8d7
> --- /dev/null
> +++ b/scripts/print_MT.py
> @@ -0,0 +1,274 @@
> +#! /usr/bin/python3

Standard shebang line for python3 is:

#!/usr/bin/env python3


> +#
> +# Script for printing machine type compatible features. It uses two JSON files
> +# that should be generated by qmp-init-properties and query-machines.
> +#
> +# Copyright (c) 2022 Maxim Davydov <maxim.davydov@openvz.org>
> +#
> +# This program is free software; you can redistribute it and/or modify
> +# it under the terms of the GNU General Public License as published by
> +# the Free Software Foundation; either version 2 of the License, or
> +# (at your option) any later version.
> +#
> +# This program 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 General Public License for more details.
> +#
> +# You should have received a copy of the GNU General Public License
> +# along with this program. If not, see <http://www.gnu.org/licenses/>.
> +#
> +
> +import pandas as pd
> +import json
> +from tabulate import tabulate
> +from argparse import ArgumentParser
> +
> +
> +# used for aliases and other names that can be changed
> +aliases = { "e1000-82540em": "e1000" }
> +
> +
> +def get_major(mt):
> +    splited = mt.split('.')
> +    if (len(splited) >= 2):
> +        return int(mt.split('.')[1])

why to call split() again? You may use splited[1]

> +    else:
> +        return 0
> +
> +
> +def get_prefix(mt):
> +    splited = mt.split('.')
> +    if (len(splited) >= 1):

In python you don't need braces around if condition:

    if len(splited) >= 1:

is the same thing.

> +        return mt.split('.')[0]
> +    else:
> +        return mt

seems, split() never return empty list, so len is always >=1.

You can do simply

return mt.split(',', 1)[0]

> +
> +
> +def get_mt_sequence(mt_data):
> +    mt_list = [mt['name'] for mt in mt_data['return']]
> +    mt_list.remove('none')
> +
> +    mt_list.sort(key=get_major, reverse=True)
> +    mt_list.sort(key=get_prefix, reverse=True)

Aha. You may use one sort() call, if use tuple as key. Something like this should work:

def parse_mt(mt):
   spl = mt.split('.')
   if len(spl) >= 2:
     return spl[0], spl[1]
   else:
     return mt, 0

...

mt_list.sort(key=parse_mt, ...)

> +
> +    return mt_list
> +
> +
> +def get_req_props(defval_data, prop_set, abstr_class_to_features):
> +    req_prop_values = dict()
> +    req_abstr_prop_values = dict()

{} construction is native way to create empty dict:

   req_prop_values = {}

> +
> +    for device in defval_data['return']:
> +        # Skip cpu devices that will break all default values for cpus
> +        if device['name'] == 'base-x86_64-cpu':
> +            continue
> +        if device['name'] == 'max-x86_64-cpu':
> +            continue
> +
> +        # some features in mt set as one absract class
> +        # but this features are owned by another class

Hmm. That all hard for me to review, because I don't know the whole model of machine types.. Don't we have a documentation somewhere? Which objects, classes, abstart classes and properties we have and what that all mean.

> +        device_props_owners = dict()
> +        for props_class in device['props']:
> +            if not 'classprops' in props_class: # for example, Object class

More pythonic is:

   if 'classprops' not in props_class:

> +                continue
> +
> +            for prop in props_class['classprops']:
> +                if not 'value' in prop:
> +                    continue
> +
> +                prop_name = device['name'] + '-' + prop['name']
> +                device_props_owners[prop['name']] = prop['value']
> +                if prop_name in prop_set:
> +                    req_prop_values[prop_name] = prop['value']
> +
> +        for props_class in device['props']:
> +            if not props_class['classname'] in abstr_class_to_features:
> +                continue
> +
> +            for req_prop in abstr_class_to_features[props_class['classname']]:
> +                if not req_prop in device_props_owners:
> +                    continue
> +
> +                prop_value = device_props_owners[req_prop]
> +                prop_name = props_class['classname'] + '-' + req_prop
> +                if req_abstr_prop_values.setdefault(prop_name, prop_value) \
> +                    != prop_value:
> +                    req_abstr_prop_values[prop_name] = 'BASED_ON_CHILD'
> +
> +    return req_prop_values, req_abstr_prop_values
> +
> +
> +def make_definition_table(mt_to_compat_props, prop_set,
> +                          req_props, req_abstr_props, is_full):
> +    mt_table = dict()
> +    for prop in sorted(prop_set):
> +        if not is_full:
> +            values = set()
> +            for mt in mt_to_compat_props:
> +                if prop in mt_to_compat_props[mt]:
> +                    values.add(mt_to_compat_props[mt][prop])
> +                else:
> +                    if prop in req_props:
> +                        values.add(req_props[prop])
> +                    else:
> +                        values.add('DEFAULT')
> +            # Skip the property if its value is the same for
> +            # all required machines
> +            if len(values) == 1:
> +                continue
> +
> +        mt_table.setdefault('property_name', []).append(prop)
> +        for mt in mt_to_compat_props:
> +            if prop in mt_to_compat_props[mt]:
> +                value = mt_to_compat_props[mt][prop]
> +                mt_table.setdefault(mt, []).append(value)
> +            else:
> +                if prop in req_props:
> +                    mt_table.setdefault(mt, []).append(req_props[prop])
> +                else:
> +                    value = req_abstr_props.get(prop, 'DEFAULT')
> +                    mt_table.setdefault(mt, []).append(value)
> +
> +    return mt_table
> +
> +
> +def get_standard_form(value):
> +    if type(value) is str:
> +        out = value.upper()
> +        if out.isnumeric():
> +            return int(out)
> +        if out == 'TRUE':
> +            return True
> +        if out == 'FALSE':
> +            return False
> +
> +    return value
> +
> +
> +def get_features(mt_data, defval_data, mts, is_full):
> +    prop_set = set()
> +    abstr_prop_set = set()
> +    mt_to_compat_props = dict()
> +    # It will be used for searching appropriate feature (sometimes class name
> +    # in machine type definition and real class name are different)
> +    abstr_class_to_features = dict()
> +
> +    for mt in mt_data['return']:
> +        if mt['name'] == 'none':
> +            continue
> +
> +        if not mt['name'] in mts:
> +            continue
> +
> +        mt_to_compat_props[mt['name']] = dict()
> +        for prop in mt['compat-props']:
> +            driver_name = aliases.get(prop['driver'], prop['driver'])
> +            prop_name = prop['driver'] + '-' + prop['property']
> +            real_name = driver_name + '-' + prop['property']
> +            # value is always string
> +            prop_val  = get_standard_form(prop['value'])
> +            if prop['abstract']:
> +                mt_to_compat_props[mt['name']][real_name] = prop_val
> +                abstr_prop_set.add(real_name)
> +                abstr_class_to_features.setdefault(driver_name,
> +                                                   set()).add(prop['property'])
> +            else:
> +                mt_to_compat_props[mt['name']][real_name] = prop_val
> +                prop_set.add(real_name)
> +
> +    req_props, req_abstr_props = get_req_props(defval_data, prop_set,
> +                                               abstr_class_to_features)
> +
> +    # join sets for global sorting by name
> +    prop_set.update(abstr_prop_set)
> +    mt_table = make_definition_table(mt_to_compat_props, prop_set, req_props,
> +                                     req_abstr_props, is_full)
> +    # to save mt sequence
> +    df = pd.DataFrame({'property_name': mt_table['property_name']})
> +    for mt in mts:
> +        if not mt in mt_table:
> +            print('Error: {0} no found'.format(mt))
> +            continue
> +        df[mt] = mt_table[mt]
> +
> +    return df
> +
> +
> +def main():
> +    parser = ArgumentParser(description='''Print definition of machine
> +                                           type (compatible features)''')
> +    parser.add_argument('--MT_compat_props', type=str, required=True,
> +                        help='''Path to JSON file with current machine type
> +                                definition. It must be generated via
> +                                query-machines with is-full option.''')
> +    parser.add_argument('--init_props', type=str, required=True,
> +                        help='''Path to JSON file with initial features. It
> +                                must be generated via
> +                                query-init-properties.''')
> +    parser.add_argument('--format', type=str,
> +                        choices=['table', 'csv', 'html', 'json'],
> +                        default='table', help='Format of the output file')
> +    parser.add_argument('--file', type=str,
> +                        help='''Path to output file. It must be set with csv
> +                                and html formats.''')
> +    parser.add_argument('--all', action='store_true',
> +                        help='''Print all available machine types (list of
> +                                machine types will be ignored''')
> +    parser.add_argument('--mt', nargs="*", type=str,
> +                        help='List of machine types that will be compared')
> +    parser.add_argument('--full', action='store_true',
> +                        help='''Print all defined properties (by default,
> +                                only properties with different values are
> +                                printed)''')
> +
> +    args = parser.parse_args()
> +
> +    if args.all == 0 and args.mt == None:

Don't compare boolean value with zero, use it as is (I'm about args.all, but comparing mt with None doesn't make real sense here too):

   if not(args.all or args.mt):

> +        print('Enter the list of required machine types (list of all '\
> +              'machine types : qemu-system-x86_64 --machine help)')
> +        return
> +
> +    with open(args.MT_compat_props) as mt_json_file:
> +        mt_data = json.load(mt_json_file)
> +
> +    with open(args.init_props) as defval_json_file:
> +        defval_data = json.load(defval_json_file)
> +
> +    if args.all:
> +        df = get_features(mt_data, defval_data, get_mt_sequence(mt_data),
> +                          args.full)
> +    else:
> +        df = get_features(mt_data, defval_data, args.mt, args.full)

I'd write it like this:

mt = args.mt or get_mt_sequence(mt_data)
df = get_feature(..., mt, args.full)

> +
> +    if args.format == 'csv':
> +        if args.file == None:
> +            print('Error: csv format requires path to output file')
> +            return
> +        df.to_csv(args.file)
> +    elif args.format == 'html':
> +        if args.file == None:
> +            print('Error: html format requires path to output file')
> +            return
> +        with open(args.file, 'w') as output_html:
> +            output_html.write(df.to_html(justify='center', col_space='400px',
> +                                         index=False))
> +    elif args.format == 'json':
> +        json_table = df.to_json()
> +        if args.file == None:
> +            print(json_table)
> +        else:
> +            with open(args.file, 'w') as output_json:
> +                output_json.write(json_table)
> +    elif args.format == 'table':
> +        table = tabulate(df, showindex=False, stralign='center',
> +                         tablefmt='fancy_grid', headers='keys')
> +        if args.file == None:
> +            print(table)
> +        else:
> +            with open(args.file, 'w') as output_table:
> +                output_table.write(table)

For me this looks like extra logic.

Why to restrict printing csv/html directly to stdout? It's native to use output redirection to save output to some file. I think you can simply drop --file argument always print to stdout.

Or, if you still want this option, it's better to prepare the whole output in string variable, and then handle it once:

if ...
elif ...
elif ...

...

if args.file:
   with open(...) as f:
     f.write(output)
else:
   print(output)

> +
> +
> +if __name__ == '__main__':
> +    main()


-- 
Best regards,
Vladimir


  reply	other threads:[~2022-03-30 15:57 UTC|newest]

Thread overview: 32+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-03-28 21:15 [PATCH v1 0/9] Machine type compatible properties Maxim Davydov
2022-03-28 21:15 ` [PATCH v1 1/9] qmp: Add dump machine " Maxim Davydov
2022-03-30 11:03   ` Vladimir Sementsov-Ogievskiy
2022-04-04  9:08     ` Maxim Davydov
2022-03-28 21:15 ` [PATCH v1 2/9] pci: add null-pointer check Maxim Davydov
2022-03-30 11:07   ` Vladimir Sementsov-Ogievskiy
2022-04-04 11:07     ` Maxim Davydov
2022-03-31 11:46   ` Igor Mammedov
2022-03-28 21:15 ` [PATCH v1 3/9] mem: appropriate handling getting mem region Maxim Davydov
2022-03-30 11:27   ` Vladimir Sementsov-Ogievskiy
2022-04-04 11:57     ` Maxim Davydov
2022-03-31 11:43   ` Igor Mammedov
2022-03-28 21:15 ` [PATCH v1 4/9] msmouse: add appropriate unregister handler Maxim Davydov
2022-03-29  8:13   ` Marc-André Lureau
2022-03-28 21:15 ` [PATCH v1 5/9] wctablet: " Maxim Davydov
2022-03-29  8:13   ` Marc-André Lureau
2022-03-28 21:15 ` [PATCH v1 6/9] chardev: add appropriate getting address Maxim Davydov
2022-03-30 11:32   ` Vladimir Sementsov-Ogievskiy
2022-04-04 12:38     ` Maxim Davydov
2022-03-28 21:15 ` [PATCH v1 7/9] colo-compare: safe finalization Maxim Davydov
2022-03-30 14:54   ` Vladimir Sementsov-Ogievskiy
2022-04-04 15:20     ` Maxim Davydov
2022-03-28 21:15 ` [PATCH v1 8/9] qom: add command to print initial properties Maxim Davydov
2022-03-30 15:17   ` Vladimir Sementsov-Ogievskiy
2022-04-04 15:33     ` Maxim Davydov
2022-03-31 11:55   ` Igor Mammedov
2022-04-04 16:08     ` Maxim Davydov
2022-03-28 21:15 ` [PATCH v1 9/9] scripts: printing machine type compat properties Maxim Davydov
2022-03-30 15:55   ` Vladimir Sementsov-Ogievskiy [this message]
2022-03-31 15:38     ` John Snow
2022-03-31 11:51 ` [PATCH v1 0/9] Machine type compatible properties Igor Mammedov
2022-04-21  8:44 ` Vladimir Sementsov-Ogievskiy

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=00246e6f-85a4-487f-7025-fe616a65ebe6@mail.ru \
    --to=v.sementsov-og@mail.ru \
    --cc=ani@anisinha.ca \
    --cc=armbru@redhat.com \
    --cc=berrange@redhat.com \
    --cc=chen.zhang@intel.com \
    --cc=crosa@redhat.com \
    --cc=den@openvz.org \
    --cc=eblake@redhat.com \
    --cc=eduardo@habkost.net \
    --cc=f4bug@amsat.org \
    --cc=imammedo@redhat.com \
    --cc=jsnow@redhat.com \
    --cc=lizhijian@fujitsu.com \
    --cc=marcandre.lureau@redhat.com \
    --cc=marcel.apfelbaum@gmail.com \
    --cc=maxim.davydov@openvz.org \
    --cc=mst@redhat.com \
    --cc=pbonzini@redhat.com \
    --cc=qemu-devel@nongnu.org \
    --cc=wangyanan55@huawei.com \
    --cc=xiaoguangrong.eric@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.