root/tools/perf/builtin-lock.c
// SPDX-License-Identifier: GPL-2.0
#include <errno.h>
#include <inttypes.h>
#include "builtin.h"
#include "perf.h"

#include "util/evlist.h" // for struct evsel_str_handler
#include "util/evsel.h"
#include "util/symbol.h"
#include "util/thread.h"
#include "util/header.h"
#include "util/target.h"
#include "util/cgroup.h"
#include "util/callchain.h"
#include "util/lock-contention.h"
#include "util/bpf_skel/lock_data.h"

#include <subcmd/pager.h>
#include <subcmd/parse-options.h>
#include "util/trace-event.h"
#include "util/tracepoint.h"

#include "util/debug.h"
#include "util/session.h"
#include "util/tool.h"
#include "util/data.h"
#include "util/string2.h"
#include "util/map.h"
#include "util/util.h"

#include <stdio.h>
#include <sys/types.h>
#include <sys/prctl.h>
#include <semaphore.h>
#include <math.h>
#include <limits.h>
#include <ctype.h>

#include <linux/list.h>
#include <linux/hash.h>
#include <linux/kernel.h>
#include <linux/zalloc.h>
#include <linux/err.h>
#include <linux/stringify.h>

static struct perf_session *session;
static struct target target;

static struct rb_root           thread_stats;

static bool combine_locks;
static bool show_thread_stats;
static bool show_lock_addrs;
static bool show_lock_owner;
static bool show_lock_cgroups;
static bool use_bpf;
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 const char *output_name = NULL;
static FILE *lock_output;

static struct lock_filter filters;
static struct lock_delay *delays;
static int nr_delays;

static enum lock_aggr_mode aggr_mode = LOCK_AGGR_ADDR;

static struct thread_stat *thread_stat_find(u32 tid)
{
        struct rb_node *node;
        struct thread_stat *st;

        node = thread_stats.rb_node;
        while (node) {
                st = container_of(node, struct thread_stat, rb);
                if (st->tid == tid)
                        return st;
                else if (tid < st->tid)
                        node = node->rb_left;
                else
                        node = node->rb_right;
        }

        return NULL;
}

static void thread_stat_insert(struct thread_stat *new)
{
        struct rb_node **rb = &thread_stats.rb_node;
        struct rb_node *parent = NULL;
        struct thread_stat *p;

        while (*rb) {
                p = container_of(*rb, struct thread_stat, rb);
                parent = *rb;

                if (new->tid < p->tid)
                        rb = &(*rb)->rb_left;
                else if (new->tid > p->tid)
                        rb = &(*rb)->rb_right;
                else
                        BUG_ON("inserting invalid thread_stat\n");
        }

        rb_link_node(&new->rb, parent, rb);
        rb_insert_color(&new->rb, &thread_stats);
}

static struct thread_stat *thread_stat_findnew_after_first(u32 tid)
{
        struct thread_stat *st;

        st = thread_stat_find(tid);
        if (st)
                return st;

        st = zalloc(sizeof(struct thread_stat));
        if (!st) {
                pr_err("memory allocation failed\n");
                return NULL;
        }

        st->tid = tid;
        INIT_LIST_HEAD(&st->seq_list);

        thread_stat_insert(st);

        return st;
}

static struct thread_stat *thread_stat_findnew_first(u32 tid);
static struct thread_stat *(*thread_stat_findnew)(u32 tid) =
        thread_stat_findnew_first;

static struct thread_stat *thread_stat_findnew_first(u32 tid)
{
        struct thread_stat *st;

        st = zalloc(sizeof(struct thread_stat));
        if (!st) {
                pr_err("memory allocation failed\n");
                return NULL;
        }
        st->tid = tid;
        INIT_LIST_HEAD(&st->seq_list);

        rb_link_node(&st->rb, NULL, &thread_stats.rb_node);
        rb_insert_color(&st->rb, &thread_stats);

        thread_stat_findnew = thread_stat_findnew_after_first;
        return st;
}

/* build simple key function one is bigger than two */
#define SINGLE_KEY(member)                                              \
        static int lock_stat_key_ ## member(struct lock_stat *one,      \
                                         struct lock_stat *two)         \
        {                                                               \
                return one->member > two->member;                       \
        }

SINGLE_KEY(nr_acquired)
SINGLE_KEY(nr_contended)
SINGLE_KEY(avg_wait_time)
SINGLE_KEY(wait_time_total)
SINGLE_KEY(wait_time_max)

static int lock_stat_key_wait_time_min(struct lock_stat *one,
                                        struct lock_stat *two)
{
        u64 s1 = one->wait_time_min;
        u64 s2 = two->wait_time_min;
        if (s1 == ULLONG_MAX)
                s1 = 0;
        if (s2 == ULLONG_MAX)
                s2 = 0;
        return s1 > s2;
}

struct lock_key {
        /*
         * name: the value for specify by user
         * this should be simpler than raw name of member
         * e.g. nr_acquired -> acquired, wait_time_total -> wait_total
         */
        const char              *name;
        /* header: the string printed on the header line */
        const char              *header;
        /* len: the printing width of the field */
        int                     len;
        /* key: a pointer to function to compare two lock stats for sorting */
        int                     (*key)(struct lock_stat*, struct lock_stat*);
        /* print: a pointer to function to print a given lock stats */
        void                    (*print)(struct lock_key*, struct lock_stat*);
        /* list: list entry to link this */
        struct list_head        list;
};

static void lock_stat_key_print_time(unsigned long long nsec, int len)
{
        static const struct {
                float base;
                const char *unit;
        } table[] = {
                { 1e9 * 3600, "h " },
                { 1e9 * 60, "m " },
                { 1e9, "s " },
                { 1e6, "ms" },
                { 1e3, "us" },
                { 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;

                fprintf(lock_output, "%*.2f %s", len - 3, nsec / table[i].base, table[i].unit);
                return;
        }

        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)        \
{                                                                       \
        fprintf(lock_output, "%*llu", key->len, (unsigned long long)ls->member);\
}

#define PRINT_TIME(member)                                              \
static void lock_stat_key_print_ ## member(struct lock_key *key,        \
                                           struct lock_stat *ls)        \
{                                                                       \
        lock_stat_key_print_time((unsigned long long)ls->member, key->len);     \
}

PRINT_KEY(nr_acquired)
PRINT_KEY(nr_contended)
PRINT_TIME(avg_wait_time)
PRINT_TIME(wait_time_total)
PRINT_TIME(wait_time_max)

static void lock_stat_key_print_wait_time_min(struct lock_key *key,
                                              struct lock_stat *ls)
{
        u64 wait_time = ls->wait_time_min;

        if (wait_time == ULLONG_MAX)
                wait_time = 0;

        lock_stat_key_print_time(wait_time, key->len);
}


static const char               *sort_key = "acquired";

static int                      (*compare)(struct lock_stat *, struct lock_stat *);

static struct rb_root           sorted; /* place to store intermediate data */
static struct rb_root           result; /* place to store sorted data */

static LIST_HEAD(lock_keys);
static const char               *output_fields;

#define DEF_KEY_LOCK(name, header, fn_suffix, len)                      \
        { #name, header, len, lock_stat_key_ ## fn_suffix, lock_stat_key_print_ ## fn_suffix, {} }
static struct lock_key report_keys[] = {
        DEF_KEY_LOCK(acquired, "acquired", nr_acquired, 10),
        DEF_KEY_LOCK(contended, "contended", nr_contended, 10),
        DEF_KEY_LOCK(avg_wait, "avg wait", avg_wait_time, 12),
        DEF_KEY_LOCK(wait_total, "total wait", wait_time_total, 12),
        DEF_KEY_LOCK(wait_max, "max wait", wait_time_max, 12),
        DEF_KEY_LOCK(wait_min, "min wait", wait_time_min, 12),

        /* extra comparisons much complicated should be here */
        { }
};

static struct lock_key contention_keys[] = {
        DEF_KEY_LOCK(contended, "contended", nr_contended, 10),
        DEF_KEY_LOCK(wait_total, "total wait", wait_time_total, 12),
        DEF_KEY_LOCK(wait_max, "max wait", wait_time_max, 12),
        DEF_KEY_LOCK(wait_min, "min wait", wait_time_min, 12),
        DEF_KEY_LOCK(avg_wait, "avg wait", avg_wait_time, 12),

        /* extra comparisons much complicated should be here */
        { }
};

static int select_key(bool contention)
{
        int i;
        struct lock_key *keys = report_keys;

        if (contention)
                keys = contention_keys;

        for (i = 0; keys[i].name; i++) {
                if (!strcmp(keys[i].name, sort_key)) {
                        compare = keys[i].key;

                        /* selected key should be in the output fields */
                        if (list_empty(&keys[i].list))
                                list_add_tail(&keys[i].list, &lock_keys);

                        return 0;
                }
        }

        pr_err("Unknown compare key: %s\n", sort_key);
        return -1;
}

static int add_output_field(bool contention, char *name)
{
        int i;
        struct lock_key *keys = report_keys;

        if (contention)
                keys = contention_keys;

        for (i = 0; keys[i].name; i++) {
                if (strcmp(keys[i].name, name))
                        continue;

                /* prevent double link */
                if (list_empty(&keys[i].list))
                        list_add_tail(&keys[i].list, &lock_keys);

                return 0;
        }

        pr_err("Unknown output field: %s\n", name);
        return -1;
}

static int setup_output_field(bool contention, const char *str)
{
        char *tok, *tmp, *orig;
        int i, ret = 0;
        struct lock_key *keys = report_keys;

        if (contention)
                keys = contention_keys;

        /* no output field given: use all of them */
        if (str == NULL) {
                for (i = 0; keys[i].name; i++)
                        list_add_tail(&keys[i].list, &lock_keys);
                return 0;
        }

        for (i = 0; keys[i].name; i++)
                INIT_LIST_HEAD(&keys[i].list);

        orig = tmp = strdup(str);
        if (orig == NULL)
                return -ENOMEM;

        while ((tok = strsep(&tmp, ",")) != NULL){
                ret = add_output_field(contention, tok);
                if (ret < 0)
                        break;
        }
        free(orig);

        return ret;
}

static void combine_lock_stats(struct lock_stat *st)
{
        struct rb_node **rb = &sorted.rb_node;
        struct rb_node *parent = NULL;
        struct lock_stat *p;
        int ret;

        while (*rb) {
                p = container_of(*rb, struct lock_stat, rb);
                parent = *rb;

                if (st->name && p->name)
                        ret = strcmp(st->name, p->name);
                else
                        ret = !!st->name - !!p->name;

                if (ret == 0) {
                        p->nr_acquired += st->nr_acquired;
                        p->nr_contended += st->nr_contended;
                        p->wait_time_total += st->wait_time_total;

                        if (p->nr_contended)
                                p->avg_wait_time = p->wait_time_total / p->nr_contended;

                        if (p->wait_time_min > st->wait_time_min)
                                p->wait_time_min = st->wait_time_min;
                        if (p->wait_time_max < st->wait_time_max)
                                p->wait_time_max = st->wait_time_max;

                        p->broken |= st->broken;
                        st->combined = 1;
                        return;
                }

                if (ret < 0)
                        rb = &(*rb)->rb_left;
                else
                        rb = &(*rb)->rb_right;
        }

        rb_link_node(&st->rb, parent, rb);
        rb_insert_color(&st->rb, &sorted);
}

static void insert_to(struct rb_root *rr, struct lock_stat *st,
                      int (*bigger)(struct lock_stat *, struct lock_stat *))
{
        struct rb_node **rb = &rr->rb_node;
        struct rb_node *parent = NULL;
        struct lock_stat *p;

        while (*rb) {
                p = container_of(*rb, struct lock_stat, rb);
                parent = *rb;

                if (bigger(st, p))
                        rb = &(*rb)->rb_left;
                else
                        rb = &(*rb)->rb_right;
        }

        rb_link_node(&st->rb, parent, rb);
        rb_insert_color(&st->rb, rr);
}

static inline void insert_to_result(struct lock_stat *st,
                                    int (*bigger)(struct lock_stat *,
                                                  struct lock_stat *))
{
        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;

        while (node->rb_left)
                node = node->rb_left;

        rb_erase(node, rr);
        return container_of(node, struct lock_stat, rb);

}

/* returns left most element of result, and erase it */
static struct lock_stat *pop_from_result(void)
{
        return pop_from(&result);
}

struct trace_lock_handler {
        /* it's used on CONFIG_LOCKDEP */
        int (*acquire_event)(struct evsel *evsel,
                             struct perf_sample *sample);

        /* it's used on CONFIG_LOCKDEP && CONFIG_LOCK_STAT */
        int (*acquired_event)(struct evsel *evsel,
                              struct perf_sample *sample);

        /* it's used on CONFIG_LOCKDEP && CONFIG_LOCK_STAT */
        int (*contended_event)(struct evsel *evsel,
                               struct perf_sample *sample);

        /* it's used on CONFIG_LOCKDEP */
        int (*release_event)(struct evsel *evsel,
                             struct perf_sample *sample);

        /* it's used when CONFIG_LOCKDEP is off */
        int (*contention_begin_event)(struct evsel *evsel,
                                      struct perf_sample *sample);

        /* it's used when CONFIG_LOCKDEP is off */
        int (*contention_end_event)(struct evsel *evsel,
                                    struct perf_sample *sample);
};

static struct lock_seq_stat *get_seq(struct thread_stat *ts, u64 addr)
{
        struct lock_seq_stat *seq;

        list_for_each_entry(seq, &ts->seq_list, list) {
                if (seq->addr == addr)
                        return seq;
        }

        seq = zalloc(sizeof(struct lock_seq_stat));
        if (!seq) {
                pr_err("memory allocation failed\n");
                return NULL;
        }
        seq->state = SEQ_STATE_UNINITIALIZED;
        seq->addr = addr;

        list_add(&seq->list, &ts->seq_list);
        return seq;
}

enum broken_state {
        BROKEN_ACQUIRE,
        BROKEN_ACQUIRED,
        BROKEN_CONTENDED,
        BROKEN_RELEASE,
        BROKEN_MAX,
};

static int bad_hist[BROKEN_MAX];

enum acquire_flags {
        TRY_LOCK = 1,
        READ_LOCK = 2,
};

static int get_key_by_aggr_mode_simple(u64 *key, u64 addr, u32 tid)
{
        switch (aggr_mode) {
        case LOCK_AGGR_ADDR:
                *key = addr;
                break;
        case LOCK_AGGR_TASK:
                *key = tid;
                break;
        case LOCK_AGGR_CALLER:
        case LOCK_AGGR_CGROUP:
        default:
                pr_err("Invalid aggregation mode: %d\n", aggr_mode);
                return -EINVAL;
        }
        return 0;
}

static u64 callchain_id(struct evsel *evsel, struct perf_sample *sample);

static int get_key_by_aggr_mode(u64 *key, u64 addr, struct evsel *evsel,
                                 struct perf_sample *sample)
{
        if (aggr_mode == LOCK_AGGR_CALLER) {
                *key = callchain_id(evsel, sample);
                return 0;
        }
        return get_key_by_aggr_mode_simple(key, addr, sample->tid);
}

static int report_lock_acquire_event(struct evsel *evsel,
                                     struct perf_sample *sample)
{
        struct lock_stat *ls;
        struct thread_stat *ts;
        struct lock_seq_stat *seq;
        const char *name = evsel__strval(evsel, sample, "name");
        u64 addr = evsel__intval(evsel, sample, "lockdep_addr");
        int flag = evsel__intval(evsel, sample, "flags");
        u64 key;
        int ret;

        ret = get_key_by_aggr_mode_simple(&key, addr, sample->tid);
        if (ret < 0)
                return ret;

        ls = lock_stat_findnew(key, name, 0);
        if (!ls)
                return -ENOMEM;

        ts = thread_stat_findnew(sample->tid);
        if (!ts)
                return -ENOMEM;

        seq = get_seq(ts, addr);
        if (!seq)
                return -ENOMEM;

        switch (seq->state) {
        case SEQ_STATE_UNINITIALIZED:
        case SEQ_STATE_RELEASED:
                if (!flag) {
                        seq->state = SEQ_STATE_ACQUIRING;
                } else {
                        if (flag & TRY_LOCK)
                                ls->nr_trylock++;
                        if (flag & READ_LOCK)
                                ls->nr_readlock++;
                        seq->state = SEQ_STATE_READ_ACQUIRED;
                        seq->read_count = 1;
                        ls->nr_acquired++;
                }
                break;
        case SEQ_STATE_READ_ACQUIRED:
                if (flag & READ_LOCK) {
                        seq->read_count++;
                        ls->nr_acquired++;
                        goto end;
                } else {
                        goto broken;
                }
                break;
        case SEQ_STATE_ACQUIRED:
        case SEQ_STATE_ACQUIRING:
        case SEQ_STATE_CONTENDED:
broken:
                /* broken lock sequence */
                if (!ls->broken) {
                        ls->broken = 1;
                        bad_hist[BROKEN_ACQUIRE]++;
                }
                list_del_init(&seq->list);
                free(seq);
                goto end;
        default:
                BUG_ON("Unknown state of lock sequence found!\n");
                break;
        }

        ls->nr_acquire++;
        seq->prev_event_time = sample->time;
end:
        return 0;
}

static int report_lock_acquired_event(struct evsel *evsel,
                                      struct perf_sample *sample)
{
        struct lock_stat *ls;
        struct thread_stat *ts;
        struct lock_seq_stat *seq;
        u64 contended_term;
        const char *name = evsel__strval(evsel, sample, "name");
        u64 addr = evsel__intval(evsel, sample, "lockdep_addr");
        u64 key;
        int ret;

        ret = get_key_by_aggr_mode_simple(&key, addr, sample->tid);
        if (ret < 0)
                return ret;

        ls = lock_stat_findnew(key, name, 0);
        if (!ls)
                return -ENOMEM;

        ts = thread_stat_findnew(sample->tid);
        if (!ts)
                return -ENOMEM;

        seq = get_seq(ts, addr);
        if (!seq)
                return -ENOMEM;

        switch (seq->state) {
        case SEQ_STATE_UNINITIALIZED:
                /* orphan event, do nothing */
                return 0;
        case SEQ_STATE_ACQUIRING:
                break;
        case SEQ_STATE_CONTENDED:
                contended_term = sample->time - seq->prev_event_time;
                ls->wait_time_total += contended_term;
                if (contended_term < ls->wait_time_min)
                        ls->wait_time_min = contended_term;
                if (ls->wait_time_max < contended_term)
                        ls->wait_time_max = contended_term;
                break;
        case SEQ_STATE_RELEASED:
        case SEQ_STATE_ACQUIRED:
        case SEQ_STATE_READ_ACQUIRED:
                /* broken lock sequence */
                if (!ls->broken) {
                        ls->broken = 1;
                        bad_hist[BROKEN_ACQUIRED]++;
                }
                list_del_init(&seq->list);
                free(seq);
                goto end;
        default:
                BUG_ON("Unknown state of lock sequence found!\n");
                break;
        }

        seq->state = SEQ_STATE_ACQUIRED;
        ls->nr_acquired++;
        ls->avg_wait_time = ls->nr_contended ? ls->wait_time_total/ls->nr_contended : 0;
        seq->prev_event_time = sample->time;
end:
        return 0;
}

static int report_lock_contended_event(struct evsel *evsel,
                                       struct perf_sample *sample)
{
        struct lock_stat *ls;
        struct thread_stat *ts;
        struct lock_seq_stat *seq;
        const char *name = evsel__strval(evsel, sample, "name");
        u64 addr = evsel__intval(evsel, sample, "lockdep_addr");
        u64 key;
        int ret;

        ret = get_key_by_aggr_mode_simple(&key, addr, sample->tid);
        if (ret < 0)
                return ret;

        ls = lock_stat_findnew(key, name, 0);
        if (!ls)
                return -ENOMEM;

        ts = thread_stat_findnew(sample->tid);
        if (!ts)
                return -ENOMEM;

        seq = get_seq(ts, addr);
        if (!seq)
                return -ENOMEM;

        switch (seq->state) {
        case SEQ_STATE_UNINITIALIZED:
                /* orphan event, do nothing */
                return 0;
        case SEQ_STATE_ACQUIRING:
                break;
        case SEQ_STATE_RELEASED:
        case SEQ_STATE_ACQUIRED:
        case SEQ_STATE_READ_ACQUIRED:
        case SEQ_STATE_CONTENDED:
                /* broken lock sequence */
                if (!ls->broken) {
                        ls->broken = 1;
                        bad_hist[BROKEN_CONTENDED]++;
                }
                list_del_init(&seq->list);
                free(seq);
                goto end;
        default:
                BUG_ON("Unknown state of lock sequence found!\n");
                break;
        }

        seq->state = SEQ_STATE_CONTENDED;
        ls->nr_contended++;
        ls->avg_wait_time = ls->wait_time_total/ls->nr_contended;
        seq->prev_event_time = sample->time;
end:
        return 0;
}

static int report_lock_release_event(struct evsel *evsel,
                                     struct perf_sample *sample)
{
        struct lock_stat *ls;
        struct thread_stat *ts;
        struct lock_seq_stat *seq;
        const char *name = evsel__strval(evsel, sample, "name");
        u64 addr = evsel__intval(evsel, sample, "lockdep_addr");
        u64 key;
        int ret;

        ret = get_key_by_aggr_mode_simple(&key, addr, sample->tid);
        if (ret < 0)
                return ret;

        ls = lock_stat_findnew(key, name, 0);
        if (!ls)
                return -ENOMEM;

        ts = thread_stat_findnew(sample->tid);
        if (!ts)
                return -ENOMEM;

        seq = get_seq(ts, addr);
        if (!seq)
                return -ENOMEM;

        switch (seq->state) {
        case SEQ_STATE_UNINITIALIZED:
                goto end;
        case SEQ_STATE_ACQUIRED:
                break;
        case SEQ_STATE_READ_ACQUIRED:
                seq->read_count--;
                BUG_ON(seq->read_count < 0);
                if (seq->read_count) {
                        ls->nr_release++;
                        goto end;
                }
                break;
        case SEQ_STATE_ACQUIRING:
        case SEQ_STATE_CONTENDED:
        case SEQ_STATE_RELEASED:
                /* broken lock sequence */
                if (!ls->broken) {
                        ls->broken = 1;
                        bad_hist[BROKEN_RELEASE]++;
                }
                goto free_seq;
        default:
                BUG_ON("Unknown state of lock sequence found!\n");
                break;
        }

        ls->nr_release++;
free_seq:
        list_del_init(&seq->list);
        free(seq);
end:
        return 0;
}

static int get_symbol_name_offset(struct map *map, struct symbol *sym, u64 ip,
                                  char *buf, int size)
{
        u64 offset;

        if (map == NULL || sym == NULL) {
                buf[0] = '\0';
                return 0;
        }

        offset = map__map_ip(map, ip) - sym->start;

        if (offset)
                return scnprintf(buf, size, "%s+%#lx", sym->name, offset);
        else
                return strlcpy(buf, sym->name, size);
}
static int lock_contention_caller(struct evsel *evsel, struct perf_sample *sample,
                                  char *buf, int size)
{
        struct thread *thread;
        struct callchain_cursor *cursor;
        struct machine *machine = &session->machines.host;
        struct symbol *sym;
        int skip = 0;
        int ret;

        /* lock names will be replaced to task name later */
        if (show_thread_stats)
                return -1;

        thread = machine__findnew_thread(machine, -1, sample->pid);
        if (thread == NULL)
                return -1;

        cursor = get_tls_callchain_cursor();

        /* use caller function name from the callchain */
        ret = thread__resolve_callchain(thread, cursor, evsel, sample,
                                        NULL, NULL, max_stack_depth);
        if (ret != 0) {
                thread__put(thread);
                return -1;
        }

        callchain_cursor_commit(cursor);
        thread__put(thread);

        while (true) {
                struct callchain_cursor_node *node;

                node = callchain_cursor_current(cursor);
                if (node == NULL)
                        break;

                /* skip first few entries - for lock functions */
                if (++skip <= stack_skip)
                        goto next;

                sym = node->ms.sym;
                if (sym && !machine__is_lock_function(machine, node->ip)) {
                        get_symbol_name_offset(node->ms.map, sym, node->ip,
                                               buf, size);
                        return 0;
                }

next:
                callchain_cursor_advance(cursor);
        }
        return -1;
}

static u64 callchain_id(struct evsel *evsel, struct perf_sample *sample)
{
        struct callchain_cursor *cursor;
        struct machine *machine = &session->machines.host;
        struct thread *thread;
        u64 hash = 0;
        int skip = 0;
        int ret;

        thread = machine__findnew_thread(machine, -1, sample->pid);
        if (thread == NULL)
                return -1;

        cursor = get_tls_callchain_cursor();
        /* use caller function name from the callchain */
        ret = thread__resolve_callchain(thread, cursor, evsel, sample,
                                        NULL, NULL, max_stack_depth);
        thread__put(thread);

        if (ret != 0)
                return -1;

        callchain_cursor_commit(cursor);

        while (true) {
                struct callchain_cursor_node *node;

                node = callchain_cursor_current(cursor);
                if (node == NULL)
                        break;

                /* skip first few entries - for lock functions */
                if (++skip <= stack_skip)
                        goto next;

                if (node->ms.sym && machine__is_lock_function(machine, node->ip))
                        goto next;

                hash ^= hash_long((unsigned long)node->ip, 64);

next:
                callchain_cursor_advance(cursor);
        }
        return hash;
}

static u64 *get_callstack(struct perf_sample *sample, int max_stack)
{
        u64 *callstack;
        u64 i;
        int c;

        callstack = calloc(max_stack, sizeof(*callstack));
        if (callstack == NULL)
                return NULL;

        for (i = 0, c = 0; i < sample->callchain->nr && c < max_stack; i++) {
                u64 ip = sample->callchain->ips[i];

                if (ip >= PERF_CONTEXT_MAX)
                        continue;

                callstack[c++] = ip;
        }
        return callstack;
}

static int report_lock_contention_begin_event(struct evsel *evsel,
                                              struct perf_sample *sample)
{
        struct lock_stat *ls;
        struct thread_stat *ts;
        struct lock_seq_stat *seq;
        u64 addr = evsel__intval(evsel, sample, "lock_addr");
        unsigned int flags = evsel__intval(evsel, sample, "flags");
        u64 key;
        int i, ret;
        static bool kmap_loaded;
        struct machine *machine = &session->machines.host;
        struct map *kmap;
        struct symbol *sym;

        ret = get_key_by_aggr_mode(&key, addr, evsel, sample);
        if (ret < 0)
                return ret;

        if (!kmap_loaded) {
                unsigned long *addrs;

                /* make sure it loads the kernel map to find lock symbols */
                map__load(machine__kernel_map(machine));
                kmap_loaded = true;

                /* convert (kernel) symbols to addresses */
                for (i = 0; i < filters.nr_syms; i++) {
                        sym = machine__find_kernel_symbol_by_name(machine,
                                                                  filters.syms[i],
                                                                  &kmap);
                        if (sym == NULL) {
                                pr_warning("ignore unknown symbol: %s\n",
                                           filters.syms[i]);
                                continue;
                        }

                        addrs = realloc(filters.addrs,
                                        (filters.nr_addrs + 1) * sizeof(*addrs));
                        if (addrs == NULL) {
                                pr_warning("memory allocation failure\n");
                                return -ENOMEM;
                        }

                        addrs[filters.nr_addrs++] = map__unmap_ip(kmap, sym->start);
                        filters.addrs = addrs;
                }
        }

        ls = lock_stat_find(key);
        if (!ls) {
                char buf[128];
                const char *name = "";

                switch (aggr_mode) {
                case LOCK_AGGR_ADDR:
                        sym = machine__find_kernel_symbol(machine, key, &kmap);
                        if (sym)
                                name = sym->name;
                        break;
                case LOCK_AGGR_CALLER:
                        name = buf;
                        if (lock_contention_caller(evsel, sample, buf, sizeof(buf)) < 0)
                                name = "Unknown";
                        break;
                case LOCK_AGGR_CGROUP:
                case LOCK_AGGR_TASK:
                default:
                        break;
                }

                ls = lock_stat_findnew(key, name, flags);
                if (!ls)
                        return -ENOMEM;
        }

        if (filters.nr_types) {
                bool found = false;

                for (i = 0; i < filters.nr_types; i++) {
                        if (flags == filters.types[i]) {
                                found = true;
                                break;
                        }
                }

                if (!found)
                        return 0;
        }

        if (filters.nr_addrs) {
                bool found = false;

                for (i = 0; i < filters.nr_addrs; i++) {
                        if (addr == filters.addrs[i]) {
                                found = true;
                                break;
                        }
                }

                if (!found)
                        return 0;
        }

        if (needs_callstack()) {
                u64 *callstack = get_callstack(sample, max_stack_depth);
                if (callstack == NULL)
                        return -ENOMEM;

                if (!match_callstack_filter(machine, callstack, max_stack_depth)) {
                        free(callstack);
                        return 0;
                }

                if (ls->callstack == NULL)
                        ls->callstack = callstack;
                else
                        free(callstack);
        }

        ts = thread_stat_findnew(sample->tid);
        if (!ts)
                return -ENOMEM;

        seq = get_seq(ts, addr);
        if (!seq)
                return -ENOMEM;

        switch (seq->state) {
        case SEQ_STATE_UNINITIALIZED:
        case SEQ_STATE_ACQUIRED:
                break;
        case SEQ_STATE_CONTENDED:
                /*
                 * It can have nested contention begin with mutex spinning,
                 * then we would use the original contention begin event and
                 * ignore the second one.
                 */
                goto end;
        case SEQ_STATE_ACQUIRING:
        case SEQ_STATE_READ_ACQUIRED:
        case SEQ_STATE_RELEASED:
                /* broken lock sequence */
                if (!ls->broken) {
                        ls->broken = 1;
                        bad_hist[BROKEN_CONTENDED]++;
                }
                list_del_init(&seq->list);
                free(seq);
                goto end;
        default:
                BUG_ON("Unknown state of lock sequence found!\n");
                break;
        }

        if (seq->state != SEQ_STATE_CONTENDED) {
                seq->state = SEQ_STATE_CONTENDED;
                seq->prev_event_time = sample->time;
                ls->nr_contended++;
        }
end:
        return 0;
}

static int report_lock_contention_end_event(struct evsel *evsel,
                                            struct perf_sample *sample)
{
        struct lock_stat *ls;
        struct thread_stat *ts;
        struct lock_seq_stat *seq;
        u64 contended_term;
        u64 addr = evsel__intval(evsel, sample, "lock_addr");
        u64 key;
        int ret;

        ret = get_key_by_aggr_mode(&key, addr, evsel, sample);
        if (ret < 0)
                return ret;

        ls = lock_stat_find(key);
        if (!ls)
                return 0;

        ts = thread_stat_find(sample->tid);
        if (!ts)
                return 0;

        seq = get_seq(ts, addr);
        if (!seq)
                return -ENOMEM;

        switch (seq->state) {
        case SEQ_STATE_UNINITIALIZED:
                goto end;
        case SEQ_STATE_CONTENDED:
                contended_term = sample->time - seq->prev_event_time;
                ls->wait_time_total += contended_term;
                if (contended_term < ls->wait_time_min)
                        ls->wait_time_min = contended_term;
                if (ls->wait_time_max < contended_term)
                        ls->wait_time_max = contended_term;
                break;
        case SEQ_STATE_ACQUIRING:
        case SEQ_STATE_ACQUIRED:
        case SEQ_STATE_READ_ACQUIRED:
        case SEQ_STATE_RELEASED:
                /* broken lock sequence */
                if (!ls->broken) {
                        ls->broken = 1;
                        bad_hist[BROKEN_ACQUIRED]++;
                }
                list_del_init(&seq->list);
                free(seq);
                goto end;
        default:
                BUG_ON("Unknown state of lock sequence found!\n");
                break;
        }

        seq->state = SEQ_STATE_ACQUIRED;
        ls->nr_acquired++;
        ls->avg_wait_time = ls->wait_time_total/ls->nr_acquired;
end:
        return 0;
}

/* lock oriented handlers */
/* TODO: handlers for CPU oriented, thread oriented */
static struct trace_lock_handler report_lock_ops  = {
        .acquire_event          = report_lock_acquire_event,
        .acquired_event         = report_lock_acquired_event,
        .contended_event        = report_lock_contended_event,
        .release_event          = report_lock_release_event,
        .contention_begin_event = report_lock_contention_begin_event,
        .contention_end_event   = report_lock_contention_end_event,
};

static struct trace_lock_handler contention_lock_ops  = {
        .contention_begin_event = report_lock_contention_begin_event,
        .contention_end_event   = report_lock_contention_end_event,
};


static struct trace_lock_handler *trace_handler;

static int evsel__process_lock_acquire(struct evsel *evsel, struct perf_sample *sample)
{
        if (trace_handler->acquire_event)
                return trace_handler->acquire_event(evsel, sample);
        return 0;
}

static int evsel__process_lock_acquired(struct evsel *evsel, struct perf_sample *sample)
{
        if (trace_handler->acquired_event)
                return trace_handler->acquired_event(evsel, sample);
        return 0;
}

static int evsel__process_lock_contended(struct evsel *evsel, struct perf_sample *sample)
{
        if (trace_handler->contended_event)
                return trace_handler->contended_event(evsel, sample);
        return 0;
}

static int evsel__process_lock_release(struct evsel *evsel, struct perf_sample *sample)
{
        if (trace_handler->release_event)
                return trace_handler->release_event(evsel, sample);
        return 0;
}

static int evsel__process_contention_begin(struct evsel *evsel, struct perf_sample *sample)
{
        if (trace_handler->contention_begin_event)
                return trace_handler->contention_begin_event(evsel, sample);
        return 0;
}

static int evsel__process_contention_end(struct evsel *evsel, struct perf_sample *sample)
{
        if (trace_handler->contention_end_event)
                return trace_handler->contention_end_event(evsel, sample);
        return 0;
}

static void print_bad_events(int bad, int total)
{
        /* Output for debug, this have to be removed */
        int i;
        int broken = 0;
        const char *name[4] =
                { "acquire", "acquired", "contended", "release" };

        for (i = 0; i < BROKEN_MAX; i++)
                broken += bad_hist[i];

        if (quiet || total == 0 || (broken == 0 && verbose <= 0))
                return;

        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++)
                fprintf(lock_output, " %10s: %d\n", name[i], bad_hist[i]);
}

/* TODO: various way to print, coloring, nano or milli sec */
static void print_result(void)
{
        struct lock_stat *st;
        struct lock_key *key;
        char cut_name[20];
        int bad, total, printed;

        if (!quiet) {
                fprintf(lock_output, "%20s ", "Name");
                list_for_each_entry(key, &lock_keys, list)
                        fprintf(lock_output, "%*s ", key->len, key->header);
                fprintf(lock_output, "\n\n");
        }

        bad = total = printed = 0;
        while ((st = pop_from_result())) {
                total++;
                if (st->broken)
                        bad++;
                if (!st->nr_acquired)
                        continue;

                bzero(cut_name, 20);

                if (strlen(st->name) < 20) {
                        /* output raw name */
                        const char *name = st->name;

                        if (show_thread_stats) {
                                struct thread *t;

                                /* st->addr contains tid of thread */
                                t = perf_session__findnew(session, st->addr);
                                name = thread__comm_str(t);
                        }

                        fprintf(lock_output, "%20s ", name);
                } else {
                        strncpy(cut_name, st->name, 16);
                        cut_name[16] = '.';
                        cut_name[17] = '.';
                        cut_name[18] = '.';
                        cut_name[19] = '\0';
                        /* cut off name for saving output style */
                        fprintf(lock_output, "%20s ", cut_name);
                }

                list_for_each_entry(key, &lock_keys, list) {
                        key->print(key, st);
                        fprintf(lock_output, " ");
                }
                fprintf(lock_output, "\n");

                if (++printed >= print_nr_entries)
                        break;
        }

        print_bad_events(bad, total);
}

static bool info_threads, info_map;

static void dump_threads(void)
{
        struct thread_stat *st;
        struct rb_node *node;
        struct thread *t;

        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);
                fprintf(lock_output, "%10d: %s\n", st->tid, thread__comm_str(t));
                node = rb_next(node);
                thread__put(t);
        }
}

static int compare_maps(struct lock_stat *a, struct lock_stat *b)
{
        int ret;

        if (a->name && b->name)
                ret = strcmp(a->name, b->name);
        else
                ret = !!a->name - !!b->name;

        if (!ret)
                return a->addr < b->addr;
        else
                return ret < 0;
}

static void dump_map(void)
{
        unsigned int i;
        struct lock_stat *st;

        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);
                }
        }

        while ((st = pop_from_result()))
                fprintf(lock_output, " %#llx: %s\n", (unsigned long long)st->addr, st->name);
}

static void dump_info(void)
{
        if (info_threads)
                dump_threads();

        if (info_map) {
                if (info_threads)
                        fputc('\n', lock_output);
                dump_map();
        }
}

static const struct evsel_str_handler lock_tracepoints[] = {
        { "lock:lock_acquire",   evsel__process_lock_acquire,   }, /* CONFIG_LOCKDEP */
        { "lock:lock_acquired",  evsel__process_lock_acquired,  }, /* CONFIG_LOCKDEP, CONFIG_LOCK_STAT */
        { "lock:lock_contended", evsel__process_lock_contended, }, /* CONFIG_LOCKDEP, CONFIG_LOCK_STAT */
        { "lock:lock_release",   evsel__process_lock_release,   }, /* CONFIG_LOCKDEP */
};

static const struct evsel_str_handler contention_tracepoints[] = {
        { "lock:contention_begin", evsel__process_contention_begin, },
        { "lock:contention_end",   evsel__process_contention_end,   },
};

static int process_event_update(const struct perf_tool *tool,
                                union perf_event *event,
                                struct evlist **pevlist)
{
        int ret;

        ret = perf_event__process_event_update(tool, event, pevlist);
        if (ret < 0)
                return ret;

        /* this can return -EEXIST since we call it for each evsel */
        perf_session__set_tracepoints_handlers(session, lock_tracepoints);
        perf_session__set_tracepoints_handlers(session, contention_tracepoints);
        return 0;
}

typedef int (*tracepoint_handler)(struct evsel *evsel,
                                  struct perf_sample *sample);

static int process_sample_event(const struct perf_tool *tool __maybe_unused,
                                union perf_event *event,
                                struct perf_sample *sample,
                                struct evsel *evsel,
                                struct machine *machine)
{
        int err = 0;
        struct thread *thread = machine__findnew_thread(machine, sample->pid,
                                                        sample->tid);

        if (thread == NULL) {
                pr_debug("problem processing %d event, skipping it.\n",
                        event->header.type);
                return -1;
        }

        if (evsel->handler != NULL) {
                tracepoint_handler f = evsel->handler;
                err = f(evsel, sample);
        }

        thread__put(thread);

        return err;
}

static void combine_result(void)
{
        unsigned int i;
        struct lock_stat *st;

        if (!combine_locks)
                return;

        for (i = 0; i < LOCKHASH_SIZE; i++) {
                hlist_for_each_entry(st, &lockhash_table[i], hash_entry) {
                        combine_lock_stats(st);
                }
        }
}

static void sort_result(void)
{
        unsigned int i;
        struct lock_stat *st;

        for (i = 0; i < LOCKHASH_SIZE; i++) {
                hlist_for_each_entry(st, &lockhash_table[i], hash_entry) {
                        insert_to_result(st, compare);
                }
        }
}

static const struct {
        unsigned int flags;
        /*
         * 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" },
        { LCB_F_SPIN | LCB_F_READ,      "rwlock:R",     "rwlock" },
        { LCB_F_SPIN | LCB_F_WRITE,     "rwlock:W",     "rwlock" },
        { LCB_F_READ,                   "rwsem:R",      "rwsem" },
        { LCB_F_WRITE,                  "rwsem:W",      "rwsem" },
        { LCB_F_RT,                     "rt-mutex",     "rt-mutex" },
        { LCB_F_RT | LCB_F_READ,        "rwlock-rt:R",  "rwlock-rt" },
        { LCB_F_RT | LCB_F_WRITE,       "rwlock-rt:W",  "rwlock-rt" },
        { LCB_F_PERCPU | LCB_F_READ,    "pcpu-sem:R",   "percpu-rwsem" },
        { LCB_F_PERCPU | LCB_F_WRITE,   "pcpu-sem:W",   "percpu-rwsem" },
        { LCB_F_MUTEX,                  "mutex",        "mutex" },
        { LCB_F_MUTEX | LCB_F_SPIN,     "mutex",        "mutex" },
        /* alias for optimistic spinning only */
        { LCB_F_MUTEX | LCB_F_SPIN,     "mutex:spin",   "mutex-spin" },
};

static const char *get_type_flags_name(unsigned int flags)
{
        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].flags_name;
        }
        return "unknown";
}

static const char *get_type_lock_name(unsigned int flags)
{
        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].lock_name;
        }
        return "unknown";
}

static void lock_filter_finish(void)
{
        zfree(&filters.types);
        filters.nr_types = 0;

        zfree(&filters.addrs);
        filters.nr_addrs = 0;

        for (int i = 0; i < filters.nr_syms; i++)
                free(filters.syms[i]);

        zfree(&filters.syms);
        filters.nr_syms = 0;

        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)
{
        sort_result();
}

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;
        case LOCK_AGGR_CGROUP:
                fprintf(lock_output, "  %s\n\n", "cgroup");
                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;
        case LOCK_AGGR_CGROUP:
                fprintf(lock_output, "%s\n", "cgroup");
                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_flags_name(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_lock_name(st->flags));
                break;
        case LOCK_AGGR_CGROUP:
                fprintf(lock_output, "  %s\n", st->name);
                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_flags_name(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_lock_name(st->flags));
                break;
        case LOCK_AGGR_CGROUP:
                fprintf(lock_output, "%s\n",st->name);
                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;
        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);

        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;
        int bad, total, printed;

        if (!quiet)
                print_header();

        bad = total = printed = 0;

        while ((st = pop_from_result())) {
                total += use_bpf ? st->nr_contended : 1;
                if (st->broken)
                        bad++;

                if (!st->wait_time_total)
                        continue;

                print_lock_stat(con, st);

                if (++printed >= print_nr_entries)
                        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())) {
                        total += use_bpf ? st->nr_contended : 1;
                        if (st->broken)
                                bad++;
                }
        }
        /* some entries are collected but hidden by the callstack filter */
        total += con->nr_filtered;

        print_footer(total, bad, &con->fails);
}

static bool force;

static int __cmd_report(bool display_info)
{
        int err = -EINVAL;
        struct perf_tool eops;
        struct perf_data data = {
                .path  = input_name,
                .mode  = PERF_DATA_MODE_READ,
                .force = force,
        };

        perf_tool__init(&eops, /*ordered_events=*/true);
        eops.attr                = perf_event__process_attr;
        eops.event_update        = process_event_update;
        eops.sample              = process_sample_event;
        eops.comm                = perf_event__process_comm;
        eops.mmap                = perf_event__process_mmap;
        eops.mmap2               = perf_event__process_mmap2;
        eops.namespaces          = perf_event__process_namespaces;
        eops.tracing_data        = perf_event__process_tracing_data;
        session = perf_session__new(&data, &eops);
        if (IS_ERR(session)) {
                pr_err("Initializing perf session failed\n");
                return PTR_ERR(session);
        }

        symbol_conf.allow_aliases = true;
        symbol__init(perf_session__env(session));

        if (!data.is_pipe) {
                if (!perf_session__has_traces(session, "lock record"))
                        goto out_delete;

                if (perf_session__set_tracepoints_handlers(session, lock_tracepoints)) {
                        pr_err("Initializing perf session tracepoint handlers failed\n");
                        goto out_delete;
                }

                if (perf_session__set_tracepoints_handlers(session, contention_tracepoints)) {
                        pr_err("Initializing perf session tracepoint handlers failed\n");
                        goto out_delete;
                }
        }

        if (setup_output_field(false, output_fields))
                goto out_delete;

        if (select_key(false))
                goto out_delete;

        if (show_thread_stats)
                aggr_mode = LOCK_AGGR_TASK;

        err = perf_session__process_events(session);
        if (err)
                goto out_delete;

        setup_pager();
        if (display_info) /* used for info subcommand */
                dump_info();
        else {
                combine_result();
                sort_result();
                print_result();
        }

out_delete:
        perf_session__delete(session);
        return err;
}

static void sighandler(int sig __maybe_unused)
{
}

static int check_lock_contention_options(const struct option *options,
                                         const char * const *usage)

{
        if (show_thread_stats && show_lock_addrs) {
                pr_err("Cannot use thread and addr mode together\n");
                parse_options_usage(usage, options, "threads", 0);
                parse_options_usage(NULL, options, "lock-addr", 0);
                return -1;
        }

        if (show_lock_owner && !use_bpf) {
                pr_err("Lock owners are available only with BPF\n");
                parse_options_usage(usage, options, "lock-owner", 0);
                parse_options_usage(NULL, options, "use-bpf", 0);
                return -1;
        }

        if (show_lock_owner && show_lock_addrs) {
                pr_err("Cannot use owner and addr mode together\n");
                parse_options_usage(usage, options, "lock-owner", 0);
                parse_options_usage(NULL, options, "lock-addr", 0);
                return -1;
        }

        if (show_lock_cgroups && !use_bpf) {
                pr_err("Cgroups are available only with BPF\n");
                parse_options_usage(usage, options, "lock-cgroup", 0);
                parse_options_usage(NULL, options, "use-bpf", 0);
                return -1;
        }

        if (show_lock_cgroups && show_lock_addrs) {
                pr_err("Cannot use cgroup and addr mode together\n");
                parse_options_usage(usage, options, "lock-cgroup", 0);
                parse_options_usage(NULL, options, "lock-addr", 0);
                return -1;
        }

        if (show_lock_cgroups && show_thread_stats) {
                pr_err("Cannot use cgroup and thread mode together\n");
                parse_options_usage(usage, options, "lock-cgroup", 0);
                parse_options_usage(NULL, options, "threads", 0);
                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) {
                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;
}

static int __cmd_contention(int argc, const char **argv)
{
        int err = -EINVAL;
        struct perf_tool eops;
        struct perf_data data = {
                .path  = input_name,
                .mode  = PERF_DATA_MODE_READ,
                .force = force,
        };
        struct lock_contention con = {
                .target = &target,
                .map_nr_entries = bpf_map_entries,
                .max_stack = max_stack_depth,
                .stack_skip = stack_skip,
                .filters = &filters,
                .delays = delays,
                .nr_delays = nr_delays,
                .save_callstack = needs_callstack(),
                .owner = show_lock_owner,
                .cgroups = RB_ROOT,
        };
        struct perf_env host_env;

        lockhash_table = calloc(LOCKHASH_SIZE, sizeof(*lockhash_table));
        if (!lockhash_table)
                return -ENOMEM;

        con.result = &lockhash_table[0];

        perf_tool__init(&eops, /*ordered_events=*/true);
        eops.attr                = perf_event__process_attr;
        eops.event_update        = process_event_update;
        eops.sample              = process_sample_event;
        eops.comm                = perf_event__process_comm;
        eops.mmap                = perf_event__process_mmap;
        eops.mmap2               = perf_event__process_mmap2;
        eops.tracing_data        = perf_event__process_tracing_data;

        perf_env__init(&host_env);
        session = __perf_session__new(use_bpf ? NULL : &data, &eops,
                                /*trace_event_repipe=*/false, &host_env);

        if (IS_ERR(session)) {
                pr_err("Initializing perf session failed\n");
                err = PTR_ERR(session);
                session = NULL;
                goto out_delete;
        }

        con.machine = &session->machines.host;

        con.aggr_mode = aggr_mode = show_thread_stats ? LOCK_AGGR_TASK :
                show_lock_addrs ? LOCK_AGGR_ADDR :
                show_lock_cgroups ? LOCK_AGGR_CGROUP : LOCK_AGGR_CALLER;

        if (con.aggr_mode == LOCK_AGGR_CALLER)
                con.save_callstack = true;

        symbol_conf.allow_aliases = true;
        symbol__init(perf_session__env(session));

        if (use_bpf) {
                err = target__validate(&target);
                if (err) {
                        char errbuf[512];

                        target__strerror(&target, err, errbuf, 512);
                        pr_err("%s\n", errbuf);
                        goto out_delete;
                }

                signal(SIGINT, sighandler);
                signal(SIGCHLD, sighandler);
                signal(SIGTERM, sighandler);

                con.evlist = evlist__new();
                if (con.evlist == NULL) {
                        err = -ENOMEM;
                        goto out_delete;
                }

                err = evlist__create_maps(con.evlist, &target);
                if (err < 0)
                        goto out_delete;

                if (argc) {
                        err = evlist__prepare_workload(con.evlist, &target,
                                                       argv, false, NULL);
                        if (err < 0)
                                goto out_delete;
                }

                err = lock_contention_prepare(&con);
                if (err < 0) {
                        pr_err("lock contention BPF setup failed\n");
                        goto out_delete;
                }
        } else if (!data.is_pipe) {
                if (!perf_session__has_traces(session, "lock record"))
                        goto out_delete;

                if (!evlist__find_evsel_by_str(session->evlist,
                                               "lock:contention_begin")) {
                        pr_err("lock contention evsel not found\n");
                        goto out_delete;
                }

                if (perf_session__set_tracepoints_handlers(session,
                                                contention_tracepoints)) {
                        pr_err("Initializing perf session tracepoint handlers failed\n");
                        goto out_delete;
                }
        }

        err = setup_output_field(true, output_fields);
        if (err) {
                pr_err("Failed to setup output field\n");
                goto out_delete;
        }

        err = select_key(true);
        if (err)
                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)
                        evlist__start_workload(con.evlist);

                /* wait for signal */
                pause();

                lock_contention_stop();
                lock_contention_read(&con);
        } else {
                err = perf_session__process_events(session);
                if (err)
                        goto out_delete;
        }

        setup_pager();

        sort_contention_result();
        print_contention_result(&con);

out_delete:
        lock_filter_finish();
        evlist__delete(con.evlist);
        lock_contention_finish(&con);
        perf_session__delete(session);
        perf_env__exit(&host_env);
        zfree(&lockhash_table);
        return err;
}


static int __cmd_record(int argc, const char **argv)
{
        const char *record_args[] = {
                "record", "-R", "-m", "1024", "-c", "1", "--synth", "task",
        };
        const char *callgraph_args[] = {
                "--call-graph", "fp," __stringify(CONTENTION_STACK_DEPTH),
        };
        unsigned int rec_argc, i, j, ret;
        unsigned int nr_tracepoints;
        unsigned int nr_callgraph_args = 0;
        const char **rec_argv;
        bool has_lock_stat = true;

        for (i = 0; i < ARRAY_SIZE(lock_tracepoints); i++) {
                if (!is_valid_tracepoint(lock_tracepoints[i].name)) {
                        pr_debug("tracepoint %s is not enabled. "
                                 "Are CONFIG_LOCKDEP and CONFIG_LOCK_STAT enabled?\n",
                                 lock_tracepoints[i].name);
                        has_lock_stat = false;
                        break;
                }
        }

        if (has_lock_stat)
                goto setup_args;

        for (i = 0; i < ARRAY_SIZE(contention_tracepoints); i++) {
                if (!is_valid_tracepoint(contention_tracepoints[i].name)) {
                        pr_err("tracepoint %s is not enabled.\n",
                               contention_tracepoints[i].name);
                        return 1;
                }
        }

        nr_callgraph_args = ARRAY_SIZE(callgraph_args);

setup_args:
        rec_argc = ARRAY_SIZE(record_args) + nr_callgraph_args + argc - 1;

        if (has_lock_stat)
                nr_tracepoints = ARRAY_SIZE(lock_tracepoints);
        else
                nr_tracepoints = ARRAY_SIZE(contention_tracepoints);

        /* factor of 2 is for -e in front of each tracepoint */
        rec_argc += 2 * nr_tracepoints;

        rec_argv = calloc(rec_argc + 1, sizeof(char *));
        if (!rec_argv)
                return -ENOMEM;

        for (i = 0; i < ARRAY_SIZE(record_args); i++)
                rec_argv[i] = record_args[i];

        for (j = 0; j < nr_tracepoints; j++) {
                rec_argv[i++] = "-e";
                rec_argv[i++] = has_lock_stat
                        ? lock_tracepoints[j].name
                        : contention_tracepoints[j].name;
        }

        for (j = 0; j < nr_callgraph_args; j++, i++)
                rec_argv[i] = callgraph_args[j];

        for (j = 1; j < (unsigned int)argc; j++, i++)
                rec_argv[i] = argv[j];

        BUG_ON(i != rec_argc);

        ret = cmd_record(i, rec_argv);
        free(rec_argv);
        return ret;
}

static int parse_map_entry(const struct option *opt, const char *str,
                            int unset __maybe_unused)
{
        unsigned long *len = (unsigned long *)opt->value;
        unsigned long val;
        char *endptr;

        errno = 0;
        val = strtoul(str, &endptr, 0);
        if (*endptr != '\0' || errno != 0) {
                pr_err("invalid BPF map length: %s\n", str);
                return -1;
        }

        *len = val;
        return 0;
}

static int parse_max_stack(const struct option *opt, const char *str,
                           int unset __maybe_unused)
{
        unsigned long *len = (unsigned long *)opt->value;
        long val;
        char *endptr;

        errno = 0;
        val = strtol(str, &endptr, 0);
        if (*endptr != '\0' || errno != 0) {
                pr_err("invalid max stack depth: %s\n", str);
                return -1;
        }

        if (val < 0 || val > sysctl__max_stack()) {
                pr_err("invalid max stack depth: %ld\n", val);
                return -1;
        }

        *len = val;
        return 0;
}

static bool add_lock_type(unsigned int flags)
{
        unsigned int *tmp;

        tmp = realloc(filters.types, (filters.nr_types + 1) * sizeof(*filters.types));
        if (tmp == NULL)
                return false;

        tmp[filters.nr_types++] = flags;
        filters.types = tmp;
        return true;
}

static int parse_lock_type(const struct option *opt __maybe_unused, const char *str,
                           int unset __maybe_unused)
{
        char *s, *tmp, *tok;

        s = strdup(str);
        if (s == NULL)
                return -1;

        for (tok = strtok_r(s, ", ", &tmp); tok; tok = strtok_r(NULL, ", ", &tmp)) {
                bool found = false;

                /* `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].flags_name, tok) &&
                                    add_lock_type(lock_type_table[i].flags)) {
                                        found = true;
                                        break;
                                }
                        }

                        if (!found) {
                                pr_err("Unknown lock flags name: %s\n", tok);
                                free(s);
                                return -1;
                        }

                        continue;
                }

                /*
                 * 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].lock_name, tok)) {
                                if (add_lock_type(lock_type_table[i].flags)) {
                                        found = true;
                                } else {
                                        free(s);
                                        return -1;
                                }
                        }
                }

                if (!found) {
                        pr_err("Unknown lock name: %s\n", tok);
                        free(s);
                        return -1;
                }

        }

        free(s);
        return 0;
}

static bool add_lock_addr(unsigned long addr)
{
        unsigned long *tmp;

        tmp = realloc(filters.addrs, (filters.nr_addrs + 1) * sizeof(*filters.addrs));
        if (tmp == NULL) {
                pr_err("Memory allocation failure\n");
                return false;
        }

        tmp[filters.nr_addrs++] = addr;
        filters.addrs = tmp;
        return true;
}

static bool add_lock_sym(char *name)
{
        char **tmp;
        char *sym = strdup(name);

        if (sym == NULL) {
                pr_err("Memory allocation failure\n");
                return false;
        }

        tmp = realloc(filters.syms, (filters.nr_syms + 1) * sizeof(*filters.syms));
        if (tmp == NULL) {
                pr_err("Memory allocation failure\n");
                free(sym);
                return false;
        }

        tmp[filters.nr_syms++] = sym;
        filters.syms = tmp;
        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)
{
        char *s, *tmp, *tok;
        int ret = 0;
        u64 addr;

        s = strdup(str);
        if (s == NULL)
                return -1;

        for (tok = strtok_r(s, ", ", &tmp); tok; tok = strtok_r(NULL, ", ", &tmp)) {
                char *end;

                addr = strtoul(tok, &end, 16);
                if (*end == '\0') {
                        if (!add_lock_addr(addr)) {
                                ret = -1;
                                break;
                        }
                        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.
                 */
                if (!add_lock_sym(tok)) {
                        ret = -1;
                        break;
                }
        }

        free(s);
        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;
}

static bool add_lock_cgroup(char *name)
{
        u64 *tmp;
        struct cgroup *cgrp;

        cgrp = cgroup__new(name, /*do_open=*/false);
        if (cgrp == NULL) {
                pr_err("Failed to create cgroup: %s\n", name);
                return false;
        }

        if (read_cgroup_id(cgrp) < 0) {
                pr_err("Failed to read cgroup id for %s\n", name);
                cgroup__put(cgrp);
                return false;
        }

        tmp = realloc(filters.cgrps, (filters.nr_cgrps + 1) * sizeof(*filters.cgrps));
        if (tmp == NULL) {
                pr_err("Memory allocation failure\n");
                return false;
        }

        tmp[filters.nr_cgrps++] = cgrp->id;
        filters.cgrps = tmp;
        cgroup__put(cgrp);
        return true;
}

static int parse_cgroup_filter(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)) {
                if (!add_lock_cgroup(tok)) {
                        ret = -1;
                        break;
                }
        }

        free(s);
        return ret;
}

static bool add_lock_delay(char *spec)
{
        char *at, *pos;
        struct lock_delay *tmp;
        unsigned long duration;

        at = strchr(spec, '@');
        if (at == NULL) {
                pr_err("lock delay should have '@' sign: %s\n", spec);
                return false;
        }
        if (at == spec) {
                pr_err("lock delay should have time before '@': %s\n", spec);
                return false;
        }

        *at = '\0';
        duration = strtoul(spec, &pos, 0);
        if (!strcmp(pos, "ns"))
                duration *= 1;
        else if (!strcmp(pos, "us"))
                duration *= 1000;
        else if (!strcmp(pos, "ms"))
                duration *= 1000 * 1000;
        else if (*pos) {
                pr_err("invalid delay time: %s@%s\n", spec, at + 1);
                return false;
        }

        if (duration > 10 * 1000 * 1000) {
                pr_err("lock delay is too long: %s (> 10ms)\n", spec);
                return false;
        }

        tmp = realloc(delays, (nr_delays + 1) * sizeof(*delays));
        if (tmp == NULL) {
                pr_err("Memory allocation failure\n");
                return false;
        }
        delays = tmp;

        delays[nr_delays].sym = strdup(at + 1);
        if (delays[nr_delays].sym == NULL) {
                pr_err("Memory allocation failure\n");
                return false;
        }
        delays[nr_delays].time = duration;

        nr_delays++;
        return true;
}

static int parse_lock_delay(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)) {
                if (!add_lock_delay(tok)) {
                        ret = -1;
                        break;
                }
        }

        free(s);
        return ret;
}

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"),
        OPT_STRING(0, "vmlinux", &symbol_conf.vmlinux_name,
                   "file", "vmlinux pathname"),
        OPT_STRING(0, "kallsyms", &symbol_conf.kallsyms_name,
                   "file", "kallsyms pathname"),
        OPT_BOOLEAN('q', "quiet", &quiet, "Do not show any warnings or messages"),
        OPT_END()
        };

        const struct option info_options[] = {
        OPT_BOOLEAN('t', "threads", &info_threads,
                    "dump the thread list in perf.data"),
        OPT_BOOLEAN('m', "map", &info_map,
                    "dump the map of lock instances (address:name table)"),
        OPT_PARENT(lock_options)
        };

        const struct option report_options[] = {
        OPT_STRING('k', "key", &sort_key, "acquired",
                    "key for sorting (acquired / contended / avg_wait / wait_total / wait_max / wait_min)"),
        OPT_STRING('F', "field", &output_fields, NULL,
                    "output fields (acquired / contended / avg_wait / wait_total / wait_max / wait_min)"),
        /* TODO: type */
        OPT_BOOLEAN('c', "combine-locks", &combine_locks,
                    "combine locks in the same class"),
        OPT_BOOLEAN('t', "threads", &show_thread_stats,
                    "show per-thread lock stats"),
        OPT_INTEGER('E', "entries", &print_nr_entries, "display this many functions"),
        OPT_PARENT(lock_options)
        };

        struct option contention_options[] = {
        OPT_STRING('k', "key", &sort_key, "wait_total",
                    "key for sorting (contended / wait_total / wait_max / wait_min / avg_wait)"),
        OPT_STRING('F', "field", &output_fields, "contended,wait_total,wait_max,avg_wait",
                    "output fields (contended / wait_total / wait_max / wait_min / avg_wait)"),
        OPT_BOOLEAN('t', "threads", &show_thread_stats,
                    "show per-thread lock stats"),
        OPT_BOOLEAN('b', "use-bpf", &use_bpf, "use BPF program to collect lock contention stats"),
        OPT_BOOLEAN('a', "all-cpus", &target.system_wide,
                    "System-wide collection from all CPUs"),
        OPT_STRING('C', "cpu", &target.cpu_list, "cpu",
                    "List of cpus to monitor"),
        OPT_STRING('p', "pid", &target.pid, "pid",
                   "Trace on existing process id"),
        OPT_STRING(0, "tid", &target.tid, "tid",
                   "Trace on existing thread id (exclusive to --pid)"),
        OPT_CALLBACK('M', "map-nr-entries", &bpf_map_entries, "num",
                     "Max number of BPF map entries", parse_map_entry),
        OPT_CALLBACK(0, "max-stack", &max_stack_depth, "num",
                     "Set the maximum stack depth when collecting lock contention, "
                     "Default: " __stringify(CONTENTION_STACK_DEPTH), parse_max_stack),
        OPT_INTEGER(0, "stack-skip", &stack_skip,
                    "Set the number of stack depth to skip when finding a lock caller, "
                    "Default: " __stringify(CONTENTION_STACK_SKIP)),
        OPT_INTEGER('E', "entries", &print_nr_entries, "display this many functions"),
        OPT_BOOLEAN('l', "lock-addr", &show_lock_addrs, "show lock stats by address"),
        OPT_CALLBACK('Y', "type-filter", NULL, "FLAGS",
                     "Filter specific type of locks", parse_lock_type),
        OPT_CALLBACK('L', "lock-filter", NULL, "ADDRS/NAMES",
                     "Filter specific address/symbol of locks", parse_lock_addr),
        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_BOOLEAN(0, "lock-cgroup", &show_lock_cgroups, "show lock stats by cgroup"),
        OPT_CALLBACK('G', "cgroup-filter", NULL, "CGROUPS",
                     "Filter specific cgroups", parse_cgroup_filter),
        OPT_CALLBACK('J', "inject-delay", NULL, "TIME@FUNC",
                     "Inject delays to specific locks", parse_lock_delay),
        OPT_PARENT(lock_options)
        };

        const char * const info_usage[] = {
                "perf lock info [<options>]",
                NULL
        };
        const char *const lock_subcommands[] = { "record", "report", "script",
                                                 "info", "contention", NULL };
        const char *lock_usage[] = {
                NULL,
                NULL
        };
        const char * const report_usage[] = {
                "perf lock report [<options>]",
                NULL
        };
        const char * const contention_usage[] = {
                "perf lock contention [<options>]",
                NULL
        };
        unsigned int i;
        int rc = 0;

        lockhash_table = calloc(LOCKHASH_SIZE, sizeof(*lockhash_table));
        if (!lockhash_table)
                return -ENOMEM;

        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)
                usage_with_options(lock_usage, lock_options);

        if (strlen(argv[0]) > 2 && strstarts("record", argv[0])) {
                return __cmd_record(argc, argv);
        } else if (strlen(argv[0]) > 2 && strstarts("report", argv[0])) {
                trace_handler = &report_lock_ops;
                if (argc) {
                        argc = parse_options(argc, argv,
                                             report_options, report_usage, 0);
                        if (argc)
                                usage_with_options(report_usage, report_options);
                }
                rc = __cmd_report(false);
        } else if (!strcmp(argv[0], "script")) {
                /* Aliased to 'perf script' */
                rc = cmd_script(argc, argv);
        } else if (!strcmp(argv[0], "info")) {
                if (argc) {
                        argc = parse_options(argc, argv,
                                             info_options, info_usage, 0);
                        if (argc)
                                usage_with_options(info_usage, info_options);
                }

                /* If neither threads nor map requested, display both */
                if (!info_threads && !info_map) {
                        info_threads = true;
                        info_map = true;
                }

                /* recycling report_lock_ops */
                trace_handler = &report_lock_ops;
                rc = __cmd_report(true);
        } else if (strlen(argv[0]) > 2 && strstarts("contention", argv[0])) {
                trace_handler = &contention_lock_ops;
                sort_key = "wait_total";
                output_fields = "contended,wait_total,wait_max,avg_wait";

#ifndef HAVE_BPF_SKEL
                set_option_nobuild(contention_options, 'b', "use-bpf",
                                   "no BUILD_BPF_SKEL=1", false);
#endif
                if (argc) {
                        argc = parse_options(argc, argv, contention_options,
                                             contention_usage, 0);
                }

                if (check_lock_contention_options(contention_options,
                                                  contention_usage) < 0)
                        return -1;

                rc = __cmd_contention(argc, argv);
        } else {
                usage_with_options(lock_usage, lock_options);
        }

        /* free usage string allocated by parse_options_subcommand */
        free((void *)lock_usage[0]);

        zfree(&lockhash_table);
        return rc;
}