pahole: Support multiple types for pretty printing
For now one has to specify them in the order they appear in the file,
i.e. for perf.data files where we have:
$ pahole --hex ~/bin/perf --header=perf_file_header < perf.data
{
.magic = 0x32454c4946524550,
.size = 0x68,
.attr_size = 0x88,
.attrs = {
.offset = 0x168,
.size = 0x220,
},
.data = {
.offset = 0x388,
.size = 0x306698,
},
.event_types = {
.offset = 0,
.size = 0,
},
.adds_features = { 0x16717ffc, 0, 0, 0 },
},
$
We need to ask for pretty printing the attrs then the data sections, as:
$ pahole ~/bin/perf --header=perf_file_header \
-C 'perf_file_attr(range=attrs),perf_event_header(range=data,sizeof,type,type_enum=perf_event_type+perf_user_event_type)' < perf.data
Notice that both types have the range= setting where in the header it
should find the instances of its respective types, the result for this
perf.data file:
$ perf evlist
instructions
cycles
cache-misses
dummy:HG
$
Those events have these attributes, which we'll match in the pahole
output for the header 'attrs' range:
$ perf evlist -v
instructions: size: 120, config: 0x1, { sample_period, sample_freq }: 4000, sample_type: IP|TID|TIME|ID|CPU|PERIOD, read_format: ID, disabled: 1, inherit: 1, freq: 1, sample_id_all: 1, exclude_guest: 1
cycles: size: 120, { sample_period, sample_freq }: 4000, sample_type: IP|TID|TIME|ID|CPU|PERIOD, read_format: ID, disabled: 1, inherit: 1, freq: 1, sample_id_all: 1, exclude_guest: 1
cache-misses: size: 120, config: 0x3, { sample_period, sample_freq }: 4000, sample_type: IP|TID|TIME|ID|CPU|PERIOD, read_format: ID, disabled: 1, inherit: 1, freq: 1, sample_id_all: 1, exclude_guest: 1
dummy:HG: type: 1, size: 120, config: 0x9, { sample_period, sample_freq }: 4000, sample_type: IP|TID|TIME|ID|CPU|PERIOD, read_format: ID, inherit: 1, mmap: 1, comm: 1, freq: 1, task: 1, sample_id_all: 1, mmap2: 1, comm_exec: 1, ksymbol: 1, bpf_event: 1
$
To make it more compact lets remove zeroed fields using grep:
$ pahole ~/bin/perf --header=perf_file_header -C 'perf_file_attr(range=attrs),perf_event_header(range=data,sizeof,type,type_enum=perf_event_type+perf_user_event_type)' < perf.data | grep -v '= 0,'
{
.attr = {
.size = 120,
.config = 1,
.sample_period = 4000,
.sample_freq = 4000,
.sample_type = 455,
.read_format = 4,
.disabled = 1,
.inherit = 1,
.freq = 1,
.sample_id_all = 1,
.exclude_guest = 1,
},
.ids = {
.offset = 104,
.size = 64,
},
},
{
.attr = {
.size = 120,
.sample_period = 4000,
.sample_freq = 4000,
.sample_type = 455,
.read_format = 4,
.disabled = 1,
.inherit = 1,
.freq = 1,
.sample_id_all = 1,
.exclude_guest = 1,
},
.ids = {
.offset = 168,
.size = 64,
},
},
{
.attr = {
.size = 120,
.config = 3,
.sample_period = 4000,
.sample_freq = 4000,
.sample_type = 455,
.read_format = 4,
.disabled = 1,
.inherit = 1,
.freq = 1,
.sample_id_all = 1,
.exclude_guest = 1,
},
.ids = {
.offset = 232,
.size = 64,
},
},
{
.attr = {
.type = 1,
.size = 120,
.config = 9,
.sample_period = 4000,
.sample_freq = 4000,
.sample_type = 455,
.read_format = 4,
.inherit = 1,
.mmap = 1,
.comm = 1,
.freq = 1,
.task = 1,
.sample_id_all = 1,
.mmap2 = 1,
.comm_exec = 1,
.ksymbol = 1,
.bpf_event = 1,
},
.ids = {
.offset = 296,
.size = 64,
},
},
{
.header = {
.type = PERF_RECORD_TIME_CONV,
.size = 32,
},
.time_shift = 31,
.time_mult = 1016798081,
.time_zero = 670877213069232,
},
{
.header = {
.type = PERF_RECORD_MMAP,
.misc = 1,
.size = 96,
},
.pid = -1,
.start = -1929379840,
.len = 14683553,
.pgoff = -1929379840,
.filename = "[kernel.kallsyms]_text",
},
{
.header = {
.type = PERF_RECORD_MMAP,
.misc = 1,
.size = 136,
},
.pid = -1,
.start = -1072852992,
.len = 139264,
.filename = "/lib/modules/5.7.8-200.fc32.x86_64/kernel/fs/fuse/fuse.ko.xz",
},
<SNIP>
{
.header = {
.type = PERF_RECORD_SAMPLE,
.misc = 1,
.size = 56,
},
.array = { -1927972873, 14267881360602, 671090228720656, 8685165, 7, 93802 },
},
{
.header = {
.type = PERF_RECORD_SAMPLE,
.misc = 1,
.size = 56,
},
.array = { -1928098583, 0, 671090229714951, 8685165, 7, 79438 },
},
{
.type = PERF_RECORD_FINISHED_ROUND,
.size = 8,
},
$
Validation is done all around:
$ pahole ~/bin/perf --header=paerf_file_header -C 'perf_file_attr(range=attrs),perf_event_header(range=data,sizeof,type,type_enum=perf_event_type+perf_user_event_type)' < perf.data | grep -v '= 0,'
pahole: --header_type=paerf_file_header not found
$
$ pahole ~/bin/perf --header=perf_file_header -C 'perf_file_atatr(range=attrs),perf_event_header(range=data,sizeof,type,type_enum=perf_event_type+perf_user_event_type)' < perf.data | grep -v '= 0,'
pahole: type 'perf_file_atatr' not found
$
$ pahole ~/bin/perf --header=perf_file_header -C 'perf_file_attr(range=atrs),perf_event_header(range=data,sizeof,type,type_enum=perf_event_type+perf_user_event_type)' < perf.data | grep -v '= 0,'
pahole: couldn't read the 'atrs.offset' member of 'perf_file_header' for evaluating range=atrs
$
$ pahole ~/bin/perf --header=perf_file_header -C 'perf_file_attr(range=attrs),perf_event_hader(range=data,sizeof,type,type_enum=perf_event_type+perf_user_event_type)' < perf.data | grep -v '= 0,'
pahole: type 'perf_event_hader' not found
$
$ pahole ~/bin/perf --header=perf_file_header -C 'perf_file_attr(range=attrs),perf_event_header(range=daata,sizeof,type,type_enum=perf_event_type+perf_user_event_type)' < perf.data | grep -v '= 0,' > /dev/null
pahole: couldn't read the 'daata.offset' member of 'perf_file_header' for evaluating range=daata
$
$ pahole ~/bin/perf --header=perf_file_header -C 'perf_file_attr(range=attrs),perf_event_header(range=data,sizeof=fads,type,type_enum=perf_event_type+perf_user_event_type)' < perf.data | grep -v '= 0,' > /dev/null
pahole: the sizeof member 'fads' wasn't found in the 'perf_event_header' type
pahole: type 'perf_event_header' not found or attributes not validated
$
The algorithm to find the types was improved to not fallback at the end,
but instead go on saving types that were found, which increases the
possibility of resolving all of them, at the end we just need to check
if all that is needed was found, printing relevant messages when this
isn't the case.
More to do, like pretty printing flags such as sample_type, etc.
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
pahole: Support multiple enums in type_enum=
See the pahole man page:
Some of the records are not found in 'type_enum=perf_event_type' so some of the
records don't get converted to a type that fully shows its contents. For perf
we know that those are in another enumeration, 'enum perf_user_event_type', so,
for these cases, we can create a 'virtual enum', i.e. the sum of two enums
and then get all those entries decoded and properly casted, first few records
with just 'enum perf_event_type':
$ pahole ~/bin/perf --header=perf_file_header --seek_bytes '$header.data.offset' --size_bytes='$header.data.size' -C 'perf_event_header(sizeof,type,type_enum=perf_event_type)' --count 4 < perf.data
{
.type = 79,
.misc = 0,
.size = 32,
},
{
.type = 73,
.misc = 0,
.size = 40,
},
{
.type = 74,
.misc = 0,
.size = 32,
},
{
.header = {
.type = PERF_RECORD_CGROUP,
.misc = 0,
.size = 40,
},
.id = 1,
.path = "/",
},
$
Now with both enumerations, i.e. with 'type_enum=perf_event_type+perf_user_event_type':
$ pahole ~/bin/perf --header=perf_file_header --seek_bytes '$header.data.offset' --size_bytes='$header.data.size' -C 'perf_event_header(sizeof,type,type_enum=perf_event_type+perf_user_event_type)' --count 5 < perf.data
{
.header = {
.type = PERF_RECORD_TIME_CONV,
.misc = 0,
.size = 32,
},
.time_shift = 31,
.time_mult = 1016803377,
.time_zero = 435759009518382,
},
{
.header = {
.type = PERF_RECORD_THREAD_MAP,
.misc = 0,
.size = 40,
},
.nr = 1,
.entries = 0x50 0x7e 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00,
},
{
.header = {
.type = PERF_RECORD_CPU_MAP,
.misc = 0,
.size = 32,
},
.data = {
.type = 1,
.data = "",
},
},
{
.header = {
.type = PERF_RECORD_CGROUP,
.misc = 0,
.size = 40,
},
.id = 1,
.path = "/",
},
{
.header = {
.type = PERF_RECORD_CGROUP,
.misc = 0,
.size = 48,
},
.id = 1553,
.path = "/system.slice",
},
$
And since the fun never ends, this needs to be properly supported (arrays of
structs):
$ pahole ~/bin/perf -C perf_record_thread_map_entry
struct perf_record_thread_map_entry {
__u64 pid; /* 0 8 */
char comm[16]; /* 8 16 */
/* size: 24, cachelines: 1, members: 2 */
/* last cacheline: 24 bytes */
};
$
that 'nr' field:
$ pahole ~/bin/perf -C perf_record_thread_map
struct perf_record_thread_map {
struct perf_event_header header; /* 0 8 */
__u64 nr; /* 8 8 */
struct perf_record_thread_map_entry entries[]; /* 16 0 */
/* size: 16, cachelines: 1, members: 3 */
/* last cacheline: 16 bytes */
};
$
So probably we need something like a file with types and its pretty printing
details, with one for 'struct perf_record_thread_map':
perf_record_thread_map(entries.nr_entries=$nr)
Meaning: the perf_record_thread_map 'entries' has a number of
sizeof(typeof entries) that is defined in the perf_record_thread_map
'nr' member. Everything starting with a '$' needs to be evaluated, like
the '$header.*' we already have, since there is no 'namespace selector'
($header. in the header case) this means: the current record.
So, when pretty printing a record that has such type (perf_record_thread_map)
this has to be taken into account, and validated by the containing 'struct
perf_event_header->size' field.
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>