ALSA: usb-audio: add support for Akai MPD16
[safe/jmp/linux-2.6] / kernel / trace / trace_stat.c
index 4cb4ff2..96cffb2 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * Infrastructure for statistic tracing (histogram output).
  *
- * Copyright (C) 2008 Frederic Weisbecker <fweisbec@gmail.com>
+ * Copyright (C) 2008-2009 Frederic Weisbecker <fweisbec@gmail.com>
  *
  * Based on the code from trace_branch.c which is
  * Copyright (C) 2008 Steven Rostedt <srostedt@redhat.com>
 
 
 #include <linux/list.h>
-#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/rbtree.h>
 #include <linux/debugfs.h>
+#include "trace_stat.h"
 #include "trace.h"
 
 
-/* List of stat entries from a tracer */
-struct trace_stat_list {
-       struct list_head list;
-       void *stat;
+/*
+ * List of stat red-black nodes from a tracer
+ * We use a such tree to sort quickly the stat
+ * entries from the tracer.
+ */
+struct stat_node {
+       struct rb_node          node;
+       void                    *stat;
 };
 
-static LIST_HEAD(stat_list);
+/* A stat session is the stats output in one file */
+struct stat_session {
+       struct list_head        session_list;
+       struct tracer_stat      *ts;
+       struct rb_root          stat_root;
+       struct mutex            stat_mutex;
+       struct dentry           *file;
+};
 
-/*
- * This is a copy of the current tracer to avoid racy
- * and dangerous output while the current tracer is
- * switched.
- */
-static struct tracer current_tracer;
+/* All of the sessions currently in use. Each stat file embed one session */
+static LIST_HEAD(all_stat_sessions);
+static DEFINE_MUTEX(all_stat_sessions_mutex);
+
+/* The root directory for all stat files */
+static struct dentry           *stat_dir;
 
 /*
- * Protect both the current tracer and the global
- * stat list.
+ * Iterate through the rbtree using a post order traversal path
+ * to release the next node.
+ * It won't necessary release one at each iteration
+ * but it will at least advance closer to the next one
+ * to be released.
  */
-static DEFINE_MUTEX(stat_list_mutex);
-
+static struct rb_node *release_next(struct tracer_stat *ts,
+                                   struct rb_node *node)
+{
+       struct stat_node *snode;
+       struct rb_node *parent = rb_parent(node);
+
+       if (node->rb_left)
+               return node->rb_left;
+       else if (node->rb_right)
+               return node->rb_right;
+       else {
+               if (!parent)
+                       ;
+               else if (parent->rb_left == node)
+                       parent->rb_left = NULL;
+               else
+                       parent->rb_right = NULL;
+
+               snode = container_of(node, struct stat_node, node);
+               if (ts->stat_release)
+                       ts->stat_release(snode->stat);
+               kfree(snode);
+
+               return parent;
+       }
+}
 
-static void reset_stat_list(void)
+static void __reset_stat_session(struct stat_session *session)
 {
-       struct trace_stat_list *node, *next;
+       struct rb_node *node = session->stat_root.rb_node;
 
-       list_for_each_entry_safe(node, next, &stat_list, list)
-               kfree(node);
+       while (node)
+               node = release_next(session->ts, node);
 
-       INIT_LIST_HEAD(&stat_list);
+       session->stat_root = RB_ROOT;
 }
 
-void init_tracer_stat(struct tracer *trace)
+static void reset_stat_session(struct stat_session *session)
 {
-       mutex_lock(&stat_list_mutex);
-       current_tracer = *trace;
-       mutex_unlock(&stat_list_mutex);
+       mutex_lock(&session->stat_mutex);
+       __reset_stat_session(session);
+       mutex_unlock(&session->stat_mutex);
+}
+
+static void destroy_session(struct stat_session *session)
+{
+       debugfs_remove(session->file);
+       __reset_stat_session(session);
+       mutex_destroy(&session->stat_mutex);
+       kfree(session);
+}
+
+typedef int (*cmp_stat_t)(void *, void *);
+
+static int insert_stat(struct rb_root *root, void *stat, cmp_stat_t cmp)
+{
+       struct rb_node **new = &(root->rb_node), *parent = NULL;
+       struct stat_node *data;
+
+       data = kzalloc(sizeof(*data), GFP_KERNEL);
+       if (!data)
+               return -ENOMEM;
+       data->stat = stat;
+
+       /*
+        * Figure out where to put new node
+        * This is a descendent sorting
+        */
+       while (*new) {
+               struct stat_node *this;
+               int result;
+
+               this = container_of(*new, struct stat_node, node);
+               result = cmp(data->stat, this->stat);
+
+               parent = *new;
+               if (result >= 0)
+                       new = &((*new)->rb_left);
+               else
+                       new = &((*new)->rb_right);
+       }
+
+       rb_link_node(&data->node, parent, new);
+       rb_insert_color(&data->node, root);
+       return 0;
 }
 
 /*
  * For tracers that don't provide a stat_cmp callback.
- * This one will force an immediate insertion on tail of
- * the list.
+ * This one will force an insertion as right-most node
+ * in the rbtree.
  */
 static int dummy_cmp(void *p1, void *p2)
 {
-       return 1;
+       return -1;
 }
 
 /*
- * Initialize the stat list at each trace_stat file opening.
+ * Initialize the stat rbtree at each trace_stat file opening.
  * All of these copies and sorting are required on all opening
  * since the stats could have changed between two file sessions.
  */
-static int stat_seq_init(void)
+static int stat_seq_init(struct stat_session *session)
 {
-       struct trace_stat_list *iter_entry, *new_entry;
-       void *prev_stat;
+       struct tracer_stat *ts = session->ts;
+       struct rb_root *root = &session->stat_root;
+       void *stat;
        int ret = 0;
        int i;
 
-       mutex_lock(&stat_list_mutex);
-       reset_stat_list();
+       mutex_lock(&session->stat_mutex);
+       __reset_stat_session(session);
 
-       if (!current_tracer.stat_start || !current_tracer.stat_next ||
-                                       !current_tracer.stat_show)
-               goto exit;
+       if (!ts->stat_cmp)
+               ts->stat_cmp = dummy_cmp;
 
-       if (!current_tracer.stat_cmp)
-               current_tracer.stat_cmp = dummy_cmp;
-
-       /*
-        * The first entry. Actually this is the second, but the first
-        * one (the stat_list head) is pointless.
-        */
-       new_entry = kmalloc(sizeof(struct trace_stat_list), GFP_KERNEL);
-       if (!new_entry) {
-               ret = -ENOMEM;
+       stat = ts->stat_start(ts);
+       if (!stat)
                goto exit;
-       }
-
-       INIT_LIST_HEAD(&new_entry->list);
-       list_add(&new_entry->list, &stat_list);
-       new_entry->stat = current_tracer.stat_start();
 
-       prev_stat = new_entry->stat;
+       ret = insert_stat(root, stat, ts->stat_cmp);
+       if (ret)
+               goto exit;
 
        /*
-        * Iterate over the tracer stat entries and store them in a sorted
-        * list.
+        * Iterate over the tracer stat entries and store them in an rbtree.
         */
        for (i = 1; ; i++) {
-               new_entry = kmalloc(sizeof(struct trace_stat_list), GFP_KERNEL);
-               if (!new_entry) {
-                       ret = -ENOMEM;
-                       goto exit_free_list;
-               }
-
-               INIT_LIST_HEAD(&new_entry->list);
-               new_entry->stat = current_tracer.stat_next(prev_stat, i);
+               stat = ts->stat_next(stat, i);
 
                /* End of insertion */
-               if (!new_entry->stat)
+               if (!stat)
                        break;
 
-               list_for_each_entry(iter_entry, &stat_list, list) {
-                       /* Insertion with a descendent sorting */
-                       if (current_tracer.stat_cmp(new_entry->stat,
-                                               iter_entry->stat) > 0) {
-
-                               list_add_tail(&new_entry->list,
-                                               &iter_entry->list);
-                               break;
-
-                       /* The current smaller value */
-                       } else if (list_is_last(&iter_entry->list,
-                                               &stat_list)) {
-                               list_add(&new_entry->list, &iter_entry->list);
-                               break;
-                       }
-               }
-
-               prev_stat = new_entry->stat;
+               ret = insert_stat(root, stat, ts->stat_cmp);
+               if (ret)
+                       goto exit_free_rbtree;
        }
+
 exit:
-       mutex_unlock(&stat_list_mutex);
+       mutex_unlock(&session->stat_mutex);
        return ret;
 
-exit_free_list:
-       reset_stat_list();
-       mutex_unlock(&stat_list_mutex);
+exit_free_rbtree:
+       __reset_stat_session(session);
+       mutex_unlock(&session->stat_mutex);
        return ret;
 }
 
 
 static void *stat_seq_start(struct seq_file *s, loff_t *pos)
 {
-       struct list_head *l = (struct list_head *)s->private;
+       struct stat_session *session = s->private;
+       struct rb_node *node;
+       int n = *pos;
+       int i;
 
-       /* Prevent from tracer switch or stat_list modification */
-       mutex_lock(&stat_list_mutex);
+       /* Prevent from tracer switch or rbtree modification */
+       mutex_lock(&session->stat_mutex);
 
        /* If we are in the beginning of the file, print the headers */
-       if (!*pos && current_tracer.stat_headers)
-               current_tracer.stat_headers(s);
+       if (session->ts->stat_headers) {
+               if (n == 0)
+                       return SEQ_START_TOKEN;
+               n--;
+       }
+
+       node = rb_first(&session->stat_root);
+       for (i = 0; node && i < n; i++)
+               node = rb_next(node);
 
-       return seq_list_start(l, *pos);
+       return node;
 }
 
 static void *stat_seq_next(struct seq_file *s, void *p, loff_t *pos)
 {
-       struct list_head *l = (struct list_head *)s->private;
+       struct stat_session *session = s->private;
+       struct rb_node *node = p;
+
+       (*pos)++;
+
+       if (p == SEQ_START_TOKEN)
+               return rb_first(&session->stat_root);
 
-       return seq_list_next(p, l, pos);
+       return rb_next(node);
 }
 
-static void stat_seq_stop(struct seq_file *m, void *p)
+static void stat_seq_stop(struct seq_file *s, void *p)
 {
-       mutex_unlock(&stat_list_mutex);
+       struct stat_session *session = s->private;
+       mutex_unlock(&session->stat_mutex);
 }
 
 static int stat_seq_show(struct seq_file *s, void *v)
 {
-       struct trace_stat_list *entry =
-               list_entry(v, struct trace_stat_list, list);
+       struct stat_session *session = s->private;
+       struct stat_node *l = container_of(v, struct stat_node, node);
 
-       return current_tracer.stat_show(s, entry->stat);
+       if (v == SEQ_START_TOKEN)
+               return session->ts->stat_headers(s);
+
+       return session->ts->stat_show(s, l->stat);
 }
 
 static const struct seq_operations trace_stat_seq_ops = {
-       .start = stat_seq_start,
-       .next = stat_seq_next,
-       .stop = stat_seq_stop,
-       .show = stat_seq_show
+       .start          = stat_seq_start,
+       .next           = stat_seq_next,
+       .stop           = stat_seq_stop,
+       .show           = stat_seq_show
 };
 
+/* The session stat is refilled and resorted at each stat file opening */
 static int tracing_stat_open(struct inode *inode, struct file *file)
 {
        int ret;
+       struct seq_file *m;
+       struct stat_session *session = inode->i_private;
+
+       ret = stat_seq_init(session);
+       if (ret)
+               return ret;
 
        ret = seq_open(file, &trace_stat_seq_ops);
-       if (!ret) {
-               struct seq_file *m = file->private_data;
-               m->private = &stat_list;
-               ret = stat_seq_init();
+       if (ret) {
+               reset_stat_session(session);
+               return ret;
        }
 
+       m = file->private_data;
+       m->private = session;
        return ret;
 }
 
-
 /*
- * Avoid consuming memory with our now useless list.
+ * Avoid consuming memory with our now useless rbtree.
  */
 static int tracing_stat_release(struct inode *i, struct file *f)
 {
-       mutex_lock(&stat_list_mutex);
-       reset_stat_list();
-       mutex_unlock(&stat_list_mutex);
-       return 0;
+       struct stat_session *session = i->i_private;
+
+       reset_stat_session(session);
+
+       return seq_release(i, f);
 }
 
 static const struct file_operations tracing_stat_fops = {
@@ -224,19 +302,87 @@ static const struct file_operations tracing_stat_fops = {
        .release        = tracing_stat_release
 };
 
-static int __init tracing_stat_init(void)
+static int tracing_stat_init(void)
 {
        struct dentry *d_tracing;
-       struct dentry *entry;
 
        d_tracing = tracing_init_dentry();
 
-       entry = debugfs_create_file("trace_stat", 0444, d_tracing,
-                                       NULL,
-                                   &tracing_stat_fops);
-       if (!entry)
+       stat_dir = debugfs_create_dir("trace_stat", d_tracing);
+       if (!stat_dir)
                pr_warning("Could not create debugfs "
                           "'trace_stat' entry\n");
        return 0;
 }
-fs_initcall(tracing_stat_init);
+
+static int init_stat_file(struct stat_session *session)
+{
+       if (!stat_dir && tracing_stat_init())
+               return -ENODEV;
+
+       session->file = debugfs_create_file(session->ts->name, 0644,
+                                           stat_dir,
+                                           session, &tracing_stat_fops);
+       if (!session->file)
+               return -ENOMEM;
+       return 0;
+}
+
+int register_stat_tracer(struct tracer_stat *trace)
+{
+       struct stat_session *session, *node;
+       int ret;
+
+       if (!trace)
+               return -EINVAL;
+
+       if (!trace->stat_start || !trace->stat_next || !trace->stat_show)
+               return -EINVAL;
+
+       /* Already registered? */
+       mutex_lock(&all_stat_sessions_mutex);
+       list_for_each_entry(node, &all_stat_sessions, session_list) {
+               if (node->ts == trace) {
+                       mutex_unlock(&all_stat_sessions_mutex);
+                       return -EINVAL;
+               }
+       }
+       mutex_unlock(&all_stat_sessions_mutex);
+
+       /* Init the session */
+       session = kzalloc(sizeof(*session), GFP_KERNEL);
+       if (!session)
+               return -ENOMEM;
+
+       session->ts = trace;
+       INIT_LIST_HEAD(&session->session_list);
+       mutex_init(&session->stat_mutex);
+
+       ret = init_stat_file(session);
+       if (ret) {
+               destroy_session(session);
+               return ret;
+       }
+
+       /* Register */
+       mutex_lock(&all_stat_sessions_mutex);
+       list_add_tail(&session->session_list, &all_stat_sessions);
+       mutex_unlock(&all_stat_sessions_mutex);
+
+       return 0;
+}
+
+void unregister_stat_tracer(struct tracer_stat *trace)
+{
+       struct stat_session *node, *tmp;
+
+       mutex_lock(&all_stat_sessions_mutex);
+       list_for_each_entry_safe(node, tmp, &all_stat_sessions, session_list) {
+               if (node->ts == trace) {
+                       list_del(&node->session_list);
+                       destroy_session(node);
+                       break;
+               }
+       }
+       mutex_unlock(&all_stat_sessions_mutex);
+}