From 3471af9c6cfa1dfa46e9607910bd4febc31095d2 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Thu, 26 Nov 2020 11:14:22 -0800 Subject: [PATCH] gallium/aux: Add GPU tracepoint mechanism This adds a mechanism, loosely inspired by the linux kernel's tracepoint mechanism, to declare and emit tracepoints. A driver provided callback is used to emit cmdstream to capture timestamps on the GPU, which are used to later "render" the emitted tracepoints. Signed-off-by: Rob Clark Acked-by: Antonio Caggiano Part-of: --- src/gallium/auxiliary/meson.build | 22 ++ src/gallium/auxiliary/util/u_trace.c | 348 ++++++++++++++++++++++++++++ src/gallium/auxiliary/util/u_trace.h | 208 +++++++++++++++++ src/gallium/auxiliary/util/u_trace.py | 219 +++++++++++++++++ src/gallium/auxiliary/util/u_trace_priv.h | 54 +++++ src/gallium/auxiliary/util/u_tracepoints.py | 92 ++++++++ 6 files changed, 943 insertions(+) create mode 100644 src/gallium/auxiliary/util/u_trace.c create mode 100644 src/gallium/auxiliary/util/u_trace.h create mode 100644 src/gallium/auxiliary/util/u_trace.py create mode 100644 src/gallium/auxiliary/util/u_trace_priv.h create mode 100644 src/gallium/auxiliary/util/u_tracepoints.py diff --git a/src/gallium/auxiliary/meson.build b/src/gallium/auxiliary/meson.build index 5344393..4eedc85 100644 --- a/src/gallium/auxiliary/meson.build +++ b/src/gallium/auxiliary/meson.build @@ -314,6 +314,9 @@ files_libgallium = files( 'util/u_texture.h', 'util/u_tile.c', 'util/u_tile.h', + 'util/u_trace.c', + 'util/u_trace.h', + 'util/u_trace_priv.h', 'util/u_transfer.c', 'util/u_transfer.h', 'util/u_transfer_helper.c', @@ -480,6 +483,25 @@ if with_dri2 and with_platform_x11 endif endif +u_trace_py = files('util/u_trace.py') + +files_u_tracepoints = custom_target( + 'u_tracepoints.[ch]', + input: 'util/u_tracepoints.py', + output: ['u_tracepoints.c', 'u_tracepoints.h'], + command: [ + prog_python, '@INPUT@', + '-p', join_paths(meson.source_root(), 'src/gallium/auxiliary/util/'), + '-C', '@OUTPUT0@', + '-H', '@OUTPUT1@', + ], + depend_files: u_trace_py, +) +files_libgallium += files_u_tracepoints +idep_u_tracepoints = declare_dependency( + sources: files_u_tracepoints, +) + u_indices_gen_c = custom_target( 'u_indices_gen.c', input : 'indices/u_indices_gen.py', diff --git a/src/gallium/auxiliary/util/u_trace.c b/src/gallium/auxiliary/util/u_trace.c new file mode 100644 index 0000000..152ff33 --- /dev/null +++ b/src/gallium/auxiliary/util/u_trace.c @@ -0,0 +1,348 @@ +/* + * Copyright © 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 + +#include "pipe/p_context.h" +#include "pipe/p_state.h" + +#include "util/list.h" +#include "util/ralloc.h" +#include "util/u_debug.h" +#include "util/u_inlines.h" + +#include "u_fifo.h" +#include "u_trace.h" + +#define __NEEDS_TRACE_PRIV +#include "u_trace_priv.h" + +#define TIMESTAMP_BUF_SIZE 0x1000 +#define TRACES_PER_CHUNK (TIMESTAMP_BUF_SIZE / sizeof(uint64_t)) + +struct u_trace_event { + const struct u_tracepoint *tp; + const void *payload; +}; + +/** + * A "chunk" of trace-events and corresponding timestamp buffer. As + * trace events are emitted, additional trace chucks will be allocated + * as needed. When u_trace_flush() is called, they are transferred + * from the u_trace to the u_trace_context queue. + */ +struct u_trace_chunk { + struct list_head node; + + struct u_trace_context *utctx; + + /* The number of traces this chunk contains so far: */ + unsigned num_traces; + + /* table of trace events: */ + struct u_trace_event traces[TRACES_PER_CHUNK]; + + /* table of driver recorded 64b timestamps, index matches index + * into traces table + */ + struct pipe_resource *timestamps; + + /** + * For trace payload, we sub-allocate from ralloc'd buffers which + * hang off of the chunk's ralloc context, so they are automatically + * free'd when the chunk is free'd + */ + uint8_t *payload_buf, *payload_end; + + struct util_queue_fence fence; + + bool last; /* this chunk is last in batch */ + bool eof; /* this chunk is last in frame */ +}; + +static void +free_chunk(void *ptr) +{ + struct u_trace_chunk *chunk = ptr; + + pipe_resource_reference(&chunk->timestamps, NULL); + + list_del(&chunk->node); +} + +static void +free_chunks(struct list_head *chunks) +{ + while (!list_is_empty(chunks)) { + struct u_trace_chunk *chunk = list_first_entry(chunks, + struct u_trace_chunk, node); + ralloc_free(chunk); + } +} + +static struct u_trace_chunk * +get_chunk(struct u_trace *ut) +{ + struct u_trace_chunk *chunk; + + /* do we currently have a non-full chunk to append msgs to? */ + if (!list_is_empty(&ut->trace_chunks)) { + chunk = list_last_entry(&ut->trace_chunks, + struct u_trace_chunk, node); + if (chunk->num_traces < TRACES_PER_CHUNK) + return chunk; + /* we need to expand to add another chunk to the batch, so + * the current one is no longer the last one of the batch: + */ + chunk->last = false; + } + + /* .. if not, then create a new one: */ + chunk = rzalloc_size(NULL, sizeof(*chunk)); + ralloc_set_destructor(chunk, free_chunk); + + chunk->utctx = ut->utctx; + + struct pipe_resource tmpl = { + .target = PIPE_BUFFER, + .format = PIPE_FORMAT_R8_UNORM, + .bind = PIPE_BIND_QUERY_BUFFER | PIPE_BIND_LINEAR, + .width0 = TIMESTAMP_BUF_SIZE, + .height0 = 1, + .depth0 = 1, + .array_size = 1, + }; + + struct pipe_screen *pscreen = ut->utctx->pctx->screen; + chunk->timestamps = pscreen->resource_create(pscreen, &tmpl); + + chunk->last = true; + + list_addtail(&chunk->node, &ut->trace_chunks); + + return chunk; +} + +DEBUG_GET_ONCE_BOOL_OPTION(trace, "GALLIUM_GPU_TRACE", false) +DEBUG_GET_ONCE_FILE_OPTION(trace_file, "GALLIUM_GPU_TRACEFILE", NULL, "w") + +static FILE * +get_tracefile(void) +{ + static FILE *tracefile = NULL; + static bool firsttime = true; + + if (firsttime) { + tracefile = debug_get_option_trace_file(); + if (!tracefile && debug_get_option_trace()) { + tracefile = stdout; + } + + firsttime = false; + } + + return tracefile; +} + +void +u_trace_context_init(struct u_trace_context *utctx, + struct pipe_context *pctx, + u_trace_record_ts record_timestamp, + u_trace_read_ts read_timestamp) +{ + utctx->pctx = pctx; + utctx->record_timestamp = record_timestamp; + utctx->read_timestamp = read_timestamp; + + utctx->last_time_ns = 0; + utctx->first_time_ns = 0; + utctx->frame_nr = 0; + + list_inithead(&utctx->flushed_trace_chunks); + + bool ret = util_queue_init(&utctx->queue, "traceq", 256, 1, + UTIL_QUEUE_INIT_USE_MINIMUM_PRIORITY | + UTIL_QUEUE_INIT_RESIZE_IF_FULL); + assert(ret); + + utctx->out = ret ? get_tracefile() : NULL; +} + +void +u_trace_context_fini(struct u_trace_context *utctx) +{ + util_queue_finish(&utctx->queue); + util_queue_destroy(&utctx->queue); + if (utctx->out) + fflush(utctx->out); + free_chunks(&utctx->flushed_trace_chunks); +} + +static void +process_chunk(void *job, int thread_index) +{ + struct u_trace_chunk *chunk = job; + struct u_trace_context *utctx = chunk->utctx; + + /* For first chunk of batch, accumulated times will be zerod: */ + if (!utctx->last_time_ns) { + fprintf(utctx->out, "+----- NS -----+ +-- Δ --+ +----- MSG -----\n"); + } + + for (unsigned idx = 0; idx < chunk->num_traces; idx++) { + const struct u_trace_event *evt = &chunk->traces[idx]; + + uint64_t ns = utctx->read_timestamp(utctx, chunk->timestamps, idx); + int32_t delta; + + if (!utctx->first_time_ns) + utctx->first_time_ns = ns; + + if (ns != U_TRACE_NO_TIMESTAMP) { + delta = utctx->last_time_ns ? ns - utctx->last_time_ns : 0; + utctx->last_time_ns = ns; + } else { + /* we skipped recording the timestamp, so it should be + * the same as last msg: + */ + ns = utctx->last_time_ns; + delta = 0; + } + + if (evt->tp->print) { + fprintf(utctx->out, "%016"PRIu64" %+9d: %s: ", ns, delta, evt->tp->name); + evt->tp->print(utctx->out, evt->payload); + } else { + fprintf(utctx->out, "%016"PRIu64" %+9d: %s\n", ns, delta, evt->tp->name); + } + } + + if (chunk->last) { + uint64_t elapsed = utctx->last_time_ns - utctx->first_time_ns; + fprintf(utctx->out, "ELAPSED: %"PRIu64" ns\n", elapsed); + + utctx->last_time_ns = 0; + utctx->first_time_ns = 0; + } + + if (chunk->eof) { + fprintf(utctx->out, "END OF FRAME %u\n", utctx->frame_nr++); + } +} + +static void +cleanup_chunk(void *job, int thread_index) +{ + ralloc_free(job); +} + +void +u_trace_context_process(struct u_trace_context *utctx, bool eof) +{ + struct list_head *chunks = &utctx->flushed_trace_chunks; + + if (list_is_empty(chunks)) + return; + + struct u_trace_chunk *last_chunk = list_last_entry(chunks, + struct u_trace_chunk, node); + last_chunk->eof = eof; + + while (!list_is_empty(chunks)) { + struct u_trace_chunk *chunk = list_first_entry(chunks, + struct u_trace_chunk, node); + + /* remove from list before enqueuing, because chunk is freed + * once it is processed by the queue: + */ + list_delinit(&chunk->node); + + util_queue_add_job(&utctx->queue, chunk, &chunk->fence, + process_chunk, cleanup_chunk, + TIMESTAMP_BUF_SIZE); + } +} + + +void +u_trace_init(struct u_trace *ut, struct u_trace_context *utctx) +{ + ut->utctx = utctx; + list_inithead(&ut->trace_chunks); + ut->enabled = !!utctx->out; +} + +void +u_trace_fini(struct u_trace *ut) +{ + /* Normally the list of trace-chunks would be empty, if they + * have been flushed to the trace-context. + */ + free_chunks(&ut->trace_chunks); +} + +/** + * Append a trace event, returning pointer to buffer of tp->payload_sz + * to be filled in with trace payload. Called by generated tracepoint + * functions. + */ +void * +u_trace_append(struct u_trace *ut, const struct u_tracepoint *tp) +{ + struct u_trace_chunk *chunk = get_chunk(ut); + + assert(tp->payload_sz == ALIGN_NPOT(tp->payload_sz, 8)); + + if (unlikely((chunk->payload_buf + tp->payload_sz) > chunk->payload_end)) { + const unsigned payload_chunk_sz = 0x100; /* TODO arbitrary size? */ + + assert(tp->payload_sz < payload_chunk_sz); + + chunk->payload_buf = ralloc_size(chunk, payload_chunk_sz); + chunk->payload_end = chunk->payload_buf + payload_chunk_sz; + } + + /* sub-allocate storage for trace payload: */ + void *payload = chunk->payload_buf; + chunk->payload_buf += tp->payload_sz; + + /* record a timestamp for the trace: */ + ut->utctx->record_timestamp(ut, chunk->timestamps, chunk->num_traces); + + chunk->traces[chunk->num_traces] = (struct u_trace_event) { + .tp = tp, + .payload = payload, + }; + + chunk->num_traces++; + + return payload; +} + +void +u_trace_flush(struct u_trace *ut) +{ + /* transfer batch's log chunks to context: */ + list_splicetail(&ut->trace_chunks, &ut->utctx->flushed_trace_chunks); + list_inithead(&ut->trace_chunks); +} diff --git a/src/gallium/auxiliary/util/u_trace.h b/src/gallium/auxiliary/util/u_trace.h new file mode 100644 index 0000000..76c81f3 --- /dev/null +++ b/src/gallium/auxiliary/util/u_trace.h @@ -0,0 +1,208 @@ +/* + * Copyright © 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. + */ + +#ifndef _U_TRACE_H +#define _U_TRACE_H + +#include +#include +#include + +#include "util/u_queue.h" + +/* A trace mechanism (very) loosely inspired by the linux kernel tracepoint + * mechanism, in that it allows for defining driver specific (or common) + * tracepoints, which generate 'trace_$name()' functions that can be + * called at various points in commandstream emit. + * + * Currently a printf backend is implemented, but the expectation is to + * also implement a perfetto backend for shipping out traces to a tool like + * AGI. + * + * Notable differences: + * + * - GPU timestamps! A driver provided callback is used to emit timestamps + * to a buffer. At a later point in time (when stalling to wait for the + * GPU is not required), the timestamps are re-united with the trace + * payload. This makes the trace mechanism suitable for profiling. + * + * - Instead of a systemwide trace ringbuffer, buffering of un-retired + * tracepoints is split into two stages. Traces are emitted to a + * 'u_trace' instance, and at a later time flushed to a 'u_trace_context' + * instance. This avoids the requirement that commandstream containing + * tracepoints is emitted in the same order as it is generated. + * + * If the hw has multiple parallel "engines" (for example, 3d/blit/compute) + * then a `u_trace_context` per-engine should be used. + * + * - Unlike kernel tracepoints, u_trace tracepoints are defined in py + * from which header and src files are generated. Since we already have + * a build dependency on python+mako, this gives more flexibility than + * clunky preprocessor macro magic. + * + */ + +struct u_trace_context; +struct u_trace; +struct u_trace_chunk; + +struct pipe_resource; + +/** + * Special reserved value to indicate that no timestamp was captured, + * and that the timestamp of the previous trace should be reused. + */ +#define U_TRACE_NO_TIMESTAMP ((uint64_t)0) + +/** + * Driver provided callback to emit commands to capture a 64b timestamp + * into the specified timestamps buffer, at the specified index. + * + * The hw counter that the driver records should be something that runs at + * a fixed rate, even as the GPU freq changes. The same source used for + * GL_TIMESTAMP queries should be appropriate. + */ +typedef void (*u_trace_record_ts)(struct u_trace *ut, + struct pipe_resource *timestamps, unsigned idx); + +/** + * Driver provided callback to read back a previously recorded timestamp. + * If necessary, this should block until the GPU has finished writing back + * the timestamps. (The timestamps will be read back in order, so it is + * safe to only synchronize on idx==0.) + * + * The returned timestamp should be in units of nanoseconds. The same + * timebase as GL_TIMESTAMP queries should be used. + * + * The driver can return the special U_TRACE_NO_TIMESTAMP value to indicate + * that no timestamp was captured and the timestamp from the previous trace + * will be re-used. (The first trace in the u_trace buf may not do this.) + * This allows the driver to detect cases where multiple tracepoints are + * emitted with no other intervening cmdstream, to avoid pointlessly + * capturing the same timestamp multiple times in a row. + */ +typedef uint64_t (*u_trace_read_ts)(struct u_trace_context *utctx, + struct pipe_resource *timestamps, unsigned idx); + +/** + * The trace context provides tracking for "in-flight" traces, once the + * cmdstream that records timestamps has been flushed. + */ +struct u_trace_context { + struct pipe_context *pctx; + u_trace_record_ts record_timestamp; + u_trace_read_ts read_timestamp; + + FILE *out; + + /* Once u_trace_flush() is called u_trace_chunk's are queued up to + * render tracepoints on a queue. The per-chunk queue jobs block until + * timestamps are available. + */ + struct util_queue queue; + + /* State to accumulate time across N chunks associated with a single + * batch (u_trace). + */ + uint64_t last_time_ns; + uint64_t first_time_ns; + + uint32_t frame_nr; + + /* list of unprocessed trace chunks in fifo order: */ + struct list_head flushed_trace_chunks; +}; + +/** + * The u_trace ptr is passed as the first arg to generated tracepoints. + * It provides buffering for tracepoint payload until the corresponding + * driver cmdstream containing the emitted commands to capture is + * flushed. + * + * Individual tracepoints emitted to u_trace are expected to be "executed" + * (ie. timestamp captured) in FIFO order with respect to other tracepoints + * emitted to the same u_trace. But the order WRT other u_trace instances + * is undefined util u_trace_flush(). + */ +struct u_trace { + struct u_trace_context *utctx; + + struct list_head trace_chunks; /* list of unflushed trace chunks in fifo order */ + + bool enabled; +}; + +void u_trace_context_init(struct u_trace_context *utctx, + struct pipe_context *pctx, + u_trace_record_ts record_timestamp, + u_trace_read_ts read_timestamp); +void u_trace_context_fini(struct u_trace_context *utctx); + +/** + * Flush (trigger processing) of traces previously flushed to the trace-context + * by u_trace_flush(). + * + * This should typically be called in the driver's pctx->flush(). + */ +void u_trace_context_process(struct u_trace_context *utctx, bool eof); + +void u_trace_init(struct u_trace *ut, struct u_trace_context *utctx); +void u_trace_fini(struct u_trace *ut); + +/** + * Flush traces to the parent trace-context. At this point, the expectation + * is that all the tracepoints are "executed" by the GPU following any previously + * flushed u_trace batch. + * + * This should typically be called when the corresponding cmdstream (containing + * the timestamp reads) is flushed to the kernel. + */ +void u_trace_flush(struct u_trace *ut); + +/* + * TODO in some cases it is useful to have composite tracepoints like this, + * to log more complex data structures.. but this is probably not where they + * should live: + */ + +void __trace_surface(struct u_trace *ut, const struct pipe_surface *psurf); +void __trace_framebuffer(struct u_trace *ut, const struct pipe_framebuffer_state *pfb); + +static inline void +trace_framebuffer_state(struct u_trace *ut, const struct pipe_framebuffer_state *pfb) +{ + if (likely(!ut->enabled)) + return; + + __trace_framebuffer(ut, pfb); + for (unsigned i = 0; i < pfb->nr_cbufs; i++) { + if (pfb->cbufs[i]) { + __trace_surface(ut, pfb->cbufs[i]); + } + } + if (pfb->zsbuf) { + __trace_surface(ut, pfb->zsbuf); + } +} + +#endif /* _U_TRACE_H */ diff --git a/src/gallium/auxiliary/util/u_trace.py b/src/gallium/auxiliary/util/u_trace.py new file mode 100644 index 0000000..395768a --- /dev/null +++ b/src/gallium/auxiliary/util/u_trace.py @@ -0,0 +1,219 @@ +# +# 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 +import os + +TRACEPOINTS = {} + +class Tracepoint(object): + """Class that represents all the information about a tracepoint + """ + def __init__(self, name, args=[], tp_struct=None, tp_print=None): + """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 [type, name] pairs + - tp_struct: (optional) array of [type, name, expr] tuples to + convert from tracepoint args to trace payload. If not specified + it will be generated from `args` (ie, [type, name, name]) + - tp_print: (optional) array of format string followed by expressions + """ + assert isinstance(name, str) + assert isinstance(args, list) + assert name not in TRACEPOINTS + + self.name = name + self.args = args + if tp_struct is None: + tp_struct = [] + for arg in args: + tp_struct.append([arg[0], arg[1], arg[1]]) + self.tp_struct = tp_struct + self.tp_print = tp_print + + TRACEPOINTS[name] = self + +HEADERS = [] + +class Header(object): + """Class that represents a header file dependency of generated tracepoints + """ + def __init__(self, hdr): + """Parameters: + + - hdr: the required header path + """ + assert isinstance(hdr, str) + self.hdr = hdr + + HEADERS.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/u_trace.h" + +% for trace_name, trace in TRACEPOINTS.items(): +void __trace_${trace_name}(struct u_trace *ut +% for arg in trace.args: + , ${arg[0]} ${arg[1]} +% endfor +); +static inline void trace_${trace_name}(struct u_trace *ut +% for arg in trace.args: + , ${arg[0]} ${arg[1]} +% endfor +) { + if (likely(!ut->enabled)) + return; + __trace_${trace_name}(ut +% for arg in trace.args: + , ${arg[1]} +% endfor + ); +} +% endfor + +#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. + */ + +% for header in HEADERS: +#include "${header.hdr}" +% endfor + +#include "${hdr}" + +#define __NEEDS_TRACE_PRIV +#include "util/u_trace_priv.h" + +% for trace_name, trace in TRACEPOINTS.items(): +/* + * ${trace_name} + */ +struct __payload_${trace_name} { +% for member in trace.tp_struct: + ${member[0]} ${member[1]}; +% endfor +}; +% if trace.tp_print is not None: +static void __print_${trace_name}(FILE *out, const void *arg) { + const struct __payload_${trace_name} *__entry = + (const struct __payload_${trace_name} *)arg; + fprintf(out, "${trace.tp_print[0]}\\n" +% for arg in trace.tp_print[1:]: + , ${arg} +% endfor + ); +} +% else: +#define __print_${trace_name} NULL +% endif +static const struct u_tracepoint __tp_${trace_name} = { + ALIGN_POT(sizeof(struct __payload_${trace_name}), 8), /* keep size 64b aligned */ + "${trace_name}", + __print_${trace_name}, +}; +void __trace_${trace_name}(struct u_trace *ut +% for arg in trace.args: + , ${arg[0]} ${arg[1]} +% endfor +) { + struct __payload_${trace_name} *__entry = + (struct __payload_${trace_name} *)u_trace_append(ut, &__tp_${trace_name}); + (void)__entry; +% for member in trace.tp_struct: + __entry->${member[1]} = ${member[2]}; +% endfor +} + +% endfor +""" + +def utrace_generate(cpath, hpath): + hdr = os.path.basename(hpath) + with open(cpath, 'wb') as f: + f.write(Template(src_template, output_encoding='utf-8').render( + hdr=hdr, + HEADERS=HEADERS, + TRACEPOINTS=TRACEPOINTS)) + + with open(hpath, 'wb') as f: + f.write(Template(hdr_template, output_encoding='utf-8').render( + hdrname=hdr.rstrip('.h').upper(), + HEADERS=HEADERS, + TRACEPOINTS=TRACEPOINTS)) diff --git a/src/gallium/auxiliary/util/u_trace_priv.h b/src/gallium/auxiliary/util/u_trace_priv.h new file mode 100644 index 0000000..38f09d1 --- /dev/null +++ b/src/gallium/auxiliary/util/u_trace_priv.h @@ -0,0 +1,54 @@ +/* + * Copyright © 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. + */ + +#ifndef __NEEDS_TRACE_PRIV +# error "Do not use this header!" +#endif + +#ifndef _U_TRACE_PRIV_H +#define _U_TRACE_PRIV_H + +#include + +#include "u_trace.h" + +/* + * Internal interface used by generated tracepoints + */ + +/** + * Tracepoint descriptor. + */ +struct u_tracepoint { + unsigned payload_sz; + const char *name; + void (*print)(FILE *out, const void *payload); +}; + +/** + * Append a tracepoint, returning pointer that can be filled with trace + * payload. + */ +void * u_trace_append(struct u_trace *ut, const struct u_tracepoint *tp); + +#endif /* _U_TRACE_PRIV_H */ diff --git a/src/gallium/auxiliary/util/u_tracepoints.py b/src/gallium/auxiliary/util/u_tracepoints.py new file mode 100644 index 0000000..c20460f --- /dev/null +++ b/src/gallium/auxiliary/util/u_tracepoints.py @@ -0,0 +1,92 @@ +# +# 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. +# + +import argparse +import sys + +# +# TODO can we do this with less boilerplate? +# +parser = argparse.ArgumentParser() +parser.add_argument('-p', '--import-path', required=True) +parser.add_argument('-C', '--src', required=True) +parser.add_argument('-H', '--hdr', required=True) +args = parser.parse_args() +sys.path.insert(0, args.import_path) + + +from u_trace import Header +from u_trace import Tracepoint +from u_trace import utrace_generate + +# +# Tracepoint definitions: +# + +Header('pipe/p_state.h') +Header('util/format/u_format.h') + +Tracepoint('surface', + args=[['const struct pipe_surface *', 'psurf']], + tp_struct=[['uint16_t', 'width', 'psurf->width'], + ['uint16_t', 'height', 'psurf->height'], + ['uint8_t', 'nr_samples', 'psurf->nr_samples'], + ['const char *', 'format', 'util_format_short_name(psurf->format)']], + tp_print=['%ux%u@%u, fmt=%s', + '__entry->width', + '__entry->height', + '__entry->nr_samples', + '__entry->format'], +) + +# Note: called internally from trace_framebuffer_state() +Tracepoint('framebuffer', + args=[['const struct pipe_framebuffer_state *', 'pfb']], + tp_struct=[['uint16_t', 'width', 'pfb->width'], + ['uint16_t', 'height', 'pfb->height'], + ['uint8_t', 'layers', 'pfb->layers'], + ['uint8_t', 'samples', 'pfb->samples'], + ['uint8_t', 'nr_cbufs', 'pfb->nr_cbufs']], + tp_print=['%ux%ux%u@%u, nr_cbufs: %u', + '__entry->width', + '__entry->height', + '__entry->layers', + '__entry->samples', + '__entry->nr_cbufs'], +) + +Tracepoint('grid_info', + args=[['const struct pipe_grid_info *', 'pgrid']], + tp_struct=[['uint8_t', 'work_dim', 'pgrid->work_dim'], + ['uint16_t', 'block_x', 'pgrid->block[0]'], + ['uint16_t', 'block_y', 'pgrid->block[1]'], + ['uint16_t', 'block_z', 'pgrid->block[2]'], + ['uint16_t', 'grid_x', 'pgrid->grid[0]'], + ['uint16_t', 'grid_y', 'pgrid->grid[1]'], + ['uint16_t', 'grid_z', 'pgrid->grid[2]']], + tp_print=['work_dim=%u, block=%ux%ux%u, grid=%ux%ux%u', '__entry->work_dim', + '__entry->block_x', '__entry->block_y', '__entry->block_z', + '__entry->grid_x', '__entry->grid_y', '__entry->grid_z'], +) + +utrace_generate(cpath=args.src, hpath=args.hdr) -- 2.7.4