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>
This commit is contained in:
parent
a1440ec3da
commit
3471af9c6c
|
@ -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',
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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 */
|
|
@ -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))
|
|
@ -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 */
|
|
@ -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)
|
Loading…
Reference in New Issue