diff options
-rw-r--r-- | kernel/trace/Makefile | 1 | ||||
-rw-r--r-- | kernel/trace/trace.c | 3 | ||||
-rw-r--r-- | kernel/trace/trace.h | 11 | ||||
-rw-r--r-- | kernel/trace/trace_functions_hist.c | 345 |
4 files changed, 360 insertions, 0 deletions
diff --git a/kernel/trace/Makefile b/kernel/trace/Makefile index 7faace3c5553..eb2bb506888b 100644 --- a/kernel/trace/Makefile +++ b/kernel/trace/Makefile @@ -34,6 +34,7 @@ obj-$(CONFIG_TRACING) += trace_printk.o obj-$(CONFIG_TRACING) += tracing_map.o obj-$(CONFIG_CONTEXT_SWITCH_TRACER) += trace_sched_switch.o obj-$(CONFIG_FUNCTION_TRACER) += trace_functions.o +obj-$(CONFIG_FUNCTION_TRACER) += trace_functions_hist.o obj-$(CONFIG_IRQSOFF_TRACER) += trace_irqsoff.o obj-$(CONFIG_PREEMPT_TRACER) += trace_irqsoff.o obj-$(CONFIG_SCHED_TRACER) += trace_sched_wakeup.o diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c index c581750bf9db..dcd950f775a6 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c @@ -6740,6 +6740,9 @@ init_tracer_tracefs(struct trace_array *tr, struct dentry *d_tracer) if (ftrace_create_function_files(tr, d_tracer)) WARN(1, "Could not allocate function filter files"); + if (ftrace_create_function_hist_files(tr, d_tracer)) + WARN(1, "Could not allocate function_hist filter files"); + #ifdef CONFIG_TRACER_SNAPSHOT trace_create_file("snapshot", 0644, d_tracer, tr, &snapshot_fops); diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h index 5e2e3b00e771..502f88be8ff2 100644 --- a/kernel/trace/trace.h +++ b/kernel/trace/trace.h @@ -820,7 +820,10 @@ static inline int ftrace_trace_task(struct task_struct *task) extern int ftrace_is_dead(void); int ftrace_create_function_files(struct trace_array *tr, struct dentry *parent); +int ftrace_create_function_hist_files(struct trace_array *tr, + struct dentry *parent); void ftrace_destroy_function_files(struct trace_array *tr); +void ftrace_destroy_function_hist_files(struct trace_array *tr); void ftrace_init_global_array_ops(struct trace_array *tr); void ftrace_init_array_ops(struct trace_array *tr, ftrace_func_t func); void ftrace_reset_array_ops(struct trace_array *tr); @@ -837,7 +840,15 @@ ftrace_create_function_files(struct trace_array *tr, { return 0; } +static inline int +ftrace_create_function_hist_files(struct trace_array *tr, + struct dentry *parent) +{ + return 0; +} static inline void ftrace_destroy_function_files(struct trace_array *tr) { } +static inline void +ftrace_destroy_function_hist_files(struct trace_array *tr) { } static inline __init void ftrace_init_global_array_ops(struct trace_array *tr) { } static inline void ftrace_reset_array_ops(struct trace_array *tr) { } diff --git a/kernel/trace/trace_functions_hist.c b/kernel/trace/trace_functions_hist.c new file mode 100644 index 000000000000..136ca76caa34 --- /dev/null +++ b/kernel/trace/trace_functions_hist.c @@ -0,0 +1,345 @@ +/* + * histogram-based function tracer + * + * Copyright (C) 2015 Tom Zanussi <tom.zanussi@linux.intel.com> + * + * Based on code from the function_tracer, that is: + * + * Copyright (C) 2007-2008 Steven Rostedt <srostedt@redhat.com> + * Copyright (C) 2008 Ingo Molnar <mingo@redhat.com> + */ +#include <linux/kallsyms.h> +#include <linux/module.h> +#include <linux/ftrace.h> +#include <linux/slab.h> + +#include "tracing_map.h" +#include "trace.h" + +static void tracing_start_function_hist(struct trace_array *tr); +static void tracing_stop_function_hist(struct trace_array *tr); +static void +function_hist_call(unsigned long ip, unsigned long parent_ip, + struct ftrace_ops *op, struct pt_regs *pt_regs); + +enum hist_key_flags { + HIST_FIELD_SYM = 1, + HIST_FIELD_HEX = 2, +}; + +struct function_hist_data { + atomic64_t total_hits; + atomic64_t drops; + unsigned long key_flags; + struct tracing_map_sort_key sort_key; + struct tracing_map *map; +}; + +#define FUNCTION_HIST_BITS_DEFAULT 16 +#define HITCOUNT_IDX 0 + +static struct function_hist_data *function_hist_data; + +static int allocate_ftrace_ops(struct trace_array *tr) +{ + struct ftrace_ops *ops; + + ops = kzalloc(sizeof(*ops), GFP_KERNEL); + if (!ops) + return -ENOMEM; + + ops->func = function_hist_call; + ops->flags = FTRACE_OPS_FL_RECURSION_SAFE; + + tr->ops = ops; + ops->private = tr; + + return 0; +} + +static int function_hist_init(struct trace_array *tr) +{ + ftrace_func_t func; + + /* + * Instance trace_arrays get their ops allocated + * at instance creation. Unless it failed + * the allocation. + */ + if (!tr->ops) + return -ENOMEM; + + func = function_hist_call; + + ftrace_init_array_ops(tr, func); + + tracing_start_function_hist(tr); + + return 0; +} + +static void function_hist_reset(struct trace_array *tr) +{ + tracing_stop_function_hist(tr); + ftrace_reset_array_ops(tr); +} + +static void +function_hist_call(unsigned long ip, unsigned long parent_ip, + struct ftrace_ops *op, struct pt_regs *pt_regs) +{ + struct trace_array *tr = op->private; + struct tracing_map_elt *elt; + void *key; + int bit; + + if (unlikely(!tr->function_enabled)) + return; + + preempt_disable_notrace(); + + bit = trace_test_and_set_recursion(TRACE_FTRACE_START, + TRACE_FTRACE_MAX); + if (bit < 0) + goto out; + + if (atomic64_read(&function_hist_data->drops)) { + atomic64_inc(&function_hist_data->drops); + goto out_clear; + } + + key = (void *)&ip; + elt = tracing_map_insert(function_hist_data->map, key); + if (elt) + tracing_map_update_sum(elt, HITCOUNT_IDX, 1); + else + atomic64_inc(&function_hist_data->drops); + + atomic64_inc(&function_hist_data->total_hits); + out_clear: + trace_clear_recursion(bit); + out: + preempt_enable_notrace(); +} + +static void destroy_hist_data(struct function_hist_data *hist_data) +{ + tracing_map_destroy(hist_data->map); + kfree(hist_data); +} + +static inline void create_sort_key(struct function_hist_data *hist_data) +{ + hist_data->sort_key.field_idx = HITCOUNT_IDX; + hist_data->sort_key.descending = false; +} + +static int create_tracing_map_fields(struct function_hist_data *hist_data) +{ + struct tracing_map *map = hist_data->map; + unsigned int idx; + + idx = tracing_map_add_sum_field(map); + if (idx < 0) + return idx; + + idx = tracing_map_add_key_field(map, 0, tracing_map_cmp_none); + if (idx < 0) + return idx; + + return 0; +} + +static struct function_hist_data *create_hist_data(unsigned int map_bits) +{ + struct function_hist_data *hist_data; + unsigned int key_size; + int ret = 0; + + hist_data = kzalloc(sizeof(*hist_data), GFP_KERNEL); + if (!hist_data) + return NULL; + + create_sort_key(hist_data); + + key_size = sizeof(unsigned long); /* ip */ + hist_data->map = tracing_map_create(map_bits, key_size, + NULL, hist_data); + if (IS_ERR(hist_data->map)) { + ret = PTR_ERR(hist_data->map); + hist_data->map = NULL; + goto free; + } + + ret = create_tracing_map_fields(hist_data); + if (ret) + goto free; + + ret = tracing_map_init(hist_data->map); + if (ret) + goto free; + out: + return hist_data; + free: + destroy_hist_data(hist_data); + if (ret) + hist_data = ERR_PTR(ret); + else + hist_data = NULL; + + goto out; +} + +static void tracing_start_function_hist(struct trace_array *tr) +{ + unsigned int hist_trigger_bits = FUNCTION_HIST_BITS_DEFAULT; + struct function_hist_data *hist_data; + + if (function_hist_data) { + destroy_hist_data(function_hist_data); + function_hist_data = NULL; + } + + hist_data = create_hist_data(hist_trigger_bits); + if (IS_ERR(hist_data)) + return; + + hist_data->key_flags |= HIST_FIELD_SYM; + + function_hist_data = hist_data; + + tr->function_enabled = 0; + register_ftrace_function(tr->ops); + tr->function_enabled = 1; +} + +static void tracing_stop_function_hist(struct trace_array *tr) +{ + tr->function_enabled = 0; + unregister_ftrace_function(tr->ops); +} + +static void function_hist_entry_print(struct seq_file *m, + struct function_hist_data *hist_data, + void *key, struct tracing_map_elt *elt) +{ + char str[KSYM_SYMBOL_LEN]; + u64 uval; + + if (hist_data->key_flags & HIST_FIELD_SYM) { + uval = *(u64 *)key; + kallsyms_lookup(uval, NULL, NULL, NULL, str); + seq_printf(m, "ip: [%llx] %-35s", uval, str); + } else if (hist_data->key_flags & HIST_FIELD_HEX) { + uval = *(u64 *)key; + seq_printf(m, "ip: %llx", uval); + } else { + uval = *(u64 *)key; + seq_printf(m, "ip: %10llu", uval); + } + + seq_printf(m, " hitcount: %10llu", + tracing_map_read_sum(elt, HITCOUNT_IDX)); + + seq_puts(m, "\n"); +} + +static int print_entries(struct seq_file *m, + struct function_hist_data *hist_data) +{ + struct tracing_map_sort_entry **sort_entries = NULL; + struct tracing_map *map = hist_data->map; + unsigned int i, n_entries; + + n_entries = tracing_map_sort_entries(map, &hist_data->sort_key, 1, + &sort_entries); + if (n_entries < 0) + return n_entries; + + for (i = 0; i < n_entries; i++) + function_hist_entry_print(m, hist_data, + sort_entries[i]->key, + sort_entries[i]->elt); + + tracing_map_destroy_sort_entries(sort_entries, n_entries); + + return n_entries; +} + +static int hist_show(struct seq_file *m, void *v) +{ + int n_entries, ret = 0; + + mutex_lock(&trace_types_lock); + + if (!function_hist_data) + goto out_unlock; + + n_entries = print_entries(m, function_hist_data); + if (n_entries < 0) { + ret = n_entries; + n_entries = 0; + } + + seq_printf(m, "\nTotals:\n Hits: %lu\n Entries: %u\n Dropped: %lu\n", + atomic64_read(&function_hist_data->total_hits), + n_entries, atomic64_read(&function_hist_data->drops)); + out_unlock: + mutex_unlock(&trace_types_lock); + + return ret; +} + +static int function_hist_open(struct inode *inode, struct file *file) +{ + return single_open(file, hist_show, file); +} + +const struct file_operations function_hist_fops = { + .open = function_hist_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +int ftrace_create_function_hist_files(struct trace_array *tr, + struct dentry *parent) +{ + struct dentry *d_tracer; + int ret; + + d_tracer = tracing_init_dentry(); + if (IS_ERR(d_tracer)) + return 0; + + trace_create_file("function_hist", 0444, d_tracer, + NULL, &function_hist_fops); + + ret = allocate_ftrace_ops(tr); + if (ret) + return ret; + + return 0; +} + +void ftrace_destroy_function_hist_files(struct trace_array *tr) +{ + kfree(tr->ops); + tr->ops = NULL; +} + +static struct tracer function_hist __tracer_data = { + .name = "function_hist", + .init = function_hist_init, + .reset = function_hist_reset, + .allow_instances = true, +#ifdef CONFIG_FTRACE_SELFTEST + .selftest = trace_selftest_startup_function, +#endif +}; + +static __init int init_function_hist(void) +{ + return register_tracer(&function_hist); +} +fs_initcall(init_function_hist); |