summaryrefslogblamecommitdiff
path: root/kernel/trace/trace_stat.c
blob: f110ce9ce7fbcaa05c592f60837d652ed4b13e45 (plain) (tree)






















                                                              
                            
















                                                     
                                            
 
                                                              
                            
 
                                   



















































                                                                        
                                               





















                                                                                
                                                                   









                                                                        
                                                              



















                                                                              
                                                             







                                                                       
                                       



                                                                    
                                                             
 
                                        








                                                      


                                                            

                                                        














































                                                                    










                                                                  
/*
 * 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;

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