tracing: Add support for named hist triggers
Allow users to define 'named' hist triggers. All triggers created with the same 'name=xxx' option will update the same shared histogram data. This expands the hist trigger syntax from this: # echo hist:keys=xxx ... [ if filter] > event/trigger to this: # echo hist:name=xxx:keys=xxx ... [ if filter] > event/trigger Named histograms must use a 'compatible' set of keys and values, which means each event added to a set of named triggers must have the same names and types. Reading the 'hist' file of any of the participating events will produce the same output as any other participating event, which is to be expected since they share the same data. Link: http://lkml.kernel.org/r/1dbc84ee3322a75daaf5b3ef1d0cc0a2fb682fc7.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:

committed by
Steven Rostedt

parent
db1388b4ff
commit
5463bfda32
@@ -117,6 +117,7 @@ struct hist_trigger_attrs {
|
||||
char *keys_str;
|
||||
char *vals_str;
|
||||
char *sort_key_str;
|
||||
char *name;
|
||||
bool pause;
|
||||
bool cont;
|
||||
bool clear;
|
||||
@@ -200,6 +201,7 @@ static void destroy_hist_trigger_attrs(struct hist_trigger_attrs *attrs)
|
||||
if (!attrs)
|
||||
return;
|
||||
|
||||
kfree(attrs->name);
|
||||
kfree(attrs->sort_key_str);
|
||||
kfree(attrs->keys_str);
|
||||
kfree(attrs->vals_str);
|
||||
@@ -227,6 +229,8 @@ static struct hist_trigger_attrs *parse_hist_trigger_attrs(char *trigger_str)
|
||||
attrs->vals_str = kstrdup(str, GFP_KERNEL);
|
||||
else if (strncmp(str, "sort=", strlen("sort=")) == 0)
|
||||
attrs->sort_key_str = kstrdup(str, GFP_KERNEL);
|
||||
else if (strncmp(str, "name=", strlen("name=")) == 0)
|
||||
attrs->name = kstrdup(str, GFP_KERNEL);
|
||||
else if (strcmp(str, "pause") == 0)
|
||||
attrs->pause = true;
|
||||
else if ((strcmp(str, "cont") == 0) ||
|
||||
@@ -1131,7 +1135,12 @@ static int event_hist_trigger_print(struct seq_file *m,
|
||||
struct hist_field *key_field;
|
||||
unsigned int i;
|
||||
|
||||
seq_puts(m, "hist:keys=");
|
||||
seq_puts(m, "hist:");
|
||||
|
||||
if (data->name)
|
||||
seq_printf(m, "%s:", data->name);
|
||||
|
||||
seq_puts(m, "keys=");
|
||||
|
||||
for_each_hist_key_field(i, hist_data) {
|
||||
key_field = hist_data->fields[i];
|
||||
@@ -1196,6 +1205,19 @@ static int event_hist_trigger_print(struct seq_file *m,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int event_hist_trigger_init(struct event_trigger_ops *ops,
|
||||
struct event_trigger_data *data)
|
||||
{
|
||||
struct hist_trigger_data *hist_data = data->private_data;
|
||||
|
||||
if (!data->ref && hist_data->attrs->name)
|
||||
save_named_trigger(hist_data->attrs->name, data);
|
||||
|
||||
data->ref++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void event_hist_trigger_free(struct event_trigger_ops *ops,
|
||||
struct event_trigger_data *data)
|
||||
{
|
||||
@@ -1206,6 +1228,8 @@ static void event_hist_trigger_free(struct event_trigger_ops *ops,
|
||||
|
||||
data->ref--;
|
||||
if (!data->ref) {
|
||||
if (data->name)
|
||||
del_named_trigger(data);
|
||||
trigger_data_free(data);
|
||||
destroy_hist_data(hist_data);
|
||||
}
|
||||
@@ -1214,10 +1238,44 @@ static void event_hist_trigger_free(struct event_trigger_ops *ops,
|
||||
static struct event_trigger_ops event_hist_trigger_ops = {
|
||||
.func = event_hist_trigger,
|
||||
.print = event_hist_trigger_print,
|
||||
.init = event_trigger_init,
|
||||
.init = event_hist_trigger_init,
|
||||
.free = event_hist_trigger_free,
|
||||
};
|
||||
|
||||
static int event_hist_trigger_named_init(struct event_trigger_ops *ops,
|
||||
struct event_trigger_data *data)
|
||||
{
|
||||
data->ref++;
|
||||
|
||||
save_named_trigger(data->named_data->name, data);
|
||||
|
||||
event_hist_trigger_init(ops, data->named_data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void event_hist_trigger_named_free(struct event_trigger_ops *ops,
|
||||
struct event_trigger_data *data)
|
||||
{
|
||||
if (WARN_ON_ONCE(data->ref <= 0))
|
||||
return;
|
||||
|
||||
event_hist_trigger_free(ops, data->named_data);
|
||||
|
||||
data->ref--;
|
||||
if (!data->ref) {
|
||||
del_named_trigger(data);
|
||||
trigger_data_free(data);
|
||||
}
|
||||
}
|
||||
|
||||
static struct event_trigger_ops event_hist_trigger_named_ops = {
|
||||
.func = event_hist_trigger,
|
||||
.print = event_hist_trigger_print,
|
||||
.init = event_hist_trigger_named_init,
|
||||
.free = event_hist_trigger_named_free,
|
||||
};
|
||||
|
||||
static struct event_trigger_ops *event_hist_get_trigger_ops(char *cmd,
|
||||
char *param)
|
||||
{
|
||||
@@ -1227,26 +1285,54 @@ static struct event_trigger_ops *event_hist_get_trigger_ops(char *cmd,
|
||||
static void hist_clear(struct event_trigger_data *data)
|
||||
{
|
||||
struct hist_trigger_data *hist_data = data->private_data;
|
||||
bool paused;
|
||||
|
||||
paused = data->paused;
|
||||
data->paused = true;
|
||||
if (data->name)
|
||||
pause_named_trigger(data);
|
||||
|
||||
synchronize_sched();
|
||||
|
||||
tracing_map_clear(hist_data->map);
|
||||
|
||||
data->paused = paused;
|
||||
if (data->name)
|
||||
unpause_named_trigger(data);
|
||||
}
|
||||
|
||||
static bool compatible_field(struct ftrace_event_field *field,
|
||||
struct ftrace_event_field *test_field)
|
||||
{
|
||||
if (field == test_field)
|
||||
return true;
|
||||
if (field == NULL || test_field == NULL)
|
||||
return false;
|
||||
if (strcmp(field->name, test_field->name) != 0)
|
||||
return false;
|
||||
if (strcmp(field->type, test_field->type) != 0)
|
||||
return false;
|
||||
if (field->size != test_field->size)
|
||||
return false;
|
||||
if (field->is_signed != test_field->is_signed)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool hist_trigger_match(struct event_trigger_data *data,
|
||||
struct event_trigger_data *data_test)
|
||||
struct event_trigger_data *data_test,
|
||||
struct event_trigger_data *named_data,
|
||||
bool ignore_filter)
|
||||
{
|
||||
struct tracing_map_sort_key *sort_key, *sort_key_test;
|
||||
struct hist_trigger_data *hist_data, *hist_data_test;
|
||||
struct hist_field *key_field, *key_field_test;
|
||||
unsigned int i;
|
||||
|
||||
if (named_data && (named_data != data_test) &&
|
||||
(named_data != data_test->named_data))
|
||||
return false;
|
||||
|
||||
if (!named_data && is_named_trigger(data_test))
|
||||
return false;
|
||||
|
||||
hist_data = data->private_data;
|
||||
hist_data_test = data_test->private_data;
|
||||
|
||||
@@ -1255,9 +1341,11 @@ static bool hist_trigger_match(struct event_trigger_data *data,
|
||||
hist_data->n_sort_keys != hist_data_test->n_sort_keys)
|
||||
return false;
|
||||
|
||||
if ((data->filter_str && !data_test->filter_str) ||
|
||||
(!data->filter_str && data_test->filter_str))
|
||||
return false;
|
||||
if (!ignore_filter) {
|
||||
if ((data->filter_str && !data_test->filter_str) ||
|
||||
(!data->filter_str && data_test->filter_str))
|
||||
return false;
|
||||
}
|
||||
|
||||
for_each_hist_field(i, hist_data) {
|
||||
key_field = hist_data->fields[i];
|
||||
@@ -1265,7 +1353,7 @@ static bool hist_trigger_match(struct event_trigger_data *data,
|
||||
|
||||
if (key_field->flags != key_field_test->flags)
|
||||
return false;
|
||||
if (key_field->field != key_field_test->field)
|
||||
if (!compatible_field(key_field->field, key_field_test->field))
|
||||
return false;
|
||||
if (key_field->offset != key_field_test->offset)
|
||||
return false;
|
||||
@@ -1280,7 +1368,7 @@ static bool hist_trigger_match(struct event_trigger_data *data,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (data->filter_str &&
|
||||
if (!ignore_filter && data->filter_str &&
|
||||
(strcmp(data->filter_str, data_test->filter_str) != 0))
|
||||
return false;
|
||||
|
||||
@@ -1292,12 +1380,26 @@ static int hist_register_trigger(char *glob, struct event_trigger_ops *ops,
|
||||
struct trace_event_file *file)
|
||||
{
|
||||
struct hist_trigger_data *hist_data = data->private_data;
|
||||
struct event_trigger_data *test;
|
||||
struct event_trigger_data *test, *named_data = NULL;
|
||||
int ret = 0;
|
||||
|
||||
if (hist_data->attrs->name) {
|
||||
named_data = find_named_trigger(hist_data->attrs->name);
|
||||
if (named_data) {
|
||||
if (!hist_trigger_match(data, named_data, named_data,
|
||||
true)) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hist_data->attrs->name && !named_data)
|
||||
goto new;
|
||||
|
||||
list_for_each_entry_rcu(test, &file->triggers, list) {
|
||||
if (test->cmd_ops->trigger_type == ETT_EVENT_HIST) {
|
||||
if (!hist_trigger_match(data, test))
|
||||
if (!hist_trigger_match(data, test, named_data, false))
|
||||
continue;
|
||||
if (hist_data->attrs->pause)
|
||||
test->paused = true;
|
||||
@@ -1310,12 +1412,19 @@ static int hist_register_trigger(char *glob, struct event_trigger_ops *ops,
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
new:
|
||||
if (hist_data->attrs->cont || hist_data->attrs->clear) {
|
||||
ret = -ENOENT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (named_data) {
|
||||
destroy_hist_data(data->private_data);
|
||||
data->private_data = named_data->private_data;
|
||||
set_named_trigger_data(data, named_data);
|
||||
data->ops = &event_hist_trigger_named_ops;
|
||||
}
|
||||
|
||||
if (hist_data->attrs->pause)
|
||||
data->paused = true;
|
||||
|
||||
@@ -1329,6 +1438,7 @@ static int hist_register_trigger(char *glob, struct event_trigger_ops *ops,
|
||||
ret++;
|
||||
|
||||
update_cond_flag(file);
|
||||
|
||||
if (trace_event_trigger_enable_disable(file, 1) < 0) {
|
||||
list_del_rcu(&data->list);
|
||||
update_cond_flag(file);
|
||||
@@ -1342,12 +1452,16 @@ static void hist_unregister_trigger(char *glob, struct event_trigger_ops *ops,
|
||||
struct event_trigger_data *data,
|
||||
struct trace_event_file *file)
|
||||
{
|
||||
struct event_trigger_data *test;
|
||||
struct hist_trigger_data *hist_data = data->private_data;
|
||||
struct event_trigger_data *test, *named_data = NULL;
|
||||
bool unregistered = false;
|
||||
|
||||
if (hist_data->attrs->name)
|
||||
named_data = find_named_trigger(hist_data->attrs->name);
|
||||
|
||||
list_for_each_entry_rcu(test, &file->triggers, list) {
|
||||
if (test->cmd_ops->trigger_type == ETT_EVENT_HIST) {
|
||||
if (!hist_trigger_match(data, test))
|
||||
if (!hist_trigger_match(data, test, named_data, false))
|
||||
continue;
|
||||
unregistered = true;
|
||||
list_del_rcu(&test->list);
|
||||
|
Reference in New Issue
Block a user