2009-06-03 01:37:05 +04:00
/*
* perf . c
*
* Performance analysis utility .
*
* This is the main hub from which the sub - commands ( perf stat ,
* perf top , perf record , perf report , etc . ) are started .
*/
2009-04-20 17:00:56 +04:00
# include "builtin.h"
2009-06-03 01:37:05 +04:00
2009-04-27 10:02:14 +04:00
# include "util/exec_cmd.h"
# include "util/cache.h"
# include "util/quote.h"
# include "util/run-command.h"
2009-07-21 22:16:29 +04:00
# include "util/parse-events.h"
# include "util/string.h"
2009-11-08 18:03:07 +03:00
# include "util/debugfs.h"
2009-04-20 17:00:56 +04:00
const char perf_usage_string [ ] =
2009-04-20 18:01:30 +04:00
" perf [--version] [--help] COMMAND [ARGS] " ;
2009-04-20 17:00:56 +04:00
const char perf_more_info_string [ ] =
" See 'perf help COMMAND' for more information on a specific command. " ;
static int use_pager = - 1 ;
struct pager_config {
const char * cmd ;
int val ;
} ;
2009-07-21 22:16:29 +04:00
static char debugfs_mntpt [ MAXPATHLEN ] ;
2009-04-20 17:00:56 +04:00
static int pager_command_config ( const char * var , const char * value , void * data )
{
struct pager_config * c = data ;
if ( ! prefixcmp ( var , " pager. " ) & & ! strcmp ( var + 6 , c - > cmd ) )
c - > val = perf_config_bool ( var , value ) ;
return 0 ;
}
/* returns 0 for "no pager", 1 for "use pager", and -1 for "not specified" */
int check_pager_config ( const char * cmd )
{
struct pager_config c ;
c . cmd = cmd ;
c . val = - 1 ;
perf_config ( pager_command_config , & c ) ;
return c . val ;
}
static void commit_pager_choice ( void ) {
switch ( use_pager ) {
case 0 :
setenv ( " PERF_PAGER " , " cat " , 1 ) ;
break ;
case 1 :
/* setup_pager(); */
break ;
default :
break ;
}
}
2009-07-21 22:16:29 +04:00
static void set_debugfs_path ( void )
{
char * path ;
path = getenv ( PERF_DEBUGFS_ENVIRONMENT ) ;
snprintf ( debugfs_path , MAXPATHLEN , " %s/%s " , path ? : debugfs_mntpt ,
" tracing/events " ) ;
}
2009-04-20 17:00:56 +04:00
static int handle_options ( const char * * * argv , int * argc , int * envchanged )
{
int handled = 0 ;
while ( * argc > 0 ) {
const char * cmd = ( * argv ) [ 0 ] ;
if ( cmd [ 0 ] ! = ' - ' )
break ;
/*
* For legacy reasons , the " version " and " help "
* commands can be written with " -- " prepended
* to make them look like flags .
*/
if ( ! strcmp ( cmd , " --help " ) | | ! strcmp ( cmd , " --version " ) )
break ;
/*
* Check remaining flags .
*/
2009-10-13 12:18:16 +04:00
if ( ! prefixcmp ( cmd , CMD_EXEC_PATH ) ) {
cmd + = strlen ( CMD_EXEC_PATH ) ;
2009-04-20 17:00:56 +04:00
if ( * cmd = = ' = ' )
perf_set_argv_exec_path ( cmd + 1 ) ;
else {
puts ( perf_exec_path ( ) ) ;
exit ( 0 ) ;
}
} else if ( ! strcmp ( cmd , " --html-path " ) ) {
puts ( system_path ( PERF_HTML_PATH ) ) ;
exit ( 0 ) ;
} else if ( ! strcmp ( cmd , " -p " ) | | ! strcmp ( cmd , " --paginate " ) ) {
use_pager = 1 ;
} else if ( ! strcmp ( cmd , " --no-pager " ) ) {
use_pager = 0 ;
if ( envchanged )
* envchanged = 1 ;
} else if ( ! strcmp ( cmd , " --perf-dir " ) ) {
if ( * argc < 2 ) {
fprintf ( stderr , " No directory given for --perf-dir. \n " ) ;
usage ( perf_usage_string ) ;
}
setenv ( PERF_DIR_ENVIRONMENT , ( * argv ) [ 1 ] , 1 ) ;
if ( envchanged )
* envchanged = 1 ;
( * argv ) + + ;
( * argc ) - - ;
handled + + ;
2009-10-13 12:18:16 +04:00
} else if ( ! prefixcmp ( cmd , CMD_PERF_DIR ) ) {
setenv ( PERF_DIR_ENVIRONMENT , cmd + strlen ( CMD_PERF_DIR ) , 1 ) ;
2009-04-20 17:00:56 +04:00
if ( envchanged )
* envchanged = 1 ;
} else if ( ! strcmp ( cmd , " --work-tree " ) ) {
if ( * argc < 2 ) {
fprintf ( stderr , " No directory given for --work-tree. \n " ) ;
usage ( perf_usage_string ) ;
}
setenv ( PERF_WORK_TREE_ENVIRONMENT , ( * argv ) [ 1 ] , 1 ) ;
if ( envchanged )
* envchanged = 1 ;
( * argv ) + + ;
( * argc ) - - ;
2009-10-13 12:18:16 +04:00
} else if ( ! prefixcmp ( cmd , CMD_WORK_TREE ) ) {
setenv ( PERF_WORK_TREE_ENVIRONMENT , cmd + strlen ( CMD_WORK_TREE ) , 1 ) ;
2009-04-20 17:00:56 +04:00
if ( envchanged )
* envchanged = 1 ;
2009-07-21 22:16:29 +04:00
} else if ( ! strcmp ( cmd , " --debugfs-dir " ) ) {
if ( * argc < 2 ) {
fprintf ( stderr , " No directory given for --debugfs-dir. \n " ) ;
usage ( perf_usage_string ) ;
}
strncpy ( debugfs_mntpt , ( * argv ) [ 1 ] , MAXPATHLEN ) ;
debugfs_mntpt [ MAXPATHLEN - 1 ] = ' \0 ' ;
if ( envchanged )
* envchanged = 1 ;
( * argv ) + + ;
( * argc ) - - ;
2009-10-13 12:18:16 +04:00
} else if ( ! prefixcmp ( cmd , CMD_DEBUGFS_DIR ) ) {
strncpy ( debugfs_mntpt , cmd + strlen ( CMD_DEBUGFS_DIR ) , MAXPATHLEN ) ;
2009-07-21 22:16:29 +04:00
debugfs_mntpt [ MAXPATHLEN - 1 ] = ' \0 ' ;
if ( envchanged )
* envchanged = 1 ;
2009-04-20 17:00:56 +04:00
} else {
fprintf ( stderr , " Unknown option: %s \n " , cmd ) ;
usage ( perf_usage_string ) ;
}
( * argv ) + + ;
( * argc ) - - ;
handled + + ;
}
return handled ;
}
static int handle_alias ( int * argcp , const char * * * argv )
{
int envchanged = 0 , ret = 0 , saved_errno = errno ;
int count , option_count ;
const char * * new_argv ;
const char * alias_command ;
char * alias_string ;
alias_command = ( * argv ) [ 0 ] ;
alias_string = alias_lookup ( alias_command ) ;
if ( alias_string ) {
if ( alias_string [ 0 ] = = ' ! ' ) {
if ( * argcp > 1 ) {
struct strbuf buf ;
strbuf_init ( & buf , PATH_MAX ) ;
strbuf_addstr ( & buf , alias_string ) ;
sq_quote_argv ( & buf , ( * argv ) + 1 , PATH_MAX ) ;
free ( alias_string ) ;
alias_string = buf . buf ;
}
ret = system ( alias_string + 1 ) ;
if ( ret > = 0 & & WIFEXITED ( ret ) & &
WEXITSTATUS ( ret ) ! = 127 )
exit ( WEXITSTATUS ( ret ) ) ;
die ( " Failed to run '%s' when expanding alias '%s' " ,
alias_string + 1 , alias_command ) ;
}
count = split_cmdline ( alias_string , & new_argv ) ;
if ( count < 0 )
die ( " Bad alias.%s string " , alias_command ) ;
option_count = handle_options ( & new_argv , & count , & envchanged ) ;
if ( envchanged )
die ( " alias '%s' changes environment variables \n "
" You can use '!perf' in the alias to do this. " ,
alias_command ) ;
memmove ( new_argv - option_count , new_argv ,
count * sizeof ( char * ) ) ;
new_argv - = option_count ;
if ( count < 1 )
die ( " empty alias for %s " , alias_command ) ;
if ( ! strcmp ( alias_command , new_argv [ 0 ] ) )
die ( " recursive alias: %s " , alias_command ) ;
new_argv = realloc ( new_argv , sizeof ( char * ) *
( count + * argcp + 1 ) ) ;
/* insert after command name */
memcpy ( new_argv + count , * argv + 1 , sizeof ( char * ) * * argcp ) ;
new_argv [ count + * argcp ] = NULL ;
* argv = new_argv ;
* argcp + = count - 1 ;
ret = 1 ;
}
errno = saved_errno ;
return ret ;
}
const char perf_version_string [ ] = PERF_VERSION ;
# define RUN_SETUP (1<<0)
# define USE_PAGER (1<<1)
/*
* require working tree to be present - - anything uses this needs
* RUN_SETUP for reading from the configuration file .
*/
# define NEED_WORK_TREE (1<<2)
struct cmd_struct {
const char * cmd ;
int ( * fn ) ( int , const char * * , const char * ) ;
int option ;
} ;
static int run_builtin ( struct cmd_struct * p , int argc , const char * * argv )
{
int status ;
struct stat st ;
const char * prefix ;
prefix = NULL ;
if ( p - > option & RUN_SETUP )
prefix = NULL ; /* setup_perf_directory(); */
if ( use_pager = = - 1 & & p - > option & RUN_SETUP )
use_pager = check_pager_config ( p - > cmd ) ;
if ( use_pager = = - 1 & & p - > option & USE_PAGER )
use_pager = 1 ;
commit_pager_choice ( ) ;
2009-07-21 22:16:29 +04:00
set_debugfs_path ( ) ;
2009-04-20 17:00:56 +04:00
status = p - > fn ( argc , argv , prefix ) ;
if ( status )
return status & 0xff ;
/* Somebody closed stdout? */
if ( fstat ( fileno ( stdout ) , & st ) )
return 0 ;
/* Ignore write errors for pipes and sockets.. */
if ( S_ISFIFO ( st . st_mode ) | | S_ISSOCK ( st . st_mode ) )
return 0 ;
/* Check for ENOSPC and EIO errors.. */
if ( fflush ( stdout ) )
die ( " write failure on standard output: %s " , strerror ( errno ) ) ;
if ( ferror ( stdout ) )
die ( " unknown write failure on standard output " ) ;
if ( fclose ( stdout ) )
die ( " close failed on standard output: %s " , strerror ( errno ) ) ;
return 0 ;
}
static void handle_internal_command ( int argc , const char * * argv )
{
const char * cmd = argv [ 0 ] ;
static struct cmd_struct commands [ ] = {
2009-11-16 21:32:45 +03:00
{ " buildid-list " , cmd_buildid_list , 0 } ,
2009-11-20 10:53:25 +03:00
{ " help " , cmd_help , 0 } ,
{ " list " , cmd_list , 0 } ,
{ " record " , cmd_record , 0 } ,
{ " report " , cmd_report , 0 } ,
{ " bench " , cmd_bench , 0 } ,
{ " stat " , cmd_stat , 0 } ,
{ " timechart " , cmd_timechart , 0 } ,
{ " top " , cmd_top , 0 } ,
{ " annotate " , cmd_annotate , 0 } ,
{ " version " , cmd_version , 0 } ,
{ " trace " , cmd_trace , 0 } ,
{ " sched " , cmd_sched , 0 } ,
{ " probe " , cmd_probe , 0 } ,
{ " kmem " , cmd_kmem , 0 } ,
2009-04-20 17:00:56 +04:00
} ;
2009-07-01 14:37:06 +04:00
unsigned int i ;
2009-04-20 17:00:56 +04:00
static const char ext [ ] = STRIP_EXTENSION ;
if ( sizeof ( ext ) > 1 ) {
i = strlen ( argv [ 0 ] ) - strlen ( ext ) ;
if ( i > 0 & & ! strcmp ( argv [ 0 ] + i , ext ) ) {
char * argv0 = strdup ( argv [ 0 ] ) ;
argv [ 0 ] = cmd = argv0 ;
argv0 [ i ] = ' \0 ' ;
}
}
/* Turn "perf cmd --help" into "perf help cmd" */
if ( argc > 1 & & ! strcmp ( argv [ 1 ] , " --help " ) ) {
argv [ 1 ] = argv [ 0 ] ;
argv [ 0 ] = cmd = " help " ;
}
for ( i = 0 ; i < ARRAY_SIZE ( commands ) ; i + + ) {
struct cmd_struct * p = commands + i ;
if ( strcmp ( p - > cmd , cmd ) )
continue ;
exit ( run_builtin ( p , argc , argv ) ) ;
}
}
static void execv_dashed_external ( const char * * argv )
{
struct strbuf cmd = STRBUF_INIT ;
const char * tmp ;
int status ;
strbuf_addf ( & cmd , " perf-%s " , argv [ 0 ] ) ;
/*
* argv [ 0 ] must be the perf command , but the argv array
* belongs to the caller , and may be reused in
* subsequent loop iterations . Save argv [ 0 ] and
* restore it on error .
*/
tmp = argv [ 0 ] ;
argv [ 0 ] = cmd . buf ;
/*
* if we fail because the command is not found , it is
* OK to return . Otherwise , we just pass along the status code .
*/
status = run_command_v_opt ( argv , 0 ) ;
if ( status ! = - ERR_RUN_COMMAND_EXEC ) {
if ( IS_RUN_COMMAND_ERR ( status ) )
die ( " unable to run '%s' " , argv [ 0 ] ) ;
exit ( - status ) ;
}
errno = ENOENT ; /* as if we called execvp */
argv [ 0 ] = tmp ;
strbuf_release ( & cmd ) ;
}
static int run_argv ( int * argcp , const char * * * argv )
{
int done_alias = 0 ;
while ( 1 ) {
/* See if it's an internal command */
handle_internal_command ( * argcp , * argv ) ;
/* .. then try the external ones */
execv_dashed_external ( * argv ) ;
/* It could be an alias -- this works around the insanity
* of overriding " perf log " with " perf show " by having
* alias . log = show
*/
if ( done_alias | | ! handle_alias ( argcp , argv ) )
break ;
done_alias = 1 ;
}
return done_alias ;
}
2009-07-21 22:16:29 +04:00
/* mini /proc/mounts parser: searching for "^blah /mount/point debugfs" */
static void get_debugfs_mntpt ( void )
{
2009-11-08 18:03:07 +03:00
const char * path = debugfs_find_mountpoint ( ) ;
2009-07-21 22:16:29 +04:00
2009-11-08 18:03:07 +03:00
if ( path )
strncpy ( debugfs_mntpt , path , sizeof ( debugfs_mntpt ) ) ;
else
debugfs_mntpt [ 0 ] = ' \0 ' ;
2009-07-21 22:16:29 +04:00
}
2009-04-20 17:00:56 +04:00
int main ( int argc , const char * * argv )
{
const char * cmd ;
cmd = perf_extract_argv0_path ( argv [ 0 ] ) ;
if ( ! cmd )
cmd = " perf-help " ;
2009-07-21 22:16:29 +04:00
/* get debugfs mount point from /proc/mounts */
get_debugfs_mntpt ( ) ;
2009-04-20 17:00:56 +04:00
/*
* " perf-xxxx " is the same as " perf xxxx " , but we obviously :
*
* - cannot take flags in between the " perf " and the " xxxx " .
* - cannot execute it externally ( since it would just do
* the same thing over again )
*
* So we just directly call the internal command handler , and
* die if that one cannot handle it .
*/
if ( ! prefixcmp ( cmd , " perf- " ) ) {
2009-05-25 16:45:24 +04:00
cmd + = 5 ;
2009-04-20 17:00:56 +04:00
argv [ 0 ] = cmd ;
handle_internal_command ( argc , argv ) ;
die ( " cannot handle %s internally " , cmd ) ;
}
/* Look for flags.. */
argv + + ;
argc - - ;
handle_options ( & argv , & argc , NULL ) ;
commit_pager_choice ( ) ;
2009-07-21 22:16:29 +04:00
set_debugfs_path ( ) ;
2009-04-20 17:00:56 +04:00
if ( argc > 0 ) {
if ( ! prefixcmp ( argv [ 0 ] , " -- " ) )
argv [ 0 ] + = 2 ;
} else {
/* The user didn't specify a command; give them help */
2009-03-13 05:20:49 +03:00
printf ( " \n usage: %s \n \n " , perf_usage_string ) ;
2009-04-20 17:00:56 +04:00
list_common_cmds_help ( ) ;
2009-03-13 05:20:49 +03:00
printf ( " \n %s \n \n " , perf_more_info_string ) ;
2009-04-20 17:00:56 +04:00
exit ( 1 ) ;
}
cmd = argv [ 0 ] ;
/*
* We use PATH to find perf commands , but we prepend some higher
* precidence paths : the " --exec-path " option , the PERF_EXEC_PATH
* environment , and the $ ( perfexecdir ) from the Makefile at build
* time .
*/
setup_path ( ) ;
while ( 1 ) {
static int done_help = 0 ;
static int was_alias = 0 ;
2009-06-06 17:19:13 +04:00
2009-04-20 17:00:56 +04:00
was_alias = run_argv ( & argc , & argv ) ;
if ( errno ! = ENOENT )
break ;
2009-06-06 17:19:13 +04:00
2009-04-20 17:00:56 +04:00
if ( was_alias ) {
fprintf ( stderr , " Expansion of alias '%s' failed; "
" '%s' is not a perf-command \n " ,
cmd , argv [ 0 ] ) ;
exit ( 1 ) ;
}
if ( ! done_help ) {
cmd = argv [ 0 ] = help_unknown_cmd ( cmd ) ;
done_help = 1 ;
} else
break ;
}
fprintf ( stderr , " Failed to run command '%s': %s \n " ,
cmd , strerror ( errno ) ) ;
return 1 ;
}