| From: Tom Zanussi <tom.zanussi@linux.intel.com> |
| Date: Mon, 15 Jan 2018 20:51:56 -0600 |
| Subject: [PATCH 30/48] tracing: Add variable reference handling to hist |
| triggers |
| |
| Add the necessary infrastructure to allow the variables defined on one |
| event to be referenced in another. This allows variables set by a |
| previous event to be referenced and used in expressions combining the |
| variable values saved by that previous event and the event fields of |
| the current event. For example, here's how a latency can be |
| calculated and saved into yet another variable named 'wakeup_lat': |
| |
| # echo 'hist:keys=pid,prio:ts0=common_timestamp ... |
| # echo 'hist:keys=next_pid:wakeup_lat=common_timestamp-$ts0 ... |
| |
| In the first event, the event's timetamp is saved into the variable |
| ts0. In the next line, ts0 is subtracted from the second event's |
| timestamp to produce the latency. |
| |
| Further users of variable references will be described in subsequent |
| patches, such as for instance how the 'wakeup_lat' variable above can |
| be displayed in a latency histogram. |
| |
| Link: http://lkml.kernel.org/r/b1d3e6975374e34d501ff417c20189c3f9b2c7b8.1516069914.git.tom.zanussi@linux.intel.com |
| |
| Signed-off-by: Tom Zanussi <tom.zanussi@linux.intel.com> |
| Signed-off-by: Steven Rostedt (VMware) <rostedt@goodmis.org> |
| (cherry picked from commit 434c1d5831194e72e6eb30d46534d75b5a985eb7) |
| Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de> |
| --- |
| kernel/trace/trace.c | 2 |
| kernel/trace/trace.h | 3 |
| kernel/trace/trace_events_hist.c | 661 +++++++++++++++++++++++++++++++++++- |
| kernel/trace/trace_events_trigger.c | 6 |
| 4 files changed, 656 insertions(+), 16 deletions(-) |
| |
| --- a/kernel/trace/trace.c |
| +++ b/kernel/trace/trace.c |
| @@ -7788,6 +7788,7 @@ static int instance_mkdir(const char *na |
| |
| INIT_LIST_HEAD(&tr->systems); |
| INIT_LIST_HEAD(&tr->events); |
| + INIT_LIST_HEAD(&tr->hist_vars); |
| |
| if (allocate_trace_buffers(tr, trace_buf_size) < 0) |
| goto out_free_tr; |
| @@ -8538,6 +8539,7 @@ ssize_t trace_parse_run_command(struct f |
| |
| INIT_LIST_HEAD(&global_trace.systems); |
| INIT_LIST_HEAD(&global_trace.events); |
| + INIT_LIST_HEAD(&global_trace.hist_vars); |
| list_add(&global_trace.list, &ftrace_trace_arrays); |
| |
| apply_trace_boot_options(); |
| --- a/kernel/trace/trace.h |
| +++ b/kernel/trace/trace.h |
| @@ -274,6 +274,7 @@ struct trace_array { |
| int function_enabled; |
| #endif |
| int time_stamp_abs_ref; |
| + struct list_head hist_vars; |
| }; |
| |
| enum { |
| @@ -1550,6 +1551,8 @@ extern void pause_named_trigger(struct e |
| extern void unpause_named_trigger(struct event_trigger_data *data); |
| extern void set_named_trigger_data(struct event_trigger_data *data, |
| struct event_trigger_data *named_data); |
| +extern struct event_trigger_data * |
| +get_named_trigger_data(struct event_trigger_data *data); |
| extern int register_event_command(struct event_command *cmd); |
| extern int unregister_event_command(struct event_command *cmd); |
| extern int register_trigger_hist_enable_disable_cmds(void); |
| --- a/kernel/trace/trace_events_hist.c |
| +++ b/kernel/trace/trace_events_hist.c |
| @@ -59,7 +59,12 @@ struct hist_field { |
| struct hist_trigger_data *hist_data; |
| struct hist_var var; |
| enum field_op_id operator; |
| + char *system; |
| + char *event_name; |
| char *name; |
| + unsigned int var_idx; |
| + unsigned int var_ref_idx; |
| + bool read_once; |
| }; |
| |
| static u64 hist_field_none(struct hist_field *field, |
| @@ -214,6 +219,7 @@ enum hist_field_flags { |
| HIST_FIELD_FL_TIMESTAMP_USECS = 1 << 11, |
| HIST_FIELD_FL_VAR = 1 << 12, |
| HIST_FIELD_FL_EXPR = 1 << 13, |
| + HIST_FIELD_FL_VAR_REF = 1 << 14, |
| }; |
| |
| struct var_defs { |
| @@ -253,6 +259,8 @@ struct hist_trigger_data { |
| struct tracing_map *map; |
| bool enable_timestamps; |
| bool remove; |
| + struct hist_field *var_refs[TRACING_MAP_VARS_MAX]; |
| + unsigned int n_var_refs; |
| }; |
| |
| static u64 hist_field_timestamp(struct hist_field *hist_field, |
| @@ -271,6 +279,214 @@ static u64 hist_field_timestamp(struct h |
| 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, |
| + unsigned int var_idx) |
| +{ |
| + struct hist_field *found = NULL; |
| + |
| + if (hist_field && hist_field->flags & HIST_FIELD_FL_VAR_REF) { |
| + if (hist_field->var.idx == var_idx && |
| + hist_field->var.hist_data == var_data) { |
| + found = hist_field; |
| + } |
| + } |
| + |
| + return found; |
| +} |
| + |
| +static struct hist_field * |
| +check_field_for_var_refs(struct hist_trigger_data *hist_data, |
| + struct hist_field *hist_field, |
| + struct hist_trigger_data *var_data, |
| + unsigned int var_idx, |
| + unsigned int level) |
| +{ |
| + struct hist_field *found = NULL; |
| + unsigned int i; |
| + |
| + if (level > 3) |
| + return found; |
| + |
| + if (!hist_field) |
| + return found; |
| + |
| + found = check_field_for_var_ref(hist_field, var_data, var_idx); |
| + if (found) |
| + return found; |
| + |
| + for (i = 0; i < HIST_FIELD_OPERANDS_MAX; i++) { |
| + struct hist_field *operand; |
| + |
| + operand = hist_field->operands[i]; |
| + found = check_field_for_var_refs(hist_data, operand, var_data, |
| + var_idx, level + 1); |
| + if (found) |
| + return found; |
| + } |
| + |
| + return found; |
| +} |
| + |
| +static struct hist_field *find_var_ref(struct hist_trigger_data *hist_data, |
| + struct hist_trigger_data *var_data, |
| + unsigned int var_idx) |
| +{ |
| + struct hist_field *hist_field, *found = NULL; |
| + unsigned int i; |
| + |
| + for_each_hist_field(i, hist_data) { |
| + hist_field = hist_data->fields[i]; |
| + found = check_field_for_var_refs(hist_data, hist_field, |
| + var_data, var_idx, 0); |
| + if (found) |
| + return found; |
| + } |
| + |
| + return found; |
| +} |
| + |
| +static struct hist_field *find_any_var_ref(struct hist_trigger_data *hist_data, |
| + unsigned int var_idx) |
| +{ |
| + struct trace_array *tr = hist_data->event_file->tr; |
| + struct hist_field *found = NULL; |
| + struct hist_var_data *var_data; |
| + |
| + list_for_each_entry(var_data, &tr->hist_vars, list) { |
| + if (var_data->hist_data == hist_data) |
| + continue; |
| + found = find_var_ref(var_data->hist_data, hist_data, var_idx); |
| + if (found) |
| + break; |
| + } |
| + |
| + return found; |
| +} |
| + |
| +static bool check_var_refs(struct hist_trigger_data *hist_data) |
| +{ |
| + struct hist_field *field; |
| + bool found = false; |
| + int i; |
| + |
| + for_each_hist_field(i, hist_data) { |
| + field = hist_data->fields[i]; |
| + if (field && field->flags & HIST_FIELD_FL_VAR) { |
| + if (find_any_var_ref(hist_data, field->var.idx)) { |
| + found = true; |
| + break; |
| + } |
| + } |
| + } |
| + |
| + return found; |
| +} |
| + |
| +static struct hist_var_data *find_hist_vars(struct hist_trigger_data *hist_data) |
| +{ |
| + struct trace_array *tr = hist_data->event_file->tr; |
| + struct hist_var_data *var_data, *found = NULL; |
| + |
| + list_for_each_entry(var_data, &tr->hist_vars, list) { |
| + if (var_data->hist_data == hist_data) { |
| + found = var_data; |
| + break; |
| + } |
| + } |
| + |
| + return found; |
| +} |
| + |
| +static bool field_has_hist_vars(struct hist_field *hist_field, |
| + unsigned int level) |
| +{ |
| + int i; |
| + |
| + if (level > 3) |
| + return false; |
| + |
| + if (!hist_field) |
| + return false; |
| + |
| + if (hist_field->flags & HIST_FIELD_FL_VAR || |
| + hist_field->flags & HIST_FIELD_FL_VAR_REF) |
| + return true; |
| + |
| + for (i = 0; i < HIST_FIELD_OPERANDS_MAX; i++) { |
| + struct hist_field *operand; |
| + |
| + operand = hist_field->operands[i]; |
| + if (field_has_hist_vars(operand, level + 1)) |
| + return true; |
| + } |
| + |
| + return false; |
| +} |
| + |
| +static bool has_hist_vars(struct hist_trigger_data *hist_data) |
| +{ |
| + struct hist_field *hist_field; |
| + int i; |
| + |
| + for_each_hist_field(i, hist_data) { |
| + hist_field = hist_data->fields[i]; |
| + if (field_has_hist_vars(hist_field, 0)) |
| + return true; |
| + } |
| + |
| + return false; |
| +} |
| + |
| +static int save_hist_vars(struct hist_trigger_data *hist_data) |
| +{ |
| + struct trace_array *tr = hist_data->event_file->tr; |
| + struct hist_var_data *var_data; |
| + |
| + var_data = find_hist_vars(hist_data); |
| + if (var_data) |
| + return 0; |
| + |
| + if (trace_array_get(tr) < 0) |
| + return -ENODEV; |
| + |
| + var_data = kzalloc(sizeof(*var_data), GFP_KERNEL); |
| + if (!var_data) { |
| + trace_array_put(tr); |
| + return -ENOMEM; |
| + } |
| + |
| + var_data->hist_data = hist_data; |
| + list_add(&var_data->list, &tr->hist_vars); |
| + |
| + return 0; |
| +} |
| + |
| +static void remove_hist_vars(struct hist_trigger_data *hist_data) |
| +{ |
| + struct trace_array *tr = hist_data->event_file->tr; |
| + struct hist_var_data *var_data; |
| + |
| + var_data = find_hist_vars(hist_data); |
| + if (!var_data) |
| + return; |
| + |
| + if (WARN_ON(check_var_refs(hist_data))) |
| + return; |
| + |
| + list_del(&var_data->list); |
| + |
| + kfree(var_data); |
| + |
| + trace_array_put(tr); |
| +} |
| + |
| static struct hist_field *find_var_field(struct hist_trigger_data *hist_data, |
| const char *var_name) |
| { |
| @@ -313,10 +529,137 @@ static struct hist_field *find_var(struc |
| return NULL; |
| } |
| |
| +static struct trace_event_file *find_var_file(struct trace_array *tr, |
| + char *system, |
| + char *event_name, |
| + char *var_name) |
| +{ |
| + struct hist_trigger_data *var_hist_data; |
| + struct hist_var_data *var_data; |
| + struct trace_event_file *file, *found = NULL; |
| + |
| + if (system) |
| + return find_event_file(tr, system, event_name); |
| + |
| + list_for_each_entry(var_data, &tr->hist_vars, list) { |
| + var_hist_data = var_data->hist_data; |
| + file = var_hist_data->event_file; |
| + if (file == found) |
| + continue; |
| + |
| + if (find_var_field(var_hist_data, var_name)) { |
| + if (found) |
| + return NULL; |
| + |
| + found = file; |
| + } |
| + } |
| + |
| + return found; |
| +} |
| + |
| +static struct hist_field *find_file_var(struct trace_event_file *file, |
| + const char *var_name) |
| +{ |
| + struct hist_trigger_data *test_data; |
| + struct event_trigger_data *test; |
| + struct hist_field *hist_field; |
| + |
| + list_for_each_entry_rcu(test, &file->triggers, list) { |
| + if (test->cmd_ops->trigger_type == ETT_EVENT_HIST) { |
| + test_data = test->private_data; |
| + hist_field = find_var_field(test_data, var_name); |
| + if (hist_field) |
| + return hist_field; |
| + } |
| + } |
| + |
| + return NULL; |
| +} |
| + |
| +static struct hist_field *find_event_var(struct hist_trigger_data *hist_data, |
| + char *system, |
| + char *event_name, |
| + char *var_name) |
| +{ |
| + struct trace_array *tr = hist_data->event_file->tr; |
| + struct hist_field *hist_field = NULL; |
| + struct trace_event_file *file; |
| + |
| + file = find_var_file(tr, system, event_name, var_name); |
| + if (!file) |
| + return NULL; |
| + |
| + hist_field = find_file_var(file, var_name); |
| + |
| + return hist_field; |
| +} |
| + |
| struct hist_elt_data { |
| char *comm; |
| + u64 *var_ref_vals; |
| }; |
| |
| +static u64 hist_field_var_ref(struct hist_field *hist_field, |
| + struct tracing_map_elt *elt, |
| + struct ring_buffer_event *rbe, |
| + void *event) |
| +{ |
| + struct hist_elt_data *elt_data; |
| + u64 var_val = 0; |
| + |
| + elt_data = elt->private_data; |
| + var_val = elt_data->var_ref_vals[hist_field->var_ref_idx]; |
| + |
| + return var_val; |
| +} |
| + |
| +static bool resolve_var_refs(struct hist_trigger_data *hist_data, void *key, |
| + u64 *var_ref_vals, bool self) |
| +{ |
| + struct hist_trigger_data *var_data; |
| + struct tracing_map_elt *var_elt; |
| + struct hist_field *hist_field; |
| + unsigned int i, var_idx; |
| + bool resolved = true; |
| + u64 var_val = 0; |
| + |
| + for (i = 0; i < hist_data->n_var_refs; i++) { |
| + hist_field = hist_data->var_refs[i]; |
| + var_idx = hist_field->var.idx; |
| + var_data = hist_field->var.hist_data; |
| + |
| + if (var_data == NULL) { |
| + resolved = false; |
| + break; |
| + } |
| + |
| + if ((self && var_data != hist_data) || |
| + (!self && var_data == hist_data)) |
| + continue; |
| + |
| + var_elt = tracing_map_lookup(var_data->map, key); |
| + if (!var_elt) { |
| + resolved = false; |
| + break; |
| + } |
| + |
| + if (!tracing_map_var_set(var_elt, var_idx)) { |
| + resolved = false; |
| + break; |
| + } |
| + |
| + if (self || !hist_field->read_once) |
| + var_val = tracing_map_read_var(var_elt, var_idx); |
| + else |
| + var_val = tracing_map_read_var_once(var_elt, var_idx); |
| + |
| + var_ref_vals[i] = var_val; |
| + } |
| + |
| + return resolved; |
| +} |
| + |
| static const char *hist_field_name(struct hist_field *field, |
| unsigned int level) |
| { |
| @@ -331,8 +674,20 @@ static const char *hist_field_name(struc |
| field_name = hist_field_name(field->operands[0], ++level); |
| else if (field->flags & HIST_FIELD_FL_TIMESTAMP) |
| field_name = "common_timestamp"; |
| - else if (field->flags & HIST_FIELD_FL_EXPR) |
| - field_name = field->name; |
| + else if (field->flags & HIST_FIELD_FL_EXPR || |
| + field->flags & HIST_FIELD_FL_VAR_REF) { |
| + if (field->system) { |
| + static char full_name[MAX_FILTER_STR_VAL]; |
| + |
| + strcat(full_name, field->system); |
| + strcat(full_name, "."); |
| + strcat(full_name, field->event_name); |
| + strcat(full_name, "."); |
| + strcat(full_name, field->name); |
| + field_name = full_name; |
| + } else |
| + field_name = field->name; |
| + } |
| |
| if (field_name == NULL) |
| field_name = ""; |
| @@ -612,6 +967,9 @@ static const char *get_hist_field_flags( |
| |
| static void expr_field_str(struct hist_field *field, char *expr) |
| { |
| + if (field->flags & HIST_FIELD_FL_VAR_REF) |
| + strcat(expr, "$"); |
| + |
| strcat(expr, hist_field_name(field, 0)); |
| |
| if (field->flags) { |
| @@ -742,6 +1100,11 @@ static struct hist_field *create_hist_fi |
| if (flags & HIST_FIELD_FL_EXPR) |
| goto out; /* caller will populate */ |
| |
| + if (flags & HIST_FIELD_FL_VAR_REF) { |
| + hist_field->fn = hist_field_var_ref; |
| + goto out; |
| + } |
| + |
| if (flags & HIST_FIELD_FL_HITCOUNT) { |
| hist_field->fn = hist_field_counter; |
| hist_field->size = sizeof(u64); |
| @@ -835,6 +1198,144 @@ static void destroy_hist_fields(struct h |
| } |
| } |
| |
| +static int init_var_ref(struct hist_field *ref_field, |
| + struct hist_field *var_field, |
| + char *system, char *event_name) |
| +{ |
| + int err = 0; |
| + |
| + ref_field->var.idx = var_field->var.idx; |
| + ref_field->var.hist_data = var_field->hist_data; |
| + ref_field->size = var_field->size; |
| + ref_field->is_signed = var_field->is_signed; |
| + ref_field->flags |= var_field->flags & |
| + (HIST_FIELD_FL_TIMESTAMP | HIST_FIELD_FL_TIMESTAMP_USECS); |
| + |
| + if (system) { |
| + ref_field->system = kstrdup(system, GFP_KERNEL); |
| + if (!ref_field->system) |
| + return -ENOMEM; |
| + } |
| + |
| + if (event_name) { |
| + ref_field->event_name = kstrdup(event_name, GFP_KERNEL); |
| + if (!ref_field->event_name) { |
| + err = -ENOMEM; |
| + goto free; |
| + } |
| + } |
| + |
| + ref_field->name = kstrdup(var_field->var.name, GFP_KERNEL); |
| + if (!ref_field->name) { |
| + err = -ENOMEM; |
| + goto free; |
| + } |
| + |
| + ref_field->type = kstrdup(var_field->type, GFP_KERNEL); |
| + if (!ref_field->type) { |
| + err = -ENOMEM; |
| + goto free; |
| + } |
| + out: |
| + return err; |
| + free: |
| + kfree(ref_field->system); |
| + kfree(ref_field->event_name); |
| + kfree(ref_field->name); |
| + |
| + goto out; |
| +} |
| + |
| +static struct hist_field *create_var_ref(struct hist_field *var_field, |
| + char *system, char *event_name) |
| +{ |
| + unsigned long flags = HIST_FIELD_FL_VAR_REF; |
| + struct hist_field *ref_field; |
| + |
| + ref_field = create_hist_field(var_field->hist_data, NULL, flags, NULL); |
| + if (ref_field) { |
| + if (init_var_ref(ref_field, var_field, system, event_name)) { |
| + destroy_hist_field(ref_field, 0); |
| + return NULL; |
| + } |
| + } |
| + |
| + return ref_field; |
| +} |
| + |
| +static bool is_var_ref(char *var_name) |
| +{ |
| + if (!var_name || strlen(var_name) < 2 || var_name[0] != '$') |
| + return false; |
| + |
| + return true; |
| +} |
| + |
| +static char *field_name_from_var(struct hist_trigger_data *hist_data, |
| + char *var_name) |
| +{ |
| + char *name, *field; |
| + unsigned int i; |
| + |
| + for (i = 0; i < hist_data->attrs->var_defs.n_vars; i++) { |
| + name = hist_data->attrs->var_defs.name[i]; |
| + |
| + if (strcmp(var_name, name) == 0) { |
| + field = hist_data->attrs->var_defs.expr[i]; |
| + if (contains_operator(field) || is_var_ref(field)) |
| + continue; |
| + return field; |
| + } |
| + } |
| + |
| + return NULL; |
| +} |
| + |
| +static char *local_field_var_ref(struct hist_trigger_data *hist_data, |
| + char *system, char *event_name, |
| + char *var_name) |
| +{ |
| + struct trace_event_call *call; |
| + |
| + if (system && event_name) { |
| + call = hist_data->event_file->event_call; |
| + |
| + if (strcmp(system, call->class->system) != 0) |
| + return NULL; |
| + |
| + if (strcmp(event_name, trace_event_name(call)) != 0) |
| + return NULL; |
| + } |
| + |
| + if (!!system != !!event_name) |
| + return NULL; |
| + |
| + if (!is_var_ref(var_name)) |
| + return NULL; |
| + |
| + var_name++; |
| + |
| + return field_name_from_var(hist_data, var_name); |
| +} |
| + |
| +static struct hist_field *parse_var_ref(struct hist_trigger_data *hist_data, |
| + char *system, char *event_name, |
| + char *var_name) |
| +{ |
| + struct hist_field *var_field = NULL, *ref_field = NULL; |
| + |
| + if (!is_var_ref(var_name)) |
| + return NULL; |
| + |
| + var_name++; |
| + |
| + var_field = find_event_var(hist_data, system, event_name, var_name); |
| + if (var_field) |
| + ref_field = create_var_ref(var_field, system, event_name); |
| + |
| + return ref_field; |
| +} |
| + |
| static struct ftrace_event_field * |
| parse_field(struct hist_trigger_data *hist_data, struct trace_event_file *file, |
| char *field_str, unsigned long *flags) |
| @@ -891,10 +1392,40 @@ static struct hist_field *parse_atom(str |
| struct trace_event_file *file, char *str, |
| unsigned long *flags, char *var_name) |
| { |
| + char *s, *ref_system = NULL, *ref_event = NULL, *ref_var = str; |
| struct ftrace_event_field *field = NULL; |
| struct hist_field *hist_field = NULL; |
| int ret = 0; |
| |
| + s = strchr(str, '.'); |
| + if (s) { |
| + s = strchr(++s, '.'); |
| + if (s) { |
| + ref_system = strsep(&str, "."); |
| + if (!str) { |
| + ret = -EINVAL; |
| + goto out; |
| + } |
| + ref_event = strsep(&str, "."); |
| + if (!str) { |
| + ret = -EINVAL; |
| + goto out; |
| + } |
| + ref_var = str; |
| + } |
| + } |
| + |
| + s = local_field_var_ref(hist_data, ref_system, ref_event, ref_var); |
| + if (!s) { |
| + hist_field = parse_var_ref(hist_data, ref_system, ref_event, ref_var); |
| + if (hist_field) { |
| + hist_data->var_refs[hist_data->n_var_refs] = hist_field; |
| + hist_field->var_ref_idx = hist_data->n_var_refs++; |
| + return hist_field; |
| + } |
| + } else |
| + str = s; |
| + |
| field = parse_field(hist_data, file, str, flags); |
| if (IS_ERR(field)) { |
| ret = PTR_ERR(field); |
| @@ -1066,6 +1597,9 @@ static struct hist_field *parse_expr(str |
| goto free; |
| } |
| |
| + operand1->read_once = true; |
| + operand2->read_once = true; |
| + |
| expr->operands[0] = operand1; |
| expr->operands[1] = operand2; |
| expr->operator = field_op; |
| @@ -1238,6 +1772,12 @@ static int create_key_field(struct hist_ |
| goto out; |
| } |
| |
| + if (hist_field->flags & HIST_FIELD_FL_VAR_REF) { |
| + destroy_hist_field(hist_field, 0); |
| + ret = -EINVAL; |
| + goto out; |
| + } |
| + |
| key_size = hist_field->size; |
| } |
| |
| @@ -1576,6 +2116,7 @@ create_hist_data(unsigned int map_bits, |
| |
| hist_data->attrs = attrs; |
| hist_data->remove = remove; |
| + hist_data->event_file = file; |
| |
| ret = create_hist_fields(hist_data, file); |
| if (ret) |
| @@ -1598,12 +2139,6 @@ create_hist_data(unsigned int map_bits, |
| 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: |
| @@ -1618,12 +2153,17 @@ create_hist_data(unsigned int map_bits, |
| |
| static void hist_trigger_elt_update(struct hist_trigger_data *hist_data, |
| struct tracing_map_elt *elt, void *rec, |
| - struct ring_buffer_event *rbe) |
| + struct ring_buffer_event *rbe, |
| + u64 *var_ref_vals) |
| { |
| + struct hist_elt_data *elt_data; |
| struct hist_field *hist_field; |
| unsigned int i, var_idx; |
| u64 hist_val; |
| |
| + elt_data = elt->private_data; |
| + elt_data->var_ref_vals = var_ref_vals; |
| + |
| for_each_hist_val_field(i, hist_data) { |
| hist_field = hist_data->fields[i]; |
| hist_val = hist_field->fn(hist_field, elt, rbe, rec); |
| @@ -1675,6 +2215,7 @@ static void event_hist_trigger(struct ev |
| struct hist_trigger_data *hist_data = data->private_data; |
| bool use_compound_key = (hist_data->n_keys > 1); |
| unsigned long entries[HIST_STACKTRACE_DEPTH]; |
| + u64 var_ref_vals[TRACING_MAP_VARS_MAX]; |
| char compound_key[HIST_KEY_SIZE_MAX]; |
| struct tracing_map_elt *elt = NULL; |
| struct stack_trace stacktrace; |
| @@ -1714,9 +2255,15 @@ static void event_hist_trigger(struct ev |
| if (use_compound_key) |
| key = compound_key; |
| |
| + if (hist_data->n_var_refs && |
| + !resolve_var_refs(hist_data, key, var_ref_vals, false)) |
| + return; |
| + |
| elt = tracing_map_insert(hist_data->map, key); |
| - if (elt) |
| - hist_trigger_elt_update(hist_data, elt, rec, rbe); |
| + if (!elt) |
| + return; |
| + |
| + hist_trigger_elt_update(hist_data, elt, rec, rbe, var_ref_vals); |
| } |
| |
| static void hist_trigger_stacktrace_print(struct seq_file *m, |
| @@ -1933,8 +2480,11 @@ static void hist_field_print(struct seq_ |
| |
| if (hist_field->flags & HIST_FIELD_FL_TIMESTAMP) |
| seq_puts(m, "common_timestamp"); |
| - else if (field_name) |
| + else if (field_name) { |
| + if (hist_field->flags & HIST_FIELD_FL_VAR_REF) |
| + seq_putc(m, '$'); |
| seq_printf(m, "%s", field_name); |
| + } |
| |
| if (hist_field->flags) { |
| const char *flags_str = get_hist_field_flags(hist_field); |
| @@ -2074,7 +2624,11 @@ static void event_hist_trigger_free(stru |
| if (!data->ref) { |
| if (data->name) |
| del_named_trigger(data); |
| + |
| trigger_data_free(data); |
| + |
| + remove_hist_vars(hist_data); |
| + |
| destroy_hist_data(hist_data); |
| } |
| } |
| @@ -2287,23 +2841,55 @@ static int hist_register_trigger(char *g |
| goto out; |
| } |
| |
| - list_add_rcu(&data->list, &file->triggers); |
| ret++; |
| |
| - update_cond_flag(file); |
| - |
| if (hist_data->enable_timestamps) |
| tracing_set_time_stamp_abs(file->tr, true); |
| + out: |
| + return ret; |
| +} |
| + |
| +static int hist_trigger_enable(struct event_trigger_data *data, |
| + struct trace_event_file *file) |
| +{ |
| + int ret = 0; |
| + |
| + list_add_tail_rcu(&data->list, &file->triggers); |
| + |
| + 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 bool hist_trigger_check_refs(struct event_trigger_data *data, |
| + struct trace_event_file *file) |
| +{ |
| + struct hist_trigger_data *hist_data = data->private_data; |
| + struct event_trigger_data *test, *named_data = NULL; |
| + |
| + 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, named_data, false)) |
| + continue; |
| + hist_data = test->private_data; |
| + if (check_var_refs(hist_data)) |
| + return true; |
| + break; |
| + } |
| + } |
| + |
| + return false; |
| +} |
| + |
| static void hist_unregister_trigger(char *glob, struct event_trigger_ops *ops, |
| struct event_trigger_data *data, |
| struct trace_event_file *file) |
| @@ -2336,11 +2922,30 @@ static void hist_unregister_trigger(char |
| } |
| } |
| |
| +static bool hist_file_check_refs(struct trace_event_file *file) |
| +{ |
| + struct hist_trigger_data *hist_data; |
| + struct event_trigger_data *test; |
| + |
| + list_for_each_entry_rcu(test, &file->triggers, list) { |
| + if (test->cmd_ops->trigger_type == ETT_EVENT_HIST) { |
| + hist_data = test->private_data; |
| + if (check_var_refs(hist_data)) |
| + return true; |
| + } |
| + } |
| + |
| + return false; |
| +} |
| + |
| static void hist_unreg_all(struct trace_event_file *file) |
| { |
| struct event_trigger_data *test, *n; |
| struct hist_trigger_data *hist_data; |
| |
| + if (hist_file_check_refs(file)) |
| + return; |
| + |
| list_for_each_entry_safe(test, n, &file->triggers, list) { |
| if (test->cmd_ops->trigger_type == ETT_EVENT_HIST) { |
| hist_data = test->private_data; |
| @@ -2416,6 +3021,11 @@ static int event_hist_trigger_func(struc |
| } |
| |
| if (remove) { |
| + if (hist_trigger_check_refs(trigger_data, file)) { |
| + ret = -EBUSY; |
| + goto out_free; |
| + } |
| + |
| cmd_ops->unreg(glob+1, trigger_ops, trigger_data, file); |
| ret = 0; |
| goto out_free; |
| @@ -2433,14 +3043,33 @@ static int event_hist_trigger_func(struc |
| goto out_free; |
| } else if (ret < 0) |
| goto out_free; |
| + |
| + if (get_named_trigger_data(trigger_data)) |
| + goto enable; |
| + |
| + if (has_hist_vars(hist_data)) |
| + save_hist_vars(hist_data); |
| + |
| + ret = tracing_map_init(hist_data->map); |
| + if (ret) |
| + goto out_unreg; |
| +enable: |
| + ret = hist_trigger_enable(trigger_data, file); |
| + if (ret) |
| + goto out_unreg; |
| + |
| /* Just return zero, not the number of registered triggers */ |
| ret = 0; |
| out: |
| return ret; |
| + out_unreg: |
| + cmd_ops->unreg(glob+1, trigger_ops, trigger_data, file); |
| out_free: |
| if (cmd_ops->set_filter) |
| cmd_ops->set_filter(NULL, trigger_data, NULL); |
| |
| + remove_hist_vars(hist_data); |
| + |
| kfree(trigger_data); |
| |
| destroy_hist_data(hist_data); |
| --- a/kernel/trace/trace_events_trigger.c |
| +++ b/kernel/trace/trace_events_trigger.c |
| @@ -909,6 +909,12 @@ void set_named_trigger_data(struct event |
| data->named_data = named_data; |
| } |
| |
| +struct event_trigger_data * |
| +get_named_trigger_data(struct event_trigger_data *data) |
| +{ |
| + return data->named_data; |
| +} |
| + |
| static void |
| traceon_trigger(struct event_trigger_data *data, void *rec, |
| struct ring_buffer_event *event) |