summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Clark <robdclark@chromium.org>2020-11-26 11:14:22 -0800
committerMarge Bot <eric+marge@anholt.net>2020-12-03 21:19:57 +0000
commit3471af9c6cfa1dfa46e9607910bd4febc31095d2 (patch)
tree5f5a99c561e508b0496d432f497f15c0286306f8
parenta1440ec3daaa4f95b5ce007157ae1371c39da5b6 (diff)
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 <robdclark@chromium.org> Acked-by: Antonio Caggiano <antonio.caggiano@collabora.com> Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/7818>
-rw-r--r--src/gallium/auxiliary/meson.build22
-rw-r--r--src/gallium/auxiliary/util/u_trace.c348
-rw-r--r--src/gallium/auxiliary/util/u_trace.h208
-rw-r--r--src/gallium/auxiliary/util/u_trace.py219
-rw-r--r--src/gallium/auxiliary/util/u_trace_priv.h54
-rw-r--r--src/gallium/auxiliary/util/u_tracepoints.py92
6 files changed, 943 insertions, 0 deletions
diff --git a/src/gallium/auxiliary/meson.build b/src/gallium/auxiliary/meson.build
index 5344393e75e..4eedc856635 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 00000000000..152ff33a82e
--- /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 <inttypes.h>
+
+#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 00000000000..76c81f310f6
--- /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 <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#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 00000000000..395768a6247
--- /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 00000000000..38f09d129dc
--- /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 <stdio.h>
+
+#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 00000000000..c20460f9c0c
--- /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)