gallium/aux: Add perfetto support to u_trace
authorRob Clark <robdclark@chromium.org>
Mon, 22 Mar 2021 20:25:49 +0000 (13:25 -0700)
committerMarge Bot <eric+marge@anholt.net>
Mon, 10 May 2021 15:34:07 +0000 (15:34 +0000)
Not really direct perfetto support, but add a way that tracepoints can
be associated with a driver provided callback which can generate
perfetto events using the timestamps collected on the GPU.

Signed-off-by: Rob Clark <robdclark@chromium.org>
Acked-by: Emma Anholt <emma@anholt.net>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/9901>

src/gallium/auxiliary/util/u_trace.c
src/gallium/auxiliary/util/u_trace.h
src/gallium/auxiliary/util/u_trace.py
src/gallium/auxiliary/util/u_trace_priv.h

index 2d9b542..ab9eb32 100644 (file)
 #define TIMESTAMP_BUF_SIZE 0x1000
 #define TRACES_PER_CHUNK   (TIMESTAMP_BUF_SIZE / sizeof(uint64_t))
 
+#ifdef HAVE_PERFETTO
+int ut_perfetto_enabled;
+
+/**
+ * Global list of contexts, so we can defer starting the queue until
+ * perfetto tracing is started.
+ *
+ * TODO locking
+ */
+struct list_head ctx_list = { &ctx_list, &ctx_list };
+#endif
+
 struct u_trace_event {
    const struct u_tracepoint *tp;
    const void *payload;
@@ -164,6 +176,21 @@ get_tracefile(void)
    return tracefile;
 }
 
+static void
+queue_init(struct u_trace_context *utctx)
+{
+   if (utctx->queue.jobs)
+      return;
+
+   bool ret = util_queue_init(&utctx->queue, "traceq", 256, 1,
+                              UTIL_QUEUE_INIT_USE_MINIMUM_PRIORITY |
+                              UTIL_QUEUE_INIT_RESIZE_IF_FULL);
+   assert(ret);
+
+   if (!ret)
+      utctx->out = NULL;
+}
+
 void
 u_trace_context_init(struct u_trace_context *utctx,
       struct pipe_context *pctx,
@@ -182,21 +209,22 @@ u_trace_context_init(struct u_trace_context *utctx,
 
    utctx->out = get_tracefile();
 
-   if (!utctx->out)
-      return;
+#ifdef HAVE_PERFETTO
+   list_add(&utctx->node, &ctx_list);
+#endif
 
-   bool ret = util_queue_init(&utctx->queue, "traceq", 256, 1,
-         UTIL_QUEUE_INIT_USE_MINIMUM_PRIORITY |
-         UTIL_QUEUE_INIT_RESIZE_IF_FULL);
-   assert(ret);
+   if (!(utctx->out || ut_perfetto_enabled))
+      return;
 
-   if (!ret)
-      utctx->out = NULL;
+   queue_init(utctx);
 }
 
 void
 u_trace_context_fini(struct u_trace_context *utctx)
 {
+#ifdef HAVE_PERFETTO
+   list_del(&utctx->node);
+#endif
    if (!utctx->out)
       return;
    util_queue_finish(&utctx->queue);
@@ -205,6 +233,23 @@ u_trace_context_fini(struct u_trace_context *utctx)
    free_chunks(&utctx->flushed_trace_chunks);
 }
 
+#ifdef HAVE_PERFETTO
+void
+u_trace_perfetto_start(void)
+{
+   list_for_each_entry (struct u_trace_context, utctx, &ctx_list, node)
+      queue_init(utctx);
+   ut_perfetto_enabled++;
+}
+
+void
+u_trace_perfetto_stop(void)
+{
+   assert(ut_perfetto_enabled > 0);
+   ut_perfetto_enabled--;
+}
+#endif
+
 static void
 process_chunk(void *job, int thread_index)
 {
@@ -212,7 +257,7 @@ process_chunk(void *job, int thread_index)
    struct u_trace_context *utctx = chunk->utctx;
 
    /* For first chunk of batch, accumulated times will be zerod: */
-   if (!utctx->last_time_ns) {
+   if (utctx->out && !utctx->last_time_ns) {
       fprintf(utctx->out, "+----- NS -----+ +-- Δ --+  +----- MSG -----\n");
    }
 
@@ -236,23 +281,32 @@ process_chunk(void *job, int thread_index)
          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 (utctx->out) {
+         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);
+         }
       }
+#ifdef HAVE_PERFETTO
+      if (evt->tp->perfetto) {
+         evt->tp->perfetto(utctx->pctx, ns, evt->payload);
+      }
+#endif
    }
 
    if (chunk->last) {
-      uint64_t elapsed = utctx->last_time_ns - utctx->first_time_ns;
-      fprintf(utctx->out, "ELAPSED: %"PRIu64" ns\n", elapsed);
+      if (utctx->out) {
+         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) {
+   if (utctx->out && chunk->eof) {
       fprintf(utctx->out, "END OF FRAME %u\n", utctx->frame_nr++);
    }
 }
index 76c81f3..c44593d 100644 (file)
 
 #include "util/u_queue.h"
 
+#include "pipe/p_context.h"
+#include "pipe/p_state.h"
+
+#ifdef  __cplusplus
+extern "C" {
+#endif
+
 /* 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
@@ -121,6 +128,11 @@ struct u_trace_context {
     */
    struct util_queue queue;
 
+#ifdef HAVE_PERFETTO
+   /* node in global list of trace contexts. */
+   struct list_head node;
+#endif
+
    /* State to accumulate time across N chunks associated with a single
     * batch (u_trace).
     */
@@ -179,6 +191,15 @@ void u_trace_fini(struct u_trace *ut);
  */
 void u_trace_flush(struct u_trace *ut);
 
+#ifdef HAVE_PERFETTO
+extern int ut_perfetto_enabled;
+
+void u_trace_perfetto_start(void);
+void u_trace_perfetto_stop(void);
+#else
+#  define ut_perfetto_enabled 0
+#endif
+
 /*
  * 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
@@ -205,4 +226,8 @@ trace_framebuffer_state(struct u_trace *ut, const struct pipe_framebuffer_state
    }
 }
 
+#ifdef  __cplusplus
+}
+#endif
+
 #endif  /* _U_TRACE_H */
index 0b44191..9b1f87e 100644 (file)
@@ -29,7 +29,7 @@ TRACEPOINTS = {}
 class Tracepoint(object):
     """Class that represents all the information about a tracepoint
     """
-    def __init__(self, name, args=[], tp_struct=None, tp_print=None):
+    def __init__(self, name, args=[], tp_struct=None, tp_print=None, tp_perfetto=None):
         """Parameters:
 
         - name: the tracepoint name, a tracepoint function with the given
@@ -41,6 +41,8 @@ class Tracepoint(object):
           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
+        - tp_perfetto: (optional) driver provided callback which can generate
+          perfetto events
         """
         assert isinstance(name, str)
         assert isinstance(args, list)
@@ -54,6 +56,7 @@ class Tracepoint(object):
                 tp_struct.append([arg[0], arg[1], arg[1]])
         self.tp_struct = tp_struct
         self.tp_print = tp_print
+        self.tp_perfetto = tp_perfetto
 
         TRACEPOINTS[name] = self
 
@@ -105,7 +108,35 @@ hdr_template = """\
 
 #include "util/u_trace.h"
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 % for trace_name, trace in TRACEPOINTS.items():
+/*
+ * ${trace_name}
+ */
+struct trace_${trace_name} {
+%    for member in trace.tp_struct:
+         ${member[0]} ${member[1]};
+%    endfor
+%    if len(trace.tp_struct) == 0:
+#ifdef  __cplusplus
+     /* avoid warnings about empty struct size mis-match in C vs C++..
+      * the size mis-match is harmless because (a) nothing will deref
+      * the empty struct, and (b) the code that cares about allocating
+      * sizeof(struct trace_${trace_name}) (and wants this to be zero
+      * if there is no payload) is C
+      */
+     uint8_t dummy;
+#endif
+%    endif
+};
+%    if trace.tp_perfetto is not None:
+#ifdef HAVE_PERFETTO
+void ${trace.tp_perfetto}(struct pipe_context *pctx, uint64_t ts_ns, const struct trace_${trace_name} *payload);
+#endif
+%    endif
 void __trace_${trace_name}(struct u_trace *ut
 %    for arg in trace.args:
      , ${arg[0]} ${arg[1]}
@@ -116,7 +147,11 @@ static inline void trace_${trace_name}(struct u_trace *ut
      , ${arg[0]} ${arg[1]}
 %    endfor
 ) {
-   if (likely(!ut->enabled))
+%    if trace.tp_perfetto is not None:
+   if (!unlikely(ut->enabled || ut_perfetto_enabled))
+%    else:
+   if (!unlikely(ut->enabled))
+%    endif
       return;
    __trace_${trace_name}(ut
 %    for arg in trace.args:
@@ -126,6 +161,10 @@ static inline void trace_${trace_name}(struct u_trace *ut
 }
 % endfor
 
+#ifdef __cplusplus
+}
+#endif
+
 #endif /* ${guard_name} */
 """
 
@@ -165,15 +204,10 @@ src_template = """\
 /*
  * ${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;
+   const struct trace_${trace_name} *__entry =
+      (const struct trace_${trace_name} *)arg;
    fprintf(out, "${trace.tp_print[0]}\\n"
 %       for arg in trace.tp_print[1:]:
            , ${arg}
@@ -184,17 +218,22 @@ static void __print_${trace_name}(FILE *out, const void *arg) {
 #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 */
+    ALIGN_POT(sizeof(struct trace_${trace_name}), 8),   /* keep size 64b aligned */
     "${trace_name}",
     __print_${trace_name},
+%    if trace.tp_perfetto is not None:
+#ifdef HAVE_PERFETTO
+    (void (*)(struct pipe_context *, uint64_t, const void *))${trace.tp_perfetto},
+#endif
+%    endif
 };
 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});
+   struct trace_${trace_name} *__entry =
+      (struct trace_${trace_name} *)u_trace_append(ut, &__tp_${trace_name});
    (void)__entry;
 %    for member in trace.tp_struct:
         __entry->${member[1]} = ${member[2]};
index 38f09d1..3e764ed 100644 (file)
@@ -43,6 +43,12 @@ struct u_tracepoint {
    unsigned payload_sz;
    const char *name;
    void (*print)(FILE *out, const void *payload);
+#ifdef HAVE_PERFETTO
+   /**
+    * Callback to emit a perfetto event, such as render-stage trace
+    */
+   void (*perfetto)(struct pipe_context *pctx, uint64_t ts_ns, const void *payload);
+#endif
 };
 
 /**