Mesa (master): gallium/aux: Add GPU tracepoint mechanism

GitLab Mirror gitlab-mirror at kemper.freedesktop.org
Thu Dec 3 21:40:22 UTC 2020


Module: Mesa
Branch: master
Commit: 3471af9c6cfa1dfa46e9607910bd4febc31095d2
URL:    http://cgit.freedesktop.org/mesa/mesa/commit/?id=3471af9c6cfa1dfa46e9607910bd4febc31095d2

Author: Rob Clark <robdclark at chromium.org>
Date:   Thu Nov 26 11:14:22 2020 -0800

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 at chromium.org>
Acked-by: Antonio Caggiano <antonio.caggiano at collabora.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/7818>

---

 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(+)

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)



More information about the mesa-commit mailing list