]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
perf hist: Implement output fields for mem stats
authorNamhyung Kim <namhyung@kernel.org>
Wed, 30 Apr 2025 20:55:43 +0000 (13:55 -0700)
committerArnaldo Carvalho de Melo <acme@redhat.com>
Fri, 2 May 2025 18:36:14 +0000 (15:36 -0300)
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>
tools/perf/ui/browsers/hists.c
tools/perf/ui/hist.c
tools/perf/util/hist.h
tools/perf/util/mem-events.c
tools/perf/util/mem-events.h
tools/perf/util/sort.c

index 67cbdec90d0bf0ea83221c86b40e6c20fee8a3a4..f6ab1310a0bdd6c4677a13ca26b2d9826237501c 100644 (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)
 {
index 2aad46bbd2ed4d93405a83479d660d9c726facd8..2a5c9f2b328b2c5cfab6e4aef612d56cf452c04f 100644 (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);
 
index 509af09691b84e1046e6b855ee75b067a9e4df88..18c696d8d568a9fa4653cdc5c0aaf9ccaa9dc662 100644 (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)
 {
index 1bc60ad3dc3125429c148d7ef1b9256658864724..a4c1e42de30f830787b2b5307bfa4d9c422d3eac 100644 (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";
+}
index 2604464f985815f683b2c18bac41d54144ceb7f0..7aeb4c5fefc8969810fc5c82b78f21baf2fdcf6e 100644 (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 */
index 6024f588f66f3156bd3ed51764d57abfa6f6f8db..7c669ea27af247e5dae854ebddff830ddb29f4cf 100644 (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);
        }