]> Pileus Git - ~andy/linux/blobdiff - kernel/trace/trace_events_filter.c
tracing/filter: Add startup tests for events filter
[~andy/linux] / kernel / trace / trace_events_filter.c
index 5b889d43d8567b24bd731fd383c83a55e3a19e63..6a642e2782411de6f8d891d4dde7f8df136d21e6 100644 (file)
@@ -467,99 +467,91 @@ static int process_ops(struct filter_pred *preds,
 
        for (i = 0; i < op->val; i++) {
                pred = &preds[op->ops[i]];
-               match = pred->fn(pred, rec);
+               if (!WARN_ON_ONCE(!pred->fn))
+                       match = pred->fn(pred, rec);
                if (!!match == type)
                        return match;
        }
        return match;
 }
 
+struct filter_match_preds_data {
+       struct filter_pred *preds;
+       int match;
+       void *rec;
+};
+
+static int filter_match_preds_cb(enum move_type move, struct filter_pred *pred,
+                                int *err, void *data)
+{
+       struct filter_match_preds_data *d = data;
+
+       *err = 0;
+       switch (move) {
+       case MOVE_DOWN:
+               /* only AND and OR have children */
+               if (pred->left != FILTER_PRED_INVALID) {
+                       /* If ops is set, then it was folded. */
+                       if (!pred->ops)
+                               return WALK_PRED_DEFAULT;
+                       /* We can treat folded ops as a leaf node */
+                       d->match = process_ops(d->preds, pred, d->rec);
+               } else {
+                       if (!WARN_ON_ONCE(!pred->fn))
+                               d->match = pred->fn(pred, d->rec);
+               }
+
+               return WALK_PRED_PARENT;
+       case MOVE_UP_FROM_LEFT:
+               /*
+                * Check for short circuits.
+                *
+                * Optimization: !!match == (pred->op == OP_OR)
+                *   is the same as:
+                * if ((match && pred->op == OP_OR) ||
+                *     (!match && pred->op == OP_AND))
+                */
+               if (!!d->match == (pred->op == OP_OR))
+                       return WALK_PRED_PARENT;
+               break;
+       case MOVE_UP_FROM_RIGHT:
+               break;
+       }
+
+       return WALK_PRED_DEFAULT;
+}
+
 /* return 1 if event matches, 0 otherwise (discard) */
 int filter_match_preds(struct event_filter *filter, void *rec)
 {
-       int match = -1;
-       enum move_type move = MOVE_DOWN;
        struct filter_pred *preds;
-       struct filter_pred *pred;
        struct filter_pred *root;
-       int n_preds;
-       int done = 0;
+       struct filter_match_preds_data data = {
+               /* match is currently meaningless */
+               .match = -1,
+               .rec   = rec,
+       };
+       int n_preds, ret;
 
        /* no filter is considered a match */
        if (!filter)
                return 1;
 
        n_preds = filter->n_preds;
-
        if (!n_preds)
                return 1;
 
        /*
         * n_preds, root and filter->preds are protect with preemption disabled.
         */
-       preds = rcu_dereference_sched(filter->preds);
        root = rcu_dereference_sched(filter->root);
        if (!root)
                return 1;
 
-       pred = root;
-
-       /* match is currently meaningless */
-       match = -1;
-
-       do {
-               switch (move) {
-               case MOVE_DOWN:
-                       /* only AND and OR have children */
-                       if (pred->left != FILTER_PRED_INVALID) {
-                               /* If ops is set, then it was folded. */
-                               if (!pred->ops) {
-                                       /* keep going to down the left side */
-                                       pred = &preds[pred->left];
-                                       continue;
-                               }
-                               /* We can treat folded ops as a leaf node */
-                               match = process_ops(preds, pred, rec);
-                       } else
-                               match = pred->fn(pred, rec);
-                       /* If this pred is the only pred */
-                       if (pred == root)
-                               break;
-                       pred = get_pred_parent(pred, preds,
-                                              pred->parent, &move);
-                       continue;
-               case MOVE_UP_FROM_LEFT:
-                       /*
-                        * Check for short circuits.
-                        *
-                        * Optimization: !!match == (pred->op == OP_OR)
-                        *   is the same as:
-                        * if ((match && pred->op == OP_OR) ||
-                        *     (!match && pred->op == OP_AND))
-                        */
-                       if (!!match == (pred->op == OP_OR)) {
-                               if (pred == root)
-                                       break;
-                               pred = get_pred_parent(pred, preds,
-                                                      pred->parent, &move);
-                               continue;
-                       }
-                       /* now go down the right side of the tree. */
-                       pred = &preds[pred->right];
-                       move = MOVE_DOWN;
-                       continue;
-               case MOVE_UP_FROM_RIGHT:
-                       /* We finished this equation. */
-                       if (pred == root)
-                               break;
-                       pred = get_pred_parent(pred, preds,
-                                              pred->parent, &move);
-                       continue;
-               }
-               done = 1;
-       } while (!done);
-
-       return match;
+       data.preds = preds = rcu_dereference_sched(filter->preds);
+       ret = walk_pred_tree(preds, root, filter_match_preds_cb, &data);
+       WARN_ON(ret);
+       return data.match;
 }
 EXPORT_SYMBOL_GPL(filter_match_preds);
 
@@ -1337,6 +1329,9 @@ 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;
 }
 
@@ -1418,53 +1413,61 @@ static int check_pred_tree(struct event_filter *filter,
                              check_pred_tree_cb, &data);
 }
 
-static int count_leafs(struct filter_pred *preds, struct filter_pred *root)
+static int count_leafs_cb(enum move_type move, struct filter_pred *pred,
+                         int *err, void *data)
 {
-       struct filter_pred *pred;
-       enum move_type move = MOVE_DOWN;
-       int count = 0;
-       int done = 0;
+       int *count = data;
 
-       pred = root;
+       if ((move == MOVE_DOWN) &&
+           (pred->left == FILTER_PRED_INVALID))
+               (*count)++;
 
-       do {
-               switch (move) {
-               case MOVE_DOWN:
-                       if (pred->left != FILTER_PRED_INVALID) {
-                               pred = &preds[pred->left];
-                               continue;
-                       }
-                       /* A leaf at the root is just a leaf in the tree */
-                       if (pred == root)
-                               return 1;
-                       count++;
-                       pred = get_pred_parent(pred, preds,
-                                              pred->parent, &move);
-                       continue;
-               case MOVE_UP_FROM_LEFT:
-                       pred = &preds[pred->right];
-                       move = MOVE_DOWN;
-                       continue;
-               case MOVE_UP_FROM_RIGHT:
-                       if (pred == root)
-                               break;
-                       pred = get_pred_parent(pred, preds,
-                                              pred->parent, &move);
-                       continue;
-               }
-               done = 1;
-       } while (!done);
+       return WALK_PRED_DEFAULT;
+}
+
+static int count_leafs(struct filter_pred *preds, struct filter_pred *root)
+{
+       int count = 0, ret;
 
+       ret = walk_pred_tree(preds, root, count_leafs_cb, &count);
+       WARN_ON(ret);
        return count;
 }
 
+struct fold_pred_data {
+       struct filter_pred *root;
+       int count;
+       int children;
+};
+
+static int fold_pred_cb(enum move_type move, struct filter_pred *pred,
+                       int *err, void *data)
+{
+       struct fold_pred_data *d = data;
+       struct filter_pred *root = d->root;
+
+       if (move != MOVE_DOWN)
+               return WALK_PRED_DEFAULT;
+       if (pred->left != FILTER_PRED_INVALID)
+               return WALK_PRED_DEFAULT;
+
+       if (WARN_ON(d->count == d->children)) {
+               *err = -EINVAL;
+               return WALK_PRED_ABORT;
+       }
+
+       pred->index &= ~FILTER_PRED_FOLD;
+       root->ops[d->count++] = pred->index;
+       return WALK_PRED_DEFAULT;
+}
+
 static int fold_pred(struct filter_pred *preds, struct filter_pred *root)
 {
-       struct filter_pred *pred;
-       enum move_type move = MOVE_DOWN;
-       int count = 0;
+       struct fold_pred_data data = {
+               .root  = root,
+               .count = 0,
+       };
        int children;
-       int done = 0;
 
        /* No need to keep the fold flag */
        root->index &= ~FILTER_PRED_FOLD;
@@ -1482,37 +1485,26 @@ static int fold_pred(struct filter_pred *preds, struct filter_pred *root)
                return -ENOMEM;
 
        root->val = children;
+       data.children = children;
+       return walk_pred_tree(preds, root, fold_pred_cb, &data);
+}
 
-       pred = root;
-       do {
-               switch (move) {
-               case MOVE_DOWN:
-                       if (pred->left != FILTER_PRED_INVALID) {
-                               pred = &preds[pred->left];
-                               continue;
-                       }
-                       if (WARN_ON(count == children))
-                               return -EINVAL;
-                       pred->index &= ~FILTER_PRED_FOLD;
-                       root->ops[count++] = pred->index;
-                       pred = get_pred_parent(pred, preds,
-                                              pred->parent, &move);
-                       continue;
-               case MOVE_UP_FROM_LEFT:
-                       pred = &preds[pred->right];
-                       move = MOVE_DOWN;
-                       continue;
-               case MOVE_UP_FROM_RIGHT:
-                       if (pred == root)
-                               break;
-                       pred = get_pred_parent(pred, preds,
-                                              pred->parent, &move);
-                       continue;
-               }
-               done = 1;
-       } while (!done);
+static int fold_pred_tree_cb(enum move_type move, struct filter_pred *pred,
+                            int *err, void *data)
+{
+       struct filter_pred *preds = data;
 
-       return 0;
+       if (move != MOVE_DOWN)
+               return WALK_PRED_DEFAULT;
+       if (!(pred->index & FILTER_PRED_FOLD))
+               return WALK_PRED_DEFAULT;
+
+       *err = fold_pred(preds, pred);
+       if (*err)
+               return WALK_PRED_ABORT;
+
+       /* eveyrhing below is folded, continue with parent */
+       return WALK_PRED_PARENT;
 }
 
 /*
@@ -1523,51 +1515,8 @@ static int fold_pred(struct filter_pred *preds, struct filter_pred *root)
 static int fold_pred_tree(struct event_filter *filter,
                           struct filter_pred *root)
 {
-       struct filter_pred *preds;
-       struct filter_pred *pred;
-       enum move_type move = MOVE_DOWN;
-       int done = 0;
-       int err;
-
-       preds = filter->preds;
-       if  (!preds)
-               return -EINVAL;
-       pred = root;
-
-       do {
-               switch (move) {
-               case MOVE_DOWN:
-                       if (pred->index & FILTER_PRED_FOLD) {
-                               err = fold_pred(preds, pred);
-                               if (err)
-                                       return err;
-                               /* Folded nodes are like leafs */
-                       } else if (pred->left != FILTER_PRED_INVALID) {
-                               pred = &preds[pred->left];
-                               continue;
-                       }
-
-                       /* A leaf at the root is just a leaf in the tree */
-                       if (pred == root)
-                               break;
-                       pred = get_pred_parent(pred, preds,
-                                              pred->parent, &move);
-                       continue;
-               case MOVE_UP_FROM_LEFT:
-                       pred = &preds[pred->right];
-                       move = MOVE_DOWN;
-                       continue;
-               case MOVE_UP_FROM_RIGHT:
-                       if (pred == root)
-                               break;
-                       pred = get_pred_parent(pred, preds,
-                                              pred->parent, &move);
-                       continue;
-               }
-               done = 1;
-       } while (!done);
-
-       return 0;
+       return walk_pred_tree(filter->preds, root, fold_pred_tree_cb,
+                             filter->preds);
 }
 
 static int replace_preds(struct ftrace_event_call *call,
@@ -1980,3 +1929,209 @@ out_unlock:
 
 #endif /* CONFIG_PERF_EVENTS */
 
+#ifdef CONFIG_FTRACE_STARTUP_TEST
+
+#include <linux/types.h>
+#include <linux/tracepoint.h>
+
+#define CREATE_TRACE_POINTS
+#include "trace_events_filter_test.h"
+
+static int test_get_filter(char *filter_str, struct ftrace_event_call *call,
+                          struct event_filter **pfilter)
+{
+       struct event_filter *filter;
+       struct filter_parse_state *ps;
+       int err = -ENOMEM;
+
+       filter = __alloc_filter();
+       if (!filter)
+               goto out;
+
+       ps = kzalloc(sizeof(*ps), GFP_KERNEL);
+       if (!ps)
+               goto free_filter;
+
+       parse_init(ps, filter_ops, filter_str);
+       err = filter_parse(ps);
+       if (err)
+               goto free_ps;
+
+       err = replace_preds(call, filter, ps, filter_str, false);
+       if (!err)
+               *pfilter = filter;
+
+ free_ps:
+       filter_opstack_clear(ps);
+       postfix_clear(ps);
+       kfree(ps);
+
+ free_filter:
+       if (err)
+               __free_filter(filter);
+
+ out:
+       return err;
+}
+
+#define DATA_REC(m, va, vb, vc, vd, ve, vf, vg, vh, nvisit) \
+{ \
+       .filter = FILTER, \
+       .rec    = { .a = va, .b = vb, .c = vc, .d = vd, \
+                   .e = ve, .f = vf, .g = vg, .h = vh }, \
+       .match  = m, \
+       .not_visited = nvisit, \
+}
+#define YES 1
+#define NO  0
+
+static struct test_filter_data_t {
+       char *filter;
+       struct ftrace_raw_ftrace_test_filter rec;
+       int match;
+       char *not_visited;
+} test_filter_data[] = {
+#define FILTER "a == 1 && b == 1 && c == 1 && d == 1 && " \
+              "e == 1 && f == 1 && g == 1 && h == 1"
+       DATA_REC(YES, 1, 1, 1, 1, 1, 1, 1, 1, ""),
+       DATA_REC(NO,  0, 1, 1, 1, 1, 1, 1, 1, "bcdefgh"),
+       DATA_REC(NO,  1, 1, 1, 1, 1, 1, 1, 0, ""),
+#undef FILTER
+#define FILTER "a == 1 || b == 1 || c == 1 || d == 1 || " \
+              "e == 1 || f == 1 || g == 1 || h == 1"
+       DATA_REC(NO,  0, 0, 0, 0, 0, 0, 0, 0, ""),
+       DATA_REC(YES, 0, 0, 0, 0, 0, 0, 0, 1, ""),
+       DATA_REC(YES, 1, 0, 0, 0, 0, 0, 0, 0, "bcdefgh"),
+#undef FILTER
+#define FILTER "(a == 1 || b == 1) && (c == 1 || d == 1) && " \
+              "(e == 1 || f == 1) && (g == 1 || h == 1)"
+       DATA_REC(NO,  0, 0, 1, 1, 1, 1, 1, 1, "dfh"),
+       DATA_REC(YES, 0, 1, 0, 1, 0, 1, 0, 1, ""),
+       DATA_REC(YES, 1, 0, 1, 0, 0, 1, 0, 1, "bd"),
+       DATA_REC(NO,  1, 0, 1, 0, 0, 1, 0, 0, "bd"),
+#undef FILTER
+#define FILTER "(a == 1 && b == 1) || (c == 1 && d == 1) || " \
+              "(e == 1 && f == 1) || (g == 1 && h == 1)"
+       DATA_REC(YES, 1, 0, 1, 1, 1, 1, 1, 1, "efgh"),
+       DATA_REC(YES, 0, 0, 0, 0, 0, 0, 1, 1, ""),
+       DATA_REC(NO,  0, 0, 0, 0, 0, 0, 0, 1, ""),
+#undef FILTER
+#define FILTER "(a == 1 && b == 1) && (c == 1 && d == 1) && " \
+              "(e == 1 && f == 1) || (g == 1 && h == 1)"
+       DATA_REC(YES, 1, 1, 1, 1, 1, 1, 0, 0, "gh"),
+       DATA_REC(NO,  0, 0, 0, 0, 0, 0, 0, 1, ""),
+       DATA_REC(YES, 1, 1, 1, 1, 1, 0, 1, 1, ""),
+#undef FILTER
+#define FILTER "((a == 1 || b == 1) || (c == 1 || d == 1) || " \
+              "(e == 1 || f == 1)) && (g == 1 || h == 1)"
+       DATA_REC(YES, 1, 1, 1, 1, 1, 1, 0, 1, "bcdef"),
+       DATA_REC(NO,  0, 0, 0, 0, 0, 0, 0, 0, ""),
+       DATA_REC(YES, 1, 1, 1, 1, 1, 0, 1, 1, "h"),
+#undef FILTER
+#define FILTER "((((((((a == 1) && (b == 1)) || (c == 1)) && (d == 1)) || " \
+              "(e == 1)) && (f == 1)) || (g == 1)) && (h == 1))"
+       DATA_REC(YES, 1, 1, 1, 1, 1, 1, 1, 1, "ceg"),
+       DATA_REC(NO,  0, 1, 0, 1, 0, 1, 0, 1, ""),
+       DATA_REC(NO,  1, 0, 1, 0, 1, 0, 1, 0, ""),
+#undef FILTER
+#define FILTER "((((((((a == 1) || (b == 1)) && (c == 1)) || (d == 1)) && " \
+              "(e == 1)) || (f == 1)) && (g == 1)) || (h == 1))"
+       DATA_REC(YES, 1, 1, 1, 1, 1, 1, 1, 1, "bdfh"),
+       DATA_REC(YES, 0, 1, 0, 1, 0, 1, 0, 1, ""),
+       DATA_REC(YES, 1, 0, 1, 0, 1, 0, 1, 0, "bdfh"),
+};
+
+#undef DATA_REC
+#undef FILTER
+#undef YES
+#undef NO
+
+#define DATA_CNT (sizeof(test_filter_data)/sizeof(struct test_filter_data_t))
+
+static int test_pred_visited;
+
+static int test_pred_visited_fn(struct filter_pred *pred, void *event)
+{
+       struct ftrace_event_field *field = pred->field;
+
+       test_pred_visited = 1;
+       printk(KERN_INFO "\npred visited %s\n", field->name);
+       return 1;
+}
+
+static int test_walk_pred_cb(enum move_type move, struct filter_pred *pred,
+                            int *err, void *data)
+{
+       char *fields = data;
+
+       if ((move == MOVE_DOWN) &&
+           (pred->left == FILTER_PRED_INVALID)) {
+               struct ftrace_event_field *field = pred->field;
+
+               if (!field) {
+                       WARN(1, "all leafs should have field defined");
+                       return WALK_PRED_DEFAULT;
+               }
+               if (!strchr(fields, *field->name))
+                       return WALK_PRED_DEFAULT;
+
+               WARN_ON(!pred->fn);
+               pred->fn = test_pred_visited_fn;
+       }
+       return WALK_PRED_DEFAULT;
+}
+
+static __init int ftrace_test_event_filter(void)
+{
+       int i;
+
+       printk(KERN_INFO "Testing ftrace filter: ");
+
+       for (i = 0; i < DATA_CNT; i++) {
+               struct event_filter *filter = NULL;
+               struct test_filter_data_t *d = &test_filter_data[i];
+               int err;
+
+               err = test_get_filter(d->filter, &event_ftrace_test_filter,
+                                     &filter);
+               if (err) {
+                       printk(KERN_INFO
+                              "Failed to get filter for '%s', err %d\n",
+                              d->filter, err);
+                       break;
+               }
+
+               if (*d->not_visited)
+                       walk_pred_tree(filter->preds, filter->root,
+                                      test_walk_pred_cb,
+                                      d->not_visited);
+
+               test_pred_visited = 0;
+               err = filter_match_preds(filter, &d->rec);
+
+               __free_filter(filter);
+
+               if (test_pred_visited) {
+                       printk(KERN_INFO
+                              "Failed, unwanted pred visited for filter %s\n",
+                              d->filter);
+                       break;
+               }
+
+               if (err != d->match) {
+                       printk(KERN_INFO
+                              "Failed to match filter '%s', expected %d\n",
+                              d->filter, d->match);
+                       break;
+               }
+       }
+
+       if (i == DATA_CNT)
+               printk(KERN_CONT "OK\n");
+
+       return 0;
+}
+
+late_initcall(ftrace_test_event_filter);
+
+#endif /* CONFIG_FTRACE_STARTUP_TEST */