tracing: Add hist trigger support for stacktraces as keys
It's often useful to be able to use a stacktrace as a hash key, for keeping a count of the number of times a particular call path resulted in a trace event, for instance. Add a special key named 'stacktrace' which can be used as key in a 'keys=' param for this purpose: # echo hist:keys=stacktrace ... \ [ if filter] > event/trigger Link: http://lkml.kernel.org/r/87515e90b3785232a874a12156174635a348edb1.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
316961988b
commit
69a0200c2e
|
@ -3843,14 +3843,14 @@ static const char readme_msg[] =
|
|||
"\t table using the key(s) and value(s) named, and the value of a\n"
|
||||
"\t sum called 'hitcount' is incremented. Keys and values\n"
|
||||
"\t correspond to fields in the event's format description. Keys\n"
|
||||
"\t can be any field. Compound keys consisting of up to two\n"
|
||||
"\t fields can be specified by the 'keys' keyword. Values must\n"
|
||||
"\t correspond to numeric fields. Sort keys consisting of up to\n"
|
||||
"\t two fields can be specified using the 'sort' keyword. The\n"
|
||||
"\t sort direction can be modified by appending '.descending' or\n"
|
||||
"\t '.ascending' to a sort field. The 'size' parameter can be\n"
|
||||
"\t used to specify more or fewer than the default 2048 entries\n"
|
||||
"\t for the hashtable size.\n\n"
|
||||
"\t can be any field, or the special string 'stacktrace'.\n"
|
||||
"\t Compound keys consisting of up to two fields can be specified\n"
|
||||
"\t by the 'keys' keyword. Values must correspond to numeric\n"
|
||||
"\t fields. Sort keys consisting of up to two fields can be\n"
|
||||
"\t specified using the 'sort' keyword. The sort direction can\n"
|
||||
"\t be modified by appending '.descending' or '.ascending' to a\n"
|
||||
"\t sort field. The 'size' parameter can be used to specify more\n"
|
||||
"\t or fewer than 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. The default format used to\n"
|
||||
"\t display a given field can be modified by appending any of the\n"
|
||||
|
|
|
@ -35,6 +35,11 @@ struct hist_field {
|
|||
unsigned int offset;
|
||||
};
|
||||
|
||||
static u64 hist_field_none(struct hist_field *field, void *event)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u64 hist_field_counter(struct hist_field *field, void *event)
|
||||
{
|
||||
return 1;
|
||||
|
@ -73,8 +78,12 @@ DEFINE_HIST_FIELD_FN(u8);
|
|||
#define for_each_hist_key_field(i, hist_data) \
|
||||
for ((i) = (hist_data)->n_vals; (i) < (hist_data)->n_fields; (i)++)
|
||||
|
||||
#define HIST_STACKTRACE_DEPTH 16
|
||||
#define HIST_STACKTRACE_SIZE (HIST_STACKTRACE_DEPTH * sizeof(unsigned long))
|
||||
#define HIST_STACKTRACE_SKIP 5
|
||||
|
||||
#define HITCOUNT_IDX 0
|
||||
#define HIST_KEY_SIZE_MAX (MAX_FILTER_STR_VAL + sizeof(u64))
|
||||
#define HIST_KEY_SIZE_MAX (MAX_FILTER_STR_VAL + HIST_STACKTRACE_SIZE)
|
||||
|
||||
enum hist_field_flags {
|
||||
HIST_FIELD_FL_HITCOUNT = 1,
|
||||
|
@ -85,6 +94,7 @@ enum hist_field_flags {
|
|||
HIST_FIELD_FL_SYM_OFFSET = 32,
|
||||
HIST_FIELD_FL_EXECNAME = 64,
|
||||
HIST_FIELD_FL_SYSCALL = 128,
|
||||
HIST_FIELD_FL_STACKTRACE = 256,
|
||||
};
|
||||
|
||||
struct hist_trigger_attrs {
|
||||
|
@ -323,6 +333,11 @@ static struct hist_field *create_hist_field(struct ftrace_event_field *field,
|
|||
goto out;
|
||||
}
|
||||
|
||||
if (flags & HIST_FIELD_FL_STACKTRACE) {
|
||||
hist_field->fn = hist_field_none;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (is_string_field(field)) {
|
||||
flags |= HIST_FIELD_FL_STRING;
|
||||
hist_field->fn = hist_field_string;
|
||||
|
@ -456,7 +471,6 @@ static int create_key_field(struct hist_trigger_data *hist_data,
|
|||
struct ftrace_event_field *field = NULL;
|
||||
unsigned long flags = 0;
|
||||
unsigned int key_size;
|
||||
char *field_name;
|
||||
int ret = 0;
|
||||
|
||||
if (WARN_ON(key_idx >= TRACING_MAP_FIELDS_MAX))
|
||||
|
@ -464,7 +478,12 @@ static int create_key_field(struct hist_trigger_data *hist_data,
|
|||
|
||||
flags |= HIST_FIELD_FL_KEY;
|
||||
|
||||
field_name = strsep(&field_str, ".");
|
||||
if (strcmp(field_str, "stacktrace") == 0) {
|
||||
flags |= HIST_FIELD_FL_STACKTRACE;
|
||||
key_size = sizeof(unsigned long) * HIST_STACKTRACE_DEPTH;
|
||||
} else {
|
||||
char *field_name = strsep(&field_str, ".");
|
||||
|
||||
if (field_str) {
|
||||
if (strcmp(field_str, "hex") == 0)
|
||||
flags |= HIST_FIELD_FL_HEX;
|
||||
|
@ -490,6 +509,7 @@ static int create_key_field(struct hist_trigger_data *hist_data,
|
|||
}
|
||||
|
||||
key_size = field->size;
|
||||
}
|
||||
|
||||
hist_data->fields[key_idx] = create_hist_field(field, flags);
|
||||
if (!hist_data->fields[key_idx]) {
|
||||
|
@ -679,7 +699,9 @@ static int create_tracing_map_fields(struct hist_trigger_data *hist_data)
|
|||
|
||||
field = hist_field->field;
|
||||
|
||||
if (is_string_field(field))
|
||||
if (hist_field->flags & HIST_FIELD_FL_STACKTRACE)
|
||||
cmp_fn = tracing_map_cmp_none;
|
||||
else if (is_string_field(field))
|
||||
cmp_fn = tracing_map_cmp_string;
|
||||
else
|
||||
cmp_fn = tracing_map_cmp_num(field->size,
|
||||
|
@ -786,7 +808,9 @@ static void hist_trigger_elt_update(struct hist_trigger_data *hist_data,
|
|||
static void event_hist_trigger(struct event_trigger_data *data, void *rec)
|
||||
{
|
||||
struct hist_trigger_data *hist_data = data->private_data;
|
||||
unsigned long entries[HIST_STACKTRACE_DEPTH];
|
||||
char compound_key[HIST_KEY_SIZE_MAX];
|
||||
struct stack_trace stacktrace;
|
||||
struct hist_field *key_field;
|
||||
struct tracing_map_elt *elt;
|
||||
u64 field_contents;
|
||||
|
@ -799,6 +823,17 @@ static void event_hist_trigger(struct event_trigger_data *data, void *rec)
|
|||
for_each_hist_key_field(i, hist_data) {
|
||||
key_field = hist_data->fields[i];
|
||||
|
||||
if (key_field->flags & HIST_FIELD_FL_STACKTRACE) {
|
||||
stacktrace.max_entries = HIST_STACKTRACE_DEPTH;
|
||||
stacktrace.entries = entries;
|
||||
stacktrace.nr_entries = 0;
|
||||
stacktrace.skip = HIST_STACKTRACE_SKIP;
|
||||
|
||||
memset(stacktrace.entries, 0, HIST_STACKTRACE_SIZE);
|
||||
save_stack_trace(&stacktrace);
|
||||
|
||||
key = entries;
|
||||
} else {
|
||||
field_contents = key_field->fn(key_field, rec);
|
||||
if (key_field->flags & HIST_FIELD_FL_STRING)
|
||||
key = (void *)(unsigned long)field_contents;
|
||||
|
@ -810,6 +845,7 @@ static void event_hist_trigger(struct event_trigger_data *data, void *rec)
|
|||
key_field->size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hist_data->n_keys > 1)
|
||||
key = compound_key;
|
||||
|
@ -819,6 +855,24 @@ static void event_hist_trigger(struct event_trigger_data *data, void *rec)
|
|||
hist_trigger_elt_update(hist_data, elt, rec);
|
||||
}
|
||||
|
||||
static void hist_trigger_stacktrace_print(struct seq_file *m,
|
||||
unsigned long *stacktrace_entries,
|
||||
unsigned int max_entries)
|
||||
{
|
||||
char str[KSYM_SYMBOL_LEN];
|
||||
unsigned int spaces = 8;
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < max_entries; i++) {
|
||||
if (stacktrace_entries[i] == ULONG_MAX)
|
||||
return;
|
||||
|
||||
seq_printf(m, "%*c", 1 + spaces, ' ');
|
||||
sprint_symbol(str, stacktrace_entries[i]);
|
||||
seq_printf(m, "%s\n", str);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
hist_trigger_entry_print(struct seq_file *m,
|
||||
struct hist_trigger_data *hist_data, void *key,
|
||||
|
@ -826,6 +880,7 @@ hist_trigger_entry_print(struct seq_file *m,
|
|||
{
|
||||
struct hist_field *key_field;
|
||||
char str[KSYM_SYMBOL_LEN];
|
||||
bool multiline = false;
|
||||
unsigned int i;
|
||||
u64 uval;
|
||||
|
||||
|
@ -867,6 +922,12 @@ hist_trigger_entry_print(struct seq_file *m,
|
|||
|
||||
seq_printf(m, "%s: %-30s[%3llu]",
|
||||
key_field->field->name, syscall_name, uval);
|
||||
} else if (key_field->flags & HIST_FIELD_FL_STACKTRACE) {
|
||||
seq_puts(m, "stacktrace:\n");
|
||||
hist_trigger_stacktrace_print(m,
|
||||
key + key_field->offset,
|
||||
HIST_STACKTRACE_DEPTH);
|
||||
multiline = true;
|
||||
} else if (key_field->flags & HIST_FIELD_FL_STRING) {
|
||||
seq_printf(m, "%s: %-50s", key_field->field->name,
|
||||
(char *)(key + key_field->offset));
|
||||
|
@ -877,7 +938,10 @@ hist_trigger_entry_print(struct seq_file *m,
|
|||
}
|
||||
}
|
||||
|
||||
seq_puts(m, " }");
|
||||
if (!multiline)
|
||||
seq_puts(m, " ");
|
||||
|
||||
seq_puts(m, "}");
|
||||
|
||||
seq_printf(m, " hitcount: %10llu",
|
||||
tracing_map_read_sum(elt, HITCOUNT_IDX));
|
||||
|
@ -1021,6 +1085,9 @@ static int event_hist_trigger_print(struct seq_file *m,
|
|||
if (i > hist_data->n_vals)
|
||||
seq_puts(m, ",");
|
||||
|
||||
if (key_field->flags & HIST_FIELD_FL_STACKTRACE)
|
||||
seq_puts(m, "stacktrace");
|
||||
else
|
||||
hist_field_print(m, key_field);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue