diff options
Diffstat (limited to 'tools/perf/builtin-lock.c')
-rw-r--r-- | tools/perf/builtin-lock.c | 385 |
1 files changed, 287 insertions, 98 deletions
diff --git a/tools/perf/builtin-lock.c b/tools/perf/builtin-lock.c index 8b505e1e5002..c15386cb1033 100644 --- a/tools/perf/builtin-lock.c +++ b/tools/perf/builtin-lock.c @@ -27,6 +27,7 @@ #include "util/map.h" #include "util/util.h" +#include <stdio.h> #include <sys/types.h> #include <sys/prctl.h> #include <semaphore.h> @@ -65,6 +66,8 @@ static int max_stack_depth = CONTENTION_STACK_DEPTH; static int stack_skip = CONTENTION_STACK_SKIP; static int print_nr_entries = INT_MAX / 2; static LIST_HEAD(callstack_filters); +static const char *output_name = NULL; +static FILE *lock_output; struct callstack_filter { struct list_head list; @@ -225,22 +228,28 @@ static void lock_stat_key_print_time(unsigned long long nsec, int len) { 0, NULL }, }; + /* for CSV output */ + if (len == 0) { + fprintf(lock_output, "%llu", nsec); + return; + } + for (int i = 0; table[i].unit; i++) { if (nsec < table[i].base) continue; - pr_info("%*.2f %s", len - 3, nsec / table[i].base, table[i].unit); + fprintf(lock_output, "%*.2f %s", len - 3, nsec / table[i].base, table[i].unit); return; } - pr_info("%*llu %s", len - 3, nsec, "ns"); + fprintf(lock_output, "%*llu %s", len - 3, nsec, "ns"); } #define PRINT_KEY(member) \ static void lock_stat_key_print_ ## member(struct lock_key *key, \ struct lock_stat *ls) \ { \ - pr_info("%*llu", key->len, (unsigned long long)ls->member); \ + fprintf(lock_output, "%*llu", key->len, (unsigned long long)ls->member);\ } #define PRINT_TIME(member) \ @@ -1329,12 +1338,12 @@ static void print_bad_events(int bad, int total) if (quiet || total == 0 || (broken == 0 && verbose <= 0)) return; - pr_info("\n=== output for debug ===\n\n"); - pr_info("bad: %d, total: %d\n", bad, total); - pr_info("bad rate: %.2f %%\n", (double)bad / (double)total * 100); - pr_info("histogram of events caused bad sequence\n"); + fprintf(lock_output, "\n=== output for debug ===\n\n"); + fprintf(lock_output, "bad: %d, total: %d\n", bad, total); + fprintf(lock_output, "bad rate: %.2f %%\n", (double)bad / (double)total * 100); + fprintf(lock_output, "histogram of events caused bad sequence\n"); for (i = 0; i < BROKEN_MAX; i++) - pr_info(" %10s: %d\n", name[i], bad_hist[i]); + fprintf(lock_output, " %10s: %d\n", name[i], bad_hist[i]); } /* TODO: various way to print, coloring, nano or milli sec */ @@ -1346,10 +1355,10 @@ static void print_result(void) int bad, total, printed; if (!quiet) { - pr_info("%20s ", "Name"); + fprintf(lock_output, "%20s ", "Name"); list_for_each_entry(key, &lock_keys, list) - pr_info("%*s ", key->len, key->header); - pr_info("\n\n"); + fprintf(lock_output, "%*s ", key->len, key->header); + fprintf(lock_output, "\n\n"); } bad = total = printed = 0; @@ -1374,7 +1383,7 @@ static void print_result(void) name = thread__comm_str(t); } - pr_info("%20s ", name); + fprintf(lock_output, "%20s ", name); } else { strncpy(cut_name, st->name, 16); cut_name[16] = '.'; @@ -1382,14 +1391,14 @@ static void print_result(void) cut_name[18] = '.'; cut_name[19] = '\0'; /* cut off name for saving output style */ - pr_info("%20s ", cut_name); + fprintf(lock_output, "%20s ", cut_name); } list_for_each_entry(key, &lock_keys, list) { key->print(key, st); - pr_info(" "); + fprintf(lock_output, " "); } - pr_info("\n"); + fprintf(lock_output, "\n"); if (++printed >= print_nr_entries) break; @@ -1406,13 +1415,13 @@ static void dump_threads(void) struct rb_node *node; struct thread *t; - pr_info("%10s: comm\n", "Thread ID"); + fprintf(lock_output, "%10s: comm\n", "Thread ID"); node = rb_first(&thread_stats); while (node) { st = container_of(node, struct thread_stat, rb); t = perf_session__findnew(session, st->tid); - pr_info("%10d: %s\n", st->tid, thread__comm_str(t)); + fprintf(lock_output, "%10d: %s\n", st->tid, thread__comm_str(t)); node = rb_next(node); thread__put(t); } @@ -1438,7 +1447,7 @@ static void dump_map(void) unsigned int i; struct lock_stat *st; - pr_info("Address of instance: name of class\n"); + fprintf(lock_output, "Address of instance: name of class\n"); for (i = 0; i < LOCKHASH_SIZE; i++) { hlist_for_each_entry(st, &lockhash_table[i], hash_entry) { insert_to_result(st, compare_maps); @@ -1446,7 +1455,7 @@ static void dump_map(void) } while ((st = pop_from_result())) - pr_info(" %#llx: %s\n", (unsigned long long)st->addr, st->name); + fprintf(lock_output, " %#llx: %s\n", (unsigned long long)st->addr, st->name); } static int dump_info(void) @@ -1626,58 +1635,242 @@ 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) + fprintf(lock_output, "%*s ", key->len, key->header); + + switch (aggr_mode) { + case LOCK_AGGR_TASK: + fprintf(lock_output, " %10s %s\n\n", "pid", + show_lock_owner ? "owner" : "comm"); + break; + case LOCK_AGGR_CALLER: + fprintf(lock_output, " %10s %s\n\n", "type", "caller"); + break; + case LOCK_AGGR_ADDR: + fprintf(lock_output, " %16s %s\n\n", "address", "symbol"); + break; + default: + break; + } +} + +static void print_header_csv(const char *sep) +{ + struct lock_key *key; + + fprintf(lock_output, "# output: "); + list_for_each_entry(key, &lock_keys, list) + fprintf(lock_output, "%s%s ", key->header, sep); + + switch (aggr_mode) { + case LOCK_AGGR_TASK: + fprintf(lock_output, "%s%s %s\n", "pid", sep, + show_lock_owner ? "owner" : "comm"); + break; + case LOCK_AGGR_CALLER: + fprintf(lock_output, "%s%s %s", "type", sep, "caller"); + if (verbose > 0) + fprintf(lock_output, "%s %s", sep, "stacktrace"); + fprintf(lock_output, "\n"); + break; + case LOCK_AGGR_ADDR: + fprintf(lock_output, "%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); + fprintf(lock_output, " "); + } + + switch (aggr_mode) { + case LOCK_AGGR_CALLER: + fprintf(lock_output, " %10s %s\n", get_type_str(st->flags), st->name); + break; + case LOCK_AGGR_TASK: + pid = st->addr; + t = perf_session__findnew(session, pid); + fprintf(lock_output, " %10d %s\n", + pid, pid == -1 ? "Unknown" : thread__comm_str(t)); + break; + case LOCK_AGGR_ADDR: + fprintf(lock_output, " %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)); + fprintf(lock_output, "\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); + fprintf(lock_output, "%s ", sep); + } + + switch (aggr_mode) { + case LOCK_AGGR_CALLER: + fprintf(lock_output, "%s%s %s", get_type_str(st->flags), sep, st->name); + if (verbose <= 0) + fprintf(lock_output, "\n"); + break; + case LOCK_AGGR_TASK: + pid = st->addr; + t = perf_session__findnew(session, pid); + fprintf(lock_output, "%d%s %s\n", pid, sep, + pid == -1 ? "Unknown" : thread__comm_str(t)); + break; + case LOCK_AGGR_ADDR: + fprintf(lock_output, "%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)); + fprintf(lock_output, "%s %#lx %s", i ? ":" : sep, (unsigned long) ip, buf); + } + fprintf(lock_output, "\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; total += broken; - pr_info("\n=== output for debug ===\n\n"); - pr_info("bad: %d, total: %d\n", broken, total); - pr_info("bad rate: %.2f %%\n", (double)broken / (double)total * 100); + fprintf(lock_output, "\n=== output for debug ===\n\n"); + fprintf(lock_output, "bad: %d, total: %d\n", broken, total); + fprintf(lock_output, "bad rate: %.2f %%\n", 100.0 * broken / total); - pr_info("histogram of failure reasons\n"); - pr_info(" %10s: %d\n", "task", fails->task); - pr_info(" %10s: %d\n", "stack", fails->stack); - pr_info(" %10s: %d\n", "time", fails->time); - pr_info(" %10s: %d\n", "data", fails->data); + fprintf(lock_output, "histogram of failure reasons\n"); + fprintf(lock_output, " %10s: %d\n", "task", fails->task); + fprintf(lock_output, " %10s: %d\n", "stack", fails->stack); + fprintf(lock_output, " %10s: %d\n", "time", fails->time); + fprintf(lock_output, " %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; + fprintf(lock_output, "# debug: total=%d%s bad=%d", total, sep, bad); + + if (use_bpf) { + fprintf(lock_output, "%s bad_%s=%d", sep, "task", fails->task); + fprintf(lock_output, "%s bad_%s=%d", sep, "stack", fails->stack); + fprintf(lock_output, "%s bad_%s=%d", sep, "time", fails->time); + fprintf(lock_output, "%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++) + fprintf(lock_output, "%s bad_%s=%d", sep, name[i], bad_hist[i]); + } + fprintf(lock_output, "\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++; @@ -1685,45 +1878,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; @@ -1740,10 +1895,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; @@ -1773,8 +1925,6 @@ static int __cmd_report(bool display_info) return PTR_ERR(session); } - /* for lock function check */ - symbol_conf.sort_by_name = true; symbol_conf.allow_aliases = true; symbol__init(&session->header.env); @@ -1849,6 +1999,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; @@ -1903,8 +2063,6 @@ static int __cmd_contention(int argc, const char **argv) if (con.aggr_mode == LOCK_AGGR_CALLER) con.save_callstack = true; - /* for lock function check */ - symbol_conf.sort_by_name = true; symbol_conf.allow_aliases = true; symbol__init(&session->header.env); @@ -1966,6 +2124,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) @@ -2264,10 +2431,29 @@ static int parse_call_stack(const struct option *opt __maybe_unused, const char return ret; } +static int parse_output(const struct option *opt __maybe_unused, const char *str, + int unset __maybe_unused) +{ + const char **name = (const char **)opt->value; + + if (str == NULL) + return -1; + + lock_output = fopen(str, "w"); + if (lock_output == NULL) { + pr_err("Cannot open %s\n", str); + return -1; + } + + *name = str; + return 0; +} + int cmd_lock(int argc, const char **argv) { const struct option lock_options[] = { OPT_STRING('i', "input", &input_name, "file", "input file name"), + OPT_CALLBACK(0, "output", &output_name, "file", "output file name", parse_output), OPT_INCR('v', "verbose", &verbose, "be more verbose (show symbol address, etc)"), OPT_BOOLEAN('D', "dump-raw-trace", &dump_trace, "dump raw trace in ASCII"), OPT_BOOLEAN('f', "force", &force, "don't complain, do it"), @@ -2334,6 +2520,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) }; @@ -2365,6 +2553,7 @@ int cmd_lock(int argc, const char **argv) for (i = 0; i < LOCKHASH_SIZE; i++) INIT_HLIST_HEAD(lockhash_table + i); + lock_output = stderr; argc = parse_options_subcommand(argc, argv, lock_options, lock_subcommands, lock_usage, PARSE_OPT_STOP_AT_NON_OPTION); if (!argc) |