Re: [PATCH] perf lock contention: Add -x option for CSV style output

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Wed, Jun 7, 2023 at 10:38 PM Namhyung Kim <namhyung@xxxxxxxxxx> wrote:
>
> Sometimes we want to process the output by external programs.  Let's add
> the -x option to specify the field separator like perf stat.
>
>   $ sudo ./perf lock con -ab -x, sleep 1
>   # output: contended, total wait, max wait, avg wait, type, caller
>   19, 194232, 21415, 10222, spinlock, process_one_work+0x1f0
>   15, 162748, 23843, 10849, rwsem:R, do_user_addr_fault+0x40e
>   4, 86740, 23415, 21685, rwlock:R, ep_poll_callback+0x2d
>   1, 84281, 84281, 84281, mutex, iwl_mvm_async_handlers_wk+0x135
>   8, 67608, 27404, 8451, spinlock, __queue_work+0x174
>   3, 58616, 31125, 19538, rwsem:W, do_mprotect_pkey+0xff
>   3, 52953, 21172, 17651, rwlock:W, do_epoll_wait+0x248
>   2, 30324, 19704, 15162, rwsem:R, do_madvise+0x3ad
>   1, 24619, 24619, 24619, spinlock, rcu_core+0xd4
>
> The first line is a comment that shows the output format.  Each line is
> separated by the given string ("," in this case).  The time is printed
> in nsec without the unit so that it can be parsed easily.
>
> The characters can be used in the output like (":", "+" and ".") are not
> allowed for the -x option.
>
>   $ ./perf lock con -x:
>   Cannot use the separator that is already used
>
>    Usage: perf lock contention [<options>]
>
>       -x, --field-separator <separator>
>                             print result in CSV format with custom separator
>
> The stacktraces are printed in the same line separated by ":".  The
> header is updated to show the stacktrace.  Also the debug output is
> added at the end as a comment.
>
>   $ sudo ./perf lock con -abv -x, -F wait_total sleep 1
>   Looking at the vmlinux_path (8 entries long)
>   symsrc__init: cannot get elf header.
>   Using /proc/kcore for kernel data
>   Using /proc/kallsyms for symbols
>   # output: total wait, type, caller, stacktrace
>   37134, spinlock, rcu_core+0xd4, 0xffffffff9d0401e4 _raw_spin_lock_irqsave+0x44: 0xffffffff9c738114 rcu_core+0xd4: ...
>   21213, spinlock, raw_spin_rq_lock_nested+0x1b, 0xffffffff9d0407c0 _raw_spin_lock+0x30: 0xffffffff9c6d9cfb raw_spin_rq_lock_nested+0x1b: ...
>   20506, rwlock:W, ep_done_scan+0x2d, 0xffffffff9c9bc4dd ep_done_scan+0x2d: 0xffffffff9c9bd5f1 do_epoll_wait+0x6d1: ...
>   18044, rwlock:R, ep_poll_callback+0x2d, 0xffffffff9d040555 _raw_read_lock_irqsave+0x45: 0xffffffff9c9bc81d ep_poll_callback+0x2d: ...
>   17890, rwlock:W, do_epoll_wait+0x47b, 0xffffffff9c9bd39b do_epoll_wait+0x47b: 0xffffffff9c9be9ef __x64_sys_epoll_wait+0x6d1: ...
>   12114, spinlock, futex_wait_queue+0x60, 0xffffffff9d0407c0 _raw_spin_lock+0x30: 0xffffffff9d037cae __schedule+0xbe: ...
>   # debug: total=7, bad=0, bad_task=0, bad_stack=0, bad_time=0, bad_data=0
>
> Also note that some field (like lock symbols) can be empty.
>
>   $ sudo ./perf lock con -abl -x, -E 10 sleep 1
>   # output: contended, total wait, max wait, avg wait, address, symbol, type
>   6, 275025, 61764, 45837, ffff9dcc9f7d60d0, , spinlock
>   18, 87716, 11196, 4873, ffff9dc540059000, , spinlock
>   2, 6472, 5499, 3236, ffff9dcc7f730e00, rq_lock, spinlock
>   3, 4429, 2341, 1476, ffff9dcc7f7b0e00, rq_lock, spinlock
>   3, 3974, 1635, 1324, ffff9dcc7f7f0e00, rq_lock, spinlock
>   4, 3290, 1326, 822, ffff9dc5f4e2cde0, , rwlock
>   3, 2894, 1023, 964, ffffffff9e0d7700, rcu_state, spinlock
>   1, 2567, 2567, 2567, ffff9dcc7f6b0e00, rq_lock, spinlock
>   4, 1259, 596, 314, ffff9dc69c2adde0, , rwlock
>   1, 934, 934, 934, ffff9dcc7f670e00, rq_lock, spinlock
>
> Signed-off-by: Namhyung Kim <namhyung@xxxxxxxxxx>

Acked-by: Ian Rogers <irogers@xxxxxxxxxx>

I think this would benefit from:
1) an output file option, so that debug messages and the lie don't
break the CSV formatting,
2) a test :-)

Thanks,
Ian

> ---
>  tools/perf/Documentation/perf-lock.txt |   5 +
>  tools/perf/builtin-lock.c              | 303 +++++++++++++++++++------
>  2 files changed, 241 insertions(+), 67 deletions(-)
>
> diff --git a/tools/perf/Documentation/perf-lock.txt b/tools/perf/Documentation/perf-lock.txt
> index 6e5ba3cd2b72..d1efcbe7a470 100644
> --- a/tools/perf/Documentation/perf-lock.txt
> +++ b/tools/perf/Documentation/perf-lock.txt
> @@ -200,6 +200,11 @@ CONTENTION OPTIONS
>         Note that it matches the substring so 'rq' would match both 'raw_spin_rq_lock'
>         and 'irq_enter_rcu'.
>
> +-x::
> +--field-separator=<SEP>::
> +       Show results using a CSV-style output to make it easy to import directly
> +       into spreadsheets. Columns are separated by the string specified in SEP.
> +
>
>  SEE ALSO
>  --------
> diff --git a/tools/perf/builtin-lock.c b/tools/perf/builtin-lock.c
> index fc8356bd6e3a..52c87cc3d224 100644
> --- a/tools/perf/builtin-lock.c
> +++ b/tools/perf/builtin-lock.c
> @@ -225,6 +225,12 @@ static void lock_stat_key_print_time(unsigned long long nsec, int len)
>                 { 0, NULL },
>         };
>
> +       /* for CSV output */
> +       if (len == 0) {
> +               pr_info("%llu", nsec);
> +               return;
> +       }
> +
>         for (int i = 0; table[i].unit; i++) {
>                 if (nsec < table[i].base)
>                         continue;
> @@ -1623,11 +1629,179 @@ static void sort_contention_result(void)
>         sort_result();
>  }
>
> -static void print_bpf_events(int total, struct lock_contention_fails *fails)
> +static void print_header_stdio(void)
> +{
> +       struct lock_key *key;
> +
> +       list_for_each_entry(key, &lock_keys, list)
> +               pr_info("%*s ", key->len, key->header);
> +
> +       switch (aggr_mode) {
> +       case LOCK_AGGR_TASK:
> +               pr_info("  %10s   %s\n\n", "pid",
> +                       show_lock_owner ? "owner" : "comm");
> +               break;
> +       case LOCK_AGGR_CALLER:
> +               pr_info("  %10s   %s\n\n", "type", "caller");
> +               break;
> +       case LOCK_AGGR_ADDR:
> +               pr_info("  %16s   %s\n\n", "address", "symbol");
> +               break;
> +       default:
> +               break;
> +       }
> +}
> +
> +static void print_header_csv(const char *sep)
> +{
> +       struct lock_key *key;
> +
> +       pr_info("# output: ");
> +       list_for_each_entry(key, &lock_keys, list)
> +               pr_info("%s%s ", key->header, sep);
> +
> +       switch (aggr_mode) {
> +       case LOCK_AGGR_TASK:
> +               pr_info("%s%s %s\n", "pid", sep,
> +                       show_lock_owner ? "owner" : "comm");
> +               break;
> +       case LOCK_AGGR_CALLER:
> +               pr_info("%s%s %s", "type", sep, "caller");
> +               if (verbose > 0)
> +                       pr_info("%s %s", sep, "stacktrace");
> +               pr_info("\n");
> +               break;
> +       case LOCK_AGGR_ADDR:
> +               pr_info("%s%s %s%s %s\n", "address", sep, "symbol", sep, "type");
> +               break;
> +       default:
> +               break;
> +       }
> +}
> +
> +static void print_header(void)
> +{
> +       if (!quiet) {
> +               if (symbol_conf.field_sep)
> +                       print_header_csv(symbol_conf.field_sep);
> +               else
> +                       print_header_stdio();
> +       }
> +}
> +
> +static void print_lock_stat_stdio(struct lock_contention *con, struct lock_stat *st)
> +{
> +       struct lock_key *key;
> +       struct thread *t;
> +       int pid;
> +
> +       list_for_each_entry(key, &lock_keys, list) {
> +               key->print(key, st);
> +               pr_info(" ");
> +       }
> +
> +       switch (aggr_mode) {
> +       case LOCK_AGGR_CALLER:
> +               pr_info("  %10s   %s\n", get_type_str(st->flags), st->name);
> +               break;
> +       case LOCK_AGGR_TASK:
> +               pid = st->addr;
> +               t = perf_session__findnew(session, pid);
> +               pr_info("  %10d   %s\n",
> +                       pid, pid == -1 ? "Unknown" : thread__comm_str(t));
> +               break;
> +       case LOCK_AGGR_ADDR:
> +               pr_info("  %016llx   %s (%s)\n", (unsigned long long)st->addr,
> +                       st->name, get_type_name(st->flags));
> +               break;
> +       default:
> +               break;
> +       }
> +
> +       if (aggr_mode == LOCK_AGGR_CALLER && verbose > 0) {
> +               struct map *kmap;
> +               struct symbol *sym;
> +               char buf[128];
> +               u64 ip;
> +
> +               for (int i = 0; i < max_stack_depth; i++) {
> +                       if (!st->callstack || !st->callstack[i])
> +                               break;
> +
> +                       ip = st->callstack[i];
> +                       sym = machine__find_kernel_symbol(con->machine, ip, &kmap);
> +                       get_symbol_name_offset(kmap, sym, ip, buf, sizeof(buf));
> +                       pr_info("\t\t\t%#lx  %s\n", (unsigned long)ip, buf);
> +               }
> +       }
> +}
> +
> +static void print_lock_stat_csv(struct lock_contention *con, struct lock_stat *st,
> +                               const char *sep)
> +{
> +       struct lock_key *key;
> +       struct thread *t;
> +       int pid;
> +
> +       list_for_each_entry(key, &lock_keys, list) {
> +               key->print(key, st);
> +               pr_info("%s ", sep);
> +       }
> +
> +       switch (aggr_mode) {
> +       case LOCK_AGGR_CALLER:
> +               pr_info("%s%s %s", get_type_str(st->flags), sep, st->name);
> +               if (verbose <= 0)
> +                       pr_info("\n");
> +               break;
> +       case LOCK_AGGR_TASK:
> +               pid = st->addr;
> +               t = perf_session__findnew(session, pid);
> +               pr_info("%d%s %s\n", pid, sep, pid == -1 ? "Unknown" : thread__comm_str(t));
> +               break;
> +       case LOCK_AGGR_ADDR:
> +               pr_info("%llx%s %s%s %s\n", (unsigned long long)st->addr, sep,
> +                       st->name, sep, get_type_name(st->flags));
> +               break;
> +       default:
> +               break;
> +       }
> +
> +       if (aggr_mode == LOCK_AGGR_CALLER && verbose > 0) {
> +               struct map *kmap;
> +               struct symbol *sym;
> +               char buf[128];
> +               u64 ip;
> +
> +               for (int i = 0; i < max_stack_depth; i++) {
> +                       if (!st->callstack || !st->callstack[i])
> +                               break;
> +
> +                       ip = st->callstack[i];
> +                       sym = machine__find_kernel_symbol(con->machine, ip, &kmap);
> +                       get_symbol_name_offset(kmap, sym, ip, buf, sizeof(buf));
> +                       pr_info("%s %#lx %s", i ? ":" : sep, (unsigned long) ip, buf);
> +               }
> +               pr_info("\n");
> +       }
> +}
> +
> +static void print_lock_stat(struct lock_contention *con, struct lock_stat *st)
> +{
> +       if (symbol_conf.field_sep)
> +               print_lock_stat_csv(con, st, symbol_conf.field_sep);
> +       else
> +               print_lock_stat_stdio(con, st);
> +}
> +
> +static void print_footer_stdio(int total, int bad, struct lock_contention_fails *fails)
>  {
>         /* Output for debug, this have to be removed */
>         int broken = fails->task + fails->stack + fails->time + fails->data;
>
> +       if (!use_bpf)
> +               print_bad_events(bad, total);
> +
>         if (quiet || total == 0 || (broken == 0 && verbose <= 0))
>                 return;
>
> @@ -1643,38 +1817,53 @@ static void print_bpf_events(int total, struct lock_contention_fails *fails)
>         pr_info(" %10s: %d\n", "data", fails->data);
>  }
>
> +static void print_footer_csv(int total, int bad, struct lock_contention_fails *fails,
> +                            const char *sep)
> +{
> +       /* Output for debug, this have to be removed */
> +       if (use_bpf)
> +               bad = fails->task + fails->stack + fails->time + fails->data;
> +
> +       if (quiet || total == 0 || (bad == 0 && verbose <= 0))
> +               return;
> +
> +       total += bad;
> +       pr_info("# debug: total=%d%s bad=%d", total, sep, bad);
> +
> +       if (use_bpf) {
> +               pr_info("%s bad_%s=%d", sep, "task", fails->task);
> +               pr_info("%s bad_%s=%d", sep, "stack", fails->stack);
> +               pr_info("%s bad_%s=%d", sep, "time", fails->time);
> +               pr_info("%s bad_%s=%d", sep, "data", fails->data);
> +       } else {
> +               int i;
> +               const char *name[4] = { "acquire", "acquired", "contended", "release" };
> +
> +               for (i = 0; i < BROKEN_MAX; i++)
> +                       pr_info("%s bad_%s=%d", sep, name[i], bad_hist[i]);
> +       }
> +       pr_info("\n");
> +}
> +
> +static void print_footer(int total, int bad, struct lock_contention_fails *fails)
> +{
> +       if (symbol_conf.field_sep)
> +               print_footer_csv(total, bad, fails, symbol_conf.field_sep);
> +       else
> +               print_footer_stdio(total, bad, fails);
> +}
> +
>  static void print_contention_result(struct lock_contention *con)
>  {
>         struct lock_stat *st;
> -       struct lock_key *key;
>         int bad, total, printed;
>
> -       if (!quiet) {
> -               list_for_each_entry(key, &lock_keys, list)
> -                       pr_info("%*s ", key->len, key->header);
> -
> -               switch (aggr_mode) {
> -               case LOCK_AGGR_TASK:
> -                       pr_info("  %10s   %s\n\n", "pid",
> -                               show_lock_owner ? "owner" : "comm");
> -                       break;
> -               case LOCK_AGGR_CALLER:
> -                       pr_info("  %10s   %s\n\n", "type", "caller");
> -                       break;
> -               case LOCK_AGGR_ADDR:
> -                       pr_info("  %16s   %s\n\n", "address", "symbol");
> -                       break;
> -               default:
> -                       break;
> -               }
> -       }
> +       if (!quiet)
> +               print_header();
>
>         bad = total = printed = 0;
>
>         while ((st = pop_from_result())) {
> -               struct thread *t;
> -               int pid;
> -
>                 total += use_bpf ? st->nr_contended : 1;
>                 if (st->broken)
>                         bad++;
> @@ -1682,45 +1871,7 @@ static void print_contention_result(struct lock_contention *con)
>                 if (!st->wait_time_total)
>                         continue;
>
> -               list_for_each_entry(key, &lock_keys, list) {
> -                       key->print(key, st);
> -                       pr_info(" ");
> -               }
> -
> -               switch (aggr_mode) {
> -               case LOCK_AGGR_CALLER:
> -                       pr_info("  %10s   %s\n", get_type_str(st->flags), st->name);
> -                       break;
> -               case LOCK_AGGR_TASK:
> -                       pid = st->addr;
> -                       t = perf_session__findnew(session, pid);
> -                       pr_info("  %10d   %s\n",
> -                               pid, pid == -1 ? "Unknown" : thread__comm_str(t));
> -                       break;
> -               case LOCK_AGGR_ADDR:
> -                       pr_info("  %016llx   %s (%s)\n", (unsigned long long)st->addr,
> -                               st->name, get_type_name(st->flags));
> -                       break;
> -               default:
> -                       break;
> -               }
> -
> -               if (aggr_mode == LOCK_AGGR_CALLER && verbose > 0) {
> -                       struct map *kmap;
> -                       struct symbol *sym;
> -                       char buf[128];
> -                       u64 ip;
> -
> -                       for (int i = 0; i < max_stack_depth; i++) {
> -                               if (!st->callstack || !st->callstack[i])
> -                                       break;
> -
> -                               ip = st->callstack[i];
> -                               sym = machine__find_kernel_symbol(con->machine, ip, &kmap);
> -                               get_symbol_name_offset(kmap, sym, ip, buf, sizeof(buf));
> -                               pr_info("\t\t\t%#lx  %s\n", (unsigned long)ip, buf);
> -                       }
> -               }
> +               print_lock_stat(con, st);
>
>                 if (++printed >= print_nr_entries)
>                         break;
> @@ -1737,10 +1888,7 @@ static void print_contention_result(struct lock_contention *con)
>         /* some entries are collected but hidden by the callstack filter */
>         total += con->nr_filtered;
>
> -       if (use_bpf)
> -               print_bpf_events(total, &con->fails);
> -       else
> -               print_bad_events(bad, total);
> +       print_footer(total, bad, &con->fails);
>  }
>
>  static bool force;
> @@ -1846,6 +1994,16 @@ static int check_lock_contention_options(const struct option *options,
>                 return -1;
>         }
>
> +       if (symbol_conf.field_sep) {
> +               if (strstr(symbol_conf.field_sep, ":") || /* part of type flags */
> +                   strstr(symbol_conf.field_sep, "+") || /* part of caller offset */
> +                   strstr(symbol_conf.field_sep, ".")) { /* can be in a symbol name */
> +                       pr_err("Cannot use the separator that is already used\n");
> +                       parse_options_usage(usage, options, "x", 1);
> +                       return -1;
> +               }
> +       }
> +
>         if (show_lock_owner)
>                 show_thread_stats = true;
>
> @@ -1963,6 +2121,15 @@ static int __cmd_contention(int argc, const char **argv)
>         if (select_key(true))
>                 goto out_delete;
>
> +       if (symbol_conf.field_sep) {
> +               int i;
> +               struct lock_key *keys = contention_keys;
> +
> +               /* do not align output in CSV format */
> +               for (i = 0; keys[i].name; i++)
> +                       keys[i].len = 0;
> +       }
> +
>         if (use_bpf) {
>                 lock_contention_start();
>                 if (argc)
> @@ -2331,6 +2498,8 @@ int cmd_lock(int argc, const char **argv)
>         OPT_CALLBACK('S', "callstack-filter", NULL, "NAMES",
>                      "Filter specific function in the callstack", parse_call_stack),
>         OPT_BOOLEAN('o', "lock-owner", &show_lock_owner, "show lock owners instead of waiters"),
> +       OPT_STRING_NOEMPTY('x', "field-separator", &symbol_conf.field_sep, "separator",
> +                  "print result in CSV format with custom separator"),
>         OPT_PARENT(lock_options)
>         };
>
> --
> 2.41.0.rc0.172.g3f132b7071-goog
>





[Index of Archives]     [Linux Samsung SoC]     [Linux Rockchip SoC]     [Linux Actions SoC]     [Linux for Synopsys ARC Processors]     [Linux NFS]     [Linux NILFS]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]


  Powered by Linux