| /* |
| * 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/seq_file.h> |
| #include <linux/debugfs.h> |
| #include "trace.h" |
| |
| |
| /* List of stat entries from a tracer */ |
| struct trace_stat_list { |
| struct list_head list; |
| void *stat; |
| }; |
| |
| static LIST_HEAD(stat_list); |
| |
| /* |
| * 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; |
| |
| /* |
| * Protect both the current tracer and the global |
| * stat list. |
| */ |
| static DEFINE_MUTEX(stat_list_mutex); |
| |
| |
| static void reset_stat_list(void) |
| { |
| struct trace_stat_list *node, *next; |
| |
| list_for_each_entry_safe(node, next, &stat_list, list) |
| kfree(node); |
| |
| INIT_LIST_HEAD(&stat_list); |
| } |
| |
| void init_tracer_stat(struct tracer *trace) |
| { |
| mutex_lock(&stat_list_mutex); |
| current_tracer = *trace; |
| mutex_unlock(&stat_list_mutex); |
| } |
| |
| /* |
| * 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(void) |
| { |
| struct trace_stat_list *iter_entry, *new_entry; |
| void *prev_stat; |
| int ret = 0; |
| int i; |
| |
| mutex_lock(&stat_list_mutex); |
| reset_stat_list(); |
| |
| if (!current_tracer.stat_start || !current_tracer.stat_next || |
| !current_tracer.stat_show) |
| goto exit; |
| |
| 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; |
| 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; |
| |
| /* |
| * Iterate over the tracer stat entries and store them in a sorted |
| * list. |
| */ |
| 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); |
| |
| /* End of insertion */ |
| if (!new_entry->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; |
| } |
| exit: |
| mutex_unlock(&stat_list_mutex); |
| return ret; |
| |
| exit_free_list: |
| reset_stat_list(); |
| mutex_unlock(&stat_list_mutex); |
| return ret; |
| } |
| |
| |
| static void *stat_seq_start(struct seq_file *s, loff_t *pos) |
| { |
| struct list_head *l = (struct list_head *)s->private; |
| |
| /* Prevent from tracer switch or stat_list modification */ |
| mutex_lock(&stat_list_mutex); |
| |
| /* If we are in the beginning of the file, print the headers */ |
| if (!*pos && current_tracer.stat_headers) |
| current_tracer.stat_headers(s); |
| |
| return seq_list_start(l, *pos); |
| } |
| |
| static void *stat_seq_next(struct seq_file *s, void *p, loff_t *pos) |
| { |
| struct list_head *l = (struct list_head *)s->private; |
| |
| return seq_list_next(p, l, pos); |
| } |
| |
| static void stat_seq_stop(struct seq_file *m, void *p) |
| { |
| mutex_unlock(&stat_list_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); |
| |
| return current_tracer.stat_show(s, entry->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 |
| }; |
| |
| static int tracing_stat_open(struct inode *inode, struct file *file) |
| { |
| int 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(); |
| } |
| |
| return ret; |
| } |
| |
| |
| /* |
| * Avoid consuming memory with our now useless list. |
| */ |
| 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; |
| } |
| |
| static const struct file_operations tracing_stat_fops = { |
| .open = tracing_stat_open, |
| .read = seq_read, |
| .llseek = seq_lseek, |
| .release = tracing_stat_release |
| }; |
| |
| static int __init 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) |
| pr_warning("Could not create debugfs " |
| "'trace_stat' entry\n"); |
| return 0; |
| } |
| fs_initcall(tracing_stat_init); |