]> asedeno.scripts.mit.edu Git - linux.git/blobdiff - kernel/trace/trace_events_hist.c
trace/probes: Remove kernel doc style from non kernel doc comment
[linux.git] / kernel / trace / trace_events_hist.c
index 449d90cfa151a3444687672f2ffe3f2ef498824b..ca46339f30090d84d72667b4207a8272d7fa1e9f 100644 (file)
@@ -313,9 +313,9 @@ struct hist_trigger_data {
        struct field_var_hist           *field_var_hists[SYNTH_FIELDS_MAX];
        unsigned int                    n_field_var_hists;
 
-       struct field_var                *max_vars[SYNTH_FIELDS_MAX];
-       unsigned int                    n_max_vars;
-       unsigned int                    n_max_var_str;
+       struct field_var                *save_vars[SYNTH_FIELDS_MAX];
+       unsigned int                    n_save_vars;
+       unsigned int                    n_save_var_str;
 };
 
 static int synth_event_create(int argc, const char **argv);
@@ -383,41 +383,157 @@ struct action_data;
 
 typedef void (*action_fn_t) (struct hist_trigger_data *hist_data,
                             struct tracing_map_elt *elt, void *rec,
-                            struct ring_buffer_event *rbe,
+                            struct ring_buffer_event *rbe, void *key,
                             struct action_data *data, u64 *var_ref_vals);
 
+typedef bool (*check_track_val_fn_t) (u64 track_val, u64 var_val);
+
+enum handler_id {
+       HANDLER_ONMATCH = 1,
+       HANDLER_ONMAX,
+       HANDLER_ONCHANGE,
+};
+
+enum action_id {
+       ACTION_SAVE = 1,
+       ACTION_TRACE,
+       ACTION_SNAPSHOT,
+};
+
 struct action_data {
+       enum handler_id         handler;
+       enum action_id          action;
+       char                    *action_name;
        action_fn_t             fn;
+
        unsigned int            n_params;
        char                    *params[SYNTH_FIELDS_MAX];
 
+       /*
+        * When a histogram trigger is hit, the values of any
+        * references to variables, including variables being passed
+        * as parameters to synthetic events, are collected into a
+        * var_ref_vals array.  This var_ref_idx is the index of the
+        * first param in the array to be passed to the synthetic
+        * event invocation.
+        */
+       unsigned int            var_ref_idx;
+       struct synth_event      *synth_event;
+       bool                    use_trace_keyword;
+       char                    *synth_event_name;
+
        union {
                struct {
-                       /*
-                        * When a histogram trigger is hit, the values of any
-                        * references to variables, including variables being passed
-                        * as parameters to synthetic events, are collected into a
-                        * var_ref_vals array.  This var_ref_idx is the index of the
-                        * first param in the array to be passed to the synthetic
-                        * event invocation.
-                        */
-                       unsigned int            var_ref_idx;
-                       char                    *match_event;
-                       char                    *match_event_system;
-                       char                    *synth_event_name;
-                       struct synth_event      *synth_event;
-               } onmatch;
+                       char                    *event;
+                       char                    *event_system;
+               } match_data;
 
                struct {
+                       /*
+                        * var_str contains the $-unstripped variable
+                        * name referenced by var_ref, and used when
+                        * printing the action.  Because var_ref
+                        * creation is deferred to create_actions(),
+                        * we need a per-action way to save it until
+                        * then, thus var_str.
+                        */
                        char                    *var_str;
-                       char                    *fn_name;
-                       unsigned int            max_var_ref_idx;
-                       struct hist_field       *max_var;
-                       struct hist_field       *var;
-               } onmax;
+
+                       /*
+                        * var_ref refers to the variable being
+                        * tracked e.g onmax($var).
+                        */
+                       struct hist_field       *var_ref;
+
+                       /*
+                        * track_var contains the 'invisible' tracking
+                        * variable created to keep the current
+                        * e.g. max value.
+                        */
+                       struct hist_field       *track_var;
+
+                       check_track_val_fn_t    check_val;
+                       action_fn_t             save_data;
+               } track_data;
        };
 };
 
+struct track_data {
+       u64                             track_val;
+       bool                            updated;
+
+       unsigned int                    key_len;
+       void                            *key;
+       struct tracing_map_elt          elt;
+
+       struct action_data              *action_data;
+       struct hist_trigger_data        *hist_data;
+};
+
+struct hist_elt_data {
+       char *comm;
+       u64 *var_ref_vals;
+       char *field_var_str[SYNTH_FIELDS_MAX];
+};
+
+struct snapshot_context {
+       struct tracing_map_elt  *elt;
+       void                    *key;
+};
+
+static void track_data_free(struct track_data *track_data)
+{
+       struct hist_elt_data *elt_data;
+
+       if (!track_data)
+               return;
+
+       kfree(track_data->key);
+
+       elt_data = track_data->elt.private_data;
+       if (elt_data) {
+               kfree(elt_data->comm);
+               kfree(elt_data);
+       }
+
+       kfree(track_data);
+}
+
+static struct track_data *track_data_alloc(unsigned int key_len,
+                                          struct action_data *action_data,
+                                          struct hist_trigger_data *hist_data)
+{
+       struct track_data *data = kzalloc(sizeof(*data), GFP_KERNEL);
+       struct hist_elt_data *elt_data;
+
+       if (!data)
+               return ERR_PTR(-ENOMEM);
+
+       data->key = kzalloc(key_len, GFP_KERNEL);
+       if (!data->key) {
+               track_data_free(data);
+               return ERR_PTR(-ENOMEM);
+       }
+
+       data->key_len = key_len;
+       data->action_data = action_data;
+       data->hist_data = hist_data;
+
+       elt_data = kzalloc(sizeof(*elt_data), GFP_KERNEL);
+       if (!elt_data) {
+               track_data_free(data);
+               return ERR_PTR(-ENOMEM);
+       }
+       data->elt.private_data = elt_data;
+
+       elt_data->comm = kzalloc(TASK_COMM_LEN, GFP_KERNEL);
+       if (!elt_data->comm) {
+               track_data_free(data);
+               return ERR_PTR(-ENOMEM);
+       }
+
+       return data;
+}
 
 static char last_hist_cmd[MAX_FILTER_STR_VAL];
 static char hist_err_str[MAX_FILTER_STR_VAL];
@@ -1078,12 +1194,12 @@ static struct synth_event *alloc_synth_event(const char *name, int n_fields,
 
 static void action_trace(struct hist_trigger_data *hist_data,
                         struct tracing_map_elt *elt, void *rec,
-                        struct ring_buffer_event *rbe,
+                        struct ring_buffer_event *rbe, void *key,
                         struct action_data *data, u64 *var_ref_vals)
 {
-       struct synth_event *event = data->onmatch.synth_event;
+       struct synth_event *event = data->synth_event;
 
-       trace_synth(event, var_ref_vals, data->onmatch.var_ref_idx);
+       trace_synth(event, var_ref_vals, data->var_ref_idx);
 }
 
 struct hist_var_data {
@@ -1200,8 +1316,8 @@ static int synth_event_create(int argc, const char **argv)
 
        /* This interface accepts group name prefix */
        if (strchr(name, '/')) {
-               len = sizeof(SYNTH_SYSTEM "/") - 1;
-               if (strncmp(name, SYNTH_SYSTEM "/", len))
+               len = str_has_prefix(name, SYNTH_SYSTEM "/");
+               if (len == 0)
                        return -EINVAL;
                name += len;
        }
@@ -1644,9 +1760,9 @@ find_match_var(struct hist_trigger_data *hist_data, char *var_name)
        for (i = 0; i < hist_data->n_actions; i++) {
                struct action_data *data = hist_data->actions[i];
 
-               if (data->fn == action_trace) {
-                       char *system = data->onmatch.match_event_system;
-                       char *event_name = data->onmatch.match_event;
+               if (data->handler == HANDLER_ONMATCH) {
+                       char *system = data->match_data.event_system;
+                       char *event_name = data->match_data.event;
 
                        file = find_var_file(tr, system, event_name, var_name);
                        if (!file)
@@ -1691,12 +1807,6 @@ static struct hist_field *find_event_var(struct hist_trigger_data *hist_data,
        return hist_field;
 }
 
-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,
                              struct tracing_map_elt *elt,
                              struct ring_buffer_event *rbe,
@@ -1882,7 +1992,8 @@ static int parse_action(char *str, struct hist_trigger_attrs *attrs)
                return ret;
 
        if ((str_has_prefix(str, "onmatch(")) ||
-           (str_has_prefix(str, "onmax("))) {
+           (str_has_prefix(str, "onmax(")) ||
+           (str_has_prefix(str, "onchange("))) {
                attrs->action_str[attrs->n_actions] = kstrdup(str, GFP_KERNEL);
                if (!attrs->action_str[attrs->n_actions]) {
                        ret = -ENOMEM;
@@ -2030,7 +2141,7 @@ static inline void save_comm(char *comm, struct task_struct *task)
                return;
        }
 
-       memcpy(comm, task->comm, TASK_COMM_LEN);
+       strncpy(comm, task->comm, TASK_COMM_LEN);
 }
 
 static void hist_elt_data_free(struct hist_elt_data *elt_data)
@@ -2076,7 +2187,7 @@ static int hist_trigger_elt_data_alloc(struct tracing_map_elt *elt)
                }
        }
 
-       n_str = hist_data->n_field_var_str + hist_data->n_max_var_str;
+       n_str = hist_data->n_field_var_str + hist_data->n_save_var_str;
 
        size = STR_VAR_LEN_MAX;
 
@@ -3050,7 +3161,7 @@ create_field_var_hist(struct hist_trigger_data *target_hist_data,
        int ret;
 
        if (target_hist_data->n_field_var_hists >= SYNTH_FIELDS_MAX) {
-               hist_err_event("onmatch: Too many field variables defined: ",
+               hist_err_event("trace action: Too many field variables defined: ",
                               subsys_name, event_name, field_name);
                return ERR_PTR(-EINVAL);
        }
@@ -3058,7 +3169,7 @@ create_field_var_hist(struct hist_trigger_data *target_hist_data,
        file = event_file(tr, subsys_name, event_name);
 
        if (IS_ERR(file)) {
-               hist_err_event("onmatch: Event file not found: ",
+               hist_err_event("trace action: Event file not found: ",
                               subsys_name, event_name, field_name);
                ret = PTR_ERR(file);
                return ERR_PTR(ret);
@@ -3072,7 +3183,7 @@ create_field_var_hist(struct hist_trigger_data *target_hist_data,
         */
        hist_data = find_compatible_hist(target_hist_data, file);
        if (!hist_data) {
-               hist_err_event("onmatch: Matching event histogram not found: ",
+               hist_err_event("trace action: Matching event histogram not found: ",
                               subsys_name, event_name, field_name);
                return ERR_PTR(-EINVAL);
        }
@@ -3134,7 +3245,7 @@ create_field_var_hist(struct hist_trigger_data *target_hist_data,
                kfree(cmd);
                kfree(var_hist->cmd);
                kfree(var_hist);
-               hist_err_event("onmatch: Couldn't create histogram for field: ",
+               hist_err_event("trace action: Couldn't create histogram for field: ",
                               subsys_name, event_name, field_name);
                return ERR_PTR(ret);
        }
@@ -3147,7 +3258,7 @@ create_field_var_hist(struct hist_trigger_data *target_hist_data,
        if (IS_ERR_OR_NULL(event_var)) {
                kfree(var_hist->cmd);
                kfree(var_hist);
-               hist_err_event("onmatch: Couldn't find synthetic variable: ",
+               hist_err_event("trace action: Couldn't find synthetic variable: ",
                               subsys_name, event_name, field_name);
                return ERR_PTR(-EINVAL);
        }
@@ -3225,13 +3336,13 @@ static void update_field_vars(struct hist_trigger_data *hist_data,
                            hist_data->n_field_vars, 0);
 }
 
-static void update_max_vars(struct hist_trigger_data *hist_data,
-                           struct tracing_map_elt *elt,
-                           struct ring_buffer_event *rbe,
-                           void *rec)
+static void save_track_data_vars(struct hist_trigger_data *hist_data,
+                                struct tracing_map_elt *elt, void *rec,
+                                struct ring_buffer_event *rbe, void *key,
+                                struct action_data *data, u64 *var_ref_vals)
 {
-       __update_field_vars(elt, rbe, rec, hist_data->max_vars,
-                           hist_data->n_max_vars, hist_data->n_field_var_str);
+       __update_field_vars(elt, rbe, rec, hist_data->save_vars,
+                           hist_data->n_save_vars, hist_data->n_field_var_str);
 }
 
 static struct hist_field *create_var(struct hist_trigger_data *hist_data,
@@ -3366,18 +3477,190 @@ create_target_field_var(struct hist_trigger_data *target_hist_data,
        return create_field_var(target_hist_data, file, var_name);
 }
 
-static void onmax_print(struct seq_file *m,
-                       struct hist_trigger_data *hist_data,
-                       struct tracing_map_elt *elt,
-                       struct action_data *data)
+static bool check_track_val_max(u64 track_val, u64 var_val)
 {
-       unsigned int i, save_var_idx, max_idx = data->onmax.max_var->var.idx;
+       if (var_val <= track_val)
+               return false;
+
+       return true;
+}
 
-       seq_printf(m, "\n\tmax: %10llu", tracing_map_read_var(elt, max_idx));
+static bool check_track_val_changed(u64 track_val, u64 var_val)
+{
+       if (var_val == track_val)
+               return false;
+
+       return true;
+}
+
+static u64 get_track_val(struct hist_trigger_data *hist_data,
+                        struct tracing_map_elt *elt,
+                        struct action_data *data)
+{
+       unsigned int track_var_idx = data->track_data.track_var->var.idx;
+       u64 track_val;
+
+       track_val = tracing_map_read_var(elt, track_var_idx);
+
+       return track_val;
+}
+
+static void save_track_val(struct hist_trigger_data *hist_data,
+                          struct tracing_map_elt *elt,
+                          struct action_data *data, u64 var_val)
+{
+       unsigned int track_var_idx = data->track_data.track_var->var.idx;
+
+       tracing_map_set_var(elt, track_var_idx, var_val);
+}
+
+static void save_track_data(struct hist_trigger_data *hist_data,
+                           struct tracing_map_elt *elt, void *rec,
+                           struct ring_buffer_event *rbe, void *key,
+                           struct action_data *data, u64 *var_ref_vals)
+{
+       if (data->track_data.save_data)
+               data->track_data.save_data(hist_data, elt, rec, rbe, key, data, var_ref_vals);
+}
+
+static bool check_track_val(struct tracing_map_elt *elt,
+                           struct action_data *data,
+                           u64 var_val)
+{
+       struct hist_trigger_data *hist_data;
+       u64 track_val;
+
+       hist_data = data->track_data.track_var->hist_data;
+       track_val = get_track_val(hist_data, elt, data);
+
+       return data->track_data.check_val(track_val, var_val);
+}
 
-       for (i = 0; i < hist_data->n_max_vars; i++) {
-               struct hist_field *save_val = hist_data->max_vars[i]->val;
-               struct hist_field *save_var = hist_data->max_vars[i]->var;
+#ifdef CONFIG_TRACER_SNAPSHOT
+static bool cond_snapshot_update(struct trace_array *tr, void *cond_data)
+{
+       /* called with tr->max_lock held */
+       struct track_data *track_data = tr->cond_snapshot->cond_data;
+       struct hist_elt_data *elt_data, *track_elt_data;
+       struct snapshot_context *context = cond_data;
+       u64 track_val;
+
+       if (!track_data)
+               return false;
+
+       track_val = get_track_val(track_data->hist_data, context->elt,
+                                 track_data->action_data);
+
+       track_data->track_val = track_val;
+       memcpy(track_data->key, context->key, track_data->key_len);
+
+       elt_data = context->elt->private_data;
+       track_elt_data = track_data->elt.private_data;
+       if (elt_data->comm)
+               strncpy(track_elt_data->comm, elt_data->comm, TASK_COMM_LEN);
+
+       track_data->updated = true;
+
+       return true;
+}
+
+static void save_track_data_snapshot(struct hist_trigger_data *hist_data,
+                                    struct tracing_map_elt *elt, void *rec,
+                                    struct ring_buffer_event *rbe, void *key,
+                                    struct action_data *data,
+                                    u64 *var_ref_vals)
+{
+       struct trace_event_file *file = hist_data->event_file;
+       struct snapshot_context context;
+
+       context.elt = elt;
+       context.key = key;
+
+       tracing_snapshot_cond(file->tr, &context);
+}
+
+static void hist_trigger_print_key(struct seq_file *m,
+                                  struct hist_trigger_data *hist_data,
+                                  void *key,
+                                  struct tracing_map_elt *elt);
+
+static struct action_data *snapshot_action(struct hist_trigger_data *hist_data)
+{
+       unsigned int i;
+
+       if (!hist_data->n_actions)
+               return NULL;
+
+       for (i = 0; i < hist_data->n_actions; i++) {
+               struct action_data *data = hist_data->actions[i];
+
+               if (data->action == ACTION_SNAPSHOT)
+                       return data;
+       }
+
+       return NULL;
+}
+
+static void track_data_snapshot_print(struct seq_file *m,
+                                     struct hist_trigger_data *hist_data)
+{
+       struct trace_event_file *file = hist_data->event_file;
+       struct track_data *track_data;
+       struct action_data *action;
+
+       track_data = tracing_cond_snapshot_data(file->tr);
+       if (!track_data)
+               return;
+
+       if (!track_data->updated)
+               return;
+
+       action = snapshot_action(hist_data);
+       if (!action)
+               return;
+
+       seq_puts(m, "\nSnapshot taken (see tracing/snapshot).  Details:\n");
+       seq_printf(m, "\ttriggering value { %s(%s) }: %10llu",
+                  action->handler == HANDLER_ONMAX ? "onmax" : "onchange",
+                  action->track_data.var_str, track_data->track_val);
+
+       seq_puts(m, "\ttriggered by event with key: ");
+       hist_trigger_print_key(m, hist_data, track_data->key, &track_data->elt);
+       seq_putc(m, '\n');
+}
+#else
+static bool cond_snapshot_update(struct trace_array *tr, void *cond_data)
+{
+       return false;
+}
+static void save_track_data_snapshot(struct hist_trigger_data *hist_data,
+                                    struct tracing_map_elt *elt, void *rec,
+                                    struct ring_buffer_event *rbe, void *key,
+                                    struct action_data *data,
+                                    u64 *var_ref_vals) {}
+static void track_data_snapshot_print(struct seq_file *m,
+                                     struct hist_trigger_data *hist_data) {}
+#endif /* CONFIG_TRACER_SNAPSHOT */
+
+static void track_data_print(struct seq_file *m,
+                            struct hist_trigger_data *hist_data,
+                            struct tracing_map_elt *elt,
+                            struct action_data *data)
+{
+       u64 track_val = get_track_val(hist_data, elt, data);
+       unsigned int i, save_var_idx;
+
+       if (data->handler == HANDLER_ONMAX)
+               seq_printf(m, "\n\tmax: %10llu", track_val);
+       else if (data->handler == HANDLER_ONCHANGE)
+               seq_printf(m, "\n\tchanged: %10llu", track_val);
+
+       if (data->action == ACTION_SNAPSHOT)
+               return;
+
+       for (i = 0; i < hist_data->n_save_vars; i++) {
+               struct hist_field *save_val = hist_data->save_vars[i]->val;
+               struct hist_field *save_var = hist_data->save_vars[i]->var;
                u64 val;
 
                save_var_idx = save_var->var.idx;
@@ -3392,64 +3675,82 @@ static void onmax_print(struct seq_file *m,
        }
 }
 
-static void onmax_save(struct hist_trigger_data *hist_data,
-                      struct tracing_map_elt *elt, void *rec,
-                      struct ring_buffer_event *rbe,
-                      struct action_data *data, u64 *var_ref_vals)
+static void ontrack_action(struct hist_trigger_data *hist_data,
+                          struct tracing_map_elt *elt, void *rec,
+                          struct ring_buffer_event *rbe, void *key,
+                          struct action_data *data, u64 *var_ref_vals)
 {
-       unsigned int max_idx = data->onmax.max_var->var.idx;
-       unsigned int max_var_ref_idx = data->onmax.max_var_ref_idx;
+       u64 var_val = var_ref_vals[data->track_data.var_ref->var_ref_idx];
 
-       u64 var_val, max_val;
-
-       var_val = var_ref_vals[max_var_ref_idx];
-       max_val = tracing_map_read_var(elt, max_idx);
-
-       if (var_val <= max_val)
-               return;
-
-       tracing_map_set_var(elt, max_idx, var_val);
-
-       update_max_vars(hist_data, elt, rbe, rec);
+       if (check_track_val(elt, data, var_val)) {
+               save_track_val(hist_data, elt, data, var_val);
+               save_track_data(hist_data, elt, rec, rbe, key, data, var_ref_vals);
+       }
 }
 
-static void onmax_destroy(struct action_data *data)
+static void action_data_destroy(struct action_data *data)
 {
        unsigned int i;
 
-       destroy_hist_field(data->onmax.max_var, 0);
-       destroy_hist_field(data->onmax.var, 0);
+       lockdep_assert_held(&event_mutex);
 
-       kfree(data->onmax.var_str);
-       kfree(data->onmax.fn_name);
+       kfree(data->action_name);
 
        for (i = 0; i < data->n_params; i++)
                kfree(data->params[i]);
 
+       if (data->synth_event)
+               data->synth_event->ref--;
+
+       kfree(data->synth_event_name);
+
        kfree(data);
 }
 
-static int onmax_create(struct hist_trigger_data *hist_data,
-                       struct action_data *data)
+static void track_data_destroy(struct hist_trigger_data *hist_data,
+                              struct action_data *data)
 {
        struct trace_event_file *file = hist_data->event_file;
-       struct hist_field *var_field, *ref_field, *max_var;
-       unsigned int var_ref_idx = hist_data->n_var_refs;
-       struct field_var *field_var;
-       char *onmax_var_str, *param;
-       unsigned int i;
+
+       destroy_hist_field(data->track_data.track_var, 0);
+       destroy_hist_field(data->track_data.var_ref, 0);
+
+       if (data->action == ACTION_SNAPSHOT) {
+               struct track_data *track_data;
+
+               track_data = tracing_cond_snapshot_data(file->tr);
+               if (track_data && track_data->hist_data == hist_data) {
+                       tracing_snapshot_cond_disable(file->tr);
+                       track_data_free(track_data);
+               }
+       }
+
+       kfree(data->track_data.var_str);
+
+       action_data_destroy(data);
+}
+
+static int action_create(struct hist_trigger_data *hist_data,
+                        struct action_data *data);
+
+static int track_data_create(struct hist_trigger_data *hist_data,
+                            struct action_data *data)
+{
+       struct hist_field *var_field, *ref_field, *track_var = NULL;
+       struct trace_event_file *file = hist_data->event_file;
+       char *track_data_var_str;
        int ret = 0;
 
-       onmax_var_str = data->onmax.var_str;
-       if (onmax_var_str[0] != '$') {
-               hist_err("onmax: For onmax(x), x must be a variable: ", onmax_var_str);
+       track_data_var_str = data->track_data.var_str;
+       if (track_data_var_str[0] != '$') {
+               hist_err("For onmax(x) or onchange(x), x must be a variable: ", track_data_var_str);
                return -EINVAL;
        }
-       onmax_var_str++;
+       track_data_var_str++;
 
-       var_field = find_target_event_var(hist_data, NULL, NULL, onmax_var_str);
+       var_field = find_target_event_var(hist_data, NULL, NULL, track_data_var_str);
        if (!var_field) {
-               hist_err("onmax: Couldn't find onmax variable: ", onmax_var_str);
+               hist_err("Couldn't find onmax or onchange variable: ", track_data_var_str);
                return -EINVAL;
        }
 
@@ -3457,39 +3758,26 @@ static int onmax_create(struct hist_trigger_data *hist_data,
        if (!ref_field)
                return -ENOMEM;
 
-       data->onmax.var = ref_field;
+       data->track_data.var_ref = ref_field;
 
-       data->fn = onmax_save;
-       data->onmax.max_var_ref_idx = var_ref_idx;
-       max_var = create_var(hist_data, file, "max", sizeof(u64), "u64");
-       if (IS_ERR(max_var)) {
-               hist_err("onmax: Couldn't create onmax variable: ", "max");
-               ret = PTR_ERR(max_var);
+       if (data->handler == HANDLER_ONMAX)
+               track_var = create_var(hist_data, file, "__max", sizeof(u64), "u64");
+       if (IS_ERR(track_var)) {
+               hist_err("Couldn't create onmax variable: ", "__max");
+               ret = PTR_ERR(track_var);
                goto out;
        }
-       data->onmax.max_var = max_var;
-
-       for (i = 0; i < data->n_params; i++) {
-               param = kstrdup(data->params[i], GFP_KERNEL);
-               if (!param) {
-                       ret = -ENOMEM;
-                       goto out;
-               }
-
-               field_var = create_target_field_var(hist_data, NULL, NULL, param);
-               if (IS_ERR(field_var)) {
-                       hist_err("onmax: Couldn't create field variable: ", param);
-                       ret = PTR_ERR(field_var);
-                       kfree(param);
-                       goto out;
-               }
-
-               hist_data->max_vars[hist_data->n_max_vars++] = field_var;
-               if (field_var->val->flags & HIST_FIELD_FL_STRING)
-                       hist_data->n_max_var_str++;
 
-               kfree(param);
+       if (data->handler == HANDLER_ONCHANGE)
+               track_var = create_var(hist_data, file, "__change", sizeof(u64), "u64");
+       if (IS_ERR(track_var)) {
+               hist_err("Couldn't create onchange variable: ", "__change");
+               ret = PTR_ERR(track_var);
+               goto out;
        }
+       data->track_data.track_var = track_var;
+
+       ret = action_create(hist_data, data);
  out:
        return ret;
 }
@@ -3497,14 +3785,18 @@ static int onmax_create(struct hist_trigger_data *hist_data,
 static int parse_action_params(char *params, struct action_data *data)
 {
        char *param, *saved_param;
+       bool first_param = true;
        int ret = 0;
 
        while (params) {
-               if (data->n_params >= SYNTH_FIELDS_MAX)
+               if (data->n_params >= SYNTH_FIELDS_MAX) {
+                       hist_err("Too many action params", "");
                        goto out;
+               }
 
                param = strsep(&params, ",");
                if (!param) {
+                       hist_err("No action param found", "");
                        ret = -EINVAL;
                        goto out;
                }
@@ -3522,86 +3814,164 @@ static int parse_action_params(char *params, struct action_data *data)
                        goto out;
                }
 
+               if (first_param && data->use_trace_keyword) {
+                       data->synth_event_name = saved_param;
+                       first_param = false;
+                       continue;
+               }
+               first_param = false;
+
                data->params[data->n_params++] = saved_param;
        }
  out:
        return ret;
 }
 
-static struct action_data *onmax_parse(char *str)
+static int action_parse(char *str, struct action_data *data,
+                       enum handler_id handler)
 {
-       char *onmax_fn_name, *onmax_var_str;
-       struct action_data *data;
-       int ret = -EINVAL;
-
-       data = kzalloc(sizeof(*data), GFP_KERNEL);
-       if (!data)
-               return ERR_PTR(-ENOMEM);
+       char *action_name;
+       int ret = 0;
 
-       onmax_var_str = strsep(&str, ")");
-       if (!onmax_var_str || !str) {
+       strsep(&str, ".");
+       if (!str) {
+               hist_err("action parsing: No action found", "");
                ret = -EINVAL;
-               goto free;
+               goto out;
        }
 
-       data->onmax.var_str = kstrdup(onmax_var_str, GFP_KERNEL);
-       if (!data->onmax.var_str) {
-               ret = -ENOMEM;
-               goto free;
+       action_name = strsep(&str, "(");
+       if (!action_name || !str) {
+               hist_err("action parsing: No action found", "");
+               ret = -EINVAL;
+               goto out;
        }
 
-       strsep(&str, ".");
-       if (!str)
-               goto free;
-
-       onmax_fn_name = strsep(&str, "(");
-       if (!onmax_fn_name || !str)
-               goto free;
-
-       if (str_has_prefix(onmax_fn_name, "save")) {
+       if (str_has_prefix(action_name, "save")) {
                char *params = strsep(&str, ")");
 
                if (!params) {
+                       hist_err("action parsing: No params found for %s", "save");
                        ret = -EINVAL;
-                       goto free;
+                       goto out;
                }
 
                ret = parse_action_params(params, data);
                if (ret)
-                       goto free;
-       } else
+                       goto out;
+
+               if (handler == HANDLER_ONMAX)
+                       data->track_data.check_val = check_track_val_max;
+               else if (handler == HANDLER_ONCHANGE)
+                       data->track_data.check_val = check_track_val_changed;
+               else {
+                       hist_err("action parsing: Handler doesn't support action: ", action_name);
+                       ret = -EINVAL;
+                       goto out;
+               }
+
+               data->track_data.save_data = save_track_data_vars;
+               data->fn = ontrack_action;
+               data->action = ACTION_SAVE;
+       } else if (str_has_prefix(action_name, "snapshot")) {
+               char *params = strsep(&str, ")");
+
+               if (!str) {
+                       hist_err("action parsing: No closing paren found: %s", params);
+                       ret = -EINVAL;
+                       goto out;
+               }
+
+               if (handler == HANDLER_ONMAX)
+                       data->track_data.check_val = check_track_val_max;
+               else if (handler == HANDLER_ONCHANGE)
+                       data->track_data.check_val = check_track_val_changed;
+               else {
+                       hist_err("action parsing: Handler doesn't support action: ", action_name);
+                       ret = -EINVAL;
+                       goto out;
+               }
+
+               data->track_data.save_data = save_track_data_snapshot;
+               data->fn = ontrack_action;
+               data->action = ACTION_SNAPSHOT;
+       } else {
+               char *params = strsep(&str, ")");
+
+               if (str_has_prefix(action_name, "trace"))
+                       data->use_trace_keyword = true;
+
+               if (params) {
+                       ret = parse_action_params(params, data);
+                       if (ret)
+                               goto out;
+               }
+
+               if (handler == HANDLER_ONMAX)
+                       data->track_data.check_val = check_track_val_max;
+               else if (handler == HANDLER_ONCHANGE)
+                       data->track_data.check_val = check_track_val_changed;
+
+               if (handler != HANDLER_ONMATCH) {
+                       data->track_data.save_data = action_trace;
+                       data->fn = ontrack_action;
+               } else
+                       data->fn = action_trace;
+
+               data->action = ACTION_TRACE;
+       }
+
+       data->action_name = kstrdup(action_name, GFP_KERNEL);
+       if (!data->action_name) {
+               ret = -ENOMEM;
+               goto out;
+       }
+
+       data->handler = handler;
+ out:
+       return ret;
+}
+
+static struct action_data *track_data_parse(struct hist_trigger_data *hist_data,
+                                           char *str, enum handler_id handler)
+{
+       struct action_data *data;
+       int ret = -EINVAL;
+       char *var_str;
+
+       data = kzalloc(sizeof(*data), GFP_KERNEL);
+       if (!data)
+               return ERR_PTR(-ENOMEM);
+
+       var_str = strsep(&str, ")");
+       if (!var_str || !str) {
+               ret = -EINVAL;
                goto free;
+       }
 
-       data->onmax.fn_name = kstrdup(onmax_fn_name, GFP_KERNEL);
-       if (!data->onmax.fn_name) {
+       data->track_data.var_str = kstrdup(var_str, GFP_KERNEL);
+       if (!data->track_data.var_str) {
                ret = -ENOMEM;
                goto free;
        }
+
+       ret = action_parse(str, data, handler);
+       if (ret)
+               goto free;
  out:
        return data;
  free:
-       onmax_destroy(data);
+       track_data_destroy(hist_data, data);
        data = ERR_PTR(ret);
        goto out;
 }
 
 static void onmatch_destroy(struct action_data *data)
 {
-       unsigned int i;
-
-       lockdep_assert_held(&event_mutex);
-
-       kfree(data->onmatch.match_event);
-       kfree(data->onmatch.match_event_system);
-       kfree(data->onmatch.synth_event_name);
-
-       for (i = 0; i < data->n_params; i++)
-               kfree(data->params[i]);
+       kfree(data->match_data.event);
+       kfree(data->match_data.event_system);
 
-       if (data->onmatch.synth_event)
-               data->onmatch.synth_event->ref--;
-
-       kfree(data);
+       action_data_destroy(data);
 }
 
 static void destroy_field_var(struct field_var *field_var)
@@ -3651,8 +4021,9 @@ static int check_synth_field(struct synth_event *event,
 }
 
 static struct hist_field *
-onmatch_find_var(struct hist_trigger_data *hist_data, struct action_data *data,
-                char *system, char *event, char *var)
+trace_action_find_var(struct hist_trigger_data *hist_data,
+                     struct action_data *data,
+                     char *system, char *event, char *var)
 {
        struct hist_field *hist_field;
 
@@ -3660,24 +4031,24 @@ onmatch_find_var(struct hist_trigger_data *hist_data, struct action_data *data,
 
        hist_field = find_target_event_var(hist_data, system, event, var);
        if (!hist_field) {
-               if (!system) {
-                       system = data->onmatch.match_event_system;
-                       event = data->onmatch.match_event;
+               if (!system && data->handler == HANDLER_ONMATCH) {
+                       system = data->match_data.event_system;
+                       event = data->match_data.event;
                }
 
                hist_field = find_event_var(hist_data, system, event, var);
        }
 
        if (!hist_field)
-               hist_err_event("onmatch: Couldn't find onmatch param: $", system, event, var);
+               hist_err_event("trace action: Couldn't find param: $", system, event, var);
 
        return hist_field;
 }
 
 static struct hist_field *
-onmatch_create_field_var(struct hist_trigger_data *hist_data,
-                        struct action_data *data, char *system,
-                        char *event, char *var)
+trace_action_create_field_var(struct hist_trigger_data *hist_data,
+                             struct action_data *data, char *system,
+                             char *event, char *var)
 {
        struct hist_field *hist_field = NULL;
        struct field_var *field_var;
@@ -3700,9 +4071,9 @@ onmatch_create_field_var(struct hist_trigger_data *hist_data,
                 * looking for fields on the onmatch(system.event.xxx)
                 * event.
                 */
-               if (!system) {
-                       system = data->onmatch.match_event_system;
-                       event = data->onmatch.match_event;
+               if (!system && data->handler == HANDLER_ONMATCH) {
+                       system = data->match_data.event_system;
+                       event = data->match_data.event;
                }
 
                /*
@@ -3724,24 +4095,30 @@ onmatch_create_field_var(struct hist_trigger_data *hist_data,
        goto out;
 }
 
-static int onmatch_create(struct hist_trigger_data *hist_data,
-                         struct trace_event_file *file,
-                         struct action_data *data)
+static int trace_action_create(struct hist_trigger_data *hist_data,
+                              struct action_data *data)
 {
        char *event_name, *param, *system = NULL;
        struct hist_field *hist_field, *var_ref;
        unsigned int i, var_ref_idx;
        unsigned int field_pos = 0;
        struct synth_event *event;
+       char *synth_event_name;
        int ret = 0;
 
        lockdep_assert_held(&event_mutex);
 
-       event = find_synth_event(data->onmatch.synth_event_name);
+       if (data->use_trace_keyword)
+               synth_event_name = data->synth_event_name;
+       else
+               synth_event_name = data->action_name;
+
+       event = find_synth_event(synth_event_name);
        if (!event) {
-               hist_err("onmatch: Couldn't find synthetic event: ", data->onmatch.synth_event_name);
+               hist_err("trace action: Couldn't find synthetic event: ", synth_event_name);
                return -EINVAL;
        }
+
        event->ref++;
 
        var_ref_idx = hist_data->n_var_refs;
@@ -3769,13 +4146,15 @@ static int onmatch_create(struct hist_trigger_data *hist_data,
                }
 
                if (param[0] == '$')
-                       hist_field = onmatch_find_var(hist_data, data, system,
-                                                     event_name, param);
+                       hist_field = trace_action_find_var(hist_data, data,
+                                                          system, event_name,
+                                                          param);
                else
-                       hist_field = onmatch_create_field_var(hist_data, data,
-                                                             system,
-                                                             event_name,
-                                                             param);
+                       hist_field = trace_action_create_field_var(hist_data,
+                                                                  data,
+                                                                  system,
+                                                                  event_name,
+                                                                  param);
 
                if (!hist_field) {
                        kfree(p);
@@ -3797,7 +4176,7 @@ static int onmatch_create(struct hist_trigger_data *hist_data,
                        continue;
                }
 
-               hist_err_event("onmatch: Param type doesn't match synthetic event field type: ",
+               hist_err_event("trace action: Param type doesn't match synthetic event field type: ",
                               system, event_name, param);
                kfree(p);
                ret = -EINVAL;
@@ -3805,14 +4184,13 @@ static int onmatch_create(struct hist_trigger_data *hist_data,
        }
 
        if (field_pos != event->n_fields) {
-               hist_err("onmatch: Param count doesn't match synthetic event field count: ", event->name);
+               hist_err("trace action: Param count doesn't match synthetic event field count: ", event->name);
                ret = -EINVAL;
                goto err;
        }
 
-       data->fn = action_trace;
-       data->onmatch.synth_event = event;
-       data->onmatch.var_ref_idx = var_ref_idx;
+       data->synth_event = event;
+       data->var_ref_idx = var_ref_idx;
  out:
        return ret;
  err:
@@ -3821,10 +4199,75 @@ static int onmatch_create(struct hist_trigger_data *hist_data,
        goto out;
 }
 
+static int action_create(struct hist_trigger_data *hist_data,
+                        struct action_data *data)
+{
+       struct trace_event_file *file = hist_data->event_file;
+       struct track_data *track_data;
+       struct field_var *field_var;
+       unsigned int i;
+       char *param;
+       int ret = 0;
+
+       if (data->action == ACTION_TRACE)
+               return trace_action_create(hist_data, data);
+
+       if (data->action == ACTION_SNAPSHOT) {
+               track_data = track_data_alloc(hist_data->key_size, data, hist_data);
+               if (IS_ERR(track_data)) {
+                       ret = PTR_ERR(track_data);
+                       goto out;
+               }
+
+               ret = tracing_snapshot_cond_enable(file->tr, track_data,
+                                                  cond_snapshot_update);
+               if (ret)
+                       track_data_free(track_data);
+
+               goto out;
+       }
+
+       if (data->action == ACTION_SAVE) {
+               if (hist_data->n_save_vars) {
+                       ret = -EEXIST;
+                       hist_err("save action: Can't have more than one save() action per hist", "");
+                       goto out;
+               }
+
+               for (i = 0; i < data->n_params; i++) {
+                       param = kstrdup(data->params[i], GFP_KERNEL);
+                       if (!param) {
+                               ret = -ENOMEM;
+                               goto out;
+                       }
+
+                       field_var = create_target_field_var(hist_data, NULL, NULL, param);
+                       if (IS_ERR(field_var)) {
+                               hist_err("save action: Couldn't create field variable: ", param);
+                               ret = PTR_ERR(field_var);
+                               kfree(param);
+                               goto out;
+                       }
+
+                       hist_data->save_vars[hist_data->n_save_vars++] = field_var;
+                       if (field_var->val->flags & HIST_FIELD_FL_STRING)
+                               hist_data->n_save_var_str++;
+                       kfree(param);
+               }
+       }
+ out:
+       return ret;
+}
+
+static int onmatch_create(struct hist_trigger_data *hist_data,
+                         struct action_data *data)
+{
+       return action_create(hist_data, data);
+}
+
 static struct action_data *onmatch_parse(struct trace_array *tr, char *str)
 {
        char *match_event, *match_event_system;
-       char *synth_event_name, *params;
        struct action_data *data;
        int ret = -EINVAL;
 
@@ -3850,43 +4293,19 @@ static struct action_data *onmatch_parse(struct trace_array *tr, char *str)
                goto free;
        }
 
-       data->onmatch.match_event = kstrdup(match_event, GFP_KERNEL);
-       if (!data->onmatch.match_event) {
-               ret = -ENOMEM;
-               goto free;
-       }
-
-       data->onmatch.match_event_system = kstrdup(match_event_system, GFP_KERNEL);
-       if (!data->onmatch.match_event_system) {
+       data->match_data.event = kstrdup(match_event, GFP_KERNEL);
+       if (!data->match_data.event) {
                ret = -ENOMEM;
                goto free;
        }
 
-       strsep(&str, ".");
-       if (!str) {
-               hist_err("onmatch: Missing . after onmatch(): ", str);
-               goto free;
-       }
-
-       synth_event_name = strsep(&str, "(");
-       if (!synth_event_name || !str) {
-               hist_err("onmatch: Missing opening paramlist paren: ", synth_event_name);
-               goto free;
-       }
-
-       data->onmatch.synth_event_name = kstrdup(synth_event_name, GFP_KERNEL);
-       if (!data->onmatch.synth_event_name) {
+       data->match_data.event_system = kstrdup(match_event_system, GFP_KERNEL);
+       if (!data->match_data.event_system) {
                ret = -ENOMEM;
                goto free;
        }
 
-       params = strsep(&str, ")");
-       if (!params || !str || (str && strlen(str))) {
-               hist_err("onmatch: Missing closing paramlist paren: ", params);
-               goto free;
-       }
-
-       ret = parse_action_params(params, data);
+       ret = action_parse(str, data, HANDLER_ONMATCH);
        if (ret)
                goto free;
  out:
@@ -4326,10 +4745,11 @@ static void destroy_actions(struct hist_trigger_data *hist_data)
        for (i = 0; i < hist_data->n_actions; i++) {
                struct action_data *data = hist_data->actions[i];
 
-               if (data->fn == action_trace)
+               if (data->handler == HANDLER_ONMATCH)
                        onmatch_destroy(data);
-               else if (data->fn == onmax_save)
-                       onmax_destroy(data);
+               else if (data->handler == HANDLER_ONMAX ||
+                        data->handler == HANDLER_ONCHANGE)
+                       track_data_destroy(hist_data, data);
                else
                        kfree(data);
        }
@@ -4355,16 +4775,24 @@ static int parse_actions(struct hist_trigger_data *hist_data)
                                ret = PTR_ERR(data);
                                break;
                        }
-                       data->fn = action_trace;
                } else if ((len = str_has_prefix(str, "onmax("))) {
                        char *action_str = str + len;
 
-                       data = onmax_parse(action_str);
+                       data = track_data_parse(hist_data, action_str,
+                                               HANDLER_ONMAX);
+                       if (IS_ERR(data)) {
+                               ret = PTR_ERR(data);
+                               break;
+                       }
+               } else if ((len = str_has_prefix(str, "onchange("))) {
+                       char *action_str = str + len;
+
+                       data = track_data_parse(hist_data, action_str,
+                                               HANDLER_ONCHANGE);
                        if (IS_ERR(data)) {
                                ret = PTR_ERR(data);
                                break;
                        }
-                       data->fn = onmax_save;
                } else {
                        ret = -EINVAL;
                        break;
@@ -4376,8 +4804,7 @@ static int parse_actions(struct hist_trigger_data *hist_data)
        return ret;
 }
 
-static int create_actions(struct hist_trigger_data *hist_data,
-                         struct trace_event_file *file)
+static int create_actions(struct hist_trigger_data *hist_data)
 {
        struct action_data *data;
        unsigned int i;
@@ -4386,14 +4813,18 @@ static int create_actions(struct hist_trigger_data *hist_data,
        for (i = 0; i < hist_data->attrs->n_actions; i++) {
                data = hist_data->actions[i];
 
-               if (data->fn == action_trace) {
-                       ret = onmatch_create(hist_data, file, data);
+               if (data->handler == HANDLER_ONMATCH) {
+                       ret = onmatch_create(hist_data, data);
                        if (ret)
-                               return ret;
-               } else if (data->fn == onmax_save) {
-                       ret = onmax_create(hist_data, data);
+                               break;
+               } else if (data->handler == HANDLER_ONMAX ||
+                          data->handler == HANDLER_ONCHANGE) {
+                       ret = track_data_create(hist_data, data);
                        if (ret)
-                               return ret;
+                               break;
+               } else {
+                       ret = -EINVAL;
+                       break;
                }
        }
 
@@ -4409,26 +4840,51 @@ static void print_actions(struct seq_file *m,
        for (i = 0; i < hist_data->n_actions; i++) {
                struct action_data *data = hist_data->actions[i];
 
-               if (data->fn == onmax_save)
-                       onmax_print(m, hist_data, elt, data);
+               if (data->action == ACTION_SNAPSHOT)
+                       continue;
+
+               if (data->handler == HANDLER_ONMAX ||
+                   data->handler == HANDLER_ONCHANGE)
+                       track_data_print(m, hist_data, elt, data);
        }
 }
 
-static void print_onmax_spec(struct seq_file *m,
-                            struct hist_trigger_data *hist_data,
-                            struct action_data *data)
+static void print_action_spec(struct seq_file *m,
+                             struct hist_trigger_data *hist_data,
+                             struct action_data *data)
 {
        unsigned int i;
 
-       seq_puts(m, ":onmax(");
-       seq_printf(m, "%s", data->onmax.var_str);
-       seq_printf(m, ").%s(", data->onmax.fn_name);
-
-       for (i = 0; i < hist_data->n_max_vars; i++) {
-               seq_printf(m, "%s", hist_data->max_vars[i]->var->var.name);
-               if (i < hist_data->n_max_vars - 1)
-                       seq_puts(m, ",");
+       if (data->action == ACTION_SAVE) {
+               for (i = 0; i < hist_data->n_save_vars; i++) {
+                       seq_printf(m, "%s", hist_data->save_vars[i]->var->var.name);
+                       if (i < hist_data->n_save_vars - 1)
+                               seq_puts(m, ",");
+               }
+       } else if (data->action == ACTION_TRACE) {
+               if (data->use_trace_keyword)
+                       seq_printf(m, "%s", data->synth_event_name);
+               for (i = 0; i < data->n_params; i++) {
+                       if (i || data->use_trace_keyword)
+                               seq_puts(m, ",");
+                       seq_printf(m, "%s", data->params[i]);
+               }
        }
+}
+
+static void print_track_data_spec(struct seq_file *m,
+                                 struct hist_trigger_data *hist_data,
+                                 struct action_data *data)
+{
+       if (data->handler == HANDLER_ONMAX)
+               seq_puts(m, ":onmax(");
+       else if (data->handler == HANDLER_ONCHANGE)
+               seq_puts(m, ":onchange(");
+       seq_printf(m, "%s", data->track_data.var_str);
+       seq_printf(m, ").%s(", data->action_name);
+
+       print_action_spec(m, hist_data, data);
+
        seq_puts(m, ")");
 }
 
@@ -4436,18 +4892,12 @@ static void print_onmatch_spec(struct seq_file *m,
                               struct hist_trigger_data *hist_data,
                               struct action_data *data)
 {
-       unsigned int i;
-
-       seq_printf(m, ":onmatch(%s.%s).", data->onmatch.match_event_system,
-                  data->onmatch.match_event);
+       seq_printf(m, ":onmatch(%s.%s).", data->match_data.event_system,
+                  data->match_data.event);
 
-       seq_printf(m, "%s(", data->onmatch.synth_event->name);
+       seq_printf(m, "%s(", data->action_name);
 
-       for (i = 0; i < data->n_params; i++) {
-               if (i)
-                       seq_puts(m, ",");
-               seq_printf(m, "%s", data->params[i]);
-       }
+       print_action_spec(m, hist_data, data);
 
        seq_puts(m, ")");
 }
@@ -4463,8 +4913,11 @@ static bool actions_match(struct hist_trigger_data *hist_data,
        for (i = 0; i < hist_data->n_actions; i++) {
                struct action_data *data = hist_data->actions[i];
                struct action_data *data_test = hist_data_test->actions[i];
+               char *action_name, *action_name_test;
 
-               if (data->fn != data_test->fn)
+               if (data->handler != data_test->handler)
+                       return false;
+               if (data->action != data_test->action)
                        return false;
 
                if (data->n_params != data_test->n_params)
@@ -4475,22 +4928,30 @@ static bool actions_match(struct hist_trigger_data *hist_data,
                                return false;
                }
 
-               if (data->fn == action_trace) {
-                       if (strcmp(data->onmatch.synth_event_name,
-                                  data_test->onmatch.synth_event_name) != 0)
-                               return false;
-                       if (strcmp(data->onmatch.match_event_system,
-                                  data_test->onmatch.match_event_system) != 0)
-                               return false;
-                       if (strcmp(data->onmatch.match_event,
-                                  data_test->onmatch.match_event) != 0)
+               if (data->use_trace_keyword)
+                       action_name = data->synth_event_name;
+               else
+                       action_name = data->action_name;
+
+               if (data_test->use_trace_keyword)
+                       action_name_test = data_test->synth_event_name;
+               else
+                       action_name_test = data_test->action_name;
+
+               if (strcmp(action_name, action_name_test) != 0)
+                       return false;
+
+               if (data->handler == HANDLER_ONMATCH) {
+                       if (strcmp(data->match_data.event_system,
+                                  data_test->match_data.event_system) != 0)
                                return false;
-               } else if (data->fn == onmax_save) {
-                       if (strcmp(data->onmax.var_str,
-                                  data_test->onmax.var_str) != 0)
+                       if (strcmp(data->match_data.event,
+                                  data_test->match_data.event) != 0)
                                return false;
-                       if (strcmp(data->onmax.fn_name,
-                                  data_test->onmax.fn_name) != 0)
+               } else if (data->handler == HANDLER_ONMAX ||
+                          data->handler == HANDLER_ONCHANGE) {
+                       if (strcmp(data->track_data.var_str,
+                                  data_test->track_data.var_str) != 0)
                                return false;
                }
        }
@@ -4507,10 +4968,11 @@ static void print_actions_spec(struct seq_file *m,
        for (i = 0; i < hist_data->n_actions; i++) {
                struct action_data *data = hist_data->actions[i];
 
-               if (data->fn == action_trace)
+               if (data->handler == HANDLER_ONMATCH)
                        print_onmatch_spec(m, hist_data, data);
-               else if (data->fn == onmax_save)
-                       print_onmax_spec(m, hist_data, data);
+               else if (data->handler == HANDLER_ONMAX ||
+                        data->handler == HANDLER_ONCHANGE)
+                       print_track_data_spec(m, hist_data, data);
        }
 }
 
@@ -4695,22 +5157,24 @@ static inline void add_to_key(char *compound_key, void *key,
                /* ensure NULL-termination */
                if (size > key_field->size - 1)
                        size = key_field->size - 1;
-       }
 
-       memcpy(compound_key + key_field->offset, key, size);
+               strncpy(compound_key + key_field->offset, (char *)key, size);
+       } else
+               memcpy(compound_key + key_field->offset, key, size);
 }
 
 static void
 hist_trigger_actions(struct hist_trigger_data *hist_data,
                     struct tracing_map_elt *elt, void *rec,
-                    struct ring_buffer_event *rbe, u64 *var_ref_vals)
+                    struct ring_buffer_event *rbe, void *key,
+                    u64 *var_ref_vals)
 {
        struct action_data *data;
        unsigned int i;
 
        for (i = 0; i < hist_data->n_actions; i++) {
                data = hist_data->actions[i];
-               data->fn(hist_data, elt, rec, rbe, data, var_ref_vals);
+               data->fn(hist_data, elt, rec, rbe, key, data, var_ref_vals);
        }
 }
 
@@ -4771,7 +5235,7 @@ static void event_hist_trigger(struct event_trigger_data *data, void *rec,
        hist_trigger_elt_update(hist_data, elt, rec, rbe, var_ref_vals);
 
        if (resolve_var_refs(hist_data, key, var_ref_vals, true))
-               hist_trigger_actions(hist_data, elt, rec, rbe, var_ref_vals);
+               hist_trigger_actions(hist_data, elt, rec, rbe, key, var_ref_vals);
 }
 
 static void hist_trigger_stacktrace_print(struct seq_file *m,
@@ -4792,10 +5256,10 @@ static void hist_trigger_stacktrace_print(struct seq_file *m,
        }
 }
 
-static void
-hist_trigger_entry_print(struct seq_file *m,
-                        struct hist_trigger_data *hist_data, void *key,
-                        struct tracing_map_elt *elt)
+static void hist_trigger_print_key(struct seq_file *m,
+                                  struct hist_trigger_data *hist_data,
+                                  void *key,
+                                  struct tracing_map_elt *elt)
 {
        struct hist_field *key_field;
        char str[KSYM_SYMBOL_LEN];
@@ -4871,6 +5335,17 @@ hist_trigger_entry_print(struct seq_file *m,
                seq_puts(m, " ");
 
        seq_puts(m, "}");
+}
+
+static void hist_trigger_entry_print(struct seq_file *m,
+                                    struct hist_trigger_data *hist_data,
+                                    void *key,
+                                    struct tracing_map_elt *elt)
+{
+       const char *field_name;
+       unsigned int i;
+
+       hist_trigger_print_key(m, hist_data, key, elt);
 
        seq_printf(m, " hitcount: %10llu",
                   tracing_map_read_sum(elt, HITCOUNT_IDX));
@@ -4937,6 +5412,8 @@ static void hist_trigger_show(struct seq_file *m,
        if (n_entries < 0)
                n_entries = 0;
 
+       track_data_snapshot_print(m, hist_data);
+
        seq_printf(m, "\nTotals:\n    Hits: %llu\n    Entries: %u\n    Dropped: %llu\n",
                   (u64)atomic64_read(&hist_data->map->hits),
                   n_entries, (u64)atomic64_read(&hist_data->map->drops));
@@ -5683,7 +6160,7 @@ static int event_hist_trigger_func(struct event_command *cmd_ops,
        if (has_hist_vars(hist_data))
                save_hist_vars(hist_data);
 
-       ret = create_actions(hist_data, file);
+       ret = create_actions(hist_data);
        if (ret)
                goto out_unreg;