Merge branches 'tracing/kmemtrace2' and 'tracing/ftrace' into tracing/urgent
This commit is contained in:
commit
99793e3dbe
13 changed files with 1410 additions and 854 deletions
|
@ -19,6 +19,8 @@ obj-$(CONFIG_FUNCTION_TRACER) += libftrace.o
|
|||
obj-$(CONFIG_RING_BUFFER) += ring_buffer.o
|
||||
|
||||
obj-$(CONFIG_TRACING) += trace.o
|
||||
obj-$(CONFIG_TRACING) += trace_output.o
|
||||
obj-$(CONFIG_TRACING) += trace_stat.o
|
||||
obj-$(CONFIG_CONTEXT_SWITCH_TRACER) += trace_sched_switch.o
|
||||
obj-$(CONFIG_SYSPROF_TRACER) += trace_sysprof.o
|
||||
obj-$(CONFIG_FUNCTION_TRACER) += trace_functions.o
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
#include <linux/irqflags.h>
|
||||
|
||||
#include "trace.h"
|
||||
#include "trace_output.h"
|
||||
|
||||
#define TRACE_BUFFER_FLAGS (RB_FL_OVERWRITE)
|
||||
|
||||
|
@ -329,132 +330,6 @@ __update_max_tr(struct trace_array *tr, struct task_struct *tsk, int cpu)
|
|||
tracing_record_cmdline(current);
|
||||
}
|
||||
|
||||
/**
|
||||
* trace_seq_printf - sequence printing of trace information
|
||||
* @s: trace sequence descriptor
|
||||
* @fmt: printf format string
|
||||
*
|
||||
* The tracer may use either sequence operations or its own
|
||||
* copy to user routines. To simplify formating of a trace
|
||||
* trace_seq_printf is used to store strings into a special
|
||||
* buffer (@s). Then the output may be either used by
|
||||
* the sequencer or pulled into another buffer.
|
||||
*/
|
||||
int
|
||||
trace_seq_printf(struct trace_seq *s, const char *fmt, ...)
|
||||
{
|
||||
int len = (PAGE_SIZE - 1) - s->len;
|
||||
va_list ap;
|
||||
int ret;
|
||||
|
||||
if (!len)
|
||||
return 0;
|
||||
|
||||
va_start(ap, fmt);
|
||||
ret = vsnprintf(s->buffer + s->len, len, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
/* If we can't write it all, don't bother writing anything */
|
||||
if (ret >= len)
|
||||
return 0;
|
||||
|
||||
s->len += ret;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
/**
|
||||
* trace_seq_puts - trace sequence printing of simple string
|
||||
* @s: trace sequence descriptor
|
||||
* @str: simple string to record
|
||||
*
|
||||
* The tracer may use either the sequence operations or its own
|
||||
* copy to user routines. This function records a simple string
|
||||
* into a special buffer (@s) for later retrieval by a sequencer
|
||||
* or other mechanism.
|
||||
*/
|
||||
static int
|
||||
trace_seq_puts(struct trace_seq *s, const char *str)
|
||||
{
|
||||
int len = strlen(str);
|
||||
|
||||
if (len > ((PAGE_SIZE - 1) - s->len))
|
||||
return 0;
|
||||
|
||||
memcpy(s->buffer + s->len, str, len);
|
||||
s->len += len;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static int
|
||||
trace_seq_putc(struct trace_seq *s, unsigned char c)
|
||||
{
|
||||
if (s->len >= (PAGE_SIZE - 1))
|
||||
return 0;
|
||||
|
||||
s->buffer[s->len++] = c;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
trace_seq_putmem(struct trace_seq *s, void *mem, size_t len)
|
||||
{
|
||||
if (len > ((PAGE_SIZE - 1) - s->len))
|
||||
return 0;
|
||||
|
||||
memcpy(s->buffer + s->len, mem, len);
|
||||
s->len += len;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
#define MAX_MEMHEX_BYTES 8
|
||||
#define HEX_CHARS (MAX_MEMHEX_BYTES*2 + 1)
|
||||
|
||||
static int
|
||||
trace_seq_putmem_hex(struct trace_seq *s, void *mem, size_t len)
|
||||
{
|
||||
unsigned char hex[HEX_CHARS];
|
||||
unsigned char *data = mem;
|
||||
int i, j;
|
||||
|
||||
#ifdef __BIG_ENDIAN
|
||||
for (i = 0, j = 0; i < len; i++) {
|
||||
#else
|
||||
for (i = len-1, j = 0; i >= 0; i--) {
|
||||
#endif
|
||||
hex[j++] = hex_asc_hi(data[i]);
|
||||
hex[j++] = hex_asc_lo(data[i]);
|
||||
}
|
||||
hex[j++] = ' ';
|
||||
|
||||
return trace_seq_putmem(s, hex, j);
|
||||
}
|
||||
|
||||
static int
|
||||
trace_seq_path(struct trace_seq *s, struct path *path)
|
||||
{
|
||||
unsigned char *p;
|
||||
|
||||
if (s->len >= (PAGE_SIZE - 1))
|
||||
return 0;
|
||||
p = d_path(path, s->buffer + s->len, PAGE_SIZE - s->len);
|
||||
if (!IS_ERR(p)) {
|
||||
p = mangle_path(s->buffer + s->len, p, "\n");
|
||||
if (p) {
|
||||
s->len = p - s->buffer;
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
s->buffer[s->len++] = '?';
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
trace_seq_reset(struct trace_seq *s)
|
||||
{
|
||||
|
@ -1472,154 +1347,6 @@ static void s_stop(struct seq_file *m, void *p)
|
|||
mutex_unlock(&trace_types_lock);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_KRETPROBES
|
||||
static inline const char *kretprobed(const char *name)
|
||||
{
|
||||
static const char tramp_name[] = "kretprobe_trampoline";
|
||||
int size = sizeof(tramp_name);
|
||||
|
||||
if (strncmp(tramp_name, name, size) == 0)
|
||||
return "[unknown/kretprobe'd]";
|
||||
return name;
|
||||
}
|
||||
#else
|
||||
static inline const char *kretprobed(const char *name)
|
||||
{
|
||||
return name;
|
||||
}
|
||||
#endif /* CONFIG_KRETPROBES */
|
||||
|
||||
static int
|
||||
seq_print_sym_short(struct trace_seq *s, const char *fmt, unsigned long address)
|
||||
{
|
||||
#ifdef CONFIG_KALLSYMS
|
||||
char str[KSYM_SYMBOL_LEN];
|
||||
const char *name;
|
||||
|
||||
kallsyms_lookup(address, NULL, NULL, NULL, str);
|
||||
|
||||
name = kretprobed(str);
|
||||
|
||||
return trace_seq_printf(s, fmt, name);
|
||||
#endif
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
seq_print_sym_offset(struct trace_seq *s, const char *fmt,
|
||||
unsigned long address)
|
||||
{
|
||||
#ifdef CONFIG_KALLSYMS
|
||||
char str[KSYM_SYMBOL_LEN];
|
||||
const char *name;
|
||||
|
||||
sprint_symbol(str, address);
|
||||
name = kretprobed(str);
|
||||
|
||||
return trace_seq_printf(s, fmt, name);
|
||||
#endif
|
||||
return 1;
|
||||
}
|
||||
|
||||
#ifndef CONFIG_64BIT
|
||||
# define IP_FMT "%08lx"
|
||||
#else
|
||||
# define IP_FMT "%016lx"
|
||||
#endif
|
||||
|
||||
int
|
||||
seq_print_ip_sym(struct trace_seq *s, unsigned long ip, unsigned long sym_flags)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!ip)
|
||||
return trace_seq_printf(s, "0");
|
||||
|
||||
if (sym_flags & TRACE_ITER_SYM_OFFSET)
|
||||
ret = seq_print_sym_offset(s, "%s", ip);
|
||||
else
|
||||
ret = seq_print_sym_short(s, "%s", ip);
|
||||
|
||||
if (!ret)
|
||||
return 0;
|
||||
|
||||
if (sym_flags & TRACE_ITER_SYM_ADDR)
|
||||
ret = trace_seq_printf(s, " <" IP_FMT ">", ip);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int seq_print_user_ip(struct trace_seq *s, struct mm_struct *mm,
|
||||
unsigned long ip, unsigned long sym_flags)
|
||||
{
|
||||
struct file *file = NULL;
|
||||
unsigned long vmstart = 0;
|
||||
int ret = 1;
|
||||
|
||||
if (mm) {
|
||||
const struct vm_area_struct *vma;
|
||||
|
||||
down_read(&mm->mmap_sem);
|
||||
vma = find_vma(mm, ip);
|
||||
if (vma) {
|
||||
file = vma->vm_file;
|
||||
vmstart = vma->vm_start;
|
||||
}
|
||||
if (file) {
|
||||
ret = trace_seq_path(s, &file->f_path);
|
||||
if (ret)
|
||||
ret = trace_seq_printf(s, "[+0x%lx]", ip - vmstart);
|
||||
}
|
||||
up_read(&mm->mmap_sem);
|
||||
}
|
||||
if (ret && ((sym_flags & TRACE_ITER_SYM_ADDR) || !file))
|
||||
ret = trace_seq_printf(s, " <" IP_FMT ">", ip);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
seq_print_userip_objs(const struct userstack_entry *entry, struct trace_seq *s,
|
||||
unsigned long sym_flags)
|
||||
{
|
||||
struct mm_struct *mm = NULL;
|
||||
int ret = 1;
|
||||
unsigned int i;
|
||||
|
||||
if (trace_flags & TRACE_ITER_SYM_USEROBJ) {
|
||||
struct task_struct *task;
|
||||
/*
|
||||
* we do the lookup on the thread group leader,
|
||||
* since individual threads might have already quit!
|
||||
*/
|
||||
rcu_read_lock();
|
||||
task = find_task_by_vpid(entry->ent.tgid);
|
||||
if (task)
|
||||
mm = get_task_mm(task);
|
||||
rcu_read_unlock();
|
||||
}
|
||||
|
||||
for (i = 0; i < FTRACE_STACK_ENTRIES; i++) {
|
||||
unsigned long ip = entry->caller[i];
|
||||
|
||||
if (ip == ULONG_MAX || !ret)
|
||||
break;
|
||||
if (i && ret)
|
||||
ret = trace_seq_puts(s, " <- ");
|
||||
if (!ip) {
|
||||
if (ret)
|
||||
ret = trace_seq_puts(s, "??");
|
||||
continue;
|
||||
}
|
||||
if (!ret)
|
||||
break;
|
||||
if (ret)
|
||||
ret = seq_print_user_ip(s, mm, ip, sym_flags);
|
||||
}
|
||||
|
||||
if (mm)
|
||||
mmput(mm);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void print_lat_help_header(struct seq_file *m)
|
||||
{
|
||||
seq_puts(m, "# _------=> CPU# \n");
|
||||
|
@ -1755,52 +1482,6 @@ lat_print_timestamp(struct trace_seq *s, u64 abs_usecs,
|
|||
trace_seq_puts(s, " : ");
|
||||
}
|
||||
|
||||
static const char state_to_char[] = TASK_STATE_TO_CHAR_STR;
|
||||
|
||||
static int task_state_char(unsigned long state)
|
||||
{
|
||||
int bit = state ? __ffs(state) + 1 : 0;
|
||||
|
||||
return bit < sizeof(state_to_char) - 1 ? state_to_char[bit] : '?';
|
||||
}
|
||||
|
||||
/*
|
||||
* The message is supposed to contain an ending newline.
|
||||
* If the printing stops prematurely, try to add a newline of our own.
|
||||
*/
|
||||
void trace_seq_print_cont(struct trace_seq *s, struct trace_iterator *iter)
|
||||
{
|
||||
struct trace_entry *ent;
|
||||
struct trace_field_cont *cont;
|
||||
bool ok = true;
|
||||
|
||||
ent = peek_next_entry(iter, iter->cpu, NULL);
|
||||
if (!ent || ent->type != TRACE_CONT) {
|
||||
trace_seq_putc(s, '\n');
|
||||
return;
|
||||
}
|
||||
|
||||
do {
|
||||
cont = (struct trace_field_cont *)ent;
|
||||
if (ok)
|
||||
ok = (trace_seq_printf(s, "%s", cont->buf) > 0);
|
||||
|
||||
ftrace_disable_cpu();
|
||||
|
||||
if (iter->buffer_iter[iter->cpu])
|
||||
ring_buffer_read(iter->buffer_iter[iter->cpu], NULL);
|
||||
else
|
||||
ring_buffer_consume(iter->tr->buffer, iter->cpu, NULL);
|
||||
|
||||
ftrace_enable_cpu();
|
||||
|
||||
ent = peek_next_entry(iter, iter->cpu, NULL);
|
||||
} while (ent && ent->type == TRACE_CONT);
|
||||
|
||||
if (!ok)
|
||||
trace_seq_putc(s, '\n');
|
||||
}
|
||||
|
||||
static void test_cpu_buff_start(struct trace_iterator *iter)
|
||||
{
|
||||
struct trace_seq *s = &iter->seq;
|
||||
|
@ -1824,17 +1505,14 @@ print_lat_fmt(struct trace_iterator *iter, unsigned int trace_idx, int cpu)
|
|||
struct trace_seq *s = &iter->seq;
|
||||
unsigned long sym_flags = (trace_flags & TRACE_ITER_SYM_MASK);
|
||||
struct trace_entry *next_entry;
|
||||
struct trace_event *event;
|
||||
unsigned long verbose = (trace_flags & TRACE_ITER_VERBOSE);
|
||||
struct trace_entry *entry = iter->ent;
|
||||
unsigned long abs_usecs;
|
||||
unsigned long rel_usecs;
|
||||
u64 next_ts;
|
||||
char *comm;
|
||||
int S, T;
|
||||
int i;
|
||||
|
||||
if (entry->type == TRACE_CONT)
|
||||
return TRACE_TYPE_HANDLED;
|
||||
int ret;
|
||||
|
||||
test_cpu_buff_start(iter);
|
||||
|
||||
|
@ -1859,96 +1537,16 @@ print_lat_fmt(struct trace_iterator *iter, unsigned int trace_idx, int cpu)
|
|||
lat_print_generic(s, entry, cpu);
|
||||
lat_print_timestamp(s, abs_usecs, rel_usecs);
|
||||
}
|
||||
switch (entry->type) {
|
||||
case TRACE_FN: {
|
||||
struct ftrace_entry *field;
|
||||
|
||||
trace_assign_type(field, entry);
|
||||
|
||||
seq_print_ip_sym(s, field->ip, sym_flags);
|
||||
trace_seq_puts(s, " (");
|
||||
seq_print_ip_sym(s, field->parent_ip, sym_flags);
|
||||
trace_seq_puts(s, ")\n");
|
||||
break;
|
||||
event = ftrace_find_event(entry->type);
|
||||
if (event && event->latency_trace) {
|
||||
ret = event->latency_trace(s, entry, sym_flags);
|
||||
if (ret)
|
||||
return ret;
|
||||
return TRACE_TYPE_HANDLED;
|
||||
}
|
||||
case TRACE_CTX:
|
||||
case TRACE_WAKE: {
|
||||
struct ctx_switch_entry *field;
|
||||
|
||||
trace_assign_type(field, entry);
|
||||
|
||||
T = task_state_char(field->next_state);
|
||||
S = task_state_char(field->prev_state);
|
||||
comm = trace_find_cmdline(field->next_pid);
|
||||
trace_seq_printf(s, " %5d:%3d:%c %s [%03d] %5d:%3d:%c %s\n",
|
||||
field->prev_pid,
|
||||
field->prev_prio,
|
||||
S, entry->type == TRACE_CTX ? "==>" : " +",
|
||||
field->next_cpu,
|
||||
field->next_pid,
|
||||
field->next_prio,
|
||||
T, comm);
|
||||
break;
|
||||
}
|
||||
case TRACE_SPECIAL: {
|
||||
struct special_entry *field;
|
||||
|
||||
trace_assign_type(field, entry);
|
||||
|
||||
trace_seq_printf(s, "# %ld %ld %ld\n",
|
||||
field->arg1,
|
||||
field->arg2,
|
||||
field->arg3);
|
||||
break;
|
||||
}
|
||||
case TRACE_STACK: {
|
||||
struct stack_entry *field;
|
||||
|
||||
trace_assign_type(field, entry);
|
||||
|
||||
for (i = 0; i < FTRACE_STACK_ENTRIES; i++) {
|
||||
if (i)
|
||||
trace_seq_puts(s, " <= ");
|
||||
seq_print_ip_sym(s, field->caller[i], sym_flags);
|
||||
}
|
||||
trace_seq_puts(s, "\n");
|
||||
break;
|
||||
}
|
||||
case TRACE_PRINT: {
|
||||
struct print_entry *field;
|
||||
|
||||
trace_assign_type(field, entry);
|
||||
|
||||
seq_print_ip_sym(s, field->ip, sym_flags);
|
||||
trace_seq_printf(s, ": %s", field->buf);
|
||||
if (entry->flags & TRACE_FLAG_CONT)
|
||||
trace_seq_print_cont(s, iter);
|
||||
break;
|
||||
}
|
||||
case TRACE_BRANCH: {
|
||||
struct trace_branch *field;
|
||||
|
||||
trace_assign_type(field, entry);
|
||||
|
||||
trace_seq_printf(s, "[%s] %s:%s:%d\n",
|
||||
field->correct ? " ok " : " MISS ",
|
||||
field->func,
|
||||
field->file,
|
||||
field->line);
|
||||
break;
|
||||
}
|
||||
case TRACE_USER_STACK: {
|
||||
struct userstack_entry *field;
|
||||
|
||||
trace_assign_type(field, entry);
|
||||
|
||||
seq_print_userip_objs(field, s, sym_flags);
|
||||
trace_seq_putc(s, '\n');
|
||||
break;
|
||||
}
|
||||
default:
|
||||
trace_seq_printf(s, "Unknown type %d\n", entry->type);
|
||||
}
|
||||
trace_seq_printf(s, "Unknown type %d\n", entry->type);
|
||||
return TRACE_TYPE_HANDLED;
|
||||
}
|
||||
|
||||
|
@ -1957,19 +1555,15 @@ static enum print_line_t print_trace_fmt(struct trace_iterator *iter)
|
|||
struct trace_seq *s = &iter->seq;
|
||||
unsigned long sym_flags = (trace_flags & TRACE_ITER_SYM_MASK);
|
||||
struct trace_entry *entry;
|
||||
struct trace_event *event;
|
||||
unsigned long usec_rem;
|
||||
unsigned long long t;
|
||||
unsigned long secs;
|
||||
char *comm;
|
||||
int ret;
|
||||
int S, T;
|
||||
int i;
|
||||
|
||||
entry = iter->ent;
|
||||
|
||||
if (entry->type == TRACE_CONT)
|
||||
return TRACE_TYPE_HANDLED;
|
||||
|
||||
test_cpu_buff_start(iter);
|
||||
|
||||
comm = trace_find_cmdline(iter->ent->pid);
|
||||
|
@ -1988,129 +1582,17 @@ static enum print_line_t print_trace_fmt(struct trace_iterator *iter)
|
|||
if (!ret)
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
|
||||
switch (entry->type) {
|
||||
case TRACE_FN: {
|
||||
struct ftrace_entry *field;
|
||||
|
||||
trace_assign_type(field, entry);
|
||||
|
||||
ret = seq_print_ip_sym(s, field->ip, sym_flags);
|
||||
if (!ret)
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
if ((sym_flags & TRACE_ITER_PRINT_PARENT) &&
|
||||
field->parent_ip) {
|
||||
ret = trace_seq_printf(s, " <-");
|
||||
if (!ret)
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
ret = seq_print_ip_sym(s,
|
||||
field->parent_ip,
|
||||
sym_flags);
|
||||
if (!ret)
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
}
|
||||
ret = trace_seq_printf(s, "\n");
|
||||
if (!ret)
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
break;
|
||||
event = ftrace_find_event(entry->type);
|
||||
if (event && event->trace) {
|
||||
ret = event->trace(s, entry, sym_flags);
|
||||
if (ret)
|
||||
return ret;
|
||||
return TRACE_TYPE_HANDLED;
|
||||
}
|
||||
case TRACE_CTX:
|
||||
case TRACE_WAKE: {
|
||||
struct ctx_switch_entry *field;
|
||||
ret = trace_seq_printf(s, "Unknown type %d\n", entry->type);
|
||||
if (!ret)
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
|
||||
trace_assign_type(field, entry);
|
||||
|
||||
T = task_state_char(field->next_state);
|
||||
S = task_state_char(field->prev_state);
|
||||
ret = trace_seq_printf(s, " %5d:%3d:%c %s [%03d] %5d:%3d:%c\n",
|
||||
field->prev_pid,
|
||||
field->prev_prio,
|
||||
S,
|
||||
entry->type == TRACE_CTX ? "==>" : " +",
|
||||
field->next_cpu,
|
||||
field->next_pid,
|
||||
field->next_prio,
|
||||
T);
|
||||
if (!ret)
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
break;
|
||||
}
|
||||
case TRACE_SPECIAL: {
|
||||
struct special_entry *field;
|
||||
|
||||
trace_assign_type(field, entry);
|
||||
|
||||
ret = trace_seq_printf(s, "# %ld %ld %ld\n",
|
||||
field->arg1,
|
||||
field->arg2,
|
||||
field->arg3);
|
||||
if (!ret)
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
break;
|
||||
}
|
||||
case TRACE_STACK: {
|
||||
struct stack_entry *field;
|
||||
|
||||
trace_assign_type(field, entry);
|
||||
|
||||
for (i = 0; i < FTRACE_STACK_ENTRIES; i++) {
|
||||
if (i) {
|
||||
ret = trace_seq_puts(s, " <= ");
|
||||
if (!ret)
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
}
|
||||
ret = seq_print_ip_sym(s, field->caller[i],
|
||||
sym_flags);
|
||||
if (!ret)
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
}
|
||||
ret = trace_seq_puts(s, "\n");
|
||||
if (!ret)
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
break;
|
||||
}
|
||||
case TRACE_PRINT: {
|
||||
struct print_entry *field;
|
||||
|
||||
trace_assign_type(field, entry);
|
||||
|
||||
seq_print_ip_sym(s, field->ip, sym_flags);
|
||||
trace_seq_printf(s, ": %s", field->buf);
|
||||
if (entry->flags & TRACE_FLAG_CONT)
|
||||
trace_seq_print_cont(s, 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;
|
||||
|
||||
trace_assign_type(field, entry);
|
||||
|
||||
trace_seq_printf(s, "[%s] %s:%s:%d\n",
|
||||
field->correct ? " ok " : " MISS ",
|
||||
field->func,
|
||||
field->file,
|
||||
field->line);
|
||||
break;
|
||||
}
|
||||
case TRACE_USER_STACK: {
|
||||
struct userstack_entry *field;
|
||||
|
||||
trace_assign_type(field, entry);
|
||||
|
||||
ret = seq_print_userip_objs(field, s, sym_flags);
|
||||
if (!ret)
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
ret = trace_seq_putc(s, '\n');
|
||||
if (!ret)
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return TRACE_TYPE_HANDLED;
|
||||
}
|
||||
|
||||
|
@ -2118,152 +1600,47 @@ static enum print_line_t print_raw_fmt(struct trace_iterator *iter)
|
|||
{
|
||||
struct trace_seq *s = &iter->seq;
|
||||
struct trace_entry *entry;
|
||||
struct trace_event *event;
|
||||
int ret;
|
||||
int S, T;
|
||||
|
||||
entry = iter->ent;
|
||||
|
||||
if (entry->type == TRACE_CONT)
|
||||
return TRACE_TYPE_HANDLED;
|
||||
|
||||
ret = trace_seq_printf(s, "%d %d %llu ",
|
||||
entry->pid, iter->cpu, iter->ts);
|
||||
if (!ret)
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
|
||||
switch (entry->type) {
|
||||
case TRACE_FN: {
|
||||
struct ftrace_entry *field;
|
||||
|
||||
trace_assign_type(field, entry);
|
||||
|
||||
ret = trace_seq_printf(s, "%x %x\n",
|
||||
field->ip,
|
||||
field->parent_ip);
|
||||
if (!ret)
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
break;
|
||||
event = ftrace_find_event(entry->type);
|
||||
if (event && event->raw) {
|
||||
ret = event->raw(s, entry, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
return TRACE_TYPE_HANDLED;
|
||||
}
|
||||
case TRACE_CTX:
|
||||
case TRACE_WAKE: {
|
||||
struct ctx_switch_entry *field;
|
||||
ret = trace_seq_printf(s, "%d ?\n", entry->type);
|
||||
if (!ret)
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
|
||||
trace_assign_type(field, entry);
|
||||
|
||||
T = task_state_char(field->next_state);
|
||||
S = entry->type == TRACE_WAKE ? '+' :
|
||||
task_state_char(field->prev_state);
|
||||
ret = trace_seq_printf(s, "%d %d %c %d %d %d %c\n",
|
||||
field->prev_pid,
|
||||
field->prev_prio,
|
||||
S,
|
||||
field->next_cpu,
|
||||
field->next_pid,
|
||||
field->next_prio,
|
||||
T);
|
||||
if (!ret)
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
break;
|
||||
}
|
||||
case TRACE_SPECIAL:
|
||||
case TRACE_USER_STACK:
|
||||
case TRACE_STACK: {
|
||||
struct special_entry *field;
|
||||
|
||||
trace_assign_type(field, entry);
|
||||
|
||||
ret = trace_seq_printf(s, "# %ld %ld %ld\n",
|
||||
field->arg1,
|
||||
field->arg2,
|
||||
field->arg3);
|
||||
if (!ret)
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
break;
|
||||
}
|
||||
case TRACE_PRINT: {
|
||||
struct print_entry *field;
|
||||
|
||||
trace_assign_type(field, entry);
|
||||
|
||||
trace_seq_printf(s, "# %lx %s", field->ip, field->buf);
|
||||
if (entry->flags & TRACE_FLAG_CONT)
|
||||
trace_seq_print_cont(s, iter);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return TRACE_TYPE_HANDLED;
|
||||
}
|
||||
|
||||
#define SEQ_PUT_FIELD_RET(s, x) \
|
||||
do { \
|
||||
if (!trace_seq_putmem(s, &(x), sizeof(x))) \
|
||||
return 0; \
|
||||
} while (0)
|
||||
|
||||
#define SEQ_PUT_HEX_FIELD_RET(s, x) \
|
||||
do { \
|
||||
BUILD_BUG_ON(sizeof(x) > MAX_MEMHEX_BYTES); \
|
||||
if (!trace_seq_putmem_hex(s, &(x), sizeof(x))) \
|
||||
return 0; \
|
||||
} while (0)
|
||||
|
||||
static enum print_line_t print_hex_fmt(struct trace_iterator *iter)
|
||||
{
|
||||
struct trace_seq *s = &iter->seq;
|
||||
unsigned char newline = '\n';
|
||||
struct trace_entry *entry;
|
||||
int S, T;
|
||||
struct trace_event *event;
|
||||
|
||||
entry = iter->ent;
|
||||
|
||||
if (entry->type == TRACE_CONT)
|
||||
return TRACE_TYPE_HANDLED;
|
||||
|
||||
SEQ_PUT_HEX_FIELD_RET(s, entry->pid);
|
||||
SEQ_PUT_HEX_FIELD_RET(s, iter->cpu);
|
||||
SEQ_PUT_HEX_FIELD_RET(s, iter->ts);
|
||||
|
||||
switch (entry->type) {
|
||||
case TRACE_FN: {
|
||||
struct ftrace_entry *field;
|
||||
event = ftrace_find_event(entry->type);
|
||||
if (event && event->hex)
|
||||
event->hex(s, entry, 0);
|
||||
|
||||
trace_assign_type(field, entry);
|
||||
|
||||
SEQ_PUT_HEX_FIELD_RET(s, field->ip);
|
||||
SEQ_PUT_HEX_FIELD_RET(s, field->parent_ip);
|
||||
break;
|
||||
}
|
||||
case TRACE_CTX:
|
||||
case TRACE_WAKE: {
|
||||
struct ctx_switch_entry *field;
|
||||
|
||||
trace_assign_type(field, entry);
|
||||
|
||||
T = task_state_char(field->next_state);
|
||||
S = entry->type == TRACE_WAKE ? '+' :
|
||||
task_state_char(field->prev_state);
|
||||
SEQ_PUT_HEX_FIELD_RET(s, field->prev_pid);
|
||||
SEQ_PUT_HEX_FIELD_RET(s, field->prev_prio);
|
||||
SEQ_PUT_HEX_FIELD_RET(s, S);
|
||||
SEQ_PUT_HEX_FIELD_RET(s, field->next_cpu);
|
||||
SEQ_PUT_HEX_FIELD_RET(s, field->next_pid);
|
||||
SEQ_PUT_HEX_FIELD_RET(s, field->next_prio);
|
||||
SEQ_PUT_HEX_FIELD_RET(s, T);
|
||||
break;
|
||||
}
|
||||
case TRACE_SPECIAL:
|
||||
case TRACE_USER_STACK:
|
||||
case TRACE_STACK: {
|
||||
struct special_entry *field;
|
||||
|
||||
trace_assign_type(field, entry);
|
||||
|
||||
SEQ_PUT_HEX_FIELD_RET(s, field->arg1);
|
||||
SEQ_PUT_HEX_FIELD_RET(s, field->arg2);
|
||||
SEQ_PUT_HEX_FIELD_RET(s, field->arg3);
|
||||
break;
|
||||
}
|
||||
}
|
||||
SEQ_PUT_FIELD_RET(s, newline);
|
||||
|
||||
return TRACE_TYPE_HANDLED;
|
||||
|
@ -2282,9 +1659,6 @@ static enum print_line_t print_printk_msg_only(struct trace_iterator *iter)
|
|||
if (!ret)
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
|
||||
if (entry->flags & TRACE_FLAG_CONT)
|
||||
trace_seq_print_cont(s, iter);
|
||||
|
||||
return TRACE_TYPE_HANDLED;
|
||||
}
|
||||
|
||||
|
@ -2292,53 +1666,19 @@ static enum print_line_t print_bin_fmt(struct trace_iterator *iter)
|
|||
{
|
||||
struct trace_seq *s = &iter->seq;
|
||||
struct trace_entry *entry;
|
||||
struct trace_event *event;
|
||||
|
||||
entry = iter->ent;
|
||||
|
||||
if (entry->type == TRACE_CONT)
|
||||
return TRACE_TYPE_HANDLED;
|
||||
|
||||
SEQ_PUT_FIELD_RET(s, entry->pid);
|
||||
SEQ_PUT_FIELD_RET(s, entry->cpu);
|
||||
SEQ_PUT_FIELD_RET(s, iter->ts);
|
||||
|
||||
switch (entry->type) {
|
||||
case TRACE_FN: {
|
||||
struct ftrace_entry *field;
|
||||
event = ftrace_find_event(entry->type);
|
||||
if (event && event->binary)
|
||||
event->binary(s, entry, 0);
|
||||
|
||||
trace_assign_type(field, entry);
|
||||
|
||||
SEQ_PUT_FIELD_RET(s, field->ip);
|
||||
SEQ_PUT_FIELD_RET(s, field->parent_ip);
|
||||
break;
|
||||
}
|
||||
case TRACE_CTX: {
|
||||
struct ctx_switch_entry *field;
|
||||
|
||||
trace_assign_type(field, entry);
|
||||
|
||||
SEQ_PUT_FIELD_RET(s, field->prev_pid);
|
||||
SEQ_PUT_FIELD_RET(s, field->prev_prio);
|
||||
SEQ_PUT_FIELD_RET(s, field->prev_state);
|
||||
SEQ_PUT_FIELD_RET(s, field->next_pid);
|
||||
SEQ_PUT_FIELD_RET(s, field->next_prio);
|
||||
SEQ_PUT_FIELD_RET(s, field->next_state);
|
||||
break;
|
||||
}
|
||||
case TRACE_SPECIAL:
|
||||
case TRACE_USER_STACK:
|
||||
case TRACE_STACK: {
|
||||
struct special_entry *field;
|
||||
|
||||
trace_assign_type(field, entry);
|
||||
|
||||
SEQ_PUT_FIELD_RET(s, field->arg1);
|
||||
SEQ_PUT_FIELD_RET(s, field->arg2);
|
||||
SEQ_PUT_FIELD_RET(s, field->arg3);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
return TRACE_TYPE_HANDLED;
|
||||
}
|
||||
|
||||
static int trace_empty(struct trace_iterator *iter)
|
||||
|
@ -3013,6 +2353,7 @@ static int tracing_set_tracer(char *buf)
|
|||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
init_tracer_stat(t);
|
||||
|
||||
trace_branch_enable(tr);
|
||||
out:
|
||||
|
@ -3877,7 +3218,7 @@ __init static int tracer_alloc_buffers(void)
|
|||
#else
|
||||
current_trace = &nop_trace;
|
||||
#endif
|
||||
|
||||
init_tracer_stat(current_trace);
|
||||
/* All seems OK, enable tracing */
|
||||
tracing_disabled = 0;
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@ enum trace_type {
|
|||
TRACE_FN,
|
||||
TRACE_CTX,
|
||||
TRACE_WAKE,
|
||||
TRACE_CONT,
|
||||
TRACE_STACK,
|
||||
TRACE_PRINT,
|
||||
TRACE_SPECIAL,
|
||||
|
@ -34,7 +33,7 @@ enum trace_type {
|
|||
TRACE_KMEM_FREE,
|
||||
TRACE_POWER,
|
||||
|
||||
__TRACE_LAST_TYPE
|
||||
__TRACE_LAST_TYPE,
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -199,7 +198,6 @@ struct kmemtrace_free_entry {
|
|||
* NEED_RESCED - reschedule is requested
|
||||
* HARDIRQ - inside an interrupt handler
|
||||
* SOFTIRQ - inside a softirq handler
|
||||
* CONT - multiple entries hold the trace item
|
||||
*/
|
||||
enum trace_flag_type {
|
||||
TRACE_FLAG_IRQS_OFF = 0x01,
|
||||
|
@ -207,7 +205,6 @@ enum trace_flag_type {
|
|||
TRACE_FLAG_NEED_RESCHED = 0x04,
|
||||
TRACE_FLAG_HARDIRQ = 0x08,
|
||||
TRACE_FLAG_SOFTIRQ = 0x10,
|
||||
TRACE_FLAG_CONT = 0x20,
|
||||
};
|
||||
|
||||
#define TRACE_BUF_SIZE 1024
|
||||
|
@ -283,7 +280,6 @@ extern void __ftrace_bad_type(void);
|
|||
do { \
|
||||
IF_ASSIGN(var, ent, struct ftrace_entry, TRACE_FN); \
|
||||
IF_ASSIGN(var, ent, struct ctx_switch_entry, 0); \
|
||||
IF_ASSIGN(var, ent, struct trace_field_cont, TRACE_CONT); \
|
||||
IF_ASSIGN(var, ent, struct stack_entry, TRACE_STACK); \
|
||||
IF_ASSIGN(var, ent, struct userstack_entry, TRACE_USER_STACK);\
|
||||
IF_ASSIGN(var, ent, struct print_entry, TRACE_PRINT); \
|
||||
|
@ -365,6 +361,21 @@ struct tracer {
|
|||
struct tracer *next;
|
||||
int print_max;
|
||||
struct tracer_flags *flags;
|
||||
|
||||
/*
|
||||
* If you change one of the following on tracing runtime, recall
|
||||
* init_tracer_stat()
|
||||
*/
|
||||
|
||||
/* Iteration over statistic entries */
|
||||
void *(*stat_start)(void);
|
||||
void *(*stat_next)(void *prev, int idx);
|
||||
/* Compare two entries for sorting (optional) for stats */
|
||||
int (*stat_cmp)(void *p1, void *p2);
|
||||
/* Print a stat entry */
|
||||
int (*stat_show)(struct seq_file *s, void *p);
|
||||
/* Print the headers of your stat entries */
|
||||
int (*stat_headers)(struct seq_file *s);
|
||||
};
|
||||
|
||||
struct trace_seq {
|
||||
|
@ -450,6 +461,8 @@ void tracing_start_sched_switch_record(void);
|
|||
int register_tracer(struct tracer *type);
|
||||
void unregister_tracer(struct tracer *type);
|
||||
|
||||
void init_tracer_stat(struct tracer *trace);
|
||||
|
||||
extern unsigned long nsecs_to_usecs(unsigned long nsecs);
|
||||
|
||||
extern unsigned long tracing_max_latency;
|
||||
|
@ -481,10 +494,10 @@ struct tracer_switch_ops {
|
|||
void *private;
|
||||
struct tracer_switch_ops *next;
|
||||
};
|
||||
|
||||
char *trace_find_cmdline(int pid);
|
||||
#endif /* CONFIG_CONTEXT_SWITCH_TRACER */
|
||||
|
||||
extern char *trace_find_cmdline(int pid);
|
||||
|
||||
#ifdef CONFIG_DYNAMIC_FTRACE
|
||||
extern unsigned long ftrace_update_tot_cnt;
|
||||
#define DYN_FTRACE_TEST_NAME trace_selftest_dynamic_test_func
|
||||
|
@ -513,15 +526,6 @@ extern int trace_selftest_startup_branch(struct tracer *trace,
|
|||
#endif /* CONFIG_FTRACE_STARTUP_TEST */
|
||||
|
||||
extern void *head_page(struct trace_array_cpu *data);
|
||||
extern int trace_seq_printf(struct trace_seq *s, const char *fmt, ...);
|
||||
extern void trace_seq_print_cont(struct trace_seq *s,
|
||||
struct trace_iterator *iter);
|
||||
|
||||
extern int
|
||||
seq_print_ip_sym(struct trace_seq *s, unsigned long ip,
|
||||
unsigned long sym_flags);
|
||||
extern ssize_t trace_seq_to_user(struct trace_seq *s, char __user *ubuf,
|
||||
size_t cnt);
|
||||
extern long ns2usecs(cycle_t nsec);
|
||||
extern int
|
||||
trace_vprintk(unsigned long ip, int depth, const char *fmt, va_list args);
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include <linux/kallsyms.h>
|
||||
|
||||
#include "trace.h"
|
||||
#include "trace_output.h"
|
||||
|
||||
static struct trace_array *boot_trace;
|
||||
static bool pre_initcalls_finished;
|
||||
|
|
|
@ -14,12 +14,17 @@
|
|||
#include <linux/hash.h>
|
||||
#include <linux/fs.h>
|
||||
#include <asm/local.h>
|
||||
|
||||
#include "trace.h"
|
||||
#include "trace_output.h"
|
||||
|
||||
static struct tracer branch_trace;
|
||||
|
||||
#ifdef CONFIG_BRANCH_TRACER
|
||||
|
||||
static int branch_tracing_enabled __read_mostly;
|
||||
static DEFINE_MUTEX(branch_tracing_mutex);
|
||||
|
||||
static struct trace_array *branch_tracer;
|
||||
|
||||
static void
|
||||
|
@ -142,22 +147,50 @@ static void branch_trace_reset(struct trace_array *tr)
|
|||
stop_branch_trace(tr);
|
||||
}
|
||||
|
||||
struct tracer branch_trace __read_mostly =
|
||||
static int
|
||||
trace_print_print(struct trace_seq *s, struct trace_entry *entry, int flags)
|
||||
{
|
||||
.name = "branch",
|
||||
.init = branch_trace_init,
|
||||
.reset = branch_trace_reset,
|
||||
#ifdef CONFIG_FTRACE_SELFTEST
|
||||
.selftest = trace_selftest_startup_branch,
|
||||
#endif
|
||||
};
|
||||
struct print_entry *field;
|
||||
|
||||
__init static int init_branch_trace(void)
|
||||
{
|
||||
return register_tracer(&branch_trace);
|
||||
trace_assign_type(field, entry);
|
||||
|
||||
if (seq_print_ip_sym(s, field->ip, flags))
|
||||
goto partial;
|
||||
|
||||
if (trace_seq_printf(s, ": %s", field->buf))
|
||||
goto partial;
|
||||
|
||||
partial:
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
}
|
||||
|
||||
device_initcall(init_branch_trace);
|
||||
static int
|
||||
trace_branch_print(struct trace_seq *s, struct trace_entry *entry, int flags)
|
||||
{
|
||||
struct trace_branch *field;
|
||||
|
||||
trace_assign_type(field, entry);
|
||||
|
||||
if (trace_seq_printf(s, "[%s] %s:%s:%d\n",
|
||||
field->correct ? " ok " : " MISS ",
|
||||
field->func,
|
||||
field->file,
|
||||
field->line))
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static struct trace_event trace_branch_event = {
|
||||
.type = TRACE_BRANCH,
|
||||
.trace = trace_branch_print,
|
||||
.latency_trace = trace_branch_print,
|
||||
.raw = trace_nop_print,
|
||||
.hex = trace_nop_print,
|
||||
.binary = trace_nop_print,
|
||||
};
|
||||
|
||||
#else
|
||||
static inline
|
||||
void trace_likely_condition(struct ftrace_branch_data *f, int val, int expect)
|
||||
|
@ -183,65 +216,38 @@ void ftrace_likely_update(struct ftrace_branch_data *f, int val, int expect)
|
|||
}
|
||||
EXPORT_SYMBOL(ftrace_likely_update);
|
||||
|
||||
struct ftrace_pointer {
|
||||
void *start;
|
||||
void *stop;
|
||||
int hit;
|
||||
};
|
||||
extern unsigned long __start_annotated_branch_profile[];
|
||||
extern unsigned long __stop_annotated_branch_profile[];
|
||||
|
||||
static void *
|
||||
t_next(struct seq_file *m, void *v, loff_t *pos)
|
||||
static int annotated_branch_stat_headers(struct seq_file *m)
|
||||
{
|
||||
const struct ftrace_pointer *f = m->private;
|
||||
struct ftrace_branch_data *p = v;
|
||||
|
||||
(*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)
|
||||
{
|
||||
const struct ftrace_pointer *fp = m->private;
|
||||
struct ftrace_branch_data *p = v;
|
||||
const char *f;
|
||||
long percent;
|
||||
|
||||
if (v == (void *)1) {
|
||||
if (fp->hit)
|
||||
seq_printf(m, " miss hit %% ");
|
||||
else
|
||||
seq_printf(m, " correct incorrect %% ");
|
||||
seq_printf(m, " Function "
|
||||
seq_printf(m, " correct incorrect %% ");
|
||||
seq_printf(m, " Function "
|
||||
" File Line\n"
|
||||
" ------- --------- - "
|
||||
" -------- "
|
||||
" ---- ----\n");
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline long get_incorrect_percent(struct ftrace_branch_data *p)
|
||||
{
|
||||
long percent;
|
||||
|
||||
if (p->correct) {
|
||||
percent = p->incorrect * 100;
|
||||
percent /= p->correct + p->incorrect;
|
||||
} else
|
||||
percent = p->incorrect ? 100 : -1;
|
||||
|
||||
return percent;
|
||||
}
|
||||
|
||||
static int branch_stat_show(struct seq_file *m, void *v)
|
||||
{
|
||||
struct ftrace_branch_data *p = v;
|
||||
const char *f;
|
||||
long percent;
|
||||
|
||||
/* Only print the file, not the path */
|
||||
f = p->file + strlen(p->file);
|
||||
|
@ -252,11 +258,7 @@ static int t_show(struct seq_file *m, void *v)
|
|||
/*
|
||||
* The miss is overlayed on correct, and hit on incorrect.
|
||||
*/
|
||||
if (p->correct) {
|
||||
percent = p->incorrect * 100;
|
||||
percent /= p->correct + p->incorrect;
|
||||
} else
|
||||
percent = p->incorrect ? 100 : -1;
|
||||
percent = get_incorrect_percent(p);
|
||||
|
||||
seq_printf(m, "%8lu %8lu ", p->correct, p->incorrect);
|
||||
if (percent < 0)
|
||||
|
@ -267,76 +269,143 @@ static int t_show(struct seq_file *m, void *v)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static struct seq_operations tracing_likely_seq_ops = {
|
||||
.start = t_start,
|
||||
.next = t_next,
|
||||
.stop = t_stop,
|
||||
.show = t_show,
|
||||
};
|
||||
|
||||
static int tracing_branch_open(struct inode *inode, struct file *file)
|
||||
static void *annotated_branch_stat_start(void)
|
||||
{
|
||||
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;
|
||||
return __start_annotated_branch_profile;
|
||||
}
|
||||
|
||||
static const struct file_operations tracing_branch_fops = {
|
||||
.open = tracing_branch_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
};
|
||||
static void *
|
||||
annotated_branch_stat_next(void *v, int idx)
|
||||
{
|
||||
struct ftrace_branch_data *p = v;
|
||||
|
||||
++p;
|
||||
|
||||
if ((void *)p >= (void *)__stop_annotated_branch_profile)
|
||||
return NULL;
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
static int annotated_branch_stat_cmp(void *p1, void *p2)
|
||||
{
|
||||
struct ftrace_branch_data *a = p1;
|
||||
struct ftrace_branch_data *b = p2;
|
||||
|
||||
long percent_a, percent_b;
|
||||
|
||||
percent_a = get_incorrect_percent(a);
|
||||
percent_b = get_incorrect_percent(b);
|
||||
|
||||
if (percent_a < percent_b)
|
||||
return -1;
|
||||
if (percent_a > percent_b)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PROFILE_ALL_BRANCHES
|
||||
enum {
|
||||
TRACE_BRANCH_OPT_ALL = 0x1
|
||||
};
|
||||
|
||||
static struct tracer_opt branch_opts[] = {
|
||||
{ TRACER_OPT(stat_all_branch, TRACE_BRANCH_OPT_ALL) },
|
||||
{ }
|
||||
};
|
||||
|
||||
static struct tracer_flags branch_flags = {
|
||||
.val = 0,
|
||||
.opts = branch_opts
|
||||
};
|
||||
|
||||
extern unsigned long __start_branch_profile[];
|
||||
extern unsigned long __stop_branch_profile[];
|
||||
|
||||
static const struct ftrace_pointer ftrace_branch_pos = {
|
||||
.start = __start_branch_profile,
|
||||
.stop = __stop_branch_profile,
|
||||
.hit = 1,
|
||||
};
|
||||
|
||||
#endif /* CONFIG_PROFILE_ALL_BRANCHES */
|
||||
|
||||
extern unsigned long __start_annotated_branch_profile[];
|
||||
extern unsigned long __stop_annotated_branch_profile[];
|
||||
|
||||
static const struct ftrace_pointer ftrace_annotated_branch_pos = {
|
||||
.start = __start_annotated_branch_profile,
|
||||
.stop = __stop_annotated_branch_profile,
|
||||
};
|
||||
|
||||
static __init int ftrace_branch_init(void)
|
||||
static int all_branch_stat_headers(struct seq_file *m)
|
||||
{
|
||||
struct dentry *d_tracer;
|
||||
struct dentry *entry;
|
||||
|
||||
d_tracer = tracing_init_dentry();
|
||||
|
||||
entry = debugfs_create_file("profile_annotated_branch", 0444, d_tracer,
|
||||
(void *)&ftrace_annotated_branch_pos,
|
||||
&tracing_branch_fops);
|
||||
if (!entry)
|
||||
pr_warning("Could not create debugfs "
|
||||
"'profile_annotatet_branch' entry\n");
|
||||
|
||||
#ifdef CONFIG_PROFILE_ALL_BRANCHES
|
||||
entry = debugfs_create_file("profile_branch", 0444, d_tracer,
|
||||
(void *)&ftrace_branch_pos,
|
||||
&tracing_branch_fops);
|
||||
if (!entry)
|
||||
pr_warning("Could not create debugfs"
|
||||
" 'profile_branch' entry\n");
|
||||
#endif
|
||||
|
||||
seq_printf(m, " miss hit %% ");
|
||||
seq_printf(m, " Function "
|
||||
" File Line\n"
|
||||
" ------- --------- - "
|
||||
" -------- "
|
||||
" ---- ----\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
device_initcall(ftrace_branch_init);
|
||||
static void *all_branch_stat_start(void)
|
||||
{
|
||||
return __start_branch_profile;
|
||||
}
|
||||
|
||||
static void *
|
||||
all_branch_stat_next(void *v, int idx)
|
||||
{
|
||||
struct ftrace_branch_data *p = v;
|
||||
|
||||
++p;
|
||||
|
||||
if ((void *)p >= (void *)__stop_branch_profile)
|
||||
return NULL;
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
static int branch_set_flag(u32 old_flags, u32 bit, int set)
|
||||
{
|
||||
if (bit == TRACE_BRANCH_OPT_ALL) {
|
||||
if (set) {
|
||||
branch_trace.stat_headers = all_branch_stat_headers;
|
||||
branch_trace.stat_start = all_branch_stat_start;
|
||||
branch_trace.stat_next = all_branch_stat_next;
|
||||
branch_trace.stat_cmp = NULL;
|
||||
} else {
|
||||
branch_trace.stat_headers =
|
||||
annotated_branch_stat_headers;
|
||||
branch_trace.stat_start = annotated_branch_stat_start;
|
||||
branch_trace.stat_next = annotated_branch_stat_next;
|
||||
branch_trace.stat_cmp = annotated_branch_stat_cmp;
|
||||
}
|
||||
init_tracer_stat(&branch_trace);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* CONFIG_PROFILE_ALL_BRANCHES */
|
||||
|
||||
static struct tracer branch_trace __read_mostly =
|
||||
{
|
||||
.name = "branch",
|
||||
#ifdef CONFIG_BRANCH_TRACER
|
||||
.init = branch_trace_init,
|
||||
.reset = branch_trace_reset,
|
||||
#ifdef CONFIG_FTRACE_SELFTEST
|
||||
.selftest = trace_selftest_startup_branch,
|
||||
#endif /* CONFIG_FTRACE_SELFTEST */
|
||||
#endif /* CONFIG_BRANCH_TRACER */
|
||||
.stat_start = annotated_branch_stat_start,
|
||||
.stat_next = annotated_branch_stat_next,
|
||||
.stat_show = branch_stat_show,
|
||||
.stat_headers = annotated_branch_stat_headers,
|
||||
.stat_cmp = annotated_branch_stat_cmp,
|
||||
#ifdef CONFIG_PROFILE_ALL_BRANCHES
|
||||
.flags = &branch_flags,
|
||||
.set_flag = branch_set_flag,
|
||||
#endif
|
||||
};
|
||||
|
||||
__init static int init_branch_trace(void)
|
||||
{
|
||||
#ifdef CONFIG_BRANCH_TRACER
|
||||
int ret;
|
||||
ret = register_ftrace_event(&trace_branch_event);
|
||||
if (!ret) {
|
||||
printk(KERN_WARNING "Warning: could not register branch events\n");
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
return register_tracer(&branch_trace);
|
||||
}
|
||||
device_initcall(init_branch_trace);
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include <linux/fs.h>
|
||||
|
||||
#include "trace.h"
|
||||
#include "trace_output.h"
|
||||
|
||||
#define TRACE_GRAPH_INDENT 2
|
||||
|
||||
|
@ -589,9 +590,6 @@ print_graph_comment(struct print_entry *trace, struct trace_seq *s,
|
|||
if (!ret)
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
|
||||
if (ent->flags & TRACE_FLAG_CONT)
|
||||
trace_seq_print_cont(s, iter);
|
||||
|
||||
/* Strip ending newline */
|
||||
if (s->buffer[s->len - 1] == '\n') {
|
||||
s->buffer[s->len - 1] = '\0';
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include <asm/ds.h>
|
||||
|
||||
#include "trace.h"
|
||||
#include "trace_output.h"
|
||||
|
||||
|
||||
#define SIZEOF_BTS (1 << 13)
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include <linux/pci.h>
|
||||
|
||||
#include "trace.h"
|
||||
#include "trace_output.h"
|
||||
|
||||
struct header_iter {
|
||||
struct pci_dev *dev;
|
||||
|
@ -262,9 +263,6 @@ static enum print_line_t mmio_print_mark(struct trace_iterator *iter)
|
|||
if (!ret)
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
|
||||
if (entry->flags & TRACE_FLAG_CONT)
|
||||
trace_seq_print_cont(s, iter);
|
||||
|
||||
return TRACE_TYPE_HANDLED;
|
||||
}
|
||||
|
||||
|
|
832
kernel/trace/trace_output.c
Normal file
832
kernel/trace/trace_output.c
Normal file
|
@ -0,0 +1,832 @@
|
|||
/*
|
||||
* trace_output.c
|
||||
*
|
||||
* Copyright (C) 2008 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/ftrace.h>
|
||||
|
||||
#include "trace_output.h"
|
||||
|
||||
/* must be a power of 2 */
|
||||
#define EVENT_HASHSIZE 128
|
||||
|
||||
static DEFINE_MUTEX(trace_event_mutex);
|
||||
static struct hlist_head event_hash[EVENT_HASHSIZE] __read_mostly;
|
||||
|
||||
static int next_event_type = __TRACE_LAST_TYPE + 1;
|
||||
|
||||
/**
|
||||
* trace_seq_printf - sequence printing of trace information
|
||||
* @s: trace sequence descriptor
|
||||
* @fmt: printf format string
|
||||
*
|
||||
* The tracer may use either sequence operations or its own
|
||||
* copy to user routines. To simplify formating of a trace
|
||||
* trace_seq_printf is used to store strings into a special
|
||||
* buffer (@s). Then the output may be either used by
|
||||
* the sequencer or pulled into another buffer.
|
||||
*/
|
||||
int
|
||||
trace_seq_printf(struct trace_seq *s, const char *fmt, ...)
|
||||
{
|
||||
int len = (PAGE_SIZE - 1) - s->len;
|
||||
va_list ap;
|
||||
int ret;
|
||||
|
||||
if (!len)
|
||||
return 0;
|
||||
|
||||
va_start(ap, fmt);
|
||||
ret = vsnprintf(s->buffer + s->len, len, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
/* If we can't write it all, don't bother writing anything */
|
||||
if (ret >= len)
|
||||
return 0;
|
||||
|
||||
s->len += ret;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
/**
|
||||
* trace_seq_puts - trace sequence printing of simple string
|
||||
* @s: trace sequence descriptor
|
||||
* @str: simple string to record
|
||||
*
|
||||
* The tracer may use either the sequence operations or its own
|
||||
* copy to user routines. This function records a simple string
|
||||
* into a special buffer (@s) for later retrieval by a sequencer
|
||||
* or other mechanism.
|
||||
*/
|
||||
int trace_seq_puts(struct trace_seq *s, const char *str)
|
||||
{
|
||||
int len = strlen(str);
|
||||
|
||||
if (len > ((PAGE_SIZE - 1) - s->len))
|
||||
return 0;
|
||||
|
||||
memcpy(s->buffer + s->len, str, len);
|
||||
s->len += len;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int trace_seq_putc(struct trace_seq *s, unsigned char c)
|
||||
{
|
||||
if (s->len >= (PAGE_SIZE - 1))
|
||||
return 0;
|
||||
|
||||
s->buffer[s->len++] = c;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int trace_seq_putmem(struct trace_seq *s, void *mem, size_t len)
|
||||
{
|
||||
if (len > ((PAGE_SIZE - 1) - s->len))
|
||||
return 0;
|
||||
|
||||
memcpy(s->buffer + s->len, mem, len);
|
||||
s->len += len;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int trace_seq_putmem_hex(struct trace_seq *s, void *mem, size_t len)
|
||||
{
|
||||
unsigned char hex[HEX_CHARS];
|
||||
unsigned char *data = mem;
|
||||
int i, j;
|
||||
|
||||
#ifdef __BIG_ENDIAN
|
||||
for (i = 0, j = 0; i < len; i++) {
|
||||
#else
|
||||
for (i = len-1, j = 0; i >= 0; i--) {
|
||||
#endif
|
||||
hex[j++] = hex_asc_hi(data[i]);
|
||||
hex[j++] = hex_asc_lo(data[i]);
|
||||
}
|
||||
hex[j++] = ' ';
|
||||
|
||||
return trace_seq_putmem(s, hex, j);
|
||||
}
|
||||
|
||||
int trace_seq_path(struct trace_seq *s, struct path *path)
|
||||
{
|
||||
unsigned char *p;
|
||||
|
||||
if (s->len >= (PAGE_SIZE - 1))
|
||||
return 0;
|
||||
p = d_path(path, s->buffer + s->len, PAGE_SIZE - s->len);
|
||||
if (!IS_ERR(p)) {
|
||||
p = mangle_path(s->buffer + s->len, p, "\n");
|
||||
if (p) {
|
||||
s->len = p - s->buffer;
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
s->buffer[s->len++] = '?';
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_KRETPROBES
|
||||
static inline const char *kretprobed(const char *name)
|
||||
{
|
||||
static const char tramp_name[] = "kretprobe_trampoline";
|
||||
int size = sizeof(tramp_name);
|
||||
|
||||
if (strncmp(tramp_name, name, size) == 0)
|
||||
return "[unknown/kretprobe'd]";
|
||||
return name;
|
||||
}
|
||||
#else
|
||||
static inline const char *kretprobed(const char *name)
|
||||
{
|
||||
return name;
|
||||
}
|
||||
#endif /* CONFIG_KRETPROBES */
|
||||
|
||||
static int
|
||||
seq_print_sym_short(struct trace_seq *s, const char *fmt, unsigned long address)
|
||||
{
|
||||
#ifdef CONFIG_KALLSYMS
|
||||
char str[KSYM_SYMBOL_LEN];
|
||||
const char *name;
|
||||
|
||||
kallsyms_lookup(address, NULL, NULL, NULL, str);
|
||||
|
||||
name = kretprobed(str);
|
||||
|
||||
return trace_seq_printf(s, fmt, name);
|
||||
#endif
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
seq_print_sym_offset(struct trace_seq *s, const char *fmt,
|
||||
unsigned long address)
|
||||
{
|
||||
#ifdef CONFIG_KALLSYMS
|
||||
char str[KSYM_SYMBOL_LEN];
|
||||
const char *name;
|
||||
|
||||
sprint_symbol(str, address);
|
||||
name = kretprobed(str);
|
||||
|
||||
return trace_seq_printf(s, fmt, name);
|
||||
#endif
|
||||
return 1;
|
||||
}
|
||||
|
||||
#ifndef CONFIG_64BIT
|
||||
# define IP_FMT "%08lx"
|
||||
#else
|
||||
# define IP_FMT "%016lx"
|
||||
#endif
|
||||
|
||||
int seq_print_user_ip(struct trace_seq *s, struct mm_struct *mm,
|
||||
unsigned long ip, unsigned long sym_flags)
|
||||
{
|
||||
struct file *file = NULL;
|
||||
unsigned long vmstart = 0;
|
||||
int ret = 1;
|
||||
|
||||
if (mm) {
|
||||
const struct vm_area_struct *vma;
|
||||
|
||||
down_read(&mm->mmap_sem);
|
||||
vma = find_vma(mm, ip);
|
||||
if (vma) {
|
||||
file = vma->vm_file;
|
||||
vmstart = vma->vm_start;
|
||||
}
|
||||
if (file) {
|
||||
ret = trace_seq_path(s, &file->f_path);
|
||||
if (ret)
|
||||
ret = trace_seq_printf(s, "[+0x%lx]",
|
||||
ip - vmstart);
|
||||
}
|
||||
up_read(&mm->mmap_sem);
|
||||
}
|
||||
if (ret && ((sym_flags & TRACE_ITER_SYM_ADDR) || !file))
|
||||
ret = trace_seq_printf(s, " <" IP_FMT ">", ip);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
seq_print_userip_objs(const struct userstack_entry *entry, struct trace_seq *s,
|
||||
unsigned long sym_flags)
|
||||
{
|
||||
struct mm_struct *mm = NULL;
|
||||
int ret = 1;
|
||||
unsigned int i;
|
||||
|
||||
if (trace_flags & TRACE_ITER_SYM_USEROBJ) {
|
||||
struct task_struct *task;
|
||||
/*
|
||||
* we do the lookup on the thread group leader,
|
||||
* since individual threads might have already quit!
|
||||
*/
|
||||
rcu_read_lock();
|
||||
task = find_task_by_vpid(entry->ent.tgid);
|
||||
if (task)
|
||||
mm = get_task_mm(task);
|
||||
rcu_read_unlock();
|
||||
}
|
||||
|
||||
for (i = 0; i < FTRACE_STACK_ENTRIES; i++) {
|
||||
unsigned long ip = entry->caller[i];
|
||||
|
||||
if (ip == ULONG_MAX || !ret)
|
||||
break;
|
||||
if (i && ret)
|
||||
ret = trace_seq_puts(s, " <- ");
|
||||
if (!ip) {
|
||||
if (ret)
|
||||
ret = trace_seq_puts(s, "??");
|
||||
continue;
|
||||
}
|
||||
if (!ret)
|
||||
break;
|
||||
if (ret)
|
||||
ret = seq_print_user_ip(s, mm, ip, sym_flags);
|
||||
}
|
||||
|
||||
if (mm)
|
||||
mmput(mm);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
seq_print_ip_sym(struct trace_seq *s, unsigned long ip, unsigned long sym_flags)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!ip)
|
||||
return trace_seq_printf(s, "0");
|
||||
|
||||
if (sym_flags & TRACE_ITER_SYM_OFFSET)
|
||||
ret = seq_print_sym_offset(s, "%s", ip);
|
||||
else
|
||||
ret = seq_print_sym_short(s, "%s", ip);
|
||||
|
||||
if (!ret)
|
||||
return 0;
|
||||
|
||||
if (sym_flags & TRACE_ITER_SYM_ADDR)
|
||||
ret = trace_seq_printf(s, " <" IP_FMT ">", ip);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const char state_to_char[] = TASK_STATE_TO_CHAR_STR;
|
||||
|
||||
static int task_state_char(unsigned long state)
|
||||
{
|
||||
int bit = state ? __ffs(state) + 1 : 0;
|
||||
|
||||
return bit < sizeof(state_to_char) - 1 ? state_to_char[bit] : '?';
|
||||
}
|
||||
|
||||
/**
|
||||
* ftrace_find_event - find a registered event
|
||||
* @type: the type of event to look for
|
||||
*
|
||||
* Returns an event of type @type otherwise NULL
|
||||
*/
|
||||
struct trace_event *ftrace_find_event(int type)
|
||||
{
|
||||
struct trace_event *event;
|
||||
struct hlist_node *n;
|
||||
unsigned key;
|
||||
|
||||
key = type & (EVENT_HASHSIZE - 1);
|
||||
|
||||
hlist_for_each_entry_rcu(event, n, &event_hash[key], node) {
|
||||
if (event->type == type)
|
||||
return event;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* register_ftrace_event - register output for an event type
|
||||
* @event: the event type to register
|
||||
*
|
||||
* Event types are stored in a hash and this hash is used to
|
||||
* find a way to print an event. If the @event->type is set
|
||||
* then it will use that type, otherwise it will assign a
|
||||
* type to use.
|
||||
*
|
||||
* If you assign your own type, please make sure it is added
|
||||
* to the trace_type enum in trace.h, to avoid collisions
|
||||
* with the dynamic types.
|
||||
*
|
||||
* Returns the event type number or zero on error.
|
||||
*/
|
||||
int register_ftrace_event(struct trace_event *event)
|
||||
{
|
||||
unsigned key;
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&trace_event_mutex);
|
||||
|
||||
if (!event->type)
|
||||
event->type = next_event_type++;
|
||||
else if (event->type > __TRACE_LAST_TYPE) {
|
||||
printk(KERN_WARNING "Need to add type to trace.h\n");
|
||||
WARN_ON(1);
|
||||
}
|
||||
|
||||
if (ftrace_find_event(event->type))
|
||||
goto out;
|
||||
|
||||
key = event->type & (EVENT_HASHSIZE - 1);
|
||||
|
||||
hlist_add_head_rcu(&event->node, &event_hash[key]);
|
||||
|
||||
ret = event->type;
|
||||
out:
|
||||
mutex_unlock(&trace_event_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* unregister_ftrace_event - remove a no longer used event
|
||||
* @event: the event to remove
|
||||
*/
|
||||
int unregister_ftrace_event(struct trace_event *event)
|
||||
{
|
||||
mutex_lock(&trace_event_mutex);
|
||||
hlist_del(&event->node);
|
||||
mutex_unlock(&trace_event_mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Standard events
|
||||
*/
|
||||
|
||||
int
|
||||
trace_nop_print(struct trace_seq *s, struct trace_entry *entry, int flags)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* TRACE_FN */
|
||||
static int
|
||||
trace_fn_latency(struct trace_seq *s, struct trace_entry *entry, int flags)
|
||||
{
|
||||
struct ftrace_entry *field;
|
||||
|
||||
trace_assign_type(field, entry);
|
||||
|
||||
if (!seq_print_ip_sym(s, field->ip, flags))
|
||||
goto partial;
|
||||
if (!trace_seq_puts(s, " ("))
|
||||
goto partial;
|
||||
if (!seq_print_ip_sym(s, field->parent_ip, flags))
|
||||
goto partial;
|
||||
if (!trace_seq_puts(s, ")\n"))
|
||||
goto partial;
|
||||
|
||||
return 0;
|
||||
|
||||
partial:
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
}
|
||||
|
||||
static int
|
||||
trace_fn_trace(struct trace_seq *s, struct trace_entry *entry, int flags)
|
||||
{
|
||||
struct ftrace_entry *field;
|
||||
|
||||
trace_assign_type(field, entry);
|
||||
|
||||
if (!seq_print_ip_sym(s, field->ip, flags))
|
||||
goto partial;
|
||||
|
||||
if ((flags & TRACE_ITER_PRINT_PARENT) && field->parent_ip) {
|
||||
if (!trace_seq_printf(s, " <-"))
|
||||
goto partial;
|
||||
if (!seq_print_ip_sym(s,
|
||||
field->parent_ip,
|
||||
flags))
|
||||
goto partial;
|
||||
}
|
||||
if (!trace_seq_printf(s, "\n"))
|
||||
goto partial;
|
||||
|
||||
return 0;
|
||||
|
||||
partial:
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
}
|
||||
|
||||
static int
|
||||
trace_fn_raw(struct trace_seq *s, struct trace_entry *entry, int flags)
|
||||
{
|
||||
struct ftrace_entry *field;
|
||||
|
||||
trace_assign_type(field, entry);
|
||||
|
||||
if (trace_seq_printf(s, "%x %x\n",
|
||||
field->ip,
|
||||
field->parent_ip))
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
trace_fn_hex(struct trace_seq *s, struct trace_entry *entry, int flags)
|
||||
{
|
||||
struct ftrace_entry *field;
|
||||
|
||||
trace_assign_type(field, entry);
|
||||
|
||||
SEQ_PUT_HEX_FIELD_RET(s, field->ip);
|
||||
SEQ_PUT_HEX_FIELD_RET(s, field->parent_ip);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
trace_fn_bin(struct trace_seq *s, struct trace_entry *entry, int flags)
|
||||
{
|
||||
struct ftrace_entry *field;
|
||||
|
||||
trace_assign_type(field, entry);
|
||||
|
||||
SEQ_PUT_FIELD_RET(s, field->ip);
|
||||
SEQ_PUT_FIELD_RET(s, field->parent_ip);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct trace_event trace_fn_event = {
|
||||
.type = TRACE_FN,
|
||||
.trace = trace_fn_trace,
|
||||
.latency_trace = trace_fn_latency,
|
||||
.raw = trace_fn_raw,
|
||||
.hex = trace_fn_hex,
|
||||
.binary = trace_fn_bin,
|
||||
};
|
||||
|
||||
/* TRACE_CTX an TRACE_WAKE */
|
||||
static int
|
||||
trace_ctxwake_print(struct trace_seq *s, struct trace_entry *entry, int flags,
|
||||
char *delim)
|
||||
{
|
||||
struct ctx_switch_entry *field;
|
||||
char *comm;
|
||||
int S, T;
|
||||
|
||||
trace_assign_type(field, entry);
|
||||
|
||||
T = task_state_char(field->next_state);
|
||||
S = task_state_char(field->prev_state);
|
||||
comm = trace_find_cmdline(field->next_pid);
|
||||
if (trace_seq_printf(s, " %5d:%3d:%c %s [%03d] %5d:%3d:%c %s\n",
|
||||
field->prev_pid,
|
||||
field->prev_prio,
|
||||
S, delim,
|
||||
field->next_cpu,
|
||||
field->next_pid,
|
||||
field->next_prio,
|
||||
T, comm))
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
trace_ctx_print(struct trace_seq *s, struct trace_entry *entry, int flags)
|
||||
{
|
||||
return trace_ctxwake_print(s, entry, flags, "==>");
|
||||
}
|
||||
|
||||
static int
|
||||
trace_wake_print(struct trace_seq *s, struct trace_entry *entry, int flags)
|
||||
{
|
||||
return trace_ctxwake_print(s, entry, flags, " +");
|
||||
}
|
||||
|
||||
static int
|
||||
trace_ctxwake_raw(struct trace_seq *s, struct trace_entry *entry, int flags,
|
||||
char S)
|
||||
{
|
||||
struct ctx_switch_entry *field;
|
||||
int T;
|
||||
|
||||
trace_assign_type(field, entry);
|
||||
|
||||
if (!S)
|
||||
task_state_char(field->prev_state);
|
||||
T = task_state_char(field->next_state);
|
||||
if (trace_seq_printf(s, "%d %d %c %d %d %d %c\n",
|
||||
field->prev_pid,
|
||||
field->prev_prio,
|
||||
S,
|
||||
field->next_cpu,
|
||||
field->next_pid,
|
||||
field->next_prio,
|
||||
T))
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
trace_ctx_raw(struct trace_seq *s, struct trace_entry *entry, int flags)
|
||||
{
|
||||
return trace_ctxwake_raw(s, entry, flags, 0);
|
||||
}
|
||||
|
||||
static int
|
||||
trace_wake_raw(struct trace_seq *s, struct trace_entry *entry, int flags)
|
||||
{
|
||||
return trace_ctxwake_raw(s, entry, flags, '+');
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
trace_ctxwake_hex(struct trace_seq *s, struct trace_entry *entry, int flags,
|
||||
char S)
|
||||
{
|
||||
struct ctx_switch_entry *field;
|
||||
int T;
|
||||
|
||||
trace_assign_type(field, entry);
|
||||
|
||||
if (!S)
|
||||
task_state_char(field->prev_state);
|
||||
T = task_state_char(field->next_state);
|
||||
|
||||
SEQ_PUT_HEX_FIELD_RET(s, field->prev_pid);
|
||||
SEQ_PUT_HEX_FIELD_RET(s, field->prev_prio);
|
||||
SEQ_PUT_HEX_FIELD_RET(s, S);
|
||||
SEQ_PUT_HEX_FIELD_RET(s, field->next_cpu);
|
||||
SEQ_PUT_HEX_FIELD_RET(s, field->next_pid);
|
||||
SEQ_PUT_HEX_FIELD_RET(s, field->next_prio);
|
||||
SEQ_PUT_HEX_FIELD_RET(s, T);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
trace_ctx_hex(struct trace_seq *s, struct trace_entry *entry, int flags)
|
||||
{
|
||||
return trace_ctxwake_hex(s, entry, flags, 0);
|
||||
}
|
||||
|
||||
static int
|
||||
trace_wake_hex(struct trace_seq *s, struct trace_entry *entry, int flags)
|
||||
{
|
||||
return trace_ctxwake_hex(s, entry, flags, '+');
|
||||
}
|
||||
|
||||
static int
|
||||
trace_ctxwake_bin(struct trace_seq *s, struct trace_entry *entry, int flags)
|
||||
{
|
||||
struct ctx_switch_entry *field;
|
||||
|
||||
trace_assign_type(field, entry);
|
||||
|
||||
SEQ_PUT_FIELD_RET(s, field->prev_pid);
|
||||
SEQ_PUT_FIELD_RET(s, field->prev_prio);
|
||||
SEQ_PUT_FIELD_RET(s, field->prev_state);
|
||||
SEQ_PUT_FIELD_RET(s, field->next_pid);
|
||||
SEQ_PUT_FIELD_RET(s, field->next_prio);
|
||||
SEQ_PUT_FIELD_RET(s, field->next_state);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct trace_event trace_ctx_event = {
|
||||
.type = TRACE_CTX,
|
||||
.trace = trace_ctx_print,
|
||||
.latency_trace = trace_ctx_print,
|
||||
.raw = trace_ctx_raw,
|
||||
.hex = trace_ctx_hex,
|
||||
.binary = trace_ctxwake_bin,
|
||||
};
|
||||
|
||||
static struct trace_event trace_wake_event = {
|
||||
.type = TRACE_WAKE,
|
||||
.trace = trace_wake_print,
|
||||
.latency_trace = trace_wake_print,
|
||||
.raw = trace_wake_raw,
|
||||
.hex = trace_wake_hex,
|
||||
.binary = trace_ctxwake_bin,
|
||||
};
|
||||
|
||||
/* TRACE_SPECIAL */
|
||||
static int
|
||||
trace_special_print(struct trace_seq *s, struct trace_entry *entry, int flags)
|
||||
{
|
||||
struct special_entry *field;
|
||||
|
||||
trace_assign_type(field, entry);
|
||||
|
||||
if (trace_seq_printf(s, "# %ld %ld %ld\n",
|
||||
field->arg1,
|
||||
field->arg2,
|
||||
field->arg3))
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
trace_special_hex(struct trace_seq *s, struct trace_entry *entry, int flags)
|
||||
{
|
||||
struct special_entry *field;
|
||||
|
||||
trace_assign_type(field, entry);
|
||||
|
||||
SEQ_PUT_HEX_FIELD_RET(s, field->arg1);
|
||||
SEQ_PUT_HEX_FIELD_RET(s, field->arg2);
|
||||
SEQ_PUT_HEX_FIELD_RET(s, field->arg3);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
trace_special_bin(struct trace_seq *s, struct trace_entry *entry, int flags)
|
||||
{
|
||||
struct special_entry *field;
|
||||
|
||||
trace_assign_type(field, entry);
|
||||
|
||||
SEQ_PUT_FIELD_RET(s, field->arg1);
|
||||
SEQ_PUT_FIELD_RET(s, field->arg2);
|
||||
SEQ_PUT_FIELD_RET(s, field->arg3);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct trace_event trace_special_event = {
|
||||
.type = TRACE_SPECIAL,
|
||||
.trace = trace_special_print,
|
||||
.latency_trace = trace_special_print,
|
||||
.raw = trace_special_print,
|
||||
.hex = trace_special_hex,
|
||||
.binary = trace_special_bin,
|
||||
};
|
||||
|
||||
/* TRACE_STACK */
|
||||
|
||||
static int
|
||||
trace_stack_print(struct trace_seq *s, struct trace_entry *entry, int flags)
|
||||
{
|
||||
struct stack_entry *field;
|
||||
int i;
|
||||
|
||||
trace_assign_type(field, entry);
|
||||
|
||||
for (i = 0; i < FTRACE_STACK_ENTRIES; i++) {
|
||||
if (i) {
|
||||
if (trace_seq_puts(s, " <= "))
|
||||
goto partial;
|
||||
|
||||
if (seq_print_ip_sym(s, field->caller[i], flags))
|
||||
goto partial;
|
||||
}
|
||||
if (trace_seq_puts(s, "\n"))
|
||||
goto partial;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
partial:
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
}
|
||||
|
||||
static struct trace_event trace_stack_event = {
|
||||
.type = TRACE_STACK,
|
||||
.trace = trace_stack_print,
|
||||
.latency_trace = trace_stack_print,
|
||||
.raw = trace_special_print,
|
||||
.hex = trace_special_hex,
|
||||
.binary = trace_special_bin,
|
||||
};
|
||||
|
||||
/* TRACE_USER_STACK */
|
||||
static int
|
||||
trace_user_stack_print(struct trace_seq *s, struct trace_entry *entry,
|
||||
int flags)
|
||||
{
|
||||
struct userstack_entry *field;
|
||||
|
||||
trace_assign_type(field, entry);
|
||||
|
||||
if (seq_print_userip_objs(field, s, flags))
|
||||
goto partial;
|
||||
|
||||
if (trace_seq_putc(s, '\n'))
|
||||
goto partial;
|
||||
|
||||
return 0;
|
||||
|
||||
partial:
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
}
|
||||
|
||||
static struct trace_event trace_user_stack_event = {
|
||||
.type = TRACE_USER_STACK,
|
||||
.trace = trace_user_stack_print,
|
||||
.latency_trace = trace_user_stack_print,
|
||||
.raw = trace_special_print,
|
||||
.hex = trace_special_hex,
|
||||
.binary = trace_special_bin,
|
||||
};
|
||||
|
||||
/* TRACE_PRINT */
|
||||
static int
|
||||
trace_print_print(struct trace_seq *s, struct trace_entry *entry, int flags)
|
||||
{
|
||||
struct print_entry *field;
|
||||
|
||||
trace_assign_type(field, entry);
|
||||
|
||||
if (seq_print_ip_sym(s, field->ip, flags))
|
||||
goto partial;
|
||||
|
||||
if (trace_seq_printf(s, ": %s", field->buf))
|
||||
goto partial;
|
||||
|
||||
return 0;
|
||||
|
||||
partial:
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
}
|
||||
|
||||
static int
|
||||
trace_print_raw(struct trace_seq *s, struct trace_entry *entry, int flags)
|
||||
{
|
||||
struct print_entry *field;
|
||||
|
||||
trace_assign_type(field, entry);
|
||||
|
||||
if (seq_print_ip_sym(s, field->ip, flags))
|
||||
goto partial;
|
||||
|
||||
if (trace_seq_printf(s, "# %lx %s", field->ip, field->buf))
|
||||
goto partial;
|
||||
|
||||
return 0;
|
||||
|
||||
partial:
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
}
|
||||
|
||||
static struct trace_event trace_print_event = {
|
||||
.type = TRACE_PRINT,
|
||||
.trace = trace_print_print,
|
||||
.latency_trace = trace_print_print,
|
||||
.raw = trace_print_raw,
|
||||
.hex = trace_nop_print,
|
||||
.binary = trace_nop_print,
|
||||
};
|
||||
|
||||
static struct trace_event *events[] __initdata = {
|
||||
&trace_fn_event,
|
||||
&trace_ctx_event,
|
||||
&trace_wake_event,
|
||||
&trace_special_event,
|
||||
&trace_stack_event,
|
||||
&trace_user_stack_event,
|
||||
&trace_print_event,
|
||||
NULL
|
||||
};
|
||||
|
||||
__init static int init_events(void)
|
||||
{
|
||||
struct trace_event *event;
|
||||
int i, ret;
|
||||
|
||||
for (i = 0; events[i]; i++) {
|
||||
event = events[i];
|
||||
|
||||
ret = register_ftrace_event(event);
|
||||
if (!ret) {
|
||||
printk(KERN_WARNING "event %d failed to register\n",
|
||||
event->type);
|
||||
WARN_ON_ONCE(1);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
device_initcall(init_events);
|
59
kernel/trace/trace_output.h
Normal file
59
kernel/trace/trace_output.h
Normal file
|
@ -0,0 +1,59 @@
|
|||
#ifndef __TRACE_EVENTS_H
|
||||
#define __TRACE_EVENTS_H
|
||||
|
||||
#include "trace.h"
|
||||
|
||||
typedef int (*trace_print_func)(struct trace_seq *s, struct trace_entry *entry,
|
||||
int flags);
|
||||
|
||||
struct trace_event {
|
||||
struct hlist_node node;
|
||||
int type;
|
||||
trace_print_func trace;
|
||||
trace_print_func latency_trace;
|
||||
trace_print_func raw;
|
||||
trace_print_func hex;
|
||||
trace_print_func binary;
|
||||
};
|
||||
|
||||
extern int trace_seq_printf(struct trace_seq *s, const char *fmt, ...);
|
||||
extern int
|
||||
seq_print_ip_sym(struct trace_seq *s, unsigned long ip,
|
||||
unsigned long sym_flags);
|
||||
extern ssize_t trace_seq_to_user(struct trace_seq *s, char __user *ubuf,
|
||||
size_t cnt);
|
||||
int trace_seq_puts(struct trace_seq *s, const char *str);
|
||||
int trace_seq_putc(struct trace_seq *s, unsigned char c);
|
||||
int trace_seq_putmem(struct trace_seq *s, void *mem, size_t len);
|
||||
int trace_seq_putmem_hex(struct trace_seq *s, void *mem, size_t len);
|
||||
int trace_seq_path(struct trace_seq *s, struct path *path);
|
||||
int seq_print_userip_objs(const struct userstack_entry *entry,
|
||||
struct trace_seq *s, unsigned long sym_flags);
|
||||
int seq_print_user_ip(struct trace_seq *s, struct mm_struct *mm,
|
||||
unsigned long ip, unsigned long sym_flags);
|
||||
|
||||
struct trace_event *ftrace_find_event(int type);
|
||||
int register_ftrace_event(struct trace_event *event);
|
||||
int unregister_ftrace_event(struct trace_event *event);
|
||||
|
||||
int
|
||||
trace_nop_print(struct trace_seq *s, struct trace_entry *entry, int flags);
|
||||
|
||||
#define MAX_MEMHEX_BYTES 8
|
||||
#define HEX_CHARS (MAX_MEMHEX_BYTES*2 + 1)
|
||||
|
||||
#define SEQ_PUT_FIELD_RET(s, x) \
|
||||
do { \
|
||||
if (!trace_seq_putmem(s, &(x), sizeof(x))) \
|
||||
return 0; \
|
||||
} while (0)
|
||||
|
||||
#define SEQ_PUT_HEX_FIELD_RET(s, x) \
|
||||
do { \
|
||||
BUILD_BUG_ON(sizeof(x) > MAX_MEMHEX_BYTES); \
|
||||
if (!trace_seq_putmem_hex(s, &(x), sizeof(x))) \
|
||||
return 0; \
|
||||
} while (0)
|
||||
|
||||
#endif
|
||||
|
|
@ -16,6 +16,7 @@
|
|||
#include <linux/module.h>
|
||||
|
||||
#include "trace.h"
|
||||
#include "trace_output.h"
|
||||
|
||||
static struct trace_array *power_trace;
|
||||
static int __read_mostly trace_power_enabled;
|
||||
|
|
|
@ -9,7 +9,6 @@ static inline int trace_valid_entry(struct trace_entry *entry)
|
|||
case TRACE_FN:
|
||||
case TRACE_CTX:
|
||||
case TRACE_WAKE:
|
||||
case TRACE_CONT:
|
||||
case TRACE_STACK:
|
||||
case TRACE_PRINT:
|
||||
case TRACE_SPECIAL:
|
||||
|
|
251
kernel/trace/trace_stat.c
Normal file
251
kernel/trace/trace_stat.c
Normal file
|
@ -0,0 +1,251 @@
|
|||
/*
|
||||
* Infrastructure for statistic tracing (histogram output).
|
||||
*
|
||||
* Copyright (C) 2008 Frederic Weisbecker <fweisbec@gmail.com>
|
||||
*
|
||||
* Based on the code from trace_branch.c which is
|
||||
* Copyright (C) 2008 Steven Rostedt <srostedt@redhat.com>
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include <linux/list.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include "trace.h"
|
||||
|
||||
|
||||
/* List of stat entries from a tracer */
|
||||
struct trace_stat_list {
|
||||
struct list_head list;
|
||||
void *stat;
|
||||
};
|
||||
|
||||
static struct trace_stat_list stat_list;
|
||||
|
||||
/*
|
||||
* This is a copy of the current tracer to avoid racy
|
||||
* and dangerous output while the current tracer is
|
||||
* switched.
|
||||
*/
|
||||
static struct tracer current_tracer;
|
||||
|
||||
/*
|
||||
* Protect both the current tracer and the global
|
||||
* stat list.
|
||||
*/
|
||||
static DEFINE_MUTEX(stat_list_mutex);
|
||||
|
||||
|
||||
static void reset_stat_list(void)
|
||||
{
|
||||
struct trace_stat_list *node;
|
||||
struct list_head *next;
|
||||
|
||||
if (list_empty(&stat_list.list))
|
||||
return;
|
||||
|
||||
node = list_entry(stat_list.list.next, struct trace_stat_list, list);
|
||||
next = node->list.next;
|
||||
|
||||
while (&node->list != next) {
|
||||
kfree(node);
|
||||
node = list_entry(next, struct trace_stat_list, list);
|
||||
}
|
||||
kfree(node);
|
||||
|
||||
INIT_LIST_HEAD(&stat_list.list);
|
||||
}
|
||||
|
||||
void init_tracer_stat(struct tracer *trace)
|
||||
{
|
||||
mutex_lock(&stat_list_mutex);
|
||||
current_tracer = *trace;
|
||||
mutex_unlock(&stat_list_mutex);
|
||||
}
|
||||
|
||||
/*
|
||||
* For tracers that don't provide a stat_cmp callback.
|
||||
* This one will force an immediate insertion on tail of
|
||||
* the list.
|
||||
*/
|
||||
static int dummy_cmp(void *p1, void *p2)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize the stat list at each trace_stat file opening.
|
||||
* All of these copies and sorting are required on all opening
|
||||
* since the stats could have changed between two file sessions.
|
||||
*/
|
||||
static int stat_seq_init(void)
|
||||
{
|
||||
struct trace_stat_list *iter_entry, *new_entry;
|
||||
void *prev_stat;
|
||||
int ret = 0;
|
||||
int i;
|
||||
|
||||
mutex_lock(&stat_list_mutex);
|
||||
reset_stat_list();
|
||||
|
||||
if (!current_tracer.stat_start || !current_tracer.stat_next ||
|
||||
!current_tracer.stat_show)
|
||||
goto exit;
|
||||
|
||||
if (!current_tracer.stat_cmp)
|
||||
current_tracer.stat_cmp = dummy_cmp;
|
||||
|
||||
/*
|
||||
* The first entry. Actually this is the second, but the first
|
||||
* one (the stat_list head) is pointless.
|
||||
*/
|
||||
new_entry = kmalloc(sizeof(struct trace_stat_list), GFP_KERNEL);
|
||||
if (!new_entry) {
|
||||
ret = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
INIT_LIST_HEAD(&new_entry->list);
|
||||
list_add(&new_entry->list, &stat_list.list);
|
||||
new_entry->stat = current_tracer.stat_start();
|
||||
|
||||
prev_stat = new_entry->stat;
|
||||
|
||||
/*
|
||||
* Iterate over the tracer stat entries and store them in a sorted
|
||||
* list.
|
||||
*/
|
||||
for (i = 1; ; i++) {
|
||||
new_entry = kmalloc(sizeof(struct trace_stat_list), GFP_KERNEL);
|
||||
if (!new_entry) {
|
||||
ret = -ENOMEM;
|
||||
goto exit_free_list;
|
||||
}
|
||||
|
||||
INIT_LIST_HEAD(&new_entry->list);
|
||||
new_entry->stat = current_tracer.stat_next(prev_stat, i);
|
||||
|
||||
/* End of insertion */
|
||||
if (!new_entry->stat)
|
||||
break;
|
||||
|
||||
list_for_each_entry(iter_entry, &stat_list.list, list) {
|
||||
/* Insertion with a descendent sorting */
|
||||
if (current_tracer.stat_cmp(new_entry->stat,
|
||||
iter_entry->stat) > 0) {
|
||||
|
||||
list_add_tail(&new_entry->list,
|
||||
&iter_entry->list);
|
||||
break;
|
||||
|
||||
/* The current smaller value */
|
||||
} else if (list_is_last(&iter_entry->list,
|
||||
&stat_list.list)) {
|
||||
list_add(&new_entry->list, &iter_entry->list);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
prev_stat = new_entry->stat;
|
||||
}
|
||||
exit:
|
||||
mutex_unlock(&stat_list_mutex);
|
||||
return ret;
|
||||
|
||||
exit_free_list:
|
||||
reset_stat_list();
|
||||
mutex_unlock(&stat_list_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static void *stat_seq_start(struct seq_file *s, loff_t *pos)
|
||||
{
|
||||
struct trace_stat_list *l = (struct trace_stat_list *)s->private;
|
||||
|
||||
/* Prevent from tracer switch or stat_list modification */
|
||||
mutex_lock(&stat_list_mutex);
|
||||
|
||||
/* If we are in the beginning of the file, print the headers */
|
||||
if (!*pos && current_tracer.stat_headers)
|
||||
current_tracer.stat_headers(s);
|
||||
|
||||
return seq_list_start(&l->list, *pos);
|
||||
}
|
||||
|
||||
static void *stat_seq_next(struct seq_file *s, void *p, loff_t *pos)
|
||||
{
|
||||
struct trace_stat_list *l = (struct trace_stat_list *)s->private;
|
||||
|
||||
return seq_list_next(p, &l->list, pos);
|
||||
}
|
||||
|
||||
static void stat_seq_stop(struct seq_file *m, void *p)
|
||||
{
|
||||
mutex_unlock(&stat_list_mutex);
|
||||
}
|
||||
|
||||
static int stat_seq_show(struct seq_file *s, void *v)
|
||||
{
|
||||
struct trace_stat_list *l = list_entry(v, struct trace_stat_list, list);
|
||||
return current_tracer.stat_show(s, l->stat);
|
||||
}
|
||||
|
||||
static const struct seq_operations trace_stat_seq_ops = {
|
||||
.start = stat_seq_start,
|
||||
.next = stat_seq_next,
|
||||
.stop = stat_seq_stop,
|
||||
.show = stat_seq_show
|
||||
};
|
||||
|
||||
static int tracing_stat_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = seq_open(file, &trace_stat_seq_ops);
|
||||
if (!ret) {
|
||||
struct seq_file *m = file->private_data;
|
||||
m->private = &stat_list;
|
||||
ret = stat_seq_init();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Avoid consuming memory with our now useless list.
|
||||
*/
|
||||
static int tracing_stat_release(struct inode *i, struct file *f)
|
||||
{
|
||||
mutex_lock(&stat_list_mutex);
|
||||
reset_stat_list();
|
||||
mutex_unlock(&stat_list_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct file_operations tracing_stat_fops = {
|
||||
.open = tracing_stat_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = tracing_stat_release
|
||||
};
|
||||
|
||||
static int __init tracing_stat_init(void)
|
||||
{
|
||||
struct dentry *d_tracing;
|
||||
struct dentry *entry;
|
||||
|
||||
INIT_LIST_HEAD(&stat_list.list);
|
||||
d_tracing = tracing_init_dentry();
|
||||
|
||||
entry = debugfs_create_file("trace_stat", 0444, d_tracing,
|
||||
NULL,
|
||||
&tracing_stat_fops);
|
||||
if (!entry)
|
||||
pr_warning("Could not create debugfs "
|
||||
"'trace_stat' entry\n");
|
||||
return 0;
|
||||
}
|
||||
fs_initcall(tracing_stat_init);
|
Loading…
Reference in a new issue