qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
From: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
To: Stefan Hajnoczi <stefanha@redhat.com>, John Snow <jsnow@redhat.com>
Cc: qemu-devel <qemu-devel@nongnu.org>,
	Qemu-block <qemu-block@nongnu.org>,
	Michael Roth <michael.roth@amd.com>,
	Markus Armbruster <armbru@redhat.com>,
	Hanna Reitz <hreitz@redhat.com>, Kevin Wolf <kwolf@redhat.com>,
	Paolo Bonzini <pbonzini@redhat.com>,
	Philippe Mathieu Daude <philmd@redhat.com>
Subject: Re: [PATCH v2 2/4] scripts/qapi/commands: gen_commands(): add add_trace_points argument
Date: Mon, 17 Jan 2022 11:45:26 +0300	[thread overview]
Message-ID: <cef3a0e8-da08-f02b-8e9a-f12d963d18a8@virtuozzo.com> (raw)
In-Reply-To: <Yd6yhi96nErPW/rV@stefanha-x1.localdomain>

12.01.2022 13:50, Stefan Hajnoczi wrote:
> On Tue, Jan 11, 2022 at 07:32:52PM -0500, John Snow wrote:
>> On Tue, Jan 11, 2022 at 6:53 PM John Snow <jsnow@redhat.com> wrote:
>>>
>>> On Thu, Dec 23, 2021 at 6:08 AM Vladimir Sementsov-Ogievskiy
>>> <vsementsov@virtuozzo.com> wrote:
>>>>
>>>> Add possibility to generate trace points for each qmp command.
>>>>
>>>> We should generate both trace points and trace-events file, for further
>>>> trace point code generation.
>>>>
>>>> Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
>>>> ---
>>>>   scripts/qapi/commands.py | 84 ++++++++++++++++++++++++++++++++++------
>>>>   1 file changed, 73 insertions(+), 11 deletions(-)
>>>>
>>>> diff --git a/scripts/qapi/commands.py b/scripts/qapi/commands.py
>>>> index 21001bbd6b..9691c11f96 100644
>>>> --- a/scripts/qapi/commands.py
>>>> +++ b/scripts/qapi/commands.py
>>>> @@ -53,7 +53,8 @@ def gen_command_decl(name: str,
>>>>   def gen_call(name: str,
>>>>                arg_type: Optional[QAPISchemaObjectType],
>>>>                boxed: bool,
>>>> -             ret_type: Optional[QAPISchemaType]) -> str:
>>>> +             ret_type: Optional[QAPISchemaType],
>>>> +             add_trace_points: bool) -> str:
>>>>       ret = ''
>>>>
>>>>       argstr = ''
>>>> @@ -71,21 +72,65 @@ def gen_call(name: str,
>>>>       if ret_type:
>>>>           lhs = 'retval = '
>>>>
>>>> -    ret = mcgen('''
>>>> +    qmp_name = f'qmp_{c_name(name)}'
>>>> +    upper = qmp_name.upper()
>>>> +
>>>> +    if add_trace_points:
>>>> +        ret += mcgen('''
>>>> +
>>>> +    if (trace_event_get_state_backends(TRACE_%(upper)s)) {
>>>> +        g_autoptr(GString) req_json = qobject_to_json(QOBJECT(args));
>>>> +        trace_%(qmp_name)s("", req_json->str);
>>>> +    }
>>>> +    ''',
>>>> +                     upper=upper, qmp_name=qmp_name)
>>>> +
>>>> +    ret += mcgen('''
>>>>
>>>>       %(lhs)sqmp_%(c_name)s(%(args)s&err);
>>>> -    error_propagate(errp, err);
>>>>   ''',
>>>>                   c_name=c_name(name), args=argstr, lhs=lhs)
>>>> -    if ret_type:
>>>> -        ret += mcgen('''
>>>> +
>>>> +    ret += mcgen('''
>>>>       if (err) {
>>>> +''')
>>>> +
>>>> +    if add_trace_points:
>>>> +        ret += mcgen('''
>>>> +        trace_%(qmp_name)s("FAIL: ", error_get_pretty(err));
>>>> +''',
>>>> +                     qmp_name=qmp_name)
>>>> +
>>>> +    ret += mcgen('''
>>>> +        error_propagate(errp, err);
>>>>           goto out;
>>>>       }
>>>> +''')
>>>> +
>>>> +    if ret_type:
>>>> +        ret += mcgen('''
>>>>
>>>>       qmp_marshal_output_%(c_name)s(retval, ret, errp);
>>>>   ''',
>>>>                        c_name=ret_type.c_name())
>>>> +
>>>> +    if add_trace_points:
>>>> +        if ret_type:
>>>> +            ret += mcgen('''
>>>> +
>>>> +    if (trace_event_get_state_backends(TRACE_%(upper)s)) {
>>>> +        g_autoptr(GString) ret_json = qobject_to_json(*ret);
>>>> +        trace_%(qmp_name)s("RET:", ret_json->str);
>>>> +    }
>>>> +    ''',
>>>> +                     upper=upper, qmp_name=qmp_name)
>>>> +        else:
>>>> +            ret += mcgen('''
>>>> +
>>>> +    trace_%(qmp_name)s("SUCCESS", "");
>>>> +    ''',
>>>> +                         qmp_name=qmp_name)
>>>> +
>>>>       return ret
>>>>
>>>>
>>>> @@ -122,10 +167,14 @@ def gen_marshal_decl(name: str) -> str:
>>>>                    proto=build_marshal_proto(name))
>>>>
>>>>
>>>> +def gen_trace(name: str) -> str:
>>>> +    return f'qmp_{c_name(name)}(const char *tag, const char *json) "%s%s"\n'
>>>> +
>>>>   def gen_marshal(name: str,
>>>>                   arg_type: Optional[QAPISchemaObjectType],
>>>>                   boxed: bool,
>>>> -                ret_type: Optional[QAPISchemaType]) -> str:
>>>> +                ret_type: Optional[QAPISchemaType],
>>>> +                add_trace_points: bool) -> str:
>>>>       have_args = boxed or (arg_type and not arg_type.is_empty())
>>>>       if have_args:
>>>>           assert arg_type is not None
>>>> @@ -180,7 +229,7 @@ def gen_marshal(name: str,
>>>>       }
>>>>   ''')
>>>>
>>>> -    ret += gen_call(name, arg_type, boxed, ret_type)
>>>> +    ret += gen_call(name, arg_type, boxed, ret_type, add_trace_points)
>>>>
>>>>       ret += mcgen('''
>>>>
>>>> @@ -238,11 +287,12 @@ def gen_register_command(name: str,
>>>>
>>>>
>>>>   class QAPISchemaGenCommandVisitor(QAPISchemaModularCVisitor):
>>>> -    def __init__(self, prefix: str):
>>>> +    def __init__(self, prefix: str, add_trace_points: bool):
>>>>           super().__init__(
>>>>               prefix, 'qapi-commands',
>>>>               ' * Schema-defined QAPI/QMP commands', None, __doc__)
>>>>           self._visited_ret_types: Dict[QAPIGenC, Set[QAPISchemaType]] = {}
>>>> +        self.add_trace_points = add_trace_points
>>>>
>>>>       def _begin_user_module(self, name: str) -> None:
>>>>           self._visited_ret_types[self._genc] = set()
>>>> @@ -261,6 +311,15 @@ def _begin_user_module(self, name: str) -> None:
>>>>
>>>>   ''',
>>>>                                commands=commands, visit=visit))
>>>> +
>>>> +        if self.add_trace_points and c_name(commands) != 'qapi_commands':
>>>> +            self._genc.add(mcgen('''
>>>> +#include "trace/trace-qapi.h"
>>>> +#include "qapi/qmp/qjson.h"
>>>> +#include "trace/trace-%(nm)s_trace_events.h"
>>>> +''',
>>>> +                                 nm=c_name(commands)))
>>>> +
>>>>           self._genh.add(mcgen('''
>>>>   #include "%(types)s.h"
>>>>
>>>> @@ -322,7 +381,9 @@ def visit_command(self,
>>>>           with ifcontext(ifcond, self._genh, self._genc):
>>>>               self._genh.add(gen_command_decl(name, arg_type, boxed, ret_type))
>>>>               self._genh.add(gen_marshal_decl(name))
>>>> -            self._genc.add(gen_marshal(name, arg_type, boxed, ret_type))
>>>> +            self._genc.add(gen_marshal(name, arg_type, boxed, ret_type,
>>>> +                                       self.add_trace_points))
>>>> +            self._gent.add(gen_trace(name))
>>>>           with self._temp_module('./init'):
>>>>               with ifcontext(ifcond, self._genh, self._genc):
>>>>                   self._genc.add(gen_register_command(
>>>> @@ -332,7 +393,8 @@ def visit_command(self,
>>>>
>>>>   def gen_commands(schema: QAPISchema,
>>>>                    output_dir: str,
>>>> -                 prefix: str) -> None:
>>>> -    vis = QAPISchemaGenCommandVisitor(prefix)
>>>> +                 prefix: str,
>>>> +                 add_trace_points: bool) -> None:
>>>> +    vis = QAPISchemaGenCommandVisitor(prefix, add_trace_points)
>>>>       schema.visit(vis)
>>>>       vis.write(output_dir)
>>>> --
>>>> 2.31.1
>>>>
>>>
>>> I looked at Stefan's feedback and I want to second his recommendation
>>> to use _enter and _exit tracepoints, this closely resembles a lot of
>>> temporary debugging code I've written for jobs/bitmaps over the years
>>> and find the technique helpful.
>>>
>>> --js
>>>
>>> (as a tangent ...
>>>
>>> I wish I had a much nicer way of doing C generation from Python, this
>>> is so ugly. It's not your fault, of course. I'm just wondering if the
>>> mailing list has any smarter ideas for future improvements to the
>>> mcgen interface, because I find this type of code really hard to
>>> review.)
>>
>> Come to think of it, we could use a framework that was originally
>> designed for HTML templating, but for C instead. Wait! Don't close the
>> email yet, I have some funny things to write still!!
>>
>> Downsides:
>> - New template language
>> - Complex
>>
>> Pros:
>> - Easier to read
>> - Easier to review
>> - Avoids reinventing the wheel
>> - Doesn't even technically add a dependency, since Sphinx already
>> relies on Jinja ...
>> - *Extremely* funny
>>
>> As an example, let's say we had a file
>> "scripts/qapi/templates/qmp_marshal_output.c" that looked like this:
>> ```
>> static void qmp_marshal_output_{{c_name}}(
>>      {{c_type}} ret_in,
>>      QObject **ret_out,
>>      Error **errp
>> ) {
>>      Visitor *v;
>>
>>      v = qobject_output_visitor_new_qmp(ret_out);
>>      if (visit_type_{{c_name}}(v, "unused", &ret_in, errp)) {
>>          visit_complete(v, ret_out);
>>      }
>>      visit_free(v);
>>      v = qapi_dealloc_visitor_new();
>>      visit_type_{{c_name}}(v, "unused", &ret_in, NULL);
>>      visit_free(v);
>> }
>> ```
>>
>> We could use Jinja to process it from Python like this:
>>
>> ```
>> import os
>> from jinja2 import PackageLoader, Environment, FileSystemLoader
>>
>> env = Environment(loader = FileSystemLoader('./templates/'))
>> template = env.get_template("qmp_marshal_output.c")
>> rendered = cgen(template.render(
>>      c_name = "foo",
>>      c_type = "int",
>> ))
>>
>> print(rendered)
>> ```
>>
>> You'd get output like this:
>>
>> ```
>> static void qmp_marshal_output_foo(
>>      int ret_in,
>>      QObject **ret_out,
>>      Error **errp
>> ) {
>>      Visitor *v;
>>
>>      v = qobject_output_visitor_new_qmp(ret_out);
>>      if (visit_type_foo(v, "unused", &ret_in, errp)) {
>>          visit_complete(v, ret_out);
>>      }
>>      visit_free(v);
>>      v = qapi_dealloc_visitor_new();
>>      visit_type_foo(v, "unused", &ret_in, NULL);
>>      visit_free(v);
>> ```
>>
>> Jinja also supports conditionals and some other logic constructs, so
>> it'd *probably* apply to most templates we'd want to write, though
>> admittedly I haven't thought through if it'd actually work everywhere
>> we'd need it, and I am mostly having a laugh.
>>
>> ...OK, back to work!
> 
> I agree that mcgen string formatting is very hard to review.
> 
> I'm not sure if it's possible to separate the C templates into large
> chunks that are easy to read though. If there are many small C templates
> then it's hard to review and I think that's the core problem, not the
> string formatting/expansion mechanism (%(var)s vs Python {var} vs jinja2
> {{var}}).
> 
> If we could stick to Python standard library features instead of jinja2
> that would be nice because while jinja2 is powerful and widely-used,
> it's probably a bit too powerful and adds complexity/boilerplate that
> could be overkill in this situation.
> 
> Stefan
> 

I think that conversion to Python f-strings would be a good start: not very invasive, no extra dependencies, but may probably solve 60-80 % of the problem.

I thought about different templating techniques when creating scripts/block-coroutine-wrapper.py, but finally it just utilizes f-string (look at line 119 for example).


-- 
Best regards,
Vladimir


  reply	other threads:[~2022-01-17  8:47 UTC|newest]

Thread overview: 19+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-12-23 11:07 [PATCH v2 0/4] trace qmp commands Vladimir Sementsov-Ogievskiy
2021-12-23 11:07 ` [PATCH v2 1/4] jobs: drop qmp_ trace points Vladimir Sementsov-Ogievskiy
2022-01-10 16:05   ` Stefan Hajnoczi
2022-01-11 23:44     ` John Snow
2022-01-12 10:45       ` Stefan Hajnoczi
2021-12-23 11:07 ` [PATCH v2 2/4] scripts/qapi/commands: gen_commands(): add add_trace_points argument Vladimir Sementsov-Ogievskiy
2022-01-10 16:22   ` Stefan Hajnoczi
2022-01-17  8:46     ` Vladimir Sementsov-Ogievskiy
2022-01-11 23:53   ` John Snow
2022-01-12  0:32     ` John Snow
2022-01-12 10:50       ` Stefan Hajnoczi
2022-01-17  8:45         ` Vladimir Sementsov-Ogievskiy [this message]
2022-01-17  9:31           ` Kevin Wolf
2021-12-23 11:07 ` [PATCH v2 3/4] scripts/qapi-gen.py: add --add-trace-points option Vladimir Sementsov-Ogievskiy
2022-01-10 16:22   ` Stefan Hajnoczi
2022-01-12  0:38   ` John Snow
2022-01-17  8:50     ` Vladimir Sementsov-Ogievskiy
2021-12-23 11:07 ` [PATCH v2 4/4] meson: generate trace points for qmp commands Vladimir Sementsov-Ogievskiy
2022-01-10 16:27   ` Stefan Hajnoczi

Reply instructions:

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

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

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

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

  git send-email \
    --in-reply-to=cef3a0e8-da08-f02b-8e9a-f12d963d18a8@virtuozzo.com \
    --to=vsementsov@virtuozzo.com \
    --cc=armbru@redhat.com \
    --cc=hreitz@redhat.com \
    --cc=jsnow@redhat.com \
    --cc=kwolf@redhat.com \
    --cc=michael.roth@amd.com \
    --cc=pbonzini@redhat.com \
    --cc=philmd@redhat.com \
    --cc=qemu-block@nongnu.org \
    --cc=qemu-devel@nongnu.org \
    --cc=stefanha@redhat.com \
    /path/to/YOUR_REPLY

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

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).