]> git.proxmox.com Git - mirror_ubuntu-artful-kernel.git/blobdiff - kernel/trace/trace_events_filter.c
ftrace, perf: Add filter support for function trace event
[mirror_ubuntu-artful-kernel.git] / kernel / trace / trace_events_filter.c
index 3da3d0ec35844b77af20eb23e6b8232948847447..431dba8b754214ee06b4d7163dc3abd91b2718b5 100644 (file)
@@ -81,6 +81,7 @@ enum {
        FILT_ERR_TOO_MANY_PREDS,
        FILT_ERR_MISSING_FIELD,
        FILT_ERR_INVALID_FILTER,
+       FILT_ERR_IP_FIELD_ONLY,
 };
 
 static char *err_text[] = {
@@ -96,6 +97,7 @@ static char *err_text[] = {
        "Too many terms in predicate expression",
        "Missing field name and/or value",
        "Meaningless filter expression",
+       "Only 'ip' field is supported for function trace",
 };
 
 struct opstack_op {
@@ -991,7 +993,12 @@ static int init_pred(struct filter_parse_state *ps,
                        fn = filter_pred_strloc;
                else
                        fn = filter_pred_pchar;
-       } else if (!is_function_field(field)) {
+       } else if (is_function_field(field)) {
+               if (strcmp(field->name, "ip")) {
+                       parse_error(ps, FILT_ERR_IP_FIELD_ONLY, 0);
+                       return -EINVAL;
+               }
+       } else {
                if (field->is_signed)
                        ret = strict_strtoll(pred->regex.pattern, 0, &val);
                else
@@ -1338,10 +1345,7 @@ static struct filter_pred *create_pred(struct filter_parse_state *ps,
 
        strcpy(pred.regex.pattern, operand2);
        pred.regex.len = strlen(pred.regex.pattern);
-
-#ifdef CONFIG_FTRACE_STARTUP_TEST
        pred.field = field;
-#endif
        return init_pred(ps, field, &pred) ? NULL : &pred;
 }
 
@@ -1954,6 +1958,148 @@ void ftrace_profile_free_filter(struct perf_event *event)
        __free_filter(filter);
 }
 
+struct function_filter_data {
+       struct ftrace_ops *ops;
+       int first_filter;
+       int first_notrace;
+};
+
+#ifdef CONFIG_FUNCTION_TRACER
+static char **
+ftrace_function_filter_re(char *buf, int len, int *count)
+{
+       char *str, *sep, **re;
+
+       str = kstrndup(buf, len, GFP_KERNEL);
+       if (!str)
+               return NULL;
+
+       /*
+        * The argv_split function takes white space
+        * as a separator, so convert ',' into spaces.
+        */
+       while ((sep = strchr(str, ',')))
+               *sep = ' ';
+
+       re = argv_split(GFP_KERNEL, str, count);
+       kfree(str);
+       return re;
+}
+
+static int ftrace_function_set_regexp(struct ftrace_ops *ops, int filter,
+                                     int reset, char *re, int len)
+{
+       int ret;
+
+       if (filter)
+               ret = ftrace_set_filter(ops, re, len, reset);
+       else
+               ret = ftrace_set_notrace(ops, re, len, reset);
+
+       return ret;
+}
+
+static int __ftrace_function_set_filter(int filter, char *buf, int len,
+                                       struct function_filter_data *data)
+{
+       int i, re_cnt, ret;
+       int *reset;
+       char **re;
+
+       reset = filter ? &data->first_filter : &data->first_notrace;
+
+       /*
+        * The 'ip' field could have multiple filters set, separated
+        * either by space or comma. We first cut the filter and apply
+        * all pieces separatelly.
+        */
+       re = ftrace_function_filter_re(buf, len, &re_cnt);
+       if (!re)
+               return -EINVAL;
+
+       for (i = 0; i < re_cnt; i++) {
+               ret = ftrace_function_set_regexp(data->ops, filter, *reset,
+                                                re[i], strlen(re[i]));
+               if (ret)
+                       break;
+
+               if (*reset)
+                       *reset = 0;
+       }
+
+       argv_free(re);
+       return ret;
+}
+
+static int ftrace_function_check_pred(struct filter_pred *pred, int leaf)
+{
+       struct ftrace_event_field *field = pred->field;
+
+       if (leaf) {
+               /*
+                * Check the leaf predicate for function trace, verify:
+                *  - only '==' and '!=' is used
+                *  - the 'ip' field is used
+                */
+               if ((pred->op != OP_EQ) && (pred->op != OP_NE))
+                       return -EINVAL;
+
+               if (strcmp(field->name, "ip"))
+                       return -EINVAL;
+       } else {
+               /*
+                * Check the non leaf predicate for function trace, verify:
+                *  - only '||' is used
+               */
+               if (pred->op != OP_OR)
+                       return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int ftrace_function_set_filter_cb(enum move_type move,
+                                        struct filter_pred *pred,
+                                        int *err, void *data)
+{
+       /* Checking the node is valid for function trace. */
+       if ((move != MOVE_DOWN) ||
+           (pred->left != FILTER_PRED_INVALID)) {
+               *err = ftrace_function_check_pred(pred, 0);
+       } else {
+               *err = ftrace_function_check_pred(pred, 1);
+               if (*err)
+                       return WALK_PRED_ABORT;
+
+               *err = __ftrace_function_set_filter(pred->op == OP_EQ,
+                                                   pred->regex.pattern,
+                                                   pred->regex.len,
+                                                   data);
+       }
+
+       return (*err) ? WALK_PRED_ABORT : WALK_PRED_DEFAULT;
+}
+
+static int ftrace_function_set_filter(struct perf_event *event,
+                                     struct event_filter *filter)
+{
+       struct function_filter_data data = {
+               .first_filter  = 1,
+               .first_notrace = 1,
+               .ops           = &event->ftrace_ops,
+       };
+
+       return walk_pred_tree(filter->preds, filter->root,
+                             ftrace_function_set_filter_cb, &data);
+}
+#else
+static int ftrace_function_set_filter(struct perf_event *event,
+                                     struct event_filter *filter)
+{
+       return -ENODEV;
+}
+#endif /* CONFIG_FUNCTION_TRACER */
+
 int ftrace_profile_set_filter(struct perf_event *event, int event_id,
                              char *filter_str)
 {
@@ -1974,9 +2120,16 @@ int ftrace_profile_set_filter(struct perf_event *event, int event_id,
                goto out_unlock;
 
        err = create_filter(call, filter_str, false, &filter);
-       if (!err)
-               event->filter = filter;
+       if (err)
+               goto free_filter;
+
+       if (ftrace_event_is_function(call))
+               err = ftrace_function_set_filter(event, filter);
        else
+               event->filter = filter;
+
+free_filter:
+       if (err || ftrace_event_is_function(call))
                __free_filter(filter);
 
 out_unlock: