2008-05-12 23:20:42 +04:00
/*
* trace task wakeup timings
*
* Copyright ( C ) 2007 - 2008 Steven Rostedt < srostedt @ redhat . com >
* Copyright ( C ) 2008 Ingo Molnar < mingo @ redhat . com >
*
* Based on code from the latency_tracer , that is :
*
* Copyright ( C ) 2004 - 2006 Ingo Molnar
* Copyright ( C ) 2004 William Lee Irwin III
*/
# include <linux/module.h>
# include <linux/fs.h>
# include <linux/debugfs.h>
# include <linux/kallsyms.h>
# include <linux/uaccess.h>
# include <linux/ftrace.h>
2008-07-18 20:16:17 +04:00
# include <trace/sched.h>
2008-05-12 23:20:42 +04:00
# include "trace.h"
static struct trace_array * wakeup_trace ;
static int __read_mostly tracer_enabled ;
static struct task_struct * wakeup_task ;
static int wakeup_cpu ;
static unsigned wakeup_prio = - 1 ;
2008-07-16 08:13:45 +04:00
static raw_spinlock_t wakeup_lock =
( raw_spinlock_t ) __RAW_SPIN_LOCK_UNLOCKED ;
2008-05-12 23:20:42 +04:00
2008-05-12 23:20:51 +04:00
static void __wakeup_reset ( struct trace_array * tr ) ;
2008-05-12 23:20:42 +04:00
2008-10-07 03:06:12 +04:00
# ifdef CONFIG_FUNCTION_TRACER
2008-05-22 08:22:19 +04:00
/*
* irqsoff uses its own tracer function to keep the overhead down :
*/
static void
wakeup_tracer_call ( unsigned long ip , unsigned long parent_ip )
{
struct trace_array * tr = wakeup_trace ;
struct trace_array_cpu * data ;
unsigned long flags ;
long disabled ;
int resched ;
int cpu ;
2008-10-01 21:14:09 +04:00
int pc ;
2008-05-22 08:22:19 +04:00
if ( likely ( ! wakeup_task ) )
return ;
2008-10-01 21:14:09 +04:00
pc = preempt_count ( ) ;
2008-11-04 07:15:56 +03:00
resched = ftrace_preempt_disable ( ) ;
2008-05-22 08:22:19 +04:00
cpu = raw_smp_processor_id ( ) ;
data = tr - > data [ cpu ] ;
disabled = atomic_inc_return ( & data - > disabled ) ;
if ( unlikely ( disabled ! = 1 ) )
goto out ;
2008-07-16 08:13:45 +04:00
local_irq_save ( flags ) ;
__raw_spin_lock ( & wakeup_lock ) ;
2008-05-22 08:22:19 +04:00
if ( unlikely ( ! wakeup_task ) )
goto unlock ;
/*
* The task can ' t disappear because it needs to
* wake up first , and we have the wakeup_lock .
*/
if ( task_cpu ( wakeup_task ) ! = cpu )
goto unlock ;
2008-10-01 21:14:09 +04:00
trace_function ( tr , data , ip , parent_ip , flags , pc ) ;
2008-05-22 08:22:19 +04:00
unlock :
2008-07-16 08:13:45 +04:00
__raw_spin_unlock ( & wakeup_lock ) ;
local_irq_restore ( flags ) ;
2008-05-22 08:22:19 +04:00
out :
atomic_dec ( & data - > disabled ) ;
2008-11-04 07:15:56 +03:00
ftrace_preempt_enable ( resched ) ;
2008-05-22 08:22:19 +04:00
}
static struct ftrace_ops trace_ops __read_mostly =
{
. func = wakeup_tracer_call ,
} ;
2008-10-07 03:06:12 +04:00
# endif /* CONFIG_FUNCTION_TRACER */
2008-05-22 08:22:19 +04:00
2008-05-12 23:20:42 +04:00
/*
* Should this new latency be reported / recorded ?
*/
2008-05-12 23:20:51 +04:00
static int report_latency ( cycle_t delta )
2008-05-12 23:20:42 +04:00
{
if ( tracing_thresh ) {
if ( delta < tracing_thresh )
return 0 ;
} else {
if ( delta < = tracing_max_latency )
return 0 ;
}
return 1 ;
}
2008-05-12 23:21:10 +04:00
static void notrace
2008-07-18 20:16:17 +04:00
probe_wakeup_sched_switch ( struct rq * rq , struct task_struct * prev ,
2008-05-12 23:21:10 +04:00
struct task_struct * next )
2008-05-12 23:20:42 +04:00
{
unsigned long latency = 0 , t0 = 0 , t1 = 0 ;
struct trace_array_cpu * data ;
cycle_t T0 , T1 , delta ;
unsigned long flags ;
long disabled ;
int cpu ;
2008-10-01 21:14:09 +04:00
int pc ;
2008-05-12 23:20:42 +04:00
2008-07-18 20:16:17 +04:00
tracing_record_cmdline ( prev ) ;
2008-05-12 23:20:42 +04:00
if ( unlikely ( ! tracer_enabled ) )
return ;
/*
* When we start a new trace , we set wakeup_task to NULL
* and then set tracer_enabled = 1. We want to make sure
* that another CPU does not see the tracer_enabled = 1
* and the wakeup_task with an older task , that might
* actually be the same as next .
*/
smp_rmb ( ) ;
if ( next ! = wakeup_task )
return ;
2008-10-01 21:14:09 +04:00
pc = preempt_count ( ) ;
2008-05-22 08:22:19 +04:00
/* The task we are waiting for is waking up */
2008-07-18 20:16:17 +04:00
data = wakeup_trace - > data [ wakeup_cpu ] ;
2008-05-12 23:20:42 +04:00
/* disable local data, not wakeup_cpu data */
cpu = raw_smp_processor_id ( ) ;
2008-07-18 20:16:17 +04:00
disabled = atomic_inc_return ( & wakeup_trace - > data [ cpu ] - > disabled ) ;
2008-05-12 23:20:42 +04:00
if ( likely ( disabled ! = 1 ) )
goto out ;
2008-07-16 08:13:45 +04:00
local_irq_save ( flags ) ;
__raw_spin_lock ( & wakeup_lock ) ;
2008-05-12 23:20:42 +04:00
/* We could race with grabbing wakeup_lock */
if ( unlikely ( ! tracer_enabled | | next ! = wakeup_task ) )
goto out_unlock ;
2008-10-01 21:14:09 +04:00
trace_function ( wakeup_trace , data , CALLER_ADDR1 , CALLER_ADDR2 , flags , pc ) ;
2008-05-12 23:20:42 +04:00
/*
* usecs conversion is slow so we try to delay the conversion
* as long as possible :
*/
T0 = data - > preempt_timestamp ;
2008-05-12 23:20:46 +04:00
T1 = ftrace_now ( cpu ) ;
2008-05-12 23:20:42 +04:00
delta = T1 - T0 ;
if ( ! report_latency ( delta ) )
goto out_unlock ;
latency = nsecs_to_usecs ( delta ) ;
tracing_max_latency = delta ;
t0 = nsecs_to_usecs ( T0 ) ;
t1 = nsecs_to_usecs ( T1 ) ;
2008-07-18 20:16:17 +04:00
update_max_tr ( wakeup_trace , wakeup_task , wakeup_cpu ) ;
2008-05-12 23:20:42 +04:00
out_unlock :
2008-07-18 20:16:17 +04:00
__wakeup_reset ( wakeup_trace ) ;
2008-07-16 08:13:45 +04:00
__raw_spin_unlock ( & wakeup_lock ) ;
local_irq_restore ( flags ) ;
2008-05-12 23:20:42 +04:00
out :
2008-07-18 20:16:17 +04:00
atomic_dec ( & wakeup_trace - > data [ cpu ] - > disabled ) ;
2008-05-12 23:21:10 +04:00
}
2008-05-12 23:20:51 +04:00
static void __wakeup_reset ( struct trace_array * tr )
2008-05-12 23:20:42 +04:00
{
struct trace_array_cpu * data ;
int cpu ;
for_each_possible_cpu ( cpu ) {
data = tr - > data [ cpu ] ;
2008-09-30 07:02:41 +04:00
tracing_reset ( tr , cpu ) ;
2008-05-12 23:20:42 +04:00
}
wakeup_cpu = - 1 ;
wakeup_prio = - 1 ;
if ( wakeup_task )
put_task_struct ( wakeup_task ) ;
wakeup_task = NULL ;
}
2008-05-12 23:20:51 +04:00
static void wakeup_reset ( struct trace_array * tr )
2008-05-12 23:20:42 +04:00
{
unsigned long flags ;
2008-07-16 08:13:45 +04:00
local_irq_save ( flags ) ;
__raw_spin_lock ( & wakeup_lock ) ;
2008-05-12 23:20:42 +04:00
__wakeup_reset ( tr ) ;
2008-07-16 08:13:45 +04:00
__raw_spin_unlock ( & wakeup_lock ) ;
local_irq_restore ( flags ) ;
2008-05-12 23:20:42 +04:00
}
2008-05-12 23:20:51 +04:00
static void
2008-07-18 20:16:17 +04:00
probe_wakeup ( struct rq * rq , struct task_struct * p )
2008-05-12 23:20:42 +04:00
{
int cpu = smp_processor_id ( ) ;
unsigned long flags ;
long disabled ;
2008-10-01 21:14:09 +04:00
int pc ;
2008-05-12 23:20:42 +04:00
2008-07-18 20:16:17 +04:00
if ( likely ( ! tracer_enabled ) )
return ;
tracing_record_cmdline ( p ) ;
tracing_record_cmdline ( current ) ;
2008-05-12 23:20:42 +04:00
if ( likely ( ! rt_task ( p ) ) | |
p - > prio > = wakeup_prio | |
2008-07-18 20:16:17 +04:00
p - > prio > = current - > prio )
2008-05-12 23:20:42 +04:00
return ;
2008-10-01 21:14:09 +04:00
pc = preempt_count ( ) ;
2008-07-18 20:16:17 +04:00
disabled = atomic_inc_return ( & wakeup_trace - > data [ cpu ] - > disabled ) ;
2008-05-12 23:20:42 +04:00
if ( unlikely ( disabled ! = 1 ) )
goto out ;
/* interrupts should be off from try_to_wake_up */
2008-07-16 08:13:45 +04:00
__raw_spin_lock ( & wakeup_lock ) ;
2008-05-12 23:20:42 +04:00
/* check for races. */
if ( ! tracer_enabled | | p - > prio > = wakeup_prio )
goto out_locked ;
/* reset the trace */
2008-07-18 20:16:17 +04:00
__wakeup_reset ( wakeup_trace ) ;
2008-05-12 23:20:42 +04:00
wakeup_cpu = task_cpu ( p ) ;
wakeup_prio = p - > prio ;
wakeup_task = p ;
get_task_struct ( wakeup_task ) ;
local_save_flags ( flags ) ;
2008-07-18 20:16:17 +04:00
wakeup_trace - > data [ wakeup_cpu ] - > preempt_timestamp = ftrace_now ( cpu ) ;
trace_function ( wakeup_trace , wakeup_trace - > data [ wakeup_cpu ] ,
2008-10-01 21:14:09 +04:00
CALLER_ADDR1 , CALLER_ADDR2 , flags , pc ) ;
2008-05-12 23:20:42 +04:00
out_locked :
2008-07-16 08:13:45 +04:00
__raw_spin_unlock ( & wakeup_lock ) ;
2008-05-12 23:20:42 +04:00
out :
2008-07-18 20:16:17 +04:00
atomic_dec ( & wakeup_trace - > data [ cpu ] - > disabled ) ;
2008-05-12 23:20:42 +04:00
}
2008-05-12 23:20:51 +04:00
static void start_wakeup_tracer ( struct trace_array * tr )
2008-05-12 23:20:42 +04:00
{
2008-05-12 23:21:10 +04:00
int ret ;
2008-07-18 20:16:17 +04:00
ret = register_trace_sched_wakeup ( probe_wakeup ) ;
2008-05-12 23:21:10 +04:00
if ( ret ) {
2008-07-18 20:16:17 +04:00
pr_info ( " wakeup trace: Couldn't activate tracepoint "
2008-05-12 23:21:10 +04:00
" probe to kernel_sched_wakeup \n " ) ;
return ;
}
2008-07-18 20:16:17 +04:00
ret = register_trace_sched_wakeup_new ( probe_wakeup ) ;
2008-05-12 23:21:10 +04:00
if ( ret ) {
2008-07-18 20:16:17 +04:00
pr_info ( " wakeup trace: Couldn't activate tracepoint "
2008-05-12 23:21:10 +04:00
" probe to kernel_sched_wakeup_new \n " ) ;
goto fail_deprobe ;
}
2008-07-18 20:16:17 +04:00
ret = register_trace_sched_switch ( probe_wakeup_sched_switch ) ;
2008-05-12 23:21:10 +04:00
if ( ret ) {
2008-07-18 20:16:17 +04:00
pr_info ( " sched trace: Couldn't activate tracepoint "
2008-05-12 23:21:10 +04:00
" probe to kernel_sched_schedule \n " ) ;
goto fail_deprobe_wake_new ;
}
2008-05-12 23:20:42 +04:00
wakeup_reset ( tr ) ;
/*
* Don ' t let the tracer_enabled = 1 show up before
* the wakeup_task is reset . This may be overkill since
* wakeup_reset does a spin_unlock after setting the
* wakeup_task to NULL , but I want to be safe .
* This is a slow path anyway .
*/
smp_wmb ( ) ;
2008-05-22 08:22:19 +04:00
register_ftrace_function ( & trace_ops ) ;
2008-05-12 23:20:42 +04:00
2008-07-11 04:58:13 +04:00
tracer_enabled = 1 ;
2008-05-12 23:20:42 +04:00
return ;
2008-05-12 23:21:10 +04:00
fail_deprobe_wake_new :
2008-07-18 20:16:17 +04:00
unregister_trace_sched_wakeup_new ( probe_wakeup ) ;
2008-05-12 23:21:10 +04:00
fail_deprobe :
2008-07-18 20:16:17 +04:00
unregister_trace_sched_wakeup ( probe_wakeup ) ;
2008-05-12 23:20:42 +04:00
}
2008-05-12 23:20:51 +04:00
static void stop_wakeup_tracer ( struct trace_array * tr )
2008-05-12 23:20:42 +04:00
{
tracer_enabled = 0 ;
2008-05-22 08:22:19 +04:00
unregister_ftrace_function ( & trace_ops ) ;
2008-07-18 20:16:17 +04:00
unregister_trace_sched_switch ( probe_wakeup_sched_switch ) ;
unregister_trace_sched_wakeup_new ( probe_wakeup ) ;
unregister_trace_sched_wakeup ( probe_wakeup ) ;
2008-05-12 23:20:42 +04:00
}
2008-05-12 23:20:51 +04:00
static void wakeup_tracer_init ( struct trace_array * tr )
2008-05-12 23:20:42 +04:00
{
wakeup_trace = tr ;
if ( tr - > ctrl )
start_wakeup_tracer ( tr ) ;
}
2008-05-12 23:20:51 +04:00
static void wakeup_tracer_reset ( struct trace_array * tr )
2008-05-12 23:20:42 +04:00
{
if ( tr - > ctrl ) {
stop_wakeup_tracer ( tr ) ;
/* make sure we put back any tasks we are tracing */
wakeup_reset ( tr ) ;
}
}
static void wakeup_tracer_ctrl_update ( struct trace_array * tr )
{
if ( tr - > ctrl )
start_wakeup_tracer ( tr ) ;
else
stop_wakeup_tracer ( tr ) ;
}
2008-05-12 23:20:51 +04:00
static void wakeup_tracer_open ( struct trace_iterator * iter )
2008-05-12 23:20:42 +04:00
{
/* stop the trace while dumping */
if ( iter - > tr - > ctrl )
stop_wakeup_tracer ( iter - > tr ) ;
}
2008-05-12 23:20:51 +04:00
static void wakeup_tracer_close ( struct trace_iterator * iter )
2008-05-12 23:20:42 +04:00
{
/* forget about any processes we were recording */
if ( iter - > tr - > ctrl )
start_wakeup_tracer ( iter - > tr ) ;
}
static struct tracer wakeup_tracer __read_mostly =
{
. name = " wakeup " ,
. init = wakeup_tracer_init ,
. reset = wakeup_tracer_reset ,
. open = wakeup_tracer_open ,
. close = wakeup_tracer_close ,
. ctrl_update = wakeup_tracer_ctrl_update ,
. print_max = 1 ,
2008-05-12 23:20:44 +04:00
# ifdef CONFIG_FTRACE_SELFTEST
. selftest = trace_selftest_startup_wakeup ,
# endif
2008-05-12 23:20:42 +04:00
} ;
__init static int init_wakeup_tracer ( void )
{
int ret ;
ret = register_tracer ( & wakeup_tracer ) ;
if ( ret )
return ret ;
return 0 ;
}
device_initcall ( init_wakeup_tracer ) ;