2023-12-13 00:13:59

by Namhyung Kim

[permalink] [raw]
Subject: [PATCHSET 00/17] perf tools: Introduce data type profiling (v3)


Hello,

I'm happy to share my work on data type profiling. This is to associate
PMU samples to data types they refer using DWARF debug information. So
basically it depends on quality of PMU events and compiler for producing
DWARF info. But it doesn't require any changes in the target program.

As it's an early stage, I've targeted the kernel on x86 to reduce the
amount of work but IIUC there's no fundamental blocker to apply it to
other architectures and applications.


* v3 changes
- split the basic part and the remaining parts for easier review. And
I'd like to focus on the basic part first as it's less controversial.
- Protect from inappropriate access by checking init_annotation (Arnaldo)
- Show warnings when DWARF location support is not built (Athira)
- Some DWARF changes are merged in the perf-tools-next already

* v2 changes
- speed up analysis by not asking (unused) line number info to objdump
- support annotate a specific data type only by passing a type name like
`perf annotate --data-type=<TYPENAME>` (PeterZ)
- allow event group view to see multiple event results together like
`perf annotate --data-type --group` (PeterZ)
- rename to `die_get_typename_from_type()` (Masami)
- add a feature check for HAVE_DWARF_CFI_SUPPORT (Masami)
- add Acked-by tags from Masami


* How to use it

To get precise memory access samples, users can use `perf mem record`
command to utilize those events supported by their architecture. Intel
machines would work best as they have dedicated memory access events but
they would have a filter to ignore low latency loads like less than 30
cycles (use --ldlat option to change the default value).

# To get memory access samples in kernel for 1 second (on Intel)
$ sudo perf mem record -a -K --ldlat=1 -- sleep 1

# Similar for the AMD (but it requires 6.3+ kernel for BPF filters)
$ sudo perf mem record -a --filter 'mem_op == load || mem_op == store, ip > 0x8000000000000000' -- sleep 1

Note that it used 'sudo' command because it's collecting the event in
system wide mode. Actually it would depend on the sysctl setting of
kernel.perf_event_paranoid. AMD still needs root due to the BPF filter
though.

Actually users can use a different event as long as it gives precise
instruction addresses in samples. But the perf mem record will pick
up the available events which will give more information like data
source or latency (for advanced usage).

After getting a profile data, you would run perf report or perf
annotate as usual to see the result. Make sure that you have a kernel
debug package installed or vmlinux with DWARF info.

I've added new options and sort keys to enable the data type profiling.
Probably I need to add it to perf mem or perf c2c command for better
user experience. I'm open to discussion how we can make it simpler and
intuitive for regular users. But let's talk about the lower level
interface for now.

In perf report, it's just a matter of selecting new sort keys: 'type'
and 'typeoff'. The 'type' shows name of the data type as a whole while
'typeoff' shows name of the field in the data type. I found it useful
to use it with --hierarchy option to group relevant entries in the same
level.

Also, if you have both load and store events, pass --group option to
see the result together. This would give 3 output columns with a dummy
event. I think we should get rid of the dummy event after recording or
discard from the output at least.

$ sudo perf report -s type,typeoff --hierarchy --group --stdio
...
#
# Samples: 10K of events 'cpu/mem-loads,ldlat=4/P, cpu/mem-stores/P, dummy:u'
# Event count (approx.): 602758064
#
# Overhead Data Type / Data Type Offset
# ........................... ............................
#
26.09% 3.28% 0.00% long unsigned int
26.09% 3.28% 0.00% long unsigned int +0 (no field)
18.48% 0.73% 0.00% struct page
10.83% 0.02% 0.00% struct page +8 (lru.next)
3.90% 0.28% 0.00% struct page +0 (flags)
3.45% 0.06% 0.00% struct page +24 (mapping)
0.25% 0.28% 0.00% struct page +48 (_mapcount.counter)
0.02% 0.06% 0.00% struct page +32 (index)
0.02% 0.00% 0.00% struct page +52 (_refcount.counter)
0.02% 0.01% 0.00% struct page +56 (memcg_data)
0.00% 0.01% 0.00% struct page +16 (lru.prev)
15.37% 17.54% 0.00% (stack operation)
15.37% 17.54% 0.00% (stack operation) +0 (no field)
11.71% 50.27% 0.00% (unknown)
11.71% 50.27% 0.00% (unknown) +0 (no field)
...

The most frequently accessed type was long unsigned int and then the
struct page and you can see the second field (lru.next) at offset
8 was accessed mostly.

The (stack operation) and (unknown) have no type and field info. FYI,
the stack operations are samples in PUSH, POP or RET instructions which
save or restore registers from/to the stack. They are usually parts of
function prologue and epilogue and have no type info.

In perf annotate, new --data-type option was added to enable data
field level annotation. Now it only shows number of samples for each
field but we can improve it. The --data-type option optionally takes an
argument to specify the name of data type to display. Otherwise, it'd
display all data types having samples.

$ sudo perf annotate --data-type=page --group
Annotate type: 'struct page' in [kernel.kallsyms] (480 samples):
event[0] = cpu/mem-loads,ldlat=4/P
event[1] = cpu/mem-stores/P
event[2] = dummy:u
============================================================================
samples offset size field
447 33 0 0 64 struct page {
108 8 0 0 8 long unsigned int flags;
319 13 0 8 40 union {
319 13 0 8 40 struct {
236 2 0 8 16 union {
236 2 0 8 16 struct list_head lru {
236 1 0 8 8 struct list_head* next;
0 1 0 16 8 struct list_head* prev;
};
236 2 0 8 16 struct {
236 1 0 8 8 void* __filler;
0 1 0 16 4 unsigned int mlock_count;
};
236 2 0 8 16 struct list_head buddy_list {
236 1 0 8 8 struct list_head* next;
0 1 0 16 8 struct list_head* prev;
};
236 2 0 8 16 struct list_head pcp_list {
236 1 0 8 8 struct list_head* next;
0 1 0 16 8 struct list_head* prev;
};
};
82 4 0 24 8 struct address_space* mapping;
1 7 0 32 8 union {
1 7 0 32 8 long unsigned int index;
1 7 0 32 8 long unsigned int share;
};
0 0 0 40 8 long unsigned int private;
};
...

This shows each struct one by one and field-level access info in C-like
style. The number of samples for the outer struct is a sum of number of
samples in every field in the struct. In unions, each field is placed
in the same offset so they will have the same number of samples.

No TUI support yet.


* How it works

The basic idea is to use DWARF location expression in debug entries for
variables. Say we got a sample in the instruction below:

0x123456: mov 0x18(%rdi), %rcx

Then we know the instruction at 0x123456 is accessing to a memory region
where %rdi register has a base address and offset 0x18 from the base.
DWARF would have a debug info entry for a function or a block which
covers that address. For example, we might have something like this:

<1><100>: Abbrev Number: 10 (DW_TAG_subroutine_type)
<101> DW_AT_name : (indirect string, offset: 0x184e6): foo
<105> DW_AT_type : <0x29ad7>
<106> DW_AT_low_pc : 0x123400
<10e> DW_AT_high_pc : 0x1234ff
<2><116>: Abbrev Number: 8 (DW_TAG_formal_parameter)
<117> DW_AT_name : (indirect string, offset: 0x18527): bar
<11b> DW_AT_type : <0x29b3a>
<11c> DW_AT_location : 1 byte block: 55 (DW_OP_reg2 (rdi))

So the function 'foo' covers the instruction from 0x123400 to 0x1234ff
and we know the sample instruction belongs to the function. And it has
a parameter called 'bar' and it's located at the %rdi register. Then we
know the instruction is using the variable bar and its type would be a
pointer (to a struct). We can follow the type info of bar and verify
its access by checking the size of the (struct) type and offset in the
instruction (0x18).

Well.. this is a simple example that the 'bar' has a single location.
Other variables might be located in various places over time but it
should be covered by the location list of the debug entry. Therefore,
as long as DWARF produces a correct location expression for a variable,
it should be able to find the variable using the location info.

Global variables and local variables are different as they can be
accessed directly without a pointer. They are located in an absolute
address or relative position from the current stack frame. So it needs
to handle such location expressions as well. Note that this part is not
included in the basic patchset but you can find it in the full series of
this work later.

However, some memory accesses don't have a variable in some cases. For
example, you have a pointer variable for a struct which contains another
pointers. And then you can directly dereference it without using a
variable. Consider the following source code.

int foo(struct baz *bar) {
...
if (bar->p->q == 0)
return 1;
...
}

This can generate instructions like below.

...
0x123456: mov 0x18(%rdi), %rcx
0x12345a: mov 0x10(%rcx), %rax <=== sample
0x12345e: test %rax, %rax
0x123461: je <...>
...

And imagine we have a sample at 0x12345a. Then it cannot find a
variable for %rcx since DWARF didn't generate one (it only knows about
'bar'). Without compiler support, all it can do is to track the code
execution in each instruction and propagate the type info in each
register and stack location by following the memory access.

Actually I found a discussion in the DWARF mailing list to support
"inverted location lists" and it seems a perfect fit for this project.
It'd be great if new DWARF would provide a way to lookup variable and
type info using a concrete location info (like a register number).

https://lists.dwarfstd.org/pipermail/dwarf-discuss/2023-June/002278.html


* Patch structure

This is the basic part of this work which only handles the pointer type
variables found in the DWARF debug info. You can find the link to the
full version at the Summary section below.

The patch 1 and 2 are preparation for the next patches. The patch 3-7
are the main logic to find a matching DWARF variable and its type using
given instruction and its location. The patch 8 and 9 add 'type' sort
key to perf report. The patch 10-14 are to support type annotation and
also import perf report with new sort keys. The patch 15 enables event
group in perf annotate. The patch 16 and 17 shows some stats for debug.


* Limitations and future work

As I said earlier, this work is in a very early shape and has many
limitations or rooms for improvement. Basically it uses objdump tool to
extract location information from the sample instruction. And the
parsing code and instruction tracking work on x86 only.

Also I only tested it with C programs (mostly vmlinux) and I believe
there are many issues on handling C++ applications. Probably other
languages (like Rust?) could be supported too. But even for C programs,
it could improve things like better supporting union and array types and
dealing with type casts and so on.

I think compiler could generate more DWARF information to help this kind
of analysis. Like I mentioned, it doesn't have a variable for
intermediate pointers when they are chained: a->b->c. This chain could
be longer and hard to track the type from the previous variable. If
compiler could generate (artificial) debug entries for the intermediate
pointers with a precise location expression and type info, it would be
really helpful.

And I plan to improve the analysis in perf tools with better integration
to the existing command like perf mem and/or perf c2c. It'd be pretty
interesting to see per-struct or per-field access patterns both for load
and store event at the same time. Also using data-source or snoop info
for each struct/field would give some insights on optimizing memory
usage or layout.

There are kernel specific issues too. Some per-cpu variable accesses
created complex instruction patterns so it was hard to determine which
data/type it accessed. For now, it just parsed simple patterns for
this-cpu access using %gs segment register. Also it should handle
self-modifying codes like kprobe, ftrace, live patch and so on. I guess
they would usually create an out-of-line copy of modified instructions
but needs more checking. And I have no idea about the status of struct
layout randomization and the DWARF info of the resulting struct. Maybe
there are more issues I'm not aware of, please let me know if you notice
something.


* Summary

Despite all the issues, I believe this would be a good addition to our
performance toolset. It would help to observe memory overheads in a
different angle and to optimize the memory usage. I'm really looking
forward to hearing any feedback.

The code is available at 'perf/data-profile-basic-v3' branch in the tree
below. The full version of the code is in 'perf/data-profile-v3' branch.

git://git.kernel.org/pub/scm/linux/kernel/git/namhyung/linux-perf.git

Enjoy,
Namhyung


Cc: Ben Woodard <[email protected]>
Cc: Joe Mario <[email protected]>
CC: Kees Cook <[email protected]>
Cc: David Blaikie <[email protected]>
Cc: Xu Liu <[email protected]>
Cc: Kan Liang <[email protected]>
Cc: Ravi Bangoria <[email protected]>
Cc: Mark Wielaard <[email protected]>
Cc: Jason Merrill <[email protected]>
Cc: Jose E. Marchesi <[email protected]>
Cc: William Huang <[email protected]>


Namhyung Kim (17):
perf dwarf-aux: Factor out die_get_typename_from_type()
perf dwarf-regs: Add get_dwarf_regnum()
perf annotate-data: Add find_data_type()
perf annotate-data: Add dso->data_types tree
perf annotate: Factor out evsel__get_arch()
perf annotate: Add annotate_get_insn_location()
perf annotate: Implement hist_entry__get_data_type()
perf report: Add 'type' sort key
perf report: Support data type profiling
perf annotate-data: Add member field in the data type
perf annotate-data: Update sample histogram for type
perf report: Add 'typeoff' sort key
perf report: Add 'symoff' sort key
perf annotate: Add --data-type option
perf annotate: Support event group display
perf annotate: Add --type-stat option for debugging
perf annotate: Add --insn-stat option for debugging

tools/perf/Documentation/perf-annotate.txt | 11 +
tools/perf/Documentation/perf-report.txt | 3 +
tools/perf/arch/x86/util/dwarf-regs.c | 38 ++
tools/perf/builtin-annotate.c | 249 ++++++++++++-
tools/perf/builtin-report.c | 15 +-
tools/perf/util/Build | 1 +
tools/perf/util/annotate-data.c | 404 +++++++++++++++++++++
tools/perf/util/annotate-data.h | 143 ++++++++
tools/perf/util/annotate.c | 304 +++++++++++++++-
tools/perf/util/annotate.h | 50 +++
tools/perf/util/dso.c | 4 +
tools/perf/util/dso.h | 2 +
tools/perf/util/dwarf-aux.c | 38 +-
tools/perf/util/dwarf-aux.h | 3 +
tools/perf/util/dwarf-regs.c | 34 ++
tools/perf/util/hist.h | 3 +
tools/perf/util/include/dwarf-regs.h | 19 +
tools/perf/util/sort.c | 202 ++++++++++-
tools/perf/util/sort.h | 7 +
tools/perf/util/symbol_conf.h | 4 +-
20 files changed, 1502 insertions(+), 32 deletions(-)
create mode 100644 tools/perf/util/annotate-data.c
create mode 100644 tools/perf/util/annotate-data.h


base-commit: 8596ba324356a7392a6639024de8c9ae7a9fce92
--
2.43.0.472.g3155946c3a-goog


2023-12-13 00:14:23

by Namhyung Kim

[permalink] [raw]
Subject: [PATCH 07/17] perf annotate: Implement hist_entry__get_data_type()

It's the function to find out the type info from the given sample data
and will be called from the hist_entry sort logic when 'type' sort key
is used.

It first calls objdump to disassemble the instructions and figure out
information about memory access at the location. Maybe we can do it
better by analyzing the instruction directly, but I'll leave it for
later work.

The memory access is determined by checking instruction operands to
have "(" and then extract register name and offset. It'll return NULL
if no data type is found.

Signed-off-by: Namhyung Kim <[email protected]>
---
tools/perf/util/annotate.c | 88 ++++++++++++++++++++++++++++++++++++++
tools/perf/util/annotate.h | 4 ++
2 files changed, 92 insertions(+)

diff --git a/tools/perf/util/annotate.c b/tools/perf/util/annotate.c
index 7c597440dc2e..8673eac4b9df 100644
--- a/tools/perf/util/annotate.c
+++ b/tools/perf/util/annotate.c
@@ -25,6 +25,7 @@
#include "units.h"
#include "debug.h"
#include "annotate.h"
+#include "annotate-data.h"
#include "evsel.h"
#include "evlist.h"
#include "bpf-event.h"
@@ -3625,3 +3626,90 @@ int annotate_get_insn_location(struct arch *arch, struct disasm_line *dl,

return 0;
}
+
+static void symbol__ensure_annotate(struct map_symbol *ms, struct evsel *evsel)
+{
+ struct disasm_line *dl, *tmp_dl;
+ struct annotation *notes;
+
+ notes = symbol__annotation(ms->sym);
+ if (!list_empty(&notes->src->source))
+ return;
+
+ if (symbol__annotate(ms, evsel, NULL) < 0)
+ return;
+
+ /* remove non-insn disasm lines for simplicity */
+ list_for_each_entry_safe(dl, tmp_dl, &notes->src->source, al.node) {
+ if (dl->al.offset == -1) {
+ list_del(&dl->al.node);
+ free(dl);
+ }
+ }
+}
+
+static struct disasm_line *find_disasm_line(struct symbol *sym, u64 ip)
+{
+ struct disasm_line *dl;
+ struct annotation *notes;
+
+ notes = symbol__annotation(sym);
+
+ list_for_each_entry(dl, &notes->src->source, al.node) {
+ if (sym->start + dl->al.offset == ip)
+ return dl;
+ }
+ return NULL;
+}
+
+/**
+ * hist_entry__get_data_type - find data type for given hist entry
+ * @he: hist entry
+ *
+ * This function first annotates the instruction at @he->ip and extracts
+ * register and offset info from it. Then it searches the DWARF debug
+ * info to get a variable and type information using the address, register,
+ * and offset.
+ */
+struct annotated_data_type *hist_entry__get_data_type(struct hist_entry *he)
+{
+ struct map_symbol *ms = &he->ms;
+ struct evsel *evsel = hists_to_evsel(he->hists);
+ struct arch *arch;
+ struct disasm_line *dl;
+ struct annotated_insn_loc loc;
+ struct annotated_op_loc *op_loc;
+ u64 ip = he->ip;
+ int i;
+
+ if (ms->map == NULL || ms->sym == NULL)
+ return NULL;
+
+ if (!symbol_conf.init_annotation)
+ return NULL;
+
+ if (evsel__get_arch(evsel, &arch) < 0)
+ return NULL;
+
+ /* Make sure it runs objdump to get disasm of the function */
+ symbol__ensure_annotate(ms, evsel);
+
+ /*
+ * Get a disasm to extract the location from the insn.
+ * This is too slow...
+ */
+ dl = find_disasm_line(ms->sym, ip);
+ if (dl == NULL)
+ return NULL;
+
+ if (annotate_get_insn_location(arch, dl, &loc) < 0)
+ return NULL;
+
+ for_each_insn_op_loc(&loc, i, op_loc) {
+ if (!op_loc->mem_ref)
+ continue;
+
+ return find_data_type(ms, ip, op_loc->reg, op_loc->offset);
+ }
+ return NULL;
+}
diff --git a/tools/perf/util/annotate.h b/tools/perf/util/annotate.h
index 25ae8893d4f9..6c75b2832286 100644
--- a/tools/perf/util/annotate.h
+++ b/tools/perf/util/annotate.h
@@ -23,6 +23,7 @@ struct option;
struct perf_sample;
struct evsel;
struct symbol;
+struct annotated_data_type;

struct ins {
const char *name;
@@ -475,4 +476,7 @@ struct annotated_insn_loc {
int annotate_get_insn_location(struct arch *arch, struct disasm_line *dl,
struct annotated_insn_loc *loc);

+/* Returns a data type from the sample instruction (if any) */
+struct annotated_data_type *hist_entry__get_data_type(struct hist_entry *he);
+
#endif /* __PERF_ANNOTATE_H */
--
2.43.0.472.g3155946c3a-goog

2023-12-13 00:14:30

by Namhyung Kim

[permalink] [raw]
Subject: [PATCH 01/17] perf dwarf-aux: Factor out die_get_typename_from_type()

The die_get_typename_from_type() is to get the name of the given DIE in
C-style type name. The difference from the die_get_typename() is that
it does not retrieve the DW_AT_type and use the given DIE directly.
This will be used when users know the type DIE already.

Cc: Masami Hiramatsu <[email protected]>
Signed-off-by: Namhyung Kim <[email protected]>
---
tools/perf/util/dwarf-aux.c | 38 ++++++++++++++++++++++++++-----------
tools/perf/util/dwarf-aux.h | 3 +++
2 files changed, 30 insertions(+), 11 deletions(-)

diff --git a/tools/perf/util/dwarf-aux.c b/tools/perf/util/dwarf-aux.c
index edd9e407bc74..7aa5fee0da19 100644
--- a/tools/perf/util/dwarf-aux.c
+++ b/tools/perf/util/dwarf-aux.c
@@ -1051,32 +1051,28 @@ Dwarf_Die *die_find_member(Dwarf_Die *st_die, const char *name,
}

/**
- * die_get_typename - Get the name of given variable DIE
- * @vr_die: a variable DIE
+ * die_get_typename_from_type - Get the name of given type DIE
+ * @type_die: a type DIE
* @buf: a strbuf for result type name
*
- * Get the name of @vr_die and stores it to @buf. Return 0 if succeeded.
+ * Get the name of @type_die and stores it to @buf. Return 0 if succeeded.
* and Return -ENOENT if failed to find type name.
* Note that the result will stores typedef name if possible, and stores
* "*(function_type)" if the type is a function pointer.
*/
-int die_get_typename(Dwarf_Die *vr_die, struct strbuf *buf)
+int die_get_typename_from_type(Dwarf_Die *type_die, struct strbuf *buf)
{
- Dwarf_Die type;
int tag, ret;
const char *tmp = "";

- if (__die_get_real_type(vr_die, &type) == NULL)
- return -ENOENT;
-
- tag = dwarf_tag(&type);
+ tag = dwarf_tag(type_die);
if (tag == DW_TAG_array_type || tag == DW_TAG_pointer_type)
tmp = "*";
else if (tag == DW_TAG_subroutine_type) {
/* Function pointer */
return strbuf_add(buf, "(function_type)", 15);
} else {
- const char *name = dwarf_diename(&type);
+ const char *name = dwarf_diename(type_die);

if (tag == DW_TAG_union_type)
tmp = "union ";
@@ -1089,7 +1085,7 @@ int die_get_typename(Dwarf_Die *vr_die, struct strbuf *buf)
/* Write a base name */
return strbuf_addf(buf, "%s%s", tmp, name ?: "");
}
- ret = die_get_typename(&type, buf);
+ ret = die_get_typename(type_die, buf);
if (ret < 0) {
/* void pointer has no type attribute */
if (tag == DW_TAG_pointer_type && ret == -ENOENT)
@@ -1100,6 +1096,26 @@ int die_get_typename(Dwarf_Die *vr_die, struct strbuf *buf)
return strbuf_addstr(buf, tmp);
}

+/**
+ * die_get_typename - Get the name of given variable DIE
+ * @vr_die: a variable DIE
+ * @buf: a strbuf for result type name
+ *
+ * Get the name of @vr_die and stores it to @buf. Return 0 if succeeded.
+ * and Return -ENOENT if failed to find type name.
+ * Note that the result will stores typedef name if possible, and stores
+ * "*(function_type)" if the type is a function pointer.
+ */
+int die_get_typename(Dwarf_Die *vr_die, struct strbuf *buf)
+{
+ Dwarf_Die type;
+
+ if (__die_get_real_type(vr_die, &type) == NULL)
+ return -ENOENT;
+
+ return die_get_typename_from_type(&type, buf);
+}
+
/**
* die_get_varname - Get the name and type of given variable DIE
* @vr_die: a variable DIE
diff --git a/tools/perf/util/dwarf-aux.h b/tools/perf/util/dwarf-aux.h
index 0ddf61fd3f8b..4e64caac6df8 100644
--- a/tools/perf/util/dwarf-aux.h
+++ b/tools/perf/util/dwarf-aux.h
@@ -116,6 +116,9 @@ Dwarf_Die *die_find_variable_at(Dwarf_Die *sp_die, const char *name,
Dwarf_Die *die_find_member(Dwarf_Die *st_die, const char *name,
Dwarf_Die *die_mem);

+/* Get the name of given type DIE */
+int die_get_typename_from_type(Dwarf_Die *type_die, struct strbuf *buf);
+
/* Get the name of given variable DIE */
int die_get_typename(Dwarf_Die *vr_die, struct strbuf *buf);

--
2.43.0.472.g3155946c3a-goog

2023-12-13 00:14:42

by Namhyung Kim

[permalink] [raw]
Subject: [PATCH 06/17] perf annotate: Add annotate_get_insn_location()

The annotate_get_insn_location() is to get the detailed information of
instruction locations like registers and offset. It has source and
target operands locations in an array. Each operand can have a
register and an offset. The offset is meaningful when mem_ref flag is
set.

Signed-off-by: Namhyung Kim <[email protected]>
---
tools/perf/util/annotate.c | 107 +++++++++++++++++++++++++++++++++++++
tools/perf/util/annotate.h | 36 +++++++++++++
2 files changed, 143 insertions(+)

diff --git a/tools/perf/util/annotate.c b/tools/perf/util/annotate.c
index 27b2a9961cd5..7c597440dc2e 100644
--- a/tools/perf/util/annotate.c
+++ b/tools/perf/util/annotate.c
@@ -31,6 +31,7 @@
#include "bpf-utils.h"
#include "block-range.h"
#include "string2.h"
+#include "dwarf-regs.h"
#include "util/event.h"
#include "util/sharded_mutex.h"
#include "arch/common.h"
@@ -3518,3 +3519,109 @@ int annotate_check_args(void)
}
return 0;
}
+
+/*
+ * Get register number and access offset from the given instruction.
+ * It assumes AT&T x86 asm format like OFFSET(REG). Maybe it needs
+ * to revisit the format when it handles different architecture.
+ * Fills @reg and @offset when return 0.
+ */
+static int extract_reg_offset(struct arch *arch, const char *str,
+ struct annotated_op_loc *op_loc)
+{
+ char *p;
+ char *regname;
+
+ if (arch->objdump.register_char == 0)
+ return -1;
+
+ /*
+ * It should start from offset, but it's possible to skip 0
+ * in the asm. So 0(%rax) should be same as (%rax).
+ *
+ * However, it also start with a segment select register like
+ * %gs:0x18(%rbx). In that case it should skip the part.
+ */
+ if (*str == arch->objdump.register_char) {
+ while (*str && !isdigit(*str) &&
+ *str != arch->objdump.memory_ref_char)
+ str++;
+ }
+
+ op_loc->offset = strtol(str, &p, 0);
+
+ p = strchr(p, arch->objdump.register_char);
+ if (p == NULL)
+ return -1;
+
+ regname = strdup(p);
+ if (regname == NULL)
+ return -1;
+
+ op_loc->reg = get_dwarf_regnum(regname, 0);
+ free(regname);
+ return 0;
+}
+
+/**
+ * annotate_get_insn_location - Get location of instruction
+ * @arch: the architecture info
+ * @dl: the target instruction
+ * @loc: a buffer to save the data
+ *
+ * Get detailed location info (register and offset) in the instruction.
+ * It needs both source and target operand and whether it accesses a
+ * memory location. The offset field is meaningful only when the
+ * corresponding mem flag is set.
+ *
+ * Some examples on x86:
+ *
+ * mov (%rax), %rcx # src_reg = rax, src_mem = 1, src_offset = 0
+ * # dst_reg = rcx, dst_mem = 0
+ *
+ * mov 0x18, %r8 # src_reg = -1, dst_reg = r8
+ */
+int annotate_get_insn_location(struct arch *arch, struct disasm_line *dl,
+ struct annotated_insn_loc *loc)
+{
+ struct ins_operands *ops;
+ struct annotated_op_loc *op_loc;
+ int i;
+
+ if (!strcmp(dl->ins.name, "lock"))
+ ops = dl->ops.locked.ops;
+ else
+ ops = &dl->ops;
+
+ if (ops == NULL)
+ return -1;
+
+ memset(loc, 0, sizeof(*loc));
+
+ for_each_insn_op_loc(loc, i, op_loc) {
+ const char *insn_str = ops->source.raw;
+
+ if (i == INSN_OP_TARGET)
+ insn_str = ops->target.raw;
+
+ /* Invalidate the register by default */
+ op_loc->reg = -1;
+
+ if (insn_str == NULL)
+ continue;
+
+ if (strchr(insn_str, arch->objdump.memory_ref_char)) {
+ op_loc->mem_ref = true;
+ extract_reg_offset(arch, insn_str, op_loc);
+ } else {
+ char *s = strdup(insn_str);
+
+ if (s) {
+ op_loc->reg = get_dwarf_regnum(s, 0);
+ free(s);
+ }
+ }
+ }
+
+ return 0;
+}
diff --git a/tools/perf/util/annotate.h b/tools/perf/util/annotate.h
index 2ef7e7dda7bd..25ae8893d4f9 100644
--- a/tools/perf/util/annotate.h
+++ b/tools/perf/util/annotate.h
@@ -439,4 +439,40 @@ int annotate_parse_percent_type(const struct option *opt, const char *_str,

int annotate_check_args(void);

+/**
+ * struct annotated_op_loc - Location info of instruction operand
+ * @reg: Register in the operand
+ * @offset: Memory access offset in the operand
+ * @mem_ref: Whether the operand accesses memory
+ */
+struct annotated_op_loc {
+ int reg;
+ int offset;
+ bool mem_ref;
+};
+
+enum annotated_insn_ops {
+ INSN_OP_SOURCE = 0,
+ INSN_OP_TARGET = 1,
+
+ INSN_OP_MAX,
+};
+
+/**
+ * struct annotated_insn_loc - Location info of instruction
+ * @ops: Array of location info for source and target operands
+ */
+struct annotated_insn_loc {
+ struct annotated_op_loc ops[INSN_OP_MAX];
+};
+
+#define for_each_insn_op_loc(insn_loc, i, op_loc) \
+ for (i = INSN_OP_SOURCE, op_loc = &(insn_loc)->ops[i]; \
+ i < INSN_OP_MAX; \
+ i++, op_loc++)
+
+/* Get detailed location info in the instruction */
+int annotate_get_insn_location(struct arch *arch, struct disasm_line *dl,
+ struct annotated_insn_loc *loc);
+
#endif /* __PERF_ANNOTATE_H */
--
2.43.0.472.g3155946c3a-goog

2023-12-13 00:14:42

by Namhyung Kim

[permalink] [raw]
Subject: [PATCH 11/17] perf annotate-data: Update sample histogram for type

The annotated_data_type__update_samples() to get histogram for data type
access. It'll be called by perf annotate to show which fields in the
data type are accessed frequently.

Signed-off-by: Namhyung Kim <[email protected]>
---
tools/perf/util/annotate-data.c | 81 +++++++++++++++++++++++++++++++++
tools/perf/util/annotate-data.h | 42 +++++++++++++++++
tools/perf/util/annotate.c | 9 +++-
3 files changed, 131 insertions(+), 1 deletion(-)

diff --git a/tools/perf/util/annotate-data.c b/tools/perf/util/annotate-data.c
index d9c63e989b6e..9942a87b0664 100644
--- a/tools/perf/util/annotate-data.c
+++ b/tools/perf/util/annotate-data.c
@@ -12,6 +12,8 @@
#include "debuginfo.h"
#include "debug.h"
#include "dso.h"
+#include "evsel.h"
+#include "evlist.h"
#include "map.h"
#include "map_symbol.h"
#include "strbuf.h"
@@ -301,6 +303,44 @@ struct annotated_data_type *find_data_type(struct map_symbol *ms, u64 ip,
return result;
}

+static int alloc_data_type_histograms(struct annotated_data_type *adt, int nr_entries)
+{
+ int i;
+ size_t sz = sizeof(struct type_hist);
+
+ sz += sizeof(struct type_hist_entry) * adt->self.size;
+
+ /* Allocate a table of pointers for each event */
+ adt->nr_histograms = nr_entries;
+ adt->histograms = calloc(nr_entries, sizeof(*adt->histograms));
+ if (adt->histograms == NULL)
+ return -ENOMEM;
+
+ /*
+ * Each histogram is allocated for the whole size of the type.
+ * TODO: Probably we can move the histogram to members.
+ */
+ for (i = 0; i < nr_entries; i++) {
+ adt->histograms[i] = zalloc(sz);
+ if (adt->histograms[i] == NULL)
+ goto err;
+ }
+ return 0;
+
+err:
+ while (--i >= 0)
+ free(adt->histograms[i]);
+ free(adt->histograms);
+ return -ENOMEM;
+}
+
+static void delete_data_type_histograms(struct annotated_data_type *adt)
+{
+ for (int i = 0; i < adt->nr_histograms; i++)
+ free(adt->histograms[i]);
+ free(adt->histograms);
+}
+
void annotated_data_type__tree_delete(struct rb_root *root)
{
struct annotated_data_type *pos;
@@ -311,7 +351,48 @@ void annotated_data_type__tree_delete(struct rb_root *root)
rb_erase(node, root);
pos = rb_entry(node, struct annotated_data_type, node);
delete_members(&pos->self);
+ delete_data_type_histograms(pos);
free(pos->self.type_name);
free(pos);
}
}
+
+/**
+ * annotated_data_type__update_samples - Update histogram
+ * @adt: Data type to update
+ * @evsel: Event to update
+ * @offset: Offset in the type
+ * @nr_samples: Number of samples at this offset
+ * @period: Event count at this offset
+ *
+ * This function updates type histogram at @ofs for @evsel. Samples are
+ * aggregated before calling this function so it can be called with more
+ * than one samples at a certain offset.
+ */
+int annotated_data_type__update_samples(struct annotated_data_type *adt,
+ struct evsel *evsel, int offset,
+ int nr_samples, u64 period)
+{
+ struct type_hist *h;
+
+ if (adt == NULL)
+ return 0;
+
+ if (adt->histograms == NULL) {
+ int nr = evsel->evlist->core.nr_entries;
+
+ if (alloc_data_type_histograms(adt, nr) < 0)
+ return -1;
+ }
+
+ if (offset < 0 || offset >= adt->self.size)
+ return -1;
+
+ h = adt->histograms[evsel->core.idx];
+
+ h->nr_samples += nr_samples;
+ h->addr[offset].nr_samples += nr_samples;
+ h->period += period;
+ h->addr[offset].period += period;
+ return 0;
+}
diff --git a/tools/perf/util/annotate-data.h b/tools/perf/util/annotate-data.h
index 33748222e6aa..d2dc025b1934 100644
--- a/tools/perf/util/annotate-data.h
+++ b/tools/perf/util/annotate-data.h
@@ -7,6 +7,7 @@
#include <linux/rbtree.h>
#include <linux/types.h>

+struct evsel;
struct map_symbol;

/**
@@ -29,16 +30,42 @@ struct annotated_member {
int size;
};

+/**
+ * struct type_hist_entry - Histogram entry per offset
+ * @nr_samples: Number of samples
+ * @period: Count of event
+ */
+struct type_hist_entry {
+ int nr_samples;
+ u64 period;
+};
+
+/**
+ * struct type_hist - Type histogram for each event
+ * @nr_samples: Total number of samples in this data type
+ * @period: Total count of the event in this data type
+ * @offset: Array of histogram entry
+ */
+struct type_hist {
+ u64 nr_samples;
+ u64 period;
+ struct type_hist_entry addr[];
+};
+
/**
* struct annotated_data_type - Data type to profile
* @node: RB-tree node for dso->type_tree
* @self: Actual type information
+ * @nr_histogram: Number of histogram entries
+ * @histograms: An array of pointers to histograms
*
* This represents a data type accessed by samples in the profile data.
*/
struct annotated_data_type {
struct rb_node node;
struct annotated_member self;
+ int nr_histograms;
+ struct type_hist **histograms;
};

extern struct annotated_data_type unknown_type;
@@ -49,6 +76,11 @@ extern struct annotated_data_type unknown_type;
struct annotated_data_type *find_data_type(struct map_symbol *ms, u64 ip,
int reg, int offset);

+/* Update type access histogram at the given offset */
+int annotated_data_type__update_samples(struct annotated_data_type *adt,
+ struct evsel *evsel, int offset,
+ int nr_samples, u64 period);
+
/* Release all data type information in the tree */
void annotated_data_type__tree_delete(struct rb_root *root);

@@ -61,6 +93,16 @@ find_data_type(struct map_symbol *ms __maybe_unused, u64 ip __maybe_unused,
return NULL;
}

+static inline int
+annotated_data_type__update_samples(struct annotated_data_type *adt __maybe_unused,
+ struct evsel *evsel __maybe_unused,
+ int offset __maybe_unused,
+ int nr_samples __maybe_unused,
+ u64 period __maybe_unused)
+{
+ return -1;
+}
+
static inline void annotated_data_type__tree_delete(struct rb_root *root __maybe_unused)
{
}
diff --git a/tools/perf/util/annotate.c b/tools/perf/util/annotate.c
index 8673eac4b9df..6747779ecef8 100644
--- a/tools/perf/util/annotate.c
+++ b/tools/perf/util/annotate.c
@@ -3679,6 +3679,7 @@ struct annotated_data_type *hist_entry__get_data_type(struct hist_entry *he)
struct disasm_line *dl;
struct annotated_insn_loc loc;
struct annotated_op_loc *op_loc;
+ struct annotated_data_type *mem_type;
u64 ip = he->ip;
int i;

@@ -3709,7 +3710,13 @@ struct annotated_data_type *hist_entry__get_data_type(struct hist_entry *he)
if (!op_loc->mem_ref)
continue;

- return find_data_type(ms, ip, op_loc->reg, op_loc->offset);
+ mem_type = find_data_type(ms, ip, op_loc->reg, op_loc->offset);
+
+ annotated_data_type__update_samples(mem_type, evsel,
+ op_loc->offset,
+ he->stat.nr_events,
+ he->stat.period);
+ return mem_type;
}
return NULL;
}
--
2.43.0.472.g3155946c3a-goog

2023-12-13 00:14:44

by Namhyung Kim

[permalink] [raw]
Subject: [PATCH 09/17] perf report: Support data type profiling

Enable type annotation when the 'type' sort key is used.
It shows type of variables the samples access at the moment.
Users can see which types are accessed frequently.

$ perf report -s dso,type --stdio
...
# Overhead Shared Object Data Type
# ........ ................. .........
#
35.47% [kernel.kallsyms] (unknown)
1.62% [kernel.kallsyms] struct sched_entry
1.23% [kernel.kallsyms] struct cfs_rq
0.83% [kernel.kallsyms] struct task_struct
0.34% [kernel.kallsyms] struct list_head
0.30% [kernel.kallsyms] struct mem_cgroup
...

Signed-off-by: Namhyung Kim <[email protected]>
---
tools/perf/builtin-report.c | 15 +++++++++++++--
1 file changed, 13 insertions(+), 2 deletions(-)

diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c
index 17fb171e898b..ed76152c8db8 100644
--- a/tools/perf/builtin-report.c
+++ b/tools/perf/builtin-report.c
@@ -96,6 +96,7 @@ struct report {
bool stitch_lbr;
bool disable_order;
bool skip_empty;
+ bool data_type;
int max_stack;
struct perf_read_values show_threads_values;
const char *pretty_printing_style;
@@ -170,7 +171,7 @@ static int hist_iter__report_callback(struct hist_entry_iter *iter,
struct mem_info *mi;
struct branch_info *bi;

- if (!ui__has_annotation() && !rep->symbol_ipc)
+ if (!ui__has_annotation() && !rep->symbol_ipc && !rep->data_type)
return 0;

if (sort__mode == SORT_MODE__BRANCH) {
@@ -1619,6 +1620,16 @@ int cmd_report(int argc, const char **argv)
sort_order = NULL;
}

+ if (sort_order && strstr(sort_order, "type")) {
+ report.data_type = true;
+ annotate_opts.annotate_src = false;
+
+#ifndef HAVE_DWARF_GETLOCATIONS_SUPPORT
+ pr_err("Error: Data type profiling is disabled due to missing DWARF support\n");
+ goto error;
+#endif
+ }
+
if (strcmp(input_name, "-") != 0)
setup_browser(true);
else
@@ -1677,7 +1688,7 @@ int cmd_report(int argc, const char **argv)
* so don't allocate extra space that won't be used in the stdio
* implementation.
*/
- if (ui__has_annotation() || report.symbol_ipc ||
+ if (ui__has_annotation() || report.symbol_ipc || report.data_type ||
report.total_cycles_mode) {
ret = symbol__annotation_init();
if (ret < 0)
--
2.43.0.472.g3155946c3a-goog

2023-12-13 00:14:46

by Namhyung Kim

[permalink] [raw]
Subject: [PATCH 10/17] perf annotate-data: Add member field in the data type

Add child member field if the current type is a composite type like a
struct or union. The member fields are linked in the children list
and do the same recursively if the child itself is a composite type.
Add 'self' member to the annotated_data_type to handle the members in
the same way.

Signed-off-by: Namhyung Kim <[email protected]>
---
tools/perf/util/annotate-data.c | 101 ++++++++++++++++++++++++++++----
tools/perf/util/annotate-data.h | 27 +++++++--
tools/perf/util/sort.c | 9 ++-
3 files changed, 119 insertions(+), 18 deletions(-)

diff --git a/tools/perf/util/annotate-data.c b/tools/perf/util/annotate-data.c
index c9c359b4d4ad..d9c63e989b6e 100644
--- a/tools/perf/util/annotate-data.c
+++ b/tools/perf/util/annotate-data.c
@@ -30,9 +30,9 @@ static int data_type_cmp(const void *_key, const struct rb_node *node)

type = rb_entry(node, struct annotated_data_type, node);

- if (key->type_size != type->type_size)
- return key->type_size - type->type_size;
- return strcmp(key->type_name, type->type_name);
+ if (key->self.size != type->self.size)
+ return key->self.size - type->self.size;
+ return strcmp(key->self.type_name, type->self.type_name);
}

static bool data_type_less(struct rb_node *node_a, const struct rb_node *node_b)
@@ -42,9 +42,80 @@ static bool data_type_less(struct rb_node *node_a, const struct rb_node *node_b)
a = rb_entry(node_a, struct annotated_data_type, node);
b = rb_entry(node_b, struct annotated_data_type, node);

- if (a->type_size != b->type_size)
- return a->type_size < b->type_size;
- return strcmp(a->type_name, b->type_name) < 0;
+ if (a->self.size != b->self.size)
+ return a->self.size < b->self.size;
+ return strcmp(a->self.type_name, b->self.type_name) < 0;
+}
+
+/* Recursively add new members for struct/union */
+static int __add_member_cb(Dwarf_Die *die, void *arg)
+{
+ struct annotated_member *parent = arg;
+ struct annotated_member *member;
+ Dwarf_Die member_type, die_mem;
+ Dwarf_Word size, loc;
+ Dwarf_Attribute attr;
+ struct strbuf sb;
+ int tag;
+
+ if (dwarf_tag(die) != DW_TAG_member)
+ return DIE_FIND_CB_SIBLING;
+
+ member = zalloc(sizeof(*member));
+ if (member == NULL)
+ return DIE_FIND_CB_END;
+
+ strbuf_init(&sb, 32);
+ die_get_typename(die, &sb);
+
+ die_get_real_type(die, &member_type);
+ if (dwarf_aggregate_size(&member_type, &size) < 0)
+ size = 0;
+
+ if (!dwarf_attr_integrate(die, DW_AT_data_member_location, &attr))
+ loc = 0;
+ else
+ dwarf_formudata(&attr, &loc);
+
+ member->type_name = strbuf_detach(&sb, NULL);
+ /* member->var_name can be NULL */
+ if (dwarf_diename(die))
+ member->var_name = strdup(dwarf_diename(die));
+ member->size = size;
+ member->offset = loc + parent->offset;
+ INIT_LIST_HEAD(&member->children);
+ list_add_tail(&member->node, &parent->children);
+
+ tag = dwarf_tag(&member_type);
+ switch (tag) {
+ case DW_TAG_structure_type:
+ case DW_TAG_union_type:
+ die_find_child(&member_type, __add_member_cb, member, &die_mem);
+ break;
+ default:
+ break;
+ }
+ return DIE_FIND_CB_SIBLING;
+}
+
+static void add_member_types(struct annotated_data_type *parent, Dwarf_Die *type)
+{
+ Dwarf_Die die_mem;
+
+ die_find_child(type, __add_member_cb, &parent->self, &die_mem);
+}
+
+static void delete_members(struct annotated_member *member)
+{
+ struct annotated_member *child, *tmp;
+
+ list_for_each_entry_safe(child, tmp, &member->children, node) {
+ list_del(&child->node);
+ delete_members(child);
+ free(child->type_name);
+ free(child->var_name);
+ free(child);
+ }
}

static struct annotated_data_type *dso__findnew_data_type(struct dso *dso,
@@ -64,8 +135,8 @@ static struct annotated_data_type *dso__findnew_data_type(struct dso *dso,
dwarf_aggregate_size(type_die, &size);

/* Check existing nodes in dso->data_types tree */
- key.type_name = type_name;
- key.type_size = size;
+ key.self.type_name = type_name;
+ key.self.size = size;
node = rb_find(&key, &dso->data_types, data_type_cmp);
if (node) {
result = rb_entry(node, struct annotated_data_type, node);
@@ -80,8 +151,15 @@ static struct annotated_data_type *dso__findnew_data_type(struct dso *dso,
return NULL;
}

- result->type_name = type_name;
- result->type_size = size;
+ result->self.type_name = type_name;
+ result->self.size = size;
+ INIT_LIST_HEAD(&result->self.children);
+
+ /*
+ * Fill member info unconditionally for now,
+ * later perf annotate would need it.
+ */
+ add_member_types(result, type_die);

rb_add(&result->node, &dso->data_types, data_type_less);
return result;
@@ -232,7 +310,8 @@ void annotated_data_type__tree_delete(struct rb_root *root)

rb_erase(node, root);
pos = rb_entry(node, struct annotated_data_type, node);
- free(pos->type_name);
+ delete_members(&pos->self);
+ free(pos->self.type_name);
free(pos);
}
}
diff --git a/tools/perf/util/annotate-data.h b/tools/perf/util/annotate-data.h
index 6efdd7e21b28..33748222e6aa 100644
--- a/tools/perf/util/annotate-data.h
+++ b/tools/perf/util/annotate-data.h
@@ -9,17 +9,36 @@

struct map_symbol;

+/**
+ * struct annotated_member - Type of member field
+ * @node: List entry in the parent list
+ * @children: List head for child nodes
+ * @type_name: Name of the member type
+ * @var_name: Name of the member variable
+ * @offset: Offset from the outer data type
+ * @size: Size of the member field
+ *
+ * This represents a member type in a data type.
+ */
+struct annotated_member {
+ struct list_head node;
+ struct list_head children;
+ char *type_name;
+ char *var_name;
+ int offset;
+ int size;
+};
+
/**
* struct annotated_data_type - Data type to profile
- * @type_name: Name of the data type
- * @type_size: Size of the data type
+ * @node: RB-tree node for dso->type_tree
+ * @self: Actual type information
*
* This represents a data type accessed by samples in the profile data.
*/
struct annotated_data_type {
struct rb_node node;
- char *type_name;
- int type_size;
+ struct annotated_member self;
};

extern struct annotated_data_type unknown_type;
diff --git a/tools/perf/util/sort.c b/tools/perf/util/sort.c
index e647f0117bb5..a41209e242ae 100644
--- a/tools/perf/util/sort.c
+++ b/tools/perf/util/sort.c
@@ -2135,7 +2135,10 @@ struct sort_entry sort_addr = {
/* --sort type */

struct annotated_data_type unknown_type = {
- .type_name = (char *)"(unknown)",
+ .self = {
+ .type_name = (char *)"(unknown)",
+ .children = LIST_HEAD_INIT(unknown_type.self.children),
+ },
};

static int64_t
@@ -2170,7 +2173,7 @@ sort__type_collapse(struct hist_entry *left, struct hist_entry *right)
right_type = right->mem_type;
}

- return strcmp(left_type->type_name, right_type->type_name);
+ return strcmp(left_type->self.type_name, right_type->self.type_name);
}

static int64_t
@@ -2182,7 +2185,7 @@ sort__type_sort(struct hist_entry *left, struct hist_entry *right)
static int hist_entry__type_snprintf(struct hist_entry *he, char *bf,
size_t size, unsigned int width)
{
- return repsep_snprintf(bf, size, "%-*s", width, he->mem_type->type_name);
+ return repsep_snprintf(bf, size, "%-*s", width, he->mem_type->self.type_name);
}

struct sort_entry sort_type = {
--
2.43.0.472.g3155946c3a-goog

2023-12-13 00:14:56

by Namhyung Kim

[permalink] [raw]
Subject: [PATCH 13/17] perf report: Add 'symoff' sort key

The symoff sort key is to print symbol and offset of sample. This is
useful for data type profiling to show exact instruction in the function
which refers the data.

$ perf report -s type,sym,typeoff,symoff --hierarchy
...
# Overhead Data Type / Symbol / Data Type Offset / Symbol Offset
# .............. .....................................................
#
1.23% struct cfs_rq
0.84% update_blocked_averages
0.19% struct cfs_rq +336 (leaf_cfs_rq_list.next)
0.19% [k] update_blocked_averages+0x96
0.19% struct cfs_rq +0 (load.weight)
0.14% [k] update_blocked_averages+0x104
0.04% [k] update_blocked_averages+0x31c
0.17% struct cfs_rq +404 (throttle_count)
0.12% [k] update_blocked_averages+0x9d
0.05% [k] update_blocked_averages+0x1f9
0.08% struct cfs_rq +272 (propagate)
0.07% [k] update_blocked_averages+0x3d3
0.02% [k] update_blocked_averages+0x45b
...

Signed-off-by: Namhyung Kim <[email protected]>
---
tools/perf/Documentation/perf-report.txt | 1 +
tools/perf/util/hist.h | 1 +
tools/perf/util/sort.c | 47 ++++++++++++++++++++++++
tools/perf/util/sort.h | 1 +
4 files changed, 50 insertions(+)

diff --git a/tools/perf/Documentation/perf-report.txt b/tools/perf/Documentation/perf-report.txt
index b57eb51b47aa..38f59ac064f7 100644
--- a/tools/perf/Documentation/perf-report.txt
+++ b/tools/perf/Documentation/perf-report.txt
@@ -120,6 +120,7 @@ OPTIONS
- simd: Flags describing a SIMD operation. "e" for empty Arm SVE predicate. "p" for partial Arm SVE predicate
- type: Data type of sample memory access.
- typeoff: Offset in the data type of sample memory access.
+ - symoff: Offset in the symbol.

By default, comm, dso and symbol keys are used.
(i.e. --sort comm,dso,symbol)
diff --git a/tools/perf/util/hist.h b/tools/perf/util/hist.h
index 18128a49309e..4a0aea0c9e00 100644
--- a/tools/perf/util/hist.h
+++ b/tools/perf/util/hist.h
@@ -84,6 +84,7 @@ enum hist_column {
HISTC_SIMD,
HISTC_TYPE,
HISTC_TYPE_OFFSET,
+ HISTC_SYMBOL_OFFSET,
HISTC_NR_COLS, /* Last entry */
};

diff --git a/tools/perf/util/sort.c b/tools/perf/util/sort.c
index d78e680d3988..0cbbd5ba8175 100644
--- a/tools/perf/util/sort.c
+++ b/tools/perf/util/sort.c
@@ -419,6 +419,52 @@ struct sort_entry sort_sym = {
.se_width_idx = HISTC_SYMBOL,
};

+/* --sort symoff */
+
+static int64_t
+sort__symoff_cmp(struct hist_entry *left, struct hist_entry *right)
+{
+ int64_t ret;
+
+ ret = sort__sym_cmp(left, right);
+ if (ret)
+ return ret;
+
+ return left->ip - right->ip;
+}
+
+static int64_t
+sort__symoff_sort(struct hist_entry *left, struct hist_entry *right)
+{
+ int64_t ret;
+
+ ret = sort__sym_sort(left, right);
+ if (ret)
+ return ret;
+
+ return left->ip - right->ip;
+}
+
+static int
+hist_entry__symoff_snprintf(struct hist_entry *he, char *bf, size_t size, unsigned int width)
+{
+ struct symbol *sym = he->ms.sym;
+
+ if (sym == NULL)
+ return repsep_snprintf(bf, size, "[%c] %-#.*llx", he->level, width - 4, he->ip);
+
+ return repsep_snprintf(bf, size, "[%c] %s+0x%llx", he->level, sym->name, he->ip - sym->start);
+}
+
+struct sort_entry sort_sym_offset = {
+ .se_header = "Symbol Offset",
+ .se_cmp = sort__symoff_cmp,
+ .se_sort = sort__symoff_sort,
+ .se_snprintf = hist_entry__symoff_snprintf,
+ .se_filter = hist_entry__sym_filter,
+ .se_width_idx = HISTC_SYMBOL_OFFSET,
+};
+
/* --sort srcline */

char *hist_entry__srcline(struct hist_entry *he)
@@ -2335,6 +2381,7 @@ static struct sort_dimension common_sort_dimensions[] = {
DIM(SORT_SIMD, "simd", sort_simd),
DIM(SORT_ANNOTATE_DATA_TYPE, "type", sort_type),
DIM(SORT_ANNOTATE_DATA_TYPE_OFFSET, "typeoff", sort_type_offset),
+ DIM(SORT_SYM_OFFSET, "symoff", sort_sym_offset),
};

#undef DIM
diff --git a/tools/perf/util/sort.h b/tools/perf/util/sort.h
index d806adcc1e1e..6f6b4189a389 100644
--- a/tools/perf/util/sort.h
+++ b/tools/perf/util/sort.h
@@ -249,6 +249,7 @@ enum sort_type {
SORT_SIMD,
SORT_ANNOTATE_DATA_TYPE,
SORT_ANNOTATE_DATA_TYPE_OFFSET,
+ SORT_SYM_OFFSET,

/* branch stack specific sort keys */
__SORT_BRANCH_STACK,
--
2.43.0.472.g3155946c3a-goog

2023-12-13 00:14:58

by Namhyung Kim

[permalink] [raw]
Subject: [PATCH 08/17] perf report: Add 'type' sort key

The 'type' sort key is to aggregate hist entries by data type they
access. Add mem_type field to hist_entry struct to save the type.
If hist_entry__get_data_type() returns NULL, it'd use the
'unknown_type' instance.

Signed-off-by: Namhyung Kim <[email protected]>
---
tools/perf/Documentation/perf-report.txt | 1 +
tools/perf/util/annotate-data.h | 2 +
tools/perf/util/hist.h | 1 +
tools/perf/util/sort.c | 69 +++++++++++++++++++++++-
tools/perf/util/sort.h | 4 ++
5 files changed, 75 insertions(+), 2 deletions(-)

diff --git a/tools/perf/Documentation/perf-report.txt b/tools/perf/Documentation/perf-report.txt
index af068b4f1e5a..aec34417090b 100644
--- a/tools/perf/Documentation/perf-report.txt
+++ b/tools/perf/Documentation/perf-report.txt
@@ -118,6 +118,7 @@ OPTIONS
- retire_lat: On X86, this reports pipeline stall of this instruction compared
to the previous instruction in cycles. And currently supported only on X86
- simd: Flags describing a SIMD operation. "e" for empty Arm SVE predicate. "p" for partial Arm SVE predicate
+ - type: Data type of sample memory access.

By default, comm, dso and symbol keys are used.
(i.e. --sort comm,dso,symbol)
diff --git a/tools/perf/util/annotate-data.h b/tools/perf/util/annotate-data.h
index ab9f187bd7f1..6efdd7e21b28 100644
--- a/tools/perf/util/annotate-data.h
+++ b/tools/perf/util/annotate-data.h
@@ -22,6 +22,8 @@ struct annotated_data_type {
int type_size;
};

+extern struct annotated_data_type unknown_type;
+
#ifdef HAVE_DWARF_SUPPORT

/* Returns data type at the location (ip, reg, offset) */
diff --git a/tools/perf/util/hist.h b/tools/perf/util/hist.h
index 5d0db96609df..7ebbf427b1ea 100644
--- a/tools/perf/util/hist.h
+++ b/tools/perf/util/hist.h
@@ -82,6 +82,7 @@ enum hist_column {
HISTC_ADDR_TO,
HISTC_ADDR,
HISTC_SIMD,
+ HISTC_TYPE,
HISTC_NR_COLS, /* Last entry */
};

diff --git a/tools/perf/util/sort.c b/tools/perf/util/sort.c
index 27b123ccd2d1..e647f0117bb5 100644
--- a/tools/perf/util/sort.c
+++ b/tools/perf/util/sort.c
@@ -24,6 +24,7 @@
#include "strbuf.h"
#include "mem-events.h"
#include "annotate.h"
+#include "annotate-data.h"
#include "event.h"
#include "time-utils.h"
#include "cgroup.h"
@@ -2094,7 +2095,7 @@ struct sort_entry sort_dso_size = {
.se_width_idx = HISTC_DSO_SIZE,
};

-/* --sort dso_size */
+/* --sort addr */

static int64_t
sort__addr_cmp(struct hist_entry *left, struct hist_entry *right)
@@ -2131,6 +2132,69 @@ struct sort_entry sort_addr = {
.se_width_idx = HISTC_ADDR,
};

+/* --sort type */
+
+struct annotated_data_type unknown_type = {
+ .type_name = (char *)"(unknown)",
+};
+
+static int64_t
+sort__type_cmp(struct hist_entry *left, struct hist_entry *right)
+{
+ return sort__addr_cmp(left, right);
+}
+
+static void sort__type_init(struct hist_entry *he)
+{
+ if (he->mem_type)
+ return;
+
+ he->mem_type = hist_entry__get_data_type(he);
+ if (he->mem_type == NULL)
+ he->mem_type = &unknown_type;
+}
+
+static int64_t
+sort__type_collapse(struct hist_entry *left, struct hist_entry *right)
+{
+ struct annotated_data_type *left_type = left->mem_type;
+ struct annotated_data_type *right_type = right->mem_type;
+
+ if (!left_type) {
+ sort__type_init(left);
+ left_type = left->mem_type;
+ }
+
+ if (!right_type) {
+ sort__type_init(right);
+ right_type = right->mem_type;
+ }
+
+ return strcmp(left_type->type_name, right_type->type_name);
+}
+
+static int64_t
+sort__type_sort(struct hist_entry *left, struct hist_entry *right)
+{
+ return sort__type_collapse(left, right);
+}
+
+static int hist_entry__type_snprintf(struct hist_entry *he, char *bf,
+ size_t size, unsigned int width)
+{
+ return repsep_snprintf(bf, size, "%-*s", width, he->mem_type->type_name);
+}
+
+struct sort_entry sort_type = {
+ .se_header = "Data Type",
+ .se_cmp = sort__type_cmp,
+ .se_collapse = sort__type_collapse,
+ .se_sort = sort__type_sort,
+ .se_init = sort__type_init,
+ .se_snprintf = hist_entry__type_snprintf,
+ .se_width_idx = HISTC_TYPE,
+};
+

struct sort_dimension {
const char *name;
@@ -2185,7 +2249,8 @@ static struct sort_dimension common_sort_dimensions[] = {
DIM(SORT_ADDR, "addr", sort_addr),
DIM(SORT_LOCAL_RETIRE_LAT, "local_retire_lat", sort_local_p_stage_cyc),
DIM(SORT_GLOBAL_RETIRE_LAT, "retire_lat", sort_global_p_stage_cyc),
- DIM(SORT_SIMD, "simd", sort_simd)
+ DIM(SORT_SIMD, "simd", sort_simd),
+ DIM(SORT_ANNOTATE_DATA_TYPE, "type", sort_type),
};

#undef DIM
diff --git a/tools/perf/util/sort.h b/tools/perf/util/sort.h
index ecfb7f1359d5..aabf0b8331a3 100644
--- a/tools/perf/util/sort.h
+++ b/tools/perf/util/sort.h
@@ -15,6 +15,7 @@

struct option;
struct thread;
+struct annotated_data_type;

extern regex_t parent_regex;
extern const char *sort_order;
@@ -34,6 +35,7 @@ extern struct sort_entry sort_dso_to;
extern struct sort_entry sort_sym_from;
extern struct sort_entry sort_sym_to;
extern struct sort_entry sort_srcline;
+extern struct sort_entry sort_type;
extern const char default_mem_sort_order[];
extern bool chk_double_cl;

@@ -154,6 +156,7 @@ struct hist_entry {
struct perf_hpp_list *hpp_list;
struct hist_entry *parent_he;
struct hist_entry_ops *ops;
+ struct annotated_data_type *mem_type;
union {
/* this is for hierarchical entry structure */
struct {
@@ -243,6 +246,7 @@ enum sort_type {
SORT_LOCAL_RETIRE_LAT,
SORT_GLOBAL_RETIRE_LAT,
SORT_SIMD,
+ SORT_ANNOTATE_DATA_TYPE,

/* branch stack specific sort keys */
__SORT_BRANCH_STACK,
--
2.43.0.472.g3155946c3a-goog

2023-12-13 00:15:44

by Namhyung Kim

[permalink] [raw]
Subject: [PATCH 17/17] perf annotate: Add --insn-stat option for debugging

This is for a debugging purpose. It'd be useful to see per-instrucion
level success/failure stats.

$ perf annotate --data-type --insn-stat
Annotate Instruction stats
total 264, ok 143 (54.2%), bad 121 (45.8%)

Name : Good Bad
-----------------------------------------------------------
movq : 45 31
movl : 22 11
popq : 0 19
cmpl : 16 3
addq : 8 7
cmpq : 11 3
cmpxchgl : 3 7
cmpxchgq : 8 0
incl : 3 3
movzbl : 4 2
incq : 4 2
decl : 6 0
...

Signed-off-by: Namhyung Kim <[email protected]>
---
tools/perf/builtin-annotate.c | 41 +++++++++++++++++++++++++++++++++++
tools/perf/util/annotate.c | 38 ++++++++++++++++++++++++++++++++
tools/perf/util/annotate.h | 8 +++++++
3 files changed, 87 insertions(+)

diff --git a/tools/perf/builtin-annotate.c b/tools/perf/builtin-annotate.c
index 55f97ab1395b..6c1cc797692d 100644
--- a/tools/perf/builtin-annotate.c
+++ b/tools/perf/builtin-annotate.c
@@ -58,6 +58,7 @@ struct perf_annotate {
bool group_set;
bool data_type;
bool type_stat;
+ bool insn_stat;
float min_percent;
const char *sym_hist_filter;
const char *cpu_list;
@@ -434,6 +435,42 @@ static void print_annotate_data_stat(struct annotated_data_stat *s)
#undef PRINT_STAT
}

+static void print_annotate_item_stat(struct list_head *head, const char *title)
+{
+ struct annotated_item_stat *istat, *pos, *iter;
+ int total_good, total_bad, total;
+ int sum1, sum2;
+ LIST_HEAD(tmp);
+
+ /* sort the list by count */
+ list_splice_init(head, &tmp);
+ total_good = total_bad = 0;
+
+ list_for_each_entry_safe(istat, pos, &tmp, list) {
+ total_good += istat->good;
+ total_bad += istat->bad;
+ sum1 = istat->good + istat->bad;
+
+ list_for_each_entry(iter, head, list) {
+ sum2 = iter->good + iter->bad;
+ if (sum1 > sum2)
+ break;
+ }
+ list_move_tail(&istat->list, &iter->list);
+ }
+ total = total_good + total_bad;
+
+ printf("Annotate %s stats\n", title);
+ printf("total %d, ok %d (%.1f%%), bad %d (%.1f%%)\n\n", total,
+ total_good, 100.0 * total_good / (total ?: 1),
+ total_bad, 100.0 * total_bad / (total ?: 1));
+ printf(" %-10s: %5s %5s\n", "Name", "Good", "Bad");
+ printf("-----------------------------------------------------------\n");
+ list_for_each_entry(istat, head, list)
+ printf(" %-10s: %5d %5d\n", istat->name, istat->good, istat->bad);
+ printf("\n");
+}
+
static void hists__find_annotations(struct hists *hists,
struct evsel *evsel,
struct perf_annotate *ann)
@@ -443,6 +480,8 @@ static void hists__find_annotations(struct hists *hists,

if (ann->type_stat)
print_annotate_data_stat(&ann_data_stat);
+ if (ann->insn_stat)
+ print_annotate_item_stat(&ann_insn_stat, "Instruction");

while (nd) {
struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node);
@@ -792,6 +831,8 @@ int cmd_annotate(int argc, const char **argv)
parse_data_type),
OPT_BOOLEAN(0, "type-stat", &annotate.type_stat,
"Show stats for the data type annotation"),
+ OPT_BOOLEAN(0, "insn-stat", &annotate.insn_stat,
+ "Show instruction stats for the data type annotation"),
OPT_END()
};
int ret;
diff --git a/tools/perf/util/annotate.c b/tools/perf/util/annotate.c
index 9870257ce21e..9b70ab110ce7 100644
--- a/tools/perf/util/annotate.c
+++ b/tools/perf/util/annotate.c
@@ -105,6 +105,7 @@ static struct ins_ops ret_ops;

/* Data type collection debug statistics */
struct annotated_data_stat ann_data_stat;
+LIST_HEAD(ann_insn_stat);

static int arch__grow_instructions(struct arch *arch)
{
@@ -3665,6 +3666,30 @@ static struct disasm_line *find_disasm_line(struct symbol *sym, u64 ip)
return NULL;
}

+static struct annotated_item_stat *annotate_data_stat(struct list_head *head,
+ const char *name)
+{
+ struct annotated_item_stat *istat;
+
+ list_for_each_entry(istat, head, list) {
+ if (!strcmp(istat->name, name))
+ return istat;
+ }
+
+ istat = zalloc(sizeof(*istat));
+ if (istat == NULL)
+ return NULL;
+
+ istat->name = strdup(name);
+ if (istat->name == NULL) {
+ free(istat);
+ return NULL;
+ }
+
+ list_add_tail(&istat->list, head);
+ return istat;
+}
+
/**
* hist_entry__get_data_type - find data type for given hist entry
* @he: hist entry
@@ -3683,6 +3708,7 @@ struct annotated_data_type *hist_entry__get_data_type(struct hist_entry *he)
struct annotated_insn_loc loc;
struct annotated_op_loc *op_loc;
struct annotated_data_type *mem_type;
+ struct annotated_item_stat *istat;
u64 ip = he->ip;
int i;

@@ -3716,8 +3742,15 @@ struct annotated_data_type *hist_entry__get_data_type(struct hist_entry *he)
return NULL;
}

+ istat = annotate_data_stat(&ann_insn_stat, dl->ins.name);
+ if (istat == NULL) {
+ ann_data_stat.no_insn++;
+ return NULL;
+ }
+
if (annotate_get_insn_location(arch, dl, &loc) < 0) {
ann_data_stat.no_insn_ops++;
+ istat->bad++;
return NULL;
}

@@ -3726,6 +3759,10 @@ struct annotated_data_type *hist_entry__get_data_type(struct hist_entry *he)
continue;

mem_type = find_data_type(ms, ip, op_loc->reg, op_loc->offset);
+ if (mem_type)
+ istat->good++;
+ else
+ istat->bad++;

if (symbol_conf.annotate_data_sample) {
annotated_data_type__update_samples(mem_type, evsel,
@@ -3738,5 +3775,6 @@ struct annotated_data_type *hist_entry__get_data_type(struct hist_entry *he)
}

ann_data_stat.no_mem_ops++;
+ istat->bad++;
return NULL;
}
diff --git a/tools/perf/util/annotate.h b/tools/perf/util/annotate.h
index 6c75b2832286..dba50762c6e8 100644
--- a/tools/perf/util/annotate.h
+++ b/tools/perf/util/annotate.h
@@ -479,4 +479,12 @@ int annotate_get_insn_location(struct arch *arch, struct disasm_line *dl,
/* Returns a data type from the sample instruction (if any) */
struct annotated_data_type *hist_entry__get_data_type(struct hist_entry *he);

+struct annotated_item_stat {
+ struct list_head list;
+ char *name;
+ int good;
+ int bad;
+};
+extern struct list_head ann_insn_stat;
+
#endif /* __PERF_ANNOTATE_H */
--
2.43.0.472.g3155946c3a-goog

2023-12-13 00:15:48

by Namhyung Kim

[permalink] [raw]
Subject: [PATCH 14/17] perf annotate: Add --data-type option

Support data type annotation with new --data-type option. It internally
uses type sort key to collect sample histogram for the type and display
every members like below.

$ perf annotate --data-type
...
Annotate type: 'struct cfs_rq' in [kernel.kallsyms] (13 samples):
============================================================================
samples offset size field
13 0 640 struct cfs_rq {
2 0 16 struct load_weight load {
2 0 8 unsigned long weight;
0 8 4 u32 inv_weight;
};
0 16 8 unsigned long runnable_weight;
0 24 4 unsigned int nr_running;
1 28 4 unsigned int h_nr_running;
...

For simplicity it prints the number of samples per field for now.
But it should be easy to show the overhead percentage instead.

The number at the outer struct is a sum of the numbers of the inner
members. For example, struct cfs_rq got total 13 samples, and 2 came
from the load (struct load_weight) and 1 from h_nr_running. Similarly,
the struct load_weight got total 2 samples and they all came from the
weight field.

I've added two new flags in the symbol_conf for this. The
annotate_data_member is to get the members of the type. This is also
needed for perf report with typeoff sort key. The annotate_data_sample
is to update sample stats for each offset and used only in annotate.

Currently it only support stdio output mode, TUI support can be added
later.

Signed-off-by: Namhyung Kim <[email protected]>
---
tools/perf/Documentation/perf-annotate.txt | 8 ++
tools/perf/builtin-annotate.c | 97 +++++++++++++++++++++-
tools/perf/util/annotate-data.c | 8 +-
tools/perf/util/annotate.c | 10 ++-
tools/perf/util/sort.c | 2 +
tools/perf/util/symbol_conf.h | 4 +-
6 files changed, 118 insertions(+), 11 deletions(-)

diff --git a/tools/perf/Documentation/perf-annotate.txt b/tools/perf/Documentation/perf-annotate.txt
index fe168e8165c8..0e6a49b7795c 100644
--- a/tools/perf/Documentation/perf-annotate.txt
+++ b/tools/perf/Documentation/perf-annotate.txt
@@ -155,6 +155,14 @@ include::itrace.txt[]
stdio or stdio2 (Default: 0). Note that this is about selection of
functions to display, not about lines within the function.

+--data-type[=TYPE_NAME]::
+ Display data type annotation instead of code. It infers data type of
+ samples (if they are memory accessing instructions) using DWARF debug
+ information. It can take an optional argument of data type name. In
+ that case it'd show annotation for the type only, otherwise it'd show
+ all data types it finds.
+
+
SEE ALSO
--------
linkperf:perf-record[1], linkperf:perf-report[1]
diff --git a/tools/perf/builtin-annotate.c b/tools/perf/builtin-annotate.c
index d880f1b039fd..8acfbbc1b9c2 100644
--- a/tools/perf/builtin-annotate.c
+++ b/tools/perf/builtin-annotate.c
@@ -20,6 +20,7 @@
#include "util/evlist.h"
#include "util/evsel.h"
#include "util/annotate.h"
+#include "util/annotate-data.h"
#include "util/event.h"
#include <subcmd/parse-options.h>
#include "util/parse-events.h"
@@ -55,9 +56,11 @@ struct perf_annotate {
bool skip_missing;
bool has_br_stack;
bool group_set;
+ bool data_type;
float min_percent;
const char *sym_hist_filter;
const char *cpu_list;
+ const char *target_data_type;
DECLARE_BITMAP(cpu_bitmap, MAX_NR_CPUS);
};

@@ -322,6 +325,32 @@ static int hist_entry__tty_annotate(struct hist_entry *he,
return symbol__tty_annotate2(&he->ms, evsel);
}

+static void print_annotated_data_type(struct annotated_data_type *mem_type,
+ struct annotated_member *member,
+ struct evsel *evsel, int indent)
+{
+ struct annotated_member *child;
+ struct type_hist *h = mem_type->histograms[evsel->core.idx];
+ int i, samples = 0;
+
+ for (i = 0; i < member->size; i++)
+ samples += h->addr[member->offset + i].nr_samples;
+
+ printf(" %10d %10d %10d %*s%s\t%s",
+ samples, member->offset, member->size, indent, "", member->type_name,
+ member->var_name ?: "");
+
+ if (!list_empty(&member->children))
+ printf(" {\n");
+
+ list_for_each_entry(child, &member->children, node)
+ print_annotated_data_type(mem_type, child, evsel, indent + 4);
+
+ if (!list_empty(&member->children))
+ printf("%*s}", 35 + indent, "");
+ printf(";\n");
+}
+
static void hists__find_annotations(struct hists *hists,
struct evsel *evsel,
struct perf_annotate *ann)
@@ -361,6 +390,40 @@ static void hists__find_annotations(struct hists *hists,
continue;
}

+ if (ann->data_type) {
+ struct dso *dso = map__dso(he->ms.map);
+
+ /* skip unknown type */
+ if (he->mem_type->histograms == NULL)
+ goto find_next;
+
+ if (ann->target_data_type) {
+ const char *type_name = he->mem_type->self.type_name;
+
+ /* skip 'struct ' prefix in the type name */
+ if (strncmp(ann->target_data_type, "struct ", 7) &&
+ !strncmp(type_name, "struct ", 7))
+ type_name += 7;
+
+ /* skip 'union ' prefix in the type name */
+ if (strncmp(ann->target_data_type, "union ", 6) &&
+ !strncmp(type_name, "union ", 6))
+ type_name += 6;
+
+ if (strcmp(ann->target_data_type, type_name))
+ goto find_next;
+ }
+
+ printf("Annotate type: '%s' in %s (%d samples):\n",
+ he->mem_type->self.type_name, dso->name, he->stat.nr_events);
+ printf("============================================================================\n");
+ printf(" %10s %10s %10s %s\n", "samples", "offset", "size", "field");
+
+ print_annotated_data_type(he->mem_type, &he->mem_type->self, evsel, 0);
+ printf("\n");
+ goto find_next;
+ }
+
if (use_browser == 2) {
int ret;
int (*annotate)(struct hist_entry *he,
@@ -496,6 +559,17 @@ static int parse_percent_limit(const struct option *opt, const char *str,
return 0;
}

+static int parse_data_type(const struct option *opt, const char *str, int unset)
+{
+ struct perf_annotate *ann = opt->value;
+
+ ann->data_type = !unset;
+ if (str)
+ ann->target_data_type = strdup(str);
+
+ return 0;
+}
+
static const char * const annotate_usage[] = {
"perf annotate [<options>]",
NULL
@@ -607,6 +681,9 @@ int cmd_annotate(int argc, const char **argv)
OPT_CALLBACK_OPTARG(0, "itrace", &itrace_synth_opts, NULL, "opts",
"Instruction Tracing options\n" ITRACE_HELP,
itrace_parse_synth_opts),
+ OPT_CALLBACK_OPTARG(0, "data-type", &annotate, NULL, "name",
+ "Show data type annotate for the memory accesses",
+ parse_data_type),

OPT_END()
};
@@ -661,6 +738,13 @@ int cmd_annotate(int argc, const char **argv)
}
#endif

+#ifndef HAVE_DWARF_GETLOCATIONS_SUPPORT
+ if (annotate.data_type) {
+ pr_err("Error: Data type profiling is disabled due to missing DWARF support\n");
+ return -ENOTSUP;
+ }
+#endif
+
ret = symbol__validate_sym_arguments();
if (ret)
return ret;
@@ -703,6 +787,14 @@ int cmd_annotate(int argc, const char **argv)
use_browser = 2;
#endif

+ /* FIXME: only support stdio for now */
+ if (annotate.data_type) {
+ use_browser = 0;
+ annotate_opts.annotate_src = false;
+ symbol_conf.annotate_data_member = true;
+ symbol_conf.annotate_data_sample = true;
+ }
+
setup_browser(true);

/*
@@ -710,7 +802,10 @@ int cmd_annotate(int argc, const char **argv)
* symbol, we do not care about the processes in annotate,
* set sort order to avoid repeated output.
*/
- sort_order = "dso,symbol";
+ if (annotate.data_type)
+ sort_order = "dso,type";
+ else
+ sort_order = "dso,symbol";

/*
* Set SORT_MODE__BRANCH so that annotate display IPC/Cycle
diff --git a/tools/perf/util/annotate-data.c b/tools/perf/util/annotate-data.c
index 9942a87b0664..06be6b279d6a 100644
--- a/tools/perf/util/annotate-data.c
+++ b/tools/perf/util/annotate-data.c
@@ -18,6 +18,7 @@
#include "map_symbol.h"
#include "strbuf.h"
#include "symbol.h"
+#include "symbol_conf.h"

/*
* Compare type name and size to maintain them in a tree.
@@ -157,11 +158,8 @@ static struct annotated_data_type *dso__findnew_data_type(struct dso *dso,
result->self.size = size;
INIT_LIST_HEAD(&result->self.children);

- /*
- * Fill member info unconditionally for now,
- * later perf annotate would need it.
- */
- add_member_types(result, type_die);
+ if (symbol_conf.annotate_data_member)
+ add_member_types(result, type_die);

rb_add(&result->node, &dso->data_types, data_type_less);
return result;
diff --git a/tools/perf/util/annotate.c b/tools/perf/util/annotate.c
index f966e8f83c5e..68424ee0215e 100644
--- a/tools/perf/util/annotate.c
+++ b/tools/perf/util/annotate.c
@@ -3712,10 +3712,12 @@ struct annotated_data_type *hist_entry__get_data_type(struct hist_entry *he)

mem_type = find_data_type(ms, ip, op_loc->reg, op_loc->offset);

- annotated_data_type__update_samples(mem_type, evsel,
- op_loc->offset,
- he->stat.nr_events,
- he->stat.period);
+ if (symbol_conf.annotate_data_sample) {
+ annotated_data_type__update_samples(mem_type, evsel,
+ op_loc->offset,
+ he->stat.nr_events,
+ he->stat.period);
+ }
he->mem_type_off = op_loc->offset;
return mem_type;
}
diff --git a/tools/perf/util/sort.c b/tools/perf/util/sort.c
index 0cbbd5ba8175..30254eb63709 100644
--- a/tools/perf/util/sort.c
+++ b/tools/perf/util/sort.c
@@ -3401,6 +3401,8 @@ int sort_dimension__add(struct perf_hpp_list *list, const char *tok,
list->thread = 1;
} else if (sd->entry == &sort_comm) {
list->comm = 1;
+ } else if (sd->entry == &sort_type_offset) {
+ symbol_conf.annotate_data_member = true;
}

return __sort_dimension__add(sd, list, level);
diff --git a/tools/perf/util/symbol_conf.h b/tools/perf/util/symbol_conf.h
index 6040286e07a6..c114bbceef40 100644
--- a/tools/perf/util/symbol_conf.h
+++ b/tools/perf/util/symbol_conf.h
@@ -44,7 +44,9 @@ struct symbol_conf {
buildid_mmap2,
guest_code,
lazy_load_kernel_maps,
- keep_exited_threads;
+ keep_exited_threads,
+ annotate_data_member,
+ annotate_data_sample;
const char *vmlinux_name,
*kallsyms_name,
*source_prefix,
--
2.43.0.472.g3155946c3a-goog

2023-12-13 00:16:03

by Namhyung Kim

[permalink] [raw]
Subject: [PATCH 15/17] perf annotate: Support event group display

When events are grouped together, it'd be natural to show them at once
like in other mode. Handle group leaders with members to collect the
number of samples together and display like below:

$ perf annotate --data-type --group
...
Annotate type: 'struct page' in vmlinux (1 samples):
event[0] = cpu/mem-loads,ldlat=30/P
event[1] = cpu/mem-stores/P
event[2] = dummy:u
============================================================================
samples offset size field
1 0 0 0 64 struct page {
0 0 0 0 8 long unsigned int flags;
0 0 0 8 40 union {
0 0 0 8 40 struct {
0 0 0 8 16 union {
0 0 0 8 16 struct list_head lru {
0 0 0 8 8 struct list_head* next;
0 0 0 16 8 struct list_head* prev;
};
0 0 0 8 16 struct {
0 0 0 8 8 void* __filler;
0 0 0 16 4 unsigned int mlock_count;
};
0 0 0 8 16 struct list_head buddy_list {
0 0 0 8 8 struct list_head* next;
0 0 0 16 8 struct list_head* prev;
};

Signed-off-by: Namhyung Kim <[email protected]>
---
tools/perf/builtin-annotate.c | 89 ++++++++++++++++++++++++++++++-----
1 file changed, 77 insertions(+), 12 deletions(-)

diff --git a/tools/perf/builtin-annotate.c b/tools/perf/builtin-annotate.c
index 8acfbbc1b9c2..3956ea1334cc 100644
--- a/tools/perf/builtin-annotate.c
+++ b/tools/perf/builtin-annotate.c
@@ -325,19 +325,64 @@ static int hist_entry__tty_annotate(struct hist_entry *he,
return symbol__tty_annotate2(&he->ms, evsel);
}

+static void print_annotated_data_header(struct hist_entry *he, struct evsel *evsel)
+{
+ struct dso *dso = map__dso(he->ms.map);
+ int nr_members = 1;
+ int nr_samples = he->stat.nr_events;
+
+ if (evsel__is_group_event(evsel)) {
+ struct hist_entry *pair;
+
+ list_for_each_entry(pair, &he->pairs.head, pairs.node)
+ nr_samples += pair->stat.nr_events;
+ }
+
+ printf("Annotate type: '%s' in %s (%d samples):\n",
+ he->mem_type->self.type_name, dso->name, nr_samples);
+
+ if (evsel__is_group_event(evsel)) {
+ struct evsel *pos;
+ int i = 0;
+
+ for_each_group_evsel(pos, evsel)
+ printf(" event[%d] = %s\n", i++, pos->name);
+
+ nr_members = evsel->core.nr_members;
+ }
+
+ printf("============================================================================\n");
+ printf("%*s %10s %10s %s\n", 11 * nr_members, "samples", "offset", "size", "field");
+}
+
static void print_annotated_data_type(struct annotated_data_type *mem_type,
struct annotated_member *member,
struct evsel *evsel, int indent)
{
struct annotated_member *child;
struct type_hist *h = mem_type->histograms[evsel->core.idx];
- int i, samples = 0;
+ int i, nr_events = 1, samples = 0;

for (i = 0; i < member->size; i++)
samples += h->addr[member->offset + i].nr_samples;
+ printf(" %10d", samples);
+
+ if (evsel__is_group_event(evsel)) {
+ struct evsel *pos;
+
+ for_each_group_member(pos, evsel) {
+ h = mem_type->histograms[pos->core.idx];
+
+ samples = 0;
+ for (i = 0; i < member->size; i++)
+ samples += h->addr[member->offset + i].nr_samples;
+ printf(" %10d", samples);
+ }
+ nr_events = evsel->core.nr_members;
+ }

- printf(" %10d %10d %10d %*s%s\t%s",
- samples, member->offset, member->size, indent, "", member->type_name,
+ printf(" %10d %10d %*s%s\t%s",
+ member->offset, member->size, indent, "", member->type_name,
member->var_name ?: "");

if (!list_empty(&member->children))
@@ -347,7 +392,7 @@ static void print_annotated_data_type(struct annotated_data_type *mem_type,
print_annotated_data_type(mem_type, child, evsel, indent + 4);

if (!list_empty(&member->children))
- printf("%*s}", 35 + indent, "");
+ printf("%*s}", 11 * nr_events + 24 + indent, "");
printf(";\n");
}

@@ -391,8 +436,6 @@ static void hists__find_annotations(struct hists *hists,
}

if (ann->data_type) {
- struct dso *dso = map__dso(he->ms.map);
-
/* skip unknown type */
if (he->mem_type->histograms == NULL)
goto find_next;
@@ -414,11 +457,7 @@ static void hists__find_annotations(struct hists *hists,
goto find_next;
}

- printf("Annotate type: '%s' in %s (%d samples):\n",
- he->mem_type->self.type_name, dso->name, he->stat.nr_events);
- printf("============================================================================\n");
- printf(" %10s %10s %10s %s\n", "samples", "offset", "size", "field");
-
+ print_annotated_data_header(he, evsel);
print_annotated_data_type(he->mem_type, &he->mem_type->self, evsel, 0);
printf("\n");
goto find_next;
@@ -521,8 +560,20 @@ static int __cmd_annotate(struct perf_annotate *ann)
evsel__reset_sample_bit(pos, CALLCHAIN);
evsel__output_resort(pos, NULL);

- if (symbol_conf.event_group && !evsel__is_group_leader(pos))
+ /*
+ * An event group needs to display other events too.
+ * Let's delay printing until other events are processed.
+ */
+ if (symbol_conf.event_group) {
+ if (!evsel__is_group_leader(pos)) {
+ struct hists *leader_hists;
+
+ leader_hists = evsel__hists(evsel__leader(pos));
+ hists__match(leader_hists, hists);
+ hists__link(leader_hists, hists);
+ }
continue;
+ }

hists__find_annotations(hists, pos, ann);
}
@@ -533,6 +584,20 @@ static int __cmd_annotate(struct perf_annotate *ann)
goto out;
}

+ /* Display group events together */
+ evlist__for_each_entry(session->evlist, pos) {
+ struct hists *hists = evsel__hists(pos);
+ u32 nr_samples = hists->stats.nr_samples;
+
+ if (nr_samples == 0)
+ continue;
+
+ if (!symbol_conf.event_group || !evsel__is_group_leader(pos))
+ continue;
+
+ hists__find_annotations(hists, pos, ann);
+ }
+
if (use_browser == 2) {
void (*show_annotations)(void);

--
2.43.0.472.g3155946c3a-goog

2023-12-13 00:16:03

by Namhyung Kim

[permalink] [raw]
Subject: [PATCH 16/17] perf annotate: Add --type-stat option for debugging

The --type-stat option is to be used with --data-type and to print
detailed failure reasons for the data type annotation.

$ perf annotate --data-type --type-stat
Annotate data type stats:
total 294, ok 116 (39.5%), bad 178 (60.5%)
-----------------------------------------------------------
30 : no_sym
40 : no_insn_ops
33 : no_mem_ops
63 : no_var
4 : no_typeinfo
8 : bad_offset

Signed-off-by: Namhyung Kim <[email protected]>
---
tools/perf/Documentation/perf-annotate.txt | 3 ++
tools/perf/builtin-annotate.c | 44 +++++++++++++++++++++-
tools/perf/util/annotate-data.c | 10 ++++-
tools/perf/util/annotate-data.h | 31 +++++++++++++++
tools/perf/util/annotate.c | 27 ++++++++++---
5 files changed, 108 insertions(+), 7 deletions(-)

diff --git a/tools/perf/Documentation/perf-annotate.txt b/tools/perf/Documentation/perf-annotate.txt
index 0e6a49b7795c..b95524bea021 100644
--- a/tools/perf/Documentation/perf-annotate.txt
+++ b/tools/perf/Documentation/perf-annotate.txt
@@ -162,6 +162,9 @@ include::itrace.txt[]
that case it'd show annotation for the type only, otherwise it'd show
all data types it finds.

+--type-stat::
+ Show stats for the data type annotation.
+

SEE ALSO
--------
diff --git a/tools/perf/builtin-annotate.c b/tools/perf/builtin-annotate.c
index 3956ea1334cc..55f97ab1395b 100644
--- a/tools/perf/builtin-annotate.c
+++ b/tools/perf/builtin-annotate.c
@@ -57,6 +57,7 @@ struct perf_annotate {
bool has_br_stack;
bool group_set;
bool data_type;
+ bool type_stat;
float min_percent;
const char *sym_hist_filter;
const char *cpu_list;
@@ -396,6 +397,43 @@ static void print_annotated_data_type(struct annotated_data_type *mem_type,
printf(";\n");
}

+static void print_annotate_data_stat(struct annotated_data_stat *s)
+{
+#define PRINT_STAT(fld) if (s->fld) printf("%10d : %s\n", s->fld, #fld)
+
+ int bad = s->no_sym +
+ s->no_insn +
+ s->no_insn_ops +
+ s->no_mem_ops +
+ s->no_reg +
+ s->no_dbginfo +
+ s->no_cuinfo +
+ s->no_var +
+ s->no_typeinfo +
+ s->invalid_size +
+ s->bad_offset;
+ int ok = s->total - bad;
+
+ printf("Annotate data type stats:\n");
+ printf("total %d, ok %d (%.1f%%), bad %d (%.1f%%)\n",
+ s->total, ok, 100.0 * ok / (s->total ?: 1), bad, 100.0 * bad / (s->total ?: 1));
+ printf("-----------------------------------------------------------\n");
+ PRINT_STAT(no_sym);
+ PRINT_STAT(no_insn);
+ PRINT_STAT(no_insn_ops);
+ PRINT_STAT(no_mem_ops);
+ PRINT_STAT(no_reg);
+ PRINT_STAT(no_dbginfo);
+ PRINT_STAT(no_cuinfo);
+ PRINT_STAT(no_var);
+ PRINT_STAT(no_typeinfo);
+ PRINT_STAT(invalid_size);
+ PRINT_STAT(bad_offset);
+ printf("\n");
+
+#undef PRINT_STAT
+}
+
static void hists__find_annotations(struct hists *hists,
struct evsel *evsel,
struct perf_annotate *ann)
@@ -403,6 +441,9 @@ static void hists__find_annotations(struct hists *hists,
struct rb_node *nd = rb_first_cached(&hists->entries), *next;
int key = K_RIGHT;

+ if (ann->type_stat)
+ print_annotate_data_stat(&ann_data_stat);
+
while (nd) {
struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node);
struct annotation *notes;
@@ -749,7 +790,8 @@ int cmd_annotate(int argc, const char **argv)
OPT_CALLBACK_OPTARG(0, "data-type", &annotate, NULL, "name",
"Show data type annotate for the memory accesses",
parse_data_type),
-
+ OPT_BOOLEAN(0, "type-stat", &annotate.type_stat,
+ "Show stats for the data type annotation"),
OPT_END()
};
int ret;
diff --git a/tools/perf/util/annotate-data.c b/tools/perf/util/annotate-data.c
index 06be6b279d6a..2ef4daf6b12b 100644
--- a/tools/perf/util/annotate-data.c
+++ b/tools/perf/util/annotate-data.c
@@ -198,6 +198,7 @@ static int check_variable(Dwarf_Die *var_die, Dwarf_Die *type_die, int offset)
/* Get the type of the variable */
if (die_get_real_type(var_die, type_die) == NULL) {
pr_debug("variable has no type\n");
+ ann_data_stat.no_typeinfo++;
return -1;
}

@@ -208,18 +209,21 @@ static int check_variable(Dwarf_Die *var_die, Dwarf_Die *type_die, int offset)
if (dwarf_tag(type_die) != DW_TAG_pointer_type ||
die_get_real_type(type_die, type_die) == NULL) {
pr_debug("no pointer or no type\n");
+ ann_data_stat.no_typeinfo++;
return -1;
}

/* Get the size of the actual type */
if (dwarf_aggregate_size(type_die, &size) < 0) {
pr_debug("type size is unknown\n");
+ ann_data_stat.invalid_size++;
return -1;
}

/* Minimal sanity check */
if ((unsigned)offset >= size) {
pr_debug("offset: %d is bigger than size: %lu\n", offset, size);
+ ann_data_stat.bad_offset++;
return -1;
}

@@ -238,6 +242,7 @@ static int find_data_type_die(struct debuginfo *di, u64 pc,
/* Get a compile_unit for this address */
if (!find_cu_die(di, pc, &cu_die)) {
pr_debug("cannot find CU for address %lx\n", pc);
+ ann_data_stat.no_cuinfo++;
return -1;
}

@@ -252,9 +257,12 @@ static int find_data_type_die(struct debuginfo *di, u64 pc,

/* Found a variable, see if it's correct */
ret = check_variable(&var_die, type_die, offset);
- break;
+ goto out;
}
+ if (ret < 0)
+ ann_data_stat.no_var++;

+out:
free(scopes);
return ret;
}
diff --git a/tools/perf/util/annotate-data.h b/tools/perf/util/annotate-data.h
index d2dc025b1934..8e73096c01d1 100644
--- a/tools/perf/util/annotate-data.h
+++ b/tools/perf/util/annotate-data.h
@@ -70,6 +70,37 @@ struct annotated_data_type {

extern struct annotated_data_type unknown_type;

+/**
+ * struct annotated_data_stat - Debug statistics
+ * @total: Total number of entry
+ * @no_sym: No symbol or map found
+ * @no_insn: Failed to get disasm line
+ * @no_insn_ops: The instruction has no operands
+ * @no_mem_ops: The instruction has no memory operands
+ * @no_reg: Failed to extract a register from the operand
+ * @no_dbginfo: The binary has no debug information
+ * @no_cuinfo: Failed to find a compile_unit
+ * @no_var: Failed to find a matching variable
+ * @no_typeinfo: Failed to get a type info for the variable
+ * @invalid_size: Failed to get a size info of the type
+ * @bad_offset: The access offset is out of the type
+ */
+struct annotated_data_stat {
+ int total;
+ int no_sym;
+ int no_insn;
+ int no_insn_ops;
+ int no_mem_ops;
+ int no_reg;
+ int no_dbginfo;
+ int no_cuinfo;
+ int no_var;
+ int no_typeinfo;
+ int invalid_size;
+ int bad_offset;
+};
+extern struct annotated_data_stat ann_data_stat;
+
#ifdef HAVE_DWARF_SUPPORT

/* Returns data type at the location (ip, reg, offset) */
diff --git a/tools/perf/util/annotate.c b/tools/perf/util/annotate.c
index 68424ee0215e..9870257ce21e 100644
--- a/tools/perf/util/annotate.c
+++ b/tools/perf/util/annotate.c
@@ -103,6 +103,9 @@ static struct ins_ops nop_ops;
static struct ins_ops lock_ops;
static struct ins_ops ret_ops;

+/* Data type collection debug statistics */
+struct annotated_data_stat ann_data_stat;
+
static int arch__grow_instructions(struct arch *arch)
{
struct ins *new_instructions;
@@ -3683,14 +3686,22 @@ struct annotated_data_type *hist_entry__get_data_type(struct hist_entry *he)
u64 ip = he->ip;
int i;

- if (ms->map == NULL || ms->sym == NULL)
+ ann_data_stat.total++;
+
+ if (ms->map == NULL || ms->sym == NULL) {
+ ann_data_stat.no_sym++;
return NULL;
+ }

- if (!symbol_conf.init_annotation)
+ if (!symbol_conf.init_annotation) {
+ ann_data_stat.no_sym++;
return NULL;
+ }

- if (evsel__get_arch(evsel, &arch) < 0)
+ if (evsel__get_arch(evsel, &arch) < 0) {
+ ann_data_stat.no_insn++;
return NULL;
+ }

/* Make sure it runs objdump to get disasm of the function */
symbol__ensure_annotate(ms, evsel);
@@ -3700,11 +3711,15 @@ struct annotated_data_type *hist_entry__get_data_type(struct hist_entry *he)
* This is too slow...
*/
dl = find_disasm_line(ms->sym, ip);
- if (dl == NULL)
+ if (dl == NULL) {
+ ann_data_stat.no_insn++;
return NULL;
+ }

- if (annotate_get_insn_location(arch, dl, &loc) < 0)
+ if (annotate_get_insn_location(arch, dl, &loc) < 0) {
+ ann_data_stat.no_insn_ops++;
return NULL;
+ }

for_each_insn_op_loc(&loc, i, op_loc) {
if (!op_loc->mem_ref)
@@ -3721,5 +3736,7 @@ struct annotated_data_type *hist_entry__get_data_type(struct hist_entry *he)
he->mem_type_off = op_loc->offset;
return mem_type;
}
+
+ ann_data_stat.no_mem_ops++;
return NULL;
}
--
2.43.0.472.g3155946c3a-goog

2023-12-13 00:16:11

by Namhyung Kim

[permalink] [raw]
Subject: [PATCH 12/17] perf report: Add 'typeoff' sort key

The typeoff sort key shows the data type name, offset and the name of
the field. This is useful to see which field in the struct is accessed
most frequently.

$ perf report -s type,typeoff --hierarchy --stdio
...
# Overhead Data Type / Data Type Offset
# ............ ............................
#
...
1.23% struct cfs_rq
0.19% struct cfs_rq +404 (throttle_count)
0.19% struct cfs_rq +0 (load.weight)
0.19% struct cfs_rq +336 (leaf_cfs_rq_list.next)
0.09% struct cfs_rq +272 (propagate)
0.09% struct cfs_rq +196 (removed.nr)
0.09% struct cfs_rq +80 (curr)
0.09% struct cfs_rq +544 (lt_b_children_throttled)
0.06% struct cfs_rq +320 (rq)

Signed-off-by: Namhyung Kim <[email protected]>
---
tools/perf/Documentation/perf-report.txt | 1 +
tools/perf/util/annotate.c | 1 +
tools/perf/util/hist.h | 1 +
tools/perf/util/sort.c | 83 +++++++++++++++++++++++-
tools/perf/util/sort.h | 2 +
5 files changed, 87 insertions(+), 1 deletion(-)

diff --git a/tools/perf/Documentation/perf-report.txt b/tools/perf/Documentation/perf-report.txt
index aec34417090b..b57eb51b47aa 100644
--- a/tools/perf/Documentation/perf-report.txt
+++ b/tools/perf/Documentation/perf-report.txt
@@ -119,6 +119,7 @@ OPTIONS
to the previous instruction in cycles. And currently supported only on X86
- simd: Flags describing a SIMD operation. "e" for empty Arm SVE predicate. "p" for partial Arm SVE predicate
- type: Data type of sample memory access.
+ - typeoff: Offset in the data type of sample memory access.

By default, comm, dso and symbol keys are used.
(i.e. --sort comm,dso,symbol)
diff --git a/tools/perf/util/annotate.c b/tools/perf/util/annotate.c
index 6747779ecef8..f966e8f83c5e 100644
--- a/tools/perf/util/annotate.c
+++ b/tools/perf/util/annotate.c
@@ -3716,6 +3716,7 @@ struct annotated_data_type *hist_entry__get_data_type(struct hist_entry *he)
op_loc->offset,
he->stat.nr_events,
he->stat.period);
+ he->mem_type_off = op_loc->offset;
return mem_type;
}
return NULL;
diff --git a/tools/perf/util/hist.h b/tools/perf/util/hist.h
index 7ebbf427b1ea..18128a49309e 100644
--- a/tools/perf/util/hist.h
+++ b/tools/perf/util/hist.h
@@ -83,6 +83,7 @@ enum hist_column {
HISTC_ADDR,
HISTC_SIMD,
HISTC_TYPE,
+ HISTC_TYPE_OFFSET,
HISTC_NR_COLS, /* Last entry */
};

diff --git a/tools/perf/util/sort.c b/tools/perf/util/sort.c
index a41209e242ae..d78e680d3988 100644
--- a/tools/perf/util/sort.c
+++ b/tools/perf/util/sort.c
@@ -2153,8 +2153,10 @@ static void sort__type_init(struct hist_entry *he)
return;

he->mem_type = hist_entry__get_data_type(he);
- if (he->mem_type == NULL)
+ if (he->mem_type == NULL) {
he->mem_type = &unknown_type;
+ he->mem_type_off = 0;
+ }
}

static int64_t
@@ -2198,6 +2200,84 @@ struct sort_entry sort_type = {
.se_width_idx = HISTC_TYPE,
};

+/* --sort typeoff */
+
+static int64_t
+sort__typeoff_sort(struct hist_entry *left, struct hist_entry *right)
+{
+ struct annotated_data_type *left_type = left->mem_type;
+ struct annotated_data_type *right_type = right->mem_type;
+ int64_t ret;
+
+ if (!left_type) {
+ sort__type_init(left);
+ left_type = left->mem_type;
+ }
+
+ if (!right_type) {
+ sort__type_init(right);
+ right_type = right->mem_type;
+ }
+
+ ret = strcmp(left_type->self.type_name, right_type->self.type_name);
+ if (ret)
+ return ret;
+ return left->mem_type_off - right->mem_type_off;
+}
+
+static void fill_member_name(char *buf, size_t sz, struct annotated_member *m,
+ int offset, bool first)
+{
+ struct annotated_member *child;
+
+ if (list_empty(&m->children))
+ return;
+
+ list_for_each_entry(child, &m->children, node) {
+ if (child->offset <= offset && offset < child->offset + child->size) {
+ int len = 0;
+
+ /* It can have anonymous struct/union members */
+ if (child->var_name) {
+ len = scnprintf(buf, sz, "%s%s",
+ first ? "" : ".", child->var_name);
+ first = false;
+ }
+
+ fill_member_name(buf + len, sz - len, child, offset, first);
+ return;
+ }
+ }
+}
+
+static int hist_entry__typeoff_snprintf(struct hist_entry *he, char *bf,
+ size_t size, unsigned int width __maybe_unused)
+{
+ struct annotated_data_type *he_type = he->mem_type;
+ char buf[4096];
+
+ buf[0] = '\0';
+ if (list_empty(&he_type->self.children))
+ snprintf(buf, sizeof(buf), "no field");
+ else
+ fill_member_name(buf, sizeof(buf), &he_type->self,
+ he->mem_type_off, true);
+ buf[4095] = '\0';
+
+ return repsep_snprintf(bf, size, "%s %+d (%s)", he_type->self.type_name,
+ he->mem_type_off, buf);
+}
+
+struct sort_entry sort_type_offset = {
+ .se_header = "Data Type Offset",
+ .se_cmp = sort__type_cmp,
+ .se_collapse = sort__typeoff_sort,
+ .se_sort = sort__typeoff_sort,
+ .se_init = sort__type_init,
+ .se_snprintf = hist_entry__typeoff_snprintf,
+ .se_width_idx = HISTC_TYPE_OFFSET,
+};
+

struct sort_dimension {
const char *name;
@@ -2254,6 +2334,7 @@ static struct sort_dimension common_sort_dimensions[] = {
DIM(SORT_GLOBAL_RETIRE_LAT, "retire_lat", sort_global_p_stage_cyc),
DIM(SORT_SIMD, "simd", sort_simd),
DIM(SORT_ANNOTATE_DATA_TYPE, "type", sort_type),
+ DIM(SORT_ANNOTATE_DATA_TYPE_OFFSET, "typeoff", sort_type_offset),
};

#undef DIM
diff --git a/tools/perf/util/sort.h b/tools/perf/util/sort.h
index aabf0b8331a3..d806adcc1e1e 100644
--- a/tools/perf/util/sort.h
+++ b/tools/perf/util/sort.h
@@ -113,6 +113,7 @@ struct hist_entry {
u64 p_stage_cyc;
u8 cpumode;
u8 depth;
+ int mem_type_off;
struct simd_flags simd_flags;

/* We are added by hists__add_dummy_entry. */
@@ -247,6 +248,7 @@ enum sort_type {
SORT_GLOBAL_RETIRE_LAT,
SORT_SIMD,
SORT_ANNOTATE_DATA_TYPE,
+ SORT_ANNOTATE_DATA_TYPE_OFFSET,

/* branch stack specific sort keys */
__SORT_BRANCH_STACK,
--
2.43.0.472.g3155946c3a-goog