| From 3a7e6b8cbda85b5dce88051f41737f5e76861079 Mon Sep 17 00:00:00 2001 |
| From: Tom Zanussi <tom.zanussi@linux.intel.com> |
| Date: Mon, 15 Jan 2018 20:52:00 -0600 |
| Subject: [PATCH 1773/1795] tracing: Add 'onmatch' hist trigger action support |
| |
| Add an 'onmatch(matching.event).<synthetic_event_name>(param list)' |
| hist trigger action which is invoked with the set of variables or |
| event fields named in the 'param list'. The result is the generation |
| of a synthetic event that consists of the values contained in those |
| variables and/or fields 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_t pid; int prio' >> \ |
| /sys/kernel/debug/tracing/synthetic_events |
| |
| # cat /sys/kernel/debug/tracing/synthetic_events |
| wakeup_new_test pid_t pid; int prio |
| |
| The following hist trigger both defines a testpid variable and |
| specifies an onmatch() trace action that uses that variable along with |
| a non-variable field to generate 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:testpid=pid:keys=$testpid:\ |
| onmatch(sched.sched_wakeup_new).wakeup_new_test($testpid, prio) \ |
| 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,prio:sort=pid,prio' >> \ |
| /sys/kernel/debug/tracing/events/synthetic/wakeup_new_test/trigger |
| |
| Link: http://lkml.kernel.org/r/8c2a574bcb7530c876629c901ecd23911b14afe8.1516069914.git.tom.zanussi@linux.intel.com |
| |
| Signed-off-by: Tom Zanussi <tom.zanussi@linux.intel.com> |
| Signed-off-by: Rajvi Jingar <rajvi.jingar@intel.com> |
| Signed-off-by: Steven Rostedt (VMware) <rostedt@goodmis.org> |
| (cherry picked from commit c282a386a39771588fe4cfdc01bbb8a255092e38) |
| Signed-off-by: Hirotaka MOTAI <Motai.Hirotaka@aj.MitsubishiElectric.co.jp> |
| Signed-off-by: Geert Uytterhoeven <geert+renesas@glider.be> |
| --- |
| kernel/trace/trace_events_hist.c | 488 ++++++++++++++++++++++++++++++- |
| 1 file changed, 475 insertions(+), 13 deletions(-) |
| |
| diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c |
| index ad96fd110707..9ac6089b7513 100644 |
| --- a/kernel/trace/trace_events_hist.c |
| +++ b/kernel/trace/trace_events_hist.c |
| @@ -285,6 +285,8 @@ struct hist_trigger_data { |
| struct action_data *actions[HIST_ACTIONS_MAX]; |
| unsigned int n_actions; |
| |
| + struct hist_field *synth_var_refs[SYNTH_FIELDS_MAX]; |
| + unsigned int n_synth_var_refs; |
| struct field_var *field_vars[SYNTH_FIELDS_MAX]; |
| unsigned int n_field_vars; |
| unsigned int n_field_var_str; |
| @@ -321,7 +323,18 @@ typedef void (*action_fn_t) (struct hist_trigger_data *hist_data, |
| |
| struct action_data { |
| action_fn_t fn; |
| - unsigned int var_ref_idx; |
| + unsigned int n_params; |
| + char *params[SYNTH_FIELDS_MAX]; |
| + |
| + union { |
| + struct { |
| + unsigned int var_ref_idx; |
| + char *match_event; |
| + char *match_event_system; |
| + char *synth_event_name; |
| + struct synth_event *synth_event; |
| + } onmatch; |
| + }; |
| }; |
| |
| static LIST_HEAD(synth_event_list); |
| @@ -887,6 +900,21 @@ static struct synth_event *alloc_synth_event(char *event_name, int n_fields, |
| return event; |
| } |
| |
| +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 synth_event *event = data->onmatch.synth_event; |
| + |
| + trace_synth(event, var_ref_vals, data->onmatch.var_ref_idx); |
| +} |
| + |
| +struct hist_var_data { |
| + struct list_head list; |
| + struct hist_trigger_data *hist_data; |
| +}; |
| + |
| static void add_or_delete_synth_event(struct synth_event *event, int delete) |
| { |
| if (delete) |
| @@ -1124,11 +1152,6 @@ static u64 hist_field_timestamp(struct hist_field *hist_field, |
| return ts; |
| } |
| |
| -struct hist_var_data { |
| - struct list_head list; |
| - struct hist_trigger_data *hist_data; |
| -}; |
| - |
| static struct hist_field * |
| check_field_for_var_ref(struct hist_field *hist_field, |
| struct hist_trigger_data *var_data, |
| @@ -1194,6 +1217,14 @@ static struct hist_field *find_var_ref(struct hist_trigger_data *hist_data, |
| return found; |
| } |
| |
| + for (i = 0; i < hist_data->n_synth_var_refs; i++) { |
| + hist_field = hist_data->synth_var_refs[i]; |
| + found = check_field_for_var_refs(hist_data, hist_field, |
| + var_data, var_idx, 0); |
| + if (found) |
| + return found; |
| + } |
| + |
| return found; |
| } |
| |
| @@ -1422,6 +1453,37 @@ static struct hist_field *find_file_var(struct trace_event_file *file, |
| return NULL; |
| } |
| |
| +static struct hist_field * |
| +find_match_var(struct hist_trigger_data *hist_data, char *var_name) |
| +{ |
| + struct trace_array *tr = hist_data->event_file->tr; |
| + struct hist_field *hist_field, *found = NULL; |
| + struct trace_event_file *file; |
| + 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) { |
| + char *system = data->onmatch.match_event_system; |
| + char *event_name = data->onmatch.match_event; |
| + |
| + file = find_var_file(tr, system, event_name, var_name); |
| + if (!file) |
| + continue; |
| + hist_field = find_file_var(file, var_name); |
| + if (hist_field) { |
| + if (found) { |
| + return ERR_PTR(-EINVAL); |
| + } |
| + |
| + found = hist_field; |
| + } |
| + } |
| + } |
| + return found; |
| +} |
| + |
| static struct hist_field *find_event_var(struct hist_trigger_data *hist_data, |
| char *system, |
| char *event_name, |
| @@ -1431,6 +1493,14 @@ static struct hist_field *find_event_var(struct hist_trigger_data *hist_data, |
| struct hist_field *hist_field = NULL; |
| struct trace_event_file *file; |
| |
| + if (!system || !event_name) { |
| + hist_field = find_match_var(hist_data, var_name); |
| + if (IS_ERR(hist_field)) |
| + return NULL; |
| + if (hist_field) |
| + return hist_field; |
| + } |
| + |
| file = find_var_file(tr, system, event_name, var_name); |
| if (!file) |
| return NULL; |
| @@ -1622,11 +1692,21 @@ static void destroy_hist_trigger_attrs(struct hist_trigger_attrs *attrs) |
| |
| static int parse_action(char *str, struct hist_trigger_attrs *attrs) |
| { |
| - int ret = 0; |
| + int ret = -EINVAL; |
| |
| if (attrs->n_actions >= HIST_ACTIONS_MAX) |
| return ret; |
| |
| + 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 = 0; |
| + } |
| + |
| return ret; |
| } |
| |
| @@ -2635,7 +2715,7 @@ find_synthetic_field_var(struct hist_trigger_data *target_hist_data, |
| * |
| * Return: The variable created for the field. |
| */ |
| -struct hist_field * |
| +static struct hist_field * |
| create_field_var_hist(struct hist_trigger_data *target_hist_data, |
| char *subsys_name, char *event_name, char *field_name) |
| { |
| @@ -2748,7 +2828,7 @@ create_field_var_hist(struct hist_trigger_data *target_hist_data, |
| return event_var; |
| } |
| |
| -struct hist_field * |
| +static struct hist_field * |
| find_target_event_var(struct hist_trigger_data *hist_data, |
| char *subsys_name, char *event_name, char *var_name) |
| { |
| @@ -2919,7 +2999,7 @@ static struct field_var *create_field_var(struct hist_trigger_data *hist_data, |
| * |
| * Return: The variable created for the field. |
| */ |
| -struct field_var * |
| +static struct field_var * |
| create_target_field_var(struct hist_trigger_data *target_hist_data, |
| char *subsys_name, char *event_name, char *var_name) |
| { |
| @@ -2943,6 +3023,27 @@ create_target_field_var(struct hist_trigger_data *target_hist_data, |
| return create_field_var(target_hist_data, file, var_name); |
| } |
| |
| +static void onmatch_destroy(struct action_data *data) |
| +{ |
| + unsigned int i; |
| + |
| + mutex_lock(&synth_event_mutex); |
| + |
| + kfree(data->onmatch.match_event); |
| + kfree(data->onmatch.match_event_system); |
| + kfree(data->onmatch.synth_event_name); |
| + |
| + for (i = 0; i < data->n_params; i++) |
| + kfree(data->params[i]); |
| + |
| + if (data->onmatch.synth_event) |
| + data->onmatch.synth_event->ref--; |
| + |
| + kfree(data); |
| + |
| + mutex_unlock(&synth_event_mutex); |
| +} |
| + |
| static void destroy_field_var(struct field_var *field_var) |
| { |
| if (!field_var) |
| @@ -2962,8 +3063,8 @@ static void destroy_field_vars(struct hist_trigger_data *hist_data) |
| destroy_field_var(hist_data->field_vars[i]); |
| } |
| |
| -void save_field_var(struct hist_trigger_data *hist_data, |
| - struct field_var *field_var) |
| +static void save_field_var(struct hist_trigger_data *hist_data, |
| + struct field_var *field_var) |
| { |
| hist_data->field_vars[hist_data->n_field_vars++] = field_var; |
| |
| @@ -2971,6 +3072,304 @@ void save_field_var(struct hist_trigger_data *hist_data, |
| hist_data->n_field_var_str++; |
| } |
| |
| + |
| +static void destroy_synth_var_refs(struct hist_trigger_data *hist_data) |
| +{ |
| + unsigned int i; |
| + |
| + for (i = 0; i < hist_data->n_synth_var_refs; i++) |
| + destroy_hist_field(hist_data->synth_var_refs[i], 0); |
| +} |
| + |
| +static void save_synth_var_ref(struct hist_trigger_data *hist_data, |
| + struct hist_field *var_ref) |
| +{ |
| + hist_data->synth_var_refs[hist_data->n_synth_var_refs++] = var_ref; |
| + |
| + hist_data->var_refs[hist_data->n_var_refs] = var_ref; |
| + var_ref->var_ref_idx = hist_data->n_var_refs++; |
| +} |
| + |
| +static int check_synth_field(struct synth_event *event, |
| + struct hist_field *hist_field, |
| + unsigned int field_pos) |
| +{ |
| + struct synth_field *field; |
| + |
| + if (field_pos >= event->n_fields) |
| + return -EINVAL; |
| + |
| + field = event->fields[field_pos]; |
| + |
| + if (strcmp(field->type, hist_field->type) != 0) |
| + return -EINVAL; |
| + |
| + return 0; |
| +} |
| + |
| +static int parse_action_params(char *params, struct action_data *data) |
| +{ |
| + char *param, *saved_param; |
| + int ret = 0; |
| + |
| + while (params) { |
| + if (data->n_params >= SYNTH_FIELDS_MAX) |
| + goto out; |
| + |
| + param = strsep(¶ms, ","); |
| + if (!param) { |
| + ret = -EINVAL; |
| + goto out; |
| + } |
| + |
| + param = strstrip(param); |
| + if (strlen(param) < 2) { |
| + ret = -EINVAL; |
| + goto out; |
| + } |
| + |
| + saved_param = kstrdup(param, GFP_KERNEL); |
| + if (!saved_param) { |
| + ret = -ENOMEM; |
| + goto out; |
| + } |
| + |
| + data->params[data->n_params++] = saved_param; |
| + } |
| + out: |
| + return ret; |
| +} |
| + |
| +static struct hist_field * |
| +onmatch_find_var(struct hist_trigger_data *hist_data, struct action_data *data, |
| + char *system, char *event, char *var) |
| +{ |
| + struct hist_field *hist_field; |
| + |
| + var++; /* skip '$' */ |
| + |
| + hist_field = find_target_event_var(hist_data, system, event, var); |
| + if (!hist_field) { |
| + if (!system) { |
| + system = data->onmatch.match_event_system; |
| + event = data->onmatch.match_event; |
| + } |
| + |
| + hist_field = find_event_var(hist_data, system, event, var); |
| + } |
| + |
| + return hist_field; |
| +} |
| + |
| +static struct hist_field * |
| +onmatch_create_field_var(struct hist_trigger_data *hist_data, |
| + struct action_data *data, char *system, |
| + char *event, char *var) |
| +{ |
| + struct hist_field *hist_field = NULL; |
| + struct field_var *field_var; |
| + |
| + /* |
| + * First try to create a field var on the target event (the |
| + * currently being defined). This will create a variable for |
| + * unqualified fields on the target event, or if qualified, |
| + * target fields that have qualified names matching the target. |
| + */ |
| + field_var = create_target_field_var(hist_data, system, event, var); |
| + |
| + if (field_var && !IS_ERR(field_var)) { |
| + save_field_var(hist_data, field_var); |
| + hist_field = field_var->var; |
| + } else { |
| + field_var = NULL; |
| + /* |
| + * If no explicit system.event is specfied, default to |
| + * looking for fields on the onmatch(system.event.xxx) |
| + * event. |
| + */ |
| + if (!system) { |
| + system = data->onmatch.match_event_system; |
| + event = data->onmatch.match_event; |
| + } |
| + |
| + /* |
| + * At this point, we're looking at a field on another |
| + * event. Because we can't modify a hist trigger on |
| + * another event to add a variable for a field, we need |
| + * to create a new trigger on that event and create the |
| + * variable at the same time. |
| + */ |
| + hist_field = create_field_var_hist(hist_data, system, event, var); |
| + if (IS_ERR(hist_field)) |
| + goto free; |
| + } |
| + out: |
| + return hist_field; |
| + free: |
| + destroy_field_var(field_var); |
| + hist_field = NULL; |
| + goto out; |
| +} |
| + |
| +static int onmatch_create(struct hist_trigger_data *hist_data, |
| + struct trace_event_file *file, |
| + struct action_data *data) |
| +{ |
| + char *event_name, *param, *system = NULL; |
| + struct hist_field *hist_field, *var_ref; |
| + unsigned int i, var_ref_idx; |
| + unsigned int field_pos = 0; |
| + struct synth_event *event; |
| + int ret = 0; |
| + |
| + mutex_lock(&synth_event_mutex); |
| + event = find_synth_event(data->onmatch.synth_event_name); |
| + if (!event) { |
| + mutex_unlock(&synth_event_mutex); |
| + return -EINVAL; |
| + } |
| + event->ref++; |
| + mutex_unlock(&synth_event_mutex); |
| + |
| + var_ref_idx = hist_data->n_var_refs; |
| + |
| + for (i = 0; i < data->n_params; i++) { |
| + char *p; |
| + |
| + p = param = kstrdup(data->params[i], GFP_KERNEL); |
| + if (!param) { |
| + ret = -ENOMEM; |
| + goto err; |
| + } |
| + |
| + system = strsep(¶m, "."); |
| + if (!param) { |
| + param = (char *)system; |
| + system = event_name = NULL; |
| + } else { |
| + event_name = strsep(¶m, "."); |
| + if (!param) { |
| + kfree(p); |
| + ret = -EINVAL; |
| + goto err; |
| + } |
| + } |
| + |
| + if (param[0] == '$') |
| + hist_field = onmatch_find_var(hist_data, data, system, |
| + event_name, param); |
| + else |
| + hist_field = onmatch_create_field_var(hist_data, data, |
| + system, |
| + event_name, |
| + param); |
| + |
| + if (!hist_field) { |
| + kfree(p); |
| + ret = -EINVAL; |
| + goto err; |
| + } |
| + |
| + if (check_synth_field(event, hist_field, field_pos) == 0) { |
| + var_ref = create_var_ref(hist_field, system, event_name); |
| + if (!var_ref) { |
| + kfree(p); |
| + ret = -ENOMEM; |
| + goto err; |
| + } |
| + |
| + save_synth_var_ref(hist_data, var_ref); |
| + field_pos++; |
| + kfree(p); |
| + continue; |
| + } |
| + |
| + kfree(p); |
| + ret = -EINVAL; |
| + goto err; |
| + } |
| + |
| + if (field_pos != event->n_fields) { |
| + ret = -EINVAL; |
| + goto err; |
| + } |
| + |
| + data->fn = action_trace; |
| + data->onmatch.synth_event = event; |
| + data->onmatch.var_ref_idx = var_ref_idx; |
| + out: |
| + return ret; |
| + err: |
| + mutex_lock(&synth_event_mutex); |
| + event->ref--; |
| + mutex_unlock(&synth_event_mutex); |
| + |
| + goto out; |
| +} |
| + |
| +static struct action_data *onmatch_parse(struct trace_array *tr, char *str) |
| +{ |
| + char *match_event, *match_event_system; |
| + char *synth_event_name, *params; |
| + struct action_data *data; |
| + int ret = -EINVAL; |
| + |
| + data = kzalloc(sizeof(*data), GFP_KERNEL); |
| + if (!data) |
| + return ERR_PTR(-ENOMEM); |
| + |
| + match_event = strsep(&str, ")"); |
| + if (!match_event || !str) |
| + goto free; |
| + |
| + match_event_system = strsep(&match_event, "."); |
| + if (!match_event) |
| + goto free; |
| + |
| + if (IS_ERR(event_file(tr, match_event_system, match_event))) |
| + goto free; |
| + |
| + data->onmatch.match_event = kstrdup(match_event, GFP_KERNEL); |
| + if (!data->onmatch.match_event) { |
| + ret = -ENOMEM; |
| + goto free; |
| + } |
| + |
| + data->onmatch.match_event_system = kstrdup(match_event_system, GFP_KERNEL); |
| + if (!data->onmatch.match_event_system) { |
| + ret = -ENOMEM; |
| + goto free; |
| + } |
| + |
| + strsep(&str, "."); |
| + if (!str) |
| + goto free; |
| + |
| + synth_event_name = strsep(&str, "("); |
| + if (!synth_event_name || !str) |
| + goto free; |
| + |
| + data->onmatch.synth_event_name = kstrdup(synth_event_name, GFP_KERNEL); |
| + if (!data->onmatch.synth_event_name) { |
| + ret = -ENOMEM; |
| + goto free; |
| + } |
| + |
| + params = strsep(&str, ")"); |
| + if (!params || !str || (str && strlen(str))) |
| + goto free; |
| + |
| + ret = parse_action_params(params, data); |
| + if (ret) |
| + goto free; |
| + out: |
| + return data; |
| + free: |
| + onmatch_destroy(data); |
| + data = ERR_PTR(ret); |
| + goto out; |
| +} |
| + |
| static int create_hitcount_val(struct hist_trigger_data *hist_data) |
| { |
| hist_data->fields[HITCOUNT_IDX] = |
| @@ -3395,18 +3794,39 @@ static void destroy_actions(struct hist_trigger_data *hist_data) |
| for (i = 0; i < hist_data->n_actions; i++) { |
| struct action_data *data = hist_data->actions[i]; |
| |
| - kfree(data); |
| + if (data->fn == action_trace) |
| + onmatch_destroy(data); |
| + else |
| + kfree(data); |
| } |
| } |
| |
| static int parse_actions(struct hist_trigger_data *hist_data) |
| { |
| + struct trace_array *tr = hist_data->event_file->tr; |
| + struct action_data *data; |
| unsigned int i; |
| int ret = 0; |
| char *str; |
| |
| 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("); |
| + |
| + data = onmatch_parse(tr, action_str); |
| + if (IS_ERR(data)) { |
| + ret = PTR_ERR(data); |
| + break; |
| + } |
| + data->fn = action_trace; |
| + } else { |
| + ret = -EINVAL; |
| + break; |
| + } |
| + |
| + hist_data->actions[hist_data->n_actions++] = data; |
| } |
| |
| return ret; |
| @@ -3421,11 +3841,50 @@ static int create_actions(struct hist_trigger_data *hist_data, |
| |
| for (i = 0; i < hist_data->attrs->n_actions; i++) { |
| data = hist_data->actions[i]; |
| + |
| + if (data->fn == action_trace) { |
| + ret = onmatch_create(hist_data, file, data); |
| + if (ret) |
| + return ret; |
| + } |
| } |
| |
| return ret; |
| } |
| |
| +static void print_onmatch_spec(struct seq_file *m, |
| + struct hist_trigger_data *hist_data, |
| + struct action_data *data) |
| +{ |
| + unsigned int i; |
| + |
| + seq_printf(m, ":onmatch(%s.%s).", data->onmatch.match_event_system, |
| + data->onmatch.match_event); |
| + |
| + seq_printf(m, "%s(", data->onmatch.synth_event->name); |
| + |
| + for (i = 0; i < data->n_params; i++) { |
| + if (i) |
| + seq_puts(m, ","); |
| + seq_printf(m, "%s", data->params[i]); |
| + } |
| + |
| + seq_puts(m, ")"); |
| +} |
| + |
| +static void print_actions_spec(struct seq_file *m, |
| + struct hist_trigger_data *hist_data) |
| +{ |
| + 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) |
| + print_onmatch_spec(m, hist_data, data); |
| + } |
| +} |
| + |
| static void destroy_field_var_hists(struct hist_trigger_data *hist_data) |
| { |
| unsigned int i; |
| @@ -3448,6 +3907,7 @@ static void destroy_hist_data(struct hist_trigger_data *hist_data) |
| destroy_actions(hist_data); |
| destroy_field_vars(hist_data); |
| destroy_field_var_hists(hist_data); |
| + destroy_synth_var_refs(hist_data); |
| |
| kfree(hist_data); |
| } |
| @@ -4004,6 +4464,8 @@ static int event_hist_trigger_print(struct seq_file *m, |
| } |
| seq_printf(m, ":size=%u", (1 << hist_data->map->map_bits)); |
| |
| + print_actions_spec(m, hist_data); |
| + |
| if (data->filter_str) |
| seq_printf(m, " if %s", data->filter_str); |
| |
| -- |
| 2.19.0 |
| |