summaryrefslogtreecommitdiffstats
path: root/kernel/trace
diff options
context:
space:
mode:
authorTom Zanussi <tom.zanussi@linux.intel.com>2016-03-03 12:54:59 -0600
committerSteven Rostedt <rostedt@goodmis.org>2016-04-19 18:56:01 -0400
commit5463bfda327b1f7310556ef3136533e27c774f13 (patch)
tree2221e87e917ba0652329ef47c1f40c024b9e5697 /kernel/trace
parentdb1388b4ffa9e31e9ff0abacc3bdb121bec8c688 (diff)
downloadlinux-stable-5463bfda327b1f7310556ef3136533e27c774f13.tar.gz
linux-stable-5463bfda327b1f7310556ef3136533e27c774f13.tar.bz2
linux-stable-5463bfda327b1f7310556ef3136533e27c774f13.zip
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>
Diffstat (limited to 'kernel/trace')
-rw-r--r--kernel/trace/trace.c14
-rw-r--r--kernel/trace/trace_events_hist.c148
2 files changed, 141 insertions, 21 deletions
diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c
index e89b2ed76d2d..4e342d354c12 100644
--- a/kernel/trace/trace.c
+++ b/kernel/trace/trace.c
@@ -3842,6 +3842,7 @@ static const char readme_msg[] =
"\t [:sort=<field1[,field2,...]>]\n"
"\t [:size=#entries]\n"
"\t [:pause][:continue][:clear]\n"
+ "\t [:name=histname1]\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(s) and value(s) named, and the value of a\n"
@@ -3854,13 +3855,18 @@ static const char readme_msg[] =
"\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 or fewer than the default 2048 entries for the hashtable size.\n"
+ "\t If a hist trigger is given a name using the 'name' parameter,\n"
+ "\t its histogram data will be shared with other triggers of the\n"
+ "\t same name, and trigger hits will update this common data.\n\n"
"\t Reading the 'hist' file for the event will dump the hash\n"
"\t table in its entirety to stdout. If there are multiple hist\n"
"\t triggers attached to an event, there will be a table for each\n"
- "\t trigger in the output. The default format used to display a\n"
- "\t given field can be modified by appending any of the following\n"
- "\t modifiers to the field name, as applicable:\n\n"
+ "\t trigger in the output. The table displayed for a named\n"
+ "\t trigger will be the same as any other instance having the\n"
+ "\t same name. The default format used to display a given field\n"
+ "\t can be modified by appending any of the following modifiers\n"
+ "\t to the field name, as applicable:\n\n"
"\t .hex display a number as a hex value\n"
"\t .sym display an address as a symbol\n"
"\t .sym-offset display an address as a symbol and offset\n"
diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c
index 4b02f8ab4dd3..bdea5603cbde 100644
--- a/kernel/trace/trace_events_hist.c
+++ b/kernel/trace/trace_events_hist.c
@@ -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);