| From: Tom Zanussi <tom.zanussi@linux.intel.com> |
| Date: Tue, 5 Sep 2017 16:57:36 -0500 |
| Subject: [PATCH 24/40] 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. |
| |
| Signed-off-by: Tom Zanussi <tom.zanussi@linux.intel.com> |
| 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 | 606 ++++++++++++++++++++++++++++++++---- |
| kernel/trace/trace_events_trigger.c | 6 |
| 4 files changed, 561 insertions(+), 56 deletions(-) |
| |
| --- a/kernel/trace/trace.c |
| +++ b/kernel/trace/trace.c |
| @@ -7380,6 +7380,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; |
| @@ -8112,6 +8113,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 |
| @@ -266,6 +266,7 @@ struct trace_array { |
| int function_enabled; |
| #endif |
| int time_stamp_abs_ref; |
| + struct list_head hist_vars; |
| }; |
| |
| enum { |
| @@ -1442,6 +1443,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 |
| @@ -60,6 +60,9 @@ struct hist_field { |
| struct hist_var var; |
| enum field_op_id operator; |
| char *name; |
| + unsigned int var_idx; |
| + unsigned int var_ref_idx; |
| + bool read_once; |
| }; |
| |
| static u64 hist_field_none(struct hist_field *field, |
| @@ -215,6 +218,7 @@ enum hist_field_flags { |
| HIST_FIELD_FL_VAR = 4096, |
| HIST_FIELD_FL_VAR_ONLY = 8192, |
| HIST_FIELD_FL_EXPR = 16384, |
| + HIST_FIELD_FL_VAR_REF = 32768, |
| }; |
| |
| struct var_defs { |
| @@ -255,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, |
| @@ -273,10 +279,344 @@ 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_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 *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, j; |
| + |
| + for_each_hist_field(i, hist_data) { |
| + hist_field = hist_data->fields[i]; |
| + found = check_var_ref(hist_field, var_data, var_idx); |
| + if (found) |
| + return found; |
| + |
| + for (j = 0; j < HIST_FIELD_OPERANDS_MAX; j++) { |
| + struct hist_field *operand; |
| + |
| + operand = hist_field->operands[j]; |
| + found = check_var_ref(operand, var_data, var_idx); |
| + 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) { |
| + 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 has_hist_vars(struct hist_trigger_data *hist_data) |
| +{ |
| + struct hist_field *hist_field; |
| + int i, j; |
| + |
| + for_each_hist_field(i, hist_data) { |
| + hist_field = hist_data->fields[i]; |
| + if (hist_field && |
| + (hist_field->flags & HIST_FIELD_FL_VAR || |
| + hist_field->flags & HIST_FIELD_FL_VAR_REF)) |
| + return true; |
| + |
| + for (j = 0; j < HIST_FIELD_OPERANDS_MAX; j++) { |
| + struct hist_field *operand; |
| + |
| + operand = hist_field->operands[j]; |
| + if (operand && |
| + (operand->flags & HIST_FIELD_FL_VAR || |
| + operand->flags & HIST_FIELD_FL_VAR_REF)) |
| + 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) |
| +{ |
| + struct hist_field *hist_field, *found = NULL; |
| + int i; |
| + |
| + for_each_hist_field(i, hist_data) { |
| + hist_field = hist_data->fields[i]; |
| + if (hist_field && hist_field->flags & HIST_FIELD_FL_VAR && |
| + strcmp(hist_field->var.name, var_name) == 0) { |
| + found = hist_field; |
| + break; |
| + } |
| + } |
| + |
| + return found; |
| +} |
| + |
| +static struct hist_field *find_var(struct trace_event_file *file, |
| + const char *var_name) |
| +{ |
| + struct hist_trigger_data *hist_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) { |
| + hist_data = test->private_data; |
| + hist_field = find_var_field(hist_data, var_name); |
| + if (hist_field) |
| + return hist_field; |
| + } |
| + } |
| + |
| + return NULL; |
| +} |
| + |
| +static struct trace_event_file *find_var_file(struct trace_array *tr, |
| + const char *system, |
| + const char *event_name, |
| + const char *var_name) |
| +{ |
| + struct hist_trigger_data *var_hist_data; |
| + struct hist_var_data *var_data; |
| + struct trace_event_call *call; |
| + struct trace_event_file *file; |
| + const char *name; |
| + |
| + list_for_each_entry(var_data, &tr->hist_vars, list) { |
| + var_hist_data = var_data->hist_data; |
| + file = var_hist_data->event_file; |
| + call = file->event_call; |
| + name = trace_event_name(call); |
| + |
| + if (!system || !event_name) { |
| + if (find_var(file, var_name)) |
| + return file; |
| + continue; |
| + } |
| + |
| + if (strcmp(event_name, name) != 0) |
| + continue; |
| + if (strcmp(system, call->class->system) != 0) |
| + continue; |
| + |
| + return file; |
| + } |
| + |
| + return NULL; |
| +} |
| + |
| +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 trace_array *tr, |
| + const char *system, |
| + const char *event_name, |
| + const char *var_name) |
| +{ |
| + 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) |
| { |
| @@ -291,7 +631,8 @@ 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) |
| + else if (field->flags & HIST_FIELD_FL_EXPR || |
| + field->flags & HIST_FIELD_FL_VAR_REF) |
| field_name = field->name; |
| |
| if (field_name == NULL) |
| @@ -574,6 +915,8 @@ static char *expr_str(struct hist_field |
| return expr; |
| } |
| |
| + if (field->operands[0]->flags & HIST_FIELD_FL_VAR_REF) |
| + strcat(expr, "$"); |
| strcat(expr, hist_field_name(field->operands[0], 0)); |
| if (field->operands[0]->flags) { |
| const char *flags_str = get_hist_field_flags(field->operands[0]); |
| @@ -596,6 +939,8 @@ static char *expr_str(struct hist_field |
| return NULL; |
| } |
| |
| + if (field->operands[1]->flags & HIST_FIELD_FL_VAR_REF) |
| + strcat(expr, "$"); |
| strcat(expr, hist_field_name(field->operands[1], 0)); |
| if (field->operands[1]->flags) { |
| const char *flags_str = get_hist_field_flags(field->operands[1]); |
| @@ -675,6 +1020,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); |
| @@ -768,6 +1118,51 @@ static void destroy_hist_fields(struct h |
| } |
| } |
| |
| +static int init_var_ref(struct hist_field *ref_field, |
| + struct hist_field *var_field) |
| +{ |
| + 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->name = kstrdup(var_field->var.name, GFP_KERNEL); |
| + if (!ref_field->name) |
| + return -ENOMEM; |
| + |
| + ref_field->type = kstrdup(var_field->type, GFP_KERNEL); |
| + if (!ref_field->type) { |
| + kfree(ref_field->name); |
| + return -ENOMEM; |
| + } |
| + |
| + return 0; |
| +} |
| + |
| +static struct hist_field *create_var_ref(struct hist_field *var_field) |
| +{ |
| + 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)) { |
| + 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) |
| { |
| @@ -779,7 +1174,7 @@ static char *field_name_from_var(struct |
| |
| if (strcmp(var_name, name) == 0) { |
| field = hist_data->attrs->var_defs.expr[i]; |
| - if (contains_operator(field)) |
| + if (contains_operator(field) || is_var_ref(field)) |
| continue; |
| return field; |
| } |
| @@ -791,11 +1186,32 @@ static char *field_name_from_var(struct |
| static char *local_field_var_ref(struct hist_trigger_data *hist_data, |
| char *var_name) |
| { |
| + 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 trace_array *tr, |
| + 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(tr, system, event_name, var_name); |
| + if (var_field) |
| + ref_field = create_var_ref(var_field); |
| + |
| + 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) |
| @@ -852,13 +1268,31 @@ struct hist_field *parse_atom(struct his |
| struct trace_event_file *file, char *str, |
| unsigned long *flags, char *var_name) |
| { |
| - char *s; |
| + char *s, *ref_system = NULL, *ref_event = NULL, *ref_var = str; |
| + struct trace_array *tr = hist_data->event_file->tr; |
| struct ftrace_event_field *field = NULL; |
| struct hist_field *hist_field = NULL; |
| int ret = 0; |
| |
| - s = local_field_var_ref(hist_data, str); |
| - if (s) |
| + s = strchr(str, '.'); |
| + if (s) { |
| + s = strchr(++s, '.'); |
| + if (s) { |
| + ref_system = strsep(&str, "."); |
| + ref_event = strsep(&str, "."); |
| + ref_var = str; |
| + } |
| + } |
| + |
| + s = local_field_var_ref(hist_data, ref_var); |
| + if (!s) { |
| + hist_field = parse_var_ref(tr, 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); |
| @@ -1029,6 +1463,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; |
| @@ -1075,43 +1512,6 @@ static int create_hitcount_val(struct hi |
| return 0; |
| } |
| |
| -static struct hist_field *find_var_field(struct hist_trigger_data *hist_data, |
| - const char *var_name) |
| -{ |
| - struct hist_field *hist_field, *found = NULL; |
| - int i; |
| - |
| - for_each_hist_field(i, hist_data) { |
| - hist_field = hist_data->fields[i]; |
| - if (hist_field && hist_field->flags & HIST_FIELD_FL_VAR && |
| - strcmp(hist_field->var.name, var_name) == 0) { |
| - found = hist_field; |
| - break; |
| - } |
| - } |
| - |
| - return found; |
| -} |
| - |
| -static struct hist_field *find_var(struct trace_event_file *file, |
| - const char *var_name) |
| -{ |
| - struct hist_trigger_data *hist_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) { |
| - hist_data = test->private_data; |
| - hist_field = find_var_field(hist_data, var_name); |
| - if (hist_field) |
| - return hist_field; |
| - } |
| - } |
| - |
| - return NULL; |
| -} |
| - |
| static int __create_val_field(struct hist_trigger_data *hist_data, |
| unsigned int val_idx, |
| struct trace_event_file *file, |
| @@ -1245,6 +1645,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; |
| } |
| |
| @@ -1580,6 +1986,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) |
| @@ -1602,12 +2009,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: |
| @@ -1622,12 +2023,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); |
| @@ -1680,6 +2086,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; |
| @@ -1719,9 +2126,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, |
| @@ -1824,7 +2237,8 @@ hist_trigger_entry_print(struct seq_file |
| field_name = hist_field_name(hist_data->fields[i], 0); |
| |
| if (hist_data->fields[i]->flags & HIST_FIELD_FL_VAR || |
| - hist_data->fields[i]->flags & HIST_FIELD_FL_EXPR) |
| + hist_data->fields[i]->flags & HIST_FIELD_FL_EXPR || |
| + hist_data->fields[i]->flags & HIST_FIELD_FL_VAR_REF) |
| continue; |
| |
| if (hist_data->fields[i]->flags & HIST_FIELD_FL_HEX) { |
| @@ -2074,7 +2488,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); |
| } |
| } |
| @@ -2288,23 +2706,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_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) |
| @@ -2335,10 +2785,30 @@ static void hist_unregister_trigger(char |
| tracing_set_time_stamp_abs(file->tr, false); |
| } |
| |
| +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; |
| + break; |
| + } |
| + } |
| + |
| + return false; |
| +} |
| + |
| static void hist_unreg_all(struct trace_event_file *file) |
| { |
| struct event_trigger_data *test, *n; |
| |
| + 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) { |
| list_del_rcu(&test->list); |
| @@ -2411,6 +2881,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; |
| @@ -2428,14 +2903,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) |