2008-12-28 20:44:51 -08:00
/*
* 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>
2009-01-10 11:34:13 -08:00
# include "trace_stat.h"
2008-12-28 20:44:51 -08:00
# include "trace.h"
/* List of stat entries from a tracer */
struct trace_stat_list {
2009-01-15 11:31:21 +01:00
struct list_head list ;
void * stat ;
2008-12-28 20:44:51 -08:00
} ;
2009-01-08 10:03:56 -08:00
/* A stat session is the stats output in one file */
struct tracer_stat_session {
2009-01-10 11:34:13 -08:00
struct list_head session_list ;
2009-01-15 11:31:21 +01:00
struct tracer_stat * ts ;
struct list_head stat_list ;
struct mutex stat_mutex ;
2009-01-10 11:34:13 -08:00
struct dentry * file ;
2009-01-08 10:03:56 -08:00
} ;
2008-12-28 20:44:51 -08:00
2009-02-17 01:10:02 -05:00
/* All of the sessions currently in use. Each stat file embed one session */
2009-01-10 11:34:13 -08:00
static LIST_HEAD ( all_stat_sessions ) ;
static DEFINE_MUTEX ( all_stat_sessions_mutex ) ;
/* The root directory for all stat files */
2009-01-15 11:31:21 +01:00
static struct dentry * stat_dir ;
2008-12-28 20:44:51 -08:00
2009-01-08 10:03:56 -08:00
static void reset_stat_session ( struct tracer_stat_session * session )
2008-12-28 20:44:51 -08:00
{
2009-01-06 21:33:30 +01:00
struct trace_stat_list * node , * next ;
2008-12-28 20:44:51 -08:00
2009-01-08 10:03:56 -08:00
list_for_each_entry_safe ( node , next , & session - > stat_list , list )
2008-12-28 20:44:51 -08:00
kfree ( node ) ;
2009-01-08 10:03:56 -08:00
INIT_LIST_HEAD ( & session - > stat_list ) ;
2008-12-28 20:44:51 -08:00
}
2009-01-10 11:34:13 -08:00
static void destroy_session ( struct tracer_stat_session * session )
2008-12-28 20:44:51 -08:00
{
2009-01-10 11:34:13 -08:00
debugfs_remove ( session - > file ) ;
reset_stat_session ( session ) ;
mutex_destroy ( & session - > stat_mutex ) ;
kfree ( session ) ;
}
2009-01-08 10:03:56 -08:00
2008-12-28 20:44:51 -08:00
/*
* 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 .
*/
2009-01-08 10:03:56 -08:00
static int stat_seq_init ( struct tracer_stat_session * session )
2008-12-28 20:44:51 -08:00
{
struct trace_stat_list * iter_entry , * new_entry ;
2009-01-08 10:03:56 -08:00
struct tracer_stat * ts = session - > ts ;
2009-03-21 02:44:50 -04:00
void * stat ;
2008-12-28 20:44:51 -08:00
int ret = 0 ;
int i ;
2009-01-08 10:03:56 -08:00
mutex_lock ( & session - > stat_mutex ) ;
reset_stat_session ( session ) ;
2008-12-28 20:44:51 -08:00
2009-01-08 10:03:56 -08:00
if ( ! ts - > stat_cmp )
ts - > stat_cmp = dummy_cmp ;
2008-12-28 20:44:51 -08:00
2009-03-21 02:44:50 -04:00
stat = ts - > stat_start ( ) ;
if ( ! stat )
goto exit ;
2008-12-28 20:44:51 -08:00
/*
* 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 ) ;
2009-01-08 10:03:56 -08:00
list_add ( & new_entry - > list , & session - > stat_list ) ;
2009-03-21 02:44:50 -04:00
new_entry - > stat = stat ;
2008-12-28 20:44:51 -08:00
/*
* Iterate over the tracer stat entries and store them in a sorted
* list .
*/
for ( i = 1 ; ; i + + ) {
2009-03-21 02:44:50 -04:00
stat = ts - > stat_next ( stat , i ) ;
/* End of insertion */
if ( ! stat )
break ;
2008-12-28 20:44:51 -08:00
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 ) ;
2009-03-21 02:44:50 -04:00
new_entry - > stat = stat ;
2008-12-28 20:44:51 -08:00
2009-03-25 16:58:39 +08:00
list_for_each_entry_reverse ( iter_entry , & session - > stat_list ,
list ) {
2009-01-08 10:03:56 -08:00
2008-12-28 20:44:51 -08:00
/* Insertion with a descendent sorting */
2009-03-25 16:58:39 +08:00
if ( ts - > stat_cmp ( iter_entry - > stat ,
new_entry - > stat ) > = 0 ) {
2008-12-28 20:44:51 -08:00
list_add ( & new_entry - > list , & iter_entry - > list ) ;
break ;
}
}
2009-03-25 16:58:39 +08:00
/* The current larger value */
if ( list_empty ( & new_entry - > list ) )
list_add ( & new_entry - > list , & session - > stat_list ) ;
2008-12-28 20:44:51 -08:00
}
exit :
2009-01-08 10:03:56 -08:00
mutex_unlock ( & session - > stat_mutex ) ;
2008-12-28 20:44:51 -08:00
return ret ;
exit_free_list :
2009-01-08 10:03:56 -08:00
reset_stat_session ( session ) ;
mutex_unlock ( & session - > stat_mutex ) ;
2008-12-28 20:44:51 -08:00
return ret ;
}
static void * stat_seq_start ( struct seq_file * s , loff_t * pos )
{
2009-01-08 10:03:56 -08:00
struct tracer_stat_session * session = s - > private ;
2008-12-28 20:44:51 -08:00
/* Prevent from tracer switch or stat_list modification */
2009-01-08 10:03:56 -08:00
mutex_lock ( & session - > stat_mutex ) ;
2008-12-28 20:44:51 -08:00
/* If we are in the beginning of the file, print the headers */
2009-01-08 10:03:56 -08:00
if ( ! * pos & & session - > ts - > stat_headers )
2009-03-25 16:27:17 +08:00
return SEQ_START_TOKEN ;
2008-12-28 20:44:51 -08:00
2009-01-08 10:03:56 -08:00
return seq_list_start ( & session - > stat_list , * pos ) ;
2008-12-28 20:44:51 -08:00
}
static void * stat_seq_next ( struct seq_file * s , void * p , loff_t * pos )
{
2009-01-08 10:03:56 -08:00
struct tracer_stat_session * session = s - > private ;
2008-12-28 20:44:51 -08:00
2009-03-25 16:27:17 +08:00
if ( p = = SEQ_START_TOKEN )
return seq_list_start ( & session - > stat_list , * pos ) ;
2009-01-08 10:03:56 -08:00
return seq_list_next ( p , & session - > stat_list , pos ) ;
2008-12-28 20:44:51 -08:00
}
2009-01-08 10:03:56 -08:00
static void stat_seq_stop ( struct seq_file * s , void * p )
2008-12-28 20:44:51 -08:00
{
2009-01-08 10:03:56 -08:00
struct tracer_stat_session * session = s - > private ;
mutex_unlock ( & session - > stat_mutex ) ;
2008-12-28 20:44:51 -08:00
}
static int stat_seq_show ( struct seq_file * s , void * v )
{
2009-01-08 10:03:56 -08:00
struct tracer_stat_session * session = s - > private ;
struct trace_stat_list * l = list_entry ( v , struct trace_stat_list , list ) ;
2009-01-06 21:33:30 +01:00
2009-03-25 16:27:17 +08:00
if ( v = = SEQ_START_TOKEN )
return session - > ts - > stat_headers ( s ) ;
2009-01-08 10:03:56 -08:00
return session - > ts - > stat_show ( s , l - > stat ) ;
2008-12-28 20:44:51 -08:00
}
static const struct seq_operations trace_stat_seq_ops = {
2009-01-15 11:31:21 +01:00
. start = stat_seq_start ,
. next = stat_seq_next ,
. stop = stat_seq_stop ,
. show = stat_seq_show
2008-12-28 20:44:51 -08:00
} ;
2009-01-08 10:03:56 -08:00
/* The session stat is refilled and resorted at each stat file opening */
2008-12-28 20:44:51 -08:00
static int tracing_stat_open ( struct inode * inode , struct file * file )
{
int ret ;
2009-01-08 10:03:56 -08:00
struct tracer_stat_session * session = inode - > i_private ;
2008-12-28 20:44:51 -08:00
ret = seq_open ( file , & trace_stat_seq_ops ) ;
if ( ! ret ) {
struct seq_file * m = file - > private_data ;
2009-01-08 10:03:56 -08:00
m - > private = session ;
ret = stat_seq_init ( session ) ;
2008-12-28 20:44:51 -08:00
}
return ret ;
}
/*
* Avoid consuming memory with our now useless list .
*/
static int tracing_stat_release ( struct inode * i , struct file * f )
{
2009-01-08 10:03:56 -08:00
struct tracer_stat_session * session = i - > i_private ;
mutex_lock ( & session - > stat_mutex ) ;
reset_stat_session ( session ) ;
mutex_unlock ( & session - > stat_mutex ) ;
2008-12-28 20:44:51 -08:00
return 0 ;
}
static const struct file_operations tracing_stat_fops = {
. open = tracing_stat_open ,
. read = seq_read ,
. llseek = seq_lseek ,
. release = tracing_stat_release
} ;
2009-01-10 11:34:13 -08:00
static int tracing_stat_init ( void )
2008-12-28 20:44:51 -08:00
{
struct dentry * d_tracing ;
d_tracing = tracing_init_dentry ( ) ;
2009-01-08 10:03:56 -08:00
stat_dir = debugfs_create_dir ( " trace_stat " , d_tracing ) ;
if ( ! stat_dir )
2008-12-28 20:44:51 -08:00
pr_warning ( " Could not create debugfs "
" 'trace_stat' entry \n " ) ;
return 0 ;
}
2009-01-10 11:34:13 -08:00
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 ;
}
2009-01-15 11:31:21 +01:00
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 ) ;
}