Am 17.01.2022 um 09:45 hat Vladimir Sementsov-Ogievskiy geschrieben:
> 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 <js...@redhat.com> wrote:
> > > > 
> > > > On Thu, Dec 23, 2021 at 6:08 AM Vladimir Sementsov-Ogievskiy
> > > > <vsement...@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 <vsement...@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.
It doesn't really. As I understand it, the main problem mcgen() solves is
outputting correctly indented C code when you don't have a single
string, but the code has to be split across multiple strings because of
conditions and loops.

In general, f-strings are a little bit nicer than the formatting to be
used with the % operator, but you'd still keep all those mcgen() calls
with code between them. They just wouldn't need **kwds any more.

Of course, in the specific case of outputting C code, f-strings would
actually be terrible because you'd have to double all braces, which tend
to occur a lot in C code. So in this case, they probably aren't a little
bit nicer, but a little bit uglier.

Kevin


Reply via email to