Skip to content

Commit

Permalink
[PATCH] Add debugging feature /proc/timer_stat
Browse files Browse the repository at this point in the history
Add /proc/timer_stats support: debugging feature to profile timer expiration.
Both the starting site, process/PID and the expiration function is captured.
This allows the quick identification of timer event sources in a system.

Sample output:

# echo 1 > /proc/timer_stats
# cat /proc/timer_stats
Timer Stats Version: v0.1
Sample period: 4.010 s
  24,     0 swapper          hrtimer_stop_sched_tick (hrtimer_sched_tick)
  11,     0 swapper          sk_reset_timer (tcp_delack_timer)
   6,     0 swapper          hrtimer_stop_sched_tick (hrtimer_sched_tick)
   2,     1 swapper          queue_delayed_work_on (delayed_work_timer_fn)
  17,     0 swapper          hrtimer_restart_sched_tick (hrtimer_sched_tick)
   2,     1 swapper          queue_delayed_work_on (delayed_work_timer_fn)
   4,  2050 pcscd            do_nanosleep (hrtimer_wakeup)
   5,  4179 sshd             sk_reset_timer (tcp_write_timer)
   4,  2248 yum-updatesd     schedule_timeout (process_timeout)
  18,     0 swapper          hrtimer_restart_sched_tick (hrtimer_sched_tick)
   3,     0 swapper          sk_reset_timer (tcp_delack_timer)
   1,     1 swapper          neigh_table_init_no_netlink (neigh_periodic_timer)
   2,     1 swapper          e1000_up (e1000_watchdog)
   1,     1 init             schedule_timeout (process_timeout)
100 total events, 25.24 events/sec

[ cleanups and hrtimers support from Thomas Gleixner <tglx@linutronix.de> ]
[bunk@stusta.de: nr_entries can become static]
Signed-off-by: Ingo Molnar <mingo@elte.hu>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Cc: john stultz <johnstul@us.ibm.com>
Cc: Roman Zippel <zippel@linux-m68k.org>
Cc: Andi Kleen <ak@suse.de>
Signed-off-by: Adrian Bunk <bunk@stusta.de>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
  • Loading branch information
Ingo Molnar authored and Linus Torvalds committed Feb 16, 2007
1 parent 8bfd9a7 commit 82f67cd
Show file tree
Hide file tree
Showing 9 changed files with 650 additions and 4 deletions.
68 changes: 68 additions & 0 deletions Documentation/hrtimer/timer_stats.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
timer_stats - timer usage statistics
------------------------------------

timer_stats is a debugging facility to make the timer (ab)usage in a Linux
system visible to kernel and userspace developers. It is not intended for
production usage as it adds significant overhead to the (hr)timer code and the
(hr)timer data structures.

timer_stats should be used by kernel and userspace developers to verify that
their code does not make unduly use of timers. This helps to avoid unnecessary
wakeups, which should be avoided to optimize power consumption.

It can be enabled by CONFIG_TIMER_STATS in the "Kernel hacking" configuration
section.

timer_stats collects information about the timer events which are fired in a
Linux system over a sample period:

- the pid of the task(process) which initialized the timer
- the name of the process which initialized the timer
- the function where the timer was intialized
- the callback function which is associated to the timer
- the number of events (callbacks)

timer_stats adds an entry to /proc: /proc/timer_stats

This entry is used to control the statistics functionality and to read out the
sampled information.

The timer_stats functionality is inactive on bootup.

To activate a sample period issue:
# echo 1 >/proc/timer_stats

To stop a sample period issue:
# echo 0 >/proc/timer_stats

The statistics can be retrieved by:
# cat /proc/timer_stats

The readout of /proc/timer_stats automatically disables sampling. The sampled
information is kept until a new sample period is started. This allows multiple
readouts.

Sample output of /proc/timer_stats:

Timerstats sample period: 3.888770 s
12, 0 swapper hrtimer_stop_sched_tick (hrtimer_sched_tick)
15, 1 swapper hcd_submit_urb (rh_timer_func)
4, 959 kedac schedule_timeout (process_timeout)
1, 0 swapper page_writeback_init (wb_timer_fn)
28, 0 swapper hrtimer_stop_sched_tick (hrtimer_sched_tick)
22, 2948 IRQ 4 tty_flip_buffer_push (delayed_work_timer_fn)
3, 3100 bash schedule_timeout (process_timeout)
1, 1 swapper queue_delayed_work_on (delayed_work_timer_fn)
1, 1 swapper queue_delayed_work_on (delayed_work_timer_fn)
1, 1 swapper neigh_table_init_no_netlink (neigh_periodic_timer)
1, 2292 ip __netdev_watchdog_up (dev_watchdog)
1, 23 events/1 do_cache_clean (delayed_work_timer_fn)
90 total events, 30.0 events/sec

The first column is the number of events, the second column the pid, the third
column is the name of the process. The forth column shows the function which
initialized the timer and in parantheses the callback function which was
executed on expiry.

Thomas, Ingo

45 changes: 45 additions & 0 deletions include/linux/hrtimer.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,11 @@ struct hrtimer {
enum hrtimer_cb_mode cb_mode;
struct list_head cb_entry;
#endif
#ifdef CONFIG_TIMER_STATS
void *start_site;
char start_comm[16];
int start_pid;
#endif
};

/**
Expand Down Expand Up @@ -311,4 +316,44 @@ extern unsigned long ktime_divns(const ktime_t kt, s64 div);
# define ktime_divns(kt, div) (unsigned long)((kt).tv64 / (div))
#endif

/*
* Timer-statistics info:
*/
#ifdef CONFIG_TIMER_STATS

extern void timer_stats_update_stats(void *timer, pid_t pid, void *startf,
void *timerf, char * comm);

static inline void timer_stats_account_hrtimer(struct hrtimer *timer)
{
timer_stats_update_stats(timer, timer->start_pid, timer->start_site,
timer->function, timer->start_comm);
}

extern void __timer_stats_hrtimer_set_start_info(struct hrtimer *timer,
void *addr);

static inline void timer_stats_hrtimer_set_start_info(struct hrtimer *timer)
{
__timer_stats_hrtimer_set_start_info(timer, __builtin_return_address(0));
}

static inline void timer_stats_hrtimer_clear_start_info(struct hrtimer *timer)
{
timer->start_site = NULL;
}
#else
static inline void timer_stats_account_hrtimer(struct hrtimer *timer)
{
}

static inline void timer_stats_hrtimer_set_start_info(struct hrtimer *timer)
{
}

static inline void timer_stats_hrtimer_clear_start_info(struct hrtimer *timer)
{
}
#endif

#endif
54 changes: 54 additions & 0 deletions include/linux/timer.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#define _LINUX_TIMER_H

#include <linux/list.h>
#include <linux/ktime.h>
#include <linux/spinlock.h>
#include <linux/stddef.h>

Expand All @@ -15,6 +16,11 @@ struct timer_list {
unsigned long data;

struct tvec_t_base_s *base;
#ifdef CONFIG_TIMER_STATS
void *start_site;
char start_comm[16];
int start_pid;
#endif
};

extern struct tvec_t_base_s boot_tvec_bases;
Expand Down Expand Up @@ -73,6 +79,54 @@ extern unsigned long next_timer_interrupt(void);
*/
extern unsigned long get_next_timer_interrupt(unsigned long now);

/*
* Timer-statistics info:
*/
#ifdef CONFIG_TIMER_STATS

extern void init_timer_stats(void);

extern void timer_stats_update_stats(void *timer, pid_t pid, void *startf,
void *timerf, char * comm);

static inline void timer_stats_account_timer(struct timer_list *timer)
{
timer_stats_update_stats(timer, timer->start_pid, timer->start_site,
timer->function, timer->start_comm);
}

extern void __timer_stats_timer_set_start_info(struct timer_list *timer,
void *addr);

static inline void timer_stats_timer_set_start_info(struct timer_list *timer)
{
__timer_stats_timer_set_start_info(timer, __builtin_return_address(0));
}

static inline void timer_stats_timer_clear_start_info(struct timer_list *timer)
{
timer->start_site = NULL;
}
#else
static inline void init_timer_stats(void)
{
}

static inline void timer_stats_account_timer(struct timer_list *timer)
{
}

static inline void timer_stats_timer_set_start_info(struct timer_list *timer)
{
}

static inline void timer_stats_timer_clear_start_info(struct timer_list *timer)
{
}
#endif

extern void delayed_work_timer_fn(unsigned long __data);

/**
* add_timer - start a timer
* @timer: the timer to be added
Expand Down
26 changes: 26 additions & 0 deletions kernel/hrtimer.c
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,18 @@ static inline void hrtimer_init_timer_hres(struct hrtimer *timer) { }

#endif /* CONFIG_HIGH_RES_TIMERS */

#ifdef CONFIG_TIMER_STATS
void __timer_stats_hrtimer_set_start_info(struct hrtimer *timer, void *addr)
{
if (timer->start_site)
return;

timer->start_site = addr;
memcpy(timer->start_comm, current->comm, TASK_COMM_LEN);
timer->start_pid = current->pid;
}
#endif

/*
* Counterpart to lock_timer_base above:
*/
Expand Down Expand Up @@ -743,6 +755,7 @@ remove_hrtimer(struct hrtimer *timer, struct hrtimer_clock_base *base)
* reprogramming happens in the interrupt handler. This is a
* rare case and less expensive than a smp call.
*/
timer_stats_hrtimer_clear_start_info(timer);
reprogram = base->cpu_base == &__get_cpu_var(hrtimer_bases);
__remove_hrtimer(timer, base, HRTIMER_STATE_INACTIVE,
reprogram);
Expand Down Expand Up @@ -791,6 +804,8 @@ hrtimer_start(struct hrtimer *timer, ktime_t tim, const enum hrtimer_mode mode)
}
timer->expires = tim;

timer_stats_hrtimer_set_start_info(timer);

enqueue_hrtimer(timer, new_base, base == new_base);

unlock_hrtimer_base(timer, &flags);
Expand Down Expand Up @@ -925,6 +940,12 @@ void hrtimer_init(struct hrtimer *timer, clockid_t clock_id,

timer->base = &cpu_base->clock_base[clock_id];
hrtimer_init_timer_hres(timer);

#ifdef CONFIG_TIMER_STATS
timer->start_site = NULL;
timer->start_pid = -1;
memset(timer->start_comm, 0, TASK_COMM_LEN);
#endif
}
EXPORT_SYMBOL_GPL(hrtimer_init);

Expand Down Expand Up @@ -1006,6 +1027,7 @@ void hrtimer_interrupt(struct clock_event_device *dev)

__remove_hrtimer(timer, base,
HRTIMER_STATE_CALLBACK, 0);
timer_stats_account_hrtimer(timer);

/*
* Note: We clear the CALLBACK bit after
Expand Down Expand Up @@ -1050,6 +1072,8 @@ static void run_hrtimer_softirq(struct softirq_action *h)
timer = list_entry(cpu_base->cb_pending.next,
struct hrtimer, cb_entry);

timer_stats_account_hrtimer(timer);

fn = timer->function;
__remove_hrtimer(timer, timer->base, HRTIMER_STATE_CALLBACK, 0);
spin_unlock_irq(&cpu_base->lock);
Expand Down Expand Up @@ -1106,6 +1130,8 @@ static inline void run_hrtimer_queue(struct hrtimer_cpu_base *cpu_base,
if (base->softirq_time.tv64 <= timer->expires.tv64)
break;

timer_stats_account_hrtimer(timer);

fn = timer->function;
__remove_hrtimer(timer, base, HRTIMER_STATE_CALLBACK, 0);
spin_unlock_irq(&cpu_base->lock);
Expand Down
1 change: 1 addition & 0 deletions kernel/time/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ obj-$(CONFIG_GENERIC_CLOCKEVENTS) += tick-common.o
obj-$(CONFIG_GENERIC_CLOCKEVENTS_BROADCAST) += tick-broadcast.o
obj-$(CONFIG_TICK_ONESHOT) += tick-oneshot.o
obj-$(CONFIG_TICK_ONESHOT) += tick-sched.o
obj-$(CONFIG_TIMER_STATS) += timer_stats.o
Loading

0 comments on commit 82f67cd

Please sign in to comment.