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
next prev parent 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).