2008-12-28 20:44:51 -08:00
/*
* Infrastructure for statistic tracing ( histogram output ) .
*
2009-05-16 06:24:36 +02:00
* Copyright ( C ) 2008 - 2009 Frederic Weisbecker < fweisbec @ gmail . com >
2008-12-28 20:44:51 -08:00
*
* Based on the code from trace_branch . c which is
* Copyright ( C ) 2008 Steven Rostedt < srostedt @ redhat . com >
*
*/
# include <linux/list.h>
2009-05-16 06:24:36 +02:00
# include <linux/rbtree.h>
2008-12-28 20:44:51 -08:00
# 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"
2009-05-16 06:24:36 +02:00
/*
* 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 ;
2009-01-15 11:31:21 +01:00
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 */
2009-05-16 05:58:49 +02:00
struct 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 ;
2009-05-16 06:24:36 +02:00
struct rb_root stat_root ;
2009-01-15 11:31:21 +01:00
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-05-16 06:24:36 +02:00
/*
* 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 .
*/
2009-07-06 16:10:18 +08:00
static struct rb_node * release_next ( struct tracer_stat * ts ,
struct rb_node * node )
2009-05-16 06:24:36 +02:00
{
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 )
2009-05-27 11:04:48 +08:00
;
else if ( parent - > rb_left = = node )
2009-05-16 06:24:36 +02:00
parent - > rb_left = NULL ;
else
parent - > rb_right = NULL ;
snode = container_of ( node , struct stat_node , node ) ;
2009-07-06 16:10:18 +08:00
if ( ts - > stat_release )
ts - > stat_release ( snode - > stat ) ;
2009-05-16 06:24:36 +02:00
kfree ( snode ) ;
return parent ;
}
}
2008-12-28 20:44:51 -08:00
2009-07-23 11:29:47 +08:00
static void __reset_stat_session ( struct stat_session * session )
2008-12-28 20:44:51 -08:00
{
2009-05-16 06:24:36 +02:00
struct rb_node * node = session - > stat_root . rb_node ;
2008-12-28 20:44:51 -08:00
2009-05-16 06:24:36 +02:00
while ( node )
2009-07-06 16:10:18 +08:00
node = release_next ( session - > ts , node ) ;
2008-12-28 20:44:51 -08:00
2009-05-16 06:24:36 +02:00
session - > stat_root = RB_ROOT ;
2008-12-28 20:44:51 -08:00
}
2009-07-23 11:29:47 +08:00
static void reset_stat_session ( struct stat_session * session )
{
mutex_lock ( & session - > stat_mutex ) ;
__reset_stat_session ( session ) ;
mutex_unlock ( & session - > stat_mutex ) ;
}
2009-05-16 05:58:49 +02:00
static void destroy_session ( struct stat_session * session )
2008-12-28 20:44:51 -08:00
{
2009-01-10 11:34:13 -08:00
debugfs_remove ( session - > file ) ;
2009-07-23 11:29:47 +08:00
__reset_stat_session ( session ) ;
2009-01-10 11:34:13 -08:00
mutex_destroy ( & session - > stat_mutex ) ;
kfree ( session ) ;
}
2009-01-08 10:03:56 -08:00
2009-05-16 06:24:36 +02:00
typedef int ( * cmp_stat_t ) ( void * , void * ) ;
2009-05-27 11:42:46 +08:00
static int insert_stat ( struct rb_root * root , void * stat , cmp_stat_t cmp )
2009-05-16 06:24:36 +02:00
{
struct rb_node * * new = & ( root - > rb_node ) , * parent = NULL ;
2009-05-27 11:42:46 +08:00
struct stat_node * data ;
data = kzalloc ( sizeof ( * data ) , GFP_KERNEL ) ;
if ( ! data )
return - ENOMEM ;
data - > stat = stat ;
2009-05-16 06:24:36 +02:00
/*
* 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 ) ;
2009-05-27 11:42:46 +08:00
return 0 ;
2009-05-16 06:24:36 +02:00
}
2008-12-28 20:44:51 -08:00
/*
* For tracers that don ' t provide a stat_cmp callback .
2009-05-27 11:42:46 +08:00
* This one will force an insertion as right - most node
* in the rbtree .
2008-12-28 20:44:51 -08:00
*/
static int dummy_cmp ( void * p1 , void * p2 )
{
2009-05-27 11:04:26 +08:00
return - 1 ;
2008-12-28 20:44:51 -08:00
}
/*
2009-05-27 11:42:46 +08:00
* Initialize the stat rbtree at each trace_stat file opening .
2008-12-28 20:44:51 -08:00
* All of these copies and sorting are required on all opening
* since the stats could have changed between two file sessions .
*/
2009-05-16 05:58:49 +02:00
static int stat_seq_init ( struct stat_session * session )
2008-12-28 20:44:51 -08:00
{
2009-01-08 10:03:56 -08:00
struct tracer_stat * ts = session - > ts ;
2009-05-27 11:42:46 +08:00
struct rb_root * root = & session - > stat_root ;
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 ) ;
2009-07-23 11:29:47 +08:00
__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-24 13:38:36 -04:00
stat = ts - > stat_start ( ts ) ;
2009-03-21 02:44:50 -04:00
if ( ! stat )
goto exit ;
2009-05-27 11:42:46 +08:00
ret = insert_stat ( root , stat , ts - > stat_cmp ) ;
if ( ret )
2008-12-28 20:44:51 -08:00
goto exit ;
/*
2009-05-27 11:42:46 +08:00
* Iterate over the tracer stat entries and store them in an rbtree .
2008-12-28 20:44:51 -08:00
*/
for ( i = 1 ; ; i + + ) {
2009-03-21 02:44:50 -04:00
stat = ts - > stat_next ( stat , i ) ;
/* End of insertion */
if ( ! stat )
break ;
2009-05-27 11:42:46 +08:00
ret = insert_stat ( root , stat , ts - > stat_cmp ) ;
if ( ret )
goto exit_free_rbtree ;
2008-12-28 20:44:51 -08:00
}
2009-05-16 06:24:36 +02:00
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 ;
2009-05-27 11:42:46 +08:00
exit_free_rbtree :
2009-07-23 11:29:47 +08:00
__reset_stat_session ( session ) ;
2009-01-08 10:03:56 -08:00
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-05-16 05:58:49 +02:00
struct stat_session * session = s - > private ;
2009-05-16 06:24:36 +02:00
struct rb_node * node ;
2009-08-17 16:52:53 +08:00
int n = * pos ;
2009-05-16 06:24:36 +02:00
int i ;
2008-12-28 20:44:51 -08:00
2009-05-27 11:42:46 +08:00
/* Prevent from tracer switch or rbtree 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-08-17 16:52:53 +08:00
if ( session - > ts - > stat_headers ) {
if ( n = = 0 )
return SEQ_START_TOKEN ;
n - - ;
}
2008-12-28 20:44:51 -08:00
2009-05-16 06:24:36 +02:00
node = rb_first ( & session - > stat_root ) ;
2009-08-17 16:52:53 +08:00
for ( i = 0 ; node & & i < n ; i + + )
2009-05-16 06:24:36 +02:00
node = rb_next ( node ) ;
return node ;
2008-12-28 20:44:51 -08:00
}
static void * stat_seq_next ( struct seq_file * s , void * p , loff_t * pos )
{
2009-05-16 05:58:49 +02:00
struct stat_session * session = s - > private ;
2009-05-16 06:24:36 +02:00
struct rb_node * node = p ;
( * pos ) + + ;
2008-12-28 20:44:51 -08:00
2009-03-25 16:27:17 +08:00
if ( p = = SEQ_START_TOKEN )
2009-05-16 06:24:36 +02:00
return rb_first ( & session - > stat_root ) ;
2009-03-25 16:27:17 +08:00
2009-05-16 06:24:36 +02:00
return rb_next ( node ) ;
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-05-16 05:58:49 +02:00
struct stat_session * session = s - > private ;
2009-01-08 10:03:56 -08:00
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-05-16 05:58:49 +02:00
struct stat_session * session = s - > private ;
2009-05-16 06:24:36 +02:00
struct stat_node * l = container_of ( v , struct stat_node , node ) ;
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-07-23 11:29:47 +08:00
struct seq_file * m ;
2009-05-16 05:58:49 +02:00
struct stat_session * session = inode - > i_private ;
2009-01-08 10:03:56 -08:00
2009-07-23 11:29:47 +08:00
ret = stat_seq_init ( session ) ;
if ( ret )
return ret ;
2008-12-28 20:44:51 -08:00
ret = seq_open ( file , & trace_stat_seq_ops ) ;
2009-07-23 11:29:47 +08:00
if ( ret ) {
reset_stat_session ( session ) ;
return ret ;
2008-12-28 20:44:51 -08:00
}
2009-07-23 11:29:47 +08:00
m = file - > private_data ;
m - > private = session ;
2008-12-28 20:44:51 -08:00
return ret ;
}
/*
2009-05-27 11:42:46 +08:00
* Avoid consuming memory with our now useless rbtree .
2008-12-28 20:44:51 -08:00
*/
static int tracing_stat_release ( struct inode * i , struct file * f )
{
2009-05-16 05:58:49 +02:00
struct stat_session * session = i - > i_private ;
2009-01-08 10:03:56 -08:00
reset_stat_session ( session ) ;
2009-07-23 11:29:47 +08:00
return seq_release ( i , f ) ;
2008-12-28 20:44:51 -08:00
}
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
2009-05-16 05:58:49 +02:00
static int init_stat_file ( struct stat_session * session )
2009-01-10 11:34:13 -08:00
{
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 )
{
2009-05-30 04:25:30 +02:00
struct stat_session * session , * node ;
2009-01-15 11:31:21 +01:00
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 ) ;
2009-05-30 04:25:30 +02:00
list_for_each_entry ( node , & all_stat_sessions , session_list ) {
2009-01-15 11:31:21 +01:00
if ( node - > ts = = trace ) {
mutex_unlock ( & all_stat_sessions_mutex ) ;
return - EINVAL ;
}
}
mutex_unlock ( & all_stat_sessions_mutex ) ;
/* Init the session */
2009-05-16 06:24:36 +02:00
session = kzalloc ( sizeof ( * session ) , GFP_KERNEL ) ;
2009-01-15 11:31:21 +01:00
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 )
{
2009-05-16 05:58:49 +02:00
struct stat_session * node , * tmp ;
2009-01-15 11:31:21 +01:00
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 ) ;
}