perf hist: Implement output fields for mem stats

This is a preparation for later changes to support mem_stat output.  The
new fields will need two lines for the header - the first line will show
type of mem stat and the second line will show the name of each item
which is returned by mem_stat_name().

Each element in the mem_stat array will be printed in percentage for the
hist_entry and their sum would be 100%.

Add new output field dimension only for SORT_MODE__MEM using mem_stat.

To handle possible name conflict with existing sort keys, move the order
of checking output field dimensions after the sort dimensions when it
looks for sort keys.

Signed-off-by: Namhyung Kim <namhyung@kernel.org>
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: Ian Rogers <irogers@google.com>
Cc: Ingo Molnar <mingo@kernel.org>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Kan Liang <kan.liang@linux.intel.com>
Cc: Leo Yan <leo.yan@arm.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Ravi Bangoria <ravi.bangoria@amd.com>
Link: https://lore.kernel.org/r/20250430205548.789750-7-namhyung@kernel.org
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
This commit is contained in:
Namhyung Kim 2025-04-30 13:55:43 -07:00 committed by Arnaldo Carvalho de Melo
parent 9fcb43e27c
commit b1fc83ca43
6 changed files with 202 additions and 12 deletions

View File

@ -1266,6 +1266,16 @@ hist_browser__hpp_color_##_type(struct perf_hpp_fmt *fmt, \
_fmttype); \
}
#define __HPP_COLOR_MEM_STAT_FN(_name, _type) \
static int \
hist_browser__hpp_color_mem_stat_##_name(struct perf_hpp_fmt *fmt, \
struct perf_hpp *hpp, \
struct hist_entry *he) \
{ \
return hpp__fmt_mem_stat(fmt, hpp, he, PERF_MEM_STAT_##_type, \
" %5.1f%%", __hpp__slsmg_color_printf);\
}
__HPP_COLOR_PERCENT_FN(overhead, period, PERF_HPP_FMT_TYPE__PERCENT)
__HPP_COLOR_PERCENT_FN(latency, latency, PERF_HPP_FMT_TYPE__LATENCY)
__HPP_COLOR_PERCENT_FN(overhead_sys, period_sys, PERF_HPP_FMT_TYPE__PERCENT)
@ -1277,6 +1287,7 @@ __HPP_COLOR_ACC_PERCENT_FN(latency_acc, latency, PERF_HPP_FMT_TYPE__LATENCY)
#undef __HPP_COLOR_PERCENT_FN
#undef __HPP_COLOR_ACC_PERCENT_FN
#undef __HPP_COLOR_MEM_STAT_FN
void hist_browser__init_hpp(void)
{

View File

@ -12,6 +12,7 @@
#include "../util/evsel.h"
#include "../util/evlist.h"
#include "../util/mem-events.h"
#include "../util/string2.h"
#include "../util/thread.h"
#include "../util/util.h"
@ -151,6 +152,45 @@ int hpp__fmt_acc(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
return hpp__fmt(fmt, hpp, he, get_field, fmtstr, print_fn, fmtype);
}
int hpp__fmt_mem_stat(struct perf_hpp_fmt *fmt __maybe_unused, struct perf_hpp *hpp,
struct hist_entry *he, enum mem_stat_type mst,
const char *fmtstr, hpp_snprint_fn print_fn)
{
struct hists *hists = he->hists;
int mem_stat_idx = -1;
char *buf = hpp->buf;
size_t size = hpp->size;
u64 total = 0;
int ret = 0;
for (int i = 0; i < hists->nr_mem_stats; i++) {
if (hists->mem_stat_types[i] == mst) {
mem_stat_idx = i;
break;
}
}
assert(mem_stat_idx != -1);
for (int i = 0; i < MEM_STAT_LEN; i++)
total += he->mem_stat[mem_stat_idx].entries[i];
assert(total != 0);
for (int i = 0; i < MEM_STAT_LEN; i++) {
u64 val = he->mem_stat[mem_stat_idx].entries[i];
ret += hpp__call_print_fn(hpp, print_fn, fmtstr, 100.0 * val / total);
}
/*
* Restore original buf and size as it's where caller expects
* the result will be saved.
*/
hpp->buf = buf;
hpp->size = size;
return ret;
}
static int field_cmp(u64 field_a, u64 field_b)
{
if (field_a > field_b)
@ -295,6 +335,23 @@ static int __hpp__sort_acc(struct hist_entry *a, struct hist_entry *b,
return ret;
}
static bool perf_hpp__is_mem_stat_entry(struct perf_hpp_fmt *fmt);
static enum mem_stat_type hpp__mem_stat_type(struct perf_hpp_fmt *fmt)
{
if (!perf_hpp__is_mem_stat_entry(fmt))
return -1;
pr_debug("Should not reach here\n");
return -1;
}
static int64_t hpp__sort_mem_stat(struct perf_hpp_fmt *fmt __maybe_unused,
struct hist_entry *a, struct hist_entry *b)
{
return a->stat.period - b->stat.period;
}
static int hpp__width_fn(struct perf_hpp_fmt *fmt,
struct perf_hpp *hpp __maybe_unused,
struct hists *hists)
@ -334,6 +391,45 @@ static int hpp__header_fn(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
return scnprintf(hpp->buf, hpp->size, "%*s", len, hdr);
}
static int hpp__header_mem_stat_fn(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
struct hists *hists, int line,
int *span __maybe_unused)
{
char *buf = hpp->buf;
int ret = 0;
int len;
enum mem_stat_type mst = hpp__mem_stat_type(fmt);
(void)hists;
if (line == 0) {
int left, right;
len = fmt->len;
left = (len - strlen(fmt->name)) / 2 - 1;
right = len - left - strlen(fmt->name) - 2;
if (left < 0)
left = 0;
if (right < 0)
right = 0;
return scnprintf(hpp->buf, hpp->size, "%.*s %s %.*s",
left, graph_dotted_line, fmt->name, right, graph_dotted_line);
}
len = hpp->size;
for (int i = 0; i < MEM_STAT_LEN; i++) {
int printed;
printed = scnprintf(buf, len, "%*s", MEM_STAT_PRINT_LEN,
mem_stat_name(mst, i));
ret += printed;
buf += printed;
len -= printed;
}
return ret;
}
int hpp_color_scnprintf(struct perf_hpp *hpp, const char *fmt, ...)
{
va_list args;
@ -459,6 +555,23 @@ static int64_t hpp__sort_##_type(struct perf_hpp_fmt *fmt __maybe_unused, \
return __hpp__sort(a, b, he_get_##_field); \
}
#define __HPP_COLOR_MEM_STAT_FN(_name, _type) \
static int hpp__color_mem_stat_##_name(struct perf_hpp_fmt *fmt, \
struct perf_hpp *hpp, \
struct hist_entry *he) \
{ \
return hpp__fmt_mem_stat(fmt, hpp, he, PERF_MEM_STAT_##_type, \
" %5.1f%%", hpp_color_scnprintf); \
}
#define __HPP_ENTRY_MEM_STAT_FN(_name, _type) \
static int hpp__entry_mem_stat_##_name(struct perf_hpp_fmt *fmt, \
struct perf_hpp *hpp, \
struct hist_entry *he) \
{ \
return hpp__fmt_mem_stat(fmt, hpp, he, PERF_MEM_STAT_##_type, \
" %5.1f%%", hpp_entry_scnprintf); \
}
#define HPP_PERCENT_FNS(_type, _field, _fmttype) \
__HPP_COLOR_PERCENT_FN(_type, _field, _fmttype) \
@ -478,6 +591,10 @@ __HPP_SORT_RAW_FN(_type, _field)
__HPP_ENTRY_AVERAGE_FN(_type, _field) \
__HPP_SORT_AVERAGE_FN(_type, _field)
#define HPP_MEM_STAT_FNS(_name, _type) \
__HPP_COLOR_MEM_STAT_FN(_name, _type) \
__HPP_ENTRY_MEM_STAT_FN(_name, _type)
HPP_PERCENT_FNS(overhead, period, PERF_HPP_FMT_TYPE__PERCENT)
HPP_PERCENT_FNS(latency, latency, PERF_HPP_FMT_TYPE__LATENCY)
HPP_PERCENT_FNS(overhead_sys, period_sys, PERF_HPP_FMT_TYPE__PERCENT)
@ -494,6 +611,8 @@ HPP_AVERAGE_FNS(weight1, weight1)
HPP_AVERAGE_FNS(weight2, weight2)
HPP_AVERAGE_FNS(weight3, weight3)
HPP_MEM_STAT_FNS(unknown, UNKNOWN) /* placeholder */
static int64_t hpp__nop_cmp(struct perf_hpp_fmt *fmt __maybe_unused,
struct hist_entry *a __maybe_unused,
struct hist_entry *b __maybe_unused)
@ -503,8 +622,7 @@ static int64_t hpp__nop_cmp(struct perf_hpp_fmt *fmt __maybe_unused,
static bool perf_hpp__is_mem_stat_entry(struct perf_hpp_fmt *fmt)
{
(void)fmt;
return false;
return fmt->sort == hpp__sort_mem_stat;
}
static bool perf_hpp__is_hpp_entry(struct perf_hpp_fmt *a)
@ -520,6 +638,14 @@ static bool hpp__equal(struct perf_hpp_fmt *a, struct perf_hpp_fmt *b)
return a->idx == b->idx;
}
static bool hpp__equal_mem_stat(struct perf_hpp_fmt *a, struct perf_hpp_fmt *b)
{
if (!perf_hpp__is_mem_stat_entry(a) || !perf_hpp__is_mem_stat_entry(b))
return false;
return a->entry == b->entry;
}
#define HPP__COLOR_PRINT_FNS(_name, _fn, _idx) \
{ \
.name = _name, \
@ -561,6 +687,20 @@ static bool hpp__equal(struct perf_hpp_fmt *a, struct perf_hpp_fmt *b)
.equal = hpp__equal, \
}
#define HPP__MEM_STAT_PRINT_FNS(_name, _fn, _type) \
{ \
.name = _name, \
.header = hpp__header_mem_stat_fn, \
.width = hpp__width_fn, \
.color = hpp__color_mem_stat_ ## _fn, \
.entry = hpp__entry_mem_stat_ ## _fn, \
.cmp = hpp__nop_cmp, \
.collapse = hpp__nop_cmp, \
.sort = hpp__sort_mem_stat, \
.idx = PERF_HPP__MEM_STAT_ ## _type, \
.equal = hpp__equal_mem_stat, \
}
struct perf_hpp_fmt perf_hpp__format[] = {
HPP__COLOR_PRINT_FNS("Overhead", overhead, OVERHEAD),
HPP__COLOR_PRINT_FNS("Latency", latency, LATENCY),
@ -575,6 +715,7 @@ struct perf_hpp_fmt perf_hpp__format[] = {
HPP__PRINT_FNS("Weight1", weight1, WEIGHT1),
HPP__PRINT_FNS("Weight2", weight2, WEIGHT2),
HPP__PRINT_FNS("Weight3", weight3, WEIGHT3),
HPP__MEM_STAT_PRINT_FNS("Unknown", unknown, UNKNOWN), /* placeholder */
};
struct perf_hpp_list perf_hpp_list = {
@ -586,11 +727,13 @@ struct perf_hpp_list perf_hpp_list = {
#undef HPP__COLOR_PRINT_FNS
#undef HPP__COLOR_ACC_PRINT_FNS
#undef HPP__PRINT_FNS
#undef HPP__MEM_STAT_PRINT_FNS
#undef HPP_PERCENT_FNS
#undef HPP_PERCENT_ACC_FNS
#undef HPP_RAW_FNS
#undef HPP_AVERAGE_FNS
#undef HPP_MEM_STAT_FNS
#undef __HPP_HEADER_FN
#undef __HPP_WIDTH_FN
@ -600,6 +743,9 @@ struct perf_hpp_list perf_hpp_list = {
#undef __HPP_ENTRY_ACC_PERCENT_FN
#undef __HPP_ENTRY_RAW_FN
#undef __HPP_ENTRY_AVERAGE_FN
#undef __HPP_COLOR_MEM_STAT_FN
#undef __HPP_ENTRY_MEM_STAT_FN
#undef __HPP_SORT_FN
#undef __HPP_SORT_ACC_FN
#undef __HPP_SORT_RAW_FN
@ -924,6 +1070,10 @@ void perf_hpp__reset_width(struct perf_hpp_fmt *fmt, struct hists *hists)
fmt->len = 8;
break;
case PERF_HPP__MEM_STAT_UNKNOWN: /* placeholder */
fmt->len = MEM_STAT_LEN * MEM_STAT_PRINT_LEN;
break;
default:
break;
}
@ -1042,12 +1192,14 @@ int perf_hpp__alloc_mem_stats(struct perf_hpp_list *list, struct evlist *evlist)
continue;
assert(nr_mem_stats < ARRAY_SIZE(mst));
mst[nr_mem_stats++] = PERF_MEM_STAT_UNKNOWN;
mst[nr_mem_stats++] = hpp__mem_stat_type(fmt);
}
if (nr_mem_stats == 0)
return 0;
list->nr_header_lines = 2;
evlist__for_each_entry(evlist, evsel) {
struct hists *hists = evsel__hists(evsel);

View File

@ -587,6 +587,7 @@ enum {
PERF_HPP__WEIGHT1,
PERF_HPP__WEIGHT2,
PERF_HPP__WEIGHT3,
PERF_HPP__MEM_STAT_UNKNOWN, /* placeholder */
PERF_HPP__MAX_INDEX
};
@ -656,6 +657,9 @@ int hpp__fmt_acc(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
struct hist_entry *he, hpp_field_fn get_field,
const char *fmtstr, hpp_snprint_fn print_fn,
enum perf_hpp_fmt_type fmtype);
int hpp__fmt_mem_stat(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
struct hist_entry *he, enum mem_stat_type mst,
const char *fmtstr, hpp_snprint_fn print_fn);
static inline void advance_hpp(struct perf_hpp *hpp, int inc)
{

View File

@ -817,3 +817,15 @@ int mem_stat_index(const enum mem_stat_type mst, const u64 val)
(void)val;
return -1;
}
/* To align output, returned string should be shorter than MEM_STAT_PRINT_LEN */
const char *mem_stat_name(const enum mem_stat_type mst, const int idx)
{
switch (mst) {
case PERF_MEM_STAT_UNKNOWN:
default:
break;
}
(void)idx;
return "N/A";
}

View File

@ -93,6 +93,9 @@ enum mem_stat_type {
PERF_MEM_STAT_UNKNOWN, /* placeholder */
};
#define MEM_STAT_PRINT_LEN 7 /* 1 space + 5 digits + 1 percent sign */
int mem_stat_index(const enum mem_stat_type mst, const u64 data_src);
const char *mem_stat_name(const enum mem_stat_type mst, const int idx);
#endif /* __PERF_MEM_EVENTS_H */

View File

@ -2598,9 +2598,11 @@ struct hpp_dimension {
struct perf_hpp_fmt *fmt;
int taken;
int was_taken;
int mem_mode;
};
#define DIM(d, n) { .name = n, .fmt = &perf_hpp__format[d], }
#define DIM_MEM(d, n) { .name = n, .fmt = &perf_hpp__format[d], .mem_mode = 1, }
static struct hpp_dimension hpp_sort_dimensions[] = {
DIM(PERF_HPP__OVERHEAD, "overhead"),
@ -2620,8 +2622,11 @@ static struct hpp_dimension hpp_sort_dimensions[] = {
DIM(PERF_HPP__WEIGHT2, "ins_lat"),
DIM(PERF_HPP__WEIGHT3, "retire_lat"),
DIM(PERF_HPP__WEIGHT3, "p_stage_cyc"),
/* used for output only when SORT_MODE__MEM */
DIM_MEM(PERF_HPP__MEM_STAT_UNKNOWN, "unknown"), /* placeholder */
};
#undef DIM_MEM
#undef DIM
struct hpp_sort_entry {
@ -3608,15 +3613,6 @@ int sort_dimension__add(struct perf_hpp_list *list, const char *tok,
return __sort_dimension__add(sd, list, level);
}
for (i = 0; i < ARRAY_SIZE(hpp_sort_dimensions); i++) {
struct hpp_dimension *hd = &hpp_sort_dimensions[i];
if (strncasecmp(tok, hd->name, strlen(tok)))
continue;
return __hpp_dimension__add(hd, list, level);
}
for (i = 0; i < ARRAY_SIZE(bstack_sort_dimensions); i++) {
struct sort_dimension *sd = &bstack_sort_dimensions[i];
@ -3658,6 +3654,15 @@ int sort_dimension__add(struct perf_hpp_list *list, const char *tok,
return 0;
}
for (i = 0; i < ARRAY_SIZE(hpp_sort_dimensions); i++) {
struct hpp_dimension *hd = &hpp_sort_dimensions[i];
if (strncasecmp(tok, hd->name, strlen(tok)))
continue;
return __hpp_dimension__add(hd, list, level);
}
if (!add_dynamic_entry(evlist, tok, level))
return 0;
@ -4020,6 +4025,9 @@ int output_field_add(struct perf_hpp_list *list, const char *tok, int *level)
if (!strcasecmp(tok, "weight"))
ui__warning("--fields weight shows the average value unlike in the --sort key.\n");
if (hd->mem_mode && sort__mode != SORT_MODE__MEMORY)
continue;
return __hpp_dimension__add_output(list, hd, *level);
}