[PATCH] x86: add the debugfs interface for the sysprof tool

From: Arjan van de Ven
Date: Tue Feb 19 2008 - 15:39:29 EST


From: Soren Sandmann <sandmann@xxxxxxxxxx>
Subject: [PATCH] x86: add the debugfs interface for the sysprof tool

The sysprof tool is a very easy to use GUI tool to find out where
userspace is spending CPU time. See
http://www.daimi.au.dk/~sandmann/sysprof/
for more information and screenshots on this tool.

Sysprof needs a 200 line kernel module to do it's work, this
module puts some simple profiling data into debugfs.

Signed-off-by: Soren Sandmann <sandmann@xxxxxxxxxx>
Signed-off-by: Arjan van de Ven <arjan@xxxxxxxxxxxxxxx>
---
arch/x86/Kconfig.debug | 10 ++
arch/x86/kernel/Makefile | 2 +
arch/x86/kernel/sysprof.c | 200 +++++++++++++++++++++++++++++++++++++++++++++
arch/x86/kernel/sysprof.h | 34 ++++++++
4 files changed, 246 insertions(+), 0 deletions(-)
create mode 100644 arch/x86/kernel/sysprof.c
create mode 100644 arch/x86/kernel/sysprof.h

diff --git a/arch/x86/Kconfig.debug b/arch/x86/Kconfig.debug
index 12c98ea..8eb06c0 100644
--- a/arch/x86/Kconfig.debug
+++ b/arch/x86/Kconfig.debug
@@ -206,6 +206,16 @@ config MMIOTRACE_TEST

Say N, unless you absolutely know what you are doing.

+config SYSPROF
+ tristate "Enable sysprof userland performance sampler"
+ depends on PROFILING
+ help
+ This option enables the sysprof debugfs file that is used by the
+ sysprof tool. sysprof is a tool to show where in userspace CPU
+ time is spent.
+
+ When in doubt, say N
+
#
# IO delay types:
#
diff --git a/arch/x86/kernel/Makefile b/arch/x86/kernel/Makefile
index 4a4260c..1e8fb66 100644
--- a/arch/x86/kernel/Makefile
+++ b/arch/x86/kernel/Makefile
@@ -80,6 +80,8 @@ obj-$(CONFIG_DEBUG_NX_TEST) += test_nx.o
obj-$(CONFIG_VMI) += vmi_32.o vmiclock_32.o
obj-$(CONFIG_PARAVIRT) += paravirt.o paravirt_patch_$(BITS).o

+obj-$(CONFIG_SYSPROF) += sysprof.o
+
ifdef CONFIG_INPUT_PCSPKR
obj-y += pcspeaker.o
endif
diff --git a/arch/x86/kernel/sysprof.c b/arch/x86/kernel/sysprof.c
new file mode 100644
index 0000000..6220b9f
--- /dev/null
+++ b/arch/x86/kernel/sysprof.c
@@ -0,0 +1,200 @@
+/* -*- c-basic-offset: 8 -*- */
+
+/* Sysprof -- Sampling, systemwide CPU profiler
+ * Copyright 2004, Red Hat, Inc.
+ * Copyright 2004, 2005, Soeren Sandmann
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/proc_fs.h>
+#include <linux/poll.h>
+#include <linux/highmem.h>
+#include <linux/pagemap.h>
+#include <linux/profile.h>
+#include <linux/debugfs.h>
+#include <asm/uaccess.h>
+#include <asm/atomic.h>
+
+#include "sysprof.h"
+
+#define SAMPLES_PER_SECOND (200)
+#define INTERVAL ((HZ <= SAMPLES_PER_SECOND)? 1 : (HZ / SAMPLES_PER_SECOND))
+#define N_TRACES 256
+
+static struct sysprof_stacktrace stack_traces[N_TRACES];
+static struct sysprof_stacktrace *head = &stack_traces[0];
+static struct sysprof_stacktrace *tail = &stack_traces[0];
+static DECLARE_WAIT_QUEUE_HEAD(wait_for_trace);
+static DECLARE_WAIT_QUEUE_HEAD(wait_for_exit);
+
+struct userspace_reader {
+ struct task_struct *task;
+ unsigned long cache_address;
+ unsigned long *cache;
+};
+
+struct stack_frame;
+
+struct stack_frame {
+ struct stack_frame __user *next;
+ unsigned long return_address;
+};
+
+static int read_frame(struct stack_frame __user *frame_pointer,
+ struct stack_frame *frame)
+{
+ if (__copy_from_user_inatomic(frame, frame_pointer,
+ sizeof(struct stack_frame)))
+ return 1;
+ return 0;
+}
+
+static DEFINE_PER_CPU(int, n_samples);
+
+static int timer_notify(struct pt_regs *regs)
+{
+ struct sysprof_stacktrace *trace = head;
+ int i;
+ int is_user;
+ static atomic_t in_timer_notify = ATOMIC_INIT(1);
+ int n;
+
+ n = ++get_cpu_var(n_samples);
+ put_cpu_var(n_samples);
+
+ if (n % INTERVAL != 0)
+ return 0;
+
+ /* 0: locked, 1: unlocked */
+
+ if (!atomic_dec_and_test(&in_timer_notify))
+ goto out;
+
+ is_user = user_mode(regs);
+
+ if (!current || current->pid == 0)
+ goto out;
+
+ if (is_user && current->state != TASK_RUNNING)
+ goto out;
+
+ if (!is_user) {
+ /* kernel */
+ trace->pid = current->pid;
+ trace->truncated = 0;
+ trace->n_addresses = 1;
+
+ /* 0x1 is taken by sysprof to mean "in kernel" */
+ trace->addresses[0] = 0x1;
+ } else {
+ struct stack_frame __user *frame_pointer;
+ struct stack_frame frame;
+ memset(trace, 0, sizeof(struct sysprof_stacktrace));
+
+ trace->pid = current->pid;
+ trace->truncated = 0;
+
+ i = 0;
+
+ trace->addresses[i++] = regs->ip;
+
+ frame_pointer = (struct stack_frame __user *)regs->bp;
+
+ while (read_frame(frame_pointer, &frame) == 0 &&
+ i < SYSPROF_MAX_ADDRESSES &&
+ (unsigned long)frame_pointer >= regs->sp) {
+ trace->addresses[i++] = frame.return_address;
+ frame_pointer = frame.next;
+ }
+
+ trace->n_addresses = i;
+
+ if (i == SYSPROF_MAX_ADDRESSES)
+ trace->truncated = 1;
+ else
+ trace->truncated = 0;
+ }
+
+ if (head++ == &stack_traces[N_TRACES - 1])
+ head = &stack_traces[0];
+
+ wake_up(&wait_for_trace);
+
+out:
+ atomic_inc(&in_timer_notify);
+ return 0;
+}
+
+static ssize_t procfile_read(struct file *filp, char __user *buffer,
+ size_t count, loff_t *ppos)
+{
+ ssize_t bcount;
+ if (head == tail)
+ return -EWOULDBLOCK;
+
+ BUG_ON(tail->pid == 0);
+ *ppos = 0;
+ bcount = simple_read_from_buffer(buffer, count, ppos,
+ tail, sizeof(struct sysprof_stacktrace));
+
+ if (tail++ == &stack_traces[N_TRACES - 1])
+ tail = &stack_traces[0];
+
+ return bcount;
+}
+
+static unsigned int procfile_poll(struct file *filp, poll_table * poll_table)
+{
+ if (head != tail)
+ return POLLIN | POLLRDNORM;
+
+ poll_wait(filp, &wait_for_trace, poll_table);
+
+ if (head != tail)
+ return POLLIN | POLLRDNORM;
+
+ return 0;
+}
+
+static const struct file_operations sysprof_fops = {
+ .owner = THIS_MODULE,
+ .read = procfile_read,
+ .poll = procfile_poll,
+};
+
+static struct dentry *debugfs_pe;
+int init_module(void)
+{
+ debugfs_pe = debugfs_create_file("sysprof-trace", 0600, NULL, NULL,
+ &sysprof_fops);
+ if (!debugfs_pe)
+ return -ENODEV;
+ register_timer_hook(timer_notify);
+
+ return 0;
+}
+
+void cleanup_module(void)
+{
+ unregister_timer_hook(timer_notify);
+ debugfs_remove(debugfs_pe);
+}
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Soeren Sandmann (sandmann@xxxxxxxxxxx)");
+MODULE_DESCRIPTION("Kernel driver for the sysprof performance analysis tool");
diff --git a/arch/x86/kernel/sysprof.h b/arch/x86/kernel/sysprof.h
new file mode 100644
index 0000000..6e16d6f
--- /dev/null
+++ b/arch/x86/kernel/sysprof.h
@@ -0,0 +1,34 @@
+/* Sysprof -- Sampling, systemwide CPU profiler
+ * Copyright 2004, Red Hat, Inc.
+ * Copyright 2004, 2005, Soeren Sandmann
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef SYSPROF_MODULE_H
+#define SYSPROF_MODULE_H
+
+#define SYSPROF_MAX_ADDRESSES 512
+
+struct sysprof_stacktrace {
+ int pid; /* -1 if in kernel */
+ int truncated;
+ int n_addresses; /* note: this can be 1 if the process was compiled
+ * with -fomit-frame-pointer or is otherwise weird
+ */
+ unsigned long addresses[SYSPROF_MAX_ADDRESSES];
+};
+
+#endif
--
1.5.4.1



--
If you want to reach me at my work email, use arjan@xxxxxxxxxxxxxxx
For development, discussion and tips for power savings,
visit http://www.lesswatts.org
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/