tracing: Add 'hist' event trigger command
'hist' triggers allow users to continually aggregate trace events, which can then be viewed afterwards by simply reading a 'hist' file containing the aggregation in a human-readable format. The basic idea is very simple and boils down to a mechanism whereby trace events, rather than being exhaustively dumped in raw form and viewed directly, are automatically 'compressed' into meaningful tables completely defined by the user. This is done strictly via single-line command-line commands and without the aid of any kind of programming language or interpreter. A surprising number of typical use cases can be accomplished by users via this simple mechanism. In fact, a large number of the tasks that users typically do using the more complicated script-based tracing tools, at least during the initial stages of an investigation, can be accomplished by simply specifying a set of keys and values to be used in the creation of a hash table. The Linux kernel trace event subsystem happens to provide an extensive list of keys and values ready-made for such a purpose in the form of the event format files associated with each trace event. By simply consulting the format file for field names of interest and by plugging them into the hist trigger command, users can create an endless number of useful aggregations to help with investigating various properties of the system. See Documentation/trace/events.txt for examples. hist triggers are implemented on top of the existing event trigger infrastructure, and as such are consistent with the existing triggers from a user's perspective as well. The basic syntax follows the existing trigger syntax. Users start an aggregation by writing a 'hist' trigger to the event of interest's trigger file: # echo hist:keys=xxx [ if filter] > event/trigger Once a hist trigger has been set up, by default it continually aggregates every matching event into a hash table using the event key and a value field named 'hitcount'. To view the aggregation at any point in time, simply read the 'hist' file in the same directory as the 'trigger' file: # cat event/hist The detailed syntax provides additional options for user control, and is described exhaustively in Documentation/trace/events.txt and in the virtual tracing/README file in the tracing subsystem. Link: http://lkml.kernel.org/r/72d263b5e1853fe9c314953b65833c3aa75479f2.1457029949.git.tom.zanussi@linux.intel.com Signed-off-by: Tom Zanussi <tom.zanussi@linux.intel.com> Tested-by: Masami Hiramatsu <masami.hiramatsu.pt@hitachi.com> Reviewed-by: Namhyung Kim <namhyung@kernel.org> Signed-off-by: Steven Rostedt <rostedt@goodmis.org>
This commit is contained in:
parent
3b772b96b8
commit
7ef224d1d0
|
@ -407,6 +407,7 @@ enum event_trigger_type {
|
||||||
ETT_SNAPSHOT = (1 << 1),
|
ETT_SNAPSHOT = (1 << 1),
|
||||||
ETT_STACKTRACE = (1 << 2),
|
ETT_STACKTRACE = (1 << 2),
|
||||||
ETT_EVENT_ENABLE = (1 << 3),
|
ETT_EVENT_ENABLE = (1 << 3),
|
||||||
|
ETT_EVENT_HIST = (1 << 4),
|
||||||
};
|
};
|
||||||
|
|
||||||
extern int filter_match_preds(struct event_filter *filter, void *rec);
|
extern int filter_match_preds(struct event_filter *filter, void *rec);
|
||||||
|
|
|
@ -538,6 +538,22 @@ config TRACING_MAP
|
||||||
generally used outside of that context, and is normally
|
generally used outside of that context, and is normally
|
||||||
selected by tracers that use it.
|
selected by tracers that use it.
|
||||||
|
|
||||||
|
config HIST_TRIGGERS
|
||||||
|
bool "Histogram triggers"
|
||||||
|
depends on ARCH_HAVE_NMI_SAFE_CMPXCHG
|
||||||
|
select TRACING_MAP
|
||||||
|
default n
|
||||||
|
help
|
||||||
|
Hist triggers allow one or more arbitrary trace event fields
|
||||||
|
to be aggregated into hash tables and dumped to stdout by
|
||||||
|
reading a debugfs/tracefs file. They're useful for
|
||||||
|
gathering quick and dirty (though precise) summaries of
|
||||||
|
event activity as an initial guide for further investigation
|
||||||
|
using more advanced tools.
|
||||||
|
|
||||||
|
See Documentation/trace/events.txt.
|
||||||
|
If in doubt, say N.
|
||||||
|
|
||||||
config MMIOTRACE_TEST
|
config MMIOTRACE_TEST
|
||||||
tristate "Test module for mmiotrace"
|
tristate "Test module for mmiotrace"
|
||||||
depends on MMIOTRACE && m
|
depends on MMIOTRACE && m
|
||||||
|
|
|
@ -54,6 +54,7 @@ obj-$(CONFIG_EVENT_TRACING) += trace_event_perf.o
|
||||||
endif
|
endif
|
||||||
obj-$(CONFIG_EVENT_TRACING) += trace_events_filter.o
|
obj-$(CONFIG_EVENT_TRACING) += trace_events_filter.o
|
||||||
obj-$(CONFIG_EVENT_TRACING) += trace_events_trigger.o
|
obj-$(CONFIG_EVENT_TRACING) += trace_events_trigger.o
|
||||||
|
obj-$(CONFIG_HIST_TRIGGERS) += trace_events_hist.o
|
||||||
obj-$(CONFIG_BPF_EVENTS) += bpf_trace.o
|
obj-$(CONFIG_BPF_EVENTS) += bpf_trace.o
|
||||||
obj-$(CONFIG_KPROBE_EVENT) += trace_kprobe.o
|
obj-$(CONFIG_KPROBE_EVENT) += trace_kprobe.o
|
||||||
obj-$(CONFIG_TRACEPOINTS) += power-traces.o
|
obj-$(CONFIG_TRACEPOINTS) += power-traces.o
|
||||||
|
|
|
@ -3812,6 +3812,9 @@ static const char readme_msg[] =
|
||||||
#endif
|
#endif
|
||||||
#ifdef CONFIG_TRACER_SNAPSHOT
|
#ifdef CONFIG_TRACER_SNAPSHOT
|
||||||
"\t\t snapshot\n"
|
"\t\t snapshot\n"
|
||||||
|
#endif
|
||||||
|
#ifdef CONFIG_HIST_TRIGGERS
|
||||||
|
"\t\t hist (see below)\n"
|
||||||
#endif
|
#endif
|
||||||
"\t example: echo traceoff > events/block/block_unplug/trigger\n"
|
"\t example: echo traceoff > events/block/block_unplug/trigger\n"
|
||||||
"\t echo traceoff:3 > events/block/block_unplug/trigger\n"
|
"\t echo traceoff:3 > events/block/block_unplug/trigger\n"
|
||||||
|
@ -3828,6 +3831,20 @@ static const char readme_msg[] =
|
||||||
"\t To remove a trigger with a count:\n"
|
"\t To remove a trigger with a count:\n"
|
||||||
"\t echo '!<trigger>:0 > <system>/<event>/trigger\n"
|
"\t echo '!<trigger>:0 > <system>/<event>/trigger\n"
|
||||||
"\t Filters can be ignored when removing a trigger.\n"
|
"\t Filters can be ignored when removing a trigger.\n"
|
||||||
|
#ifdef CONFIG_HIST_TRIGGERS
|
||||||
|
" hist trigger\t- If set, event hits are aggregated into a hash table\n"
|
||||||
|
"\t Format: hist:keys=<field1>\n"
|
||||||
|
"\t [:size=#entries]\n"
|
||||||
|
"\t [if <filter>]\n\n"
|
||||||
|
"\t When a matching event is hit, an entry is added to a hash\n"
|
||||||
|
"\t table using the key named, and the value of a sum called\n"
|
||||||
|
"\t 'hitcount' is incremented. Keys correspond to fields in the\n"
|
||||||
|
"\t event's format description. Keys can be any field. The\n"
|
||||||
|
"\t 'size' parameter can be used to specify more or fewer than\n"
|
||||||
|
"\t the default 2048 entries for the hashtable size.\n\n"
|
||||||
|
"\t Reading the 'hist' file for the event will dump the hash\n"
|
||||||
|
"\t table in its entirety to stdout."
|
||||||
|
#endif
|
||||||
;
|
;
|
||||||
|
|
||||||
static ssize_t
|
static ssize_t
|
||||||
|
|
|
@ -1162,6 +1162,13 @@ extern struct mutex event_mutex;
|
||||||
extern struct list_head ftrace_events;
|
extern struct list_head ftrace_events;
|
||||||
|
|
||||||
extern const struct file_operations event_trigger_fops;
|
extern const struct file_operations event_trigger_fops;
|
||||||
|
extern const struct file_operations event_hist_fops;
|
||||||
|
|
||||||
|
#ifdef CONFIG_HIST_TRIGGERS
|
||||||
|
extern int register_trigger_hist_cmd(void);
|
||||||
|
#else
|
||||||
|
static inline int register_trigger_hist_cmd(void) { return 0; }
|
||||||
|
#endif
|
||||||
|
|
||||||
extern int register_trigger_cmds(void);
|
extern int register_trigger_cmds(void);
|
||||||
extern void clear_event_triggers(struct trace_array *tr);
|
extern void clear_event_triggers(struct trace_array *tr);
|
||||||
|
|
|
@ -2141,6 +2141,10 @@ event_create_dir(struct dentry *parent, struct trace_event_file *file)
|
||||||
trace_create_file("trigger", 0644, file->dir, file,
|
trace_create_file("trigger", 0644, file->dir, file,
|
||||||
&event_trigger_fops);
|
&event_trigger_fops);
|
||||||
|
|
||||||
|
#ifdef CONFIG_HIST_TRIGGERS
|
||||||
|
trace_create_file("hist", 0444, file->dir, file,
|
||||||
|
&event_hist_fops);
|
||||||
|
#endif
|
||||||
trace_create_file("format", 0444, file->dir, call,
|
trace_create_file("format", 0444, file->dir, call,
|
||||||
&ftrace_event_format_fops);
|
&ftrace_event_format_fops);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,849 @@
|
||||||
|
/*
|
||||||
|
* trace_events_hist - trace event hist triggers
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2015 Tom Zanussi <tom.zanussi@linux.intel.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/kallsyms.h>
|
||||||
|
#include <linux/mutex.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/stacktrace.h>
|
||||||
|
|
||||||
|
#include "tracing_map.h"
|
||||||
|
#include "trace.h"
|
||||||
|
|
||||||
|
struct hist_field;
|
||||||
|
|
||||||
|
typedef u64 (*hist_field_fn_t) (struct hist_field *field, void *event);
|
||||||
|
|
||||||
|
struct hist_field {
|
||||||
|
struct ftrace_event_field *field;
|
||||||
|
unsigned long flags;
|
||||||
|
hist_field_fn_t fn;
|
||||||
|
unsigned int size;
|
||||||
|
};
|
||||||
|
|
||||||
|
static u64 hist_field_counter(struct hist_field *field, void *event)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u64 hist_field_string(struct hist_field *hist_field, void *event)
|
||||||
|
{
|
||||||
|
char *addr = (char *)(event + hist_field->field->offset);
|
||||||
|
|
||||||
|
return (u64)(unsigned long)addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define DEFINE_HIST_FIELD_FN(type) \
|
||||||
|
static u64 hist_field_##type(struct hist_field *hist_field, void *event)\
|
||||||
|
{ \
|
||||||
|
type *addr = (type *)(event + hist_field->field->offset); \
|
||||||
|
\
|
||||||
|
return (u64)*addr; \
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_HIST_FIELD_FN(s64);
|
||||||
|
DEFINE_HIST_FIELD_FN(u64);
|
||||||
|
DEFINE_HIST_FIELD_FN(s32);
|
||||||
|
DEFINE_HIST_FIELD_FN(u32);
|
||||||
|
DEFINE_HIST_FIELD_FN(s16);
|
||||||
|
DEFINE_HIST_FIELD_FN(u16);
|
||||||
|
DEFINE_HIST_FIELD_FN(s8);
|
||||||
|
DEFINE_HIST_FIELD_FN(u8);
|
||||||
|
|
||||||
|
#define for_each_hist_field(i, hist_data) \
|
||||||
|
for ((i) = 0; (i) < (hist_data)->n_fields; (i)++)
|
||||||
|
|
||||||
|
#define for_each_hist_val_field(i, hist_data) \
|
||||||
|
for ((i) = 0; (i) < (hist_data)->n_vals; (i)++)
|
||||||
|
|
||||||
|
#define for_each_hist_key_field(i, hist_data) \
|
||||||
|
for ((i) = (hist_data)->n_vals; (i) < (hist_data)->n_fields; (i)++)
|
||||||
|
|
||||||
|
#define HITCOUNT_IDX 0
|
||||||
|
#define HIST_KEY_MAX 1
|
||||||
|
#define HIST_KEY_SIZE_MAX MAX_FILTER_STR_VAL
|
||||||
|
|
||||||
|
enum hist_field_flags {
|
||||||
|
HIST_FIELD_FL_HITCOUNT = 1,
|
||||||
|
HIST_FIELD_FL_KEY = 2,
|
||||||
|
HIST_FIELD_FL_STRING = 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct hist_trigger_attrs {
|
||||||
|
char *keys_str;
|
||||||
|
unsigned int map_bits;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct hist_trigger_data {
|
||||||
|
struct hist_field *fields[TRACING_MAP_FIELDS_MAX];
|
||||||
|
unsigned int n_vals;
|
||||||
|
unsigned int n_keys;
|
||||||
|
unsigned int n_fields;
|
||||||
|
unsigned int key_size;
|
||||||
|
struct tracing_map_sort_key sort_keys[TRACING_MAP_SORT_KEYS_MAX];
|
||||||
|
unsigned int n_sort_keys;
|
||||||
|
struct trace_event_file *event_file;
|
||||||
|
struct hist_trigger_attrs *attrs;
|
||||||
|
struct tracing_map *map;
|
||||||
|
};
|
||||||
|
|
||||||
|
static hist_field_fn_t select_value_fn(int field_size, int field_is_signed)
|
||||||
|
{
|
||||||
|
hist_field_fn_t fn = NULL;
|
||||||
|
|
||||||
|
switch (field_size) {
|
||||||
|
case 8:
|
||||||
|
if (field_is_signed)
|
||||||
|
fn = hist_field_s64;
|
||||||
|
else
|
||||||
|
fn = hist_field_u64;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
if (field_is_signed)
|
||||||
|
fn = hist_field_s32;
|
||||||
|
else
|
||||||
|
fn = hist_field_u32;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
if (field_is_signed)
|
||||||
|
fn = hist_field_s16;
|
||||||
|
else
|
||||||
|
fn = hist_field_u16;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
if (field_is_signed)
|
||||||
|
fn = hist_field_s8;
|
||||||
|
else
|
||||||
|
fn = hist_field_u8;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parse_map_size(char *str)
|
||||||
|
{
|
||||||
|
unsigned long size, map_bits;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
strsep(&str, "=");
|
||||||
|
if (!str) {
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = kstrtoul(str, 0, &size);
|
||||||
|
if (ret)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
map_bits = ilog2(roundup_pow_of_two(size));
|
||||||
|
if (map_bits < TRACING_MAP_BITS_MIN ||
|
||||||
|
map_bits > TRACING_MAP_BITS_MAX)
|
||||||
|
ret = -EINVAL;
|
||||||
|
else
|
||||||
|
ret = map_bits;
|
||||||
|
out:
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void destroy_hist_trigger_attrs(struct hist_trigger_attrs *attrs)
|
||||||
|
{
|
||||||
|
if (!attrs)
|
||||||
|
return;
|
||||||
|
|
||||||
|
kfree(attrs->keys_str);
|
||||||
|
kfree(attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct hist_trigger_attrs *parse_hist_trigger_attrs(char *trigger_str)
|
||||||
|
{
|
||||||
|
struct hist_trigger_attrs *attrs;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
attrs = kzalloc(sizeof(*attrs), GFP_KERNEL);
|
||||||
|
if (!attrs)
|
||||||
|
return ERR_PTR(-ENOMEM);
|
||||||
|
|
||||||
|
while (trigger_str) {
|
||||||
|
char *str = strsep(&trigger_str, ":");
|
||||||
|
|
||||||
|
if ((strncmp(str, "key=", strlen("key=")) == 0) ||
|
||||||
|
(strncmp(str, "keys=", strlen("keys=")) == 0))
|
||||||
|
attrs->keys_str = kstrdup(str, GFP_KERNEL);
|
||||||
|
else if (strncmp(str, "size=", strlen("size=")) == 0) {
|
||||||
|
int map_bits = parse_map_size(str);
|
||||||
|
|
||||||
|
if (map_bits < 0) {
|
||||||
|
ret = map_bits;
|
||||||
|
goto free;
|
||||||
|
}
|
||||||
|
attrs->map_bits = map_bits;
|
||||||
|
} else {
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto free;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!attrs->keys_str) {
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto free;
|
||||||
|
}
|
||||||
|
|
||||||
|
return attrs;
|
||||||
|
free:
|
||||||
|
destroy_hist_trigger_attrs(attrs);
|
||||||
|
|
||||||
|
return ERR_PTR(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void destroy_hist_field(struct hist_field *hist_field)
|
||||||
|
{
|
||||||
|
kfree(hist_field);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct hist_field *create_hist_field(struct ftrace_event_field *field,
|
||||||
|
unsigned long flags)
|
||||||
|
{
|
||||||
|
struct hist_field *hist_field;
|
||||||
|
|
||||||
|
if (field && is_function_field(field))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
hist_field = kzalloc(sizeof(struct hist_field), GFP_KERNEL);
|
||||||
|
if (!hist_field)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (flags & HIST_FIELD_FL_HITCOUNT) {
|
||||||
|
hist_field->fn = hist_field_counter;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_string_field(field)) {
|
||||||
|
flags |= HIST_FIELD_FL_STRING;
|
||||||
|
hist_field->fn = hist_field_string;
|
||||||
|
} else {
|
||||||
|
hist_field->fn = select_value_fn(field->size,
|
||||||
|
field->is_signed);
|
||||||
|
if (!hist_field->fn) {
|
||||||
|
destroy_hist_field(hist_field);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out:
|
||||||
|
hist_field->field = field;
|
||||||
|
hist_field->flags = flags;
|
||||||
|
|
||||||
|
return hist_field;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void destroy_hist_fields(struct hist_trigger_data *hist_data)
|
||||||
|
{
|
||||||
|
unsigned int i;
|
||||||
|
|
||||||
|
for (i = 0; i < TRACING_MAP_FIELDS_MAX; i++) {
|
||||||
|
if (hist_data->fields[i]) {
|
||||||
|
destroy_hist_field(hist_data->fields[i]);
|
||||||
|
hist_data->fields[i] = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int create_hitcount_val(struct hist_trigger_data *hist_data)
|
||||||
|
{
|
||||||
|
hist_data->fields[HITCOUNT_IDX] =
|
||||||
|
create_hist_field(NULL, HIST_FIELD_FL_HITCOUNT);
|
||||||
|
if (!hist_data->fields[HITCOUNT_IDX])
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
hist_data->n_vals++;
|
||||||
|
|
||||||
|
if (WARN_ON(hist_data->n_vals > TRACING_MAP_VALS_MAX))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int create_val_fields(struct hist_trigger_data *hist_data,
|
||||||
|
struct trace_event_file *file)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = create_hitcount_val(hist_data);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int create_key_field(struct hist_trigger_data *hist_data,
|
||||||
|
unsigned int key_idx,
|
||||||
|
struct trace_event_file *file,
|
||||||
|
char *field_str)
|
||||||
|
{
|
||||||
|
struct ftrace_event_field *field = NULL;
|
||||||
|
unsigned long flags = 0;
|
||||||
|
unsigned int key_size;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
if (WARN_ON(key_idx >= TRACING_MAP_FIELDS_MAX))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
flags |= HIST_FIELD_FL_KEY;
|
||||||
|
|
||||||
|
field = trace_find_event_field(file->event_call, field_str);
|
||||||
|
if (!field) {
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
key_size = field->size;
|
||||||
|
|
||||||
|
hist_data->fields[key_idx] = create_hist_field(field, flags);
|
||||||
|
if (!hist_data->fields[key_idx]) {
|
||||||
|
ret = -ENOMEM;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
key_size = ALIGN(key_size, sizeof(u64));
|
||||||
|
hist_data->fields[key_idx]->size = key_size;
|
||||||
|
hist_data->key_size = key_size;
|
||||||
|
if (hist_data->key_size > HIST_KEY_SIZE_MAX) {
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
hist_data->n_keys++;
|
||||||
|
|
||||||
|
if (WARN_ON(hist_data->n_keys > TRACING_MAP_KEYS_MAX))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
ret = key_size;
|
||||||
|
out:
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int create_key_fields(struct hist_trigger_data *hist_data,
|
||||||
|
struct trace_event_file *file)
|
||||||
|
{
|
||||||
|
unsigned int i, n_vals = hist_data->n_vals;
|
||||||
|
char *fields_str, *field_str;
|
||||||
|
int ret = -EINVAL;
|
||||||
|
|
||||||
|
fields_str = hist_data->attrs->keys_str;
|
||||||
|
if (!fields_str)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
strsep(&fields_str, "=");
|
||||||
|
if (!fields_str)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
for (i = n_vals; i < n_vals + HIST_KEY_MAX; i++) {
|
||||||
|
field_str = strsep(&fields_str, ",");
|
||||||
|
if (!field_str)
|
||||||
|
break;
|
||||||
|
ret = create_key_field(hist_data, i, file, field_str);
|
||||||
|
if (ret < 0)
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
if (fields_str) {
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
ret = 0;
|
||||||
|
out:
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int create_hist_fields(struct hist_trigger_data *hist_data,
|
||||||
|
struct trace_event_file *file)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = create_val_fields(hist_data, file);
|
||||||
|
if (ret)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
ret = create_key_fields(hist_data, file);
|
||||||
|
if (ret)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
hist_data->n_fields = hist_data->n_vals + hist_data->n_keys;
|
||||||
|
out:
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int create_sort_keys(struct hist_trigger_data *hist_data)
|
||||||
|
{
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
hist_data->n_sort_keys = 1; /* sort_keys[0] is always hitcount */
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void destroy_hist_data(struct hist_trigger_data *hist_data)
|
||||||
|
{
|
||||||
|
destroy_hist_trigger_attrs(hist_data->attrs);
|
||||||
|
destroy_hist_fields(hist_data);
|
||||||
|
tracing_map_destroy(hist_data->map);
|
||||||
|
kfree(hist_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int create_tracing_map_fields(struct hist_trigger_data *hist_data)
|
||||||
|
{
|
||||||
|
struct tracing_map *map = hist_data->map;
|
||||||
|
struct ftrace_event_field *field;
|
||||||
|
struct hist_field *hist_field;
|
||||||
|
unsigned int i, idx;
|
||||||
|
|
||||||
|
for_each_hist_field(i, hist_data) {
|
||||||
|
hist_field = hist_data->fields[i];
|
||||||
|
if (hist_field->flags & HIST_FIELD_FL_KEY) {
|
||||||
|
tracing_map_cmp_fn_t cmp_fn;
|
||||||
|
|
||||||
|
field = hist_field->field;
|
||||||
|
|
||||||
|
if (is_string_field(field))
|
||||||
|
cmp_fn = tracing_map_cmp_string;
|
||||||
|
else
|
||||||
|
cmp_fn = tracing_map_cmp_num(field->size,
|
||||||
|
field->is_signed);
|
||||||
|
idx = tracing_map_add_key_field(map, 0, cmp_fn);
|
||||||
|
} else
|
||||||
|
idx = tracing_map_add_sum_field(map);
|
||||||
|
|
||||||
|
if (idx < 0)
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct hist_trigger_data *
|
||||||
|
create_hist_data(unsigned int map_bits,
|
||||||
|
struct hist_trigger_attrs *attrs,
|
||||||
|
struct trace_event_file *file)
|
||||||
|
{
|
||||||
|
struct hist_trigger_data *hist_data;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
hist_data = kzalloc(sizeof(*hist_data), GFP_KERNEL);
|
||||||
|
if (!hist_data)
|
||||||
|
return ERR_PTR(-ENOMEM);
|
||||||
|
|
||||||
|
hist_data->attrs = attrs;
|
||||||
|
|
||||||
|
ret = create_hist_fields(hist_data, file);
|
||||||
|
if (ret)
|
||||||
|
goto free;
|
||||||
|
|
||||||
|
ret = create_sort_keys(hist_data);
|
||||||
|
if (ret)
|
||||||
|
goto free;
|
||||||
|
|
||||||
|
hist_data->map = tracing_map_create(map_bits, hist_data->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;
|
||||||
|
|
||||||
|
hist_data->event_file = file;
|
||||||
|
out:
|
||||||
|
return hist_data;
|
||||||
|
free:
|
||||||
|
hist_data->attrs = NULL;
|
||||||
|
|
||||||
|
destroy_hist_data(hist_data);
|
||||||
|
|
||||||
|
hist_data = ERR_PTR(ret);
|
||||||
|
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hist_trigger_elt_update(struct hist_trigger_data *hist_data,
|
||||||
|
struct tracing_map_elt *elt,
|
||||||
|
void *rec)
|
||||||
|
{
|
||||||
|
struct hist_field *hist_field;
|
||||||
|
unsigned int i;
|
||||||
|
u64 hist_val;
|
||||||
|
|
||||||
|
for_each_hist_val_field(i, hist_data) {
|
||||||
|
hist_field = hist_data->fields[i];
|
||||||
|
hist_val = hist_field->fn(hist_field, rec);
|
||||||
|
tracing_map_update_sum(elt, i, hist_val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void event_hist_trigger(struct event_trigger_data *data, void *rec)
|
||||||
|
{
|
||||||
|
struct hist_trigger_data *hist_data = data->private_data;
|
||||||
|
struct hist_field *key_field;
|
||||||
|
struct tracing_map_elt *elt;
|
||||||
|
u64 field_contents;
|
||||||
|
void *key = NULL;
|
||||||
|
unsigned int i;
|
||||||
|
|
||||||
|
for_each_hist_key_field(i, hist_data) {
|
||||||
|
key_field = hist_data->fields[i];
|
||||||
|
|
||||||
|
field_contents = key_field->fn(key_field, rec);
|
||||||
|
if (key_field->flags & HIST_FIELD_FL_STRING)
|
||||||
|
key = (void *)(unsigned long)field_contents;
|
||||||
|
else
|
||||||
|
key = (void *)&field_contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
elt = tracing_map_insert(hist_data->map, key);
|
||||||
|
if (elt)
|
||||||
|
hist_trigger_elt_update(hist_data, elt, rec);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
hist_trigger_entry_print(struct seq_file *m,
|
||||||
|
struct hist_trigger_data *hist_data, void *key,
|
||||||
|
struct tracing_map_elt *elt)
|
||||||
|
{
|
||||||
|
struct hist_field *key_field;
|
||||||
|
unsigned int i;
|
||||||
|
u64 uval;
|
||||||
|
|
||||||
|
seq_puts(m, "{ ");
|
||||||
|
|
||||||
|
for_each_hist_key_field(i, hist_data) {
|
||||||
|
key_field = hist_data->fields[i];
|
||||||
|
|
||||||
|
if (i > hist_data->n_vals)
|
||||||
|
seq_puts(m, ", ");
|
||||||
|
|
||||||
|
if (key_field->flags & HIST_FIELD_FL_STRING) {
|
||||||
|
seq_printf(m, "%s: %-50s", key_field->field->name,
|
||||||
|
(char *)key);
|
||||||
|
} else {
|
||||||
|
uval = *(u64 *)key;
|
||||||
|
seq_printf(m, "%s: %10llu",
|
||||||
|
key_field->field->name, uval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
seq_puts(m, " }");
|
||||||
|
|
||||||
|
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 hist_trigger_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_keys,
|
||||||
|
hist_data->n_sort_keys,
|
||||||
|
&sort_entries);
|
||||||
|
if (n_entries < 0)
|
||||||
|
return n_entries;
|
||||||
|
|
||||||
|
for (i = 0; i < n_entries; i++)
|
||||||
|
hist_trigger_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)
|
||||||
|
{
|
||||||
|
struct event_trigger_data *test, *data = NULL;
|
||||||
|
struct trace_event_file *event_file;
|
||||||
|
struct hist_trigger_data *hist_data;
|
||||||
|
int n_entries, ret = 0;
|
||||||
|
|
||||||
|
mutex_lock(&event_mutex);
|
||||||
|
|
||||||
|
event_file = event_file_data(m->private);
|
||||||
|
if (unlikely(!event_file)) {
|
||||||
|
ret = -ENODEV;
|
||||||
|
goto out_unlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
list_for_each_entry_rcu(test, &event_file->triggers, list) {
|
||||||
|
if (test->cmd_ops->trigger_type == ETT_EVENT_HIST) {
|
||||||
|
data = test;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!data)
|
||||||
|
goto out_unlock;
|
||||||
|
|
||||||
|
seq_puts(m, "# event histogram\n#\n# trigger info: ");
|
||||||
|
data->ops->print(m, data->ops, data);
|
||||||
|
seq_puts(m, "\n");
|
||||||
|
|
||||||
|
hist_data = data->private_data;
|
||||||
|
n_entries = print_entries(m, hist_data);
|
||||||
|
if (n_entries < 0) {
|
||||||
|
ret = n_entries;
|
||||||
|
n_entries = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
seq_printf(m, "\nTotals:\n Hits: %llu\n Entries: %u\n Dropped: %llu\n",
|
||||||
|
(u64)atomic64_read(&hist_data->map->hits),
|
||||||
|
n_entries, (u64)atomic64_read(&hist_data->map->drops));
|
||||||
|
out_unlock:
|
||||||
|
mutex_unlock(&event_mutex);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int event_hist_open(struct inode *inode, struct file *file)
|
||||||
|
{
|
||||||
|
return single_open(file, hist_show, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
const struct file_operations event_hist_fops = {
|
||||||
|
.open = event_hist_open,
|
||||||
|
.read = seq_read,
|
||||||
|
.llseek = seq_lseek,
|
||||||
|
.release = single_release,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void hist_field_print(struct seq_file *m, struct hist_field *hist_field)
|
||||||
|
{
|
||||||
|
seq_printf(m, "%s", hist_field->field->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int event_hist_trigger_print(struct seq_file *m,
|
||||||
|
struct event_trigger_ops *ops,
|
||||||
|
struct event_trigger_data *data)
|
||||||
|
{
|
||||||
|
struct hist_trigger_data *hist_data = data->private_data;
|
||||||
|
struct hist_field *key_field;
|
||||||
|
unsigned int i;
|
||||||
|
|
||||||
|
seq_puts(m, "hist:keys=");
|
||||||
|
|
||||||
|
for_each_hist_key_field(i, hist_data) {
|
||||||
|
key_field = hist_data->fields[i];
|
||||||
|
|
||||||
|
if (i > hist_data->n_vals)
|
||||||
|
seq_puts(m, ",");
|
||||||
|
|
||||||
|
hist_field_print(m, key_field);
|
||||||
|
}
|
||||||
|
|
||||||
|
seq_puts(m, ":vals=");
|
||||||
|
seq_puts(m, "hitcount");
|
||||||
|
|
||||||
|
seq_puts(m, ":sort=");
|
||||||
|
seq_puts(m, "hitcount");
|
||||||
|
|
||||||
|
seq_printf(m, ":size=%u", (1 << hist_data->map->map_bits));
|
||||||
|
|
||||||
|
if (data->filter_str)
|
||||||
|
seq_printf(m, " if %s", data->filter_str);
|
||||||
|
|
||||||
|
seq_puts(m, " [active]");
|
||||||
|
|
||||||
|
seq_putc(m, '\n');
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void event_hist_trigger_free(struct event_trigger_ops *ops,
|
||||||
|
struct event_trigger_data *data)
|
||||||
|
{
|
||||||
|
struct hist_trigger_data *hist_data = data->private_data;
|
||||||
|
|
||||||
|
if (WARN_ON_ONCE(data->ref <= 0))
|
||||||
|
return;
|
||||||
|
|
||||||
|
data->ref--;
|
||||||
|
if (!data->ref) {
|
||||||
|
trigger_data_free(data);
|
||||||
|
destroy_hist_data(hist_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct event_trigger_ops event_hist_trigger_ops = {
|
||||||
|
.func = event_hist_trigger,
|
||||||
|
.print = event_hist_trigger_print,
|
||||||
|
.init = event_trigger_init,
|
||||||
|
.free = event_hist_trigger_free,
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct event_trigger_ops *event_hist_get_trigger_ops(char *cmd,
|
||||||
|
char *param)
|
||||||
|
{
|
||||||
|
return &event_hist_trigger_ops;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int hist_register_trigger(char *glob, struct event_trigger_ops *ops,
|
||||||
|
struct event_trigger_data *data,
|
||||||
|
struct trace_event_file *file)
|
||||||
|
{
|
||||||
|
struct event_trigger_data *test;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
list_for_each_entry_rcu(test, &file->triggers, list) {
|
||||||
|
if (test->cmd_ops->trigger_type == ETT_EVENT_HIST) {
|
||||||
|
ret = -EEXIST;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data->ops->init) {
|
||||||
|
ret = data->ops->init(data->ops, data);
|
||||||
|
if (ret < 0)
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
list_add_rcu(&data->list, &file->triggers);
|
||||||
|
ret++;
|
||||||
|
|
||||||
|
update_cond_flag(file);
|
||||||
|
if (trace_event_trigger_enable_disable(file, 1) < 0) {
|
||||||
|
list_del_rcu(&data->list);
|
||||||
|
update_cond_flag(file);
|
||||||
|
ret--;
|
||||||
|
}
|
||||||
|
out:
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int event_hist_trigger_func(struct event_command *cmd_ops,
|
||||||
|
struct trace_event_file *file,
|
||||||
|
char *glob, char *cmd, char *param)
|
||||||
|
{
|
||||||
|
unsigned int hist_trigger_bits = TRACING_MAP_BITS_DEFAULT;
|
||||||
|
struct event_trigger_data *trigger_data;
|
||||||
|
struct hist_trigger_attrs *attrs;
|
||||||
|
struct event_trigger_ops *trigger_ops;
|
||||||
|
struct hist_trigger_data *hist_data;
|
||||||
|
char *trigger;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
if (!param)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
/* separate the trigger from the filter (k:v [if filter]) */
|
||||||
|
trigger = strsep(¶m, " \t");
|
||||||
|
if (!trigger)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
attrs = parse_hist_trigger_attrs(trigger);
|
||||||
|
if (IS_ERR(attrs))
|
||||||
|
return PTR_ERR(attrs);
|
||||||
|
|
||||||
|
if (attrs->map_bits)
|
||||||
|
hist_trigger_bits = attrs->map_bits;
|
||||||
|
|
||||||
|
hist_data = create_hist_data(hist_trigger_bits, attrs, file);
|
||||||
|
if (IS_ERR(hist_data)) {
|
||||||
|
destroy_hist_trigger_attrs(attrs);
|
||||||
|
return PTR_ERR(hist_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
trigger_ops = cmd_ops->get_trigger_ops(cmd, trigger);
|
||||||
|
|
||||||
|
ret = -ENOMEM;
|
||||||
|
trigger_data = kzalloc(sizeof(*trigger_data), GFP_KERNEL);
|
||||||
|
if (!trigger_data)
|
||||||
|
goto out_free;
|
||||||
|
|
||||||
|
trigger_data->count = -1;
|
||||||
|
trigger_data->ops = trigger_ops;
|
||||||
|
trigger_data->cmd_ops = cmd_ops;
|
||||||
|
|
||||||
|
INIT_LIST_HEAD(&trigger_data->list);
|
||||||
|
RCU_INIT_POINTER(trigger_data->filter, NULL);
|
||||||
|
|
||||||
|
trigger_data->private_data = hist_data;
|
||||||
|
|
||||||
|
if (glob[0] == '!') {
|
||||||
|
cmd_ops->unreg(glob+1, trigger_ops, trigger_data, file);
|
||||||
|
ret = 0;
|
||||||
|
goto out_free;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!param) /* if param is non-empty, it's supposed to be a filter */
|
||||||
|
goto out_reg;
|
||||||
|
|
||||||
|
if (!cmd_ops->set_filter)
|
||||||
|
goto out_reg;
|
||||||
|
|
||||||
|
ret = cmd_ops->set_filter(param, trigger_data, file);
|
||||||
|
if (ret < 0)
|
||||||
|
goto out_free;
|
||||||
|
out_reg:
|
||||||
|
ret = cmd_ops->reg(glob, trigger_ops, trigger_data, file);
|
||||||
|
/*
|
||||||
|
* The above returns on success the # of triggers registered,
|
||||||
|
* but if it didn't register any it returns zero. Consider no
|
||||||
|
* triggers registered a failure too.
|
||||||
|
*/
|
||||||
|
if (!ret) {
|
||||||
|
ret = -ENOENT;
|
||||||
|
goto out_free;
|
||||||
|
} else if (ret < 0)
|
||||||
|
goto out_free;
|
||||||
|
/* Just return zero, not the number of registered triggers */
|
||||||
|
ret = 0;
|
||||||
|
out:
|
||||||
|
return ret;
|
||||||
|
out_free:
|
||||||
|
if (cmd_ops->set_filter)
|
||||||
|
cmd_ops->set_filter(NULL, trigger_data, NULL);
|
||||||
|
|
||||||
|
kfree(trigger_data);
|
||||||
|
|
||||||
|
destroy_hist_data(hist_data);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct event_command trigger_hist_cmd = {
|
||||||
|
.name = "hist",
|
||||||
|
.trigger_type = ETT_EVENT_HIST,
|
||||||
|
.flags = EVENT_CMD_FL_NEEDS_REC,
|
||||||
|
.func = event_hist_trigger_func,
|
||||||
|
.reg = hist_register_trigger,
|
||||||
|
.unreg = unregister_trigger,
|
||||||
|
.get_trigger_ops = event_hist_get_trigger_ops,
|
||||||
|
.set_filter = set_trigger_filter,
|
||||||
|
};
|
||||||
|
|
||||||
|
__init int register_trigger_hist_cmd(void)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = register_event_command(&trigger_hist_cmd);
|
||||||
|
WARN_ON(ret < 0);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
|
@ -1447,6 +1447,7 @@ __init int register_trigger_cmds(void)
|
||||||
register_trigger_snapshot_cmd();
|
register_trigger_snapshot_cmd();
|
||||||
register_trigger_stacktrace_cmd();
|
register_trigger_stacktrace_cmd();
|
||||||
register_trigger_enable_disable_cmds();
|
register_trigger_enable_disable_cmds();
|
||||||
|
register_trigger_hist_cmd();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue