diff mbox

[v2,2/2] full introspection support for QMP

Message ID 1373971062-28909-3-git-send-email-akong@redhat.com
State New
Headers show

Commit Message

Amos Kong July 16, 2013, 10:37 a.m. UTC
Introduces new monitor command to query QMP schema information,
the return data is a dynamical and nested dict/list, it contains
the useful metadata to help management to check feature support,
QMP commands detail, etc.

I added a document for QMP introspection support.
(docs/qmp-full-introspection.txt)

We need to parse all commands json definition, and generated a
dynamical tree, QMP infrastructure will convert the tree to
json string and return to QMP client.

So here I defined a 'DataObject' type in qapi-schema.json,
it's used to describe the dynamical dictionary/list/string.

{ 'type': 'DataObject',
  'data': { '*key': 'str', '*type': 'str', '*data': ['DataObject'] } }

Not all the keys in data will be used.
 # List: type
 # Dict: key, type
 # nested List: type, data
 # nested Dict: key, type, data

The DataObject is described in docs/qmp-full-introspection.txt in
detail.

The following content gives an example of query-tpm-types:

 ## Define example in qapi-schema.json:

 { 'enum': 'TpmType', 'data': [ 'passthrough' ] }
 { 'command': 'query-tpm-types', 'returns': ['TpmType'] }

 ## Returned description:

 {
     "name": "query-tpm-types",
     "type": "Command",
     "returns": [
         {
             "type": "TpmType",
             "data": [
                 {
                     "type": "passthrough"
                 }
             ]
         }
     ]
 },

'TpmType' is a defined type, it will be extended in returned
description. [ 'passthrough' ] is a list, so 'type' and 'data'
will be used.

TODO:
We can add events definations to qapi-schema.json by another
patch, then event can also be queried.

Introduce another command 'query-qga-schema' to query QGA schema
information, it's easy to add this support with current current
patch.

Signed-off-by: Amos Kong <akong@redhat.com>
---
 docs/qmp-full-introspection.txt | 143 +++++++++++++++++++
 qapi-schema.json                |  69 +++++++++
 qmp-commands.hx                 |  39 +++++
 qmp.c                           | 307 ++++++++++++++++++++++++++++++++++++++++
 4 files changed, 558 insertions(+)
 create mode 100644 docs/qmp-full-introspection.txt

Comments

Paolo Bonzini July 16, 2013, 10:48 a.m. UTC | #1
Il 16/07/2013 12:37, Amos Kong ha scritto:
> So here I defined a 'DataObject' type in qapi-schema.json,
> it's used to describe the dynamical dictionary/list/string.
> 
> { 'type': 'DataObject',
>   'data': { '*key': 'str', '*type': 'str', '*data': ['DataObject'] } }

This is missing '*optional': 'bool'.  Also, how do you distinguish these:

  { 'command': 'query-tpm-types', 'returns': 'TpmType] }
  { 'command': 'query-tpm-types', 'returns': ['TpmType'] }

Could it have to be like this?

   'data': { '*key': 'str', '*type': 'str', '*list': 'bool',
             '*optional': 'bool',
             '*data': ['DataObject'] } }

Can you document, in the commit message or the code, how you avoid
infinite loops (possible with optional or list fields)?

Paolo

> Not all the keys in data will be used.
>  # List: type
>  # Dict: key, type
>  # nested List: type, data
>  # nested Dict: key, type, data
> 
> The DataObject is described in docs/qmp-full-introspection.txt in
> detail.
> 
> The following content gives an example of query-tpm-types:
> 
>  ## Define example in qapi-schema.json:
> 
>  { 'enum': 'TpmType', 'data': [ 'passthrough' ] }
>  { 'command': 'query-tpm-types', 'returns': ['TpmType'] }
> 
>  ## Returned description:
> 
>  {
>      "name": "query-tpm-types",
>      "type": "Command",
>      "returns": [
>          {
>              "type": "TpmType",
>              "data": [
>                  {
>                      "type": "passthrough"
>                  }
>              ]
>          }
>      ]
>  },
Amos Kong July 16, 2013, 11:04 a.m. UTC | #2
On Tue, Jul 16, 2013 at 12:48:36PM +0200, Paolo Bonzini wrote:
> Il 16/07/2013 12:37, Amos Kong ha scritto:
> > So here I defined a 'DataObject' type in qapi-schema.json,
> > it's used to describe the dynamical dictionary/list/string.
> > 
> > { 'type': 'DataObject',
> >   'data': { '*key': 'str', '*type': 'str', '*data': ['DataObject'] } }
 
Hi Paolo,

> This is missing '*optional': 'bool'.  Also, how do you distinguish these:
> 
>   { 'command': 'query-tpm-types', 'returns': 'TpmType] }

do you mean 'TpmType' ? not 'TpmType]

        {
            "name": "query-tpm-types",
            "type": "Command",
            "returns": [
         >      {
         >          "type": "passthrough"
         >      }
            ]
        },

>   { 'command': 'query-tpm-types', 'returns': ['TpmType'] }

        {
            "name": "query-tpm-types",
            "type": "Command",
            "returns": [
         >      {
         >          "type": "TpmType",
         >          "data": [
         >              {
         >                  "type": "passthrough"
         >              }
         >          ]
         >      }
            ]
        },

> 
> Could it have to be like this?
> 
>    'data': { '*key': 'str', '*type': 'str', '*list': 'bool',
>              '*optional': 'bool',
>              '*data': ['DataObject'] } }

there are three conditions:
1) list
2) dict
3) string
 
> Can you document, in the commit message or the code,


    I added a document for QMP introspection support.
    (docs/qmp-full-introspection.txt)

    The DataObject is described in docs/qmp-full-introspection.txt in
    detail.

> how you avoid infinite loops (possible with optional or list fields)?


+We have four types (ImageInfo, BlockStats, PciDeviceInfo, SchemaData)
+that uses themself in their own define data directly or indirectly,
+we will not repeatedly extend them to avoid dead loop.

+/*
+ * Use a string to record the visit path, type index of each node
+ * will be saved to the string, indexes are split by ':'.
+ */

related functions:
 pop_id()
 push_id()

The detail needs to be added to qmp-full-introspection.txt
 
> Paolo
> 
> > Not all the keys in data will be used.
> >  # List: type
> >  # Dict: key, type
> >  # nested List: type, data
> >  # nested Dict: key, type, data
> > 
> > The DataObject is described in docs/qmp-full-introspection.txt in
> > detail.
> > 
> > The following content gives an example of query-tpm-types:
> > 
> >  ## Define example in qapi-schema.json:
> > 
> >  { 'enum': 'TpmType', 'data': [ 'passthrough' ] }
> >  { 'command': 'query-tpm-types', 'returns': ['TpmType'] }
> > 
> >  ## Returned description:
> > 
> >  {
> >      "name": "query-tpm-types",
> >      "type": "Command",
> >      "returns": [
> >          {
> >              "type": "TpmType",
> >              "data": [
> >                  {
> >                      "type": "passthrough"
> >                  }
> >              ]
> >          }
> >      ]
> >  },
Paolo Bonzini July 16, 2013, 11:08 a.m. UTC | #3
Il 16/07/2013 13:04, Amos Kong ha scritto:
>>> > > So here I defined a 'DataObject' type in qapi-schema.json,
>>> > > it's used to describe the dynamical dictionary/list/string.
>>> > > 
>>> > > { 'type': 'DataObject',
>>> > >   'data': { '*key': 'str', '*type': 'str', '*data': ['DataObject'] } }
>  
> Hi Paolo,
> 
>> > This is missing '*optional': 'bool'.  Also, how do you distinguish these:
>> > 
>> >   { 'command': 'query-tpm-types', 'returns': 'TpmType] }
> do you mean 'TpmType' ? not 'TpmType]

Yes.

>         {
>             "name": "query-tpm-types",
>             "type": "Command",
>             "returns": [
>          >      {
>          >          "type": "passthrough"
>          >      }
>             ]
>         },
> 
>> >   { 'command': 'query-tpm-types', 'returns': ['TpmType'] }
>         {
>             "name": "query-tpm-types",
>             "type": "Command",
>             "returns": [
>          >      {
>          >          "type": "TpmType",
>          >          "data": [
>          >              {
>          >                  "type": "passthrough"
>          >              }
>          >          ]
>          >      }
>             ]
>         },

Thanks.  I see this is unique, but it is also not too intuitive.

So, could you add a "kind" field to DataObject that is an enum
(list/dict/scalar, or something like that)?  This would make it easier
to parse (for humans at least, but I guess also for programs).

Paolo

>> > 
>> > Could it have to be like this?
>> > 
>> >    'data': { '*key': 'str', '*type': 'str', '*list': 'bool',
>> >              '*optional': 'bool',
>> >              '*data': ['DataObject'] } }
> there are three conditions:
> 1) list
> 2) dict
> 3) string
>  
>> > Can you document, in the commit message or the code,
> 
>     I added a document for QMP introspection support.
>     (docs/qmp-full-introspection.txt)
> 
>     The DataObject is described in docs/qmp-full-introspection.txt in
>     detail.
> 
>> > how you avoid infinite loops (possible with optional or list fields)?
> 
> +We have four types (ImageInfo, BlockStats, PciDeviceInfo, SchemaData)
> +that uses themself in their own define data directly or indirectly,
> +we will not repeatedly extend them to avoid dead loop.
> 
> +/*
> + * Use a string to record the visit path, type index of each node
> + * will be saved to the string, indexes are split by ':'.
> + */
> 
> related functions:
>  pop_id()
>  push_id()
> 
> The detail needs to be added to qmp-full-introspection.txt
>
Amos Kong July 16, 2013, 12:04 p.m. UTC | #4
On Tue, Jul 16, 2013 at 01:08:55PM +0200, Paolo Bonzini wrote:
> Il 16/07/2013 13:04, Amos Kong ha scritto:
> >>> > > So here I defined a 'DataObject' type in qapi-schema.json,
> >>> > > it's used to describe the dynamical dictionary/list/string.
> >>> > > 
> >>> > > { 'type': 'DataObject',
> >>> > >   'data': { '*key': 'str', '*type': 'str', '*data': ['DataObject'] } }
> >  
> > Hi Paolo,
> > 
> >> > This is missing '*optional': 'bool'.  Also, how do you distinguish these:
> >> > 
> >> >   { 'command': 'query-tpm-types', 'returns': 'TpmType] }
> > do you mean 'TpmType' ? not 'TpmType]
> 
> Yes.
> 
> >         {
> >             "name": "query-tpm-types",
> >             "type": "Command",
> >             "returns": [
> >          >      {
> >          >          "type": "passthrough"
> >          >      }
> >             ]
> >         },
> > 
> >> >   { 'command': 'query-tpm-types', 'returns': ['TpmType'] }
> >         {
> >             "name": "query-tpm-types",
> >             "type": "Command",
> >             "returns": [
> >          >      {
> >          >          "type": "TpmType",
> >          >          "data": [
> >          >              {
> >          >                  "type": "passthrough"
> >          >              }
> >          >          ]
> >          >      }
> >             ]
> >         },
> 
> Thanks.  I see this is unique, but it is also not too intuitive.
> 
> So, could you add a "kind" field to DataObject that is an enum
> (list/dict/scalar, or something like that)?  This would make it easier
> to parse (for humans at least, but I guess also for programs).

I thought we can identify the kind by some judgment.
 if the dict has key 'key', it's a dict
 if no 'key', have 'type', it's a list
 if only have 'type', it's a buildin type (or extended type that
   doesn't need to be extended)
 if no 'key', have 'type' & 'data', it's extended list type
 if have 'key', 'type', 'data', it's extended dict type

I will added a 'kind' field to make it clearer.

KIND enum:
  list
  dict
  str

scalar(bool):   Or just simplely check if have 'data' key?
  true/false

Amos

> Paolo
Paolo Bonzini July 16, 2013, 12:18 p.m. UTC | #5
Il 16/07/2013 14:04, Amos Kong ha scritto:
>> > Thanks.  I see this is unique, but it is also not too intuitive.
>> > 
>> > So, could you add a "kind" field to DataObject that is an enum
>> > (list/dict/scalar, or something like that)?  This would make it easier
>> > to parse (for humans at least, but I guess also for programs).
> I thought we can identify the kind by some judgment.

Yes, I understood that.  Strictly speaking the kind is redundant, but it
seems to me that it makes the API easier to understand and use.

>  if the dict has key 'key', it's a dict
>  if no 'key', have 'type', it's a list
>  if only have 'type', it's a buildin type (or extended type that
>    doesn't need to be extended)
>  if no 'key', have 'type' & 'data', it's extended list type
>  if have 'key', 'type', 'data', it's extended dict type
> 
> I will added a 'kind' field to make it clearer.
> 
> KIND enum:
>   list
>   dict
>   str

Why "str" and not "scalar" for a builtin type?  It's not necessarily a
string, is it?

Paolo

> scalar(bool):   Or just simplely check if have 'data' key?
>   true/false
Luiz Capitulino July 17, 2013, 8:36 p.m. UTC | #6
On Tue, 16 Jul 2013 18:37:42 +0800
Amos Kong <akong@redhat.com> wrote:

> Introduces new monitor command to query QMP schema information,
> the return data is a dynamical and nested dict/list, it contains
> the useful metadata to help management to check feature support,
> QMP commands detail, etc.
> 
> I added a document for QMP introspection support.
> (docs/qmp-full-introspection.txt)
> 
> We need to parse all commands json definition, and generated a
> dynamical tree, QMP infrastructure will convert the tree to
> json string and return to QMP client.
> 
> So here I defined a 'DataObject' type in qapi-schema.json,
> it's used to describe the dynamical dictionary/list/string.

I skimmed over the patch and made some comments, but my impression
is that this is getting too complex... Why did we move from letting
mngt query type by type (last version) to this version which returns
all commands and their input types? Does this satisfy libvirt needs?

> 
> { 'type': 'DataObject',
>   'data': { '*key': 'str', '*type': 'str', '*data': ['DataObject'] } }
> 
> Not all the keys in data will be used.
>  # List: type
>  # Dict: key, type
>  # nested List: type, data
>  # nested Dict: key, type, data
> 
> The DataObject is described in docs/qmp-full-introspection.txt in
> detail.
> 
> The following content gives an example of query-tpm-types:
> 
>  ## Define example in qapi-schema.json:
> 
>  { 'enum': 'TpmType', 'data': [ 'passthrough' ] }
>  { 'command': 'query-tpm-types', 'returns': ['TpmType'] }
> 
>  ## Returned description:
> 
>  {
>      "name": "query-tpm-types",
>      "type": "Command",
>      "returns": [
>          {
>              "type": "TpmType",
>              "data": [
>                  {
>                      "type": "passthrough"
>                  }
>              ]
>          }
>      ]
>  },
> 
> 'TpmType' is a defined type, it will be extended in returned
> description. [ 'passthrough' ] is a list, so 'type' and 'data'
> will be used.
> 
> TODO:
> We can add events definations to qapi-schema.json by another
> patch, then event can also be queried.
> 
> Introduce another command 'query-qga-schema' to query QGA schema
> information, it's easy to add this support with current current
> patch.
> 
> Signed-off-by: Amos Kong <akong@redhat.com>
> ---
>  docs/qmp-full-introspection.txt | 143 +++++++++++++++++++
>  qapi-schema.json                |  69 +++++++++
>  qmp-commands.hx                 |  39 +++++
>  qmp.c                           | 307 ++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 558 insertions(+)
>  create mode 100644 docs/qmp-full-introspection.txt
> 
> diff --git a/docs/qmp-full-introspection.txt b/docs/qmp-full-introspection.txt
> new file mode 100644
> index 0000000..cc0fb80
> --- /dev/null
> +++ b/docs/qmp-full-introspection.txt
> @@ -0,0 +1,143 @@
> += full introspection support for QMP =
> +
> +== Purpose ==
> +
> +Add a new interface to provide QMP schema information to management,
> +the return data is a dynamical and nested dict/list, it contains
> +the useful metadata to help management to check feature support,
> +QMP commands detail, etc.
> +
> +== Usage ==
> +
> +Execute QMP command:
> +
> +  { "execute": "query-qmp-schema" }
> +
> +Returns:
> +
> +  { "return": [
> +      {
> +          "name": "query-name",
> +          "type": "Command",
> +          "returns": [
> +              {
> +                  "key": "*name",
> +                  "type": "str"
> +              }
> +          ]
> +      },
> +      ...
> +   }
> +
> +The whole schema information will be returned in one go, it contains
> +commands and event. It doesn't support to be filtered by type or name.
> +
> +We have four types (ImageInfo, BlockStats, PciDeviceInfo, SchemaData)
> +that uses themself in their own define data directly or indirectly,
> +we will not repeatedly extend them to avoid dead loop.
> +
> +== more description of 'DataObject' type ==
> +
> +We use 'DataObject' to describe dynamical data struct, it might be
> +nested dictionary, list or string.
> +
> +'DataObject' itself is a arbitrary and nested dictionary, the
> +dictionary has three keys ('key', 'type', 'data'), 'key' and
> +'data' are optional.
> +
> +* For describing Dictionary, we set the key to 'key', and set the
> +  value to 'type'
> +* For describing List, we don't set 'key', just set the value to
> +  'type'
> +* If the value of dictionary or list is non-native type, we extend
> +  the non-native type to dictionary, set it to 'data',  and set the
> +  non-native type's name to 'type'.
> +* If the value of dictionary or list is dictionary or list, 'type'
> +  won't be set.
> +
> +== examples ==
> +
> +1) Dict, value is native type
> +{ 'id': 'str', ... }
> +--------------------
> +[
> +    {
> +        "key": "id",
> +        "type": "str"
> +    },
> +    .....
> +]
> +
> +2) Dict, value is defined types
> +{ 'options': 'TpmTypeOptions' }
> +-------------------------------
> +[
> +    {
> +        "key": "options",
> +        "type": "TpmTypeOptions",
> +        "data": [
> +            {
> +                "key": "passthrough",
> +                "type": "str",
> +            }
> +        ]
> +    },
> +    .....
> +]
> +
> +3) List, value is native type
> +['str', ... ]
> +-------------
> +[
> +    {
> +        "type": "str"
> +    },
> +    ....
> +]
> +
> +4) List, value is defined types
> +['TpmTypeOptions', ... ]
> +------------------------
> +[
> +    {
> +        "type": "TpmTypeOptions",
> +        "data": [
> +            {
> +                "key": "passthrough",
> +                "type": "str",
> +            }
> +        ]
> +    },
> +    .....
> +]
> +
> +5) Dict, value is dictionary
> +{ 'info': { 'age': 'init', ... } }
> +-----------------------------
> +[
> +    {
> +        "key": "info",
> +        "data": [
> +            {
> +                "key": "age",
> +                "type": "init",
> +            },
> +            ...
> +        ]
> +    },
> +]
> +
> +6) Dict, value is list
> +{ 'info': [ 'str', ... } }
> +-----------------------------
> +[
> +    {
> +        "key": "info",
> +        "data": [
> +            {
> +                "type": "str",
> +            },
> +            ...
> +        ]
> +    },
> +]
> diff --git a/qapi-schema.json b/qapi-schema.json
> index 7b9fef1..cf03391 100644
> --- a/qapi-schema.json
> +++ b/qapi-schema.json
> @@ -3679,3 +3679,72 @@
>              '*cpuid-input-ecx': 'int',
>              'cpuid-register': 'X86CPURegister32',
>              'features': 'int' } }
> +
> +##
> +# @DataObject
> +#
> +# Details of a data object, it can be nested dictionary/list
> +#
> +# @key: #optional Data object key
> +#
> +# @type: Data object type name
> +#
> +# @optional: #optional whether the data object is optional
> +#
> +# @data: #optional DataObject list, can be a dictionary or list type
> +#
> +# Since: 1.6
> +##
> +{ 'type': 'DataObject',
> +  'data': { '*key': 'str', '*type': 'str', '*optional': 'bool', '*data': ['DataObject'] } }
> +
> +##
> +# @SchemaMetaType
> +#
> +# Possible meta types of a schema entry
> +#
> +# @Command: QMP command
> +#
> +# @Type: defined new data type
> +#
> +# @Enumeration: enumeration data type
> +#
> +# @Union: union data type
> +#
> +# Since: 1.6
> +##
> +{ 'enum': 'SchemaMetaType',
> +  'data': ['Command', 'Type', 'Enumeration', 'Union'] }
> +
> +##
> +# @SchemaEntry
> +#
> +# Details of schema items
> +#
> +# @type: Entry's type in string format

It's not a string, it's a SchemaMetaType.

> +#
> +# @name: Entry name
> +#
> +# @data: #optional list of DataObject. This can have different meaning
> +#        depending on the 'type' value. For example, for a QMP command,
> +#        this member contains an argument listing. For an enumeration,
> +#        it contains the enum's values and so on
> +#
> +# @returns: #optional list of DataObject, return data after executing
> +#           QMP command
> +#
> +# Since: 1.6
> +##
> +{ 'type': 'SchemaEntry', 'data': { 'type': 'SchemaMetaType',
> +  'name': 'str', '*data': ['DataObject'], '*returns': ['DataObject'] } }
> +
> +##
> +# @query-qmp-schema
> +#
> +# Query QMP schema information
> +#
> +# Returns: list of @SchemaEntry. Returns an error if json string is invalid.
> +#
> +# Since: 1.6
> +##
> +{ 'command': 'query-qmp-schema', 'returns': ['SchemaEntry'] }
> diff --git a/qmp-commands.hx b/qmp-commands.hx
> index e075df4..e3cbe93 100644
> --- a/qmp-commands.hx
> +++ b/qmp-commands.hx
> @@ -3047,3 +3047,42 @@ Example:
>  <- { "return": {} }
>  
>  EQMP
> +
> +    {
> +        .name       = "query-qmp-schema",
> +        .args_type  = "",
> +        .mhandler.cmd_new = qmp_marshal_input_query_qmp_schema,
> +    },
> +
> +
> +SQMP
> +query-qmp-schema
> +----------------
> +
> +query qmp schema information
> +
> +Return a json-object with the following information:
> +
> +- "name": qmp schema name (json-string)
> +- "type": qmp schema type, it can be 'comand', 'type', 'enum', 'union', 'event'
> +- "data": schema data (json-object, optional)
> +- "returns": return data of qmp command (json-object, optional)
> +
> +Example:
> +
> +-> { "execute": "query-qmp-schema" }
> +<- { "return": [
> +       {
> +           "name": "query-name",
> +           "type": "Command",
> +           "returns": [
> +               {
> +                    "key": "*name",
> +                   "type": "str"
> +               }
> +           ]
> +       }
> +     ]
> +   }
> +
> +EQMP
> diff --git a/qmp.c b/qmp.c
> index 4c149b3..3ace3a6 100644
> --- a/qmp.c
> +++ b/qmp.c
> @@ -25,6 +25,8 @@
>  #include "sysemu/blockdev.h"
>  #include "qom/qom-qobject.h"
>  #include "hw/boards.h"
> +#include "qmp-schema.h"
> +#include "qapi/qmp/qjson.h"
>  
>  NameInfo *qmp_query_name(Error **errp)
>  {
> @@ -486,6 +488,311 @@ CpuDefinitionInfoList *qmp_query_cpu_definitions(Error **errp)
>      return arch_query_cpu_definitions(errp);
>  }
>  
> +/*
> + * Use a string to record the visit path, type index of each node
> + * will be saved to the string, indexes are split by ':'.
> + */
> +static char visit_path_str[1024];

visit path? I don't get this.

> +
> +/* push the type index to visit_path_str */
> +static void push_id(int id)
> +{
> +    char *end = strrchr(visit_path_str, ':');
> +    char type_idx[256];
> +    int num;
> +
> +    num = sprintf(type_idx, "%d:", id);
> +
> +    if (end) {
> +        /* avoid overflow */
> +        assert(end - visit_path_str + 1 + num < sizeof(visit_path_str));
> +        sprintf(end + 1, "%d:", id);
> +    } else {
> +        sprintf(visit_path_str, "%d:", id);
> +    }
> +}
> +
> +/* pop the type index from visit_path_str */
> +static void pop_id(void)
> +{
> +    char *p = strrchr(visit_path_str, ':');
> +
> +    assert(p != NULL);
> +    *p = '\0';
> +    p = strrchr(visit_path_str, ':');
> +    if (p) {
> +        *(p + 1) = '\0';
> +    } else {
> +        visit_path_str[0] = '\0';
> +    }
> +}
> +
> +static const char *qstring_copy_str(QObject *data)
> +{
> +    QString *qstr;
> +
> +    if (!data) {
> +        return NULL;
> +    }
> +    qstr = qobject_to_qstring(data);
> +    if (qstr) {
> +        return qstring_get_str(qstr);
> +    } else {
> +        return NULL;
> +    }
> +}
> +
> +static DataObjectList *visit_qobj_dict(QObject *data);
> +static DataObjectList *visit_qobj_list(QObject *data);
> +
> +/* extend defined type to json object */
> +static DataObjectList *extend_type(const char* str)
> +{
> +    DataObjectList *data_list;
> +    QObject *data;
> +    QDict *qdict;
> +    const QDictEntry *ent;
> +    int i;
> +
> +    /* don't extend builtin types */
> +    if (!strcmp(str, "str") || !strcmp(str, "int") ||
> +        !strcmp(str, "number") || !strcmp(str, "bool") ||
> +        !strcmp(str, "int8") || !strcmp(str, "int16") ||
> +        !strcmp(str, "int32") || !strcmp(str, "int64") ||
> +        !strcmp(str, "uint8") || !strcmp(str, "uint16") ||
> +        !strcmp(str, "uint32") || !strcmp(str, "uint64")) {
> +        return NULL;
> +    }
> +
> +    for (i = 0; qmp_schema_table[i]; i++) {
> +        data = qobject_from_json(qmp_schema_table[i]);
> +        assert(data != NULL);
> +
> +        qdict = qobject_to_qdict(data);
> +        assert(qdict != NULL);
> +
> +        ent = qdict_first(qdict);
> +        if (!qdict_get(qdict, "enum") && !qdict_get(qdict, "type")
> +            && !qdict_get(qdict, "union")) {
> +            continue;
> +        }
> +
> +        if (!strcmp(str, qstring_copy_str(ent->value))) {
> +            char *start, *end;
> +            char cur_idx[256];
> +            char type_idx[256];
> +
> +            start = visit_path_str;
> +            sprintf(type_idx, "%d", i);
> +            while(start) {
> +                end = strchr(start, ':');
> +                if (!end) {
> +                    break;
> +                }
> +                snprintf(cur_idx, end - start + 1, "%s", start);
> +                start = end + 1;
> +                /* if the type was already extended in parent node,
> +                 * we don't extend it again to avoid dead loop. */
> +                if (!strcmp(cur_idx, type_idx)) {
> +                    return NULL;
> +                }
> +            }
> +            /* push index to visit_path_str before extending */
> +            push_id(i);
> +
> +            data = qdict_get(qdict, "data");
> +            if(data) {
> +                if (data->type->code == QTYPE_QDICT) {
> +                    data_list = visit_qobj_dict(data);
> +                } else if (data->type->code == QTYPE_QLIST) {
> +                    data_list = visit_qobj_list(data);
> +                }
> +                /* pop index from visit_path_str after extending */
> +                pop_id();
> +
> +                return data_list;
> +            }
> +        }
> +    }
> +
> +    return NULL;
> +}
> +
> +static DataObjectList *visit_qobj_list(QObject *data)
> +{
> +    DataObjectList *obj_list, *obj_last_entry, *obj_entry;
> +    DataObject *obj_info;
> +    const QListEntry *ent;
> +    QList *qlist;
> +
> +    qlist = qobject_to_qlist(data);
> +    assert(qlist != NULL);
> +
> +    obj_list = NULL;
> +    for (ent = qlist_first(qlist); ent; ent = qlist_next(ent)) {
> +        obj_info = g_malloc0(sizeof(*obj_info));
> +        obj_entry = g_malloc0(sizeof(*obj_entry));
> +        obj_entry->value = obj_info;
> +        obj_entry->next = NULL;
> +
> +        if (!obj_list) {
> +            obj_list = obj_entry;
> +        } else {
> +            obj_last_entry->next = obj_entry;
> +        }
> +        obj_last_entry = obj_entry;
> +
> +        if (ent->value->type->code == QTYPE_QDICT) {
> +            obj_info->data = visit_qobj_dict(ent->value);
> +        } else if (ent->value->type->code == QTYPE_QLIST) {
> +            obj_info->data = visit_qobj_list(ent->value);
> +        } else if (ent->value->type->code == QTYPE_QSTRING) {
> +            obj_info->has_type = true;
> +            obj_info->type = g_strdup(qstring_copy_str(ent->value));
> +            obj_info->data = extend_type(qstring_copy_str(ent->value));
> +        }
> +        if (obj_info->data) {
> +            obj_info->has_data = true;
> +        }
> +    }
> +
> +    return obj_list;
> +}
> +
> +static DataObjectList *visit_qobj_dict(QObject *data)
> +{
> +    DataObjectList *obj_list, *obj_last_entry, *obj_entry;
> +    DataObject *obj_info;
> +    const QDictEntry *ent;
> +    QDict *qdict;
> +
> +    qdict = qobject_to_qdict(data);
> +    assert(qdict != NULL);
> +
> +    obj_list = NULL;
> +    for (ent = qdict_first(qdict); ent; ent = qdict_next(qdict, ent)) {
> +        obj_info = g_malloc0(sizeof(*obj_info));
> +        obj_entry = g_malloc0(sizeof(*obj_entry));
> +        obj_entry->value = obj_info;
> +        obj_entry->next = NULL;
> +
> +        if (!obj_list) {
> +            obj_list = obj_entry;
> +        } else {
> +            obj_last_entry->next = obj_entry;
> +        }
> +        obj_last_entry = obj_entry;
> +
> +        if (ent->key[0] == '*') {
> +            obj_info->key = g_strdup(ent->key + 1);
> +            obj_info->has_optional = true;
> +            obj_info->optional = true;
> +        } else {
> +            obj_info->key = g_strdup(ent->key);
> +        }
> +        obj_info->has_key = true;
> +
> +        if (ent->value->type->code == QTYPE_QDICT) {
> +            obj_info->data = visit_qobj_dict(ent->value);
> +        } else if (ent->value->type->code == QTYPE_QLIST) {
> +           obj_info->data = visit_qobj_list(ent->value);
> +        } else if (ent->value->type->code == QTYPE_QSTRING) {
> +            obj_info->has_type = true;
> +            obj_info->type = g_strdup(qstring_copy_str(ent->value));
> +            obj_info->data = extend_type(qstring_copy_str(ent->value));
> +        }
> +        if (obj_info->data) {
> +            obj_info->has_data = true;
> +        }
> +    }
> +
> +    return obj_list;
> +}
> +
> +SchemaEntryList *qmp_query_qmp_schema(Error **errp)
> +{
> +    SchemaEntryList *list, *last_entry, *entry;
> +    SchemaEntry *info;
> +    DataObjectList *obj_entry;
> +    DataObject *obj_info;
> +    QObject *data;
> +    QDict *qdict;
> +    int i;
> +
> +    list = NULL;
> +    for (i = 0; qmp_schema_table[i]; i++) {
> +        data = qobject_from_json(qmp_schema_table[i]);
> +        assert(data != NULL);
> +
> +        qdict = qobject_to_qdict(data);
> +        assert(qdict != NULL);
> +
> +        if (qdict_get(qdict, "command")) {
> +            info = g_malloc0(sizeof(*info));
> +            info->type = SCHEMA_META_TYPE_COMMAND;
> +            info->name = strdup(qdict_get_str(qdict, "command"));

s/strdup/g_strdup

> +        } else {
> +            continue;
> +        }

You return only commands. That is, types are returned as part of the
command input. ErrorClass can't be queried then? I'm not judging, only
observing.

Eric, this is good enough for libvirt?

Btw, you're leaking data on the else clause.

> +
> +        memset(visit_path_str, 0, sizeof(visit_path_str));
> +        data = qdict_get(qdict, "data");
> +        if (data) {
> +            info->has_data = true;
> +            if (data->type->code == QTYPE_QLIST) {
> +                info->data = visit_qobj_list(data);
> +            } else if (data->type->code == QTYPE_QDICT) {
> +                info->data = visit_qobj_dict(data);
> +            } else if (data->type->code == QTYPE_QSTRING) {
> +                info->data = extend_type(qstring_get_str(qobject_to_qstring(data)));
> +                if (!info->data) {
> +                    obj_info = g_malloc0(sizeof(*obj_info));
> +                    obj_entry = g_malloc0(sizeof(*obj_entry));
> +                    obj_entry->value = obj_info;
> +                    obj_info->has_type = true;
> +                    obj_info->type = g_strdup(qdict_get_str(qdict, "data"));
> +                    info->data = obj_entry;
> +                }
> +            } else {
> +                abort();

Pleae, explain why you're aborting here.

> +            }
> +        }
> +
> +        memset(visit_path_str, 0, sizeof(visit_path_str));
> +        data = qdict_get(qdict, "returns");
> +        if (data) {
> +            info->has_returns = true;
> +            if (data->type->code == QTYPE_QLIST) {
> +                info->returns = visit_qobj_list(data);
> +            } else if (data->type->code == QTYPE_QDICT) {
> +                info->returns = visit_qobj_dict(data);
> +            } else if (data->type->code == QTYPE_QSTRING) {
> +                info->returns = extend_type(qstring_copy_str(data));
> +                if (!info->returns) {
> +                    obj_info = g_malloc0(sizeof(*obj_info));
> +                    obj_entry = g_malloc0(sizeof(*obj_entry));
> +                    obj_entry->value = obj_info;
> +                    obj_info->has_type = true;
> +                    obj_info->type = g_strdup(qdict_get_str(qdict, "returns"));
> +                    info->returns = obj_entry;
> +                }
> +            }
> +        }
> +
> +        entry = g_malloc0(sizeof(DataObjectList *));
> +        entry->value = info;
> +        entry->next = NULL;
> +        if (!list) {
> +            list = entry;
> +        } else {
> +            last_entry->next = entry;
> +        }
> +        last_entry = entry;
> +    }
> +
> +    return list;
> +}
> +
>  void qmp_add_client(const char *protocol, const char *fdname,
>                      bool has_skipauth, bool skipauth, bool has_tls, bool tls,
>                      Error **errp)
Eric Blake July 19, 2013, 8:26 p.m. UTC | #7
On 07/17/2013 02:36 PM, Luiz Capitulino wrote:
>> We need to parse all commands json definition, and generated a
>> dynamical tree, QMP infrastructure will convert the tree to
>> json string and return to QMP client.
>>
>> So here I defined a 'DataObject' type in qapi-schema.json,
>> it's used to describe the dynamical dictionary/list/string.
> 
> I skimmed over the patch and made some comments, but my impression
> is that this is getting too complex... Why did we move from letting
> mngt query type by type (last version) to this version which returns
> all commands and their input types? Does this satisfy libvirt needs?

Libvirt can query once, and then browse through the (giant) reply as
many times as needed to drill down to a full definition.  The ability to
filter by passing in a name to look up is a bonus, but not mandatory.

I'm also working on a reply to the full patch.

> You return only commands. That is, types are returned as part of the
> command input. ErrorClass can't be queried then? I'm not judging, only
> observing.
> 
> Eric, this is good enough for libvirt?

It might be sufficient (after all, you can't use a type except by
passing it as part of a command [argument or return] or event [which we
still don't have converted into qapi...]).  In one respect, it means
that if we want to know if an optional field was added for command
'foo', we start by querying command foo; then regardless of what type
names were used, we will see that in the results for the command.  On
the other hand, we've been arguing that qapi type names are part of the
API, and that we shouldn't arbitrarily change type names (particularly
not once introspection is in place).  Thus, if I can query for the
contents of type 'FooDict', that shaves a step from querying the
structure of command 'foo' that uses the type 'FooDict'.

In other words, it will "work" for libvirt, but it may not be optimal.
Eric Blake July 19, 2013, 10:05 p.m. UTC | #8
On 07/16/2013 04:37 AM, Amos Kong wrote:
> Introduces new monitor command to query QMP schema information,
> the return data is a dynamical and nested dict/list, it contains

s/dynamical/dynamic/

> the useful metadata to help management to check feature support,
> QMP commands detail, etc.
> 
> I added a document for QMP introspection support.
> (docs/qmp-full-introspection.txt)

Yay - docs make a world of difference.

> 
> We need to parse all commands json definition, and generated a

mixing tense ("need" present, "generated" past); for commit messages,
present tense is generally appropriate.  Thus:

s/generated/generate/

> dynamical tree, QMP infrastructure will convert the tree to

s/dynamical/dynamic/

> json string and return to QMP client.
> 
> So here I defined a 'DataObject' type in qapi-schema.json,
> it's used to describe the dynamical dictionary/list/string.

s/dynamical/dynamic/

> 
> { 'type': 'DataObject',
>   'data': { '*key': 'str', '*type': 'str', '*data': ['DataObject'] } }
> 
> Not all the keys in data will be used.
>  # List: type
>  # Dict: key, type
>  # nested List: type, data
>  # nested Dict: key, type, data

I wonder if we can take advantage of Kevin's work on unions to make this
MUCH easier to use:

https://lists.gnu.org/archive/html/qemu-devel/2013-07/msg01407.html

{ 'enum': 'DataObjectType',
  'data': [ 'command', 'struct', 'union', 'enum' ] }
# extend to add 'event' later

{ 'type': 'DataObjectBase',
  'data': { 'name': 'str' } }

{ 'union': 'DataObjectMemberType',
  'discriminator': {},
  'data': { 'reference': 'str',
            'inline': 'DataObject' } }

{ 'type': DataObjectMember',
  'data': { 'name': 'str', 'type': 'DataObjectMemberType',
            '*optional': 'bool', '*recursive': 'bool' } }

{ 'type': 'DataObjectStruct',
  'data': { 'data': [ 'DataObjectMember' ] } }
{ 'type': 'DataObjectEnum',
  'data': { 'data': [ 'str' ] } }
{ 'type': 'DataObjectUnion',
  'data': { 'data': [ 'DataObjectMember' ], '*base': 'str',
            '*discriminator': 'str' } }
{ 'type': 'DataObjectCommand',
  'data': { 'data': [ 'DataObjectMember' ], '*returns': 'DataObject' } }

{ 'union': 'DataObject',
  'base': 'DataObjectBase',
  'discriminator': 'type',
  'data': {
    'struct': 'DataObjectStruct',
    'enum': 'DataObjectEnum',
    'union': 'DataObjectUnion',
    'command': 'DataObjectCommand',
    'array': {} }

> 
> The DataObject is described in docs/qmp-full-introspection.txt in
> detail.
> 
> The following content gives an example of query-tpm-types:
> 
>  ## Define example in qapi-schema.json:
> 
>  { 'enum': 'TpmType', 'data': [ 'passthrough' ] }
>  { 'command': 'query-tpm-types', 'returns': ['TpmType'] }

It might be more useful to have a (made-up) example that shows as many
details as possible - a command that takes arguments and has a return
type will exercise more of the code paths than a query command with only
a return.

> 
>  ## Returned description:
> 
>  {
>      "name": "query-tpm-types",
>      "type": "Command",
>      "returns": [
>          {
>              "type": "TpmType",
>              "data": [
>                  {
>                      "type": "passthrough"
>                  }
>              ]

I need a way to know the difference between a TpmType returned directly
in a dict, vs. a return containing an array of TpmType.

>          }
>      ]
>  },

Thus, using the discriminated union I set out above, I would return
something like:
{ "name": "TpmType", "type": "enum",
  "data": [ "passthrough" ] },
{ "name": "query-tpm-types", "type": "command",
  "data": [ ],
  "returns": { "name": "TpmType", "type": "array" } }

> 
> 'TpmType' is a defined type, it will be extended in returned
> description. [ 'passthrough' ] is a list, so 'type' and 'data'
> will be used.
> 
> TODO:
> We can add events definations to qapi-schema.json by another

s/definations/definitions/

> patch, then event can also be queried.
> 
> Introduce another command 'query-qga-schema' to query QGA schema
> information, it's easy to add this support with current current
> patch.

Such a command would be part of the QGA interface, not a QMP command.
But yes, it is needed, and the ideal patch series from you will include
that patch as part of the series.  But I don't mind waiting until we get
the interface nailed down before you actually implement the QGA repeat
of the interface.

> 
> Signed-off-by: Amos Kong <akong@redhat.com>
> ---
>  docs/qmp-full-introspection.txt | 143 +++++++++++++++++++
>  qapi-schema.json                |  69 +++++++++
>  qmp-commands.hx                 |  39 +++++
>  qmp.c                           | 307 ++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 558 insertions(+)
>  create mode 100644 docs/qmp-full-introspection.txt
> 
> diff --git a/docs/qmp-full-introspection.txt b/docs/qmp-full-introspection.txt
> new file mode 100644
> index 0000000..cc0fb80
> --- /dev/null
> +++ b/docs/qmp-full-introspection.txt
> @@ -0,0 +1,143 @@
> += full introspection support for QMP =
> +

Is it worth merging this into the existing qapi-code-gen.txt, or at
least having the two documents refer to one another, since they deal
with related concepts (turning the .json file into generated code)?

> +== Purpose ==
> +
> +Add a new interface to provide QMP schema information to management,

s/a new/an/ - after a year, the interface won't be new, but this doc
will still be relevant.

> +the return data is a dynamical and nested dict/list, it contains

s/dynamical/dynamic/

> +the useful metadata to help management to check feature support,
> +QMP commands detail, etc.
> +
> +== Usage ==
> +
> +Execute QMP command:
> +
> +  { "execute": "query-qmp-schema" }
> +
> +Returns:
> +
> +  { "return": [
> +      {
> +          "name": "query-name",
> +          "type": "Command",
> +          "returns": [
> +              {
> +                  "key": "*name",
> +                  "type": "str"
> +              }
> +          ]

Are we trying to use struct names where they exist for compactness, or
trying to inline-expand everything as far as possible until we run into
self-referential recursion?

> +      },
> +      ...
> +   }
> +
> +The whole schema information will be returned in one go, it contains
> +commands and event. It doesn't support to be filtered by type or name.

s/event/events/
s/It/At present, it/
s/to be filtered/filtering/

> +
> +We have four types (ImageInfo, BlockStats, PciDeviceInfo, SchemaData)

This list will get out of date quickly.  I'd just word it generically:

We have several self-referential types

> +that uses themself in their own define data directly or indirectly,

s/uses themself/use themselves/

> +we will not repeatedly extend them to avoid dead loop.

Would it be worth a flag in the QMP output when a type is not being
further expanded because it is a nested occurrence of itself?  That is,
given my proposed layout above, ImageInfo would turn into something like:

{ "name": "ImageInfo", "type": "struct",
  "data": [ { "name": "filename", "type", "str" },
            ...
            { "name": "backing-image", "type": "ImageInfo",
               "optional": true, "recursive": true } ] },

> +
> +== more description of 'DataObject' type ==
> +
> +We use 'DataObject' to describe dynamical data struct, it might be

s/dynamical/dynamic/

> +nested dictionary, list or string.
> +
> +'DataObject' itself is a arbitrary and nested dictionary, the
> +dictionary has three keys ('key', 'type', 'data'), 'key' and

spacing is odd.

> +'data' are optional.
> +
> +* For describing Dictionary, we set the key to 'key', and set the
> +  value to 'type'
> +* For describing List, we don't set 'key', just set the value to
> +  'type'

Again, if you use the idea of a discriminated union, you don't need
quite the complexity in describing this: dictionaries are listed with
key/type/optional tuples, lists (enums) are listed with just an array of
strings, and the QAPI perfectly described that difference by the
discriminator telling you 'struct' vs. 'enum'.

> +* If the value of dictionary or list is non-native type, we extend
> +  the non-native type to dictionary, set it to 'data',  and set the
> +  non-native type's name to 'type'.

Again, I tried to set up the QAPI that describes something that uses an
anonymous union that either gives a string (the name of a primitive or
already-defined type) or a struct (a recursion into another layer of
struct describing the structure of that type in line).

> diff --git a/qapi-schema.json b/qapi-schema.json
> index 7b9fef1..cf03391 100644
> --- a/qapi-schema.json
> +++ b/qapi-schema.json
> @@ -3679,3 +3679,72 @@
>              '*cpuid-input-ecx': 'int',
>              'cpuid-register': 'X86CPURegister32',
>              'features': 'int' } }
> +
> +##
> +# @DataObject
> +#
> +# Details of a data object, it can be nested dictionary/list
> +#
> +# @key: #optional Data object key
> +#
> +# @type: Data object type name
> +#
> +# @optional: #optional whether the data object is optional

mention that the default is false.

> +#
> +# @data: #optional DataObject list, can be a dictionary or list type

so if 'data' is present, we are describing @type in more detail; if it
is absent, then @type is primitive.

> +#
> +# Since: 1.6
> +##
> +{ 'type': 'DataObject',
> +  'data': { '*key': 'str', '*type': 'str', '*optional': 'bool', '*data': ['DataObject'] } }
> +

'*type' should be an enum type, not open-coded string.


> +##
> +# @SchemaMetaType
> +#
> +# Possible meta types of a schema entry
> +#
> +# @Command: QMP command
> +#
> +# @Type: defined new data type
> +#
> +# @Enumeration: enumeration data type
> +#
> +# @Union: union data type
> +#
> +# Since: 1.6
> +##
> +{ 'enum': 'SchemaMetaType',
> +  'data': ['Command', 'Type', 'Enumeration', 'Union'] }

Do we need to start these with a capital?  On the other hand, having
them as capitals may make it easier to ensure we are outputting correct
information.
> +
> +##
> +# @SchemaEntry
> +#
> +# Details of schema items
> +#
> +# @type: Entry's type in string format
> +#
> +# @name: Entry name
> +#
> +# @data: #optional list of DataObject. This can have different meaning
> +#        depending on the 'type' value. For example, for a QMP command,
> +#        this member contains an argument listing. For an enumeration,
> +#        it contains the enum's values and so on

This argues for making it a union type.

> +#
> +# @returns: #optional list of DataObject, return data after executing
> +#           QMP command
> +#
> +# Since: 1.6
> +##
> +{ 'type': 'SchemaEntry', 'data': { 'type': 'SchemaMetaType',
> +  'name': 'str', '*data': ['DataObject'], '*returns': ['DataObject'] } }
> +
> +##
> +# @query-qmp-schema
> +#
> +# Query QMP schema information
> +#
> +# Returns: list of @SchemaEntry. Returns an error if json string is invalid.

If you don't take any arguments, then the "returns an error" statement
is impossible.

> +#
> +# Since: 1.6
> +##
> +{ 'command': 'query-qmp-schema', 'returns': ['SchemaEntry'] }
> diff --git a/qmp-commands.hx b/qmp-commands.hx
> index e075df4..e3cbe93 100644
> --- a/qmp-commands.hx
>  
> +/*
> + * Use a string to record the visit path, type index of each node
> + * will be saved to the string, indexes are split by ':'.
> + */
> +static char visit_path_str[1024];

Is a fixed width buffer really the best solution, or does glib offer us
something better?  For example, a hash table, or even a growable string.

> +
> +    for (i = 0; qmp_schema_table[i]; i++) {
> +        data = qobject_from_json(qmp_schema_table[i]);
> +        assert(data != NULL);
> +
> +        qdict = qobject_to_qdict(data);
> +        assert(qdict != NULL);
> +
> +        ent = qdict_first(qdict);
> +        if (!qdict_get(qdict, "enum") && !qdict_get(qdict, "type")
> +            && !qdict_get(qdict, "union")) {
> +            continue;
> +        }

Why are we doing the work in C code, every time the command is called?
Wouldn't it be more efficient to do the work in python code, at the time
the qmp_schema_table C code is first generated, so that the generated
code is already in the right format?

> +SchemaEntryList *qmp_query_qmp_schema(Error **errp)
> +{
> +    SchemaEntryList *list, *last_entry, *entry;
> +    SchemaEntry *info;
> +    DataObjectList *obj_entry;
> +    DataObject *obj_info;
> +    QObject *data;
> +    QDict *qdict;
> +    int i;
> +
> +    list = NULL;
> +    for (i = 0; qmp_schema_table[i]; i++) {
> +        data = qobject_from_json(qmp_schema_table[i]);
> +        assert(data != NULL);
> +
> +        qdict = qobject_to_qdict(data);
> +        assert(qdict != NULL);
> +
> +        if (qdict_get(qdict, "command")) {
> +            info = g_malloc0(sizeof(*info));
> +            info->type = SCHEMA_META_TYPE_COMMAND;
> +            info->name = strdup(qdict_get_str(qdict, "command"));
> +        } else {
> +            continue;
> +        }
> +

Don't we want to also output types (struct, union, enum) and eventually
events, not just commands?

I hope we're converging, but I'm starting to worry that we won't have a
design in place in time for the 1.6 release.
Amos Kong July 26, 2013, 7:03 a.m. UTC | #9
On Tue, Jul 16, 2013 at 02:18:37PM +0200, Paolo Bonzini wrote:
> Il 16/07/2013 14:04, Amos Kong ha scritto:
> >> > Thanks.  I see this is unique, but it is also not too intuitive.
> >> > 
> >> > So, could you add a "kind" field to DataObject that is an enum
> >> > (list/dict/scalar, or something like that)?  This would make it easier
> >> > to parse (for humans at least, but I guess also for programs).
> > I thought we can identify the kind by some judgment.
> 
> Yes, I understood that.  Strictly speaking the kind is redundant, but it
> seems to me that it makes the API easier to understand and use.
> 
> >  if the dict has key 'key', it's a dict
> >  if no 'key', have 'type', it's a list
> >  if only have 'type', it's a buildin type (or extended type that
> >    doesn't need to be extended)
> >  if no 'key', have 'type' & 'data', it's extended list type
> >  if have 'key', 'type', 'data', it's extended dict type
> > 
> > I will added a 'kind' field to make it clearer.
> > 
> > KIND enum:
> >   list
> >   dict
> >   str
> 
> Why "str" and not "scalar" for a builtin type?  It's not necessarily a
> string, is it?

right, 'scalar' is better.

> Paolo
> 
> > scalar(bool):   Or just simplely check if have 'data' key?
> >   true/false
Amos Kong July 26, 2013, 7:21 a.m. UTC | #10
On Wed, Jul 17, 2013 at 04:36:06PM -0400, Luiz Capitulino wrote:
> On Tue, 16 Jul 2013 18:37:42 +0800
> Amos Kong <akong@redhat.com> wrote:
> 
> > Introduces new monitor command to query QMP schema information,
> > the return data is a dynamical and nested dict/list, it contains
> > the useful metadata to help management to check feature support,
> > QMP commands detail, etc.
> > 
> > I added a document for QMP introspection support.
> > (docs/qmp-full-introspection.txt)
> > 
> > We need to parse all commands json definition, and generated a
> > dynamical tree, QMP infrastructure will convert the tree to
> > json string and return to QMP client.
> > 
> > So here I defined a 'DataObject' type in qapi-schema.json,
> > it's used to describe the dynamical dictionary/list/string.
> 
> I skimmed over the patch and made some comments, but my impression
> is that this is getting too complex... Why did we move from letting
> mngt query type by type (last version) to this version which returns
> all commands and their input types? Does this satisfy libvirt needs?
 

> > diff --git a/qmp.c b/qmp.c
> > index 4c149b3..3ace3a6 100644
> > --- a/qmp.c
> > +++ b/qmp.c
> > @@ -25,6 +25,8 @@
> >  #include "sysemu/blockdev.h"
> >  #include "qom/qom-qobject.h"
> >  #include "hw/boards.h"
> > +#include "qmp-schema.h"
> > +#include "qapi/qmp/qjson.h"
> >  
> >  NameInfo *qmp_query_name(Error **errp)
> >  {
> > @@ -486,6 +488,311 @@ CpuDefinitionInfoList *qmp_query_cpu_definitions(Error **errp)
> >      return arch_query_cpu_definitions(errp);
> >  }
> >  
> > +/*
> > + * Use a string to record the visit path, type index of each node
> > + * will be saved to the string, indexes are split by ':'.
> > + */
> > +static char visit_path_str[1024];
> 
> visit path? I don't get this.

The return data is a dynamic tree. When we found defined type, we need to extend it.
But some defined type is used in its child's node. So I use a visit_path_str to
record the type index in one node. We don't extend one type, if it's already
been extened in parent's node.

{'type': 'a', 'data': ['a', 'b', 'c']}
{'command': 'cmd', 'data': 'a'} 

'a' is a defined type, we will extended it. But we will not extend 'a'
in data list of type 'a'.

I will add the explain to the doc.
    
> > +
> > +/* push the type index to visit_path_str */
> > +static void push_id(int id)
> > +{
> > +    char *end = strrchr(visit_path_str, ':');
> > +    char type_idx[256];
> > +    int num;
> > +
> > +    num = sprintf(type_idx, "%d:", id);
> > +
> > +    if (end) {
> > +        /* avoid overflow */
> > +        assert(end - visit_path_str + 1 + num < sizeof(visit_path_str));
> > +        sprintf(end + 1, "%d:", id);
> > +    } else {
> > +        sprintf(visit_path_str, "%d:", id);
> > +    }
> > +}
> > +
> > +/* pop the type index from visit_path_str */
> > +static void pop_id(void)
> > +{
> > +    char *p = strrchr(visit_path_str, ':');
> > +
> > +    assert(p != NULL);
> > +    *p = '\0';
> > +    p = strrchr(visit_path_str, ':');
> > +    if (p) {
> > +        *(p + 1) = '\0';
> > +    } else {
> > +        visit_path_str[0] = '\0';
> > +    }
> > +}


> > +SchemaEntryList *qmp_query_qmp_schema(Error **errp)
> > +{
> > +    SchemaEntryList *list, *last_entry, *entry;
> > +    SchemaEntry *info;
> > +    DataObjectList *obj_entry;
> > +    DataObject *obj_info;
> > +    QObject *data;
> > +    QDict *qdict;
> > +    int i;
> > +
> > +    list = NULL;
> > +    for (i = 0; qmp_schema_table[i]; i++) {
> > +        data = qobject_from_json(qmp_schema_table[i]);
> > +        assert(data != NULL);
> > +
> > +        qdict = qobject_to_qdict(data);
> > +        assert(qdict != NULL);
> > +
> > +        if (qdict_get(qdict, "command")) {
> > +            info = g_malloc0(sizeof(*info));
> > +            info->type = SCHEMA_META_TYPE_COMMAND;
> > +            info->name = strdup(qdict_get_str(qdict, "command"));
> 
> s/strdup/g_strdup
> 
> > +        } else {
> > +            continue;
> > +        }
> 
> You return only commands. That is, types are returned as part of the
> command input.

Right.

> ErrorClass can't be queried then? I'm not judging, only
> observing.

We can return the 'type', 'enum', 'union', 'etc' if libvirt needs them.

> Eric, this is good enough for libvirt?
> 
> Btw, you're leaking data on the else clause.
> 
> > +
> > +        memset(visit_path_str, 0, sizeof(visit_path_str));
> > +        data = qdict_get(qdict, "data");
> > +        if (data) {
> > +            info->has_data = true;
> > +            if (data->type->code == QTYPE_QLIST) {
> > +                info->data = visit_qobj_list(data);
> > +            } else if (data->type->code == QTYPE_QDICT) {
> > +                info->data = visit_qobj_dict(data);
> > +            } else if (data->type->code == QTYPE_QSTRING) {
> > +                info->data = extend_type(qstring_get_str(qobject_to_qstring(data)));
> > +                if (!info->data) {
> > +                    obj_info = g_malloc0(sizeof(*obj_info));
> > +                    obj_entry = g_malloc0(sizeof(*obj_entry));
> > +                    obj_entry->value = obj_info;
> > +                    obj_info->has_type = true;
> > +                    obj_info->type = g_strdup(qdict_get_str(qdict, "data"));
> > +                    info->data = obj_entry;
> > +                }
> > +            } else {
> > +                abort();
> 
> Pleae, explain why you're aborting here.

{ 'command': 'query-qmp-schema', 'data': XXXX }

the type of XXXX can only be list, dictionary or buildin-type.
XXXX is the value in define dictionary.
 
> > +            }
> > +        }
Amos Kong July 26, 2013, 7:51 a.m. UTC | #11
On Fri, Jul 19, 2013 at 04:05:16PM -0600, Eric Blake wrote:
> On 07/16/2013 04:37 AM, Amos Kong wrote:
> > Introduces new monitor command to query QMP schema information,
> > the return data is a dynamical and nested dict/list, it contains
> 
> s/dynamical/dynamic/
> 
> > the useful metadata to help management to check feature support,
> > QMP commands detail, etc.
> > 
> > I added a document for QMP introspection support.
> > (docs/qmp-full-introspection.txt)
> 
> Yay - docs make a world of difference.
> 
> > 
> > We need to parse all commands json definition, and generated a
> 
> mixing tense ("need" present, "generated" past); for commit messages,
> present tense is generally appropriate.  Thus:
> 
> s/generated/generate/
> 
> > dynamical tree, QMP infrastructure will convert the tree to
> 
> s/dynamical/dynamic/
> 
> > json string and return to QMP client.
> > 
> > So here I defined a 'DataObject' type in qapi-schema.json,
> > it's used to describe the dynamical dictionary/list/string.
> 
> s/dynamical/dynamic/
> 
> > 
> > { 'type': 'DataObject',
> >   'data': { '*key': 'str', '*type': 'str', '*data': ['DataObject'] } }
> > 
> > Not all the keys in data will be used.
> >  # List: type
> >  # Dict: key, type
> >  # nested List: type, data
> >  # nested Dict: key, type, data
> 
> I wonder if we can take advantage of Kevin's work on unions to make this
> MUCH easier to use:
> 
> https://lists.gnu.org/archive/html/qemu-devel/2013-07/msg01407.html
> 
> { 'enum': 'DataObjectType',
>   'data': [ 'command', 'struct', 'union', 'enum' ] }

It's necessary!!

> # extend to add 'event' later
> 
> { 'type': 'DataObjectBase',
>   'data': { 'name': 'str' } }
> 
> { 'union': 'DataObjectMemberType',
>   'discriminator': {},
>   'data': { 'reference': 'str',
>             'inline': 'DataObject' } }
> 
> { 'type': DataObjectMember',
>   'data': { 'name': 'str', 'type': 'DataObjectMemberType',
>             '*optional': 'bool', '*recursive': 'bool' } }
> 
> { 'type': 'DataObjectStruct',
>   'data': { 'data': [ 'DataObjectMember' ] } }
> { 'type': 'DataObjectEnum',
>   'data': { 'data': [ 'str' ] } }
> { 'type': 'DataObjectUnion',
>   'data': { 'data': [ 'DataObjectMember' ], '*base': 'str',
>             '*discriminator': 'str' } }
> { 'type': 'DataObjectCommand',
>   'data': { 'data': [ 'DataObjectMember' ], '*returns': 'DataObject' } }
 
Defining DataObject to a union, it will solve some hidden problem :)

> { 'union': 'DataObject',
>   'base': 'DataObjectBase',
>   'discriminator': 'type',
>   'data': {
>     'struct': 'DataObjectStruct',
>     'enum': 'DataObjectEnum',
>     'union': 'DataObjectUnion',
>     'command': 'DataObjectCommand',
>     'array': {} }
> 
> > 
> > The DataObject is described in docs/qmp-full-introspection.txt in
> > detail.
> > 
> > The following content gives an example of query-tpm-types:
> > 
> >  ## Define example in qapi-schema.json:
> > 
> >  { 'enum': 'TpmType', 'data': [ 'passthrough' ] }
> >  { 'command': 'query-tpm-types', 'returns': ['TpmType'] }
> 
> It might be more useful to have a (made-up) example that shows as many
> details as possible - a command that takes arguments and has a return
> type will exercise more of the code paths than a query command with only
> a return.

OK.

> > 
> >  ## Returned description:
> > 
> >  {
> >      "name": "query-tpm-types",
> >      "type": "Command",
> >      "returns": [
> >          {
> >              "type": "TpmType",
> >              "data": [
> >                  {
> >                      "type": "passthrough"
> >                  }
> >              ]
> 
> I need a way to know the difference between a TpmType returned directly
> in a dict, vs. a return containing an array of TpmType.

QObject is always a dictionary, it can describe list and dictionary.
I will added a 'KIND' field for QObject.
 
> >          }
> >      ]
> >  },
> 
> Thus, using the discriminated union I set out above, I would return
> something like:
> { "name": "TpmType", "type": "enum",
>   "data": [ "passthrough" ] },
> { "name": "query-tpm-types", "type": "command",
>   "data": [ ],
>   "returns": { "name": "TpmType", "type": "array" } }
> 
> > 
> > 'TpmType' is a defined type, it will be extended in returned
> > description. [ 'passthrough' ] is a list, so 'type' and 'data'
> > will be used.
> > 
> > TODO:
> > We can add events definations to qapi-schema.json by another
> 
> s/definations/definitions/
> 
> > patch, then event can also be queried.
> > 
> > Introduce another command 'query-qga-schema' to query QGA schema
> > information, it's easy to add this support with current current
> > patch.
> 
> Such a command would be part of the QGA interface, not a QMP command.
> But yes, it is needed, and the ideal patch series from you will include
> that patch as part of the series.  But I don't mind waiting until we get
> the interface nailed down before you actually implement the QGA repeat
> of the interface.
> 
> > 
> > Signed-off-by: Amos Kong <akong@redhat.com>
> > ---
> >  docs/qmp-full-introspection.txt | 143 +++++++++++++++++++
> >  qapi-schema.json                |  69 +++++++++
> >  qmp-commands.hx                 |  39 +++++
> >  qmp.c                           | 307 ++++++++++++++++++++++++++++++++++++++++
> >  4 files changed, 558 insertions(+)
> >  create mode 100644 docs/qmp-full-introspection.txt
> > 
> > diff --git a/docs/qmp-full-introspection.txt b/docs/qmp-full-introspection.txt
> > new file mode 100644
> > index 0000000..cc0fb80
> > --- /dev/null
> > +++ b/docs/qmp-full-introspection.txt
> > @@ -0,0 +1,143 @@
> > += full introspection support for QMP =
> > +
> 
> Is it worth merging this into the existing qapi-code-gen.txt, or at
> least having the two documents refer to one another, since they deal
> with related concepts (turning the .json file into generated code)?

This doc is not just about code generating (we just generated a simple
head file), most of work is about "full-introspection", the usage of
DataObject, implementation detail(visit path), examples.
 
> > +== Purpose ==
> > +
> > +Add a new interface to provide QMP schema information to management,
> 
> s/a new/an/ - after a year, the interface won't be new, but this doc
> will still be relevant.
> 
> > +the return data is a dynamical and nested dict/list, it contains
> 
> s/dynamical/dynamic/
> 
> > +the useful metadata to help management to check feature support,
> > +QMP commands detail, etc.
> > +
> > +== Usage ==
> > +
> > +Execute QMP command:
> > +
> > +  { "execute": "query-qmp-schema" }
> > +
> > +Returns:
> > +
> > +  { "return": [
> > +      {
> > +          "name": "query-name",
> > +          "type": "Command",
> > +          "returns": [
> > +              {
> > +                  "key": "*name",
> > +                  "type": "str"
> > +              }
> > +          ]
> 
> Are we trying to use struct names where they exist for compactness, or
> trying to inline-expand everything as far as possible until we run into
> self-referential recursion?

inline-expand everything

> > +      },
> > +      ...
> > +   }
> > +
> > +The whole schema information will be returned in one go, it contains
> > +commands and event. It doesn't support to be filtered by type or name.
> 
> s/event/events/
> s/It/At present, it/
> s/to be filtered/filtering/
> 
> > +
> > +We have four types (ImageInfo, BlockStats, PciDeviceInfo, SchemaData)
> 
> This list will get out of date quickly.  I'd just word it generically:
> 
> We have several self-referential types
>
> > +that uses themself in their own define data directly or indirectly,
> 
> s/uses themself/use themselves/
> 
> > +we will not repeatedly extend them to avoid dead loop.
> 
> Would it be worth a flag in the QMP output when a type is not being
> further expanded because it is a nested occurrence of itself?

ok.

>  That is,
> given my proposed layout above, ImageInfo would turn into something like:
> 
> { "name": "ImageInfo", "type": "struct",
>   "data": [ { "name": "filename", "type", "str" },
>             ...
>             { "name": "backing-image", "type": "ImageInfo",
>                "optional": true, "recursive": true } ] },
> 
> > +
> > +== more description of 'DataObject' type ==
> > +
> > +We use 'DataObject' to describe dynamical data struct, it might be
> 
> s/dynamical/dynamic/
> 
> > +nested dictionary, list or string.
> > +
> > +'DataObject' itself is a arbitrary and nested dictionary, the
> > +dictionary has three keys ('key', 'type', 'data'), 'key' and
> 
> spacing is odd.
> 
> > +'data' are optional.
> > +
> > +* For describing Dictionary, we set the key to 'key', and set the
> > +  value to 'type'
> > +* For describing List, we don't set 'key', just set the value to
> > +  'type'
> 
> Again, if you use the idea of a discriminated union, you don't need
> quite the complexity in describing this: dictionaries are listed with
> key/type/optional tuples, lists (enums) are listed with just an array of
> strings, and the QAPI perfectly described that difference by the
> discriminator telling you 'struct' vs. 'enum'.
> 
> > +* If the value of dictionary or list is non-native type, we extend
> > +  the non-native type to dictionary, set it to 'data',  and set the
> > +  non-native type's name to 'type'.
> 
> Again, I tried to set up the QAPI that describes something that uses an
> anonymous union that either gives a string (the name of a primitive or
> already-defined type) or a struct (a recursion into another layer of
> struct describing the structure of that type in line).
> 
> > diff --git a/qapi-schema.json b/qapi-schema.json
> > index 7b9fef1..cf03391 100644
> > --- a/qapi-schema.json
> > +++ b/qapi-schema.json
> > @@ -3679,3 +3679,72 @@
> >              '*cpuid-input-ecx': 'int',
> >              'cpuid-register': 'X86CPURegister32',
> >              'features': 'int' } }
> > +
> > +##
> > +# @DataObject
> > +#
> > +# Details of a data object, it can be nested dictionary/list
> > +#
> > +# @key: #optional Data object key
> > +#
> > +# @type: Data object type name
> > +#
> > +# @optional: #optional whether the data object is optional
> 
> mention that the default is false.
> 
> > +#
> > +# @data: #optional DataObject list, can be a dictionary or list type
> 
> so if 'data' is present, we are describing @type in more detail; if it
> is absent, then @type is primitive.
> 
> > +#
> > +# Since: 1.6
> > +##
> > +{ 'type': 'DataObject',
> > +  'data': { '*key': 'str', '*type': 'str', '*optional': 'bool', '*data': ['DataObject'] } }
> > +
> 
> '*type' should be an enum type, not open-coded string.
 
Right!
 
> > +##
> > +# @SchemaMetaType
> > +#
> > +# Possible meta types of a schema entry
> > +#
> > +# @Command: QMP command
> > +#
> > +# @Type: defined new data type
> > +#
> > +# @Enumeration: enumeration data type
> > +#
> > +# @Union: union data type
> > +#
> > +# Since: 1.6
> > +##
> > +{ 'enum': 'SchemaMetaType',
> > +  'data': ['Command', 'Type', 'Enumeration', 'Union'] }
> 
> Do we need to start these with a capital?  On the other hand, having
> them as capitals may make it easier to ensure we are outputting correct
> information.
> > +
> > +##
> > +# @SchemaEntry
> > +#
> > +# Details of schema items
> > +#
> > +# @type: Entry's type in string format
> > +#
> > +# @name: Entry name
> > +#
> > +# @data: #optional list of DataObject. This can have different meaning
> > +#        depending on the 'type' value. For example, for a QMP command,
> > +#        this member contains an argument listing. For an enumeration,
> > +#        it contains the enum's values and so on
> 
> This argues for making it a union type.
> 
> > +#
> > +# @returns: #optional list of DataObject, return data after executing
> > +#           QMP command
> > +#
> > +# Since: 1.6
> > +##
> > +{ 'type': 'SchemaEntry', 'data': { 'type': 'SchemaMetaType',
> > +  'name': 'str', '*data': ['DataObject'], '*returns': ['DataObject'] } }
> > +
> > +##
> > +# @query-qmp-schema
> > +#
> > +# Query QMP schema information
> > +#
> > +# Returns: list of @SchemaEntry. Returns an error if json string is invalid.
> 
> If you don't take any arguments, then the "returns an error" statement
> is impossible.

When we execute the full introspecting, we will parse the json strings
in the head file (converted from .json file). Return error if the
json string is invild in head file.
 
> > +#
> > +# Since: 1.6
> > +##
> > +{ 'command': 'query-qmp-schema', 'returns': ['SchemaEntry'] }
> > diff --git a/qmp-commands.hx b/qmp-commands.hx
> > index e075df4..e3cbe93 100644
> > --- a/qmp-commands.hx
> >  
> > +/*
> > + * Use a string to record the visit path, type index of each node
> > + * will be saved to the string, indexes are split by ':'.
> > + */
> > +static char visit_path_str[1024];
> 
> Is a fixed width buffer really the best solution, or does glib offer us
> something better?  For example, a hash table, or even a growable string.
 
1024 is enough by experience, and I have a check in push_id() which
will fill string in visit_path_str.

+static void push_id(int id)
+{
+    char *end = strrchr(visit_path_str, ':');
+    char type_idx[256];
+    int num;
+
+    num = sprintf(type_idx, "%d:", id);
+
+    if (end) {
+        /* avoid overflow */
+        assert(end - visit_path_str + 1 + num < sizeof(visit_path_str));


> > +
> > +    for (i = 0; qmp_schema_table[i]; i++) {
> > +        data = qobject_from_json(qmp_schema_table[i]);
> > +        assert(data != NULL);
> > +
> > +        qdict = qobject_to_qdict(data);
> > +        assert(qdict != NULL);
> > +
> > +        ent = qdict_first(qdict);
> > +        if (!qdict_get(qdict, "enum") && !qdict_get(qdict, "type")
> > +            && !qdict_get(qdict, "union")) {
> > +            continue;
> > +        }
> 
> Why are we doing the work in C code, every time the command is called?
> Wouldn't it be more efficient to do the work in python code, at the time
> the qmp_schema_table C code is first generated, so that the generated
> code is already in the right format?

Right now, we parse the json string, and generate a dynamic data
struct and set it as the output of QMP.

Parse json string by qapi (python) script and generate C code ( a big
tree, it's buildup by many nested structs. We need to visit the big
static tree and generate another dynamic tree for QMP.

I think current implement is better. I will try the python solution.
 
> > +SchemaEntryList *qmp_query_qmp_schema(Error **errp)
> > +{
> > +    SchemaEntryList *list, *last_entry, *entry;
> > +    SchemaEntry *info;
> > +    DataObjectList *obj_entry;
> > +    DataObject *obj_info;
> > +    QObject *data;
> > +    QDict *qdict;
> > +    int i;
> > +
> > +    list = NULL;
> > +    for (i = 0; qmp_schema_table[i]; i++) {
> > +        data = qobject_from_json(qmp_schema_table[i]);
> > +        assert(data != NULL);
> > +
> > +        qdict = qobject_to_qdict(data);
> > +        assert(qdict != NULL);
> > +
> > +        if (qdict_get(qdict, "command")) {
> > +            info = g_malloc0(sizeof(*info));
> > +            info->type = SCHEMA_META_TYPE_COMMAND;
> > +            info->name = strdup(qdict_get_str(qdict, "command"));
> > +        } else {
> > +            continue;
> > +        }
> > +
> 
> Don't we want to also output types (struct, union, enum) and eventually
> events, not just commands?

I think struct, union, enum are used in commands, so I didn't
repeatedly output them. If it's not repeated, I will output all of
them in next version.
 
> I hope we're converging, but I'm starting to worry that we won't have a
> design in place in time for the 1.6 release.
Eric Blake July 26, 2013, 11:52 a.m. UTC | #12
On 07/26/2013 01:51 AM, Amos Kong wrote:

>>> +# Query QMP schema information
>>> +#
>>> +# Returns: list of @SchemaEntry. Returns an error if json string is invalid.
>>
>> If you don't take any arguments, then the "returns an error" statement
>> is impossible.
> 
> When we execute the full introspecting, we will parse the json strings
> in the head file (converted from .json file). Return error if the
> json string is invild in head file.

We should fail to compile if the conversion from .json to head file is
not correct.  No working qemu binary should ever encounter an invalid
head file.
Amos Kong Nov. 27, 2013, 2:32 a.m. UTC | #13
On Fri, Jul 19, 2013 at 04:05:16PM -0600, Eric Blake wrote:
> On 07/16/2013 04:37 AM, Amos Kong wrote:
> > Introduces new monitor command to query QMP schema information,
> > the return data is a dynamical and nested dict/list, it contains
> 
> s/dynamical/dynamic/
> 
> > the useful metadata to help management to check feature support,
> > QMP commands detail, etc.
> > 
> > I added a document for QMP introspection support.
> > (docs/qmp-full-introspection.txt)
> 
> Yay - docs make a world of difference.
> 
> > 
> > We need to parse all commands json definition, and generated a
> 
> mixing tense ("need" present, "generated" past); for commit messages,
> present tense is generally appropriate.  Thus:
> 
> s/generated/generate/
> 
> > dynamical tree, QMP infrastructure will convert the tree to
> 
> s/dynamical/dynamic/
> 
> > json string and return to QMP client.
> > 
> > So here I defined a 'DataObject' type in qapi-schema.json,
> > it's used to describe the dynamical dictionary/list/string.
> 
> s/dynamical/dynamic/
> 
> > 
> > { 'type': 'DataObject',
> >   'data': { '*key': 'str', '*type': 'str', '*data': ['DataObject'] } }
> > 
> > Not all the keys in data will be used.
> >  # List: type
> >  # Dict: key, type
> >  # nested List: type, data
> >  # nested Dict: key, type, data
> 
> I wonder if we can take advantage of Kevin's work on unions to make this
> MUCH easier to use:
> 
> https://lists.gnu.org/archive/html/qemu-devel/2013-07/msg01407.html
> 
> { 'enum': 'DataObjectType',
>   'data': [ 'command', 'struct', 'union', 'enum' ] }
> # extend to add 'event' later
> 
> { 'type': 'DataObjectBase',
>   'data': { 'name': 'str' } }
> 
> { 'union': 'DataObjectMemberType',
>   'discriminator': {},
>   'data': { 'reference': 'str',
>             'inline': 'DataObject' } }
> 
> { 'type': DataObjectMember',
>   'data': { 'name': 'str', 'type': 'DataObjectMemberType',
>             '*optional': 'bool', '*recursive': 'bool' } }
> 
> { 'type': 'DataObjectStruct',
>   'data': { 'data': [ 'DataObjectMember' ] } }
> { 'type': 'DataObjectEnum',
>   'data': { 'data': [ 'str' ] } }
> { 'type': 'DataObjectUnion',
>   'data': { 'data': [ 'DataObjectMember' ], '*base': 'str',
>             '*discriminator': 'str' } }
> { 'type': 'DataObjectCommand',
>   'data': { 'data': [ 'DataObjectMember' ], '*returns': 'DataObject' } }

 
> { 'union': 'DataObject',
>   'base': 'DataObjectBase',
>   'discriminator': 'type',
>   'data': {
>     'struct': 'DataObjectStruct',
>     'enum': 'DataObjectEnum',
>     'union': 'DataObjectUnion',
>     'command': 'DataObjectCommand',

>     'array': {} }


In my patch, I used a _dictionary_ to describe this kind of thing
 1) dict,  2) list, 3) str

The above line is used for Dictionary or List, it should be:
      'array': ['DataObject']

But I touched a new error:
qapi-visit.c: In function ‘visit_type_DataObject’:
qapi-visit.c:7255:29: error: implicit declaration of function ‘visit_type_DataObjectList_fields’ [-Werror=implicit-function-declaration]
                             visit_type_DataObjectList_fields(m, &(*obj)->array, &err);

----
So I try to defined 

{ 'type': 'DataObjectArray', 'data': ['DataObject'] }
'DataObjectArrayType' } }

{ 'union': 'DataObject',
  'base': 'DataObjectBase',
  'discriminator': 'name',
  'data': {
    'array': 'DataObjectArray',

got error:
Traceback (most recent call last):
  File "/home/devel/qemu/scripts/qapi-types.py", line 471, in <module>
    ret += generate_struct(expr) + "\n"
  File "/home/devel/qemu/scripts/qapi-types.py", line 101, in generate_struct
    ret += generate_struct_fields(members)
  File "/home/devel/qemu/scripts/qapi-types.py", line 67, in generate_struct_fields
    for argname, argentry, optional, structured in parse_args(members):
  File "/home/devel/qemu/scripts/qapi.py", line 197, in parse_args
    argentry = typeinfo[member]
TypeError: list indices must be integers, not str

----
a new definition:

{ 'enum': 'DataObjectArrayType', 'data': ['Dictionary', 'List'] }
{ 'type': 'DataObjectArray', 'data': {'data': ['DataObject'], 'type':
'DataObjectArrayType' } }

{ 'union': 'DataObject',
  'base': 'DataObjectBase',
  'discriminator': 'name',
  'data': {
    'array': 'DataObjectArray',

-----
In my V2, I parse the schema just according the Format attribute (dict, str, list)
Eric suggested defination is wonderful, but it's not flexible as mine ;-)
The data type, format (dict/str/list), more matadata should be considered.
It makes the parse very complex.

I have to simple it, the matadata will also provided, just make the
parse work easyer. Libvirt can still get good info as using Eric's
defination.


Thanks, Amos

> > The DataObject is described in docs/qmp-full-introspection.txt in
> > detail.
> > 
> > The following content gives an example of query-tpm-types:
> > 
> >  ## Define example in qapi-schema.json:
> > 
> >  { 'enum': 'TpmType', 'data': [ 'passthrough' ] }
> >  { 'command': 'query-tpm-types', 'returns': ['TpmType'] }
> 
> It might be more useful to have a (made-up) example that shows as many
> details as possible - a command that takes arguments and has a return
> type will exercise more of the code paths than a query command with only
> a return.
> 
> > 
> >  ## Returned description:
> > 
> >  {
> >      "name": "query-tpm-types",
> >      "type": "Command",
> >      "returns": [
> >          {
> >              "type": "TpmType",
> >              "data": [
> >                  {
> >                      "type": "passthrough"
> >                  }
> >              ]
> 
> I need a way to know the difference between a TpmType returned directly
> in a dict, vs. a return containing an array of TpmType.
> 
> >          }
> >      ]
> >  },
> 
> Thus, using the discriminated union I set out above, I would return
> something like:
> { "name": "TpmType", "type": "enum",
>   "data": [ "passthrough" ] },
> { "name": "query-tpm-types", "type": "command",
>   "data": [ ],
>   "returns": { "name": "TpmType", "type": "array" } }
> 
> > 
> > 'TpmType' is a defined type, it will be extended in returned
> > description. [ 'passthrough' ] is a list, so 'type' and 'data'
> > will be used.
> > 
> > TODO:
> > We can add events definations to qapi-schema.json by another
> 
> s/definations/definitions/
> 
> > patch, then event can also be queried.
> > 
> > Introduce another command 'query-qga-schema' to query QGA schema
> > information, it's easy to add this support with current current
> > patch.
> 
> Such a command would be part of the QGA interface, not a QMP command.
> But yes, it is needed, and the ideal patch series from you will include
> that patch as part of the series.  But I don't mind waiting until we get
> the interface nailed down before you actually implement the QGA repeat
> of the interface.
> 
> > 
> > Signed-off-by: Amos Kong <akong@redhat.com>
> > ---
> >  docs/qmp-full-introspection.txt | 143 +++++++++++++++++++
> >  qapi-schema.json                |  69 +++++++++
> >  qmp-commands.hx                 |  39 +++++
> >  qmp.c                           | 307 ++++++++++++++++++++++++++++++++++++++++
> >  4 files changed, 558 insertions(+)
> >  create mode 100644 docs/qmp-full-introspection.txt
> > 
> > diff --git a/docs/qmp-full-introspection.txt b/docs/qmp-full-introspection.txt
> > new file mode 100644
> > index 0000000..cc0fb80
> > --- /dev/null
> > +++ b/docs/qmp-full-introspection.txt
> > @@ -0,0 +1,143 @@
> > += full introspection support for QMP =
> > +
> 
> Is it worth merging this into the existing qapi-code-gen.txt, or at
> least having the two documents refer to one another, since they deal
> with related concepts (turning the .json file into generated code)?
> 
> > +== Purpose ==
> > +
> > +Add a new interface to provide QMP schema information to management,
> 
> s/a new/an/ - after a year, the interface won't be new, but this doc
> will still be relevant.
> 
> > +the return data is a dynamical and nested dict/list, it contains
> 
> s/dynamical/dynamic/
> 
> > +the useful metadata to help management to check feature support,
> > +QMP commands detail, etc.
> > +
> > +== Usage ==
> > +
> > +Execute QMP command:
> > +
> > +  { "execute": "query-qmp-schema" }
> > +
> > +Returns:
> > +
> > +  { "return": [
> > +      {
> > +          "name": "query-name",
> > +          "type": "Command",
> > +          "returns": [
> > +              {
> > +                  "key": "*name",
> > +                  "type": "str"
> > +              }
> > +          ]
> 
> Are we trying to use struct names where they exist for compactness, or
> trying to inline-expand everything as far as possible until we run into
> self-referential recursion?
> 
> > +      },
> > +      ...
> > +   }
> > +
> > +The whole schema information will be returned in one go, it contains
> > +commands and event. It doesn't support to be filtered by type or name.
> 
> s/event/events/
> s/It/At present, it/
> s/to be filtered/filtering/
> 
> > +
> > +We have four types (ImageInfo, BlockStats, PciDeviceInfo, SchemaData)
> 
> This list will get out of date quickly.  I'd just word it generically:
> 
> We have several self-referential types
> 
> > +that uses themself in their own define data directly or indirectly,
> 
> s/uses themself/use themselves/
> 
> > +we will not repeatedly extend them to avoid dead loop.
> 
> Would it be worth a flag in the QMP output when a type is not being
> further expanded because it is a nested occurrence of itself?  That is,
> given my proposed layout above, ImageInfo would turn into something like:
> 
> { "name": "ImageInfo", "type": "struct",
>   "data": [ { "name": "filename", "type", "str" },
>             ...
>             { "name": "backing-image", "type": "ImageInfo",
>                "optional": true, "recursive": true } ] },
> 
> > +
> > +== more description of 'DataObject' type ==
> > +
> > +We use 'DataObject' to describe dynamical data struct, it might be
> 
> s/dynamical/dynamic/
> 
> > +nested dictionary, list or string.
> > +
> > +'DataObject' itself is a arbitrary and nested dictionary, the
> > +dictionary has three keys ('key', 'type', 'data'), 'key' and
> 
> spacing is odd.
> 
> > +'data' are optional.
> > +
> > +* For describing Dictionary, we set the key to 'key', and set the
> > +  value to 'type'
> > +* For describing List, we don't set 'key', just set the value to
> > +  'type'
> 
> Again, if you use the idea of a discriminated union, you don't need
> quite the complexity in describing this: dictionaries are listed with
> key/type/optional tuples, lists (enums) are listed with just an array of
> strings, and the QAPI perfectly described that difference by the
> discriminator telling you 'struct' vs. 'enum'.
> 
> > +* If the value of dictionary or list is non-native type, we extend
> > +  the non-native type to dictionary, set it to 'data',  and set the
> > +  non-native type's name to 'type'.
> 
> Again, I tried to set up the QAPI that describes something that uses an
> anonymous union that either gives a string (the name of a primitive or
> already-defined type) or a struct (a recursion into another layer of
> struct describing the structure of that type in line).
> 
> > diff --git a/qapi-schema.json b/qapi-schema.json
> > index 7b9fef1..cf03391 100644
> > --- a/qapi-schema.json
> > +++ b/qapi-schema.json
> > @@ -3679,3 +3679,72 @@
> >              '*cpuid-input-ecx': 'int',
> >              'cpuid-register': 'X86CPURegister32',
> >              'features': 'int' } }
> > +
> > +##
> > +# @DataObject
> > +#
> > +# Details of a data object, it can be nested dictionary/list
> > +#
> > +# @key: #optional Data object key
> > +#
> > +# @type: Data object type name
> > +#
> > +# @optional: #optional whether the data object is optional
> 
> mention that the default is false.
> 
> > +#
> > +# @data: #optional DataObject list, can be a dictionary or list type
> 
> so if 'data' is present, we are describing @type in more detail; if it
> is absent, then @type is primitive.
> 
> > +#
> > +# Since: 1.6
> > +##
> > +{ 'type': 'DataObject',
> > +  'data': { '*key': 'str', '*type': 'str', '*optional': 'bool', '*data': ['DataObject'] } }
> > +
> 
> '*type' should be an enum type, not open-coded string.
> 
> 
> > +##
> > +# @SchemaMetaType
> > +#
> > +# Possible meta types of a schema entry
> > +#
> > +# @Command: QMP command
> > +#
> > +# @Type: defined new data type
> > +#
> > +# @Enumeration: enumeration data type
> > +#
> > +# @Union: union data type
> > +#
> > +# Since: 1.6
> > +##
> > +{ 'enum': 'SchemaMetaType',
> > +  'data': ['Command', 'Type', 'Enumeration', 'Union'] }
> 
> Do we need to start these with a capital?  On the other hand, having
> them as capitals may make it easier to ensure we are outputting correct
> information.
> > +
> > +##
> > +# @SchemaEntry
> > +#
> > +# Details of schema items
> > +#
> > +# @type: Entry's type in string format
> > +#
> > +# @name: Entry name
> > +#
> > +# @data: #optional list of DataObject. This can have different meaning
> > +#        depending on the 'type' value. For example, for a QMP command,
> > +#        this member contains an argument listing. For an enumeration,
> > +#        it contains the enum's values and so on
> 
> This argues for making it a union type.
> 
> > +#
> > +# @returns: #optional list of DataObject, return data after executing
> > +#           QMP command
> > +#
> > +# Since: 1.6
> > +##
> > +{ 'type': 'SchemaEntry', 'data': { 'type': 'SchemaMetaType',
> > +  'name': 'str', '*data': ['DataObject'], '*returns': ['DataObject'] } }
> > +
> > +##
> > +# @query-qmp-schema
> > +#
> > +# Query QMP schema information
> > +#
> > +# Returns: list of @SchemaEntry. Returns an error if json string is invalid.
> 
> If you don't take any arguments, then the "returns an error" statement
> is impossible.
> 
> > +#
> > +# Since: 1.6
> > +##
> > +{ 'command': 'query-qmp-schema', 'returns': ['SchemaEntry'] }
> > diff --git a/qmp-commands.hx b/qmp-commands.hx
> > index e075df4..e3cbe93 100644
> > --- a/qmp-commands.hx
> >  
> > +/*
> > + * Use a string to record the visit path, type index of each node
> > + * will be saved to the string, indexes are split by ':'.
> > + */
> > +static char visit_path_str[1024];
> 
> Is a fixed width buffer really the best solution, or does glib offer us
> something better?  For example, a hash table, or even a growable string.
> 
> > +
> > +    for (i = 0; qmp_schema_table[i]; i++) {
> > +        data = qobject_from_json(qmp_schema_table[i]);
> > +        assert(data != NULL);
> > +
> > +        qdict = qobject_to_qdict(data);
> > +        assert(qdict != NULL);
> > +
> > +        ent = qdict_first(qdict);
> > +        if (!qdict_get(qdict, "enum") && !qdict_get(qdict, "type")
> > +            && !qdict_get(qdict, "union")) {
> > +            continue;
> > +        }
> 
> Why are we doing the work in C code, every time the command is called?
> Wouldn't it be more efficient to do the work in python code, at the time
> the qmp_schema_table C code is first generated, so that the generated
> code is already in the right format?
> 
> > +SchemaEntryList *qmp_query_qmp_schema(Error **errp)
> > +{
> > +    SchemaEntryList *list, *last_entry, *entry;
> > +    SchemaEntry *info;
> > +    DataObjectList *obj_entry;
> > +    DataObject *obj_info;
> > +    QObject *data;
> > +    QDict *qdict;
> > +    int i;
> > +
> > +    list = NULL;
> > +    for (i = 0; qmp_schema_table[i]; i++) {
> > +        data = qobject_from_json(qmp_schema_table[i]);
> > +        assert(data != NULL);
> > +
> > +        qdict = qobject_to_qdict(data);
> > +        assert(qdict != NULL);
> > +
> > +        if (qdict_get(qdict, "command")) {
> > +            info = g_malloc0(sizeof(*info));
> > +            info->type = SCHEMA_META_TYPE_COMMAND;
> > +            info->name = strdup(qdict_get_str(qdict, "command"));
> > +        } else {
> > +            continue;
> > +        }
> > +
> 
> Don't we want to also output types (struct, union, enum) and eventually
> events, not just commands?
> 
> I hope we're converging, but I'm starting to worry that we won't have a
> design in place in time for the 1.6 release.
> 
> -- 
> Eric Blake   eblake redhat com    +1-919-301-3266
> Libvirt virtualization library http://libvirt.org
>
Kevin Wolf Nov. 27, 2013, 9:51 a.m. UTC | #14
Am 27.11.2013 um 03:32 hat Amos Kong geschrieben:
> On Fri, Jul 19, 2013 at 04:05:16PM -0600, Eric Blake wrote:
> > On 07/16/2013 04:37 AM, Amos Kong wrote:
> > > { 'type': 'DataObject',
> > >   'data': { '*key': 'str', '*type': 'str', '*data': ['DataObject'] } }
> > > 
> > > Not all the keys in data will be used.
> > >  # List: type
> > >  # Dict: key, type
> > >  # nested List: type, data
> > >  # nested Dict: key, type, data
> > 
> > I wonder if we can take advantage of Kevin's work on unions to make this
> > MUCH easier to use:
> > 
> > https://lists.gnu.org/archive/html/qemu-devel/2013-07/msg01407.html
> > 
> > { 'enum': 'DataObjectType',
> >   'data': [ 'command', 'struct', 'union', 'enum' ] }
> > # extend to add 'event' later
> > 
> > { 'type': 'DataObjectBase',
> >   'data': { 'name': 'str' } }
> > 
> > { 'union': 'DataObjectMemberType',
> >   'discriminator': {},
> >   'data': { 'reference': 'str',
> >             'inline': 'DataObject' } }
> > 
> > { 'type': DataObjectMember',
> >   'data': { 'name': 'str', 'type': 'DataObjectMemberType',
> >             '*optional': 'bool', '*recursive': 'bool' } }
> > 
> > { 'type': 'DataObjectStruct',
> >   'data': { 'data': [ 'DataObjectMember' ] } }
> > { 'type': 'DataObjectEnum',
> >   'data': { 'data': [ 'str' ] } }
> > { 'type': 'DataObjectUnion',
> >   'data': { 'data': [ 'DataObjectMember' ], '*base': 'str',
> >             '*discriminator': 'str' } }
> > { 'type': 'DataObjectCommand',
> >   'data': { 'data': [ 'DataObjectMember' ], '*returns': 'DataObject' } }
> 
>  
> > { 'union': 'DataObject',
> >   'base': 'DataObjectBase',
> >   'discriminator': 'type',
> >   'data': {
> >     'struct': 'DataObjectStruct',
> >     'enum': 'DataObjectEnum',
> >     'union': 'DataObjectUnion',
> >     'command': 'DataObjectCommand',
> 
> >     'array': {} }
> 
> 
> In my patch, I used a _dictionary_ to describe this kind of thing
>  1) dict,  2) list, 3) str
> 
> The above line is used for Dictionary or List, it should be:
>       'array': ['DataObject']
> 
> But I touched a new error:
> qapi-visit.c: In function ‘visit_type_DataObject’:
> qapi-visit.c:7255:29: error: implicit declaration of function ‘visit_type_DataObjectList_fields’ [-Werror=implicit-function-declaration]
>                              visit_type_DataObjectList_fields(m, &(*obj)->array, &err);
> 
> ----
> So I try to defined 
> 
> { 'type': 'DataObjectArray', 'data': ['DataObject'] }
> 'DataObjectArrayType' } }
> 
> { 'union': 'DataObject',
>   'base': 'DataObjectBase',
>   'discriminator': 'name',
>   'data': {
>     'array': 'DataObjectArray',
> 
> got error:
> Traceback (most recent call last):
>   File "/home/devel/qemu/scripts/qapi-types.py", line 471, in <module>
>     ret += generate_struct(expr) + "\n"
>   File "/home/devel/qemu/scripts/qapi-types.py", line 101, in generate_struct
>     ret += generate_struct_fields(members)
>   File "/home/devel/qemu/scripts/qapi-types.py", line 67, in generate_struct_fields
>     for argname, argentry, optional, structured in parse_args(members):
>   File "/home/devel/qemu/scripts/qapi.py", line 197, in parse_args
>     argentry = typeinfo[member]
> TypeError: list indices must be integers, not str

I've never had a need for unions inside an array so far, but that
doesn't make them less useful. We'll need them in more places than just
here.

So instead of working around the current limitations, the right thing to
do is to modify the QAPI generator to allow this kind of definitions.

Kevin
Amos Kong Dec. 20, 2013, 11:57 a.m. UTC | #15
(resend without big attachment)

Hello Eric, other

We had "command, enumeration, type, unionobj" in Eric suggested DataObject
union, it's helpful for us to provide meaningful metadata in the output.
but there still exists some problem.

We should describe some arbitrary data struct, I would like to call it "undefined struct"

eg 1: 
  { 'type': 'VersionInfo',
    'data': {'qemu': {'major': 'int', 'minor': 'int', 'micro': 'int'},
             'package': 'str'} }
  it's same as:
  { 'type': 'newtype',
    'data': {'major': 'int', 'minor': 'int', 'micro': 'int'} }
  { 'type': 'VersionInfo',
    'data': { 'qemu': 'newtype', 'package': 'str'} }

  The difference between original 'DataObjectType' and 'DataObjectUndefinedStruct'
  is that we don't have 'name' for the DataObject union for undefined struct.
  so I set the 'name' item in DataObjectBase to be optional.

eg 2:
  { 'command': 'human-monitor-command',
  'data': {'command-line': 'str', '*cpu-index': 'int'},
  'returns': 'str' }
  ... 'returns': ['RxFilterInfo'] }
  ... 'returns': 'ChardevReturn' }

  We returns str (native type), list or extended dict here. Sometimes we
  returns a defined type, but it doesn't need to be extended. And we need 
  to describe this kind of data (type str, dict or list) by "DataObject"
  in schema definition of DataObject** type.

  So I added a "'reference-type': 'String'" in DataObject union.
  list, dict will still be described by "DataObjectType"


You can find the draft patches here:
  https://github.com/kongove/qemu/commits/qmp-introspection
I will post the V3 when I finish the cleanup.

Thanks, Amos


Command output
==============
https://raw.github.com/kongove/misc/master/txt/qmp-introspection.output.txt (1.6M)

Latest schema definition
========================
{ 'type': 'DataObjectBase',
  'data': { '*name': 'str', 'type': 'str' } }
{ 'union': 'DataObjectMemberType',
  'discriminator': {},
  'data': { 'reference': 'str',
            'undefined': 'DataObject',
            'extend': 'DataObject' } }
{ 'type': 'DataObjectMember',
  'data': { 'type': 'DataObjectMemberType', '*name': 'str',
            '*optional': 'bool', '*recursive': 'bool' } }
{ 'type': 'DataObjectCommand',
  'data': { '*data': [ 'DataObjectMember' ],
            '*returns': 'DataObject',
            '*gen': 'bool' } }
{ 'type': 'DataObjectEnumeration',
  'data': { 'data': [ 'str' ] } }
{ 'type': 'DataObjectType',
  'data': { 'data': [ 'DataObjectMember' ] } }
{ 'type': 'DataObjectUndefinedStruct',
  'data': { 'data': [ 'DataObjectMember' ] } }
{ 'type': 'DataObjectUnion',
  'data': { 'data': [ 'DataObjectMember' ], '*base': 'str',
            '*discriminator': 'str' } }
{ 'union': 'DataObject',
  'base': 'DataObjectBase',
  'discriminator': 'type',
  'data': {
    'command': 'DataObjectCommand',
    'enumeration': 'DataObjectEnumeration',
    'type': 'DataObjectType',
    'undefined-struct': 'DataObjectUndefinedStruct',
    'reference-type': 'String',
    'unionobj': 'DataObjectUnion' } }
{ 'command': 'query-qmp-schema', 'returns': ['DataObject'] }
Paolo Bonzini Dec. 20, 2013, 6:03 p.m. UTC | #16
Il 20/12/2013 12:57, Amos Kong ha scritto:
> { 'type': 'DataObjectBase',
>   'data': { '*name': 'str', 'type': 'str' } }
> { 'union': 'DataObjectMemberType',
>   'discriminator': {},
>   'data': { 'reference': 'str',
>             'undefined': 'DataObject',
>             'extend': 'DataObject' } }

What is the purpose of "undefined"?  I don't see any occurrence of any
of "undefined" or "extend" in the sample.

> { 'type': 'DataObjectMember',
>   'data': { 'type': 'DataObjectMemberType', '*name': 'str',
>             '*optional': 'bool', '*recursive': 'bool' } }
> { 'type': 'DataObjectCommand',
>   'data': { '*data': [ 'DataObjectMember' ],
>             '*returns': 'DataObject',
>             '*gen': 'bool' } }
> { 'type': 'DataObjectEnumeration',
>   'data': { 'data': [ 'str' ] } }
> { 'type': 'DataObjectType',
>   'data': { 'data': [ 'DataObjectMember' ] } }
> { 'type': 'DataObjectUndefinedStruct',

Perhaps Unnamed or Anonymous?

>   'data': { 'data': [ 'DataObjectMember' ] } }
> { 'type': 'DataObjectUnion',
>   'data': { 'data': [ 'DataObjectMember' ], '*base': 'str',
>             '*discriminator': 'str' } }
> { 'union': 'DataObject',
>   'base': 'DataObjectBase',
>   'discriminator': 'type',
>   'data': {
>     'command': 'DataObjectCommand',
>     'enumeration': 'DataObjectEnumeration',
>     'type': 'DataObjectType',
>     'undefined-struct': 'DataObjectUndefinedStruct',
>     'reference-type': 'String',
>     'unionobj': 'DataObjectUnion' } }
> { 'command': 'query-qmp-schema', 'returns': ['DataObject'] }

I think forcing expansion of everything that isn't unnamed/anonymous
makes the schema much larger and unwieldy.  Otherwise looks great!

Paolo
Wayne Xia Dec. 23, 2013, 6:32 a.m. UTC | #17
Hi, Amos

> (resend without big attachment)
>
> Hello Eric, other
>
> We had "command, enumeration, type, unionobj" in Eric suggested DataObject
> union, it's helpful for us to provide meaningful metadata in the output.
> but there still exists some problem.
>
> We should describe some arbitrary data struct, I would like to call it "undefined struct"
>
   If user have defined an arbitrary or embbed data struct, I think it
is better leave as it is, instead of generate a new struct for it.
Since qapi-visit.c and qapi-types.c doesn't have a "undefined" struct
for it now, it is a bit risk to do it only in QMP introspection. Maybe
leave it now, and support it when we found it is really useful?(and add
it in qapi-visit.c and qapi-types.c correspondly)


> eg 1:
>    { 'type': 'VersionInfo',
>      'data': {'qemu': {'major': 'int', 'minor': 'int', 'micro': 'int'},
>               'package': 'str'} }
>    it's same as:
>    { 'type': 'newtype',
>      'data': {'major': 'int', 'minor': 'int', 'micro': 'int'} }
>    { 'type': 'VersionInfo',
>      'data': { 'qemu': 'newtype', 'package': 'str'} }
>
>    The difference between original 'DataObjectType' and 'DataObjectUndefinedStruct'
>    is that we don't have 'name' for the DataObject union for undefined struct.
>    so I set the 'name' item in DataObjectBase to be optional.
>
> eg 2:
>    { 'command': 'human-monitor-command',
>    'data': {'command-line': 'str', '*cpu-index': 'int'},
>    'returns': 'str' }
>    ... 'returns': ['RxFilterInfo'] }
>    ... 'returns': 'ChardevReturn' }
>
>    We returns str (native type), list or extended dict here. Sometimes we
>    returns a defined type, but it doesn't need to be extended. And we need
>    to describe this kind of data (type str, dict or list) by "DataObject"
>    in schema definition of DataObject** type.
>
>    So I added a "'reference-type': 'String'" in DataObject union.
>    list, dict will still be described by "DataObjectType"
>
  I guess you want to tip what type may be returned? It seems a bit too
agressive, since the qapi-schema.json didn't tip that those types may
be returned.


>
> You can find the draft patches here:
>    https://github.com/kongove/qemu/commits/qmp-introspection
> I will post the V3 when I finish the cleanup.
>
> Thanks, Amos
>
>
> Command output
> ==============
> https://raw.github.com/kongove/misc/master/txt/qmp-introspection.output.txt (1.6M)
>
> Latest schema definition
> ========================
> { 'type': 'DataObjectBase',
>    'data': { '*name': 'str', 'type': 'str' } }
> { 'union': 'DataObjectMemberType',
>    'discriminator': {},
>    'data': { 'reference': 'str',
>              'undefined': 'DataObject',
>              'extend': 'DataObject' } }
> { 'type': 'DataObjectMember',
>    'data': { 'type': 'DataObjectMemberType', '*name': 'str',
>              '*optional': 'bool', '*recursive': 'bool' } }
> { 'type': 'DataObjectCommand',
>    'data': { '*data': [ 'DataObjectMember' ],
>              '*returns': 'DataObject',
>              '*gen': 'bool' } }
> { 'type': 'DataObjectEnumeration',
>    'data': { 'data': [ 'str' ] } }
> { 'type': 'DataObjectType',
>    'data': { 'data': [ 'DataObjectMember' ] } }
> { 'type': 'DataObjectUndefinedStruct',
>    'data': { 'data': [ 'DataObjectMember' ] } }
> { 'type': 'DataObjectUnion',
>    'data': { 'data': [ 'DataObjectMember' ], '*base': 'str',
>              '*discriminator': 'str' } }
> { 'union': 'DataObject',
>    'base': 'DataObjectBase',
>    'discriminator': 'type',
>    'data': {
>      'command': 'DataObjectCommand',
>      'enumeration': 'DataObjectEnumeration',
>      'type': 'DataObjectType',
>      'undefined-struct': 'DataObjectUndefinedStruct',
>      'reference-type': 'String',
>      'unionobj': 'DataObjectUnion' } }
> { 'command': 'query-qmp-schema', 'returns': ['DataObject'] }
>
Amos Kong Dec. 23, 2013, 7:15 a.m. UTC | #18
On Mon, Dec 23, 2013 at 02:32:46PM +0800, Wenchao Xia wrote:
> Hi, Amos
> 
> >(resend without big attachment)
> >
> >Hello Eric, other
> >
> >We had "command, enumeration, type, unionobj" in Eric suggested DataObject
> >union, it's helpful for us to provide meaningful metadata in the output.
> >but there still exists some problem.
> >
> >We should describe some arbitrary data struct, I would like to call it "undefined struct"
> >
>   If user have defined an arbitrary or embbed data struct, I think it
> is better leave as it is, instead of generate a new struct for it.

I don't really generate a new struct for it, just try the arbitrary
data as an abstract 'undefined' struct.

In the output, it's "leave as it is".
 
> Since qapi-visit.c and qapi-types.c doesn't have a "undefined" struct
> for it now, it is a bit risk to do it only in QMP introspection. Maybe
> leave it now, and support it when we found it is really useful?(and add
> it in qapi-visit.c and qapi-types.c correspondly)
> 
> >eg 1:
> >   { 'type': 'VersionInfo',
> >     'data': {'qemu': {'major': 'int', 'minor': 'int', 'micro': 'int'},
> >              'package': 'str'} }
> >   it's same as:
> >   { 'type': 'newtype',
> >     'data': {'major': 'int', 'minor': 'int', 'micro': 'int'} }
> >   { 'type': 'VersionInfo',
> >     'data': { 'qemu': 'newtype', 'package': 'str'} }
> >
> >   The difference between original 'DataObjectType' and 'DataObjectUndefinedStruct'
> >   is that we don't have 'name' for the DataObject union for undefined struct.
> >   so I set the 'name' item in DataObjectBase to be optional.
> >
> >eg 2:
> >   { 'command': 'human-monitor-command',
> >   'data': {'command-line': 'str', '*cpu-index': 'int'},
> >   'returns': 'str' }
> >   ... 'returns': ['RxFilterInfo'] }
> >   ... 'returns': 'ChardevReturn' }
> >
> >   We returns str (native type), list or extended dict here. Sometimes we
> >   returns a defined type, but it doesn't need to be extended. And we need
> >   to describe this kind of data (type str, dict or list) by "DataObject"
> >   in schema definition of DataObject** type.
> >
> >   So I added a "'reference-type': 'String'" in DataObject union.
> >   list, dict will still be described by "DataObjectType"
> >
>  I guess you want to tip what type may be returned? It seems a bit too
> agressive, since the qapi-schema.json didn't tip that those types may
> be returned.

In command schema, the value of 'returns' tips the model of return data.
It can be string(defined type/union/enum/etc) or undefined list/dict.

> >You can find the draft patches here:
> >   https://github.com/kongove/qemu/commits/qmp-introspection
> >I will post the V3 when I finish the cleanup.
Amos Kong Dec. 23, 2013, 8:11 a.m. UTC | #19
On Fri, Dec 20, 2013 at 07:03:57PM +0100, Paolo Bonzini wrote:
> Il 20/12/2013 12:57, Amos Kong ha scritto:
> > { 'type': 'DataObjectBase',
> >   'data': { '*name': 'str', 'type': 'str' } }
> > { 'union': 'DataObjectMemberType',
> >   'discriminator': {},
> >   'data': { 'reference': 'str',
> >             'undefined': 'DataObject',
> >             'extend': 'DataObject' } }
> 
> What is the purpose of "undefined"?  I don't see any occurrence of any
> of "undefined" or "extend" in the sample.


| { 'type': 'VersionInfo',
|   'data': {'qemu': {'major': 'int', 'minor': 'int', 'micro': 'int'},
|           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

| {
|   "name": "VersionInfo", 
|   "type": "type", 
|   "data": [
|~      {
|~          "name": "qemu", 
|~          "optional": false, 
|~          "type": {
|~              "type": "undefined-struct", 
|~              "data": [
|~                  {
|~                      "name": "micro", 
|~                      "optional": false, 
|~                      "recursive": false, 
|~                      "type": "int"
|~                  }, 
|~                  {
|~                      "name": "minor", 
|~                      "optional": false, 
|~                      "recursive": false, 
|~                      "type": "int"
|~                  }, 
|~                  {
|~                      "name": "major", 
|~                      "optional": false, 
|~                      "recursive": false, 
|~                      "type": "int"
|~                  }
|~              ]
|~          }
|~      }, 

> > { 'type': 'DataObjectMember',
> >   'data': { 'type': 'DataObjectMemberType', '*name': 'str',
> >             '*optional': 'bool', '*recursive': 'bool' } }
> > { 'type': 'DataObjectCommand',
> >   'data': { '*data': [ 'DataObjectMember' ],
> >             '*returns': 'DataObject',
> >             '*gen': 'bool' } }
> > { 'type': 'DataObjectEnumeration',
> >   'data': { 'data': [ 'str' ] } }
> > { 'type': 'DataObjectType',
> >   'data': { 'data': [ 'DataObjectMember' ] } }
> > { 'type': 'DataObjectUndefinedStruct',
> 
> Perhaps Unnamed or Anonymous?
 
Anonymous is good.

> >   'data': { 'data': [ 'DataObjectMember' ] } }
> > { 'type': 'DataObjectUnion',
> >   'data': { 'data': [ 'DataObjectMember' ], '*base': 'str',
> >             '*discriminator': 'str' } }
> > { 'union': 'DataObject',
> >   'base': 'DataObjectBase',
> >   'discriminator': 'type',
> >   'data': {
> >     'command': 'DataObjectCommand',
> >     'enumeration': 'DataObjectEnumeration',
> >     'type': 'DataObjectType',
> >     'undefined-struct': 'DataObjectUndefinedStruct',
> >     'reference-type': 'String',
> >     'unionobj': 'DataObjectUnion' } }
> > { 'command': 'query-qmp-schema', 'returns': ['DataObject'] }
> 
> I think forcing expansion of everything that isn't unnamed/anonymous
> makes the schema much larger and unwieldy.  Otherwise looks great!

We want to provide more useful metadata, and used some enum/unions to
indicate the dynamic type.

In the output, some simple data is processed too unwieldy. In another
side, some complex data is described clearly. It's also caused by some
limitation of QAPI infrastructure.
 
> Paolo
diff mbox

Patch

diff --git a/docs/qmp-full-introspection.txt b/docs/qmp-full-introspection.txt
new file mode 100644
index 0000000..cc0fb80
--- /dev/null
+++ b/docs/qmp-full-introspection.txt
@@ -0,0 +1,143 @@ 
+= full introspection support for QMP =
+
+== Purpose ==
+
+Add a new interface to provide QMP schema information to management,
+the return data is a dynamical and nested dict/list, it contains
+the useful metadata to help management to check feature support,
+QMP commands detail, etc.
+
+== Usage ==
+
+Execute QMP command:
+
+  { "execute": "query-qmp-schema" }
+
+Returns:
+
+  { "return": [
+      {
+          "name": "query-name",
+          "type": "Command",
+          "returns": [
+              {
+                  "key": "*name",
+                  "type": "str"
+              }
+          ]
+      },
+      ...
+   }
+
+The whole schema information will be returned in one go, it contains
+commands and event. It doesn't support to be filtered by type or name.
+
+We have four types (ImageInfo, BlockStats, PciDeviceInfo, SchemaData)
+that uses themself in their own define data directly or indirectly,
+we will not repeatedly extend them to avoid dead loop.
+
+== more description of 'DataObject' type ==
+
+We use 'DataObject' to describe dynamical data struct, it might be
+nested dictionary, list or string.
+
+'DataObject' itself is a arbitrary and nested dictionary, the
+dictionary has three keys ('key', 'type', 'data'), 'key' and
+'data' are optional.
+
+* For describing Dictionary, we set the key to 'key', and set the
+  value to 'type'
+* For describing List, we don't set 'key', just set the value to
+  'type'
+* If the value of dictionary or list is non-native type, we extend
+  the non-native type to dictionary, set it to 'data',  and set the
+  non-native type's name to 'type'.
+* If the value of dictionary or list is dictionary or list, 'type'
+  won't be set.
+
+== examples ==
+
+1) Dict, value is native type
+{ 'id': 'str', ... }
+--------------------
+[
+    {
+        "key": "id",
+        "type": "str"
+    },
+    .....
+]
+
+2) Dict, value is defined types
+{ 'options': 'TpmTypeOptions' }
+-------------------------------
+[
+    {
+        "key": "options",
+        "type": "TpmTypeOptions",
+        "data": [
+            {
+                "key": "passthrough",
+                "type": "str",
+            }
+        ]
+    },
+    .....
+]
+
+3) List, value is native type
+['str', ... ]
+-------------
+[
+    {
+        "type": "str"
+    },
+    ....
+]
+
+4) List, value is defined types
+['TpmTypeOptions', ... ]
+------------------------
+[
+    {
+        "type": "TpmTypeOptions",
+        "data": [
+            {
+                "key": "passthrough",
+                "type": "str",
+            }
+        ]
+    },
+    .....
+]
+
+5) Dict, value is dictionary
+{ 'info': { 'age': 'init', ... } }
+-----------------------------
+[
+    {
+        "key": "info",
+        "data": [
+            {
+                "key": "age",
+                "type": "init",
+            },
+            ...
+        ]
+    },
+]
+
+6) Dict, value is list
+{ 'info': [ 'str', ... } }
+-----------------------------
+[
+    {
+        "key": "info",
+        "data": [
+            {
+                "type": "str",
+            },
+            ...
+        ]
+    },
+]
diff --git a/qapi-schema.json b/qapi-schema.json
index 7b9fef1..cf03391 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -3679,3 +3679,72 @@ 
             '*cpuid-input-ecx': 'int',
             'cpuid-register': 'X86CPURegister32',
             'features': 'int' } }
+
+##
+# @DataObject
+#
+# Details of a data object, it can be nested dictionary/list
+#
+# @key: #optional Data object key
+#
+# @type: Data object type name
+#
+# @optional: #optional whether the data object is optional
+#
+# @data: #optional DataObject list, can be a dictionary or list type
+#
+# Since: 1.6
+##
+{ 'type': 'DataObject',
+  'data': { '*key': 'str', '*type': 'str', '*optional': 'bool', '*data': ['DataObject'] } }
+
+##
+# @SchemaMetaType
+#
+# Possible meta types of a schema entry
+#
+# @Command: QMP command
+#
+# @Type: defined new data type
+#
+# @Enumeration: enumeration data type
+#
+# @Union: union data type
+#
+# Since: 1.6
+##
+{ 'enum': 'SchemaMetaType',
+  'data': ['Command', 'Type', 'Enumeration', 'Union'] }
+
+##
+# @SchemaEntry
+#
+# Details of schema items
+#
+# @type: Entry's type in string format
+#
+# @name: Entry name
+#
+# @data: #optional list of DataObject. This can have different meaning
+#        depending on the 'type' value. For example, for a QMP command,
+#        this member contains an argument listing. For an enumeration,
+#        it contains the enum's values and so on
+#
+# @returns: #optional list of DataObject, return data after executing
+#           QMP command
+#
+# Since: 1.6
+##
+{ 'type': 'SchemaEntry', 'data': { 'type': 'SchemaMetaType',
+  'name': 'str', '*data': ['DataObject'], '*returns': ['DataObject'] } }
+
+##
+# @query-qmp-schema
+#
+# Query QMP schema information
+#
+# Returns: list of @SchemaEntry. Returns an error if json string is invalid.
+#
+# Since: 1.6
+##
+{ 'command': 'query-qmp-schema', 'returns': ['SchemaEntry'] }
diff --git a/qmp-commands.hx b/qmp-commands.hx
index e075df4..e3cbe93 100644
--- a/qmp-commands.hx
+++ b/qmp-commands.hx
@@ -3047,3 +3047,42 @@  Example:
 <- { "return": {} }
 
 EQMP
+
+    {
+        .name       = "query-qmp-schema",
+        .args_type  = "",
+        .mhandler.cmd_new = qmp_marshal_input_query_qmp_schema,
+    },
+
+
+SQMP
+query-qmp-schema
+----------------
+
+query qmp schema information
+
+Return a json-object with the following information:
+
+- "name": qmp schema name (json-string)
+- "type": qmp schema type, it can be 'comand', 'type', 'enum', 'union', 'event'
+- "data": schema data (json-object, optional)
+- "returns": return data of qmp command (json-object, optional)
+
+Example:
+
+-> { "execute": "query-qmp-schema" }
+<- { "return": [
+       {
+           "name": "query-name",
+           "type": "Command",
+           "returns": [
+               {
+                    "key": "*name",
+                   "type": "str"
+               }
+           ]
+       }
+     ]
+   }
+
+EQMP
diff --git a/qmp.c b/qmp.c
index 4c149b3..3ace3a6 100644
--- a/qmp.c
+++ b/qmp.c
@@ -25,6 +25,8 @@ 
 #include "sysemu/blockdev.h"
 #include "qom/qom-qobject.h"
 #include "hw/boards.h"
+#include "qmp-schema.h"
+#include "qapi/qmp/qjson.h"
 
 NameInfo *qmp_query_name(Error **errp)
 {
@@ -486,6 +488,311 @@  CpuDefinitionInfoList *qmp_query_cpu_definitions(Error **errp)
     return arch_query_cpu_definitions(errp);
 }
 
+/*
+ * Use a string to record the visit path, type index of each node
+ * will be saved to the string, indexes are split by ':'.
+ */
+static char visit_path_str[1024];
+
+/* push the type index to visit_path_str */
+static void push_id(int id)
+{
+    char *end = strrchr(visit_path_str, ':');
+    char type_idx[256];
+    int num;
+
+    num = sprintf(type_idx, "%d:", id);
+
+    if (end) {
+        /* avoid overflow */
+        assert(end - visit_path_str + 1 + num < sizeof(visit_path_str));
+        sprintf(end + 1, "%d:", id);
+    } else {
+        sprintf(visit_path_str, "%d:", id);
+    }
+}
+
+/* pop the type index from visit_path_str */
+static void pop_id(void)
+{
+    char *p = strrchr(visit_path_str, ':');
+
+    assert(p != NULL);
+    *p = '\0';
+    p = strrchr(visit_path_str, ':');
+    if (p) {
+        *(p + 1) = '\0';
+    } else {
+        visit_path_str[0] = '\0';
+    }
+}
+
+static const char *qstring_copy_str(QObject *data)
+{
+    QString *qstr;
+
+    if (!data) {
+        return NULL;
+    }
+    qstr = qobject_to_qstring(data);
+    if (qstr) {
+        return qstring_get_str(qstr);
+    } else {
+        return NULL;
+    }
+}
+
+static DataObjectList *visit_qobj_dict(QObject *data);
+static DataObjectList *visit_qobj_list(QObject *data);
+
+/* extend defined type to json object */
+static DataObjectList *extend_type(const char* str)
+{
+    DataObjectList *data_list;
+    QObject *data;
+    QDict *qdict;
+    const QDictEntry *ent;
+    int i;
+
+    /* don't extend builtin types */
+    if (!strcmp(str, "str") || !strcmp(str, "int") ||
+        !strcmp(str, "number") || !strcmp(str, "bool") ||
+        !strcmp(str, "int8") || !strcmp(str, "int16") ||
+        !strcmp(str, "int32") || !strcmp(str, "int64") ||
+        !strcmp(str, "uint8") || !strcmp(str, "uint16") ||
+        !strcmp(str, "uint32") || !strcmp(str, "uint64")) {
+        return NULL;
+    }
+
+    for (i = 0; qmp_schema_table[i]; i++) {
+        data = qobject_from_json(qmp_schema_table[i]);
+        assert(data != NULL);
+
+        qdict = qobject_to_qdict(data);
+        assert(qdict != NULL);
+
+        ent = qdict_first(qdict);
+        if (!qdict_get(qdict, "enum") && !qdict_get(qdict, "type")
+            && !qdict_get(qdict, "union")) {
+            continue;
+        }
+
+        if (!strcmp(str, qstring_copy_str(ent->value))) {
+            char *start, *end;
+            char cur_idx[256];
+            char type_idx[256];
+
+            start = visit_path_str;
+            sprintf(type_idx, "%d", i);
+            while(start) {
+                end = strchr(start, ':');
+                if (!end) {
+                    break;
+                }
+                snprintf(cur_idx, end - start + 1, "%s", start);
+                start = end + 1;
+                /* if the type was already extended in parent node,
+                 * we don't extend it again to avoid dead loop. */
+                if (!strcmp(cur_idx, type_idx)) {
+                    return NULL;
+                }
+            }
+            /* push index to visit_path_str before extending */
+            push_id(i);
+
+            data = qdict_get(qdict, "data");
+            if(data) {
+                if (data->type->code == QTYPE_QDICT) {
+                    data_list = visit_qobj_dict(data);
+                } else if (data->type->code == QTYPE_QLIST) {
+                    data_list = visit_qobj_list(data);
+                }
+                /* pop index from visit_path_str after extending */
+                pop_id();
+
+                return data_list;
+            }
+        }
+    }
+
+    return NULL;
+}
+
+static DataObjectList *visit_qobj_list(QObject *data)
+{
+    DataObjectList *obj_list, *obj_last_entry, *obj_entry;
+    DataObject *obj_info;
+    const QListEntry *ent;
+    QList *qlist;
+
+    qlist = qobject_to_qlist(data);
+    assert(qlist != NULL);
+
+    obj_list = NULL;
+    for (ent = qlist_first(qlist); ent; ent = qlist_next(ent)) {
+        obj_info = g_malloc0(sizeof(*obj_info));
+        obj_entry = g_malloc0(sizeof(*obj_entry));
+        obj_entry->value = obj_info;
+        obj_entry->next = NULL;
+
+        if (!obj_list) {
+            obj_list = obj_entry;
+        } else {
+            obj_last_entry->next = obj_entry;
+        }
+        obj_last_entry = obj_entry;
+
+        if (ent->value->type->code == QTYPE_QDICT) {
+            obj_info->data = visit_qobj_dict(ent->value);
+        } else if (ent->value->type->code == QTYPE_QLIST) {
+            obj_info->data = visit_qobj_list(ent->value);
+        } else if (ent->value->type->code == QTYPE_QSTRING) {
+            obj_info->has_type = true;
+            obj_info->type = g_strdup(qstring_copy_str(ent->value));
+            obj_info->data = extend_type(qstring_copy_str(ent->value));
+        }
+        if (obj_info->data) {
+            obj_info->has_data = true;
+        }
+    }
+
+    return obj_list;
+}
+
+static DataObjectList *visit_qobj_dict(QObject *data)
+{
+    DataObjectList *obj_list, *obj_last_entry, *obj_entry;
+    DataObject *obj_info;
+    const QDictEntry *ent;
+    QDict *qdict;
+
+    qdict = qobject_to_qdict(data);
+    assert(qdict != NULL);
+
+    obj_list = NULL;
+    for (ent = qdict_first(qdict); ent; ent = qdict_next(qdict, ent)) {
+        obj_info = g_malloc0(sizeof(*obj_info));
+        obj_entry = g_malloc0(sizeof(*obj_entry));
+        obj_entry->value = obj_info;
+        obj_entry->next = NULL;
+
+        if (!obj_list) {
+            obj_list = obj_entry;
+        } else {
+            obj_last_entry->next = obj_entry;
+        }
+        obj_last_entry = obj_entry;
+
+        if (ent->key[0] == '*') {
+            obj_info->key = g_strdup(ent->key + 1);
+            obj_info->has_optional = true;
+            obj_info->optional = true;
+        } else {
+            obj_info->key = g_strdup(ent->key);
+        }
+        obj_info->has_key = true;
+
+        if (ent->value->type->code == QTYPE_QDICT) {
+            obj_info->data = visit_qobj_dict(ent->value);
+        } else if (ent->value->type->code == QTYPE_QLIST) {
+           obj_info->data = visit_qobj_list(ent->value);
+        } else if (ent->value->type->code == QTYPE_QSTRING) {
+            obj_info->has_type = true;
+            obj_info->type = g_strdup(qstring_copy_str(ent->value));
+            obj_info->data = extend_type(qstring_copy_str(ent->value));
+        }
+        if (obj_info->data) {
+            obj_info->has_data = true;
+        }
+    }
+
+    return obj_list;
+}
+
+SchemaEntryList *qmp_query_qmp_schema(Error **errp)
+{
+    SchemaEntryList *list, *last_entry, *entry;
+    SchemaEntry *info;
+    DataObjectList *obj_entry;
+    DataObject *obj_info;
+    QObject *data;
+    QDict *qdict;
+    int i;
+
+    list = NULL;
+    for (i = 0; qmp_schema_table[i]; i++) {
+        data = qobject_from_json(qmp_schema_table[i]);
+        assert(data != NULL);
+
+        qdict = qobject_to_qdict(data);
+        assert(qdict != NULL);
+
+        if (qdict_get(qdict, "command")) {
+            info = g_malloc0(sizeof(*info));
+            info->type = SCHEMA_META_TYPE_COMMAND;
+            info->name = strdup(qdict_get_str(qdict, "command"));
+        } else {
+            continue;
+        }
+
+        memset(visit_path_str, 0, sizeof(visit_path_str));
+        data = qdict_get(qdict, "data");
+        if (data) {
+            info->has_data = true;
+            if (data->type->code == QTYPE_QLIST) {
+                info->data = visit_qobj_list(data);
+            } else if (data->type->code == QTYPE_QDICT) {
+                info->data = visit_qobj_dict(data);
+            } else if (data->type->code == QTYPE_QSTRING) {
+                info->data = extend_type(qstring_get_str(qobject_to_qstring(data)));
+                if (!info->data) {
+                    obj_info = g_malloc0(sizeof(*obj_info));
+                    obj_entry = g_malloc0(sizeof(*obj_entry));
+                    obj_entry->value = obj_info;
+                    obj_info->has_type = true;
+                    obj_info->type = g_strdup(qdict_get_str(qdict, "data"));
+                    info->data = obj_entry;
+                }
+            } else {
+                abort();
+            }
+        }
+
+        memset(visit_path_str, 0, sizeof(visit_path_str));
+        data = qdict_get(qdict, "returns");
+        if (data) {
+            info->has_returns = true;
+            if (data->type->code == QTYPE_QLIST) {
+                info->returns = visit_qobj_list(data);
+            } else if (data->type->code == QTYPE_QDICT) {
+                info->returns = visit_qobj_dict(data);
+            } else if (data->type->code == QTYPE_QSTRING) {
+                info->returns = extend_type(qstring_copy_str(data));
+                if (!info->returns) {
+                    obj_info = g_malloc0(sizeof(*obj_info));
+                    obj_entry = g_malloc0(sizeof(*obj_entry));
+                    obj_entry->value = obj_info;
+                    obj_info->has_type = true;
+                    obj_info->type = g_strdup(qdict_get_str(qdict, "returns"));
+                    info->returns = obj_entry;
+                }
+            }
+        }
+
+        entry = g_malloc0(sizeof(DataObjectList *));
+        entry->value = info;
+        entry->next = NULL;
+        if (!list) {
+            list = entry;
+        } else {
+            last_entry->next = entry;
+        }
+        last_entry = entry;
+    }
+
+    return list;
+}
+
 void qmp_add_client(const char *protocol, const char *fdname,
                     bool has_skipauth, bool skipauth, bool has_tls, bool tls,
                     Error **errp)