mirror of
https://github.com/torvalds/linux.git
synced 2024-11-11 06:31:49 +00:00
Merge branches 'tracing/blktrace', 'tracing/ftrace', 'tracing/function-graph-tracer' and 'tracing/power-tracer' into tracing/core
This commit is contained in:
commit
c7cc773076
@ -127,6 +127,8 @@ of ftrace. Here is a list of some of the key files:
|
||||
be traced. If a function exists in both set_ftrace_filter
|
||||
and set_ftrace_notrace, the function will _not_ be traced.
|
||||
|
||||
set_ftrace_pid: Have the function tracer only trace a single thread.
|
||||
|
||||
available_filter_functions: This lists the functions that ftrace
|
||||
has processed and can trace. These are the function
|
||||
names that you can pass to "set_ftrace_filter" or
|
||||
@ -1073,6 +1075,83 @@ For simple one time traces, the above is sufficent. For anything else,
|
||||
a search through /proc/mounts may be needed to find where the debugfs
|
||||
file-system is mounted.
|
||||
|
||||
|
||||
Single thread tracing
|
||||
---------------------
|
||||
|
||||
By writing into /debug/tracing/set_ftrace_pid you can trace a
|
||||
single thread. For example:
|
||||
|
||||
# cat /debug/tracing/set_ftrace_pid
|
||||
no pid
|
||||
# echo 3111 > /debug/tracing/set_ftrace_pid
|
||||
# cat /debug/tracing/set_ftrace_pid
|
||||
3111
|
||||
# echo function > /debug/tracing/current_tracer
|
||||
# cat /debug/tracing/trace | head
|
||||
# tracer: function
|
||||
#
|
||||
# TASK-PID CPU# TIMESTAMP FUNCTION
|
||||
# | | | | |
|
||||
yum-updatesd-3111 [003] 1637.254676: finish_task_switch <-thread_return
|
||||
yum-updatesd-3111 [003] 1637.254681: hrtimer_cancel <-schedule_hrtimeout_range
|
||||
yum-updatesd-3111 [003] 1637.254682: hrtimer_try_to_cancel <-hrtimer_cancel
|
||||
yum-updatesd-3111 [003] 1637.254683: lock_hrtimer_base <-hrtimer_try_to_cancel
|
||||
yum-updatesd-3111 [003] 1637.254685: fget_light <-do_sys_poll
|
||||
yum-updatesd-3111 [003] 1637.254686: pipe_poll <-do_sys_poll
|
||||
# echo -1 > /debug/tracing/set_ftrace_pid
|
||||
# cat /debug/tracing/trace |head
|
||||
# tracer: function
|
||||
#
|
||||
# TASK-PID CPU# TIMESTAMP FUNCTION
|
||||
# | | | | |
|
||||
##### CPU 3 buffer started ####
|
||||
yum-updatesd-3111 [003] 1701.957688: free_poll_entry <-poll_freewait
|
||||
yum-updatesd-3111 [003] 1701.957689: remove_wait_queue <-free_poll_entry
|
||||
yum-updatesd-3111 [003] 1701.957691: fput <-free_poll_entry
|
||||
yum-updatesd-3111 [003] 1701.957692: audit_syscall_exit <-sysret_audit
|
||||
yum-updatesd-3111 [003] 1701.957693: path_put <-audit_syscall_exit
|
||||
|
||||
If you want to trace a function when executing, you could use
|
||||
something like this simple program:
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int main (int argc, char **argv)
|
||||
{
|
||||
if (argc < 1)
|
||||
exit(-1);
|
||||
|
||||
if (fork() > 0) {
|
||||
int fd, ffd;
|
||||
char line[64];
|
||||
int s;
|
||||
|
||||
ffd = open("/debug/tracing/current_tracer", O_WRONLY);
|
||||
if (ffd < 0)
|
||||
exit(-1);
|
||||
write(ffd, "nop", 3);
|
||||
|
||||
fd = open("/debug/tracing/set_ftrace_pid", O_WRONLY);
|
||||
s = sprintf(line, "%d\n", getpid());
|
||||
write(fd, line, s);
|
||||
|
||||
write(ffd, "function", 8);
|
||||
|
||||
close(fd);
|
||||
close(ffd);
|
||||
|
||||
execvp(argv[1], argv+1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
dynamic ftrace
|
||||
--------------
|
||||
|
||||
|
@ -29,7 +29,7 @@ config X86
|
||||
select HAVE_FTRACE_MCOUNT_RECORD
|
||||
select HAVE_DYNAMIC_FTRACE
|
||||
select HAVE_FUNCTION_TRACER
|
||||
select HAVE_FUNCTION_RET_TRACER if X86_32
|
||||
select HAVE_FUNCTION_GRAPH_TRACER if X86_32
|
||||
select HAVE_FUNCTION_TRACE_MCOUNT_TEST
|
||||
select HAVE_KVM if ((X86_32 && !X86_VOYAGER && !X86_VISWS && !X86_NUMAQ) || X86_64)
|
||||
select HAVE_ARCH_KGDB if !X86_VOYAGER
|
||||
|
@ -28,7 +28,7 @@ struct dyn_arch_ftrace {
|
||||
#endif /* __ASSEMBLY__ */
|
||||
#endif /* CONFIG_FUNCTION_TRACER */
|
||||
|
||||
#ifdef CONFIG_FUNCTION_RET_TRACER
|
||||
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
|
||||
|
||||
#ifndef __ASSEMBLY__
|
||||
|
||||
@ -51,6 +51,6 @@ struct ftrace_ret_stack {
|
||||
extern void return_to_handler(void);
|
||||
|
||||
#endif /* __ASSEMBLY__ */
|
||||
#endif /* CONFIG_FUNCTION_RET_TRACER */
|
||||
#endif /* CONFIG_FUNCTION_GRAPH_TRACER */
|
||||
|
||||
#endif /* _ASM_X86_FTRACE_H */
|
||||
|
@ -14,7 +14,7 @@ CFLAGS_REMOVE_paravirt-spinlocks.o = -pg
|
||||
CFLAGS_REMOVE_ftrace.o = -pg
|
||||
endif
|
||||
|
||||
ifdef CONFIG_FUNCTION_RET_TRACER
|
||||
ifdef CONFIG_FUNCTION_GRAPH_TRACER
|
||||
# Don't trace __switch_to() but let it for function tracer
|
||||
CFLAGS_REMOVE_process_32.o = -pg
|
||||
endif
|
||||
@ -70,7 +70,7 @@ obj-$(CONFIG_X86_LOCAL_APIC) += apic.o nmi.o
|
||||
obj-$(CONFIG_X86_IO_APIC) += io_apic.o
|
||||
obj-$(CONFIG_X86_REBOOTFIXUPS) += reboot_fixups_32.o
|
||||
obj-$(CONFIG_DYNAMIC_FTRACE) += ftrace.o
|
||||
obj-$(CONFIG_FUNCTION_RET_TRACER) += ftrace.o
|
||||
obj-$(CONFIG_FUNCTION_GRAPH_TRACER) += ftrace.o
|
||||
obj-$(CONFIG_KEXEC) += machine_kexec_$(BITS).o
|
||||
obj-$(CONFIG_KEXEC) += relocate_kernel_$(BITS).o crash.o
|
||||
obj-$(CONFIG_CRASH_DUMP) += crash_dump_$(BITS).o
|
||||
|
@ -33,6 +33,7 @@
|
||||
#include <linux/cpufreq.h>
|
||||
#include <linux/compiler.h>
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/ftrace.h>
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <acpi/processor.h>
|
||||
@ -391,6 +392,7 @@ static int acpi_cpufreq_target(struct cpufreq_policy *policy,
|
||||
unsigned int next_perf_state = 0; /* Index into perf table */
|
||||
unsigned int i;
|
||||
int result = 0;
|
||||
struct power_trace it;
|
||||
|
||||
dprintk("acpi_cpufreq_target %d (%d)\n", target_freq, policy->cpu);
|
||||
|
||||
@ -427,6 +429,8 @@ static int acpi_cpufreq_target(struct cpufreq_policy *policy,
|
||||
}
|
||||
}
|
||||
|
||||
trace_power_mark(&it, POWER_PSTATE, next_perf_state);
|
||||
|
||||
switch (data->cpu_feature) {
|
||||
case SYSTEM_INTEL_MSR_CAPABLE:
|
||||
cmd.type = SYSTEM_INTEL_MSR_CAPABLE;
|
||||
|
@ -1174,6 +1174,11 @@ ftrace_call:
|
||||
popl %edx
|
||||
popl %ecx
|
||||
popl %eax
|
||||
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
|
||||
.globl ftrace_graph_call
|
||||
ftrace_graph_call:
|
||||
jmp ftrace_stub
|
||||
#endif
|
||||
|
||||
.globl ftrace_stub
|
||||
ftrace_stub:
|
||||
@ -1188,9 +1193,9 @@ ENTRY(mcount)
|
||||
|
||||
cmpl $ftrace_stub, ftrace_trace_function
|
||||
jnz trace
|
||||
#ifdef CONFIG_FUNCTION_RET_TRACER
|
||||
cmpl $ftrace_stub, ftrace_function_return
|
||||
jnz ftrace_return_caller
|
||||
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
|
||||
cmpl $ftrace_stub, ftrace_graph_return
|
||||
jnz ftrace_graph_caller
|
||||
#endif
|
||||
.globl ftrace_stub
|
||||
ftrace_stub:
|
||||
@ -1215,8 +1220,8 @@ END(mcount)
|
||||
#endif /* CONFIG_DYNAMIC_FTRACE */
|
||||
#endif /* CONFIG_FUNCTION_TRACER */
|
||||
|
||||
#ifdef CONFIG_FUNCTION_RET_TRACER
|
||||
ENTRY(ftrace_return_caller)
|
||||
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
|
||||
ENTRY(ftrace_graph_caller)
|
||||
cmpl $0, function_trace_stop
|
||||
jne ftrace_stub
|
||||
|
||||
@ -1230,7 +1235,7 @@ ENTRY(ftrace_return_caller)
|
||||
popl %ecx
|
||||
popl %eax
|
||||
ret
|
||||
END(ftrace_return_caller)
|
||||
END(ftrace_graph_caller)
|
||||
|
||||
.globl return_to_handler
|
||||
return_to_handler:
|
||||
|
@ -111,7 +111,6 @@ static void ftrace_mod_code(void)
|
||||
*/
|
||||
mod_code_status = probe_kernel_write(mod_code_ip, mod_code_newcode,
|
||||
MCOUNT_INSN_SIZE);
|
||||
|
||||
}
|
||||
|
||||
void ftrace_nmi_enter(void)
|
||||
@ -323,9 +322,53 @@ int __init ftrace_dyn_arch_init(void *data)
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_FUNCTION_RET_TRACER
|
||||
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
|
||||
|
||||
#ifndef CONFIG_DYNAMIC_FTRACE
|
||||
#ifdef CONFIG_DYNAMIC_FTRACE
|
||||
extern void ftrace_graph_call(void);
|
||||
|
||||
static int ftrace_mod_jmp(unsigned long ip,
|
||||
int old_offset, int new_offset)
|
||||
{
|
||||
unsigned char code[MCOUNT_INSN_SIZE];
|
||||
|
||||
if (probe_kernel_read(code, (void *)ip, MCOUNT_INSN_SIZE))
|
||||
return -EFAULT;
|
||||
|
||||
if (code[0] != 0xe9 || old_offset != *(int *)(&code[1]))
|
||||
return -EINVAL;
|
||||
|
||||
*(int *)(&code[1]) = new_offset;
|
||||
|
||||
if (do_ftrace_mod_code(ip, &code))
|
||||
return -EPERM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ftrace_enable_ftrace_graph_caller(void)
|
||||
{
|
||||
unsigned long ip = (unsigned long)(&ftrace_graph_call);
|
||||
int old_offset, new_offset;
|
||||
|
||||
old_offset = (unsigned long)(&ftrace_stub) - (ip + MCOUNT_INSN_SIZE);
|
||||
new_offset = (unsigned long)(&ftrace_graph_caller) - (ip + MCOUNT_INSN_SIZE);
|
||||
|
||||
return ftrace_mod_jmp(ip, old_offset, new_offset);
|
||||
}
|
||||
|
||||
int ftrace_disable_ftrace_graph_caller(void)
|
||||
{
|
||||
unsigned long ip = (unsigned long)(&ftrace_graph_call);
|
||||
int old_offset, new_offset;
|
||||
|
||||
old_offset = (unsigned long)(&ftrace_graph_caller) - (ip + MCOUNT_INSN_SIZE);
|
||||
new_offset = (unsigned long)(&ftrace_stub) - (ip + MCOUNT_INSN_SIZE);
|
||||
|
||||
return ftrace_mod_jmp(ip, old_offset, new_offset);
|
||||
}
|
||||
|
||||
#else /* CONFIG_DYNAMIC_FTRACE */
|
||||
|
||||
/*
|
||||
* These functions are picked from those used on
|
||||
@ -343,11 +386,12 @@ void ftrace_nmi_exit(void)
|
||||
{
|
||||
atomic_dec(&in_nmi);
|
||||
}
|
||||
|
||||
#endif /* !CONFIG_DYNAMIC_FTRACE */
|
||||
|
||||
/* Add a function return address to the trace stack on thread info.*/
|
||||
static int push_return_trace(unsigned long ret, unsigned long long time,
|
||||
unsigned long func)
|
||||
unsigned long func, int *depth)
|
||||
{
|
||||
int index;
|
||||
|
||||
@ -365,21 +409,22 @@ static int push_return_trace(unsigned long ret, unsigned long long time,
|
||||
current->ret_stack[index].ret = ret;
|
||||
current->ret_stack[index].func = func;
|
||||
current->ret_stack[index].calltime = time;
|
||||
*depth = index;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Retrieve a function return address to the trace stack on thread info.*/
|
||||
static void pop_return_trace(unsigned long *ret, unsigned long long *time,
|
||||
unsigned long *func, unsigned long *overrun)
|
||||
static void pop_return_trace(struct ftrace_graph_ret *trace, unsigned long *ret)
|
||||
{
|
||||
int index;
|
||||
|
||||
index = current->curr_ret_stack;
|
||||
*ret = current->ret_stack[index].ret;
|
||||
*func = current->ret_stack[index].func;
|
||||
*time = current->ret_stack[index].calltime;
|
||||
*overrun = atomic_read(¤t->trace_overrun);
|
||||
trace->func = current->ret_stack[index].func;
|
||||
trace->calltime = current->ret_stack[index].calltime;
|
||||
trace->overrun = atomic_read(¤t->trace_overrun);
|
||||
trace->depth = index;
|
||||
current->curr_ret_stack--;
|
||||
}
|
||||
|
||||
@ -389,13 +434,14 @@ static void pop_return_trace(unsigned long *ret, unsigned long long *time,
|
||||
*/
|
||||
unsigned long ftrace_return_to_handler(void)
|
||||
{
|
||||
struct ftrace_retfunc trace;
|
||||
pop_return_trace(&trace.ret, &trace.calltime, &trace.func,
|
||||
&trace.overrun);
|
||||
trace.rettime = cpu_clock(raw_smp_processor_id());
|
||||
ftrace_function_return(&trace);
|
||||
struct ftrace_graph_ret trace;
|
||||
unsigned long ret;
|
||||
|
||||
return trace.ret;
|
||||
pop_return_trace(&trace, &ret);
|
||||
trace.rettime = cpu_clock(raw_smp_processor_id());
|
||||
ftrace_graph_return(&trace);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -407,6 +453,7 @@ void prepare_ftrace_return(unsigned long *parent, unsigned long self_addr)
|
||||
unsigned long old;
|
||||
unsigned long long calltime;
|
||||
int faulted;
|
||||
struct ftrace_graph_ent trace;
|
||||
unsigned long return_hooker = (unsigned long)
|
||||
&return_to_handler;
|
||||
|
||||
@ -440,20 +487,27 @@ void prepare_ftrace_return(unsigned long *parent, unsigned long self_addr)
|
||||
);
|
||||
|
||||
if (WARN_ON(faulted)) {
|
||||
unregister_ftrace_return();
|
||||
unregister_ftrace_graph();
|
||||
return;
|
||||
}
|
||||
|
||||
if (WARN_ON(!__kernel_text_address(old))) {
|
||||
unregister_ftrace_return();
|
||||
unregister_ftrace_graph();
|
||||
*parent = old;
|
||||
return;
|
||||
}
|
||||
|
||||
calltime = cpu_clock(raw_smp_processor_id());
|
||||
|
||||
if (push_return_trace(old, calltime, self_addr) == -EBUSY)
|
||||
if (push_return_trace(old, calltime,
|
||||
self_addr, &trace.depth) == -EBUSY) {
|
||||
*parent = old;
|
||||
return;
|
||||
}
|
||||
|
||||
trace.func = self_addr;
|
||||
ftrace_graph_entry(&trace);
|
||||
|
||||
}
|
||||
|
||||
#endif /* CONFIG_FUNCTION_RET_TRACER */
|
||||
#endif /* CONFIG_FUNCTION_GRAPH_TRACER */
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include <linux/module.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/clockchips.h>
|
||||
#include <linux/ftrace.h>
|
||||
#include <asm/system.h>
|
||||
|
||||
unsigned long idle_halt;
|
||||
@ -100,6 +101,9 @@ static inline int hlt_use_halt(void)
|
||||
void default_idle(void)
|
||||
{
|
||||
if (hlt_use_halt()) {
|
||||
struct power_trace it;
|
||||
|
||||
trace_power_start(&it, POWER_CSTATE, 1);
|
||||
current_thread_info()->status &= ~TS_POLLING;
|
||||
/*
|
||||
* TS_POLLING-cleared state must be visible before we
|
||||
@ -112,6 +116,7 @@ void default_idle(void)
|
||||
else
|
||||
local_irq_enable();
|
||||
current_thread_info()->status |= TS_POLLING;
|
||||
trace_power_end(&it);
|
||||
} else {
|
||||
local_irq_enable();
|
||||
/* loop is done by the caller */
|
||||
@ -154,24 +159,31 @@ EXPORT_SYMBOL_GPL(cpu_idle_wait);
|
||||
*/
|
||||
void mwait_idle_with_hints(unsigned long ax, unsigned long cx)
|
||||
{
|
||||
struct power_trace it;
|
||||
|
||||
trace_power_start(&it, POWER_CSTATE, (ax>>4)+1);
|
||||
if (!need_resched()) {
|
||||
__monitor((void *)¤t_thread_info()->flags, 0, 0);
|
||||
smp_mb();
|
||||
if (!need_resched())
|
||||
__mwait(ax, cx);
|
||||
}
|
||||
trace_power_end(&it);
|
||||
}
|
||||
|
||||
/* Default MONITOR/MWAIT with no hints, used for default C1 state */
|
||||
static void mwait_idle(void)
|
||||
{
|
||||
struct power_trace it;
|
||||
if (!need_resched()) {
|
||||
trace_power_start(&it, POWER_CSTATE, 1);
|
||||
__monitor((void *)¤t_thread_info()->flags, 0, 0);
|
||||
smp_mb();
|
||||
if (!need_resched())
|
||||
__sti_mwait(0, 0);
|
||||
else
|
||||
local_irq_enable();
|
||||
trace_power_end(&it);
|
||||
} else
|
||||
local_irq_enable();
|
||||
}
|
||||
@ -183,9 +195,13 @@ static void mwait_idle(void)
|
||||
*/
|
||||
static void poll_idle(void)
|
||||
{
|
||||
struct power_trace it;
|
||||
|
||||
trace_power_start(&it, POWER_CSTATE, 0);
|
||||
local_irq_enable();
|
||||
while (!need_resched())
|
||||
cpu_relax();
|
||||
trace_power_end(&it);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -115,8 +115,13 @@ extern int ftrace_update_ftrace_func(ftrace_func_t func);
|
||||
extern void ftrace_caller(void);
|
||||
extern void ftrace_call(void);
|
||||
extern void mcount_call(void);
|
||||
#ifdef CONFIG_FUNCTION_RET_TRACER
|
||||
extern void ftrace_return_caller(void);
|
||||
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
|
||||
extern void ftrace_graph_caller(void);
|
||||
extern int ftrace_enable_ftrace_graph_caller(void);
|
||||
extern int ftrace_disable_ftrace_graph_caller(void);
|
||||
#else
|
||||
static inline int ftrace_enable_ftrace_graph_caller(void) { return 0; }
|
||||
static inline int ftrace_disable_ftrace_graph_caller(void) { return 0; }
|
||||
#endif
|
||||
|
||||
/**
|
||||
@ -311,35 +316,77 @@ ftrace_init_module(struct module *mod,
|
||||
unsigned long *start, unsigned long *end) { }
|
||||
#endif
|
||||
|
||||
enum {
|
||||
POWER_NONE = 0,
|
||||
POWER_CSTATE = 1,
|
||||
POWER_PSTATE = 2,
|
||||
};
|
||||
|
||||
struct power_trace {
|
||||
#ifdef CONFIG_POWER_TRACER
|
||||
ktime_t stamp;
|
||||
ktime_t end;
|
||||
int type;
|
||||
int state;
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifdef CONFIG_POWER_TRACER
|
||||
extern void trace_power_start(struct power_trace *it, unsigned int type,
|
||||
unsigned int state);
|
||||
extern void trace_power_mark(struct power_trace *it, unsigned int type,
|
||||
unsigned int state);
|
||||
extern void trace_power_end(struct power_trace *it);
|
||||
#else
|
||||
static inline void trace_power_start(struct power_trace *it, unsigned int type,
|
||||
unsigned int state) { }
|
||||
static inline void trace_power_mark(struct power_trace *it, unsigned int type,
|
||||
unsigned int state) { }
|
||||
static inline void trace_power_end(struct power_trace *it) { }
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* Structure that defines an entry function trace.
|
||||
*/
|
||||
struct ftrace_graph_ent {
|
||||
unsigned long func; /* Current function */
|
||||
int depth;
|
||||
};
|
||||
|
||||
/*
|
||||
* Structure that defines a return function trace.
|
||||
*/
|
||||
struct ftrace_retfunc {
|
||||
unsigned long ret; /* Return address */
|
||||
struct ftrace_graph_ret {
|
||||
unsigned long func; /* Current function */
|
||||
unsigned long long calltime;
|
||||
unsigned long long rettime;
|
||||
/* Number of functions that overran the depth limit for current task */
|
||||
unsigned long overrun;
|
||||
int depth;
|
||||
};
|
||||
|
||||
#ifdef CONFIG_FUNCTION_RET_TRACER
|
||||
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
|
||||
#define FTRACE_RETFUNC_DEPTH 50
|
||||
#define FTRACE_RETSTACK_ALLOC_SIZE 32
|
||||
/* Type of a callback handler of tracing return function */
|
||||
typedef void (*trace_function_return_t)(struct ftrace_retfunc *);
|
||||
/* Type of the callback handlers for tracing function graph*/
|
||||
typedef void (*trace_func_graph_ret_t)(struct ftrace_graph_ret *); /* return */
|
||||
typedef void (*trace_func_graph_ent_t)(struct ftrace_graph_ent *); /* entry */
|
||||
|
||||
extern int register_ftrace_return(trace_function_return_t func);
|
||||
/* The current handler in use */
|
||||
extern trace_function_return_t ftrace_function_return;
|
||||
extern void unregister_ftrace_return(void);
|
||||
extern int register_ftrace_graph(trace_func_graph_ret_t retfunc,
|
||||
trace_func_graph_ent_t entryfunc);
|
||||
|
||||
extern void ftrace_retfunc_init_task(struct task_struct *t);
|
||||
extern void ftrace_retfunc_exit_task(struct task_struct *t);
|
||||
/* The current handlers in use */
|
||||
extern trace_func_graph_ret_t ftrace_graph_return;
|
||||
extern trace_func_graph_ent_t ftrace_graph_entry;
|
||||
|
||||
extern void unregister_ftrace_graph(void);
|
||||
|
||||
extern void ftrace_graph_init_task(struct task_struct *t);
|
||||
extern void ftrace_graph_exit_task(struct task_struct *t);
|
||||
#else
|
||||
static inline void ftrace_retfunc_init_task(struct task_struct *t) { }
|
||||
static inline void ftrace_retfunc_exit_task(struct task_struct *t) { }
|
||||
static inline void ftrace_graph_init_task(struct task_struct *t) { }
|
||||
static inline void ftrace_graph_exit_task(struct task_struct *t) { }
|
||||
#endif
|
||||
|
||||
#endif /* _LINUX_FTRACE_H */
|
||||
|
@ -2,7 +2,7 @@
|
||||
#define _LINUX_FTRACE_IRQ_H
|
||||
|
||||
|
||||
#if defined(CONFIG_DYNAMIC_FTRACE) || defined(CONFIG_FUNCTION_RET_TRACER)
|
||||
#if defined(CONFIG_DYNAMIC_FTRACE) || defined(CONFIG_FUNCTION_GRAPH_TRACER)
|
||||
extern void ftrace_nmi_enter(void);
|
||||
extern void ftrace_nmi_exit(void);
|
||||
#else
|
||||
|
@ -1365,7 +1365,7 @@ struct task_struct {
|
||||
unsigned long default_timer_slack_ns;
|
||||
|
||||
struct list_head *scm_work_list;
|
||||
#ifdef CONFIG_FUNCTION_RET_TRACER
|
||||
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
|
||||
/* Index of current stored adress in ret_stack */
|
||||
int curr_ret_stack;
|
||||
/* Stack of return addresses for return function tracing */
|
||||
|
@ -21,7 +21,7 @@ CFLAGS_REMOVE_cgroup-debug.o = -pg
|
||||
CFLAGS_REMOVE_sched_clock.o = -pg
|
||||
CFLAGS_REMOVE_sched.o = -pg
|
||||
endif
|
||||
ifdef CONFIG_FUNCTION_RET_TRACER
|
||||
ifdef CONFIG_FUNCTION_GRAPH_TRACER
|
||||
CFLAGS_REMOVE_extable.o = -pg # For __kernel_text_address()
|
||||
CFLAGS_REMOVE_module.o = -pg # For __module_text_address()
|
||||
endif
|
||||
|
@ -140,7 +140,7 @@ void free_task(struct task_struct *tsk)
|
||||
prop_local_destroy_single(&tsk->dirties);
|
||||
free_thread_info(tsk->stack);
|
||||
rt_mutex_debug_task_free(tsk);
|
||||
ftrace_retfunc_exit_task(tsk);
|
||||
ftrace_graph_exit_task(tsk);
|
||||
free_task_struct(tsk);
|
||||
}
|
||||
EXPORT_SYMBOL(free_task);
|
||||
@ -1271,7 +1271,7 @@ static struct task_struct *copy_process(unsigned long clone_flags,
|
||||
total_forks++;
|
||||
spin_unlock(¤t->sighand->siglock);
|
||||
write_unlock_irq(&tasklist_lock);
|
||||
ftrace_retfunc_init_task(p);
|
||||
ftrace_graph_init_task(p);
|
||||
proc_fork_connector(p);
|
||||
cgroup_post_fork(p);
|
||||
return p;
|
||||
|
@ -5901,7 +5901,7 @@ void __cpuinit init_idle(struct task_struct *idle, int cpu)
|
||||
* The idle tasks have their own, simple scheduling class:
|
||||
*/
|
||||
idle->sched_class = &idle_sched_class;
|
||||
ftrace_retfunc_init_task(idle);
|
||||
ftrace_graph_init_task(idle);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -12,7 +12,7 @@ config NOP_TRACER
|
||||
config HAVE_FUNCTION_TRACER
|
||||
bool
|
||||
|
||||
config HAVE_FUNCTION_RET_TRACER
|
||||
config HAVE_FUNCTION_GRAPH_TRACER
|
||||
bool
|
||||
|
||||
config HAVE_FUNCTION_TRACE_MCOUNT_TEST
|
||||
@ -63,15 +63,18 @@ config FUNCTION_TRACER
|
||||
(the bootup default), then the overhead of the instructions is very
|
||||
small and not measurable even in micro-benchmarks.
|
||||
|
||||
config FUNCTION_RET_TRACER
|
||||
bool "Kernel Function return Tracer"
|
||||
depends on HAVE_FUNCTION_RET_TRACER
|
||||
config FUNCTION_GRAPH_TRACER
|
||||
bool "Kernel Function Graph Tracer"
|
||||
depends on HAVE_FUNCTION_GRAPH_TRACER
|
||||
depends on FUNCTION_TRACER
|
||||
help
|
||||
Enable the kernel to trace a function at its return.
|
||||
It's first purpose is to trace the duration of functions.
|
||||
This is done by setting the current return address on the thread
|
||||
info structure of the current task.
|
||||
Enable the kernel to trace a function at both its return
|
||||
and its entry.
|
||||
It's first purpose is to trace the duration of functions and
|
||||
draw a call graph for each thread with some informations like
|
||||
the return value.
|
||||
This is done by setting the current return address on the current
|
||||
task structure into a stack of calls.
|
||||
|
||||
config IRQSOFF_TRACER
|
||||
bool "Interrupts-off Latency Tracer"
|
||||
@ -217,6 +220,17 @@ config BRANCH_TRACER
|
||||
|
||||
Say N if unsure.
|
||||
|
||||
config POWER_TRACER
|
||||
bool "Trace power consumption behavior"
|
||||
depends on DEBUG_KERNEL
|
||||
depends on X86
|
||||
select TRACING
|
||||
help
|
||||
This tracer helps developers to analyze and optimize the kernels
|
||||
power management decisions, specifically the C-state and P-state
|
||||
behavior.
|
||||
|
||||
|
||||
config STACK_TRACER
|
||||
bool "Trace max stack"
|
||||
depends on HAVE_FUNCTION_TRACER
|
||||
|
@ -29,8 +29,9 @@ obj-$(CONFIG_NOP_TRACER) += trace_nop.o
|
||||
obj-$(CONFIG_STACK_TRACER) += trace_stack.o
|
||||
obj-$(CONFIG_MMIOTRACE) += trace_mmiotrace.o
|
||||
obj-$(CONFIG_BOOT_TRACER) += trace_boot.o
|
||||
obj-$(CONFIG_FUNCTION_RET_TRACER) += trace_functions_return.o
|
||||
obj-$(CONFIG_FUNCTION_GRAPH_TRACER) += trace_functions_graph.o
|
||||
obj-$(CONFIG_TRACE_BRANCH_PROFILING) += trace_branch.o
|
||||
obj-$(CONFIG_BTS_TRACER) += trace_bts.o
|
||||
obj-$(CONFIG_POWER_TRACER) += trace_power.o
|
||||
|
||||
libftrace-y := ftrace.o
|
||||
|
@ -47,12 +47,12 @@
|
||||
int ftrace_enabled __read_mostly;
|
||||
static int last_ftrace_enabled;
|
||||
|
||||
/* ftrace_pid_trace >= 0 will only trace threads with this pid */
|
||||
static int ftrace_pid_trace = -1;
|
||||
|
||||
/* Quick disabling of function tracer. */
|
||||
int function_trace_stop;
|
||||
|
||||
/* By default, current tracing type is normal tracing. */
|
||||
enum ftrace_tracing_type_t ftrace_tracing_type = FTRACE_TYPE_ENTER;
|
||||
|
||||
/*
|
||||
* ftrace_disabled is set when an anomaly is discovered.
|
||||
* ftrace_disabled is much stronger than ftrace_enabled.
|
||||
@ -61,6 +61,7 @@ static int ftrace_disabled __read_mostly;
|
||||
|
||||
static DEFINE_SPINLOCK(ftrace_lock);
|
||||
static DEFINE_MUTEX(ftrace_sysctl_lock);
|
||||
static DEFINE_MUTEX(ftrace_start_lock);
|
||||
|
||||
static struct ftrace_ops ftrace_list_end __read_mostly =
|
||||
{
|
||||
@ -70,6 +71,7 @@ static struct ftrace_ops ftrace_list_end __read_mostly =
|
||||
static struct ftrace_ops *ftrace_list __read_mostly = &ftrace_list_end;
|
||||
ftrace_func_t ftrace_trace_function __read_mostly = ftrace_stub;
|
||||
ftrace_func_t __ftrace_trace_function __read_mostly = ftrace_stub;
|
||||
ftrace_func_t ftrace_pid_function __read_mostly = ftrace_stub;
|
||||
|
||||
static void ftrace_list_func(unsigned long ip, unsigned long parent_ip)
|
||||
{
|
||||
@ -86,6 +88,21 @@ static void ftrace_list_func(unsigned long ip, unsigned long parent_ip)
|
||||
};
|
||||
}
|
||||
|
||||
static void ftrace_pid_func(unsigned long ip, unsigned long parent_ip)
|
||||
{
|
||||
if (current->pid != ftrace_pid_trace)
|
||||
return;
|
||||
|
||||
ftrace_pid_function(ip, parent_ip);
|
||||
}
|
||||
|
||||
static void set_ftrace_pid_function(ftrace_func_t func)
|
||||
{
|
||||
/* do not set ftrace_pid_function to itself! */
|
||||
if (func != ftrace_pid_func)
|
||||
ftrace_pid_function = func;
|
||||
}
|
||||
|
||||
/**
|
||||
* clear_ftrace_function - reset the ftrace function
|
||||
*
|
||||
@ -96,6 +113,7 @@ void clear_ftrace_function(void)
|
||||
{
|
||||
ftrace_trace_function = ftrace_stub;
|
||||
__ftrace_trace_function = ftrace_stub;
|
||||
ftrace_pid_function = ftrace_stub;
|
||||
}
|
||||
|
||||
#ifndef CONFIG_HAVE_FUNCTION_TRACE_MCOUNT_TEST
|
||||
@ -128,20 +146,26 @@ static int __register_ftrace_function(struct ftrace_ops *ops)
|
||||
ftrace_list = ops;
|
||||
|
||||
if (ftrace_enabled) {
|
||||
ftrace_func_t func;
|
||||
|
||||
if (ops->next == &ftrace_list_end)
|
||||
func = ops->func;
|
||||
else
|
||||
func = ftrace_list_func;
|
||||
|
||||
if (ftrace_pid_trace >= 0) {
|
||||
set_ftrace_pid_function(func);
|
||||
func = ftrace_pid_func;
|
||||
}
|
||||
|
||||
/*
|
||||
* For one func, simply call it directly.
|
||||
* For more than one func, call the chain.
|
||||
*/
|
||||
#ifdef CONFIG_HAVE_FUNCTION_TRACE_MCOUNT_TEST
|
||||
if (ops->next == &ftrace_list_end)
|
||||
ftrace_trace_function = ops->func;
|
||||
else
|
||||
ftrace_trace_function = ftrace_list_func;
|
||||
ftrace_trace_function = func;
|
||||
#else
|
||||
if (ops->next == &ftrace_list_end)
|
||||
__ftrace_trace_function = ops->func;
|
||||
else
|
||||
__ftrace_trace_function = ftrace_list_func;
|
||||
__ftrace_trace_function = func;
|
||||
ftrace_trace_function = ftrace_test_stop_func;
|
||||
#endif
|
||||
}
|
||||
@ -182,8 +206,19 @@ static int __unregister_ftrace_function(struct ftrace_ops *ops)
|
||||
|
||||
if (ftrace_enabled) {
|
||||
/* If we only have one func left, then call that directly */
|
||||
if (ftrace_list->next == &ftrace_list_end)
|
||||
ftrace_trace_function = ftrace_list->func;
|
||||
if (ftrace_list->next == &ftrace_list_end) {
|
||||
ftrace_func_t func = ftrace_list->func;
|
||||
|
||||
if (ftrace_pid_trace >= 0) {
|
||||
set_ftrace_pid_function(func);
|
||||
func = ftrace_pid_func;
|
||||
}
|
||||
#ifdef CONFIG_HAVE_FUNCTION_TRACE_MCOUNT_TEST
|
||||
ftrace_trace_function = func;
|
||||
#else
|
||||
__ftrace_trace_function = func;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
@ -192,6 +227,38 @@ static int __unregister_ftrace_function(struct ftrace_ops *ops)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void ftrace_update_pid_func(void)
|
||||
{
|
||||
ftrace_func_t func;
|
||||
|
||||
/* should not be called from interrupt context */
|
||||
spin_lock(&ftrace_lock);
|
||||
|
||||
if (ftrace_trace_function == ftrace_stub)
|
||||
goto out;
|
||||
|
||||
func = ftrace_trace_function;
|
||||
|
||||
if (ftrace_pid_trace >= 0) {
|
||||
set_ftrace_pid_function(func);
|
||||
func = ftrace_pid_func;
|
||||
} else {
|
||||
if (func != ftrace_pid_func)
|
||||
goto out;
|
||||
|
||||
set_ftrace_pid_function(func);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_HAVE_FUNCTION_TRACE_MCOUNT_TEST
|
||||
ftrace_trace_function = func;
|
||||
#else
|
||||
__ftrace_trace_function = func;
|
||||
#endif
|
||||
|
||||
out:
|
||||
spin_unlock(&ftrace_lock);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_DYNAMIC_FTRACE
|
||||
#ifndef CONFIG_FTRACE_MCOUNT_RECORD
|
||||
# error Dynamic ftrace depends on MCOUNT_RECORD
|
||||
@ -211,6 +278,8 @@ enum {
|
||||
FTRACE_UPDATE_TRACE_FUNC = (1 << 2),
|
||||
FTRACE_ENABLE_MCOUNT = (1 << 3),
|
||||
FTRACE_DISABLE_MCOUNT = (1 << 4),
|
||||
FTRACE_START_FUNC_RET = (1 << 5),
|
||||
FTRACE_STOP_FUNC_RET = (1 << 6),
|
||||
};
|
||||
|
||||
static int ftrace_filtered;
|
||||
@ -395,14 +464,7 @@ __ftrace_replace_code(struct dyn_ftrace *rec, int enable)
|
||||
unsigned long ip, fl;
|
||||
unsigned long ftrace_addr;
|
||||
|
||||
#ifdef CONFIG_FUNCTION_RET_TRACER
|
||||
if (ftrace_tracing_type == FTRACE_TYPE_ENTER)
|
||||
ftrace_addr = (unsigned long)ftrace_caller;
|
||||
else
|
||||
ftrace_addr = (unsigned long)ftrace_return_caller;
|
||||
#else
|
||||
ftrace_addr = (unsigned long)ftrace_caller;
|
||||
#endif
|
||||
|
||||
ip = rec->ip;
|
||||
|
||||
@ -535,6 +597,11 @@ static int __ftrace_modify_code(void *data)
|
||||
if (*command & FTRACE_UPDATE_TRACE_FUNC)
|
||||
ftrace_update_ftrace_func(ftrace_trace_function);
|
||||
|
||||
if (*command & FTRACE_START_FUNC_RET)
|
||||
ftrace_enable_ftrace_graph_caller();
|
||||
else if (*command & FTRACE_STOP_FUNC_RET)
|
||||
ftrace_disable_ftrace_graph_caller();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -545,12 +612,22 @@ static void ftrace_run_update_code(int command)
|
||||
|
||||
static ftrace_func_t saved_ftrace_func;
|
||||
static int ftrace_start_up;
|
||||
static DEFINE_MUTEX(ftrace_start_lock);
|
||||
|
||||
static void ftrace_startup(void)
|
||||
static void ftrace_startup_enable(int command)
|
||||
{
|
||||
int command = 0;
|
||||
if (saved_ftrace_func != ftrace_trace_function) {
|
||||
saved_ftrace_func = ftrace_trace_function;
|
||||
command |= FTRACE_UPDATE_TRACE_FUNC;
|
||||
}
|
||||
|
||||
if (!command || !ftrace_enabled)
|
||||
return;
|
||||
|
||||
ftrace_run_update_code(command);
|
||||
}
|
||||
|
||||
static void ftrace_startup(int command)
|
||||
{
|
||||
if (unlikely(ftrace_disabled))
|
||||
return;
|
||||
|
||||
@ -558,23 +635,13 @@ static void ftrace_startup(void)
|
||||
ftrace_start_up++;
|
||||
command |= FTRACE_ENABLE_CALLS;
|
||||
|
||||
if (saved_ftrace_func != ftrace_trace_function) {
|
||||
saved_ftrace_func = ftrace_trace_function;
|
||||
command |= FTRACE_UPDATE_TRACE_FUNC;
|
||||
}
|
||||
ftrace_startup_enable(command);
|
||||
|
||||
if (!command || !ftrace_enabled)
|
||||
goto out;
|
||||
|
||||
ftrace_run_update_code(command);
|
||||
out:
|
||||
mutex_unlock(&ftrace_start_lock);
|
||||
}
|
||||
|
||||
static void ftrace_shutdown(void)
|
||||
static void ftrace_shutdown(int command)
|
||||
{
|
||||
int command = 0;
|
||||
|
||||
if (unlikely(ftrace_disabled))
|
||||
return;
|
||||
|
||||
@ -1262,13 +1329,10 @@ static struct file_operations ftrace_notrace_fops = {
|
||||
.release = ftrace_notrace_release,
|
||||
};
|
||||
|
||||
static __init int ftrace_init_debugfs(void)
|
||||
static __init int ftrace_init_dyn_debugfs(struct dentry *d_tracer)
|
||||
{
|
||||
struct dentry *d_tracer;
|
||||
struct dentry *entry;
|
||||
|
||||
d_tracer = tracing_init_dentry();
|
||||
|
||||
entry = debugfs_create_file("available_filter_functions", 0444,
|
||||
d_tracer, NULL, &ftrace_avail_fops);
|
||||
if (!entry)
|
||||
@ -1295,8 +1359,6 @@ static __init int ftrace_init_debugfs(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
fs_initcall(ftrace_init_debugfs);
|
||||
|
||||
static int ftrace_convert_nops(struct module *mod,
|
||||
unsigned long *start,
|
||||
unsigned long *end)
|
||||
@ -1382,12 +1444,101 @@ static int __init ftrace_nodyn_init(void)
|
||||
}
|
||||
device_initcall(ftrace_nodyn_init);
|
||||
|
||||
# define ftrace_startup() do { } while (0)
|
||||
# define ftrace_shutdown() do { } while (0)
|
||||
static inline int ftrace_init_dyn_debugfs(struct dentry *d_tracer) { return 0; }
|
||||
static inline void ftrace_startup_enable(int command) { }
|
||||
/* Keep as macros so we do not need to define the commands */
|
||||
# define ftrace_startup(command) do { } while (0)
|
||||
# define ftrace_shutdown(command) do { } while (0)
|
||||
# define ftrace_startup_sysctl() do { } while (0)
|
||||
# define ftrace_shutdown_sysctl() do { } while (0)
|
||||
#endif /* CONFIG_DYNAMIC_FTRACE */
|
||||
|
||||
static ssize_t
|
||||
ftrace_pid_read(struct file *file, char __user *ubuf,
|
||||
size_t cnt, loff_t *ppos)
|
||||
{
|
||||
char buf[64];
|
||||
int r;
|
||||
|
||||
if (ftrace_pid_trace >= 0)
|
||||
r = sprintf(buf, "%u\n", ftrace_pid_trace);
|
||||
else
|
||||
r = sprintf(buf, "no pid\n");
|
||||
|
||||
return simple_read_from_buffer(ubuf, cnt, ppos, buf, r);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
ftrace_pid_write(struct file *filp, const char __user *ubuf,
|
||||
size_t cnt, loff_t *ppos)
|
||||
{
|
||||
char buf[64];
|
||||
long val;
|
||||
int ret;
|
||||
|
||||
if (cnt >= sizeof(buf))
|
||||
return -EINVAL;
|
||||
|
||||
if (copy_from_user(&buf, ubuf, cnt))
|
||||
return -EFAULT;
|
||||
|
||||
buf[cnt] = 0;
|
||||
|
||||
ret = strict_strtol(buf, 10, &val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
mutex_lock(&ftrace_start_lock);
|
||||
if (ret < 0) {
|
||||
/* disable pid tracing */
|
||||
if (ftrace_pid_trace < 0)
|
||||
goto out;
|
||||
ftrace_pid_trace = -1;
|
||||
|
||||
} else {
|
||||
|
||||
if (ftrace_pid_trace == val)
|
||||
goto out;
|
||||
|
||||
ftrace_pid_trace = val;
|
||||
}
|
||||
|
||||
/* update the function call */
|
||||
ftrace_update_pid_func();
|
||||
ftrace_startup_enable(0);
|
||||
|
||||
out:
|
||||
mutex_unlock(&ftrace_start_lock);
|
||||
|
||||
return cnt;
|
||||
}
|
||||
|
||||
static struct file_operations ftrace_pid_fops = {
|
||||
.read = ftrace_pid_read,
|
||||
.write = ftrace_pid_write,
|
||||
};
|
||||
|
||||
static __init int ftrace_init_debugfs(void)
|
||||
{
|
||||
struct dentry *d_tracer;
|
||||
struct dentry *entry;
|
||||
|
||||
d_tracer = tracing_init_dentry();
|
||||
if (!d_tracer)
|
||||
return 0;
|
||||
|
||||
ftrace_init_dyn_debugfs(d_tracer);
|
||||
|
||||
entry = debugfs_create_file("set_ftrace_pid", 0644, d_tracer,
|
||||
NULL, &ftrace_pid_fops);
|
||||
if (!entry)
|
||||
pr_warning("Could not create debugfs "
|
||||
"'set_ftrace_pid' entry\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
fs_initcall(ftrace_init_debugfs);
|
||||
|
||||
/**
|
||||
* ftrace_kill - kill ftrace
|
||||
*
|
||||
@ -1422,15 +1573,9 @@ int register_ftrace_function(struct ftrace_ops *ops)
|
||||
|
||||
mutex_lock(&ftrace_sysctl_lock);
|
||||
|
||||
if (ftrace_tracing_type == FTRACE_TYPE_RETURN) {
|
||||
ret = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = __register_ftrace_function(ops);
|
||||
ftrace_startup();
|
||||
ftrace_startup(0);
|
||||
|
||||
out:
|
||||
mutex_unlock(&ftrace_sysctl_lock);
|
||||
return ret;
|
||||
}
|
||||
@ -1447,7 +1592,7 @@ int unregister_ftrace_function(struct ftrace_ops *ops)
|
||||
|
||||
mutex_lock(&ftrace_sysctl_lock);
|
||||
ret = __unregister_ftrace_function(ops);
|
||||
ftrace_shutdown();
|
||||
ftrace_shutdown(0);
|
||||
mutex_unlock(&ftrace_sysctl_lock);
|
||||
|
||||
return ret;
|
||||
@ -1496,14 +1641,15 @@ ftrace_enable_sysctl(struct ctl_table *table, int write,
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_FUNCTION_RET_TRACER
|
||||
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
|
||||
|
||||
static atomic_t ftrace_retfunc_active;
|
||||
|
||||
/* The callback that hooks the return of a function */
|
||||
trace_function_return_t ftrace_function_return =
|
||||
(trace_function_return_t)ftrace_stub;
|
||||
static atomic_t ftrace_graph_active;
|
||||
|
||||
/* The callbacks that hook a function */
|
||||
trace_func_graph_ret_t ftrace_graph_return =
|
||||
(trace_func_graph_ret_t)ftrace_stub;
|
||||
trace_func_graph_ent_t ftrace_graph_entry =
|
||||
(trace_func_graph_ent_t)ftrace_stub;
|
||||
|
||||
/* Try to assign a return stack array on FTRACE_RETSTACK_ALLOC_SIZE tasks. */
|
||||
static int alloc_retstack_tasklist(struct ftrace_ret_stack **ret_stack_list)
|
||||
@ -1549,7 +1695,7 @@ free:
|
||||
}
|
||||
|
||||
/* Allocate a return stack for each task */
|
||||
static int start_return_tracing(void)
|
||||
static int start_graph_tracing(void)
|
||||
{
|
||||
struct ftrace_ret_stack **ret_stack_list;
|
||||
int ret;
|
||||
@ -1569,52 +1715,46 @@ static int start_return_tracing(void)
|
||||
return ret;
|
||||
}
|
||||
|
||||
int register_ftrace_return(trace_function_return_t func)
|
||||
int register_ftrace_graph(trace_func_graph_ret_t retfunc,
|
||||
trace_func_graph_ent_t entryfunc)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&ftrace_sysctl_lock);
|
||||
|
||||
/*
|
||||
* Don't launch return tracing if normal function
|
||||
* tracing is already running.
|
||||
*/
|
||||
if (ftrace_trace_function != ftrace_stub) {
|
||||
ret = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
atomic_inc(&ftrace_retfunc_active);
|
||||
ret = start_return_tracing();
|
||||
atomic_inc(&ftrace_graph_active);
|
||||
ret = start_graph_tracing();
|
||||
if (ret) {
|
||||
atomic_dec(&ftrace_retfunc_active);
|
||||
atomic_dec(&ftrace_graph_active);
|
||||
goto out;
|
||||
}
|
||||
ftrace_tracing_type = FTRACE_TYPE_RETURN;
|
||||
ftrace_function_return = func;
|
||||
ftrace_startup();
|
||||
|
||||
ftrace_graph_return = retfunc;
|
||||
ftrace_graph_entry = entryfunc;
|
||||
|
||||
ftrace_startup(FTRACE_START_FUNC_RET);
|
||||
|
||||
out:
|
||||
mutex_unlock(&ftrace_sysctl_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void unregister_ftrace_return(void)
|
||||
void unregister_ftrace_graph(void)
|
||||
{
|
||||
mutex_lock(&ftrace_sysctl_lock);
|
||||
|
||||
atomic_dec(&ftrace_retfunc_active);
|
||||
ftrace_function_return = (trace_function_return_t)ftrace_stub;
|
||||
ftrace_shutdown();
|
||||
/* Restore normal tracing type */
|
||||
ftrace_tracing_type = FTRACE_TYPE_ENTER;
|
||||
atomic_dec(&ftrace_graph_active);
|
||||
ftrace_graph_return = (trace_func_graph_ret_t)ftrace_stub;
|
||||
ftrace_graph_entry = (trace_func_graph_ent_t)ftrace_stub;
|
||||
ftrace_shutdown(FTRACE_STOP_FUNC_RET);
|
||||
|
||||
mutex_unlock(&ftrace_sysctl_lock);
|
||||
}
|
||||
|
||||
/* Allocate a return stack for newly created task */
|
||||
void ftrace_retfunc_init_task(struct task_struct *t)
|
||||
void ftrace_graph_init_task(struct task_struct *t)
|
||||
{
|
||||
if (atomic_read(&ftrace_retfunc_active)) {
|
||||
if (atomic_read(&ftrace_graph_active)) {
|
||||
t->ret_stack = kmalloc(FTRACE_RETFUNC_DEPTH
|
||||
* sizeof(struct ftrace_ret_stack),
|
||||
GFP_KERNEL);
|
||||
@ -1626,7 +1766,7 @@ void ftrace_retfunc_init_task(struct task_struct *t)
|
||||
t->ret_stack = NULL;
|
||||
}
|
||||
|
||||
void ftrace_retfunc_exit_task(struct task_struct *t)
|
||||
void ftrace_graph_exit_task(struct task_struct *t)
|
||||
{
|
||||
struct ftrace_ret_stack *ret_stack = t->ret_stack;
|
||||
|
||||
@ -1638,5 +1778,3 @@ void ftrace_retfunc_exit_task(struct task_struct *t)
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
@ -804,7 +804,7 @@ static void trace_save_cmdline(struct task_struct *tsk)
|
||||
spin_unlock(&trace_cmdline_lock);
|
||||
}
|
||||
|
||||
static char *trace_find_cmdline(int pid)
|
||||
char *trace_find_cmdline(int pid)
|
||||
{
|
||||
char *cmdline = "<...>";
|
||||
unsigned map;
|
||||
@ -878,15 +878,15 @@ trace_function(struct trace_array *tr, struct trace_array_cpu *data,
|
||||
ring_buffer_unlock_commit(tr->buffer, event, irq_flags);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_FUNCTION_RET_TRACER
|
||||
static void __trace_function_return(struct trace_array *tr,
|
||||
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
|
||||
static void __trace_graph_entry(struct trace_array *tr,
|
||||
struct trace_array_cpu *data,
|
||||
struct ftrace_retfunc *trace,
|
||||
struct ftrace_graph_ent *trace,
|
||||
unsigned long flags,
|
||||
int pc)
|
||||
{
|
||||
struct ring_buffer_event *event;
|
||||
struct ftrace_ret_entry *entry;
|
||||
struct ftrace_graph_ent_entry *entry;
|
||||
unsigned long irq_flags;
|
||||
|
||||
if (unlikely(local_read(&__get_cpu_var(ftrace_cpu_disabled))))
|
||||
@ -898,12 +898,32 @@ static void __trace_function_return(struct trace_array *tr,
|
||||
return;
|
||||
entry = ring_buffer_event_data(event);
|
||||
tracing_generic_entry_update(&entry->ent, flags, pc);
|
||||
entry->ent.type = TRACE_FN_RET;
|
||||
entry->ip = trace->func;
|
||||
entry->parent_ip = trace->ret;
|
||||
entry->rettime = trace->rettime;
|
||||
entry->calltime = trace->calltime;
|
||||
entry->overrun = trace->overrun;
|
||||
entry->ent.type = TRACE_GRAPH_ENT;
|
||||
entry->graph_ent = *trace;
|
||||
ring_buffer_unlock_commit(global_trace.buffer, event, irq_flags);
|
||||
}
|
||||
|
||||
static void __trace_graph_return(struct trace_array *tr,
|
||||
struct trace_array_cpu *data,
|
||||
struct ftrace_graph_ret *trace,
|
||||
unsigned long flags,
|
||||
int pc)
|
||||
{
|
||||
struct ring_buffer_event *event;
|
||||
struct ftrace_graph_ret_entry *entry;
|
||||
unsigned long irq_flags;
|
||||
|
||||
if (unlikely(local_read(&__get_cpu_var(ftrace_cpu_disabled))))
|
||||
return;
|
||||
|
||||
event = ring_buffer_lock_reserve(global_trace.buffer, sizeof(*entry),
|
||||
&irq_flags);
|
||||
if (!event)
|
||||
return;
|
||||
entry = ring_buffer_event_data(event);
|
||||
tracing_generic_entry_update(&entry->ent, flags, pc);
|
||||
entry->ent.type = TRACE_GRAPH_RET;
|
||||
entry->ret = *trace;
|
||||
ring_buffer_unlock_commit(global_trace.buffer, event, irq_flags);
|
||||
}
|
||||
#endif
|
||||
@ -1177,8 +1197,8 @@ function_trace_call(unsigned long ip, unsigned long parent_ip)
|
||||
local_irq_restore(flags);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_FUNCTION_RET_TRACER
|
||||
void trace_function_return(struct ftrace_retfunc *trace)
|
||||
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
|
||||
void trace_graph_entry(struct ftrace_graph_ent *trace)
|
||||
{
|
||||
struct trace_array *tr = &global_trace;
|
||||
struct trace_array_cpu *data;
|
||||
@ -1193,12 +1213,33 @@ void trace_function_return(struct ftrace_retfunc *trace)
|
||||
disabled = atomic_inc_return(&data->disabled);
|
||||
if (likely(disabled == 1)) {
|
||||
pc = preempt_count();
|
||||
__trace_function_return(tr, data, trace, flags, pc);
|
||||
__trace_graph_entry(tr, data, trace, flags, pc);
|
||||
}
|
||||
atomic_dec(&data->disabled);
|
||||
raw_local_irq_restore(flags);
|
||||
}
|
||||
#endif /* CONFIG_FUNCTION_RET_TRACER */
|
||||
|
||||
void trace_graph_return(struct ftrace_graph_ret *trace)
|
||||
{
|
||||
struct trace_array *tr = &global_trace;
|
||||
struct trace_array_cpu *data;
|
||||
unsigned long flags;
|
||||
long disabled;
|
||||
int cpu;
|
||||
int pc;
|
||||
|
||||
raw_local_irq_save(flags);
|
||||
cpu = raw_smp_processor_id();
|
||||
data = tr->data[cpu];
|
||||
disabled = atomic_inc_return(&data->disabled);
|
||||
if (likely(disabled == 1)) {
|
||||
pc = preempt_count();
|
||||
__trace_graph_return(tr, data, trace, flags, pc);
|
||||
}
|
||||
atomic_dec(&data->disabled);
|
||||
raw_local_irq_restore(flags);
|
||||
}
|
||||
#endif /* CONFIG_FUNCTION_GRAPH_TRACER */
|
||||
|
||||
static struct ftrace_ops trace_ops __read_mostly =
|
||||
{
|
||||
@ -2000,9 +2041,11 @@ static enum print_line_t print_trace_fmt(struct trace_iterator *iter)
|
||||
trace_seq_print_cont(s, iter);
|
||||
break;
|
||||
}
|
||||
case TRACE_FN_RET: {
|
||||
return print_return_function(iter);
|
||||
break;
|
||||
case TRACE_GRAPH_RET: {
|
||||
return print_graph_function(iter);
|
||||
}
|
||||
case TRACE_GRAPH_ENT: {
|
||||
return print_graph_function(iter);
|
||||
}
|
||||
case TRACE_BRANCH: {
|
||||
struct trace_branch *field;
|
||||
|
@ -25,9 +25,11 @@ enum trace_type {
|
||||
TRACE_BRANCH,
|
||||
TRACE_BOOT_CALL,
|
||||
TRACE_BOOT_RET,
|
||||
TRACE_FN_RET,
|
||||
TRACE_GRAPH_RET,
|
||||
TRACE_GRAPH_ENT,
|
||||
TRACE_USER_STACK,
|
||||
TRACE_BTS,
|
||||
TRACE_POWER,
|
||||
|
||||
__TRACE_LAST_TYPE
|
||||
};
|
||||
@ -56,14 +58,16 @@ struct ftrace_entry {
|
||||
unsigned long parent_ip;
|
||||
};
|
||||
|
||||
/* Function call entry */
|
||||
struct ftrace_graph_ent_entry {
|
||||
struct trace_entry ent;
|
||||
struct ftrace_graph_ent graph_ent;
|
||||
};
|
||||
|
||||
/* Function return entry */
|
||||
struct ftrace_ret_entry {
|
||||
struct trace_entry ent;
|
||||
unsigned long ip;
|
||||
unsigned long parent_ip;
|
||||
unsigned long long calltime;
|
||||
unsigned long long rettime;
|
||||
unsigned long overrun;
|
||||
struct ftrace_graph_ret_entry {
|
||||
struct trace_entry ent;
|
||||
struct ftrace_graph_ret ret;
|
||||
};
|
||||
extern struct tracer boot_tracer;
|
||||
|
||||
@ -160,6 +164,11 @@ struct bts_entry {
|
||||
unsigned long to;
|
||||
};
|
||||
|
||||
struct trace_power {
|
||||
struct trace_entry ent;
|
||||
struct power_trace state_data;
|
||||
};
|
||||
|
||||
/*
|
||||
* trace_flag_type is an enumeration that holds different
|
||||
* states when a trace occurs. These are:
|
||||
@ -264,8 +273,12 @@ extern void __ftrace_bad_type(void);
|
||||
IF_ASSIGN(var, ent, struct trace_boot_call, TRACE_BOOT_CALL);\
|
||||
IF_ASSIGN(var, ent, struct trace_boot_ret, TRACE_BOOT_RET);\
|
||||
IF_ASSIGN(var, ent, struct trace_branch, TRACE_BRANCH); \
|
||||
IF_ASSIGN(var, ent, struct ftrace_ret_entry, TRACE_FN_RET);\
|
||||
IF_ASSIGN(var, ent, struct ftrace_graph_ent_entry, \
|
||||
TRACE_GRAPH_ENT); \
|
||||
IF_ASSIGN(var, ent, struct ftrace_graph_ret_entry, \
|
||||
TRACE_GRAPH_RET); \
|
||||
IF_ASSIGN(var, ent, struct bts_entry, TRACE_BTS);\
|
||||
IF_ASSIGN(var, ent, struct trace_power, TRACE_POWER); \
|
||||
__ftrace_bad_type(); \
|
||||
} while (0)
|
||||
|
||||
@ -397,9 +410,9 @@ void trace_function(struct trace_array *tr,
|
||||
unsigned long ip,
|
||||
unsigned long parent_ip,
|
||||
unsigned long flags, int pc);
|
||||
void
|
||||
trace_function_return(struct ftrace_retfunc *trace);
|
||||
|
||||
void trace_graph_return(struct ftrace_graph_ret *trace);
|
||||
void trace_graph_entry(struct ftrace_graph_ent *trace);
|
||||
void trace_bts(struct trace_array *tr,
|
||||
unsigned long from,
|
||||
unsigned long to);
|
||||
@ -444,6 +457,7 @@ struct tracer_switch_ops {
|
||||
struct tracer_switch_ops *next;
|
||||
};
|
||||
|
||||
char *trace_find_cmdline(int pid);
|
||||
#endif /* CONFIG_CONTEXT_SWITCH_TRACER */
|
||||
|
||||
#ifdef CONFIG_DYNAMIC_FTRACE
|
||||
@ -489,11 +503,11 @@ extern int trace_vprintk(unsigned long ip, const char *fmt, va_list args);
|
||||
extern unsigned long trace_flags;
|
||||
|
||||
/* Standard output formatting function used for function return traces */
|
||||
#ifdef CONFIG_FUNCTION_RET_TRACER
|
||||
extern enum print_line_t print_return_function(struct trace_iterator *iter);
|
||||
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
|
||||
extern enum print_line_t print_graph_function(struct trace_iterator *iter);
|
||||
#else
|
||||
static inline enum print_line_t
|
||||
print_return_function(struct trace_iterator *iter)
|
||||
print_graph_function(struct trace_iterator *iter)
|
||||
{
|
||||
return TRACE_TYPE_UNHANDLED;
|
||||
}
|
||||
|
175
kernel/trace/trace_functions_graph.c
Normal file
175
kernel/trace/trace_functions_graph.c
Normal file
@ -0,0 +1,175 @@
|
||||
/*
|
||||
*
|
||||
* Function graph tracer.
|
||||
* Copyright (c) 2008 Frederic Weisbecker <fweisbec@gmail.com>
|
||||
* Mostly borrowed from function tracer which
|
||||
* is Copyright (c) Steven Rostedt <srostedt@redhat.com>
|
||||
*
|
||||
*/
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/ftrace.h>
|
||||
#include <linux/fs.h>
|
||||
|
||||
#include "trace.h"
|
||||
|
||||
#define TRACE_GRAPH_INDENT 2
|
||||
|
||||
#define TRACE_GRAPH_PRINT_OVERRUN 0x1
|
||||
static struct tracer_opt trace_opts[] = {
|
||||
/* Display overruns or not */
|
||||
{ TRACER_OPT(overrun, TRACE_GRAPH_PRINT_OVERRUN) },
|
||||
{ } /* Empty entry */
|
||||
};
|
||||
|
||||
static struct tracer_flags tracer_flags = {
|
||||
.val = 0, /* Don't display overruns by default */
|
||||
.opts = trace_opts
|
||||
};
|
||||
|
||||
/* pid on the last trace processed */
|
||||
static pid_t last_pid[NR_CPUS] = { [0 ... NR_CPUS-1] = -1 };
|
||||
|
||||
static int graph_trace_init(struct trace_array *tr)
|
||||
{
|
||||
int cpu, ret;
|
||||
|
||||
for_each_online_cpu(cpu)
|
||||
tracing_reset(tr, cpu);
|
||||
|
||||
ret = register_ftrace_graph(&trace_graph_return,
|
||||
&trace_graph_entry);
|
||||
if (ret)
|
||||
return ret;
|
||||
tracing_start_cmdline_record();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void graph_trace_reset(struct trace_array *tr)
|
||||
{
|
||||
tracing_stop_cmdline_record();
|
||||
unregister_ftrace_graph();
|
||||
}
|
||||
|
||||
/* If the pid changed since the last trace, output this event */
|
||||
static int verif_pid(struct trace_seq *s, pid_t pid, int cpu)
|
||||
{
|
||||
char *comm;
|
||||
|
||||
if (last_pid[cpu] != -1 && last_pid[cpu] == pid)
|
||||
return 1;
|
||||
|
||||
last_pid[cpu] = pid;
|
||||
comm = trace_find_cmdline(pid);
|
||||
|
||||
return trace_seq_printf(s, "\nCPU[%03d]"
|
||||
" ------------8<---------- thread %s-%d"
|
||||
" ------------8<----------\n\n",
|
||||
cpu, comm, pid);
|
||||
}
|
||||
|
||||
static enum print_line_t
|
||||
print_graph_entry(struct ftrace_graph_ent *call, struct trace_seq *s,
|
||||
struct trace_entry *ent, int cpu)
|
||||
{
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
if (!verif_pid(s, ent->pid, cpu))
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
|
||||
ret = trace_seq_printf(s, "CPU[%03d] ", cpu);
|
||||
if (!ret)
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
|
||||
for (i = 0; i < call->depth * TRACE_GRAPH_INDENT; i++) {
|
||||
ret = trace_seq_printf(s, " ");
|
||||
if (!ret)
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
}
|
||||
|
||||
ret = seq_print_ip_sym(s, call->func, 0);
|
||||
if (!ret)
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
|
||||
ret = trace_seq_printf(s, "() {\n");
|
||||
if (!ret)
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
return TRACE_TYPE_HANDLED;
|
||||
}
|
||||
|
||||
static enum print_line_t
|
||||
print_graph_return(struct ftrace_graph_ret *trace, struct trace_seq *s,
|
||||
struct trace_entry *ent, int cpu)
|
||||
{
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
if (!verif_pid(s, ent->pid, cpu))
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
|
||||
ret = trace_seq_printf(s, "CPU[%03d] ", cpu);
|
||||
if (!ret)
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
|
||||
for (i = 0; i < trace->depth * TRACE_GRAPH_INDENT; i++) {
|
||||
ret = trace_seq_printf(s, " ");
|
||||
if (!ret)
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
}
|
||||
|
||||
ret = trace_seq_printf(s, "} ");
|
||||
if (!ret)
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
|
||||
ret = trace_seq_printf(s, "%llu\n", trace->rettime - trace->calltime);
|
||||
if (!ret)
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
|
||||
if (tracer_flags.val & TRACE_GRAPH_PRINT_OVERRUN) {
|
||||
ret = trace_seq_printf(s, " (Overruns: %lu)\n",
|
||||
trace->overrun);
|
||||
if (!ret)
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
}
|
||||
return TRACE_TYPE_HANDLED;
|
||||
}
|
||||
|
||||
enum print_line_t
|
||||
print_graph_function(struct trace_iterator *iter)
|
||||
{
|
||||
struct trace_seq *s = &iter->seq;
|
||||
struct trace_entry *entry = iter->ent;
|
||||
|
||||
switch (entry->type) {
|
||||
case TRACE_GRAPH_ENT: {
|
||||
struct ftrace_graph_ent_entry *field;
|
||||
trace_assign_type(field, entry);
|
||||
return print_graph_entry(&field->graph_ent, s, entry,
|
||||
iter->cpu);
|
||||
}
|
||||
case TRACE_GRAPH_RET: {
|
||||
struct ftrace_graph_ret_entry *field;
|
||||
trace_assign_type(field, entry);
|
||||
return print_graph_return(&field->ret, s, entry, iter->cpu);
|
||||
}
|
||||
default:
|
||||
return TRACE_TYPE_UNHANDLED;
|
||||
}
|
||||
}
|
||||
|
||||
static struct tracer graph_trace __read_mostly = {
|
||||
.name = "function-graph",
|
||||
.init = graph_trace_init,
|
||||
.reset = graph_trace_reset,
|
||||
.print_line = print_graph_function,
|
||||
.flags = &tracer_flags,
|
||||
};
|
||||
|
||||
static __init int init_graph_trace(void)
|
||||
{
|
||||
return register_tracer(&graph_trace);
|
||||
}
|
||||
|
||||
device_initcall(init_graph_trace);
|
@ -1,98 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* Function return tracer.
|
||||
* Copyright (c) 2008 Frederic Weisbecker <fweisbec@gmail.com>
|
||||
* Mostly borrowed from function tracer which
|
||||
* is Copyright (c) Steven Rostedt <srostedt@redhat.com>
|
||||
*
|
||||
*/
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/ftrace.h>
|
||||
#include <linux/fs.h>
|
||||
|
||||
#include "trace.h"
|
||||
|
||||
|
||||
#define TRACE_RETURN_PRINT_OVERRUN 0x1
|
||||
static struct tracer_opt trace_opts[] = {
|
||||
/* Display overruns or not */
|
||||
{ TRACER_OPT(overrun, TRACE_RETURN_PRINT_OVERRUN) },
|
||||
{ } /* Empty entry */
|
||||
};
|
||||
|
||||
static struct tracer_flags tracer_flags = {
|
||||
.val = 0, /* Don't display overruns by default */
|
||||
.opts = trace_opts
|
||||
};
|
||||
|
||||
|
||||
static int return_trace_init(struct trace_array *tr)
|
||||
{
|
||||
int cpu;
|
||||
for_each_online_cpu(cpu)
|
||||
tracing_reset(tr, cpu);
|
||||
|
||||
return register_ftrace_return(&trace_function_return);
|
||||
}
|
||||
|
||||
static void return_trace_reset(struct trace_array *tr)
|
||||
{
|
||||
unregister_ftrace_return();
|
||||
}
|
||||
|
||||
|
||||
enum print_line_t
|
||||
print_return_function(struct trace_iterator *iter)
|
||||
{
|
||||
struct trace_seq *s = &iter->seq;
|
||||
struct trace_entry *entry = iter->ent;
|
||||
struct ftrace_ret_entry *field;
|
||||
int ret;
|
||||
|
||||
if (entry->type == TRACE_FN_RET) {
|
||||
trace_assign_type(field, entry);
|
||||
ret = trace_seq_printf(s, "%pF -> ", (void *)field->parent_ip);
|
||||
if (!ret)
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
|
||||
ret = seq_print_ip_sym(s, field->ip,
|
||||
trace_flags & TRACE_ITER_SYM_MASK);
|
||||
if (!ret)
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
|
||||
ret = trace_seq_printf(s, " (%llu ns)",
|
||||
field->rettime - field->calltime);
|
||||
if (!ret)
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
|
||||
if (tracer_flags.val & TRACE_RETURN_PRINT_OVERRUN) {
|
||||
ret = trace_seq_printf(s, " (Overruns: %lu)",
|
||||
field->overrun);
|
||||
if (!ret)
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
}
|
||||
|
||||
ret = trace_seq_printf(s, "\n");
|
||||
if (!ret)
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
|
||||
return TRACE_TYPE_HANDLED;
|
||||
}
|
||||
return TRACE_TYPE_UNHANDLED;
|
||||
}
|
||||
|
||||
static struct tracer return_trace __read_mostly = {
|
||||
.name = "return",
|
||||
.init = return_trace_init,
|
||||
.reset = return_trace_reset,
|
||||
.print_line = print_return_function,
|
||||
.flags = &tracer_flags,
|
||||
};
|
||||
|
||||
static __init int init_return_trace(void)
|
||||
{
|
||||
return register_tracer(&return_trace);
|
||||
}
|
||||
|
||||
device_initcall(init_return_trace);
|
179
kernel/trace/trace_power.c
Normal file
179
kernel/trace/trace_power.c
Normal file
@ -0,0 +1,179 @@
|
||||
/*
|
||||
* ring buffer based C-state tracer
|
||||
*
|
||||
* Arjan van de Ven <arjan@linux.intel.com>
|
||||
* Copyright (C) 2008 Intel Corporation
|
||||
*
|
||||
* Much is borrowed from trace_boot.c which is
|
||||
* Copyright (C) 2008 Frederic Weisbecker <fweisbec@gmail.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/ftrace.h>
|
||||
#include <linux/kallsyms.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
#include "trace.h"
|
||||
|
||||
static struct trace_array *power_trace;
|
||||
static int __read_mostly trace_power_enabled;
|
||||
|
||||
|
||||
static void start_power_trace(struct trace_array *tr)
|
||||
{
|
||||
trace_power_enabled = 1;
|
||||
}
|
||||
|
||||
static void stop_power_trace(struct trace_array *tr)
|
||||
{
|
||||
trace_power_enabled = 0;
|
||||
}
|
||||
|
||||
|
||||
static int power_trace_init(struct trace_array *tr)
|
||||
{
|
||||
int cpu;
|
||||
power_trace = tr;
|
||||
|
||||
trace_power_enabled = 1;
|
||||
|
||||
for_each_cpu_mask(cpu, cpu_possible_map)
|
||||
tracing_reset(tr, cpu);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static enum print_line_t power_print_line(struct trace_iterator *iter)
|
||||
{
|
||||
int ret = 0;
|
||||
struct trace_entry *entry = iter->ent;
|
||||
struct trace_power *field ;
|
||||
struct power_trace *it;
|
||||
struct trace_seq *s = &iter->seq;
|
||||
struct timespec stamp;
|
||||
struct timespec duration;
|
||||
|
||||
trace_assign_type(field, entry);
|
||||
it = &field->state_data;
|
||||
stamp = ktime_to_timespec(it->stamp);
|
||||
duration = ktime_to_timespec(ktime_sub(it->end, it->stamp));
|
||||
|
||||
if (entry->type == TRACE_POWER) {
|
||||
if (it->type == POWER_CSTATE)
|
||||
ret = trace_seq_printf(s, "[%5ld.%09ld] CSTATE: Going to C%i on cpu %i for %ld.%09ld\n",
|
||||
stamp.tv_sec,
|
||||
stamp.tv_nsec,
|
||||
it->state, iter->cpu,
|
||||
duration.tv_sec,
|
||||
duration.tv_nsec);
|
||||
if (it->type == POWER_PSTATE)
|
||||
ret = trace_seq_printf(s, "[%5ld.%09ld] PSTATE: Going to P%i on cpu %i\n",
|
||||
stamp.tv_sec,
|
||||
stamp.tv_nsec,
|
||||
it->state, iter->cpu);
|
||||
if (!ret)
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
return TRACE_TYPE_HANDLED;
|
||||
}
|
||||
return TRACE_TYPE_UNHANDLED;
|
||||
}
|
||||
|
||||
static struct tracer power_tracer __read_mostly =
|
||||
{
|
||||
.name = "power",
|
||||
.init = power_trace_init,
|
||||
.start = start_power_trace,
|
||||
.stop = stop_power_trace,
|
||||
.reset = stop_power_trace,
|
||||
.print_line = power_print_line,
|
||||
};
|
||||
|
||||
static int init_power_trace(void)
|
||||
{
|
||||
return register_tracer(&power_tracer);
|
||||
}
|
||||
device_initcall(init_power_trace);
|
||||
|
||||
void trace_power_start(struct power_trace *it, unsigned int type,
|
||||
unsigned int level)
|
||||
{
|
||||
if (!trace_power_enabled)
|
||||
return;
|
||||
|
||||
memset(it, 0, sizeof(struct power_trace));
|
||||
it->state = level;
|
||||
it->type = type;
|
||||
it->stamp = ktime_get();
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(trace_power_start);
|
||||
|
||||
|
||||
void trace_power_end(struct power_trace *it)
|
||||
{
|
||||
struct ring_buffer_event *event;
|
||||
struct trace_power *entry;
|
||||
struct trace_array_cpu *data;
|
||||
unsigned long irq_flags;
|
||||
struct trace_array *tr = power_trace;
|
||||
|
||||
if (!trace_power_enabled)
|
||||
return;
|
||||
|
||||
preempt_disable();
|
||||
it->end = ktime_get();
|
||||
data = tr->data[smp_processor_id()];
|
||||
|
||||
event = ring_buffer_lock_reserve(tr->buffer, sizeof(*entry),
|
||||
&irq_flags);
|
||||
if (!event)
|
||||
goto out;
|
||||
entry = ring_buffer_event_data(event);
|
||||
tracing_generic_entry_update(&entry->ent, 0, 0);
|
||||
entry->ent.type = TRACE_POWER;
|
||||
entry->state_data = *it;
|
||||
ring_buffer_unlock_commit(tr->buffer, event, irq_flags);
|
||||
|
||||
trace_wake_up();
|
||||
|
||||
out:
|
||||
preempt_enable();
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(trace_power_end);
|
||||
|
||||
void trace_power_mark(struct power_trace *it, unsigned int type,
|
||||
unsigned int level)
|
||||
{
|
||||
struct ring_buffer_event *event;
|
||||
struct trace_power *entry;
|
||||
struct trace_array_cpu *data;
|
||||
unsigned long irq_flags;
|
||||
struct trace_array *tr = power_trace;
|
||||
|
||||
if (!trace_power_enabled)
|
||||
return;
|
||||
|
||||
memset(it, 0, sizeof(struct power_trace));
|
||||
it->state = level;
|
||||
it->type = type;
|
||||
it->stamp = ktime_get();
|
||||
preempt_disable();
|
||||
it->end = it->stamp;
|
||||
data = tr->data[smp_processor_id()];
|
||||
|
||||
event = ring_buffer_lock_reserve(tr->buffer, sizeof(*entry),
|
||||
&irq_flags);
|
||||
if (!event)
|
||||
goto out;
|
||||
entry = ring_buffer_event_data(event);
|
||||
tracing_generic_entry_update(&entry->ent, 0, 0);
|
||||
entry->ent.type = TRACE_POWER;
|
||||
entry->state_data = *it;
|
||||
ring_buffer_unlock_commit(tr->buffer, event, irq_flags);
|
||||
|
||||
trace_wake_up();
|
||||
|
||||
out:
|
||||
preempt_enable();
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(trace_power_mark);
|
@ -112,6 +112,8 @@ my ($arch, $bits, $objdump, $objcopy, $cc,
|
||||
# Acceptable sections to record.
|
||||
my %text_sections = (
|
||||
".text" => 1,
|
||||
".sched.text" => 1,
|
||||
".spinlock.text" => 1,
|
||||
);
|
||||
|
||||
$objdump = "objdump" if ((length $objdump) == 0);
|
||||
|
108
scripts/trace/power.pl
Normal file
108
scripts/trace/power.pl
Normal file
@ -0,0 +1,108 @@
|
||||
#!/usr/bin/perl
|
||||
|
||||
# Copyright 2008, Intel Corporation
|
||||
#
|
||||
# This file is part of the Linux kernel
|
||||
#
|
||||
# This program file 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; version 2 of the License.
|
||||
#
|
||||
# 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 in a file named COPYING; if not, write to the
|
||||
# Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor,
|
||||
# Boston, MA 02110-1301 USA
|
||||
#
|
||||
# Authors:
|
||||
# Arjan van de Ven <arjan@linux.intel.com>
|
||||
|
||||
|
||||
#
|
||||
# This script turns a cstate ftrace output into a SVG graphic that shows
|
||||
# historic C-state information
|
||||
#
|
||||
#
|
||||
# cat /sys/kernel/debug/tracing/trace | perl power.pl > out.svg
|
||||
#
|
||||
|
||||
my @styles;
|
||||
my $base = 0;
|
||||
|
||||
my @pstate_last;
|
||||
my @pstate_level;
|
||||
|
||||
$styles[0] = "fill:rgb(0,0,255);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)";
|
||||
$styles[1] = "fill:rgb(0,255,0);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)";
|
||||
$styles[2] = "fill:rgb(255,0,20);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)";
|
||||
$styles[3] = "fill:rgb(255,255,20);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)";
|
||||
$styles[4] = "fill:rgb(255,0,255);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)";
|
||||
$styles[5] = "fill:rgb(0,255,255);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)";
|
||||
$styles[6] = "fill:rgb(0,128,255);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)";
|
||||
$styles[7] = "fill:rgb(0,255,128);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)";
|
||||
$styles[8] = "fill:rgb(0,25,20);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)";
|
||||
|
||||
|
||||
print "<?xml version=\"1.0\" standalone=\"no\"?> \n";
|
||||
print "<svg width=\"10000\" height=\"100%\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\">\n";
|
||||
|
||||
my $scale = 30000.0;
|
||||
while (<>) {
|
||||
my $line = $_;
|
||||
if ($line =~ /([0-9\.]+)\] CSTATE: Going to C([0-9]) on cpu ([0-9]+) for ([0-9\.]+)/) {
|
||||
if ($base == 0) {
|
||||
$base = $1;
|
||||
}
|
||||
my $time = $1 - $base;
|
||||
$time = $time * $scale;
|
||||
my $C = $2;
|
||||
my $cpu = $3;
|
||||
my $y = 400 * $cpu;
|
||||
my $duration = $4 * $scale;
|
||||
my $msec = int($4 * 100000)/100.0;
|
||||
my $height = $C * 20;
|
||||
$style = $styles[$C];
|
||||
|
||||
$y = $y + 140 - $height;
|
||||
|
||||
$x2 = $time + 4;
|
||||
$y2 = $y + 4;
|
||||
|
||||
|
||||
print "<rect x=\"$time\" width=\"$duration\" y=\"$y\" height=\"$height\" style=\"$style\"/>\n";
|
||||
print "<text transform=\"translate($x2,$y2) rotate(90)\">C$C $msec</text>\n";
|
||||
}
|
||||
if ($line =~ /([0-9\.]+)\] PSTATE: Going to P([0-9]) on cpu ([0-9]+)/) {
|
||||
my $time = $1 - $base;
|
||||
my $state = $2;
|
||||
my $cpu = $3;
|
||||
|
||||
if (defined($pstate_last[$cpu])) {
|
||||
my $from = $pstate_last[$cpu];
|
||||
my $oldstate = $pstate_state[$cpu];
|
||||
my $duration = ($time-$from) * $scale;
|
||||
|
||||
$from = $from * $scale;
|
||||
my $to = $from + $duration;
|
||||
my $height = 140 - ($oldstate * (140/8));
|
||||
|
||||
my $y = 400 * $cpu + 200 + $height;
|
||||
my $y2 = $y+4;
|
||||
my $style = $styles[8];
|
||||
|
||||
print "<rect x=\"$from\" y=\"$y\" width=\"$duration\" height=\"5\" style=\"$style\"/>\n";
|
||||
print "<text transform=\"translate($from,$y2)\">P$oldstate (cpu $cpu)</text>\n";
|
||||
};
|
||||
|
||||
$pstate_last[$cpu] = $time;
|
||||
$pstate_state[$cpu] = $state;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
print "</svg>\n";
|
Loading…
Reference in New Issue
Block a user