# # Copyright (C) 2020 Google, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), # to deal in the Software without restriction, including without limitation # the rights to use, copy, modify, merge, publish, distribute, sublicense, # and/or sell copies of the Software, and to permit persons to whom the # Software is furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice (including the next # paragraph) shall be included in all copies or substantial portions of the # Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. # from mako.template import Template from mako import exceptions from collections import namedtuple from enum import IntEnum import os TRACEPOINTS = {} TRACEPOINTS_TOGGLES = {} class Tracepoint(object): """Class that represents all the information about a tracepoint """ def __init__(self, name, args=[], toggle_name=None, tp_struct=None, tp_print=None, tp_perfetto=None, tp_markers=None, tp_flags=[], need_cs_param=True): """Parameters: - name: the tracepoint name, a tracepoint function with the given name (prefixed by 'trace_') will be generated with the specied args (following a u_trace ptr). Calling this tracepoint will emit a trace, if tracing is enabled. - args: the tracepoint func args, an array of TracepointArg - tp_print: (optional) array of format string followed by expressions - tp_perfetto: (optional) driver provided callback which can generate perfetto events - tp_markers: (optional) driver provided printf-style callback which can generate CS markers, this requires 'need_cs_param' as the first param is the CS that the label should be emitted into - need_cs_param: whether tracepoint functions need an additional cs parameter. """ assert isinstance(name, str) assert isinstance(args, list) assert name not in TRACEPOINTS def needs_storage(a): if a.c_format is None: return False if a.is_indirect: return False return True self.name = name self.args = args # For storage data, include all the specified tp_struct by the caller # as well as arguments needing storage self.tp_struct = [] if tp_struct is not None: self.tp_struct += tp_struct self.tp_struct += [x for x in args if needs_storage(x)] # For printing, include all the arguments & tp_struct elements that # have a format printer self.tp_print = [x for x in args if x.c_format is not None] if tp_struct is not None: self.tp_print += [x for x in tp_struct if x.c_format is not None] self.has_variable_arg = False for arg in self.tp_struct: if arg.length_arg != None and not arg.length_arg.isdigit(): self.has_variable_arg = True break self.tp_print_custom = tp_print # Compute the offset of each indirect argument self.indirect_args = [x for x in args if x.is_indirect] indirect_sizes = [] for indirect in self.indirect_args: indirect.indirect_offset = ' + '.join(indirect_sizes) if len(indirect_sizes) > 0 else 0 indirect_sizes.append(f"sizeof({indirect.type}") self.tp_perfetto = tp_perfetto self.tp_markers = tp_markers self.tp_flags = tp_flags self.toggle_name = toggle_name self.need_cs_param = need_cs_param TRACEPOINTS[name] = self if toggle_name is not None and toggle_name not in TRACEPOINTS_TOGGLES: TRACEPOINTS_TOGGLES[toggle_name] = len(TRACEPOINTS_TOGGLES) def can_generate_print(self): return self.args is not None and len(self.args) > 0 def enabled_expr(self, trace_toggle_name): if trace_toggle_name is None: return "true" assert self.toggle_name is not None return "({0} & {1}_{2})".format(trace_toggle_name, trace_toggle_name.upper(), self.toggle_name.upper()) class TracepointArgStruct(): """Represents struct that is being passed as an argument """ def __init__(self, type, var, c_format=None, fields=[], is_indirect=False): """Parameters: - type: argument's C type. - var: name of the argument """ assert isinstance(type, str) assert isinstance(var, str) self.type = type self.var = var self.name = var self.is_indirect = is_indirect self.indirect_offset = 0 self.is_struct = True self.c_format = c_format self.fields = fields self.to_prim_type = None if self.is_indirect: self.func_param = f"struct u_trace_address {self.var}" else: self.func_param = f"{self.type} {self.var}" def value_expr(self, entry_name): ret = None if self.is_struct: if self.is_indirect: ret = ", ".join([f"__{self.name}->{f}" for f in self.fields]) else: ret = ", ".join([f"{entry_name}->{self.name}.{f}" for f in self.fields]) else: ret = f"{entry_name}->{self.name}" return ret class TracepointArg(object): """Class that represents either an argument being passed or a field in a struct """ def __init__(self, type, var, c_format=None, name=None, to_prim_type=None, length_arg=None, copy_func=None, is_indirect=False): """Parameters: - type: argument's C type. - var: either an argument name or a field in the struct - c_format: printf format to print the value. - name: (optional) name that will be used in intermidiate structs and will be displayed in output or perfetto, otherwise var will be used. - to_prim_type: (optional) C function to convert from arg's type to a type compatible with c_format. - length_arg: whether this argument is a variable length array """ assert isinstance(type, str) assert isinstance(var, str) self.type = type self.var = var self.c_format = c_format if name is None: name = var self.name = name self.to_prim_type = to_prim_type self.length_arg = length_arg self.copy_func = copy_func self.is_struct = False self.is_indirect = is_indirect self.indirect_offset = 0 if self.is_indirect: pass elif self.type == "str": if self.length_arg and self.length_arg.isdigit(): self.struct_member = f"char {self.name}[{length_arg} + 1]" else: self.struct_member = f"char {self.name}[0]" else: self.struct_member = f"{self.type} {self.name}" if self.is_indirect: self.func_param = f"struct u_trace_address {self.var}" elif self.type == "str": self.func_param = f"const char *{self.var}" else: self.func_param = f"{self.type} {self.var}" def value_expr(self, entry_name): if self.is_indirect: ret = f"*__{self.name}" else: ret = f"{entry_name}->{self.name}" if not self.is_struct and self.to_prim_type: ret = self.to_prim_type.format(ret) return ret HEADERS = [] class HeaderScope(IntEnum): HEADER = (1 << 0) SOURCE = (1 << 1) PERFETTO = (1 << 2) class Header(object): """Class that represents a header file dependency of generated tracepoints """ def __init__(self, hdr, scope=HeaderScope.HEADER): """Parameters: - hdr: the required header path """ assert isinstance(hdr, str) self.hdr = hdr self.scope = scope HEADERS.append(self) FORWARD_DECLS = [] class ForwardDecl(object): """Class that represents a forward declaration """ def __init__(self, decl): assert isinstance(decl, str) self.decl = decl FORWARD_DECLS.append(self) hdr_template = """\ /* Copyright (C) 2020 Google, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ <% guard_name = '_' + hdrname + '_H' %> #ifndef ${guard_name} #define ${guard_name} % for header in HEADERS: #include "${header.hdr}" % endfor #include "util/perf/u_trace.h" #ifdef __cplusplus extern "C" { #endif % for declaration in FORWARD_DECLS: ${declaration.decl}; % endfor % if trace_toggle_name is not None: enum ${trace_toggle_name.lower()} { % for toggle_name, config_id in TRACEPOINTS_TOGGLES.items(): ${trace_toggle_name.upper()}_${toggle_name.upper()} = 1ull << ${config_id}, % endfor }; extern uint64_t ${trace_toggle_name}; void ${trace_toggle_name}_config_variable(void); % endif % for trace_name, trace in TRACEPOINTS.items(): /* * ${trace_name} */ struct trace_${trace_name} { % for arg in trace.tp_struct: ${arg.struct_member}; % endfor % if len(trace.tp_struct) == 0: #ifdef __cplusplus /* avoid warnings about empty struct size mis-match in C vs C++.. * the size mis-match is harmless because (a) nothing will deref * the empty struct, and (b) the code that cares about allocating * sizeof(struct trace_${trace_name}) (and wants this to be zero * if there is no payload) is C */ uint8_t dummy; #endif % endif }; % if trace.tp_perfetto is not None: #ifdef HAVE_PERFETTO void ${trace.tp_perfetto}( ${ctx_param}, uint64_t ts_ns, uint16_t tp_idx, const void *flush_data, const struct trace_${trace_name} *payload, const void *indirect_data); #endif % endif void __trace_${trace_name}( struct u_trace *ut , enum u_trace_type enabled_traces % if trace.need_cs_param: , void *cs % endif % for arg in trace.args: , ${arg.func_param} % endfor ); static ALWAYS_INLINE void trace_${trace_name}( struct u_trace *ut % if trace.need_cs_param: , void *cs % endif % for arg in trace.args: , ${arg.func_param} % endfor ) { enum u_trace_type enabled_traces = p_atomic_read_relaxed(&ut->utctx->enabled_traces); if (!unlikely(enabled_traces != 0 && ${trace.enabled_expr(trace_toggle_name)})) return; __trace_${trace_name}( ut , enabled_traces % if trace.need_cs_param: , cs % endif % for arg in trace.args: , ${arg.var} % endfor ); } % endfor #ifdef __cplusplus } #endif #endif /* ${guard_name} */ """ src_template = """\ /* Copyright (C) 2020 Google, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include "${hdr}" % for header in HEADERS: #include "${header.hdr}" % endfor #define __NEEDS_TRACE_PRIV #include "util/u_debug.h" #include "util/perf/u_trace_priv.h" % if trace_toggle_name is not None: static const struct debug_control config_control[] = { % for toggle_name in TRACEPOINTS_TOGGLES.keys(): { "${toggle_name}", ${trace_toggle_name.upper()}_${toggle_name.upper()}, }, % endfor { NULL, 0, }, }; uint64_t ${trace_toggle_name} = 0; static void ${trace_toggle_name}_variable_once(void) { uint64_t default_value = 0 % for name in trace_toggle_defaults: | ${trace_toggle_name.upper()}_${name.upper()} % endfor ; ${trace_toggle_name} = parse_enable_string(getenv("${trace_toggle_name.upper()}"), default_value, config_control); } void ${trace_toggle_name}_config_variable(void) { static once_flag process_${trace_toggle_name}_variable_flag = ONCE_FLAG_INIT; call_once(&process_${trace_toggle_name}_variable_flag, ${trace_toggle_name}_variable_once); } % endif % for index, (trace_name, trace) in enumerate(TRACEPOINTS.items()): /* * ${trace_name} */ % if trace.can_generate_print(): static void __print_${trace_name}(FILE *out, const void *arg, const void *indirect) { % if len(trace.tp_struct) > 0: const struct trace_${trace_name} *__entry = (const struct trace_${trace_name} *)arg; % endif % for arg in trace.indirect_args: const ${arg.type} *__${arg.name} = (const ${arg.type} *) ((char *)indirect + ${arg.indirect_offset}); % endfor % if trace.tp_print_custom is not None: fprintf(out, "${trace.tp_print_custom[0]}\\n" % for arg in trace.tp_print_custom[1:]: , ${arg} % endfor % else: fprintf(out, "" % for arg in trace.tp_print: "${arg.name}=${arg.c_format}, " % endfor "\\n" % for arg in trace.tp_print: ,${arg.value_expr("__entry")} % endfor % endif ); } static void __print_json_${trace_name}(FILE *out, const void *arg, const void *indirect) { % if len(trace.tp_struct) > 0: const struct trace_${trace_name} *__entry = (const struct trace_${trace_name} *)arg; % endif % for arg in trace.indirect_args: const ${arg.type} *__${arg.var} = (const ${arg.type} *) ((char *)indirect + ${arg.indirect_offset}); % endfor % if trace.tp_print_custom is not None: fprintf(out, "\\"unstructured\\": \\"${trace.tp_print_custom[0]}\\"" % for arg in trace.tp_print_custom[1:]: , ${arg} % endfor % else: fprintf(out, "" % for arg in trace.tp_print: "\\"${arg.name}\\": \\"${arg.c_format}\\"" % if arg != trace.tp_print[-1]: ", " % endif % endfor % for arg in trace.tp_print: ,${arg.value_expr("__entry")} % endfor % endif ); } % else: #define __print_${trace_name} NULL #define __print_json_${trace_name} NULL % endif % if trace.tp_markers is not None: __attribute__((format(printf, 3, 4))) void ${trace.tp_markers}(struct u_trace_context *utctx, void *, const char *, ...); static void __emit_label_${trace_name}(struct u_trace_context *utctx, void *cs, struct trace_${trace_name} *entry) { ${trace.tp_markers}(utctx, cs, "${trace_name}(" % for idx,arg in enumerate(trace.tp_print): % if not arg.is_indirect: "${"," if idx != 0 else ""}${arg.name}=${arg.c_format}" % endif % endfor ")" % for arg in trace.tp_print: % if not arg.is_indirect: ,${arg.value_expr('entry')} % endif % endfor ); } % endif static const struct u_tracepoint __tp_${trace_name} = { "${trace_name}", ALIGN_POT(sizeof(struct trace_${trace_name}), 8), /* keep size 64b aligned */ 0 % for arg in trace.indirect_args: + sizeof(${arg.type}) % endfor , ${0 if len(trace.tp_flags) == 0 else " | ".join(trace.tp_flags)}, ${index}, __print_${trace_name}, __print_json_${trace_name}, % if trace.tp_perfetto is not None: #ifdef HAVE_PERFETTO (void (*)(void *pctx, uint64_t, uint16_t, const void *, const void *, const void *))${trace.tp_perfetto}, #endif % endif }; void __trace_${trace_name}( struct u_trace *ut , enum u_trace_type enabled_traces % if trace.need_cs_param: , void *cs % endif % for arg in trace.args: , ${arg.func_param} % endfor ) { struct trace_${trace_name} entry; % if len(trace.indirect_args) > 0: struct u_trace_address indirects[] = { % for arg in trace.indirect_args: ${arg.var}, % endfor }; uint8_t indirect_sizes[] = { % for arg in trace.indirect_args: sizeof(${arg.type}), % endfor }; % endif UNUSED struct trace_${trace_name} *__entry = enabled_traces & U_TRACE_TYPE_REQUIRE_QUEUING ? (struct trace_${trace_name} *)u_trace_appendv(ut, ${"cs," if trace.need_cs_param else "NULL,"} &__tp_${trace_name}, 0 % for arg in trace.tp_struct: % if arg.length_arg is not None and not arg.length_arg.isdigit(): + ${arg.length_arg} % endif % endfor , % if len(trace.indirect_args) > 0: ARRAY_SIZE(indirects), indirects, indirect_sizes % else: 0, NULL, NULL % endif ) : &entry; % for arg in trace.tp_struct: % if arg.copy_func is None: __entry->${arg.name} = ${arg.var}; % else: ${arg.copy_func}(__entry->${arg.name}, ${arg.var}, ${arg.length_arg}); % endif % endfor % if trace.tp_markers is not None: if (enabled_traces & U_TRACE_TYPE_MARKERS) __emit_label_${trace_name}(ut->utctx, cs, __entry); % endif } % endfor """ def utrace_generate(cpath, hpath, ctx_param, trace_toggle_name=None, trace_toggle_defaults=[]): """Parameters: - cpath: c file to generate. - hpath: h file to generate. - ctx_param: type of the first parameter to the perfetto vfuncs. - trace_toggle_name: (optional) name of the environment variable enabling/disabling tracepoints. - trace_toggle_defaults: (optional) list of tracepoints enabled by default. """ if cpath is not None: hdr = os.path.basename(cpath).rsplit('.', 1)[0] + '.h' with open(cpath, 'w', encoding='utf-8') as f: try: f.write(Template(src_template).render( hdr=hdr, ctx_param=ctx_param, trace_toggle_name=trace_toggle_name, trace_toggle_defaults=trace_toggle_defaults, HEADERS=[h for h in HEADERS if h.scope & HeaderScope.SOURCE], TRACEPOINTS=TRACEPOINTS, TRACEPOINTS_TOGGLES=TRACEPOINTS_TOGGLES)) except: print(exceptions.text_error_template().render()) if hpath is not None: hdr = os.path.basename(hpath) with open(hpath, 'w', encoding='utf-8') as f: try: f.write(Template(hdr_template).render( hdrname=hdr.rstrip('.h').upper(), ctx_param=ctx_param, trace_toggle_name=trace_toggle_name, HEADERS=[h for h in HEADERS if h.scope & HeaderScope.HEADER], FORWARD_DECLS=FORWARD_DECLS, TRACEPOINTS=TRACEPOINTS, TRACEPOINTS_TOGGLES=TRACEPOINTS_TOGGLES)) except: print(exceptions.text_error_template().render()) perfetto_utils_hdr_template = """\ /* * Copyright © 2021 Igalia S.L. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ <% guard_name = '_' + hdrname + '_H' %> #ifndef ${guard_name} #define ${guard_name} #include % for header in HEADERS: #include "${header.hdr}" % endfor UNUSED static const char *${basename}_names[] = { % for trace_name, trace in TRACEPOINTS.items(): "${trace_name}", % endfor }; % for trace_name, trace in TRACEPOINTS.items(): static void UNUSED trace_payload_as_extra_${trace_name}(perfetto::protos::pbzero::GpuRenderStageEvent *event, const struct trace_${trace_name} *payload, const void *indirect_data) { % if trace.tp_perfetto is not None and len(trace.tp_print) > 0: char buf[128]; % for arg in trace.tp_print: { auto data = event->add_extra_data(); data->set_name("${arg.name}"); % if arg.is_indirect: const ${arg.type}* __${arg.var} = (const ${arg.type}*)((uint8_t *)indirect_data + ${arg.indirect_offset}); % endif sprintf(buf, "${arg.c_format}", ${arg.value_expr("payload")}); data->set_value(buf); } % endfor % endif } % endfor #endif /* ${guard_name} */ """ def utrace_generate_perfetto_utils(hpath,basename="tracepoint"): if hpath is not None: hdr = os.path.basename(hpath) with open(hpath, 'w', encoding='utf-8') as f: try: f.write(Template(perfetto_utils_hdr_template).render( basename=basename, hdrname=hdr.rstrip('.h').upper(), HEADERS=[h for h in HEADERS if h.scope & HeaderScope.PERFETTO], TRACEPOINTS=TRACEPOINTS)) except: print(exceptions.text_error_template().render())