diff options
| author | Bjoern Brandenburg <bbb@mpi-sws.org> | 2015-08-09 07:18:46 -0400 |
|---|---|---|
| committer | Bjoern Brandenburg <bbb@mpi-sws.org> | 2015-08-09 06:21:17 -0400 |
| commit | fc6ac04ddc314b9cff5bdb92c8330569658076a3 (patch) | |
| tree | eda30cc3610074a32d24679c2cf426d31fcd3663 | |
| parent | 13a30b8fd8fd4a5f41d0bb82718024e5a3823c2e (diff) | |
Add TRACE() debug tracing support
This patch adds the infrastructure for the TRACE() debug macro.
Conflicts:
kernel/printk.c
| -rw-r--r-- | include/litmus/debug_trace.h | 40 | ||||
| -rw-r--r-- | kernel/printk/printk.c | 13 | ||||
| -rw-r--r-- | litmus/Kconfig | 53 | ||||
| -rw-r--r-- | litmus/Makefile | 1 | ||||
| -rw-r--r-- | litmus/sched_trace.c | 251 |
5 files changed, 357 insertions, 1 deletions
diff --git a/include/litmus/debug_trace.h b/include/litmus/debug_trace.h new file mode 100644 index 000000000000..1266ac6a760c --- /dev/null +++ b/include/litmus/debug_trace.h | |||
| @@ -0,0 +1,40 @@ | |||
| 1 | #ifndef LITMUS_DEBUG_TRACE_H | ||
| 2 | #define LITMUS_DEBUG_TRACE_H | ||
| 3 | |||
| 4 | #ifdef CONFIG_SCHED_DEBUG_TRACE | ||
| 5 | void sched_trace_log_message(const char* fmt, ...); | ||
| 6 | void dump_trace_buffer(int max); | ||
| 7 | #else | ||
| 8 | |||
| 9 | #define sched_trace_log_message(fmt, ...) | ||
| 10 | |||
| 11 | #endif | ||
| 12 | |||
| 13 | extern atomic_t __log_seq_no; | ||
| 14 | |||
| 15 | #ifdef CONFIG_SCHED_DEBUG_TRACE_CALLER | ||
| 16 | #define TRACE_PREFIX "%d P%d [%s@%s:%d]: " | ||
| 17 | #define TRACE_ARGS atomic_add_return(1, &__log_seq_no), \ | ||
| 18 | raw_smp_processor_id(), \ | ||
| 19 | __FUNCTION__, __FILE__, __LINE__ | ||
| 20 | #else | ||
| 21 | #define TRACE_PREFIX "%d P%d: " | ||
| 22 | #define TRACE_ARGS atomic_add_return(1, &__log_seq_no), \ | ||
| 23 | raw_smp_processor_id() | ||
| 24 | #endif | ||
| 25 | |||
| 26 | #define TRACE(fmt, args...) \ | ||
| 27 | sched_trace_log_message(TRACE_PREFIX fmt, \ | ||
| 28 | TRACE_ARGS, ## args) | ||
| 29 | |||
| 30 | #define TRACE_TASK(t, fmt, args...) \ | ||
| 31 | TRACE("(%s/%d:%d) " fmt, \ | ||
| 32 | t ? (t)->comm : "null", \ | ||
| 33 | t ? (t)->pid : 0, \ | ||
| 34 | t ? (t)->rt_param.job_params.job_no : 0, \ | ||
| 35 | ##args) | ||
| 36 | |||
| 37 | #define TRACE_CUR(fmt, args...) \ | ||
| 38 | TRACE_TASK(current, fmt, ## args) | ||
| 39 | |||
| 40 | #endif | ||
diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c index c099b082cd02..1c7c3dcdc919 100644 --- a/kernel/printk/printk.c +++ b/kernel/printk/printk.c | |||
| @@ -63,6 +63,13 @@ int console_printk[4] = { | |||
| 63 | }; | 63 | }; |
| 64 | 64 | ||
| 65 | /* | 65 | /* |
| 66 | * divert printk() messages when there is a LITMUS^RT debug listener | ||
| 67 | */ | ||
| 68 | #include <litmus/debug_trace.h> | ||
| 69 | int trace_override = 0; | ||
| 70 | int trace_recurse = 0; | ||
| 71 | |||
| 72 | /* | ||
| 66 | * Low level drivers may need that to know if they can schedule in | 73 | * Low level drivers may need that to know if they can schedule in |
| 67 | * their unblank() callback or not. So let's export it. | 74 | * their unblank() callback or not. So let's export it. |
| 68 | */ | 75 | */ |
| @@ -1678,6 +1685,10 @@ asmlinkage int vprintk_emit(int facility, int level, | |||
| 1678 | */ | 1685 | */ |
| 1679 | text_len = vscnprintf(text, sizeof(textbuf), fmt, args); | 1686 | text_len = vscnprintf(text, sizeof(textbuf), fmt, args); |
| 1680 | 1687 | ||
| 1688 | /* if LITMUS^RT tracer is active divert printk() msgs */ | ||
| 1689 | if (trace_override && !trace_recurse) | ||
| 1690 | TRACE("%s", text); | ||
| 1691 | |||
| 1681 | /* mark and strip a trailing newline */ | 1692 | /* mark and strip a trailing newline */ |
| 1682 | if (text_len && text[text_len-1] == '\n') { | 1693 | if (text_len && text[text_len-1] == '\n') { |
| 1683 | text_len--; | 1694 | text_len--; |
| @@ -2628,7 +2639,7 @@ static DEFINE_PER_CPU(struct irq_work, wake_up_klogd_work) = { | |||
| 2628 | void wake_up_klogd(void) | 2639 | void wake_up_klogd(void) |
| 2629 | { | 2640 | { |
| 2630 | preempt_disable(); | 2641 | preempt_disable(); |
| 2631 | if (waitqueue_active(&log_wait)) { | 2642 | if (!trace_override && waitqueue_active(&log_wait)) { |
| 2632 | this_cpu_or(printk_pending, PRINTK_PENDING_WAKEUP); | 2643 | this_cpu_or(printk_pending, PRINTK_PENDING_WAKEUP); |
| 2633 | irq_work_queue(this_cpu_ptr(&wake_up_klogd_work)); | 2644 | irq_work_queue(this_cpu_ptr(&wake_up_klogd_work)); |
| 2634 | } | 2645 | } |
diff --git a/litmus/Kconfig b/litmus/Kconfig index 0c7e06b922a1..e4624ee20adc 100644 --- a/litmus/Kconfig +++ b/litmus/Kconfig | |||
| @@ -49,6 +49,59 @@ config SCHED_OVERHEAD_TRACE_SHIFT | |||
| 49 | 24 => 512 MB | 49 | 24 => 512 MB |
| 50 | 26 => 2G MB | 50 | 26 => 2G MB |
| 51 | 51 | ||
| 52 | config SCHED_DEBUG_TRACE | ||
| 53 | bool "TRACE() debugging" | ||
| 54 | default n | ||
| 55 | help | ||
| 56 | Include support for sched_trace_log_messageg(), which is used to | ||
| 57 | implement TRACE(). If disabled, no TRACE() messages will be included | ||
| 58 | in the kernel, and no overheads due to debugging statements will be | ||
| 59 | incurred by the scheduler. Disable if the overhead is not acceptable | ||
| 60 | (e.g. benchmarking). | ||
| 61 | |||
| 62 | Say Yes for debugging. | ||
| 63 | Say No for overhead tracing. | ||
| 64 | |||
| 65 | config SCHED_DEBUG_TRACE_SHIFT | ||
| 66 | int "Buffer size for TRACE() buffer" | ||
| 67 | depends on SCHED_DEBUG_TRACE | ||
| 68 | range 14 22 | ||
| 69 | default 18 | ||
| 70 | help | ||
| 71 | |||
| 72 | Select the amount of memory needed per for the TRACE() buffer, as a | ||
| 73 | power of two. The TRACE() buffer is global and statically allocated. If | ||
| 74 | the buffer is too small, there will be holes in the TRACE() log if the | ||
| 75 | buffer-flushing task is starved. | ||
| 76 | |||
| 77 | The default should be sufficient for most systems. Increase the buffer | ||
| 78 | size if the log contains holes. Reduce the buffer size when running on | ||
| 79 | a memory-constrained system. | ||
| 80 | |||
| 81 | Examples: 14 => 16KB | ||
| 82 | 18 => 256KB | ||
| 83 | 20 => 1MB | ||
| 84 | |||
| 85 | This buffer is exported to usespace using a misc device as | ||
| 86 | 'litmus/log'. On a system with default udev rules, a corresponding | ||
| 87 | character device node should be created at /dev/litmus/log. The buffer | ||
| 88 | can be flushed using cat, e.g., 'cat /dev/litmus/log > my_log_file.txt'. | ||
| 89 | |||
| 90 | config SCHED_DEBUG_TRACE_CALLER | ||
| 91 | bool "Include [function@file:line] tag in TRACE() log" | ||
| 92 | depends on SCHED_DEBUG_TRACE | ||
| 93 | default n | ||
| 94 | help | ||
| 95 | With this option enabled, TRACE() prepends | ||
| 96 | |||
| 97 | "[<function name>@<filename>:<line number>]" | ||
| 98 | |||
| 99 | to each message in the debug log. Enable this to aid in figuring out | ||
| 100 | what was called in which order. The downside is that it adds a lot of | ||
| 101 | clutter. | ||
| 102 | |||
| 103 | If unsure, say No. | ||
| 104 | |||
| 52 | endmenu | 105 | endmenu |
| 53 | 106 | ||
| 54 | endmenu | 107 | endmenu |
diff --git a/litmus/Makefile b/litmus/Makefile index 99f90c34f141..07f065f9748f 100644 --- a/litmus/Makefile +++ b/litmus/Makefile | |||
| @@ -3,4 +3,5 @@ | |||
| 3 | # | 3 | # |
| 4 | 4 | ||
| 5 | obj-$(CONFIG_FEATHER_TRACE) += ft_event.o ftdev.o | 5 | obj-$(CONFIG_FEATHER_TRACE) += ft_event.o ftdev.o |
| 6 | obj-$(CONFIG_SCHED_DEBUG_TRACE) += sched_trace.o | ||
| 6 | obj-$(CONFIG_SCHED_OVERHEAD_TRACE) += trace.o | 7 | obj-$(CONFIG_SCHED_OVERHEAD_TRACE) += trace.o |
diff --git a/litmus/sched_trace.c b/litmus/sched_trace.c new file mode 100644 index 000000000000..e8648f308ccd --- /dev/null +++ b/litmus/sched_trace.c | |||
| @@ -0,0 +1,251 @@ | |||
| 1 | /* | ||
| 2 | * sched_trace.c -- record scheduling events to a byte stream. | ||
| 3 | */ | ||
| 4 | #include <linux/spinlock.h> | ||
| 5 | #include <linux/mutex.h> | ||
| 6 | |||
| 7 | #include <linux/fs.h> | ||
| 8 | #include <linux/slab.h> | ||
| 9 | #include <linux/miscdevice.h> | ||
| 10 | #include <asm/uaccess.h> | ||
| 11 | #include <linux/module.h> | ||
| 12 | #include <linux/sysrq.h> | ||
| 13 | #include <linux/sched.h> | ||
| 14 | #include <linux/kfifo.h> | ||
| 15 | |||
| 16 | atomic_t __log_seq_no = ATOMIC_INIT(0); | ||
| 17 | |||
| 18 | #define SCHED_TRACE_NAME "litmus/log" | ||
| 19 | |||
| 20 | /* Compute size of TRACE() buffer */ | ||
| 21 | #define LITMUS_TRACE_BUF_SIZE (1 << CONFIG_SCHED_DEBUG_TRACE_SHIFT) | ||
| 22 | |||
| 23 | /* Max length of one read from the buffer */ | ||
| 24 | #define MAX_READ_LEN (64 * 1024) | ||
| 25 | |||
| 26 | /* Max length for one write --- by TRACE() --- to the buffer. This is used to | ||
| 27 | * allocate a per-cpu buffer for printf() formatting. */ | ||
| 28 | #define MSG_SIZE 255 | ||
| 29 | |||
| 30 | |||
| 31 | static DEFINE_MUTEX(reader_mutex); | ||
| 32 | static atomic_t reader_cnt = ATOMIC_INIT(0); | ||
| 33 | static DEFINE_KFIFO(debug_buffer, char, LITMUS_TRACE_BUF_SIZE); | ||
| 34 | |||
| 35 | |||
| 36 | static DEFINE_RAW_SPINLOCK(log_buffer_lock); | ||
| 37 | static DEFINE_PER_CPU(char[MSG_SIZE], fmt_buffer); | ||
| 38 | |||
| 39 | /* | ||
| 40 | * sched_trace_log_message - Write to the trace buffer (log_buffer) | ||
| 41 | * | ||
| 42 | * This is the only function accessing the log_buffer from inside the | ||
| 43 | * kernel for writing. | ||
| 44 | * Concurrent access to sched_trace_log_message must be serialized using | ||
| 45 | * log_buffer_lock | ||
| 46 | * The maximum length of a formatted message is 255 | ||
| 47 | */ | ||
| 48 | void sched_trace_log_message(const char* fmt, ...) | ||
| 49 | { | ||
| 50 | unsigned long flags; | ||
| 51 | va_list args; | ||
| 52 | size_t len; | ||
| 53 | char* buf; | ||
| 54 | |||
| 55 | if (!atomic_read(&reader_cnt)) | ||
| 56 | /* early exit if nobody is listening */ | ||
| 57 | return; | ||
| 58 | |||
| 59 | va_start(args, fmt); | ||
| 60 | local_irq_save(flags); | ||
| 61 | |||
| 62 | /* format message */ | ||
| 63 | buf = this_cpu_ptr(fmt_buffer); | ||
| 64 | len = vscnprintf(buf, MSG_SIZE, fmt, args); | ||
| 65 | |||
| 66 | raw_spin_lock(&log_buffer_lock); | ||
| 67 | /* Don't copy the trailing null byte, we don't want null bytes in a | ||
| 68 | * text file. | ||
| 69 | */ | ||
| 70 | kfifo_in(&debug_buffer, buf, len); | ||
| 71 | raw_spin_unlock(&log_buffer_lock); | ||
| 72 | |||
| 73 | local_irq_restore(flags); | ||
| 74 | va_end(args); | ||
| 75 | } | ||
| 76 | |||
| 77 | |||
| 78 | /* | ||
| 79 | * log_read - Read the trace buffer | ||
| 80 | * | ||
| 81 | * This function is called as a file operation from userspace. | ||
| 82 | * Readers can sleep. Access is serialized through reader_mutex | ||
| 83 | */ | ||
| 84 | static ssize_t log_read(struct file *filp, | ||
| 85 | char __user *to, size_t len, | ||
| 86 | loff_t *f_pos) | ||
| 87 | { | ||
| 88 | /* we ignore f_pos, this is strictly sequential */ | ||
| 89 | |||
| 90 | ssize_t error = -EINVAL; | ||
| 91 | char* mem; | ||
| 92 | |||
| 93 | if (mutex_lock_interruptible(&reader_mutex)) { | ||
| 94 | error = -ERESTARTSYS; | ||
| 95 | goto out; | ||
| 96 | } | ||
| 97 | |||
| 98 | if (len > MAX_READ_LEN) | ||
| 99 | len = MAX_READ_LEN; | ||
| 100 | |||
| 101 | mem = kmalloc(len, GFP_KERNEL); | ||
| 102 | if (!mem) { | ||
| 103 | error = -ENOMEM; | ||
| 104 | goto out_unlock; | ||
| 105 | } | ||
| 106 | |||
| 107 | error = kfifo_out(&debug_buffer, mem, len); | ||
| 108 | while (!error) { | ||
| 109 | set_current_state(TASK_INTERRUPTIBLE); | ||
| 110 | schedule_timeout(110); | ||
| 111 | if (signal_pending(current)) | ||
| 112 | error = -ERESTARTSYS; | ||
| 113 | else | ||
| 114 | error = kfifo_out(&debug_buffer, mem, len); | ||
| 115 | } | ||
| 116 | |||
| 117 | if (error > 0 && copy_to_user(to, mem, error)) | ||
| 118 | error = -EFAULT; | ||
| 119 | |||
| 120 | kfree(mem); | ||
| 121 | out_unlock: | ||
| 122 | mutex_unlock(&reader_mutex); | ||
| 123 | out: | ||
| 124 | return error; | ||
| 125 | } | ||
| 126 | |||
| 127 | /* | ||
| 128 | * Enable redirection of printk() messages to the trace buffer. | ||
| 129 | * Defined in kernel/printk.c | ||
| 130 | */ | ||
| 131 | extern int trace_override; | ||
| 132 | extern int trace_recurse; | ||
| 133 | |||
| 134 | /* | ||
| 135 | * log_open - open the global log message ring buffer. | ||
| 136 | */ | ||
| 137 | static int log_open(struct inode *in, struct file *filp) | ||
| 138 | { | ||
| 139 | int error = -EINVAL; | ||
| 140 | |||
| 141 | if (mutex_lock_interruptible(&reader_mutex)) { | ||
| 142 | error = -ERESTARTSYS; | ||
| 143 | goto out; | ||
| 144 | } | ||
| 145 | |||
| 146 | atomic_inc(&reader_cnt); | ||
| 147 | error = 0; | ||
| 148 | |||
| 149 | printk(KERN_DEBUG | ||
| 150 | "sched_trace kfifo with buffer starting at: 0x%p\n", | ||
| 151 | debug_buffer.buf); | ||
| 152 | |||
| 153 | /* override printk() */ | ||
| 154 | trace_override++; | ||
| 155 | |||
| 156 | mutex_unlock(&reader_mutex); | ||
| 157 | out: | ||
| 158 | return error; | ||
| 159 | } | ||
| 160 | |||
| 161 | static int log_release(struct inode *in, struct file *filp) | ||
| 162 | { | ||
| 163 | int error = -EINVAL; | ||
| 164 | |||
| 165 | if (mutex_lock_interruptible(&reader_mutex)) { | ||
| 166 | error = -ERESTARTSYS; | ||
| 167 | goto out; | ||
| 168 | } | ||
| 169 | |||
| 170 | atomic_dec(&reader_cnt); | ||
| 171 | |||
| 172 | /* release printk() overriding */ | ||
| 173 | trace_override--; | ||
| 174 | |||
| 175 | printk(KERN_DEBUG "sched_trace kfifo released\n"); | ||
| 176 | |||
| 177 | mutex_unlock(&reader_mutex); | ||
| 178 | out: | ||
| 179 | return error; | ||
| 180 | } | ||
| 181 | |||
| 182 | /* | ||
| 183 | * log_fops - The file operations for accessing the global LITMUS log message | ||
| 184 | * buffer. | ||
| 185 | * | ||
| 186 | * Except for opening the device file it uses the same operations as trace_fops. | ||
| 187 | */ | ||
| 188 | static struct file_operations log_fops = { | ||
| 189 | .owner = THIS_MODULE, | ||
| 190 | .open = log_open, | ||
| 191 | .release = log_release, | ||
| 192 | .read = log_read, | ||
| 193 | }; | ||
| 194 | |||
| 195 | static struct miscdevice litmus_log_dev = { | ||
| 196 | .name = SCHED_TRACE_NAME, | ||
| 197 | .minor = MISC_DYNAMIC_MINOR, | ||
| 198 | .fops = &log_fops, | ||
| 199 | }; | ||
| 200 | |||
| 201 | #ifdef CONFIG_MAGIC_SYSRQ | ||
| 202 | void dump_trace_buffer(int max) | ||
| 203 | { | ||
| 204 | char line[80]; | ||
| 205 | int len; | ||
| 206 | int count = 0; | ||
| 207 | |||
| 208 | /* potential, but very unlikely, race... */ | ||
| 209 | trace_recurse = 1; | ||
| 210 | while ((max == 0 || count++ < max) && | ||
| 211 | (len = kfifo_out(&debug_buffer, line, sizeof(line - 1))) > 0) { | ||
| 212 | line[len] = '\0'; | ||
| 213 | printk("%s", line); | ||
| 214 | } | ||
| 215 | trace_recurse = 0; | ||
| 216 | } | ||
| 217 | |||
| 218 | static void sysrq_dump_trace_buffer(int key) | ||
| 219 | { | ||
| 220 | dump_trace_buffer(100); | ||
| 221 | } | ||
| 222 | |||
| 223 | static struct sysrq_key_op sysrq_dump_trace_buffer_op = { | ||
| 224 | .handler = sysrq_dump_trace_buffer, | ||
| 225 | .help_msg = "dump-trace-buffer(Y)", | ||
| 226 | .action_msg = "writing content of TRACE() buffer", | ||
| 227 | }; | ||
| 228 | #endif | ||
| 229 | |||
| 230 | static int __init init_sched_trace(void) | ||
| 231 | { | ||
| 232 | printk("Initializing TRACE() device\n"); | ||
| 233 | |||
| 234 | #ifdef CONFIG_MAGIC_SYSRQ | ||
| 235 | /* offer some debugging help */ | ||
| 236 | if (!register_sysrq_key('y', &sysrq_dump_trace_buffer_op)) | ||
| 237 | printk("Registered dump-trace-buffer(Y) magic sysrq.\n"); | ||
| 238 | else | ||
| 239 | printk("Could not register dump-trace-buffer(Y) magic sysrq.\n"); | ||
| 240 | #endif | ||
| 241 | |||
| 242 | return misc_register(&litmus_log_dev); | ||
| 243 | } | ||
| 244 | |||
| 245 | static void __exit exit_sched_trace(void) | ||
| 246 | { | ||
| 247 | misc_deregister(&litmus_log_dev); | ||
| 248 | } | ||
| 249 | |||
| 250 | module_init(init_sched_trace); | ||
| 251 | module_exit(exit_sched_trace); | ||
