diff options
| author | Ingo Molnar <mingo@elte.hu> | 2007-02-16 04:28:13 -0500 |
|---|---|---|
| committer | Linus Torvalds <torvalds@woody.linux-foundation.org> | 2007-02-16 11:13:59 -0500 |
| commit | 82f67cd9fca8c8762c15ba7ed0d5747588c1e221 (patch) | |
| tree | 1ff7e5cc496580b85bb42fb1d7b19dcbef7b7776 /kernel | |
| parent | 8bfd9a7a229b5f3d3eda5d7d45c2eebec5b4ba16 (diff) | |
[PATCH] Add debugging feature /proc/timer_stat
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>
Diffstat (limited to 'kernel')
| -rw-r--r-- | kernel/hrtimer.c | 26 | ||||
| -rw-r--r-- | kernel/time/Makefile | 1 | ||||
| -rw-r--r-- | kernel/time/timer_stats.c | 411 | ||||
| -rw-r--r-- | kernel/timer.c | 31 | ||||
| -rw-r--r-- | kernel/workqueue.c | 7 |
5 files changed, 472 insertions, 4 deletions
diff --git a/kernel/hrtimer.c b/kernel/hrtimer.c index 62aad8e1a383..476cb0c0b4a4 100644 --- a/kernel/hrtimer.c +++ b/kernel/hrtimer.c | |||
| @@ -585,6 +585,18 @@ static inline void hrtimer_init_timer_hres(struct hrtimer *timer) { } | |||
| 585 | 585 | ||
| 586 | #endif /* CONFIG_HIGH_RES_TIMERS */ | 586 | #endif /* CONFIG_HIGH_RES_TIMERS */ |
| 587 | 587 | ||
| 588 | #ifdef CONFIG_TIMER_STATS | ||
| 589 | void __timer_stats_hrtimer_set_start_info(struct hrtimer *timer, void *addr) | ||
| 590 | { | ||
| 591 | if (timer->start_site) | ||
| 592 | return; | ||
| 593 | |||
| 594 | timer->start_site = addr; | ||
| 595 | memcpy(timer->start_comm, current->comm, TASK_COMM_LEN); | ||
| 596 | timer->start_pid = current->pid; | ||
| 597 | } | ||
| 598 | #endif | ||
| 599 | |||
| 588 | /* | 600 | /* |
| 589 | * Counterpart to lock_timer_base above: | 601 | * Counterpart to lock_timer_base above: |
| 590 | */ | 602 | */ |
| @@ -743,6 +755,7 @@ remove_hrtimer(struct hrtimer *timer, struct hrtimer_clock_base *base) | |||
| 743 | * reprogramming happens in the interrupt handler. This is a | 755 | * reprogramming happens in the interrupt handler. This is a |
| 744 | * rare case and less expensive than a smp call. | 756 | * rare case and less expensive than a smp call. |
| 745 | */ | 757 | */ |
| 758 | timer_stats_hrtimer_clear_start_info(timer); | ||
| 746 | reprogram = base->cpu_base == &__get_cpu_var(hrtimer_bases); | 759 | reprogram = base->cpu_base == &__get_cpu_var(hrtimer_bases); |
| 747 | __remove_hrtimer(timer, base, HRTIMER_STATE_INACTIVE, | 760 | __remove_hrtimer(timer, base, HRTIMER_STATE_INACTIVE, |
| 748 | reprogram); | 761 | reprogram); |
| @@ -791,6 +804,8 @@ hrtimer_start(struct hrtimer *timer, ktime_t tim, const enum hrtimer_mode mode) | |||
| 791 | } | 804 | } |
| 792 | timer->expires = tim; | 805 | timer->expires = tim; |
| 793 | 806 | ||
| 807 | timer_stats_hrtimer_set_start_info(timer); | ||
| 808 | |||
| 794 | enqueue_hrtimer(timer, new_base, base == new_base); | 809 | enqueue_hrtimer(timer, new_base, base == new_base); |
| 795 | 810 | ||
| 796 | unlock_hrtimer_base(timer, &flags); | 811 | unlock_hrtimer_base(timer, &flags); |
| @@ -925,6 +940,12 @@ void hrtimer_init(struct hrtimer *timer, clockid_t clock_id, | |||
| 925 | 940 | ||
| 926 | timer->base = &cpu_base->clock_base[clock_id]; | 941 | timer->base = &cpu_base->clock_base[clock_id]; |
| 927 | hrtimer_init_timer_hres(timer); | 942 | hrtimer_init_timer_hres(timer); |
| 943 | |||
| 944 | #ifdef CONFIG_TIMER_STATS | ||
| 945 | timer->start_site = NULL; | ||
| 946 | timer->start_pid = -1; | ||
| 947 | memset(timer->start_comm, 0, TASK_COMM_LEN); | ||
| 948 | #endif | ||
| 928 | } | 949 | } |
| 929 | EXPORT_SYMBOL_GPL(hrtimer_init); | 950 | EXPORT_SYMBOL_GPL(hrtimer_init); |
| 930 | 951 | ||
| @@ -1006,6 +1027,7 @@ void hrtimer_interrupt(struct clock_event_device *dev) | |||
| 1006 | 1027 | ||
| 1007 | __remove_hrtimer(timer, base, | 1028 | __remove_hrtimer(timer, base, |
| 1008 | HRTIMER_STATE_CALLBACK, 0); | 1029 | HRTIMER_STATE_CALLBACK, 0); |
| 1030 | timer_stats_account_hrtimer(timer); | ||
| 1009 | 1031 | ||
| 1010 | /* | 1032 | /* |
| 1011 | * Note: We clear the CALLBACK bit after | 1033 | * Note: We clear the CALLBACK bit after |
| @@ -1050,6 +1072,8 @@ static void run_hrtimer_softirq(struct softirq_action *h) | |||
| 1050 | timer = list_entry(cpu_base->cb_pending.next, | 1072 | timer = list_entry(cpu_base->cb_pending.next, |
| 1051 | struct hrtimer, cb_entry); | 1073 | struct hrtimer, cb_entry); |
| 1052 | 1074 | ||
| 1075 | timer_stats_account_hrtimer(timer); | ||
| 1076 | |||
| 1053 | fn = timer->function; | 1077 | fn = timer->function; |
| 1054 | __remove_hrtimer(timer, timer->base, HRTIMER_STATE_CALLBACK, 0); | 1078 | __remove_hrtimer(timer, timer->base, HRTIMER_STATE_CALLBACK, 0); |
| 1055 | spin_unlock_irq(&cpu_base->lock); | 1079 | spin_unlock_irq(&cpu_base->lock); |
| @@ -1106,6 +1130,8 @@ static inline void run_hrtimer_queue(struct hrtimer_cpu_base *cpu_base, | |||
| 1106 | if (base->softirq_time.tv64 <= timer->expires.tv64) | 1130 | if (base->softirq_time.tv64 <= timer->expires.tv64) |
| 1107 | break; | 1131 | break; |
| 1108 | 1132 | ||
| 1133 | timer_stats_account_hrtimer(timer); | ||
| 1134 | |||
| 1109 | fn = timer->function; | 1135 | fn = timer->function; |
| 1110 | __remove_hrtimer(timer, base, HRTIMER_STATE_CALLBACK, 0); | 1136 | __remove_hrtimer(timer, base, HRTIMER_STATE_CALLBACK, 0); |
| 1111 | spin_unlock_irq(&cpu_base->lock); | 1137 | spin_unlock_irq(&cpu_base->lock); |
diff --git a/kernel/time/Makefile b/kernel/time/Makefile index f246bc836b9a..252b4b5d9dd0 100644 --- a/kernel/time/Makefile +++ b/kernel/time/Makefile | |||
| @@ -5,3 +5,4 @@ obj-$(CONFIG_GENERIC_CLOCKEVENTS) += tick-common.o | |||
| 5 | obj-$(CONFIG_GENERIC_CLOCKEVENTS_BROADCAST) += tick-broadcast.o | 5 | obj-$(CONFIG_GENERIC_CLOCKEVENTS_BROADCAST) += tick-broadcast.o |
| 6 | obj-$(CONFIG_TICK_ONESHOT) += tick-oneshot.o | 6 | obj-$(CONFIG_TICK_ONESHOT) += tick-oneshot.o |
| 7 | obj-$(CONFIG_TICK_ONESHOT) += tick-sched.o | 7 | obj-$(CONFIG_TICK_ONESHOT) += tick-sched.o |
| 8 | obj-$(CONFIG_TIMER_STATS) += timer_stats.o | ||
diff --git a/kernel/time/timer_stats.c b/kernel/time/timer_stats.c new file mode 100644 index 000000000000..1bc4882e28e0 --- /dev/null +++ b/kernel/time/timer_stats.c | |||
| @@ -0,0 +1,411 @@ | |||
| 1 | /* | ||
| 2 | * kernel/time/timer_stats.c | ||
| 3 | * | ||
| 4 | * Collect timer usage statistics. | ||
| 5 | * | ||
| 6 | * Copyright(C) 2006, Red Hat, Inc., Ingo Molnar | ||
| 7 | * Copyright(C) 2006 Timesys Corp., Thomas Gleixner <tglx@timesys.com> | ||
| 8 | * | ||
| 9 | * timer_stats is based on timer_top, a similar functionality which was part of | ||
| 10 | * Con Kolivas dyntick patch set. It was developed by Daniel Petrini at the | ||
| 11 | * Instituto Nokia de Tecnologia - INdT - Manaus. timer_top's design was based | ||
| 12 | * on dynamic allocation of the statistics entries and linear search based | ||
| 13 | * lookup combined with a global lock, rather than the static array, hash | ||
| 14 | * and per-CPU locking which is used by timer_stats. It was written for the | ||
| 15 | * pre hrtimer kernel code and therefore did not take hrtimers into account. | ||
| 16 | * Nevertheless it provided the base for the timer_stats implementation and | ||
| 17 | * was a helpful source of inspiration. Kudos to Daniel and the Nokia folks | ||
| 18 | * for this effort. | ||
| 19 | * | ||
| 20 | * timer_top.c is | ||
| 21 | * Copyright (C) 2005 Instituto Nokia de Tecnologia - INdT - Manaus | ||
| 22 | * Written by Daniel Petrini <d.pensator@gmail.com> | ||
| 23 | * timer_top.c was released under the GNU General Public License version 2 | ||
| 24 | * | ||
| 25 | * We export the addresses and counting of timer functions being called, | ||
| 26 | * the pid and cmdline from the owner process if applicable. | ||
| 27 | * | ||
| 28 | * Start/stop data collection: | ||
| 29 | * # echo 1[0] >/proc/timer_stats | ||
| 30 | * | ||
| 31 | * Display the information collected so far: | ||
| 32 | * # cat /proc/timer_stats | ||
| 33 | * | ||
| 34 | * This program is free software; you can redistribute it and/or modify | ||
| 35 | * it under the terms of the GNU General Public License version 2 as | ||
| 36 | * published by the Free Software Foundation. | ||
| 37 | */ | ||
| 38 | |||
| 39 | #include <linux/proc_fs.h> | ||
| 40 | #include <linux/module.h> | ||
| 41 | #include <linux/spinlock.h> | ||
| 42 | #include <linux/sched.h> | ||
| 43 | #include <linux/seq_file.h> | ||
| 44 | #include <linux/kallsyms.h> | ||
| 45 | |||
| 46 | #include <asm/uaccess.h> | ||
| 47 | |||
| 48 | /* | ||
| 49 | * This is our basic unit of interest: a timer expiry event identified | ||
| 50 | * by the timer, its start/expire functions and the PID of the task that | ||
| 51 | * started the timer. We count the number of times an event happens: | ||
| 52 | */ | ||
| 53 | struct entry { | ||
| 54 | /* | ||
| 55 | * Hash list: | ||
| 56 | */ | ||
| 57 | struct entry *next; | ||
| 58 | |||
| 59 | /* | ||
| 60 | * Hash keys: | ||
| 61 | */ | ||
| 62 | void *timer; | ||
| 63 | void *start_func; | ||
| 64 | void *expire_func; | ||
| 65 | pid_t pid; | ||
| 66 | |||
| 67 | /* | ||
| 68 | * Number of timeout events: | ||
| 69 | */ | ||
| 70 | unsigned long count; | ||
| 71 | |||
| 72 | /* | ||
| 73 | * We save the command-line string to preserve | ||
| 74 | * this information past task exit: | ||
| 75 | */ | ||
| 76 | char comm[TASK_COMM_LEN + 1]; | ||
| 77 | |||
| 78 | } ____cacheline_aligned_in_smp; | ||
| 79 | |||
| 80 | /* | ||
| 81 | * Spinlock protecting the tables - not taken during lookup: | ||
