sources/perf: Capture the CRTC number and MSC for vblank events.

This will help make sense of vblank events in a multi-screen
environment (where there are two streams of vblanks), and hopefully
also useful for correlating to compositor events.
This commit is contained in:
Eric Anholt
2018-05-16 14:22:37 +01:00
committed by Christian Hergert
parent 88af69c3ec
commit a3bda35b28
2 changed files with 160 additions and 47 deletions

View File

@ -88,17 +88,30 @@ typedef struct
guint64 time; guint64 time;
guint64 n_ips; guint64 n_ips;
guint64 ips[0]; guint64 ips[0];
} SpPerfCounterEventSample; } SpPerfCounterEventCallchain;
typedef struct
{
struct perf_event_header header;
guint64 identifier;
guint64 ip;
guint32 pid;
guint32 tid;
guint64 time;
guint32 raw_size;
guchar raw[];
} SpPerfCounterEventTracepoint;
typedef union typedef union
{ {
struct perf_event_header header; struct perf_event_header header;
guint8 raw[0]; guint8 raw[0];
SpPerfCounterEventFork fork; SpPerfCounterEventFork fork;
SpPerfCounterEventComm comm; SpPerfCounterEventComm comm;
SpPerfCounterEventExit exit; SpPerfCounterEventExit exit;
SpPerfCounterEventMmap mmap; SpPerfCounterEventMmap mmap;
SpPerfCounterEventSample sample; SpPerfCounterEventCallchain callchain;
SpPerfCounterEventTracepoint tracepoint;
} SpPerfCounterEvent; } SpPerfCounterEvent;
#pragma pack(pop) #pragma pack(pop)

View File

@ -61,7 +61,8 @@ enum SpTracepoint {
typedef struct { typedef struct {
enum SpTracepoint tp; enum SpTracepoint tp;
const char *path; const char *path;
const char **fields;
} SpOptionalTracepoint; } SpOptionalTracepoint;
/* Global list of the optional tracepoints we might want to watch. */ /* Global list of the optional tracepoints we might want to watch. */
@ -77,7 +78,8 @@ static const SpOptionalTracepoint optional_tracepoints[] = {
* won't get the event since it comes in on an IRQ handler, not for * won't get the event since it comes in on an IRQ handler, not for
* our pid. * our pid.
*/ */
{ DRM_VBLANK, "drm/drm_vblank_event" }, { DRM_VBLANK, "drm/drm_vblank_event",
(const char *[]){ "crtc", "seq", NULL } },
}; };
/* Struct describing tracepoint events. /* Struct describing tracepoint events.
@ -89,6 +91,7 @@ static const SpOptionalTracepoint optional_tracepoints[] = {
*/ */
typedef struct { typedef struct {
enum SpTracepoint tp; enum SpTracepoint tp;
gsize field_offsets[0];
} SpTracepointDesc; } SpTracepointDesc;
struct _SpPerfSource struct _SpPerfSource
@ -172,53 +175,54 @@ do_emit_exited (gpointer data)
} }
static void static void
sp_perf_source_handle_tracepoint (SpPerfSource *self, sp_perf_source_handle_tracepoint (SpPerfSource *self,
gint cpu, gint cpu,
const SpPerfCounterEventSample *sample, const SpPerfCounterEventTracepoint *sample,
SpTracepointDesc *tp_desc) SpTracepointDesc *tp_desc)
{ {
switch (tp_desc->tp) gchar *message = NULL;
{
case DRM_VBLANK:
sp_capture_writer_add_mark (self->writer,
sample->time,
cpu,
sample->pid,
0,
"drm",
"vblank",
NULL);
break;
default: /* Note that field_offsets[] correspond to the
break; * SpOptionalTracepoint->fields[] strings. Yes, this is gross.
} */
switch (tp_desc->tp)
{
case DRM_VBLANK:
message = g_strdup_printf ("crtc=%d, seq=%u",
*(gint *)(sample->raw +
tp_desc->field_offsets[0]),
*(guint *)(sample->raw +
tp_desc->field_offsets[1]));
sp_capture_writer_add_mark (self->writer,
sample->time,
cpu,
sample->pid,
0,
"drm",
"vblank",
message);
break;
default:
break;
}
g_free (message);
} }
static void static void
sp_perf_source_handle_sample (SpPerfSource *self, sp_perf_source_handle_callchain (SpPerfSource *self,
gint cpu, gint cpu,
const SpPerfCounterEventSample *sample) const SpPerfCounterEventCallchain *sample)
{ {
const guint64 *ips; const guint64 *ips;
gint n_ips; gint n_ips;
guint64 trace[3]; guint64 trace[3];
SpTracepointDesc *tp_desc;
g_assert (SP_IS_PERF_SOURCE (self)); g_assert (SP_IS_PERF_SOURCE (self));
g_assert (sample != NULL); g_assert (sample != NULL);
/* We don't capture IPs with tracepoints, and get _RAW data instead. Handle
* them separately.
*/
tp_desc = g_hash_table_lookup (self->tracepoint_event_ids,
GINT_TO_POINTER (sample->identifier));
if (tp_desc)
{
sp_perf_source_handle_tracepoint (self, cpu, sample, tp_desc);
return;
}
ips = sample->ips; ips = sample->ips;
n_ips = sample->n_ips; n_ips = sample->n_ips;
@ -264,6 +268,7 @@ sp_perf_source_handle_event (SpPerfCounterEvent *event,
gpointer user_data) gpointer user_data)
{ {
SpPerfSource *self = user_data; SpPerfSource *self = user_data;
SpTracepointDesc *tp_desc;
gsize offset; gsize offset;
gint64 time; gint64 time;
@ -345,7 +350,21 @@ sp_perf_source_handle_event (SpPerfCounterEvent *event,
break; break;
case PERF_RECORD_SAMPLE: case PERF_RECORD_SAMPLE:
sp_perf_source_handle_sample (self, cpu, &event->sample); /* We don't capture IPs with tracepoints, and get _RAW data
* instead. Handle them separately.
*/
g_assert (&event->callchain.identifier == &event->tracepoint.identifier);
tp_desc = g_hash_table_lookup (self->tracepoint_event_ids,
GINT_TO_POINTER (event->callchain.identifier));
if (tp_desc)
{
sp_perf_source_handle_tracepoint (self, cpu, &event->tracepoint,
tp_desc);
}
else
{
sp_perf_source_handle_callchain (self, cpu, &event->callchain);
}
break; break;
case PERF_RECORD_THROTTLE: case PERF_RECORD_THROTTLE:
@ -381,6 +400,74 @@ sp_perf_get_tracepoint_config (const char *path, gint64 *config)
return TRUE; return TRUE;
} }
static gboolean
sp_perf_get_tracepoint_fields (SpTracepointDesc *tp_desc,
const SpOptionalTracepoint *optional_tp,
GError **error)
{
gchar *filename = NULL;
gchar *contents;
size_t len;
gint i;
filename = g_strdup_printf ("/sys/kernel/debug/tracing/events/%s/format",
optional_tp->path);
if (!filename)
return FALSE;
if (!g_file_get_contents (filename, &contents, &len, NULL))
{
g_free (filename);
return FALSE;
}
g_free (filename);
/* Look up our fields. Some example strings:
*
* field:unsigned short common_type; offset:0; size:2; signed:0;
* field:int crtc; offset:8; size:4; signed:1;
* field:unsigned int seq; offset:12; size:4; signed:0;
*/
for (i = 0; optional_tp->fields[i] != NULL; i++)
{
gchar *pattern = g_strdup_printf ("%s;\toffset:", optional_tp->fields[i]);
gchar *match;
gint64 offset;
match = strstr (contents, pattern);
if (!match)
{
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
_("Sysprof failed to find field '%s'."),
optional_tp->fields[i]);
g_free (contents);
return FALSE;
}
offset = g_ascii_strtoll (match + strlen (pattern),
NULL, 0);
if (offset == G_MININT64 && errno != 0)
{
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
_("Sysprof failed to parse offset for '%s'."),
optional_tp->fields[i]);
g_free (contents);
return FALSE;
}
tp_desc->field_offsets[i] = offset;
g_free (pattern);
}
g_free (contents);
return TRUE;
}
/* Adds a perf tracepoint event, if it's available. /* Adds a perf tracepoint event, if it's available.
* *
* These are kernel tracepoints that we want to include in our capture * These are kernel tracepoints that we want to include in our capture
@ -400,6 +487,7 @@ sp_perf_source_add_optional_tracepoint (SpPerfSource *self,
gint64 config; gint64 config;
gint64 id; gint64 id;
int ret; int ret;
gint num_fields;
if (!sp_perf_get_tracepoint_config(optional_tracepoint->path, &config)) if (!sp_perf_get_tracepoint_config(optional_tracepoint->path, &config))
return; return;
@ -409,7 +497,7 @@ sp_perf_source_add_optional_tracepoint (SpPerfSource *self,
| PERF_SAMPLE_IP | PERF_SAMPLE_IP
| PERF_SAMPLE_TID | PERF_SAMPLE_TID
| PERF_SAMPLE_IDENTIFIER | PERF_SAMPLE_IDENTIFIER
| PERF_SAMPLE_CALLCHAIN | PERF_SAMPLE_RAW
| PERF_SAMPLE_TIME; | PERF_SAMPLE_TIME;
attr.config = config; attr.config = config;
attr.sample_period = 1; attr.sample_period = 1;
@ -434,7 +522,12 @@ sp_perf_source_add_optional_tracepoint (SpPerfSource *self,
return; return;
} }
tp_desc = g_malloc (sizeof (*tp_desc)); /* The fields list is NULL-terminated, count how many are there. */
for (num_fields = 0; optional_tracepoint->fields[num_fields]; num_fields++)
;
tp_desc = g_malloc (sizeof (*tp_desc) +
sizeof(*tp_desc->field_offsets) * num_fields);
if (!tp_desc) if (!tp_desc)
{ {
close(fd); close(fd);
@ -443,6 +536,13 @@ sp_perf_source_add_optional_tracepoint (SpPerfSource *self,
tp_desc->tp = optional_tracepoint->tp; tp_desc->tp = optional_tracepoint->tp;
if (!sp_perf_get_tracepoint_fields (tp_desc, optional_tracepoint, error))
{
free(tp_desc);
close(fd);
return;
}
/* Here's where we should inspect the /format file to determine how /* Here's where we should inspect the /format file to determine how
* to pick fields out of the _RAW data. * to pick fields out of the _RAW data.
*/ */