perf lock: Fix state machine to recognize lock sequence
[safe/jmp/linux-2.6] / tools / perf / builtin-lock.c
index 6c38e4f..716d8c5 100644 (file)
@@ -23,6 +23,8 @@
 #include <linux/list.h>
 #include <linux/hash.h>
 
+static struct perf_session *session;
+
 /* based on kernel/lockdep.c */
 #define LOCKHASH_BITS          12
 #define LOCKHASH_SIZE          (1UL << LOCKHASH_BITS)
@@ -32,9 +34,6 @@ static struct list_head lockhash_table[LOCKHASH_SIZE];
 #define __lockhashfn(key)      hash_long((unsigned long)key, LOCKHASH_BITS)
 #define lockhashentry(key)     (lockhash_table + __lockhashfn((key)))
 
-#define LOCK_STATE_UNLOCKED    0              /* initial state */
-#define LOCK_STATE_LOCKED      1
-
 struct lock_stat {
        struct list_head        hash_entry;
        struct rb_node          rb;             /* used for sorting */
@@ -47,20 +46,151 @@ struct lock_stat {
        void                    *addr;          /* address of lockdep_map, used as ID */
        char                    *name;          /* for strcpy(), we cannot use const */
 
-       int                     state;
-       u64                     prev_event_time; /* timestamp of previous event */
-
-       unsigned int            nr_acquired;
        unsigned int            nr_acquire;
+       unsigned int            nr_acquired;
        unsigned int            nr_contended;
        unsigned int            nr_release;
 
+       unsigned int            nr_readlock;
+       unsigned int            nr_trylock;
        /* these times are in nano sec. */
        u64                     wait_time_total;
        u64                     wait_time_min;
        u64                     wait_time_max;
+
+       int                     discard; /* flag of blacklist */
+};
+
+/*
+ * States of lock_seq_stat
+ *
+ * UNINITIALIZED is required for detecting first event of acquire.
+ * As the nature of lock events, there is no guarantee
+ * that the first event for the locks are acquire,
+ * it can be acquired, contended or release.
+ */
+#define SEQ_STATE_UNINITIALIZED      0        /* initial state */
+#define SEQ_STATE_RELEASED     1
+#define SEQ_STATE_ACQUIRING    2
+#define SEQ_STATE_ACQUIRED     3
+#define SEQ_STATE_READ_ACQUIRED        4
+#define SEQ_STATE_CONTENDED    5
+
+/*
+ * MAX_LOCK_DEPTH
+ * Imported from include/linux/sched.h.
+ * Should this be synchronized?
+ */
+#define MAX_LOCK_DEPTH 48
+
+/*
+ * struct lock_seq_stat:
+ * Place to put on state of one lock sequence
+ * 1) acquire -> acquired -> release
+ * 2) acquire -> contended -> acquired -> release
+ * 3) acquire (with read or try) -> release
+ * 4) Are there other patterns?
+ */
+struct lock_seq_stat {
+       struct list_head        list;
+       int                     state;
+       u64                     prev_event_time;
+       void                    *addr;
+
+       int                     read_count;
 };
 
+struct thread_stat {
+       struct rb_node          rb;
+
+       u32                     tid;
+       struct list_head        seq_list;
+};
+
+static struct rb_root          thread_stats;
+
+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)
+               die("memory allocation failed\n");
+
+       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)
+               die("memory allocation failed\n");
+       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,      \
@@ -175,8 +305,6 @@ static struct lock_stat *lock_stat_findnew(void *addr, const char *name)
                goto alloc_failed;
        strcpy(new->name, name);
 
-       /* LOCK_STATE_UNLOCKED == 0 isn't guaranteed forever */
-       new->state = LOCK_STATE_UNLOCKED;
        new->wait_time_min = ULLONG_MAX;
 
        list_add(&new->hash_entry, entry);
@@ -198,6 +326,7 @@ struct raw_event_sample {
 struct trace_acquire_event {
        void                    *addr;
        const char              *name;
+       int                     flag;
 };
 
 struct trace_acquired_event {
@@ -241,120 +370,246 @@ struct trace_lock_handler {
                              struct thread *thread);
 };
 
+static struct lock_seq_stat *get_seq(struct thread_stat *ts, void *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)
+               die("Not enough memory\n");
+       seq->state = SEQ_STATE_UNINITIALIZED;
+       seq->addr = addr;
+
+       list_add(&seq->list, &ts->seq_list);
+       return seq;
+}
+
+static int bad_hist[4];
+
 static void
 report_lock_acquire_event(struct trace_acquire_event *acquire_event,
                        struct event *__event __used,
                        int cpu __used,
-                       u64 timestamp,
+                       u64 timestamp __used,
                        struct thread *thread __used)
 {
-       struct lock_stat *st;
+       struct lock_stat *ls;
+       struct thread_stat *ts;
+       struct lock_seq_stat *seq;
+
+       ls = lock_stat_findnew(acquire_event->addr, acquire_event->name);
+       if (ls->discard)
+               return;
 
-       st = lock_stat_findnew(acquire_event->addr, acquire_event->name);
+       ts = thread_stat_findnew(thread->pid);
+       seq = get_seq(ts, acquire_event->addr);
 
-       switch (st->state) {
-       case LOCK_STATE_UNLOCKED:
+       switch (seq->state) {
+       case SEQ_STATE_UNINITIALIZED:
+       case SEQ_STATE_RELEASED:
+               if (!acquire_event->flag) {
+                       seq->state = SEQ_STATE_ACQUIRING;
+               } else {
+                       if (acquire_event->flag & 1)
+                               ls->nr_trylock++;
+                       if (acquire_event->flag & 2)
+                               ls->nr_readlock++;
+                       seq->state = SEQ_STATE_READ_ACQUIRED;
+                       seq->read_count = 1;
+                       ls->nr_acquired++;
+               }
+               break;
+       case SEQ_STATE_READ_ACQUIRED:
+               if (acquire_event->flag & 2) {
+                       seq->read_count++;
+                       ls->nr_acquired++;
+                       goto end;
+               } else {
+                       goto broken;
+               }
                break;
-       case LOCK_STATE_LOCKED:
+       case SEQ_STATE_ACQUIRED:
+       case SEQ_STATE_ACQUIRING:
+       case SEQ_STATE_CONTENDED:
+broken:
+               /* broken lock sequence, discard it */
+               ls->discard = 1;
+               bad_hist[0]++;
+               list_del(&seq->list);
+               free(seq);
+               goto end;
                break;
        default:
-               BUG_ON(1);
+               BUG_ON("Unknown state of lock sequence found!\n");
                break;
        }
 
-       st->prev_event_time = timestamp;
+       ls->nr_acquire++;
+       seq->prev_event_time = timestamp;
+end:
+       return;
 }
 
 static void
 report_lock_acquired_event(struct trace_acquired_event *acquired_event,
                         struct event *__event __used,
                         int cpu __used,
-                        u64 timestamp,
+                        u64 timestamp __used,
                         struct thread *thread __used)
 {
-       struct lock_stat *st;
+       struct lock_stat *ls;
+       struct thread_stat *ts;
+       struct lock_seq_stat *seq;
+       u64 contended_term;
 
-       st = lock_stat_findnew(acquired_event->addr, acquired_event->name);
+       ls = lock_stat_findnew(acquired_event->addr, acquired_event->name);
+       if (ls->discard)
+               return;
+
+       ts = thread_stat_findnew(thread->pid);
+       seq = get_seq(ts, acquired_event->addr);
 
-       switch (st->state) {
-       case LOCK_STATE_UNLOCKED:
-               st->state = LOCK_STATE_LOCKED;
-               st->nr_acquired++;
+       switch (seq->state) {
+       case SEQ_STATE_UNINITIALIZED:
+               /* orphan event, do nothing */
+               return;
+       case SEQ_STATE_ACQUIRING:
                break;
-       case LOCK_STATE_LOCKED:
+       case SEQ_STATE_CONTENDED:
+               contended_term = timestamp - seq->prev_event_time;
+               ls->wait_time_total += contended_term;
+
+               if (contended_term < ls->wait_time_min)
+                       ls->wait_time_min = contended_term;
+               else 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, discard it */
+               ls->discard = 1;
+               bad_hist[1]++;
+               list_del(&seq->list);
+               free(seq);
+               goto end;
+               break;
+
        default:
-               BUG_ON(1);
+               BUG_ON("Unknown state of lock sequence found!\n");
                break;
        }
 
-       st->prev_event_time = timestamp;
+       seq->state = SEQ_STATE_ACQUIRED;
+       ls->nr_acquired++;
+       seq->prev_event_time = timestamp;
+end:
+       return;
 }
 
 static void
 report_lock_contended_event(struct trace_contended_event *contended_event,
                          struct event *__event __used,
                          int cpu __used,
-                         u64 timestamp,
+                         u64 timestamp __used,
                          struct thread *thread __used)
 {
-       struct lock_stat *st;
+       struct lock_stat *ls;
+       struct thread_stat *ts;
+       struct lock_seq_stat *seq;
+
+       ls = lock_stat_findnew(contended_event->addr, contended_event->name);
+       if (ls->discard)
+               return;
 
-       st = lock_stat_findnew(contended_event->addr, contended_event->name);
+       ts = thread_stat_findnew(thread->pid);
+       seq = get_seq(ts, contended_event->addr);
 
-       switch (st->state) {
-       case LOCK_STATE_UNLOCKED:
+       switch (seq->state) {
+       case SEQ_STATE_UNINITIALIZED:
+               /* orphan event, do nothing */
+               return;
+       case SEQ_STATE_ACQUIRING:
                break;
-       case LOCK_STATE_LOCKED:
-               st->nr_contended++;
+       case SEQ_STATE_RELEASED:
+       case SEQ_STATE_ACQUIRED:
+       case SEQ_STATE_READ_ACQUIRED:
+       case SEQ_STATE_CONTENDED:
+               /* broken lock sequence, discard it */
+               ls->discard = 1;
+               bad_hist[2]++;
+               list_del(&seq->list);
+               free(seq);
+               goto end;
                break;
        default:
-               BUG_ON(1);
+               BUG_ON("Unknown state of lock sequence found!\n");
                break;
        }
 
-       st->prev_event_time = timestamp;
+       seq->state = SEQ_STATE_CONTENDED;
+       ls->nr_contended++;
+       seq->prev_event_time = timestamp;
+end:
+       return;
 }
 
 static void
 report_lock_release_event(struct trace_release_event *release_event,
                        struct event *__event __used,
                        int cpu __used,
-                       u64 timestamp,
+                       u64 timestamp __used,
                        struct thread *thread __used)
 {
-       struct lock_stat *st;
-       u64 hold_time;
+       struct lock_stat *ls;
+       struct thread_stat *ts;
+       struct lock_seq_stat *seq;
 
-       st = lock_stat_findnew(release_event->addr, release_event->name);
+       ls = lock_stat_findnew(release_event->addr, release_event->name);
+       if (ls->discard)
+               return;
 
-       switch (st->state) {
-       case LOCK_STATE_UNLOCKED:
-               break;
-       case LOCK_STATE_LOCKED:
-               st->state = LOCK_STATE_UNLOCKED;
-               hold_time = timestamp - st->prev_event_time;
+       ts = thread_stat_findnew(thread->pid);
+       seq = get_seq(ts, release_event->addr);
 
-               if (timestamp < st->prev_event_time) {
-                       /* terribly, this can happen... */
+       switch (seq->state) {
+       case SEQ_STATE_UNINITIALIZED:
+               goto end;
+               break;
+       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;
                }
-
-               if (st->wait_time_min > hold_time)
-                       st->wait_time_min = hold_time;
-               if (st->wait_time_max < hold_time)
-                       st->wait_time_max = hold_time;
-               st->wait_time_total += hold_time;
-
-               st->nr_release++;
+               break;
+       case SEQ_STATE_ACQUIRING:
+       case SEQ_STATE_CONTENDED:
+       case SEQ_STATE_RELEASED:
+               /* broken lock sequence, discard it */
+               ls->discard = 1;
+               bad_hist[3]++;
+               goto free_seq;
                break;
        default:
-               BUG_ON(1);
+               BUG_ON("Unknown state of lock sequence found!\n");
                break;
        }
 
+       ls->nr_release++;
+free_seq:
+       list_del(&seq->list);
+       free(seq);
 end:
-       st->prev_event_time = timestamp;
+       return;
 }
 
 /* lock oriented handlers */
@@ -381,6 +636,7 @@ process_lock_acquire_event(void *data,
        tmp = raw_field_value(event, "lockdep_addr", data);
        memcpy(&acquire_event.addr, &tmp, sizeof(void *));
        acquire_event.name = (char *)raw_field_ptr(event, "name", data);
+       acquire_event.flag = (int)raw_field_value(event, "flag", data);
 
        if (trace_handler->acquire_event)
                trace_handler->acquire_event(&acquire_event, event, cpu, timestamp, thread);
@@ -441,8 +697,8 @@ process_lock_release_event(void *data,
 }
 
 static void
-process_raw_event(void *data, int cpu,
-                 u64 timestamp, struct thread *thread)
+process_raw_event(void *data, int cpu __used,
+                 u64 timestamp __used, struct thread *thread __used)
 {
        struct event *event;
        int type;
@@ -604,14 +860,15 @@ static void queue_raw_event(void *data, int raw_size, int cpu,
        }
 }
 
-static int process_sample_event(event_t *event, struct perf_session *session)
+static int process_sample_event(event_t *event, struct perf_session *s)
 {
        struct thread *thread;
        struct sample_data data;
 
        bzero(&data, sizeof(struct sample_data));
-       event__parse_sample(event, session->sample_type, &data);
-       thread = perf_session__findnew(session, data.pid);
+       event__parse_sample(event, s->sample_type, &data);
+       /* CAUTION: using tid as thread.pid */
+       thread = perf_session__findnew(s, data.tid);
 
        if (thread == NULL) {
                pr_debug("problem processing %d event, skipping it.\n",
@@ -634,8 +891,8 @@ static void print_result(void)
 {
        struct lock_stat *st;
        char cut_name[20];
+       int bad, total;
 
-       printf("%18s ", "ID");
        printf("%20s ", "Name");
        printf("%10s ", "acquired");
        printf("%10s ", "contended");
@@ -646,11 +903,15 @@ static void print_result(void)
 
        printf("\n\n");
 
+       bad = total = 0;
        while ((st = pop_from_result())) {
+               total++;
+               if (st->discard) {
+                       bad++;
+                       continue;
+               }
                bzero(cut_name, 20);
 
-               printf("%p ", st->addr);
-
                if (strlen(st->name) < 16) {
                        /* output raw name */
                        printf("%20s ", st->name);
@@ -673,6 +934,21 @@ static void print_result(void)
                       0 : st->wait_time_min);
                printf("\n");
        }
+
+       {
+               /* Output for debug, this have to be removed */
+               int i;
+               const char *name[4] =
+                       { "acquire", "acquired", "contended", "release" };
+
+               printf("\n=== output for debug===\n\n");
+               printf("bad:%d, total:%d\n", bad, total);
+               printf("bad rate:%f\n", (double)(bad / total));
+
+               printf("histogram of events caused bad sequence\n");
+               for (i = 0; i < 4; i++)
+                       printf(" %10s: %d\n", name[i], bad_hist[i]);
+       }
 }
 
 static void dump_map(void)
@@ -692,8 +968,6 @@ static struct perf_event_ops eops = {
        .comm                   = event__process_comm,
 };
 
-static struct perf_session *session;
-
 static int read_events(void)
 {
        session = perf_session__new(input_name, O_RDONLY, 0);