Commit 5500fa51199aee770ce53718853732600543619e
Committed by
Steven Rostedt
1 parent
02aa3162ed
Exists in
smarc-l5.0.0_1.0.0-ga
and in
5 other branches
ftrace, perf: Add filter support for function trace event
Adding support to filter function trace event via perf interface. It is now possible to use filter interface in the perf tool like: perf record -e ftrace:function --filter="(ip == mm_*)" ls The filter syntax is restricted to the the 'ip' field only, and following operators are accepted '==' '!=' '||', ending up with the filter strings like: ip == f1[, ]f2 ... || ip != f3[, ]f4 ... with comma ',' or space ' ' as a function separator. If the space ' ' is used as a separator, the right side of the assignment needs to be enclosed in double quotes '"', e.g.: perf record -e ftrace:function --filter '(ip == do_execve,sys_*,ext*)' ls perf record -e ftrace:function --filter '(ip == "do_execve,sys_*,ext*")' ls perf record -e ftrace:function --filter '(ip == "do_execve sys_* ext*")' ls The '==' operator adds trace filter with same effect as would be added via set_ftrace_filter file. The '!=' operator adds trace filter with same effect as would be added via set_ftrace_notrace file. The right side of the '!=', '==' operators is list of functions or regexp. to be added to filter separated by space. The '||' operator is used for connecting multiple filter definitions together. It is possible to have more than one '==' and '!=' operators within one filter string. Link: http://lkml.kernel.org/r/1329317514-8131-8-git-send-email-jolsa@redhat.com Signed-off-by: Jiri Olsa <jolsa@redhat.com> Signed-off-by: Steven Rostedt <rostedt@goodmis.org>
Showing 5 changed files with 172 additions and 12 deletions Side-by-side Diff
include/linux/ftrace.h
... | ... | @@ -250,6 +250,7 @@ |
250 | 250 | int len, int reset); |
251 | 251 | void ftrace_set_global_filter(unsigned char *buf, int len, int reset); |
252 | 252 | void ftrace_set_global_notrace(unsigned char *buf, int len, int reset); |
253 | +void ftrace_free_filter(struct ftrace_ops *ops); | |
253 | 254 | |
254 | 255 | int register_ftrace_command(struct ftrace_func_command *cmd); |
255 | 256 | int unregister_ftrace_command(struct ftrace_func_command *cmd); |
... | ... | @@ -380,9 +381,6 @@ |
380 | 381 | #else |
381 | 382 | static inline int skip_trace(unsigned long ip) { return 0; } |
382 | 383 | static inline int ftrace_force_update(void) { return 0; } |
383 | -static inline void ftrace_set_filter(unsigned char *buf, int len, int reset) | |
384 | -{ | |
385 | -} | |
386 | 384 | static inline void ftrace_disable_daemon(void) { } |
387 | 385 | static inline void ftrace_enable_daemon(void) { } |
388 | 386 | static inline void ftrace_release_mod(struct module *mod) {} |
... | ... | @@ -406,6 +404,9 @@ |
406 | 404 | */ |
407 | 405 | #define ftrace_regex_open(ops, flag, inod, file) ({ -ENODEV; }) |
408 | 406 | #define ftrace_set_early_filter(ops, buf, enable) do { } while (0) |
407 | +#define ftrace_set_filter(ops, buf, len, reset) ({ -ENODEV; }) | |
408 | +#define ftrace_set_notrace(ops, buf, len, reset) ({ -ENODEV; }) | |
409 | +#define ftrace_free_filter(ops) do { } while (0) | |
409 | 410 | |
410 | 411 | static inline ssize_t ftrace_filter_write(struct file *file, const char __user *ubuf, |
411 | 412 | size_t cnt, loff_t *ppos) { return -ENODEV; } |
kernel/trace/ftrace.c
... | ... | @@ -1186,6 +1186,12 @@ |
1186 | 1186 | call_rcu_sched(&hash->rcu, __free_ftrace_hash_rcu); |
1187 | 1187 | } |
1188 | 1188 | |
1189 | +void ftrace_free_filter(struct ftrace_ops *ops) | |
1190 | +{ | |
1191 | + free_ftrace_hash(ops->filter_hash); | |
1192 | + free_ftrace_hash(ops->notrace_hash); | |
1193 | +} | |
1194 | + | |
1189 | 1195 | static struct ftrace_hash *alloc_ftrace_hash(int size_bits) |
1190 | 1196 | { |
1191 | 1197 | struct ftrace_hash *hash; |
kernel/trace/trace.h
kernel/trace/trace_event_perf.c
... | ... | @@ -298,7 +298,9 @@ |
298 | 298 | static int perf_ftrace_function_unregister(struct perf_event *event) |
299 | 299 | { |
300 | 300 | struct ftrace_ops *ops = &event->ftrace_ops; |
301 | - return unregister_ftrace_function(ops); | |
301 | + int ret = unregister_ftrace_function(ops); | |
302 | + ftrace_free_filter(ops); | |
303 | + return ret; | |
302 | 304 | } |
303 | 305 | |
304 | 306 | static void perf_ftrace_function_enable(struct perf_event *event) |
kernel/trace/trace_events_filter.c
... | ... | @@ -81,6 +81,7 @@ |
81 | 81 | FILT_ERR_TOO_MANY_PREDS, |
82 | 82 | FILT_ERR_MISSING_FIELD, |
83 | 83 | FILT_ERR_INVALID_FILTER, |
84 | + FILT_ERR_IP_FIELD_ONLY, | |
84 | 85 | }; |
85 | 86 | |
86 | 87 | static char *err_text[] = { |
... | ... | @@ -96,6 +97,7 @@ |
96 | 97 | "Too many terms in predicate expression", |
97 | 98 | "Missing field name and/or value", |
98 | 99 | "Meaningless filter expression", |
100 | + "Only 'ip' field is supported for function trace", | |
99 | 101 | }; |
100 | 102 | |
101 | 103 | struct opstack_op { |
... | ... | @@ -991,7 +993,12 @@ |
991 | 993 | fn = filter_pred_strloc; |
992 | 994 | else |
993 | 995 | fn = filter_pred_pchar; |
994 | - } else if (!is_function_field(field)) { | |
996 | + } else if (is_function_field(field)) { | |
997 | + if (strcmp(field->name, "ip")) { | |
998 | + parse_error(ps, FILT_ERR_IP_FIELD_ONLY, 0); | |
999 | + return -EINVAL; | |
1000 | + } | |
1001 | + } else { | |
995 | 1002 | if (field->is_signed) |
996 | 1003 | ret = strict_strtoll(pred->regex.pattern, 0, &val); |
997 | 1004 | else |
998 | 1005 | |
... | ... | @@ -1338,10 +1345,7 @@ |
1338 | 1345 | |
1339 | 1346 | strcpy(pred.regex.pattern, operand2); |
1340 | 1347 | pred.regex.len = strlen(pred.regex.pattern); |
1341 | - | |
1342 | -#ifdef CONFIG_FTRACE_STARTUP_TEST | |
1343 | 1348 | pred.field = field; |
1344 | -#endif | |
1345 | 1349 | return init_pred(ps, field, &pred) ? NULL : &pred; |
1346 | 1350 | } |
1347 | 1351 | |
... | ... | @@ -1954,6 +1958,148 @@ |
1954 | 1958 | __free_filter(filter); |
1955 | 1959 | } |
1956 | 1960 | |
1961 | +struct function_filter_data { | |
1962 | + struct ftrace_ops *ops; | |
1963 | + int first_filter; | |
1964 | + int first_notrace; | |
1965 | +}; | |
1966 | + | |
1967 | +#ifdef CONFIG_FUNCTION_TRACER | |
1968 | +static char ** | |
1969 | +ftrace_function_filter_re(char *buf, int len, int *count) | |
1970 | +{ | |
1971 | + char *str, *sep, **re; | |
1972 | + | |
1973 | + str = kstrndup(buf, len, GFP_KERNEL); | |
1974 | + if (!str) | |
1975 | + return NULL; | |
1976 | + | |
1977 | + /* | |
1978 | + * The argv_split function takes white space | |
1979 | + * as a separator, so convert ',' into spaces. | |
1980 | + */ | |
1981 | + while ((sep = strchr(str, ','))) | |
1982 | + *sep = ' '; | |
1983 | + | |
1984 | + re = argv_split(GFP_KERNEL, str, count); | |
1985 | + kfree(str); | |
1986 | + return re; | |
1987 | +} | |
1988 | + | |
1989 | +static int ftrace_function_set_regexp(struct ftrace_ops *ops, int filter, | |
1990 | + int reset, char *re, int len) | |
1991 | +{ | |
1992 | + int ret; | |
1993 | + | |
1994 | + if (filter) | |
1995 | + ret = ftrace_set_filter(ops, re, len, reset); | |
1996 | + else | |
1997 | + ret = ftrace_set_notrace(ops, re, len, reset); | |
1998 | + | |
1999 | + return ret; | |
2000 | +} | |
2001 | + | |
2002 | +static int __ftrace_function_set_filter(int filter, char *buf, int len, | |
2003 | + struct function_filter_data *data) | |
2004 | +{ | |
2005 | + int i, re_cnt, ret; | |
2006 | + int *reset; | |
2007 | + char **re; | |
2008 | + | |
2009 | + reset = filter ? &data->first_filter : &data->first_notrace; | |
2010 | + | |
2011 | + /* | |
2012 | + * The 'ip' field could have multiple filters set, separated | |
2013 | + * either by space or comma. We first cut the filter and apply | |
2014 | + * all pieces separatelly. | |
2015 | + */ | |
2016 | + re = ftrace_function_filter_re(buf, len, &re_cnt); | |
2017 | + if (!re) | |
2018 | + return -EINVAL; | |
2019 | + | |
2020 | + for (i = 0; i < re_cnt; i++) { | |
2021 | + ret = ftrace_function_set_regexp(data->ops, filter, *reset, | |
2022 | + re[i], strlen(re[i])); | |
2023 | + if (ret) | |
2024 | + break; | |
2025 | + | |
2026 | + if (*reset) | |
2027 | + *reset = 0; | |
2028 | + } | |
2029 | + | |
2030 | + argv_free(re); | |
2031 | + return ret; | |
2032 | +} | |
2033 | + | |
2034 | +static int ftrace_function_check_pred(struct filter_pred *pred, int leaf) | |
2035 | +{ | |
2036 | + struct ftrace_event_field *field = pred->field; | |
2037 | + | |
2038 | + if (leaf) { | |
2039 | + /* | |
2040 | + * Check the leaf predicate for function trace, verify: | |
2041 | + * - only '==' and '!=' is used | |
2042 | + * - the 'ip' field is used | |
2043 | + */ | |
2044 | + if ((pred->op != OP_EQ) && (pred->op != OP_NE)) | |
2045 | + return -EINVAL; | |
2046 | + | |
2047 | + if (strcmp(field->name, "ip")) | |
2048 | + return -EINVAL; | |
2049 | + } else { | |
2050 | + /* | |
2051 | + * Check the non leaf predicate for function trace, verify: | |
2052 | + * - only '||' is used | |
2053 | + */ | |
2054 | + if (pred->op != OP_OR) | |
2055 | + return -EINVAL; | |
2056 | + } | |
2057 | + | |
2058 | + return 0; | |
2059 | +} | |
2060 | + | |
2061 | +static int ftrace_function_set_filter_cb(enum move_type move, | |
2062 | + struct filter_pred *pred, | |
2063 | + int *err, void *data) | |
2064 | +{ | |
2065 | + /* Checking the node is valid for function trace. */ | |
2066 | + if ((move != MOVE_DOWN) || | |
2067 | + (pred->left != FILTER_PRED_INVALID)) { | |
2068 | + *err = ftrace_function_check_pred(pred, 0); | |
2069 | + } else { | |
2070 | + *err = ftrace_function_check_pred(pred, 1); | |
2071 | + if (*err) | |
2072 | + return WALK_PRED_ABORT; | |
2073 | + | |
2074 | + *err = __ftrace_function_set_filter(pred->op == OP_EQ, | |
2075 | + pred->regex.pattern, | |
2076 | + pred->regex.len, | |
2077 | + data); | |
2078 | + } | |
2079 | + | |
2080 | + return (*err) ? WALK_PRED_ABORT : WALK_PRED_DEFAULT; | |
2081 | +} | |
2082 | + | |
2083 | +static int ftrace_function_set_filter(struct perf_event *event, | |
2084 | + struct event_filter *filter) | |
2085 | +{ | |
2086 | + struct function_filter_data data = { | |
2087 | + .first_filter = 1, | |
2088 | + .first_notrace = 1, | |
2089 | + .ops = &event->ftrace_ops, | |
2090 | + }; | |
2091 | + | |
2092 | + return walk_pred_tree(filter->preds, filter->root, | |
2093 | + ftrace_function_set_filter_cb, &data); | |
2094 | +} | |
2095 | +#else | |
2096 | +static int ftrace_function_set_filter(struct perf_event *event, | |
2097 | + struct event_filter *filter) | |
2098 | +{ | |
2099 | + return -ENODEV; | |
2100 | +} | |
2101 | +#endif /* CONFIG_FUNCTION_TRACER */ | |
2102 | + | |
1957 | 2103 | int ftrace_profile_set_filter(struct perf_event *event, int event_id, |
1958 | 2104 | char *filter_str) |
1959 | 2105 | { |
1960 | 2106 | |
... | ... | @@ -1974,9 +2120,16 @@ |
1974 | 2120 | goto out_unlock; |
1975 | 2121 | |
1976 | 2122 | err = create_filter(call, filter_str, false, &filter); |
1977 | - if (!err) | |
1978 | - event->filter = filter; | |
2123 | + if (err) | |
2124 | + goto free_filter; | |
2125 | + | |
2126 | + if (ftrace_event_is_function(call)) | |
2127 | + err = ftrace_function_set_filter(event, filter); | |
1979 | 2128 | else |
2129 | + event->filter = filter; | |
2130 | + | |
2131 | +free_filter: | |
2132 | + if (err || ftrace_event_is_function(call)) | |
1980 | 2133 | __free_filter(filter); |
1981 | 2134 | |
1982 | 2135 | out_unlock: |