Users should be able to directly specify event fields in hist trigger
'actions' rather than being forced to explicitly create a variable for
that purpose.

Add support allowing fields to be used directly in actions, which
essentially does just that - creates 'invisible' variables for each
bare field specified in an action.  If a bare field refers to a field
on another (matching) event, it even creates a special histogram for
the purpose (since variables can't be defined on an existing histogram
after histogram creation).

Signed-off-by: Tom Zanussi <tom.zanu...@linux.intel.com>
---
 kernel/trace/trace_events_hist.c | 452 ++++++++++++++++++++++++++++++++++++++-
 1 file changed, 451 insertions(+), 1 deletion(-)

diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c
index 2906b92..ea006a0 100644
--- a/kernel/trace/trace_events_hist.c
+++ b/kernel/trace/trace_events_hist.c
@@ -254,6 +254,16 @@ struct hist_trigger_attrs {
        struct var_defs var_defs;
 };
 
+struct field_var {
+       struct hist_field       *var;
+       struct hist_field       *val;
+};
+
+struct field_var_hist {
+       struct hist_trigger_data        *hist_data;
+       char                            *cmd;
+};
+
 struct hist_trigger_data {
        struct hist_field               *fields[HIST_FIELDS_MAX];
        unsigned int                    n_vals;
@@ -274,6 +284,14 @@ 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;
+       struct field_var_hist           *field_var_hists[SYNTH_FIELDS_MAX];
+       unsigned int                    n_field_var_hists;
 };
 
 struct synth_field {
@@ -1392,6 +1410,7 @@ static struct hist_field *find_event_var(struct 
trace_array *tr,
 struct hist_elt_data {
        char *comm;
        u64 *var_ref_vals;
+       char *field_var_str[SYNTH_FIELDS_MAX];
 };
 
 static u64 hist_field_var_ref(struct hist_field *hist_field,
@@ -1669,7 +1688,14 @@ static inline void save_comm(char *comm, struct 
task_struct *task)
 
 static void hist_trigger_elt_data_free(struct tracing_map_elt *elt)
 {
+       struct hist_trigger_data *hist_data = elt->map->private_data;
        struct hist_elt_data *private_data = elt->private_data;
+       unsigned int i, n_str;
+
+       n_str = hist_data->n_field_var_str;
+
+       for (i = 0; i < n_str; i++)
+               kfree(private_data->field_var_str[i]);
 
        kfree(private_data->comm);
        kfree(private_data);
@@ -1681,7 +1707,7 @@ static int hist_trigger_elt_data_alloc(struct 
tracing_map_elt *elt)
        unsigned int size = TASK_COMM_LEN + 1;
        struct hist_elt_data *elt_data;
        struct hist_field *key_field;
-       unsigned int i;
+       unsigned int i, n_str;
 
        elt->private_data = elt_data = kzalloc(sizeof(*elt_data), GFP_KERNEL);
        if (!elt_data)
@@ -1701,6 +1727,18 @@ static int hist_trigger_elt_data_alloc(struct 
tracing_map_elt *elt)
                }
        }
 
+       n_str = hist_data->n_field_var_str;
+
+       size = STR_VAR_LEN_MAX;
+
+       for (i = 0; i < n_str; i++) {
+               elt_data->field_var_str[i] = kzalloc(size, GFP_KERNEL);
+               if (!elt_data->field_var_str[i]) {
+                       hist_trigger_elt_data_free(elt);
+                       return -ENOMEM;
+               }
+       }
+
        return 0;
 }
 
@@ -2347,6 +2385,387 @@ static struct hist_field *parse_expr(struct 
hist_trigger_data *hist_data,
        return ERR_PTR(ret);
 }
 
+static char *find_trigger_filter(struct hist_trigger_data *hist_data,
+                                struct trace_event_file *file)
+{
+       struct event_trigger_data *test;
+
+       list_for_each_entry_rcu(test, &file->triggers, list) {
+               if (test->cmd_ops->trigger_type == ETT_EVENT_HIST) {
+                       if (test->private_data == hist_data)
+                               return test->filter_str;
+               }
+       }
+
+       return NULL;
+}
+
+static struct event_command trigger_hist_cmd;
+static int event_hist_trigger_func(struct event_command *cmd_ops,
+                                  struct trace_event_file *file,
+                                  char *glob, char *cmd, char *param);
+
+static bool compatible_keys(struct hist_trigger_data *target_hist_data,
+                           struct hist_trigger_data *hist_data,
+                           unsigned int n_keys)
+{
+       struct hist_field *target_hist_field, *hist_field;
+       unsigned int n, i, j;
+
+       if (hist_data->n_fields - hist_data->n_vals != n_keys)
+               return false;
+
+       i = hist_data->n_vals;
+       j = target_hist_data->n_vals;
+
+       for (n = 0; n < n_keys; n++) {
+               hist_field = hist_data->fields[i + n];
+               target_hist_field = hist_data->fields[j + n];
+
+               if (strcmp(hist_field->type, target_hist_field->type) != 0)
+                       return false;
+               if (hist_field->size != target_hist_field->size)
+                       return false;
+               if (hist_field->is_signed != target_hist_field->is_signed)
+                       return false;
+       }
+
+       return true;
+}
+
+static struct hist_trigger_data *
+find_compatible_hist(struct hist_trigger_data *target_hist_data,
+                    struct trace_event_file *file)
+{
+       struct hist_trigger_data *hist_data;
+       struct event_trigger_data *test;
+       unsigned int n_keys;
+
+       n_keys = target_hist_data->n_fields - target_hist_data->n_vals;
+
+       list_for_each_entry_rcu(test, &file->triggers, list) {
+               if (test->cmd_ops->trigger_type == ETT_EVENT_HIST) {
+                       hist_data = test->private_data;
+
+                       if (compatible_keys(target_hist_data, hist_data, 
n_keys))
+                               return hist_data;
+               }
+       }
+
+       return NULL;
+}
+
+static struct trace_event_file *event_file(struct trace_array *tr,
+                                          char *system, char *event_name)
+{
+       struct trace_event_file *file;
+
+       file = find_event_file(tr, system, event_name);
+       if (!file)
+               return ERR_PTR(-EINVAL);
+
+       return file;
+}
+
+static struct hist_field *
+create_field_var_hist(struct hist_trigger_data *target_hist_data,
+                     char *system, char *event_name, char *field_name)
+{
+       struct trace_array *tr = target_hist_data->event_file->tr;
+       struct hist_field *event_var = ERR_PTR(-EINVAL);
+       struct hist_trigger_data *hist_data;
+       unsigned int i, n, first = true;
+       struct field_var_hist *var_hist;
+       struct trace_event_file *file;
+       struct hist_field *key_field;
+       char *saved_filter;
+       char *cmd;
+       int ret;
+
+       if (target_hist_data->n_field_var_hists >= SYNTH_FIELDS_MAX)
+               return ERR_PTR(-EINVAL);
+
+       file = event_file(tr, system, event_name);
+
+       if (IS_ERR(file)) {
+               ret = PTR_ERR(file);
+               return ERR_PTR(ret);
+       }
+
+       hist_data = find_compatible_hist(target_hist_data, file);
+       if (!hist_data)
+               return ERR_PTR(-EINVAL);
+
+       var_hist = kzalloc(sizeof(*var_hist), GFP_KERNEL);
+       if (!var_hist)
+               return ERR_PTR(-ENOMEM);
+
+       cmd = kzalloc(MAX_FILTER_STR_VAL, GFP_KERNEL);
+       if (!cmd) {
+               kfree(var_hist);
+               return ERR_PTR(-ENOMEM);
+       }
+
+       strcat(cmd, "keys=");
+
+       for_each_hist_key_field(i, hist_data) {
+               key_field = hist_data->fields[i];
+               if (!first)
+                       strcat(cmd, ",");
+               strcat(cmd, key_field->field->name);
+               first = false;
+       }
+
+       strcat(cmd, ":synthetic_");
+       strcat(cmd, field_name);
+       strcat(cmd, "=");
+       strcat(cmd, field_name);
+
+       saved_filter = find_trigger_filter(hist_data, file);
+       if (saved_filter) {
+               strcat(cmd, " if ");
+               strcat(cmd, saved_filter);
+       }
+
+       var_hist->cmd = kstrdup(cmd, GFP_KERNEL);
+       if (!var_hist->cmd) {
+               kfree(cmd);
+               kfree(var_hist);
+               return ERR_PTR(-ENOMEM);
+       }
+
+       var_hist->hist_data = hist_data;
+
+       ret = event_hist_trigger_func(&trigger_hist_cmd, file,
+                                     "", "hist", cmd);
+       if (ret) {
+               kfree(cmd);
+               kfree(var_hist->cmd);
+               kfree(var_hist);
+               return ERR_PTR(ret);
+       }
+
+       strcpy(cmd, "synthetic_");
+       strcat(cmd, field_name);
+
+       event_var = find_event_var(tr, system, event_name, cmd);
+       if (!event_var) {
+               kfree(cmd);
+               kfree(var_hist->cmd);
+               kfree(var_hist);
+               return ERR_PTR(-EINVAL);
+       }
+
+       n = target_hist_data->n_field_var_hists;
+       target_hist_data->field_var_hists[n] = var_hist;
+       target_hist_data->n_field_var_hists++;
+
+       return event_var;
+}
+
+static struct hist_field *
+find_target_event_var(struct hist_trigger_data *hist_data,
+                     char *system, char *event_name, char *var_name)
+{
+       struct trace_event_file *file = hist_data->event_file;
+       struct hist_field *hist_field = NULL;
+
+       if (system) {
+               struct trace_event_call *call;
+
+               if (!event_name)
+                       return NULL;
+
+               call = file->event_call;
+
+               if (strcmp(system, call->class->system) != 0)
+                       return NULL;
+
+               if (strcmp(event_name, trace_event_name(call)) != 0)
+                       return NULL;
+       }
+
+       hist_field = find_var_field(hist_data, var_name);
+
+       return hist_field;
+}
+
+static inline void __update_field_vars(struct tracing_map_elt *elt,
+                                      struct ring_buffer_event *rbe,
+                                      void *rec,
+                                      struct field_var **field_vars,
+                                      unsigned int n_field_vars,
+                                      unsigned int field_var_str_start)
+{
+       struct hist_elt_data *elt_data = elt->private_data;
+       unsigned int i, j, var_idx;
+       u64 var_val;
+
+       for (i = 0, j = field_var_str_start; i < n_field_vars; i++) {
+               struct field_var *field_var = field_vars[i];
+               struct hist_field *var = field_var->var;
+               struct hist_field *val = field_var->val;
+
+               var_val = val->fn(val, elt, rbe, rec);
+               var_idx = var->var.idx;
+
+               if (val->flags & HIST_FIELD_FL_STRING) {
+                       char *str = elt_data->field_var_str[j++];
+                       char *val_str = (char *)(uintptr_t)var_val;
+
+                       strncpy(str, val_str, STR_VAR_LEN_MAX);
+                       var_val = (u64)(uintptr_t)str;
+               }
+               tracing_map_set_var(elt, var_idx, var_val);
+       }
+}
+
+static void update_field_vars(struct hist_trigger_data *hist_data,
+                             struct tracing_map_elt *elt,
+                             struct ring_buffer_event *rbe,
+                             void *rec)
+{
+       __update_field_vars(elt, rbe, rec, hist_data->field_vars,
+                           hist_data->n_field_vars, 0);
+}
+
+static struct hist_field *create_var(struct hist_trigger_data *hist_data,
+                                    struct trace_event_file *file,
+                                    char *name, int size, const char *type)
+{
+       struct hist_field *var;
+       int idx;
+
+       if (find_var(file, name) && !hist_data->remove) {
+               var = ERR_PTR(-EINVAL);
+               goto out;
+       }
+
+       var = kzalloc(sizeof(struct hist_field), GFP_KERNEL);
+       if (!var) {
+               var = ERR_PTR(-ENOMEM);
+               goto out;
+       }
+
+       idx = tracing_map_add_var(hist_data->map);
+       if (idx < 0) {
+               kfree(var);
+               var = ERR_PTR(-EINVAL);
+               goto out;
+       }
+
+       var->flags = HIST_FIELD_FL_VAR;
+       var->var.idx = idx;
+       var->var.hist_data = var->hist_data = hist_data;
+       var->size = size;
+       var->var.name = kstrdup(name, GFP_KERNEL);
+       var->type = kstrdup(type, GFP_KERNEL);
+       if (!var->var.name || !var->type) {
+               kfree(var->var.name);
+               kfree(var->type);
+               kfree(var);
+               var = ERR_PTR(-ENOMEM);
+       }
+ out:
+       return var;
+}
+
+static struct field_var *create_field_var(struct hist_trigger_data *hist_data,
+                                         struct trace_event_file *file,
+                                         char *field_name)
+{
+       struct hist_field *val = NULL, *var = NULL;
+       unsigned long flags = HIST_FIELD_FL_VAR;
+       struct field_var *field_var;
+       int ret = 0;
+
+       if (hist_data->n_field_vars >= SYNTH_FIELDS_MAX) {
+               ret = -EINVAL;
+               goto err;
+       }
+
+       val = parse_atom(hist_data, file, field_name, &flags, NULL);
+       if (IS_ERR(val)) {
+               ret = PTR_ERR(val);
+               goto err;
+       }
+
+       var = create_var(hist_data, file, field_name, val->size, val->type);
+       if (IS_ERR(var)) {
+               kfree(val);
+               ret = PTR_ERR(var);
+               goto err;
+       }
+
+       field_var = kzalloc(sizeof(struct field_var), GFP_KERNEL);
+       if (!field_var) {
+               kfree(val);
+               kfree(var);
+               ret =  -ENOMEM;
+               goto err;
+       }
+
+       field_var->var = var;
+       field_var->val = val;
+ out:
+       return field_var;
+ err:
+       field_var = ERR_PTR(ret);
+       goto out;
+}
+
+static struct field_var *
+create_target_field_var(struct hist_trigger_data *hist_data,
+                       char *system, char *event_name, char *var_name)
+{
+       struct trace_event_file *file = hist_data->event_file;
+
+       if (system) {
+               struct trace_event_call *call;
+
+               if (!event_name)
+                       return NULL;
+
+               call = file->event_call;
+
+               if (strcmp(system, call->class->system) != 0)
+                       return NULL;
+
+               if (strcmp(event_name, trace_event_name(call)) != 0)
+                       return NULL;
+       }
+
+       return create_field_var(hist_data, file, var_name);
+}
+
+static void destroy_field_var(struct field_var *field_var)
+{
+       if (!field_var)
+               return;
+
+       destroy_hist_field(field_var->var, 0);
+       destroy_hist_field(field_var->val, 0);
+
+       kfree(field_var);
+}
+
+static void destroy_field_vars(struct hist_trigger_data *hist_data)
+{
+       unsigned int i;
+
+       for (i = 0; i < hist_data->n_field_vars; i++)
+               destroy_field_var(hist_data->field_vars[i]);
+}
+
+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;
+
+       if (field_var->val->flags & HIST_FIELD_FL_STRING)
+               hist_data->n_field_var_str++;
+}
+
 static int create_hitcount_val(struct hist_trigger_data *hist_data)
 {
        hist_data->fields[HITCOUNT_IDX] =
@@ -2814,6 +3233,16 @@ static void print_actions_spec(struct seq_file *m,
        }
 }
 
+static void destroy_field_var_hists(struct hist_trigger_data *hist_data)
+{
+       unsigned int i;
+
+       for (i = 0; i < hist_data->n_field_var_hists; i++) {
+               kfree(hist_data->field_var_hists[i]->cmd);
+               kfree(hist_data->field_var_hists[i]);
+       }
+}
+
 static void destroy_hist_data(struct hist_trigger_data *hist_data)
 {
        if (!hist_data)
@@ -2824,6 +3253,8 @@ static void destroy_hist_data(struct hist_trigger_data 
*hist_data)
        tracing_map_destroy(hist_data->map);
 
        destroy_actions(hist_data);
+       destroy_field_vars(hist_data);
+       destroy_field_var_hists(hist_data);
 
        kfree(hist_data);
 }
@@ -2957,6 +3388,8 @@ static void hist_trigger_elt_update(struct 
hist_trigger_data *hist_data,
                        tracing_map_set_var(elt, var_idx, hist_val);
                }
        }
+
+       update_field_vars(hist_data, elt, rbe, rec);
 }
 
 static inline void add_to_key(char *compound_key, void *key,
@@ -3677,6 +4110,21 @@ static bool hist_trigger_check_refs(struct 
event_trigger_data *data,
        return false;
 }
 
+static void unregister_field_var_hists(struct hist_trigger_data *hist_data)
+{
+       struct trace_event_file *file;
+       unsigned int i;
+       char *cmd;
+       int ret;
+
+       for (i = 0; i < hist_data->n_field_var_hists; i++) {
+               file = hist_data->field_var_hists[i]->hist_data->event_file;
+               cmd = hist_data->field_var_hists[i]->cmd;
+               ret = event_hist_trigger_func(&trigger_hist_cmd, file,
+                                             "!hist", "hist", cmd);
+       }
+}
+
 static void hist_unregister_trigger(char *glob, struct event_trigger_ops *ops,
                                    struct event_trigger_data *data,
                                    struct trace_event_file *file)
@@ -3692,6 +4140,7 @@ static void hist_unregister_trigger(char *glob, struct 
event_trigger_ops *ops,
                if (test->cmd_ops->trigger_type == ETT_EVENT_HIST) {
                        if (!hist_trigger_match(data, test, named_data, false))
                                continue;
+                       unregister_field_var_hists(test->private_data);
                        unregistered = true;
                        list_del_rcu(&test->list);
                        trace_event_trigger_enable_disable(file, 0);
@@ -3733,6 +4182,7 @@ static void hist_unreg_all(struct trace_event_file *file)
 
        list_for_each_entry_safe(test, n, &file->triggers, list) {
                if (test->cmd_ops->trigger_type == ETT_EVENT_HIST) {
+                       unregister_field_var_hists(test->private_data);
                        list_del_rcu(&test->list);
                        trace_event_trigger_enable_disable(file, 0);
                        update_cond_flag(file);
-- 
1.9.3

Reply via email to