[RFC][PATCH 19/21] tracing: Add 'onmatch' hist trigger action support

From: Tom Zanussi
Date: Wed Feb 08 2017 - 12:31:34 EST


Add an 'onmatch().trace(event)' hist trigger action which is invoked
with the set of resolved variables named in the given synthetic event.
The result is the generation of a synthetic event that consists of the
values contained in those variables at the time the invoking event was
hit.

As an example the below defines a simple synthetic event using a
variable defined on the sched_wakeup_new event, and shows the event
definition with unresolved fields, since the sched_wakeup_new event
with the testpid variable hasn't been defined yet:

# echo 'wakeup_new_test pid=sched_wakeup_new:testpid' >> \
/sys/kernel/debug/tracing/synthetic_events

# cat /sys/kernel/debug/tracing/synthetic_events
wakeup_new_test pid=sched_wakeup_new:testpid*

The following hist trigger both defines the missing testpid variable
and specifies an onmatch().trace action that generates a
wakeup_new_test synthetic event whenever a sched_wakeup_new event
occurs, which because of the 'if comm == "cyclictest"' filter only
happens when the executable is cyclictest:

# echo 'hist:keys=testpid=pid:onmatch().trace(wakeup_new_test) \
if comm=="cyclictest"' >> \
/sys/kernel/debug/tracing/events/sched/sched_wakeup_new/trigger

Creating and displaying a histogram based on those events is now just
a matter of using the fields and new synthetic event in the
tracing/events/synthetic directory, as usual:

# echo 'hist:keys=pid:sort=pid' >> \
/sys/kernel/debug/tracing/events/synthetic/wakeup_new_test/trigger

Signed-off-by: Tom Zanussi <tom.zanussi@xxxxxxxxxxxxxxx>
---
kernel/trace/trace_events_hist.c | 167 +++++++++++++++++++++++++++++++++++++++
1 file changed, 167 insertions(+)

diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c
index 46da09f..2f9efb8 100644
--- a/kernel/trace/trace_events_hist.c
+++ b/kernel/trace/trace_events_hist.c
@@ -279,6 +279,7 @@ static u64 hist_field_timestamp(struct hist_field *hist_field,
}

static LIST_HEAD(hist_var_list);
+static LIST_HEAD(hist_action_list);

struct hist_var_data {
struct list_head list;
@@ -610,6 +611,16 @@ static int parse_action(char *str, struct hist_trigger_attrs *attrs)
if (attrs->n_actions == HIST_ACTIONS_MAX)
return -EINVAL;

+ if ((strncmp(str, "onmatch(", strlen("onmatch(")) == 0)) {
+ attrs->action_str[attrs->n_actions] = kstrdup(str, GFP_KERNEL);
+ if (!attrs->action_str[attrs->n_actions]) {
+ ret = -ENOMEM;
+ return ret;
+ }
+ attrs->n_actions++;
+ ret = 1;
+ }
+
return ret;
}

@@ -1823,6 +1834,129 @@ static int add_synthetic_var_refs(struct hist_trigger_data *hist_data,
return var_ref_idx;
}

+static void action_trace(struct hist_trigger_data *hist_data,
+ struct tracing_map_elt *elt, void *rec,
+ struct ring_buffer_event *rbe,
+ struct action_data *data, u64 *var_ref_vals)
+{
+ struct synthetic_event *event = data->synthetic_event;
+
+ trace_synthetic(event, var_ref_vals, data->var_ref_idx);
+}
+
+static bool check_hist_action_refs(struct hist_trigger_data *hist_data,
+ struct synthetic_event *event)
+{
+ unsigned int i;
+
+ for (i = 0; i < hist_data->n_actions; i++) {
+ struct action_data *data = hist_data->actions[i];
+
+ if (data->fn == action_trace && data->synthetic_event == event)
+ return true;
+ }
+
+ return false;
+}
+
+static bool check_synthetic_action_refs(struct synthetic_event *event)
+{
+ struct hist_var_data *var_data;
+
+ list_for_each_entry(var_data, &hist_action_list, list)
+ if (check_hist_action_refs(var_data->hist_data, event))
+ return true;
+
+ return false;
+}
+
+static struct hist_var_data *find_hist_actions(struct hist_trigger_data *hist_data)
+{
+ struct hist_var_data *var_data, *found = NULL;
+
+ list_for_each_entry(var_data, &hist_action_list, list) {
+ if (var_data->hist_data == hist_data) {
+ found = var_data;
+ break;
+ }
+ }
+
+ return found;
+}
+
+static int save_hist_actions(struct hist_trigger_data *hist_data)
+{
+ struct hist_var_data *var_data;
+
+ var_data = find_hist_actions(hist_data);
+ if (var_data)
+ return 0;
+
+ var_data = kzalloc(sizeof(*var_data), GFP_KERNEL);
+ if (!var_data)
+ return -ENOMEM;
+
+ var_data->hist_data = hist_data;
+ list_add(&var_data->list, &hist_action_list);
+
+ return 0;
+}
+
+static int remove_hist_actions(struct hist_trigger_data *hist_data)
+{
+ struct hist_var_data *var_data;
+
+ var_data = find_hist_actions(hist_data);
+ if (!var_data)
+ return -EINVAL;
+
+ list_del(&var_data->list);
+
+ return 0;
+}
+
+static int create_onmatch_data(char *str, struct hist_trigger_data *hist_data)
+{
+ char *fn_name, *param;
+ struct action_data *data;
+ int ret = 0;
+
+ strsep(&str, ".");
+ if (!str)
+ return -EINVAL;
+
+ fn_name = strsep(&str, "(");
+ if (!fn_name || !str)
+ return -EINVAL;
+
+ if (strncmp(fn_name, "trace", strlen("trace")) == 0) {
+ struct synthetic_event *event;
+
+ param = strsep(&str, ")");
+ if (!param)
+ return -EINVAL;
+
+ event = find_synthetic_event(param);
+ if (!event)
+ return -EINVAL;
+
+ if (!resolve_pending_var_refs(event))
+ return -EINVAL;
+
+ data = kzalloc(sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->fn = action_trace;
+ data->synthetic_event = event;
+ data->var_ref_idx = add_synthetic_var_refs(hist_data, event);
+ hist_data->actions[hist_data->n_actions++] = data;
+ save_hist_actions(hist_data);
+ }
+
+ return ret;
+}
+
static void destroy_actions(struct hist_trigger_data *hist_data)
{
unsigned int i;
@@ -1842,6 +1976,14 @@ static int create_actions(struct hist_trigger_data *hist_data)

for (i = 0; i < hist_data->attrs->n_actions; i++) {
str = hist_data->attrs->action_str[i];
+
+ if (strncmp(str, "onmatch(", strlen("onmatch(")) == 0) {
+ char *action_str = str + strlen("onmatch(");
+
+ ret = create_onmatch_data(action_str, hist_data);
+ if (ret)
+ return ret;
+ }
}

return ret;
@@ -1858,6 +2000,16 @@ static void print_actions(struct seq_file *m,
}
}

+static void print_onmatch_spec(struct seq_file *m,
+ struct hist_trigger_data *hist_data,
+ struct action_data *data)
+{
+ seq_puts(m, ":onmatch().");
+
+ if (data->synthetic_event)
+ seq_printf(m, "trace(%s)", data->synthetic_event->name);
+}
+
static void print_actions_spec(struct seq_file *m,
struct hist_trigger_data *hist_data)
{
@@ -1865,6 +2017,9 @@ static void print_actions_spec(struct seq_file *m,

for (i = 0; i < hist_data->n_actions; i++) {
struct action_data *data = hist_data->actions[i];
+
+ if (data->fn == action_trace)
+ print_onmatch_spec(m, hist_data, data);
}
}

@@ -2428,6 +2583,9 @@ static void event_hist_trigger_free(struct event_trigger_ops *ops,
if (remove_hist_vars(hist_data))
return;

+ if (remove_hist_actions(hist_data))
+ return;
+
destroy_hist_data(hist_data);
}
}
@@ -3417,6 +3575,10 @@ static int create_synthetic_event(int argc, char **argv)
event = find_synthetic_event(token);
if (event) {
if (delete_event) {
+ if (check_synthetic_action_refs(event)) {
+ ret = -EINVAL;
+ goto out;
+ }
remove_synthetic_event(event);
goto err;
} else
@@ -3462,6 +3624,11 @@ static int release_all_synthetic_events(void)

mutex_lock(&synthetic_event_mutex);

+ list_for_each_entry(event, &synthetic_events_list, list) {
+ if (check_synthetic_action_refs(event))
+ return -EINVAL;
+ }
+
list_for_each_entry_safe(event, e, &synthetic_events_list, list) {
remove_synthetic_event(event);
free_synthetic_event(event);
--
1.9.3