forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1367 lines
32 KiB
1367 lines
32 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
/* |
|
* |
|
* Function graph tracer. |
|
* Copyright (c) 2008-2009 Frederic Weisbecker <[email protected]> |
|
* Mostly borrowed from function tracer which |
|
* is Copyright (c) Steven Rostedt <[email protected]> |
|
* |
|
*/ |
|
#include <linux/uaccess.h> |
|
#include <linux/ftrace.h> |
|
#include <linux/interrupt.h> |
|
#include <linux/slab.h> |
|
#include <linux/fs.h> |
|
|
|
#include "trace.h" |
|
#include "trace_output.h" |
|
|
|
/* When set, irq functions will be ignored */ |
|
static int ftrace_graph_skip_irqs; |
|
|
|
struct fgraph_cpu_data { |
|
pid_t last_pid; |
|
int depth; |
|
int depth_irq; |
|
int ignore; |
|
unsigned long enter_funcs[FTRACE_RETFUNC_DEPTH]; |
|
}; |
|
|
|
struct fgraph_data { |
|
struct fgraph_cpu_data __percpu *cpu_data; |
|
|
|
/* Place to preserve last processed entry. */ |
|
struct ftrace_graph_ent_entry ent; |
|
struct ftrace_graph_ret_entry ret; |
|
int failed; |
|
int cpu; |
|
}; |
|
|
|
#define TRACE_GRAPH_INDENT 2 |
|
|
|
unsigned int fgraph_max_depth; |
|
|
|
static struct tracer_opt trace_opts[] = { |
|
/* Display overruns? (for self-debug purpose) */ |
|
{ TRACER_OPT(funcgraph-overrun, TRACE_GRAPH_PRINT_OVERRUN) }, |
|
/* Display CPU ? */ |
|
{ TRACER_OPT(funcgraph-cpu, TRACE_GRAPH_PRINT_CPU) }, |
|
/* Display Overhead ? */ |
|
{ TRACER_OPT(funcgraph-overhead, TRACE_GRAPH_PRINT_OVERHEAD) }, |
|
/* Display proc name/pid */ |
|
{ TRACER_OPT(funcgraph-proc, TRACE_GRAPH_PRINT_PROC) }, |
|
/* Display duration of execution */ |
|
{ TRACER_OPT(funcgraph-duration, TRACE_GRAPH_PRINT_DURATION) }, |
|
/* Display absolute time of an entry */ |
|
{ TRACER_OPT(funcgraph-abstime, TRACE_GRAPH_PRINT_ABS_TIME) }, |
|
/* Display interrupts */ |
|
{ TRACER_OPT(funcgraph-irqs, TRACE_GRAPH_PRINT_IRQS) }, |
|
/* Display function name after trailing } */ |
|
{ TRACER_OPT(funcgraph-tail, TRACE_GRAPH_PRINT_TAIL) }, |
|
/* Include sleep time (scheduled out) between entry and return */ |
|
{ TRACER_OPT(sleep-time, TRACE_GRAPH_SLEEP_TIME) }, |
|
|
|
#ifdef CONFIG_FUNCTION_PROFILER |
|
/* Include time within nested functions */ |
|
{ TRACER_OPT(graph-time, TRACE_GRAPH_GRAPH_TIME) }, |
|
#endif |
|
|
|
{ } /* Empty entry */ |
|
}; |
|
|
|
static struct tracer_flags tracer_flags = { |
|
/* Don't display overruns, proc, or tail by default */ |
|
.val = TRACE_GRAPH_PRINT_CPU | TRACE_GRAPH_PRINT_OVERHEAD | |
|
TRACE_GRAPH_PRINT_DURATION | TRACE_GRAPH_PRINT_IRQS | |
|
TRACE_GRAPH_SLEEP_TIME | TRACE_GRAPH_GRAPH_TIME, |
|
.opts = trace_opts |
|
}; |
|
|
|
static struct trace_array *graph_array; |
|
|
|
/* |
|
* DURATION column is being also used to display IRQ signs, |
|
* following values are used by print_graph_irq and others |
|
* to fill in space into DURATION column. |
|
*/ |
|
enum { |
|
FLAGS_FILL_FULL = 1 << TRACE_GRAPH_PRINT_FILL_SHIFT, |
|
FLAGS_FILL_START = 2 << TRACE_GRAPH_PRINT_FILL_SHIFT, |
|
FLAGS_FILL_END = 3 << TRACE_GRAPH_PRINT_FILL_SHIFT, |
|
}; |
|
|
|
static void |
|
print_graph_duration(struct trace_array *tr, unsigned long long duration, |
|
struct trace_seq *s, u32 flags); |
|
|
|
int __trace_graph_entry(struct trace_array *tr, |
|
struct ftrace_graph_ent *trace, |
|
unsigned int trace_ctx) |
|
{ |
|
struct trace_event_call *call = &event_funcgraph_entry; |
|
struct ring_buffer_event *event; |
|
struct trace_buffer *buffer = tr->array_buffer.buffer; |
|
struct ftrace_graph_ent_entry *entry; |
|
|
|
event = trace_buffer_lock_reserve(buffer, TRACE_GRAPH_ENT, |
|
sizeof(*entry), trace_ctx); |
|
if (!event) |
|
return 0; |
|
entry = ring_buffer_event_data(event); |
|
entry->graph_ent = *trace; |
|
if (!call_filter_check_discard(call, entry, buffer, event)) |
|
trace_buffer_unlock_commit_nostack(buffer, event); |
|
|
|
return 1; |
|
} |
|
|
|
static inline int ftrace_graph_ignore_irqs(void) |
|
{ |
|
if (!ftrace_graph_skip_irqs || trace_recursion_test(TRACE_IRQ_BIT)) |
|
return 0; |
|
|
|
return in_irq(); |
|
} |
|
|
|
int trace_graph_entry(struct ftrace_graph_ent *trace) |
|
{ |
|
struct trace_array *tr = graph_array; |
|
struct trace_array_cpu *data; |
|
unsigned long flags; |
|
unsigned int trace_ctx; |
|
long disabled; |
|
int ret; |
|
int cpu; |
|
|
|
if (trace_recursion_test(TRACE_GRAPH_NOTRACE_BIT)) |
|
return 0; |
|
|
|
/* |
|
* Do not trace a function if it's filtered by set_graph_notrace. |
|
* Make the index of ret stack negative to indicate that it should |
|
* ignore further functions. But it needs its own ret stack entry |
|
* to recover the original index in order to continue tracing after |
|
* returning from the function. |
|
*/ |
|
if (ftrace_graph_notrace_addr(trace->func)) { |
|
trace_recursion_set(TRACE_GRAPH_NOTRACE_BIT); |
|
/* |
|
* Need to return 1 to have the return called |
|
* that will clear the NOTRACE bit. |
|
*/ |
|
return 1; |
|
} |
|
|
|
if (!ftrace_trace_task(tr)) |
|
return 0; |
|
|
|
if (ftrace_graph_ignore_func(trace)) |
|
return 0; |
|
|
|
if (ftrace_graph_ignore_irqs()) |
|
return 0; |
|
|
|
/* |
|
* Stop here if tracing_threshold is set. We only write function return |
|
* events to the ring buffer. |
|
*/ |
|
if (tracing_thresh) |
|
return 1; |
|
|
|
local_irq_save(flags); |
|
cpu = raw_smp_processor_id(); |
|
data = per_cpu_ptr(tr->array_buffer.data, cpu); |
|
disabled = atomic_inc_return(&data->disabled); |
|
if (likely(disabled == 1)) { |
|
trace_ctx = tracing_gen_ctx_flags(flags); |
|
ret = __trace_graph_entry(tr, trace, trace_ctx); |
|
} else { |
|
ret = 0; |
|
} |
|
|
|
atomic_dec(&data->disabled); |
|
local_irq_restore(flags); |
|
|
|
return ret; |
|
} |
|
|
|
static void |
|
__trace_graph_function(struct trace_array *tr, |
|
unsigned long ip, unsigned int trace_ctx) |
|
{ |
|
u64 time = trace_clock_local(); |
|
struct ftrace_graph_ent ent = { |
|
.func = ip, |
|
.depth = 0, |
|
}; |
|
struct ftrace_graph_ret ret = { |
|
.func = ip, |
|
.depth = 0, |
|
.calltime = time, |
|
.rettime = time, |
|
}; |
|
|
|
__trace_graph_entry(tr, &ent, trace_ctx); |
|
__trace_graph_return(tr, &ret, trace_ctx); |
|
} |
|
|
|
void |
|
trace_graph_function(struct trace_array *tr, |
|
unsigned long ip, unsigned long parent_ip, |
|
unsigned int trace_ctx) |
|
{ |
|
__trace_graph_function(tr, ip, trace_ctx); |
|
} |
|
|
|
void __trace_graph_return(struct trace_array *tr, |
|
struct ftrace_graph_ret *trace, |
|
unsigned int trace_ctx) |
|
{ |
|
struct trace_event_call *call = &event_funcgraph_exit; |
|
struct ring_buffer_event *event; |
|
struct trace_buffer *buffer = tr->array_buffer.buffer; |
|
struct ftrace_graph_ret_entry *entry; |
|
|
|
event = trace_buffer_lock_reserve(buffer, TRACE_GRAPH_RET, |
|
sizeof(*entry), trace_ctx); |
|
if (!event) |
|
return; |
|
entry = ring_buffer_event_data(event); |
|
entry->ret = *trace; |
|
if (!call_filter_check_discard(call, entry, buffer, event)) |
|
trace_buffer_unlock_commit_nostack(buffer, event); |
|
} |
|
|
|
void trace_graph_return(struct ftrace_graph_ret *trace) |
|
{ |
|
struct trace_array *tr = graph_array; |
|
struct trace_array_cpu *data; |
|
unsigned long flags; |
|
unsigned int trace_ctx; |
|
long disabled; |
|
int cpu; |
|
|
|
ftrace_graph_addr_finish(trace); |
|
|
|
if (trace_recursion_test(TRACE_GRAPH_NOTRACE_BIT)) { |
|
trace_recursion_clear(TRACE_GRAPH_NOTRACE_BIT); |
|
return; |
|
} |
|
|
|
local_irq_save(flags); |
|
cpu = raw_smp_processor_id(); |
|
data = per_cpu_ptr(tr->array_buffer.data, cpu); |
|
disabled = atomic_inc_return(&data->disabled); |
|
if (likely(disabled == 1)) { |
|
trace_ctx = tracing_gen_ctx_flags(flags); |
|
__trace_graph_return(tr, trace, trace_ctx); |
|
} |
|
atomic_dec(&data->disabled); |
|
local_irq_restore(flags); |
|
} |
|
|
|
void set_graph_array(struct trace_array *tr) |
|
{ |
|
graph_array = tr; |
|
|
|
/* Make graph_array visible before we start tracing */ |
|
|
|
smp_mb(); |
|
} |
|
|
|
static void trace_graph_thresh_return(struct ftrace_graph_ret *trace) |
|
{ |
|
ftrace_graph_addr_finish(trace); |
|
|
|
if (trace_recursion_test(TRACE_GRAPH_NOTRACE_BIT)) { |
|
trace_recursion_clear(TRACE_GRAPH_NOTRACE_BIT); |
|
return; |
|
} |
|
|
|
if (tracing_thresh && |
|
(trace->rettime - trace->calltime < tracing_thresh)) |
|
return; |
|
else |
|
trace_graph_return(trace); |
|
} |
|
|
|
static struct fgraph_ops funcgraph_thresh_ops = { |
|
.entryfunc = &trace_graph_entry, |
|
.retfunc = &trace_graph_thresh_return, |
|
}; |
|
|
|
static struct fgraph_ops funcgraph_ops = { |
|
.entryfunc = &trace_graph_entry, |
|
.retfunc = &trace_graph_return, |
|
}; |
|
|
|
static int graph_trace_init(struct trace_array *tr) |
|
{ |
|
int ret; |
|
|
|
set_graph_array(tr); |
|
if (tracing_thresh) |
|
ret = register_ftrace_graph(&funcgraph_thresh_ops); |
|
else |
|
ret = register_ftrace_graph(&funcgraph_ops); |
|
if (ret) |
|
return ret; |
|
tracing_start_cmdline_record(); |
|
|
|
return 0; |
|
} |
|
|
|
static void graph_trace_reset(struct trace_array *tr) |
|
{ |
|
tracing_stop_cmdline_record(); |
|
if (tracing_thresh) |
|
unregister_ftrace_graph(&funcgraph_thresh_ops); |
|
else |
|
unregister_ftrace_graph(&funcgraph_ops); |
|
} |
|
|
|
static int graph_trace_update_thresh(struct trace_array *tr) |
|
{ |
|
graph_trace_reset(tr); |
|
return graph_trace_init(tr); |
|
} |
|
|
|
static int max_bytes_for_cpu; |
|
|
|
static void print_graph_cpu(struct trace_seq *s, int cpu) |
|
{ |
|
/* |
|
* Start with a space character - to make it stand out |
|
* to the right a bit when trace output is pasted into |
|
* email: |
|
*/ |
|
trace_seq_printf(s, " %*d) ", max_bytes_for_cpu, cpu); |
|
} |
|
|
|
#define TRACE_GRAPH_PROCINFO_LENGTH 14 |
|
|
|
static void print_graph_proc(struct trace_seq *s, pid_t pid) |
|
{ |
|
char comm[TASK_COMM_LEN]; |
|
/* sign + log10(MAX_INT) + '\0' */ |
|
char pid_str[11]; |
|
int spaces = 0; |
|
int len; |
|
int i; |
|
|
|
trace_find_cmdline(pid, comm); |
|
comm[7] = '\0'; |
|
sprintf(pid_str, "%d", pid); |
|
|
|
/* 1 stands for the "-" character */ |
|
len = strlen(comm) + strlen(pid_str) + 1; |
|
|
|
if (len < TRACE_GRAPH_PROCINFO_LENGTH) |
|
spaces = TRACE_GRAPH_PROCINFO_LENGTH - len; |
|
|
|
/* First spaces to align center */ |
|
for (i = 0; i < spaces / 2; i++) |
|
trace_seq_putc(s, ' '); |
|
|
|
trace_seq_printf(s, "%s-%s", comm, pid_str); |
|
|
|
/* Last spaces to align center */ |
|
for (i = 0; i < spaces - (spaces / 2); i++) |
|
trace_seq_putc(s, ' '); |
|
} |
|
|
|
|
|
static void print_graph_lat_fmt(struct trace_seq *s, struct trace_entry *entry) |
|
{ |
|
trace_seq_putc(s, ' '); |
|
trace_print_lat_fmt(s, entry); |
|
trace_seq_puts(s, " | "); |
|
} |
|
|
|
/* If the pid changed since the last trace, output this event */ |
|
static void |
|
verif_pid(struct trace_seq *s, pid_t pid, int cpu, struct fgraph_data *data) |
|
{ |
|
pid_t prev_pid; |
|
pid_t *last_pid; |
|
|
|
if (!data) |
|
return; |
|
|
|
last_pid = &(per_cpu_ptr(data->cpu_data, cpu)->last_pid); |
|
|
|
if (*last_pid == pid) |
|
return; |
|
|
|
prev_pid = *last_pid; |
|
*last_pid = pid; |
|
|
|
if (prev_pid == -1) |
|
return; |
|
/* |
|
* Context-switch trace line: |
|
|
|
------------------------------------------ |
|
| 1) migration/0--1 => sshd-1755 |
|
------------------------------------------ |
|
|
|
*/ |
|
trace_seq_puts(s, " ------------------------------------------\n"); |
|
print_graph_cpu(s, cpu); |
|
print_graph_proc(s, prev_pid); |
|
trace_seq_puts(s, " => "); |
|
print_graph_proc(s, pid); |
|
trace_seq_puts(s, "\n ------------------------------------------\n\n"); |
|
} |
|
|
|
static struct ftrace_graph_ret_entry * |
|
get_return_for_leaf(struct trace_iterator *iter, |
|
struct ftrace_graph_ent_entry *curr) |
|
{ |
|
struct fgraph_data *data = iter->private; |
|
struct ring_buffer_iter *ring_iter = NULL; |
|
struct ring_buffer_event *event; |
|
struct ftrace_graph_ret_entry *next; |
|
|
|
/* |
|
* If the previous output failed to write to the seq buffer, |
|
* then we just reuse the data from before. |
|
*/ |
|
if (data && data->failed) { |
|
curr = &data->ent; |
|
next = &data->ret; |
|
} else { |
|
|
|
ring_iter = trace_buffer_iter(iter, iter->cpu); |
|
|
|
/* First peek to compare current entry and the next one */ |
|
if (ring_iter) |
|
event = ring_buffer_iter_peek(ring_iter, NULL); |
|
else { |
|
/* |
|
* We need to consume the current entry to see |
|
* the next one. |
|
*/ |
|
ring_buffer_consume(iter->array_buffer->buffer, iter->cpu, |
|
NULL, NULL); |
|
event = ring_buffer_peek(iter->array_buffer->buffer, iter->cpu, |
|
NULL, NULL); |
|
} |
|
|
|
if (!event) |
|
return NULL; |
|
|
|
next = ring_buffer_event_data(event); |
|
|
|
if (data) { |
|
/* |
|
* Save current and next entries for later reference |
|
* if the output fails. |
|
*/ |
|
data->ent = *curr; |
|
/* |
|
* If the next event is not a return type, then |
|
* we only care about what type it is. Otherwise we can |
|
* safely copy the entire event. |
|
*/ |
|
if (next->ent.type == TRACE_GRAPH_RET) |
|
data->ret = *next; |
|
else |
|
data->ret.ent.type = next->ent.type; |
|
} |
|
} |
|
|
|
if (next->ent.type != TRACE_GRAPH_RET) |
|
return NULL; |
|
|
|
if (curr->ent.pid != next->ent.pid || |
|
curr->graph_ent.func != next->ret.func) |
|
return NULL; |
|
|
|
/* this is a leaf, now advance the iterator */ |
|
if (ring_iter) |
|
ring_buffer_iter_advance(ring_iter); |
|
|
|
return next; |
|
} |
|
|
|
static void print_graph_abs_time(u64 t, struct trace_seq *s) |
|
{ |
|
unsigned long usecs_rem; |
|
|
|
usecs_rem = do_div(t, NSEC_PER_SEC); |
|
usecs_rem /= 1000; |
|
|
|
trace_seq_printf(s, "%5lu.%06lu | ", |
|
(unsigned long)t, usecs_rem); |
|
} |
|
|
|
static void |
|
print_graph_rel_time(struct trace_iterator *iter, struct trace_seq *s) |
|
{ |
|
unsigned long long usecs; |
|
|
|
usecs = iter->ts - iter->array_buffer->time_start; |
|
do_div(usecs, NSEC_PER_USEC); |
|
|
|
trace_seq_printf(s, "%9llu us | ", usecs); |
|
} |
|
|
|
static void |
|
print_graph_irq(struct trace_iterator *iter, unsigned long addr, |
|
enum trace_type type, int cpu, pid_t pid, u32 flags) |
|
{ |
|
struct trace_array *tr = iter->tr; |
|
struct trace_seq *s = &iter->seq; |
|
struct trace_entry *ent = iter->ent; |
|
|
|
if (addr < (unsigned long)__irqentry_text_start || |
|
addr >= (unsigned long)__irqentry_text_end) |
|
return; |
|
|
|
if (tr->trace_flags & TRACE_ITER_CONTEXT_INFO) { |
|
/* Absolute time */ |
|
if (flags & TRACE_GRAPH_PRINT_ABS_TIME) |
|
print_graph_abs_time(iter->ts, s); |
|
|
|
/* Relative time */ |
|
if (flags & TRACE_GRAPH_PRINT_REL_TIME) |
|
print_graph_rel_time(iter, s); |
|
|
|
/* Cpu */ |
|
if (flags & TRACE_GRAPH_PRINT_CPU) |
|
print_graph_cpu(s, cpu); |
|
|
|
/* Proc */ |
|
if (flags & TRACE_GRAPH_PRINT_PROC) { |
|
print_graph_proc(s, pid); |
|
trace_seq_puts(s, " | "); |
|
} |
|
|
|
/* Latency format */ |
|
if (tr->trace_flags & TRACE_ITER_LATENCY_FMT) |
|
print_graph_lat_fmt(s, ent); |
|
} |
|
|
|
/* No overhead */ |
|
print_graph_duration(tr, 0, s, flags | FLAGS_FILL_START); |
|
|
|
if (type == TRACE_GRAPH_ENT) |
|
trace_seq_puts(s, "==========>"); |
|
else |
|
trace_seq_puts(s, "<=========="); |
|
|
|
print_graph_duration(tr, 0, s, flags | FLAGS_FILL_END); |
|
trace_seq_putc(s, '\n'); |
|
} |
|
|
|
void |
|
trace_print_graph_duration(unsigned long long duration, struct trace_seq *s) |
|
{ |
|
unsigned long nsecs_rem = do_div(duration, 1000); |
|
/* log10(ULONG_MAX) + '\0' */ |
|
char usecs_str[21]; |
|
char nsecs_str[5]; |
|
int len; |
|
int i; |
|
|
|
sprintf(usecs_str, "%lu", (unsigned long) duration); |
|
|
|
/* Print msecs */ |
|
trace_seq_printf(s, "%s", usecs_str); |
|
|
|
len = strlen(usecs_str); |
|
|
|
/* Print nsecs (we don't want to exceed 7 numbers) */ |
|
if (len < 7) { |
|
size_t slen = min_t(size_t, sizeof(nsecs_str), 8UL - len); |
|
|
|
snprintf(nsecs_str, slen, "%03lu", nsecs_rem); |
|
trace_seq_printf(s, ".%s", nsecs_str); |
|
len += strlen(nsecs_str) + 1; |
|
} |
|
|
|
trace_seq_puts(s, " us "); |
|
|
|
/* Print remaining spaces to fit the row's width */ |
|
for (i = len; i < 8; i++) |
|
trace_seq_putc(s, ' '); |
|
} |
|
|
|
static void |
|
print_graph_duration(struct trace_array *tr, unsigned long long duration, |
|
struct trace_seq *s, u32 flags) |
|
{ |
|
if (!(flags & TRACE_GRAPH_PRINT_DURATION) || |
|
!(tr->trace_flags & TRACE_ITER_CONTEXT_INFO)) |
|
return; |
|
|
|
/* No real adata, just filling the column with spaces */ |
|
switch (flags & TRACE_GRAPH_PRINT_FILL_MASK) { |
|
case FLAGS_FILL_FULL: |
|
trace_seq_puts(s, " | "); |
|
return; |
|
case FLAGS_FILL_START: |
|
trace_seq_puts(s, " "); |
|
return; |
|
case FLAGS_FILL_END: |
|
trace_seq_puts(s, " |"); |
|
return; |
|
} |
|
|
|
/* Signal a overhead of time execution to the output */ |
|
if (flags & TRACE_GRAPH_PRINT_OVERHEAD) |
|
trace_seq_printf(s, "%c ", trace_find_mark(duration)); |
|
else |
|
trace_seq_puts(s, " "); |
|
|
|
trace_print_graph_duration(duration, s); |
|
trace_seq_puts(s, "| "); |
|
} |
|
|
|
/* Case of a leaf function on its call entry */ |
|
static enum print_line_t |
|
print_graph_entry_leaf(struct trace_iterator *iter, |
|
struct ftrace_graph_ent_entry *entry, |
|
struct ftrace_graph_ret_entry *ret_entry, |
|
struct trace_seq *s, u32 flags) |
|
{ |
|
struct fgraph_data *data = iter->private; |
|
struct trace_array *tr = iter->tr; |
|
struct ftrace_graph_ret *graph_ret; |
|
struct ftrace_graph_ent *call; |
|
unsigned long long duration; |
|
int cpu = iter->cpu; |
|
int i; |
|
|
|
graph_ret = &ret_entry->ret; |
|
call = &entry->graph_ent; |
|
duration = graph_ret->rettime - graph_ret->calltime; |
|
|
|
if (data) { |
|
struct fgraph_cpu_data *cpu_data; |
|
|
|
cpu_data = per_cpu_ptr(data->cpu_data, cpu); |
|
|
|
/* |
|
* Comments display at + 1 to depth. Since |
|
* this is a leaf function, keep the comments |
|
* equal to this depth. |
|
*/ |
|
cpu_data->depth = call->depth - 1; |
|
|
|
/* No need to keep this function around for this depth */ |
|
if (call->depth < FTRACE_RETFUNC_DEPTH && |
|
!WARN_ON_ONCE(call->depth < 0)) |
|
cpu_data->enter_funcs[call->depth] = 0; |
|
} |
|
|
|
/* Overhead and duration */ |
|
print_graph_duration(tr, duration, s, flags); |
|
|
|
/* Function */ |
|
for (i = 0; i < call->depth * TRACE_GRAPH_INDENT; i++) |
|
trace_seq_putc(s, ' '); |
|
|
|
trace_seq_printf(s, "%ps();\n", (void *)call->func); |
|
|
|
print_graph_irq(iter, graph_ret->func, TRACE_GRAPH_RET, |
|
cpu, iter->ent->pid, flags); |
|
|
|
return trace_handle_return(s); |
|
} |
|
|
|
static enum print_line_t |
|
print_graph_entry_nested(struct trace_iterator *iter, |
|
struct ftrace_graph_ent_entry *entry, |
|
struct trace_seq *s, int cpu, u32 flags) |
|
{ |
|
struct ftrace_graph_ent *call = &entry->graph_ent; |
|
struct fgraph_data *data = iter->private; |
|
struct trace_array *tr = iter->tr; |
|
int i; |
|
|
|
if (data) { |
|
struct fgraph_cpu_data *cpu_data; |
|
int cpu = iter->cpu; |
|
|
|
cpu_data = per_cpu_ptr(data->cpu_data, cpu); |
|
cpu_data->depth = call->depth; |
|
|
|
/* Save this function pointer to see if the exit matches */ |
|
if (call->depth < FTRACE_RETFUNC_DEPTH && |
|
!WARN_ON_ONCE(call->depth < 0)) |
|
cpu_data->enter_funcs[call->depth] = call->func; |
|
} |
|
|
|
/* No time */ |
|
print_graph_duration(tr, 0, s, flags | FLAGS_FILL_FULL); |
|
|
|
/* Function */ |
|
for (i = 0; i < call->depth * TRACE_GRAPH_INDENT; i++) |
|
trace_seq_putc(s, ' '); |
|
|
|
trace_seq_printf(s, "%ps() {\n", (void *)call->func); |
|
|
|
if (trace_seq_has_overflowed(s)) |
|
return TRACE_TYPE_PARTIAL_LINE; |
|
|
|
/* |
|
* we already consumed the current entry to check the next one |
|
* and see if this is a leaf. |
|
*/ |
|
return TRACE_TYPE_NO_CONSUME; |
|
} |
|
|
|
static void |
|
print_graph_prologue(struct trace_iterator *iter, struct trace_seq *s, |
|
int type, unsigned long addr, u32 flags) |
|
{ |
|
struct fgraph_data *data = iter->private; |
|
struct trace_entry *ent = iter->ent; |
|
struct trace_array *tr = iter->tr; |
|
int cpu = iter->cpu; |
|
|
|
/* Pid */ |
|
verif_pid(s, ent->pid, cpu, data); |
|
|
|
if (type) |
|
/* Interrupt */ |
|
print_graph_irq(iter, addr, type, cpu, ent->pid, flags); |
|
|
|
if (!(tr->trace_flags & TRACE_ITER_CONTEXT_INFO)) |
|
return; |
|
|
|
/* Absolute time */ |
|
if (flags & TRACE_GRAPH_PRINT_ABS_TIME) |
|
print_graph_abs_time(iter->ts, s); |
|
|
|
/* Relative time */ |
|
if (flags & TRACE_GRAPH_PRINT_REL_TIME) |
|
print_graph_rel_time(iter, s); |
|
|
|
/* Cpu */ |
|
if (flags & TRACE_GRAPH_PRINT_CPU) |
|
print_graph_cpu(s, cpu); |
|
|
|
/* Proc */ |
|
if (flags & TRACE_GRAPH_PRINT_PROC) { |
|
print_graph_proc(s, ent->pid); |
|
trace_seq_puts(s, " | "); |
|
} |
|
|
|
/* Latency format */ |
|
if (tr->trace_flags & TRACE_ITER_LATENCY_FMT) |
|
print_graph_lat_fmt(s, ent); |
|
|
|
return; |
|
} |
|
|
|
/* |
|
* Entry check for irq code |
|
* |
|
* returns 1 if |
|
* - we are inside irq code |
|
* - we just entered irq code |
|
* |
|
* retunns 0 if |
|
* - funcgraph-interrupts option is set |
|
* - we are not inside irq code |
|
*/ |
|
static int |
|
check_irq_entry(struct trace_iterator *iter, u32 flags, |
|
unsigned long addr, int depth) |
|
{ |
|
int cpu = iter->cpu; |
|
int *depth_irq; |
|
struct fgraph_data *data = iter->private; |
|
|
|
/* |
|
* If we are either displaying irqs, or we got called as |
|
* a graph event and private data does not exist, |
|
* then we bypass the irq check. |
|
*/ |
|
if ((flags & TRACE_GRAPH_PRINT_IRQS) || |
|
(!data)) |
|
return 0; |
|
|
|
depth_irq = &(per_cpu_ptr(data->cpu_data, cpu)->depth_irq); |
|
|
|
/* |
|
* We are inside the irq code |
|
*/ |
|
if (*depth_irq >= 0) |
|
return 1; |
|
|
|
if ((addr < (unsigned long)__irqentry_text_start) || |
|
(addr >= (unsigned long)__irqentry_text_end)) |
|
return 0; |
|
|
|
/* |
|
* We are entering irq code. |
|
*/ |
|
*depth_irq = depth; |
|
return 1; |
|
} |
|
|
|
/* |
|
* Return check for irq code |
|
* |
|
* returns 1 if |
|
* - we are inside irq code |
|
* - we just left irq code |
|
* |
|
* returns 0 if |
|
* - funcgraph-interrupts option is set |
|
* - we are not inside irq code |
|
*/ |
|
static int |
|
check_irq_return(struct trace_iterator *iter, u32 flags, int depth) |
|
{ |
|
int cpu = iter->cpu; |
|
int *depth_irq; |
|
struct fgraph_data *data = iter->private; |
|
|
|
/* |
|
* If we are either displaying irqs, or we got called as |
|
* a graph event and private data does not exist, |
|
* then we bypass the irq check. |
|
*/ |
|
if ((flags & TRACE_GRAPH_PRINT_IRQS) || |
|
(!data)) |
|
return 0; |
|
|
|
depth_irq = &(per_cpu_ptr(data->cpu_data, cpu)->depth_irq); |
|
|
|
/* |
|
* We are not inside the irq code. |
|
*/ |
|
if (*depth_irq == -1) |
|
return 0; |
|
|
|
/* |
|
* We are inside the irq code, and this is returning entry. |
|
* Let's not trace it and clear the entry depth, since |
|
* we are out of irq code. |
|
* |
|
* This condition ensures that we 'leave the irq code' once |
|
* we are out of the entry depth. Thus protecting us from |
|
* the RETURN entry loss. |
|
*/ |
|
if (*depth_irq >= depth) { |
|
*depth_irq = -1; |
|
return 1; |
|
} |
|
|
|
/* |
|
* We are inside the irq code, and this is not the entry. |
|
*/ |
|
return 1; |
|
} |
|
|
|
static enum print_line_t |
|
print_graph_entry(struct ftrace_graph_ent_entry *field, struct trace_seq *s, |
|
struct trace_iterator *iter, u32 flags) |
|
{ |
|
struct fgraph_data *data = iter->private; |
|
struct ftrace_graph_ent *call = &field->graph_ent; |
|
struct ftrace_graph_ret_entry *leaf_ret; |
|
static enum print_line_t ret; |
|
int cpu = iter->cpu; |
|
|
|
if (check_irq_entry(iter, flags, call->func, call->depth)) |
|
return TRACE_TYPE_HANDLED; |
|
|
|
print_graph_prologue(iter, s, TRACE_GRAPH_ENT, call->func, flags); |
|
|
|
leaf_ret = get_return_for_leaf(iter, field); |
|
if (leaf_ret) |
|
ret = print_graph_entry_leaf(iter, field, leaf_ret, s, flags); |
|
else |
|
ret = print_graph_entry_nested(iter, field, s, cpu, flags); |
|
|
|
if (data) { |
|
/* |
|
* If we failed to write our output, then we need to make |
|
* note of it. Because we already consumed our entry. |
|
*/ |
|
if (s->full) { |
|
data->failed = 1; |
|
data->cpu = cpu; |
|
} else |
|
data->failed = 0; |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
static enum print_line_t |
|
print_graph_return(struct ftrace_graph_ret *trace, struct trace_seq *s, |
|
struct trace_entry *ent, struct trace_iterator *iter, |
|
u32 flags) |
|
{ |
|
unsigned long long duration = trace->rettime - trace->calltime; |
|
struct fgraph_data *data = iter->private; |
|
struct trace_array *tr = iter->tr; |
|
pid_t pid = ent->pid; |
|
int cpu = iter->cpu; |
|
int func_match = 1; |
|
int i; |
|
|
|
if (check_irq_return(iter, flags, trace->depth)) |
|
return TRACE_TYPE_HANDLED; |
|
|
|
if (data) { |
|
struct fgraph_cpu_data *cpu_data; |
|
int cpu = iter->cpu; |
|
|
|
cpu_data = per_cpu_ptr(data->cpu_data, cpu); |
|
|
|
/* |
|
* Comments display at + 1 to depth. This is the |
|
* return from a function, we now want the comments |
|
* to display at the same level of the bracket. |
|
*/ |
|
cpu_data->depth = trace->depth - 1; |
|
|
|
if (trace->depth < FTRACE_RETFUNC_DEPTH && |
|
!WARN_ON_ONCE(trace->depth < 0)) { |
|
if (cpu_data->enter_funcs[trace->depth] != trace->func) |
|
func_match = 0; |
|
cpu_data->enter_funcs[trace->depth] = 0; |
|
} |
|
} |
|
|
|
print_graph_prologue(iter, s, 0, 0, flags); |
|
|
|
/* Overhead and duration */ |
|
print_graph_duration(tr, duration, s, flags); |
|
|
|
/* Closing brace */ |
|
for (i = 0; i < trace->depth * TRACE_GRAPH_INDENT; i++) |
|
trace_seq_putc(s, ' '); |
|
|
|
/* |
|
* If the return function does not have a matching entry, |
|
* then the entry was lost. Instead of just printing |
|
* the '}' and letting the user guess what function this |
|
* belongs to, write out the function name. Always do |
|
* that if the funcgraph-tail option is enabled. |
|
*/ |
|
if (func_match && !(flags & TRACE_GRAPH_PRINT_TAIL)) |
|
trace_seq_puts(s, "}\n"); |
|
else |
|
trace_seq_printf(s, "} /* %ps */\n", (void *)trace->func); |
|
|
|
/* Overrun */ |
|
if (flags & TRACE_GRAPH_PRINT_OVERRUN) |
|
trace_seq_printf(s, " (Overruns: %u)\n", |
|
trace->overrun); |
|
|
|
print_graph_irq(iter, trace->func, TRACE_GRAPH_RET, |
|
cpu, pid, flags); |
|
|
|
return trace_handle_return(s); |
|
} |
|
|
|
static enum print_line_t |
|
print_graph_comment(struct trace_seq *s, struct trace_entry *ent, |
|
struct trace_iterator *iter, u32 flags) |
|
{ |
|
struct trace_array *tr = iter->tr; |
|
unsigned long sym_flags = (tr->trace_flags & TRACE_ITER_SYM_MASK); |
|
struct fgraph_data *data = iter->private; |
|
struct trace_event *event; |
|
int depth = 0; |
|
int ret; |
|
int i; |
|
|
|
if (data) |
|
depth = per_cpu_ptr(data->cpu_data, iter->cpu)->depth; |
|
|
|
print_graph_prologue(iter, s, 0, 0, flags); |
|
|
|
/* No time */ |
|
print_graph_duration(tr, 0, s, flags | FLAGS_FILL_FULL); |
|
|
|
/* Indentation */ |
|
if (depth > 0) |
|
for (i = 0; i < (depth + 1) * TRACE_GRAPH_INDENT; i++) |
|
trace_seq_putc(s, ' '); |
|
|
|
/* The comment */ |
|
trace_seq_puts(s, "/* "); |
|
|
|
switch (iter->ent->type) { |
|
case TRACE_BPUTS: |
|
ret = trace_print_bputs_msg_only(iter); |
|
if (ret != TRACE_TYPE_HANDLED) |
|
return ret; |
|
break; |
|
case TRACE_BPRINT: |
|
ret = trace_print_bprintk_msg_only(iter); |
|
if (ret != TRACE_TYPE_HANDLED) |
|
return ret; |
|
break; |
|
case TRACE_PRINT: |
|
ret = trace_print_printk_msg_only(iter); |
|
if (ret != TRACE_TYPE_HANDLED) |
|
return ret; |
|
break; |
|
default: |
|
event = ftrace_find_event(ent->type); |
|
if (!event) |
|
return TRACE_TYPE_UNHANDLED; |
|
|
|
ret = event->funcs->trace(iter, sym_flags, event); |
|
if (ret != TRACE_TYPE_HANDLED) |
|
return ret; |
|
} |
|
|
|
if (trace_seq_has_overflowed(s)) |
|
goto out; |
|
|
|
/* Strip ending newline */ |
|
if (s->buffer[s->seq.len - 1] == '\n') { |
|
s->buffer[s->seq.len - 1] = '\0'; |
|
s->seq.len--; |
|
} |
|
|
|
trace_seq_puts(s, " */\n"); |
|
out: |
|
return trace_handle_return(s); |
|
} |
|
|
|
|
|
enum print_line_t |
|
print_graph_function_flags(struct trace_iterator *iter, u32 flags) |
|
{ |
|
struct ftrace_graph_ent_entry *field; |
|
struct fgraph_data *data = iter->private; |
|
struct trace_entry *entry = iter->ent; |
|
struct trace_seq *s = &iter->seq; |
|
int cpu = iter->cpu; |
|
int ret; |
|
|
|
if (data && per_cpu_ptr(data->cpu_data, cpu)->ignore) { |
|
per_cpu_ptr(data->cpu_data, cpu)->ignore = 0; |
|
return TRACE_TYPE_HANDLED; |
|
} |
|
|
|
/* |
|
* If the last output failed, there's a possibility we need |
|
* to print out the missing entry which would never go out. |
|
*/ |
|
if (data && data->failed) { |
|
field = &data->ent; |
|
iter->cpu = data->cpu; |
|
ret = print_graph_entry(field, s, iter, flags); |
|
if (ret == TRACE_TYPE_HANDLED && iter->cpu != cpu) { |
|
per_cpu_ptr(data->cpu_data, iter->cpu)->ignore = 1; |
|
ret = TRACE_TYPE_NO_CONSUME; |
|
} |
|
iter->cpu = cpu; |
|
return ret; |
|
} |
|
|
|
switch (entry->type) { |
|
case TRACE_GRAPH_ENT: { |
|
/* |
|
* print_graph_entry() may consume the current event, |
|
* thus @field may become invalid, so we need to save it. |
|
* sizeof(struct ftrace_graph_ent_entry) is very small, |
|
* it can be safely saved at the stack. |
|
*/ |
|
struct ftrace_graph_ent_entry saved; |
|
trace_assign_type(field, entry); |
|
saved = *field; |
|
return print_graph_entry(&saved, s, iter, flags); |
|
} |
|
case TRACE_GRAPH_RET: { |
|
struct ftrace_graph_ret_entry *field; |
|
trace_assign_type(field, entry); |
|
return print_graph_return(&field->ret, s, entry, iter, flags); |
|
} |
|
case TRACE_STACK: |
|
case TRACE_FN: |
|
/* dont trace stack and functions as comments */ |
|
return TRACE_TYPE_UNHANDLED; |
|
|
|
default: |
|
return print_graph_comment(s, entry, iter, flags); |
|
} |
|
|
|
return TRACE_TYPE_HANDLED; |
|
} |
|
|
|
static enum print_line_t |
|
print_graph_function(struct trace_iterator *iter) |
|
{ |
|
return print_graph_function_flags(iter, tracer_flags.val); |
|
} |
|
|
|
static enum print_line_t |
|
print_graph_function_event(struct trace_iterator *iter, int flags, |
|
struct trace_event *event) |
|
{ |
|
return print_graph_function(iter); |
|
} |
|
|
|
static void print_lat_header(struct seq_file *s, u32 flags) |
|
{ |
|
static const char spaces[] = " " /* 16 spaces */ |
|
" " /* 4 spaces */ |
|
" "; /* 17 spaces */ |
|
int size = 0; |
|
|
|
if (flags & TRACE_GRAPH_PRINT_ABS_TIME) |
|
size += 16; |
|
if (flags & TRACE_GRAPH_PRINT_REL_TIME) |
|
size += 16; |
|
if (flags & TRACE_GRAPH_PRINT_CPU) |
|
size += 4; |
|
if (flags & TRACE_GRAPH_PRINT_PROC) |
|
size += 17; |
|
|
|
seq_printf(s, "#%.*s _-----=> irqs-off \n", size, spaces); |
|
seq_printf(s, "#%.*s / _----=> need-resched \n", size, spaces); |
|
seq_printf(s, "#%.*s| / _---=> hardirq/softirq \n", size, spaces); |
|
seq_printf(s, "#%.*s|| / _--=> preempt-depth \n", size, spaces); |
|
seq_printf(s, "#%.*s||| / \n", size, spaces); |
|
} |
|
|
|
static void __print_graph_headers_flags(struct trace_array *tr, |
|
struct seq_file *s, u32 flags) |
|
{ |
|
int lat = tr->trace_flags & TRACE_ITER_LATENCY_FMT; |
|
|
|
if (lat) |
|
print_lat_header(s, flags); |
|
|
|
/* 1st line */ |
|
seq_putc(s, '#'); |
|
if (flags & TRACE_GRAPH_PRINT_ABS_TIME) |
|
seq_puts(s, " TIME "); |
|
if (flags & TRACE_GRAPH_PRINT_REL_TIME) |
|
seq_puts(s, " REL TIME "); |
|
if (flags & TRACE_GRAPH_PRINT_CPU) |
|
seq_puts(s, " CPU"); |
|
if (flags & TRACE_GRAPH_PRINT_PROC) |
|
seq_puts(s, " TASK/PID "); |
|
if (lat) |
|
seq_puts(s, "|||| "); |
|
if (flags & TRACE_GRAPH_PRINT_DURATION) |
|
seq_puts(s, " DURATION "); |
|
seq_puts(s, " FUNCTION CALLS\n"); |
|
|
|
/* 2nd line */ |
|
seq_putc(s, '#'); |
|
if (flags & TRACE_GRAPH_PRINT_ABS_TIME) |
|
seq_puts(s, " | "); |
|
if (flags & TRACE_GRAPH_PRINT_REL_TIME) |
|
seq_puts(s, " | "); |
|
if (flags & TRACE_GRAPH_PRINT_CPU) |
|
seq_puts(s, " | "); |
|
if (flags & TRACE_GRAPH_PRINT_PROC) |
|
seq_puts(s, " | | "); |
|
if (lat) |
|
seq_puts(s, "|||| "); |
|
if (flags & TRACE_GRAPH_PRINT_DURATION) |
|
seq_puts(s, " | | "); |
|
seq_puts(s, " | | | |\n"); |
|
} |
|
|
|
static void print_graph_headers(struct seq_file *s) |
|
{ |
|
print_graph_headers_flags(s, tracer_flags.val); |
|
} |
|
|
|
void print_graph_headers_flags(struct seq_file *s, u32 flags) |
|
{ |
|
struct trace_iterator *iter = s->private; |
|
struct trace_array *tr = iter->tr; |
|
|
|
if (!(tr->trace_flags & TRACE_ITER_CONTEXT_INFO)) |
|
return; |
|
|
|
if (tr->trace_flags & TRACE_ITER_LATENCY_FMT) { |
|
/* print nothing if the buffers are empty */ |
|
if (trace_empty(iter)) |
|
return; |
|
|
|
print_trace_header(s, iter); |
|
} |
|
|
|
__print_graph_headers_flags(tr, s, flags); |
|
} |
|
|
|
void graph_trace_open(struct trace_iterator *iter) |
|
{ |
|
/* pid and depth on the last trace processed */ |
|
struct fgraph_data *data; |
|
gfp_t gfpflags; |
|
int cpu; |
|
|
|
iter->private = NULL; |
|
|
|
/* We can be called in atomic context via ftrace_dump() */ |
|
gfpflags = (in_atomic() || irqs_disabled()) ? GFP_ATOMIC : GFP_KERNEL; |
|
|
|
data = kzalloc(sizeof(*data), gfpflags); |
|
if (!data) |
|
goto out_err; |
|
|
|
data->cpu_data = alloc_percpu_gfp(struct fgraph_cpu_data, gfpflags); |
|
if (!data->cpu_data) |
|
goto out_err_free; |
|
|
|
for_each_possible_cpu(cpu) { |
|
pid_t *pid = &(per_cpu_ptr(data->cpu_data, cpu)->last_pid); |
|
int *depth = &(per_cpu_ptr(data->cpu_data, cpu)->depth); |
|
int *ignore = &(per_cpu_ptr(data->cpu_data, cpu)->ignore); |
|
int *depth_irq = &(per_cpu_ptr(data->cpu_data, cpu)->depth_irq); |
|
|
|
*pid = -1; |
|
*depth = 0; |
|
*ignore = 0; |
|
*depth_irq = -1; |
|
} |
|
|
|
iter->private = data; |
|
|
|
return; |
|
|
|
out_err_free: |
|
kfree(data); |
|
out_err: |
|
pr_warn("function graph tracer: not enough memory\n"); |
|
} |
|
|
|
void graph_trace_close(struct trace_iterator *iter) |
|
{ |
|
struct fgraph_data *data = iter->private; |
|
|
|
if (data) { |
|
free_percpu(data->cpu_data); |
|
kfree(data); |
|
} |
|
} |
|
|
|
static int |
|
func_graph_set_flag(struct trace_array *tr, u32 old_flags, u32 bit, int set) |
|
{ |
|
if (bit == TRACE_GRAPH_PRINT_IRQS) |
|
ftrace_graph_skip_irqs = !set; |
|
|
|
if (bit == TRACE_GRAPH_SLEEP_TIME) |
|
ftrace_graph_sleep_time_control(set); |
|
|
|
if (bit == TRACE_GRAPH_GRAPH_TIME) |
|
ftrace_graph_graph_time_control(set); |
|
|
|
return 0; |
|
} |
|
|
|
static struct trace_event_functions graph_functions = { |
|
.trace = print_graph_function_event, |
|
}; |
|
|
|
static struct trace_event graph_trace_entry_event = { |
|
.type = TRACE_GRAPH_ENT, |
|
.funcs = &graph_functions, |
|
}; |
|
|
|
static struct trace_event graph_trace_ret_event = { |
|
.type = TRACE_GRAPH_RET, |
|
.funcs = &graph_functions |
|
}; |
|
|
|
static struct tracer graph_trace __tracer_data = { |
|
.name = "function_graph", |
|
.update_thresh = graph_trace_update_thresh, |
|
.open = graph_trace_open, |
|
.pipe_open = graph_trace_open, |
|
.close = graph_trace_close, |
|
.pipe_close = graph_trace_close, |
|
.init = graph_trace_init, |
|
.reset = graph_trace_reset, |
|
.print_line = print_graph_function, |
|
.print_header = print_graph_headers, |
|
.flags = &tracer_flags, |
|
.set_flag = func_graph_set_flag, |
|
#ifdef CONFIG_FTRACE_SELFTEST |
|
.selftest = trace_selftest_startup_function_graph, |
|
#endif |
|
}; |
|
|
|
|
|
static ssize_t |
|
graph_depth_write(struct file *filp, const char __user *ubuf, size_t cnt, |
|
loff_t *ppos) |
|
{ |
|
unsigned long val; |
|
int ret; |
|
|
|
ret = kstrtoul_from_user(ubuf, cnt, 10, &val); |
|
if (ret) |
|
return ret; |
|
|
|
fgraph_max_depth = val; |
|
|
|
*ppos += cnt; |
|
|
|
return cnt; |
|
} |
|
|
|
static ssize_t |
|
graph_depth_read(struct file *filp, char __user *ubuf, size_t cnt, |
|
loff_t *ppos) |
|
{ |
|
char buf[15]; /* More than enough to hold UINT_MAX + "\n"*/ |
|
int n; |
|
|
|
n = sprintf(buf, "%d\n", fgraph_max_depth); |
|
|
|
return simple_read_from_buffer(ubuf, cnt, ppos, buf, n); |
|
} |
|
|
|
static const struct file_operations graph_depth_fops = { |
|
.open = tracing_open_generic, |
|
.write = graph_depth_write, |
|
.read = graph_depth_read, |
|
.llseek = generic_file_llseek, |
|
}; |
|
|
|
static __init int init_graph_tracefs(void) |
|
{ |
|
int ret; |
|
|
|
ret = tracing_init_dentry(); |
|
if (ret) |
|
return 0; |
|
|
|
trace_create_file("max_graph_depth", 0644, NULL, |
|
NULL, &graph_depth_fops); |
|
|
|
return 0; |
|
} |
|
fs_initcall(init_graph_tracefs); |
|
|
|
static __init int init_graph_trace(void) |
|
{ |
|
max_bytes_for_cpu = snprintf(NULL, 0, "%u", nr_cpu_ids - 1); |
|
|
|
if (!register_trace_event(&graph_trace_entry_event)) { |
|
pr_warn("Warning: could not register graph trace events\n"); |
|
return 1; |
|
} |
|
|
|
if (!register_trace_event(&graph_trace_ret_event)) { |
|
pr_warn("Warning: could not register graph trace events\n"); |
|
return 1; |
|
} |
|
|
|
return register_tracer(&graph_trace); |
|
} |
|
|
|
core_initcall(init_graph_trace);
|
|
|