| /* |
| * Infrastructure for statistic tracing (histogram output). |
| * |
| * Copyright (C) 2008 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/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; |
| }; |
| |
| /* A stat session is the stats output in one file */ |
| struct tracer_stat_session { |
| struct list_head session_list; |
| struct tracer_stat *ts; |
| struct list_head stat_list; |
| struct mutex stat_mutex; |
| struct dentry *file; |
| }; |
| |
| /* 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; |
| |
| |
| static void reset_stat_session(struct tracer_stat_session *session) |
| { |
| struct trace_stat_list *node, *next; |
| |
| list_for_each_entry_safe(node, next, &session->stat_list, list) |
| kfree(node); |
| |
| INIT_LIST_HEAD(&session->stat_list); |
| } |
| |
| static void destroy_session(struct tracer_stat_session *session) |
| { |
| debugfs_remove(session->file); |
| reset_stat_session(session); |
| mutex_destroy(&session->stat_mutex); |
| kfree(session); |
| } |
| |
| /* |
| * For tracers that don't provide a stat_cmp callback. |
| * This one will force an immediate insertion on tail of |
| * the list. |
| */ |
| static int dummy_cmp(void *p1, void *p2) |
| { |
| return 1; |
| } |
| |
| /* |
| * Initialize the stat list 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(struct tracer_stat_session *session) |
| { |
| struct trace_stat_list *iter_entry, *new_entry; |
| struct tracer_stat *ts = session->ts; |
| void *stat; |
| int ret = 0; |
| int i; |
| |
| mutex_lock(&session->stat_mutex); |
| reset_stat_session(session); |
| |
| if (!ts->stat_cmp) |
| ts->stat_cmp = dummy_cmp; |
| |
| stat = ts->stat_start(); |
| if (!stat) |
| goto exit; |
| |
| /* |
| * 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; |
| goto exit; |
| } |
| |
| INIT_LIST_HEAD(&new_entry->list); |
| |
| list_add(&new_entry->list, &session->stat_list); |
| |
| new_entry->stat = stat; |
| |
| /* |
| * Iterate over the tracer stat entries and store them in a sorted |
| * list. |
| */ |
| for (i = 1; ; i++) { |
| stat = ts->stat_next(stat, i); |
| |
| /* End of insertion */ |
| if (!stat) |
| break; |
| |
| 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 = stat; |
| |
| list_for_each_entry_reverse(iter_entry, &session->stat_list, |
| list) { |
| |
| /* Insertion with a descendent sorting */ |
| if (ts->stat_cmp(iter_entry->stat, |
| new_entry->stat) >= 0) { |
| |
| list_add(&new_entry->list, &iter_entry->list); |
| break; |
| } |
| } |
| |
| /* The current larger value */ |
| if (list_empty(&new_entry->list)) |
| list_add(&new_entry->list, &session->stat_list); |
| } |
| exit: |
| mutex_unlock(&session->stat_mutex); |
| return ret; |
| |
| exit_free_list: |
| reset_stat_session(session); |
| mutex_unlock(&session->stat_mutex); |
| return ret; |
| } |
| |
| |
| static void *stat_seq_start(struct seq_file *s, loff_t *pos) |
| { |
| struct tracer_stat_session *session = s->private; |
| |
| /* Prevent from tracer switch or stat_list modification */ |
| mutex_lock(&session->stat_mutex); |
| |
| /* If we are in the beginning of the file, print the headers */ |
| if (!*pos && session->ts->stat_headers) |
| return SEQ_START_TOKEN; |
| |
| return seq_list_start(&session->stat_list, *pos); |
| } |
| |
| static void *stat_seq_next(struct seq_file *s, void *p, loff_t *pos) |
| { |
| struct tracer_stat_session *session = s->private; |
| |
| if (p == SEQ_START_TOKEN) |
| return seq_list_start(&session->stat_list, *pos); |
| |
| return seq_list_next(p, &session->stat_list, pos); |
| } |
| |
| static void stat_seq_stop(struct seq_file *s, void *p) |
| { |
| struct tracer_stat_session *session = s->private; |
| mutex_unlock(&session->stat_mutex); |
| } |
| |
| static int stat_seq_show(struct seq_file *s, void *v) |
| { |
| struct tracer_stat_session *session = s->private; |
| struct trace_stat_list *l = list_entry(v, struct trace_stat_list, list); |
| |
| 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 |
| }; |
| |
| /* 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 tracer_stat_session *session = inode->i_private; |
| |
| ret = seq_open(file, &trace_stat_seq_ops); |
| if (!ret) { |
| struct seq_file *m = file->private_data; |
| m->private = session; |
| ret = stat_seq_init(session); |
| } |
| |
| return ret; |
| } |
| |
| /* |
| * Avoid consuming memory with our now useless list. |
| */ |
| static int tracing_stat_release(struct inode *i, struct file *f) |
| { |
| struct tracer_stat_session *session = i->i_private; |
| |
| mutex_lock(&session->stat_mutex); |
| reset_stat_session(session); |
| mutex_unlock(&session->stat_mutex); |
| |
| return 0; |
| } |
| |
| static const struct file_operations tracing_stat_fops = { |
| .open = tracing_stat_open, |
| .read = seq_read, |
| .llseek = seq_lseek, |
| .release = tracing_stat_release |
| }; |
| |
| static int tracing_stat_init(void) |
| { |
| struct dentry *d_tracing; |
| |
| d_tracing = tracing_init_dentry(); |
| |
| stat_dir = debugfs_create_dir("trace_stat", d_tracing); |
| if (!stat_dir) |
| pr_warning("Could not create debugfs " |
| "'trace_stat' entry\n"); |
| return 0; |
| } |
| |
| static int init_stat_file(struct tracer_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 tracer_stat_session *session, *node, *tmp; |
| 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_safe(node, tmp, &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 = kmalloc(sizeof(struct tracer_stat_session), GFP_KERNEL); |
| if (!session) |
| return -ENOMEM; |
| |
| session->ts = trace; |
| INIT_LIST_HEAD(&session->session_list); |
| INIT_LIST_HEAD(&session->stat_list); |
| mutex_init(&session->stat_mutex); |
| session->file = NULL; |
| |
| 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 tracer_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); |
| } |