diff options
Diffstat (limited to 'tools/perf/builtin-lock.c')
-rw-r--r-- | tools/perf/builtin-lock.c | 271 |
1 files changed, 107 insertions, 164 deletions
diff --git a/tools/perf/builtin-lock.c b/tools/perf/builtin-lock.c index 33a456980664..05e7bc30488a 100644 --- a/tools/perf/builtin-lock.c +++ b/tools/perf/builtin-lock.c @@ -46,15 +46,6 @@ static struct perf_session *session; static struct target target; -/* based on kernel/lockdep.c */ -#define LOCKHASH_BITS 12 -#define LOCKHASH_SIZE (1UL << LOCKHASH_BITS) - -static struct hlist_head *lockhash_table; - -#define __lockhashfn(key) hash_long((unsigned long)key, LOCKHASH_BITS) -#define lockhashentry(key) (lockhash_table + __lockhashfn((key))) - static struct rb_root thread_stats; static bool combine_locks; @@ -67,24 +58,13 @@ static unsigned long bpf_map_entries = MAX_ENTRIES; 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; - char name[]; -}; - static struct lock_filter filters; static enum lock_aggr_mode aggr_mode = LOCK_AGGR_ADDR; -static bool needs_callstack(void) -{ - return !list_empty(&callstack_filters); -} - static struct thread_stat *thread_stat_find(u32 tid) { struct rb_node *node; @@ -438,16 +418,13 @@ static void combine_lock_stats(struct lock_stat *st) rb_insert_color(&st->rb, &sorted); } -static void insert_to_result(struct lock_stat *st, - int (*bigger)(struct lock_stat *, struct lock_stat *)) +static void insert_to(struct rb_root *rr, struct lock_stat *st, + int (*bigger)(struct lock_stat *, struct lock_stat *)) { - struct rb_node **rb = &result.rb_node; + struct rb_node **rb = &rr->rb_node; struct rb_node *parent = NULL; struct lock_stat *p; - if (combine_locks && st->combined) - return; - while (*rb) { p = container_of(*rb, struct lock_stat, rb); parent = *rb; @@ -459,13 +436,21 @@ static void insert_to_result(struct lock_stat *st, } rb_link_node(&st->rb, parent, rb); - rb_insert_color(&st->rb, &result); + rb_insert_color(&st->rb, rr); } -/* returns left most element of result, and erase it */ -static struct lock_stat *pop_from_result(void) +static inline void insert_to_result(struct lock_stat *st, + int (*bigger)(struct lock_stat *, + struct lock_stat *)) { - struct rb_node *node = result.rb_node; + if (combine_locks && st->combined) + return; + insert_to(&result, st, bigger); +} + +static inline struct lock_stat *pop_from(struct rb_root *rr) +{ + struct rb_node *node = rr->rb_node; if (!node) return NULL; @@ -473,95 +458,15 @@ static struct lock_stat *pop_from_result(void) while (node->rb_left) node = node->rb_left; - rb_erase(node, &result); + rb_erase(node, rr); return container_of(node, struct lock_stat, rb); -} - -struct lock_stat *lock_stat_find(u64 addr) -{ - struct hlist_head *entry = lockhashentry(addr); - struct lock_stat *ret; - - hlist_for_each_entry(ret, entry, hash_entry) { - if (ret->addr == addr) - return ret; - } - return NULL; -} - -struct lock_stat *lock_stat_findnew(u64 addr, const char *name, int flags) -{ - struct hlist_head *entry = lockhashentry(addr); - struct lock_stat *ret, *new; - hlist_for_each_entry(ret, entry, hash_entry) { - if (ret->addr == addr) - return ret; - } - - new = zalloc(sizeof(struct lock_stat)); - if (!new) - goto alloc_failed; - - new->addr = addr; - new->name = strdup(name); - if (!new->name) { - free(new); - goto alloc_failed; - } - - new->flags = flags; - new->wait_time_min = ULLONG_MAX; - - hlist_add_head(&new->hash_entry, entry); - return new; - -alloc_failed: - pr_err("memory allocation failed\n"); - return NULL; } -bool match_callstack_filter(struct machine *machine, u64 *callstack) +/* returns left most element of result, and erase it */ +static struct lock_stat *pop_from_result(void) { - struct map *kmap; - struct symbol *sym; - u64 ip; - const char *arch = perf_env__arch(machine->env); - - if (list_empty(&callstack_filters)) - return true; - - for (int i = 0; i < max_stack_depth; i++) { - struct callstack_filter *filter; - - /* - * In powerpc, the callchain saved by kernel always includes - * first three entries as the NIP (next instruction pointer), - * LR (link register), and the contents of LR save area in the - * second stack frame. In certain scenarios its possible to have - * invalid kernel instruction addresses in either LR or the second - * stack frame's LR. In that case, kernel will store that address as - * zero. - * - * The below check will continue to look into callstack, - * incase first or second callstack index entry has 0 - * address for powerpc. - */ - if (!callstack || (!callstack[i] && (strcmp(arch, "powerpc") || - (i != 1 && i != 2)))) - break; - - ip = callstack[i]; - sym = machine__find_kernel_symbol(machine, ip, &kmap); - if (sym == NULL) - continue; - - list_for_each_entry(filter, &callstack_filters, list) { - if (strstr(sym->name, filter->name)) - return true; - } - } - return false; + return pop_from(&result); } struct trace_lock_handler { @@ -1165,7 +1070,7 @@ static int report_lock_contention_begin_event(struct evsel *evsel, if (callstack == NULL) return -ENOMEM; - if (!match_callstack_filter(machine, callstack)) { + if (!match_callstack_filter(machine, callstack, max_stack_depth)) { free(callstack); return 0; } @@ -1575,8 +1480,13 @@ static void sort_result(void) static const struct { unsigned int flags; - const char *str; - const char *name; + /* + * Name of the lock flags (access), with delimeter ':'. + * For example, rwsem:R of rwsem:W. + */ + const char *flags_name; + /* Name of the lock (type), for example, rwlock or rwsem. */ + const char *lock_name; } lock_type_table[] = { { 0, "semaphore", "semaphore" }, { LCB_F_SPIN, "spinlock", "spinlock" }, @@ -1595,24 +1505,24 @@ static const struct { { LCB_F_MUTEX | LCB_F_SPIN, "mutex:spin", "mutex-spin" }, }; -static const char *get_type_str(unsigned int flags) +static const char *get_type_flags_name(unsigned int flags) { - flags &= LCB_F_MAX_FLAGS - 1; + flags &= LCB_F_TYPE_MASK; for (unsigned int i = 0; i < ARRAY_SIZE(lock_type_table); i++) { if (lock_type_table[i].flags == flags) - return lock_type_table[i].str; + return lock_type_table[i].flags_name; } return "unknown"; } -static const char *get_type_name(unsigned int flags) +static const char *get_type_lock_name(unsigned int flags) { - flags &= LCB_F_MAX_FLAGS - 1; + flags &= LCB_F_TYPE_MASK; for (unsigned int i = 0; i < ARRAY_SIZE(lock_type_table); i++) { if (lock_type_table[i].flags == flags) - return lock_type_table[i].name; + return lock_type_table[i].lock_name; } return "unknown"; } @@ -1633,6 +1543,12 @@ static void lock_filter_finish(void) zfree(&filters.cgrps); filters.nr_cgrps = 0; + + for (int i = 0; i < filters.nr_slabs; i++) + free(filters.slabs[i]); + + zfree(&filters.slabs); + filters.nr_slabs = 0; } static void sort_contention_result(void) @@ -1719,7 +1635,7 @@ static void print_lock_stat_stdio(struct lock_contention *con, struct lock_stat switch (aggr_mode) { case LOCK_AGGR_CALLER: - fprintf(lock_output, " %10s %s\n", get_type_str(st->flags), st->name); + fprintf(lock_output, " %10s %s\n", get_type_flags_name(st->flags), st->name); break; case LOCK_AGGR_TASK: pid = st->addr; @@ -1729,7 +1645,7 @@ static void print_lock_stat_stdio(struct lock_contention *con, struct lock_stat break; case LOCK_AGGR_ADDR: fprintf(lock_output, " %016llx %s (%s)\n", (unsigned long long)st->addr, - st->name, get_type_name(st->flags)); + st->name, get_type_lock_name(st->flags)); break; case LOCK_AGGR_CGROUP: fprintf(lock_output, " %s\n", st->name); @@ -1770,7 +1686,7 @@ static void print_lock_stat_csv(struct lock_contention *con, struct lock_stat *s switch (aggr_mode) { case LOCK_AGGR_CALLER: - fprintf(lock_output, "%s%s %s", get_type_str(st->flags), sep, st->name); + fprintf(lock_output, "%s%s %s", get_type_flags_name(st->flags), sep, st->name); if (verbose <= 0) fprintf(lock_output, "\n"); break; @@ -1782,7 +1698,7 @@ static void print_lock_stat_csv(struct lock_contention *con, struct lock_stat *s 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)); + st->name, sep, get_type_lock_name(st->flags)); break; case LOCK_AGGR_CGROUP: fprintf(lock_output, "%s\n",st->name); @@ -1901,6 +1817,22 @@ static void print_contention_result(struct lock_contention *con) break; } + if (con->owner && con->save_callstack && verbose > 0) { + struct rb_root root = RB_ROOT; + + if (symbol_conf.field_sep) + fprintf(lock_output, "# owner stack trace:\n"); + else + fprintf(lock_output, "\n=== owner stack trace ===\n\n"); + while ((st = pop_owner_stack_trace(con))) + insert_to(&root, st, compare); + + while ((st = pop_from(&root))) { + print_lock_stat(con, st); + free(st); + } + } + if (print_nr_entries) { /* update the total/bad stats */ while ((st = pop_from_result())) { @@ -2046,8 +1978,10 @@ static int check_lock_contention_options(const struct option *options, } } - if (show_lock_owner) - show_thread_stats = true; + if (show_lock_owner && !show_thread_stats) { + pr_warning("Now -o try to show owner's callstack instead of pid and comm.\n"); + pr_warning("Please use -t option too to keep the old behavior.\n"); + } return 0; } @@ -2137,7 +2071,8 @@ static int __cmd_contention(int argc, const char **argv) goto out_delete; } - if (lock_contention_prepare(&con) < 0) { + err = lock_contention_prepare(&con); + if (err < 0) { pr_err("lock contention BPF setup failed\n"); goto out_delete; } @@ -2158,10 +2093,14 @@ static int __cmd_contention(int argc, const char **argv) } } - if (setup_output_field(true, output_fields)) + err = setup_output_field(true, output_fields); + if (err) { + pr_err("Failed to setup output field\n"); goto out_delete; + } - if (select_key(true)) + err = select_key(true); + if (err) goto out_delete; if (symbol_conf.field_sep) { @@ -2345,10 +2284,10 @@ static int parse_lock_type(const struct option *opt __maybe_unused, const char * for (tok = strtok_r(s, ", ", &tmp); tok; tok = strtok_r(NULL, ", ", &tmp)) { bool found = false; - /* `tok` is `str` in `lock_type_table` if it contains ':'. */ + /* `tok` is a flags name if it contains ':'. */ if (strchr(tok, ':')) { for (unsigned int i = 0; i < ARRAY_SIZE(lock_type_table); i++) { - if (!strcmp(lock_type_table[i].str, tok) && + if (!strcmp(lock_type_table[i].flags_name, tok) && add_lock_type(lock_type_table[i].flags)) { found = true; break; @@ -2365,11 +2304,14 @@ static int parse_lock_type(const struct option *opt __maybe_unused, const char * } /* - * Otherwise `tok` is `name` in `lock_type_table`. + * Otherwise `tok` is a lock name. * Single lock name could contain multiple flags. + * Replace alias `pcpu-sem` with actual name `percpu-rwsem. */ + if (!strcmp(tok, "pcpu-sem")) + tok = (char *)"percpu-rwsem"; for (unsigned int i = 0; i < ARRAY_SIZE(lock_type_table); i++) { - if (!strcmp(lock_type_table[i].name, tok)) { + if (!strcmp(lock_type_table[i].lock_name, tok)) { if (add_lock_type(lock_type_table[i].flags)) { found = true; } else { @@ -2428,6 +2370,27 @@ static bool add_lock_sym(char *name) return true; } +static bool add_lock_slab(char *name) +{ + char **tmp; + char *sym = strdup(name); + + if (sym == NULL) { + pr_err("Memory allocation failure\n"); + return false; + } + + tmp = realloc(filters.slabs, (filters.nr_slabs + 1) * sizeof(*filters.slabs)); + if (tmp == NULL) { + pr_err("Memory allocation failure\n"); + return false; + } + + tmp[filters.nr_slabs++] = sym; + filters.slabs = tmp; + return true; +} + static int parse_lock_addr(const struct option *opt __maybe_unused, const char *str, int unset __maybe_unused) { @@ -2451,6 +2414,14 @@ static int parse_lock_addr(const struct option *opt __maybe_unused, const char * continue; } + if (*tok == '&') { + if (!add_lock_slab(tok + 1)) { + ret = -1; + break; + } + continue; + } + /* * At this moment, we don't have kernel symbols. Save the symbols * in a separate list and resolve them to addresses later. @@ -2465,34 +2436,6 @@ static int parse_lock_addr(const struct option *opt __maybe_unused, const char * return ret; } -static int parse_call_stack(const struct option *opt __maybe_unused, const char *str, - int unset __maybe_unused) -{ - char *s, *tmp, *tok; - int ret = 0; - - s = strdup(str); - if (s == NULL) - return -1; - - for (tok = strtok_r(s, ", ", &tmp); tok; tok = strtok_r(NULL, ", ", &tmp)) { - struct callstack_filter *entry; - - entry = malloc(sizeof(*entry) + strlen(tok) + 1); - if (entry == NULL) { - pr_err("Memory allocation failure\n"); - free(s); - return -1; - } - - strcpy(entry->name, tok); - list_add_tail(&entry->list, &callstack_filters); - } - - free(s); - return ret; -} - static int parse_output(const struct option *opt __maybe_unused, const char *str, int unset __maybe_unused) { |