2008-11-12 13:14:39 +08:00
|
|
|
/*
|
|
|
|
* unlikely profiler
|
|
|
|
*
|
|
|
|
* Copyright (C) 2008 Steven Rostedt <srostedt@redhat.com>
|
|
|
|
*/
|
|
|
|
#include <linux/kallsyms.h>
|
|
|
|
#include <linux/seq_file.h>
|
|
|
|
#include <linux/spinlock.h>
|
2008-11-29 11:12:46 +08:00
|
|
|
#include <linux/irqflags.h>
|
2008-11-12 13:14:39 +08:00
|
|
|
#include <linux/debugfs.h>
|
|
|
|
#include <linux/uaccess.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/ftrace.h>
|
|
|
|
#include <linux/hash.h>
|
|
|
|
#include <linux/fs.h>
|
|
|
|
#include <asm/local.h>
|
|
|
|
#include "trace.h"
|
|
|
|
|
2008-11-13 04:24:24 +08:00
|
|
|
#ifdef CONFIG_BRANCH_TRACER
|
2008-11-12 13:14:40 +08:00
|
|
|
|
2008-11-13 04:24:24 +08:00
|
|
|
static int branch_tracing_enabled __read_mostly;
|
|
|
|
static DEFINE_MUTEX(branch_tracing_mutex);
|
|
|
|
static struct trace_array *branch_tracer;
|
2008-11-12 13:14:40 +08:00
|
|
|
|
|
|
|
static void
|
2008-11-13 04:24:24 +08:00
|
|
|
probe_likely_condition(struct ftrace_branch_data *f, int val, int expect)
|
2008-11-12 13:14:40 +08:00
|
|
|
{
|
2008-11-13 04:24:24 +08:00
|
|
|
struct trace_array *tr = branch_tracer;
|
2008-11-12 13:14:40 +08:00
|
|
|
struct ring_buffer_event *event;
|
2008-11-13 04:24:24 +08:00
|
|
|
struct trace_branch *entry;
|
2008-11-12 13:14:40 +08:00
|
|
|
unsigned long flags, irq_flags;
|
|
|
|
int cpu, pc;
|
|
|
|
const char *p;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* I would love to save just the ftrace_likely_data pointer, but
|
|
|
|
* this code can also be used by modules. Ugly things can happen
|
|
|
|
* if the module is unloaded, and then we go and read the
|
|
|
|
* pointer. This is slower, but much safer.
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (unlikely(!tr))
|
|
|
|
return;
|
|
|
|
|
2008-12-03 04:34:05 +08:00
|
|
|
local_irq_save(flags);
|
2008-11-12 13:14:40 +08:00
|
|
|
cpu = raw_smp_processor_id();
|
|
|
|
if (atomic_inc_return(&tr->data[cpu]->disabled) != 1)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
event = ring_buffer_lock_reserve(tr->buffer, sizeof(*entry),
|
|
|
|
&irq_flags);
|
|
|
|
if (!event)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
pc = preempt_count();
|
|
|
|
entry = ring_buffer_event_data(event);
|
|
|
|
tracing_generic_entry_update(&entry->ent, flags, pc);
|
2008-11-13 04:24:24 +08:00
|
|
|
entry->ent.type = TRACE_BRANCH;
|
2008-11-12 13:14:40 +08:00
|
|
|
|
|
|
|
/* Strip off the path, only save the file */
|
|
|
|
p = f->file + strlen(f->file);
|
|
|
|
while (p >= f->file && *p != '/')
|
|
|
|
p--;
|
|
|
|
p++;
|
|
|
|
|
|
|
|
strncpy(entry->func, f->func, TRACE_FUNC_SIZE);
|
|
|
|
strncpy(entry->file, p, TRACE_FILE_SIZE);
|
|
|
|
entry->func[TRACE_FUNC_SIZE] = 0;
|
|
|
|
entry->file[TRACE_FILE_SIZE] = 0;
|
|
|
|
entry->line = f->line;
|
|
|
|
entry->correct = val == expect;
|
|
|
|
|
|
|
|
ring_buffer_unlock_commit(tr->buffer, event, irq_flags);
|
|
|
|
|
|
|
|
out:
|
|
|
|
atomic_dec(&tr->data[cpu]->disabled);
|
2008-12-03 04:34:05 +08:00
|
|
|
local_irq_restore(flags);
|
2008-11-12 13:14:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static inline
|
2008-11-13 04:24:24 +08:00
|
|
|
void trace_likely_condition(struct ftrace_branch_data *f, int val, int expect)
|
2008-11-12 13:14:40 +08:00
|
|
|
{
|
2008-11-13 04:24:24 +08:00
|
|
|
if (!branch_tracing_enabled)
|
2008-11-12 13:14:40 +08:00
|
|
|
return;
|
|
|
|
|
|
|
|
probe_likely_condition(f, val, expect);
|
|
|
|
}
|
|
|
|
|
2008-11-13 04:24:24 +08:00
|
|
|
int enable_branch_tracing(struct trace_array *tr)
|
2008-11-12 13:14:40 +08:00
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
|
2008-11-13 04:24:24 +08:00
|
|
|
mutex_lock(&branch_tracing_mutex);
|
|
|
|
branch_tracer = tr;
|
2008-11-12 13:14:40 +08:00
|
|
|
/*
|
|
|
|
* Must be seen before enabling. The reader is a condition
|
|
|
|
* where we do not need a matching rmb()
|
|
|
|
*/
|
|
|
|
smp_wmb();
|
2008-11-13 04:24:24 +08:00
|
|
|
branch_tracing_enabled++;
|
|
|
|
mutex_unlock(&branch_tracing_mutex);
|
2008-11-12 13:14:40 +08:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2008-11-13 04:24:24 +08:00
|
|
|
void disable_branch_tracing(void)
|
2008-11-12 13:14:40 +08:00
|
|
|
{
|
2008-11-13 04:24:24 +08:00
|
|
|
mutex_lock(&branch_tracing_mutex);
|
2008-11-12 13:14:40 +08:00
|
|
|
|
2008-11-13 04:24:24 +08:00
|
|
|
if (!branch_tracing_enabled)
|
2008-11-12 13:14:40 +08:00
|
|
|
goto out_unlock;
|
|
|
|
|
2008-11-13 04:24:24 +08:00
|
|
|
branch_tracing_enabled--;
|
2008-11-12 13:14:40 +08:00
|
|
|
|
|
|
|
out_unlock:
|
2008-11-13 04:24:24 +08:00
|
|
|
mutex_unlock(&branch_tracing_mutex);
|
2008-11-12 13:14:40 +08:00
|
|
|
}
|
2008-11-13 04:24:24 +08:00
|
|
|
|
|
|
|
static void start_branch_trace(struct trace_array *tr)
|
|
|
|
{
|
|
|
|
enable_branch_tracing(tr);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void stop_branch_trace(struct trace_array *tr)
|
|
|
|
{
|
|
|
|
disable_branch_tracing();
|
|
|
|
}
|
|
|
|
|
2008-11-16 12:57:26 +08:00
|
|
|
static int branch_trace_init(struct trace_array *tr)
|
2008-11-13 04:24:24 +08:00
|
|
|
{
|
|
|
|
int cpu;
|
|
|
|
|
|
|
|
for_each_online_cpu(cpu)
|
|
|
|
tracing_reset(tr, cpu);
|
|
|
|
|
|
|
|
start_branch_trace(tr);
|
2008-11-16 12:57:26 +08:00
|
|
|
return 0;
|
2008-11-13 04:24:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void branch_trace_reset(struct trace_array *tr)
|
|
|
|
{
|
|
|
|
stop_branch_trace(tr);
|
|
|
|
}
|
|
|
|
|
|
|
|
struct tracer branch_trace __read_mostly =
|
|
|
|
{
|
|
|
|
.name = "branch",
|
|
|
|
.init = branch_trace_init,
|
|
|
|
.reset = branch_trace_reset,
|
|
|
|
#ifdef CONFIG_FTRACE_SELFTEST
|
|
|
|
.selftest = trace_selftest_startup_branch,
|
|
|
|
#endif
|
|
|
|
};
|
|
|
|
|
|
|
|
__init static int init_branch_trace(void)
|
|
|
|
{
|
|
|
|
return register_tracer(&branch_trace);
|
|
|
|
}
|
|
|
|
|
|
|
|
device_initcall(init_branch_trace);
|
2008-11-12 13:14:40 +08:00
|
|
|
#else
|
|
|
|
static inline
|
2008-11-13 04:24:24 +08:00
|
|
|
void trace_likely_condition(struct ftrace_branch_data *f, int val, int expect)
|
2008-11-12 13:14:40 +08:00
|
|
|
{
|
|
|
|
}
|
2008-11-13 04:24:24 +08:00
|
|
|
#endif /* CONFIG_BRANCH_TRACER */
|
2008-11-12 13:14:40 +08:00
|
|
|
|
2008-11-13 04:24:24 +08:00
|
|
|
void ftrace_likely_update(struct ftrace_branch_data *f, int val, int expect)
|
2008-11-12 13:14:39 +08:00
|
|
|
{
|
2008-11-12 13:14:40 +08:00
|
|
|
/*
|
|
|
|
* I would love to have a trace point here instead, but the
|
|
|
|
* trace point code is so inundated with unlikely and likely
|
|
|
|
* conditions that the recursive nightmare that exists is too
|
|
|
|
* much to try to get working. At least for now.
|
|
|
|
*/
|
|
|
|
trace_likely_condition(f, val, expect);
|
|
|
|
|
2008-11-12 13:14:39 +08:00
|
|
|
/* FIXME: Make this atomic! */
|
|
|
|
if (val == expect)
|
|
|
|
f->correct++;
|
|
|
|
else
|
|
|
|
f->incorrect++;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(ftrace_likely_update);
|
|
|
|
|
|
|
|
struct ftrace_pointer {
|
|
|
|
void *start;
|
|
|
|
void *stop;
|
2008-11-21 14:30:54 +08:00
|
|
|
int hit;
|
2008-11-12 13:14:39 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
static void *
|
|
|
|
t_next(struct seq_file *m, void *v, loff_t *pos)
|
|
|
|
{
|
2008-11-22 03:44:57 +08:00
|
|
|
const struct ftrace_pointer *f = m->private;
|
2008-11-13 04:24:24 +08:00
|
|
|
struct ftrace_branch_data *p = v;
|
2008-11-12 13:14:39 +08:00
|
|
|
|
|
|
|
(*pos)++;
|
|
|
|
|
|
|
|
if (v == (void *)1)
|
|
|
|
return f->start;
|
|
|
|
|
|
|
|
++p;
|
|
|
|
|
|
|
|
if ((void *)p >= (void *)f->stop)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void *t_start(struct seq_file *m, loff_t *pos)
|
|
|
|
{
|
|
|
|
void *t = (void *)1;
|
|
|
|
loff_t l = 0;
|
|
|
|
|
|
|
|
for (; t && l < *pos; t = t_next(m, t, &l))
|
|
|
|
;
|
|
|
|
|
|
|
|
return t;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void t_stop(struct seq_file *m, void *p)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
static int t_show(struct seq_file *m, void *v)
|
|
|
|
{
|
2008-11-22 03:44:57 +08:00
|
|
|
const struct ftrace_pointer *fp = m->private;
|
2008-11-13 04:24:24 +08:00
|
|
|
struct ftrace_branch_data *p = v;
|
2008-11-12 13:14:39 +08:00
|
|
|
const char *f;
|
2008-11-21 14:51:53 +08:00
|
|
|
long percent;
|
2008-11-12 13:14:39 +08:00
|
|
|
|
|
|
|
if (v == (void *)1) {
|
2008-11-21 14:30:54 +08:00
|
|
|
if (fp->hit)
|
|
|
|
seq_printf(m, " miss hit %% ");
|
|
|
|
else
|
|
|
|
seq_printf(m, " correct incorrect %% ");
|
|
|
|
seq_printf(m, " Function "
|
2008-11-12 13:14:39 +08:00
|
|
|
" File Line\n"
|
|
|
|
" ------- --------- - "
|
|
|
|
" -------- "
|
|
|
|
" ---- ----\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Only print the file, not the path */
|
|
|
|
f = p->file + strlen(p->file);
|
|
|
|
while (f >= p->file && *f != '/')
|
|
|
|
f--;
|
|
|
|
f++;
|
|
|
|
|
2008-11-21 14:30:54 +08:00
|
|
|
/*
|
|
|
|
* The miss is overlayed on correct, and hit on incorrect.
|
|
|
|
*/
|
2008-11-12 13:14:39 +08:00
|
|
|
if (p->correct) {
|
|
|
|
percent = p->incorrect * 100;
|
|
|
|
percent /= p->correct + p->incorrect;
|
|
|
|
} else
|
2008-11-21 14:51:53 +08:00
|
|
|
percent = p->incorrect ? 100 : -1;
|
2008-11-12 13:14:39 +08:00
|
|
|
|
2008-11-21 14:51:53 +08:00
|
|
|
seq_printf(m, "%8lu %8lu ", p->correct, p->incorrect);
|
|
|
|
if (percent < 0)
|
|
|
|
seq_printf(m, " X ");
|
|
|
|
else
|
|
|
|
seq_printf(m, "%3ld ", percent);
|
2008-11-12 13:14:39 +08:00
|
|
|
seq_printf(m, "%-30.30s %-20.20s %d\n", p->func, f, p->line);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct seq_operations tracing_likely_seq_ops = {
|
|
|
|
.start = t_start,
|
|
|
|
.next = t_next,
|
|
|
|
.stop = t_stop,
|
|
|
|
.show = t_show,
|
|
|
|
};
|
|
|
|
|
2008-11-21 13:40:40 +08:00
|
|
|
static int tracing_branch_open(struct inode *inode, struct file *file)
|
2008-11-12 13:14:39 +08:00
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = seq_open(file, &tracing_likely_seq_ops);
|
|
|
|
if (!ret) {
|
|
|
|
struct seq_file *m = file->private_data;
|
|
|
|
m->private = (void *)inode->i_private;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2008-11-21 13:40:40 +08:00
|
|
|
static const struct file_operations tracing_branch_fops = {
|
|
|
|
.open = tracing_branch_open,
|
2008-11-12 13:14:39 +08:00
|
|
|
.read = seq_read,
|
|
|
|
.llseek = seq_lseek,
|
|
|
|
};
|
|
|
|
|
2008-11-21 14:30:54 +08:00
|
|
|
#ifdef CONFIG_PROFILE_ALL_BRANCHES
|
|
|
|
extern unsigned long __start_branch_profile[];
|
|
|
|
extern unsigned long __stop_branch_profile[];
|
|
|
|
|
2008-11-22 03:44:57 +08:00
|
|
|
static const struct ftrace_pointer ftrace_branch_pos = {
|
2008-11-21 14:30:54 +08:00
|
|
|
.start = __start_branch_profile,
|
|
|
|
.stop = __stop_branch_profile,
|
|
|
|
.hit = 1,
|
|
|
|
};
|
|
|
|
|
|
|
|
#endif /* CONFIG_PROFILE_ALL_BRANCHES */
|
|
|
|
|
2008-11-21 13:40:40 +08:00
|
|
|
extern unsigned long __start_annotated_branch_profile[];
|
|
|
|
extern unsigned long __stop_annotated_branch_profile[];
|
2008-11-12 13:14:39 +08:00
|
|
|
|
2008-11-21 13:40:40 +08:00
|
|
|
static const struct ftrace_pointer ftrace_annotated_branch_pos = {
|
|
|
|
.start = __start_annotated_branch_profile,
|
|
|
|
.stop = __stop_annotated_branch_profile,
|
2008-11-12 13:14:39 +08:00
|
|
|
};
|
|
|
|
|
2008-11-13 04:24:24 +08:00
|
|
|
static __init int ftrace_branch_init(void)
|
2008-11-12 13:14:39 +08:00
|
|
|
{
|
|
|
|
struct dentry *d_tracer;
|
|
|
|
struct dentry *entry;
|
|
|
|
|
|
|
|
d_tracer = tracing_init_dentry();
|
|
|
|
|
2008-11-21 13:40:40 +08:00
|
|
|
entry = debugfs_create_file("profile_annotated_branch", 0444, d_tracer,
|
2008-11-22 03:44:57 +08:00
|
|
|
(void *)&ftrace_annotated_branch_pos,
|
2008-11-21 13:40:40 +08:00
|
|
|
&tracing_branch_fops);
|
2008-11-12 13:14:39 +08:00
|
|
|
if (!entry)
|
2008-11-21 13:40:40 +08:00
|
|
|
pr_warning("Could not create debugfs "
|
|
|
|
"'profile_annotatet_branch' entry\n");
|
2008-11-12 13:14:39 +08:00
|
|
|
|
2008-11-21 14:30:54 +08:00
|
|
|
#ifdef CONFIG_PROFILE_ALL_BRANCHES
|
|
|
|
entry = debugfs_create_file("profile_branch", 0444, d_tracer,
|
2008-11-22 03:44:57 +08:00
|
|
|
(void *)&ftrace_branch_pos,
|
2008-11-21 14:30:54 +08:00
|
|
|
&tracing_branch_fops);
|
|
|
|
if (!entry)
|
|
|
|
pr_warning("Could not create debugfs"
|
|
|
|
" 'profile_branch' entry\n");
|
|
|
|
#endif
|
|
|
|
|
2008-11-12 13:14:39 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-11-13 04:24:24 +08:00
|
|
|
device_initcall(ftrace_branch_init);
|