950 lines
24 KiB
Diff
950 lines
24 KiB
Diff
From: Tom Zanussi <tom.zanussi@linux.intel.com>
|
|
Date: Fri, 17 Nov 2017 14:33:01 -0600
|
|
Subject: [PATCH 22/37] tracing: Add variable reference handling to hist
|
|
triggers
|
|
Origin: https://www.kernel.org/pub/linux/kernel/projects/rt/4.14/older/patches-4.14.1-rt3.tar.xz
|
|
|
|
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 | 632 ++++++++++++++++++++++++++++++++++--
|
|
kernel/trace/trace_events_trigger.c | 6
|
|
4 files changed, 623 insertions(+), 20 deletions(-)
|
|
|
|
--- a/kernel/trace/trace.c
|
|
+++ b/kernel/trace/trace.c
|
|
@@ -7804,6 +7804,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;
|
|
@@ -8554,6 +8555,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,216 @@ 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 > 2)
|
|
+ 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;
|
|
+
|
|
+ found = find_var_ref(hist_data, hist_data, var_idx);
|
|
+ if (found)
|
|
+ return found;
|
|
+
|
|
+ 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 field_has_hist_vars(struct hist_field *hist_field,
|
|
+ unsigned int level)
|
|
+{
|
|
+ int i;
|
|
+
|
|
+ if (level > 2)
|
|
+ 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 +531,148 @@ 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_call *call;
|
|
+ struct trace_event_file *file, *found = NULL;
|
|
+ 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(var_hist_data, file, var_name)) {
|
|
+ if (found) {
|
|
+ return NULL;
|
|
+ }
|
|
+
|
|
+ found = file;
|
|
+ }
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (strcmp(event_name, name) != 0)
|
|
+ continue;
|
|
+ if (strcmp(system, call->class->system) != 0)
|
|
+ continue;
|
|
+
|
|
+ found = file;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ 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 +687,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 = "";
|
|
@@ -636,6 +1004,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]);
|
|
@@ -658,6 +1028,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]);
|
|
@@ -737,6 +1109,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);
|
|
@@ -830,6 +1207,77 @@ 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;
|
|
+
|
|
+ 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)
|
|
{
|
|
@@ -841,7 +1289,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;
|
|
}
|
|
@@ -853,11 +1301,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 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)
|
|
@@ -914,13 +1383,38 @@ 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 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, ".");
|
|
+ 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_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);
|
|
@@ -1094,6 +1588,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;
|
|
@@ -1266,6 +1763,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;
|
|
}
|
|
|
|
@@ -1604,6 +2107,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)
|
|
@@ -1626,12 +2130,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:
|
|
@@ -1646,12 +2144,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);
|
|
@@ -1703,6 +2206,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;
|
|
@@ -1742,9 +2246,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,
|
|
@@ -1961,8 +2471,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);
|
|
@@ -2102,7 +2615,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);
|
|
}
|
|
}
|
|
@@ -2315,23 +2832,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)
|
|
@@ -2364,11 +2913,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;
|
|
@@ -2444,6 +3012,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;
|
|
@@ -2461,14 +3034,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)
|