f2606835d7
Allow users to specify trace event fields to use in aggregated sums via a new 'vals=' keyword. Before this addition, the only aggregated sum supported was the implied value 'hitcount'. With this addition, 'hitcount' is also supported as an explicit value field, as is any numeric trace event field. This expands the hist trigger syntax from this: # echo hist:keys=xxx [ if filter] > event/trigger to this: # echo hist:keys=xxx:vals=yyy [ if filter] > event/trigger Link: http://lkml.kernel.org/r/2a5d1adb5ba6c65d7bb2148e379f2fed47f29a68.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>
927 lines
20 KiB
C
927 lines
20 KiB
C
/*
|
|
* 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;
|
|
char *vals_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->vals_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, "val=", strlen("val=")) == 0) ||
|
|
(strncmp(str, "vals=", strlen("vals=")) == 0) ||
|
|
(strncmp(str, "values=", strlen("values=")) == 0))
|
|
attrs->vals_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_field(struct hist_trigger_data *hist_data,
|
|
unsigned int val_idx,
|
|
struct trace_event_file *file,
|
|
char *field_str)
|
|
{
|
|
struct ftrace_event_field *field = NULL;
|
|
unsigned long flags = 0;
|
|
int ret = 0;
|
|
|
|
if (WARN_ON(val_idx >= TRACING_MAP_VALS_MAX))
|
|
return -EINVAL;
|
|
field = trace_find_event_field(file->event_call, field_str);
|
|
if (!field) {
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
hist_data->fields[val_idx] = create_hist_field(field, flags);
|
|
if (!hist_data->fields[val_idx]) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
++hist_data->n_vals;
|
|
|
|
if (WARN_ON(hist_data->n_vals > TRACING_MAP_VALS_MAX))
|
|
ret = -EINVAL;
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static int create_val_fields(struct hist_trigger_data *hist_data,
|
|
struct trace_event_file *file)
|
|
{
|
|
char *fields_str, *field_str;
|
|
unsigned int i, j;
|
|
int ret;
|
|
|
|
ret = create_hitcount_val(hist_data);
|
|
if (ret)
|
|
goto out;
|
|
|
|
fields_str = hist_data->attrs->vals_str;
|
|
if (!fields_str)
|
|
goto out;
|
|
|
|
strsep(&fields_str, "=");
|
|
if (!fields_str)
|
|
goto out;
|
|
|
|
for (i = 0, j = 1; i < TRACING_MAP_VALS_MAX &&
|
|
j < TRACING_MAP_VALS_MAX; i++) {
|
|
field_str = strsep(&fields_str, ",");
|
|
if (!field_str)
|
|
break;
|
|
if (strcmp(field_str, "hitcount") == 0)
|
|
continue;
|
|
ret = create_val_field(hist_data, j++, file, field_str);
|
|
if (ret)
|
|
goto out;
|
|
}
|
|
if (fields_str && (strcmp(fields_str, "hitcount") != 0))
|
|
ret = -EINVAL;
|
|
out:
|
|
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));
|
|
|
|
for (i = 1; i < hist_data->n_vals; i++) {
|
|
seq_printf(m, " %s: %10llu",
|
|
hist_data->fields[i]->field->name,
|
|
tracing_map_read_sum(elt, i));
|
|
}
|
|
|
|
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=");
|
|
|
|
for_each_hist_val_field(i, hist_data) {
|
|
if (i == HITCOUNT_IDX)
|
|
seq_puts(m, "hitcount");
|
|
else {
|
|
seq_puts(m, ",");
|
|
hist_field_print(m, hist_data->fields[i]);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|