tracing/filter: Swap entire filter of events
When creating a new filter, instead of allocating the filter to the event call first and then processing the filter, it is easier to process a temporary filter and then just swap it with the call filter. By doing this, it simplifies the code. A filter is allocated and processed, when it is done, it is swapped with the call filter, synchronize_sched() is called to make sure all callers are done with the old filter (filters are called with premption disabled), and then the old filter is freed. Cc: Tom Zanussi <tzanussi@gmail.com> Signed-off-by: Steven Rostedt <rostedt@goodmis.org>
This commit is contained in:
committed by
Steven Rostedt
parent
bf93f9ed3a
commit
75b8e98263
@@ -425,10 +425,15 @@ int filter_match_preds(struct event_filter *filter, void *rec)
|
|||||||
struct filter_pred *preds;
|
struct filter_pred *preds;
|
||||||
struct filter_pred *pred;
|
struct filter_pred *pred;
|
||||||
struct filter_pred *root;
|
struct filter_pred *root;
|
||||||
int n_preds = ACCESS_ONCE(filter->n_preds);
|
int n_preds;
|
||||||
int done = 0;
|
int done = 0;
|
||||||
|
|
||||||
/* no filter is considered a match */
|
/* no filter is considered a match */
|
||||||
|
if (!filter)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
n_preds = filter->n_preds;
|
||||||
|
|
||||||
if (!n_preds)
|
if (!n_preds)
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
@@ -509,6 +514,9 @@ static void parse_error(struct filter_parse_state *ps, int err, int pos)
|
|||||||
|
|
||||||
static void remove_filter_string(struct event_filter *filter)
|
static void remove_filter_string(struct event_filter *filter)
|
||||||
{
|
{
|
||||||
|
if (!filter)
|
||||||
|
return;
|
||||||
|
|
||||||
kfree(filter->filter_string);
|
kfree(filter->filter_string);
|
||||||
filter->filter_string = NULL;
|
filter->filter_string = NULL;
|
||||||
}
|
}
|
||||||
@@ -568,9 +576,10 @@ static void append_filter_err(struct filter_parse_state *ps,
|
|||||||
|
|
||||||
void print_event_filter(struct ftrace_event_call *call, struct trace_seq *s)
|
void print_event_filter(struct ftrace_event_call *call, struct trace_seq *s)
|
||||||
{
|
{
|
||||||
struct event_filter *filter = call->filter;
|
struct event_filter *filter;
|
||||||
|
|
||||||
mutex_lock(&event_mutex);
|
mutex_lock(&event_mutex);
|
||||||
|
filter = call->filter;
|
||||||
if (filter && filter->filter_string)
|
if (filter && filter->filter_string)
|
||||||
trace_seq_printf(s, "%s\n", filter->filter_string);
|
trace_seq_printf(s, "%s\n", filter->filter_string);
|
||||||
else
|
else
|
||||||
@@ -581,9 +590,10 @@ void print_event_filter(struct ftrace_event_call *call, struct trace_seq *s)
|
|||||||
void print_subsystem_event_filter(struct event_subsystem *system,
|
void print_subsystem_event_filter(struct event_subsystem *system,
|
||||||
struct trace_seq *s)
|
struct trace_seq *s)
|
||||||
{
|
{
|
||||||
struct event_filter *filter = system->filter;
|
struct event_filter *filter;
|
||||||
|
|
||||||
mutex_lock(&event_mutex);
|
mutex_lock(&event_mutex);
|
||||||
|
filter = system->filter;
|
||||||
if (filter && filter->filter_string)
|
if (filter && filter->filter_string)
|
||||||
trace_seq_printf(s, "%s\n", filter->filter_string);
|
trace_seq_printf(s, "%s\n", filter->filter_string);
|
||||||
else
|
else
|
||||||
@@ -745,26 +755,9 @@ static void __free_preds(struct event_filter *filter)
|
|||||||
filter->n_preds = 0;
|
filter->n_preds = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void reset_preds(struct event_filter *filter)
|
static void filter_disable(struct ftrace_event_call *call)
|
||||||
{
|
{
|
||||||
int n_preds = filter->n_preds;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
filter->n_preds = 0;
|
|
||||||
filter->root = NULL;
|
|
||||||
if (!filter->preds)
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (i = 0; i < n_preds; i++)
|
|
||||||
filter->preds[i].fn = filter_pred_none;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void filter_disable_preds(struct ftrace_event_call *call)
|
|
||||||
{
|
|
||||||
struct event_filter *filter = call->filter;
|
|
||||||
|
|
||||||
call->flags &= ~TRACE_EVENT_FL_FILTERED;
|
call->flags &= ~TRACE_EVENT_FL_FILTERED;
|
||||||
reset_preds(filter);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void __free_filter(struct event_filter *filter)
|
static void __free_filter(struct event_filter *filter)
|
||||||
@@ -777,11 +770,16 @@ static void __free_filter(struct event_filter *filter)
|
|||||||
kfree(filter);
|
kfree(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Called when destroying the ftrace_event_call.
|
||||||
|
* The call is being freed, so we do not need to worry about
|
||||||
|
* the call being currently used. This is for module code removing
|
||||||
|
* the tracepoints from within it.
|
||||||
|
*/
|
||||||
void destroy_preds(struct ftrace_event_call *call)
|
void destroy_preds(struct ftrace_event_call *call)
|
||||||
{
|
{
|
||||||
__free_filter(call->filter);
|
__free_filter(call->filter);
|
||||||
call->filter = NULL;
|
call->filter = NULL;
|
||||||
call->flags &= ~TRACE_EVENT_FL_FILTERED;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct event_filter *__alloc_filter(void)
|
static struct event_filter *__alloc_filter(void)
|
||||||
@@ -789,11 +787,6 @@ static struct event_filter *__alloc_filter(void)
|
|||||||
struct event_filter *filter;
|
struct event_filter *filter;
|
||||||
|
|
||||||
filter = kzalloc(sizeof(*filter), GFP_KERNEL);
|
filter = kzalloc(sizeof(*filter), GFP_KERNEL);
|
||||||
if (!filter)
|
|
||||||
return ERR_PTR(-ENOMEM);
|
|
||||||
|
|
||||||
filter->n_preds = 0;
|
|
||||||
|
|
||||||
return filter;
|
return filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -838,36 +831,6 @@ static int __alloc_preds(struct event_filter *filter, int n_preds)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int init_filter(struct ftrace_event_call *call)
|
|
||||||
{
|
|
||||||
if (call->filter)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
call->flags &= ~TRACE_EVENT_FL_FILTERED;
|
|
||||||
call->filter = __alloc_filter();
|
|
||||||
if (IS_ERR(call->filter))
|
|
||||||
return PTR_ERR(call->filter);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int init_subsystem_preds(struct event_subsystem *system)
|
|
||||||
{
|
|
||||||
struct ftrace_event_call *call;
|
|
||||||
int err;
|
|
||||||
|
|
||||||
list_for_each_entry(call, &ftrace_events, list) {
|
|
||||||
if (strcmp(call->class->system, system->name) != 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
err = init_filter(call);
|
|
||||||
if (err)
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void filter_free_subsystem_preds(struct event_subsystem *system)
|
static void filter_free_subsystem_preds(struct event_subsystem *system)
|
||||||
{
|
{
|
||||||
struct ftrace_event_call *call;
|
struct ftrace_event_call *call;
|
||||||
@@ -876,11 +839,23 @@ static void filter_free_subsystem_preds(struct event_subsystem *system)
|
|||||||
if (strcmp(call->class->system, system->name) != 0)
|
if (strcmp(call->class->system, system->name) != 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
filter_disable_preds(call);
|
filter_disable(call);
|
||||||
remove_filter_string(call->filter);
|
remove_filter_string(call->filter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void filter_free_subsystem_filters(struct event_subsystem *system)
|
||||||
|
{
|
||||||
|
struct ftrace_event_call *call;
|
||||||
|
|
||||||
|
list_for_each_entry(call, &ftrace_events, list) {
|
||||||
|
if (strcmp(call->class->system, system->name) != 0)
|
||||||
|
continue;
|
||||||
|
__free_filter(call->filter);
|
||||||
|
call->filter = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static int filter_add_pred_fn(struct filter_parse_state *ps,
|
static int filter_add_pred_fn(struct filter_parse_state *ps,
|
||||||
struct ftrace_event_call *call,
|
struct ftrace_event_call *call,
|
||||||
struct event_filter *filter,
|
struct event_filter *filter,
|
||||||
@@ -1743,88 +1718,129 @@ fail:
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct filter_list {
|
||||||
|
struct list_head list;
|
||||||
|
struct event_filter *filter;
|
||||||
|
};
|
||||||
|
|
||||||
static int replace_system_preds(struct event_subsystem *system,
|
static int replace_system_preds(struct event_subsystem *system,
|
||||||
struct filter_parse_state *ps,
|
struct filter_parse_state *ps,
|
||||||
char *filter_string)
|
char *filter_string)
|
||||||
{
|
{
|
||||||
struct ftrace_event_call *call;
|
struct ftrace_event_call *call;
|
||||||
|
struct filter_list *filter_item;
|
||||||
|
struct filter_list *tmp;
|
||||||
|
LIST_HEAD(filter_list);
|
||||||
bool fail = true;
|
bool fail = true;
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
list_for_each_entry(call, &ftrace_events, list) {
|
list_for_each_entry(call, &ftrace_events, list) {
|
||||||
struct event_filter *filter = call->filter;
|
|
||||||
|
|
||||||
if (strcmp(call->class->system, system->name) != 0)
|
if (strcmp(call->class->system, system->name) != 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
/* try to see if the filter can be applied */
|
/*
|
||||||
err = replace_preds(call, filter, ps, filter_string, true);
|
* Try to see if the filter can be applied
|
||||||
|
* (filter arg is ignored on dry_run)
|
||||||
|
*/
|
||||||
|
err = replace_preds(call, NULL, ps, filter_string, true);
|
||||||
if (err)
|
if (err)
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* set all filter pred counts to zero */
|
|
||||||
list_for_each_entry(call, &ftrace_events, list) {
|
list_for_each_entry(call, &ftrace_events, list) {
|
||||||
struct event_filter *filter = call->filter;
|
struct event_filter *filter;
|
||||||
|
|
||||||
if (strcmp(call->class->system, system->name) != 0)
|
if (strcmp(call->class->system, system->name) != 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
reset_preds(filter);
|
filter_item = kzalloc(sizeof(*filter_item), GFP_KERNEL);
|
||||||
}
|
if (!filter_item)
|
||||||
|
goto fail_mem;
|
||||||
|
|
||||||
/*
|
list_add_tail(&filter_item->list, &filter_list);
|
||||||
* Since some of the preds may be used under preemption
|
|
||||||
* we need to wait for them to finish before we may
|
|
||||||
* reallocate them.
|
|
||||||
*/
|
|
||||||
synchronize_sched();
|
|
||||||
|
|
||||||
list_for_each_entry(call, &ftrace_events, list) {
|
filter_item->filter = __alloc_filter();
|
||||||
struct event_filter *filter = call->filter;
|
if (!filter_item->filter)
|
||||||
|
goto fail_mem;
|
||||||
|
filter = filter_item->filter;
|
||||||
|
|
||||||
if (strcmp(call->class->system, system->name) != 0)
|
/* Can only fail on no memory */
|
||||||
continue;
|
err = replace_filter_string(filter, filter_string);
|
||||||
|
|
||||||
/* really apply the filter */
|
|
||||||
filter_disable_preds(call);
|
|
||||||
err = replace_preds(call, filter, ps, filter_string, false);
|
|
||||||
if (err)
|
if (err)
|
||||||
filter_disable_preds(call);
|
goto fail_mem;
|
||||||
else {
|
|
||||||
|
err = replace_preds(call, filter, ps, filter_string, false);
|
||||||
|
if (err) {
|
||||||
|
filter_disable(call);
|
||||||
|
parse_error(ps, FILT_ERR_BAD_SUBSYS_FILTER, 0);
|
||||||
|
append_filter_err(ps, filter);
|
||||||
|
} else
|
||||||
call->flags |= TRACE_EVENT_FL_FILTERED;
|
call->flags |= TRACE_EVENT_FL_FILTERED;
|
||||||
replace_filter_string(filter, filter_string);
|
/*
|
||||||
}
|
* Regardless of if this returned an error, we still
|
||||||
|
* replace the filter for the call.
|
||||||
|
*/
|
||||||
|
filter = call->filter;
|
||||||
|
call->filter = filter_item->filter;
|
||||||
|
filter_item->filter = filter;
|
||||||
|
|
||||||
fail = false;
|
fail = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fail)
|
if (fail)
|
||||||
goto fail;
|
goto fail;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The calls can still be using the old filters.
|
||||||
|
* Do a synchronize_sched() to ensure all calls are
|
||||||
|
* done with them before we free them.
|
||||||
|
*/
|
||||||
|
synchronize_sched();
|
||||||
|
list_for_each_entry_safe(filter_item, tmp, &filter_list, list) {
|
||||||
|
__free_filter(filter_item->filter);
|
||||||
|
list_del(&filter_item->list);
|
||||||
|
kfree(filter_item);
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
fail:
|
fail:
|
||||||
|
/* No call succeeded */
|
||||||
|
list_for_each_entry_safe(filter_item, tmp, &filter_list, list) {
|
||||||
|
list_del(&filter_item->list);
|
||||||
|
kfree(filter_item);
|
||||||
|
}
|
||||||
parse_error(ps, FILT_ERR_BAD_SUBSYS_FILTER, 0);
|
parse_error(ps, FILT_ERR_BAD_SUBSYS_FILTER, 0);
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
fail_mem:
|
||||||
|
/* If any call succeeded, we still need to sync */
|
||||||
|
if (!fail)
|
||||||
|
synchronize_sched();
|
||||||
|
list_for_each_entry_safe(filter_item, tmp, &filter_list, list) {
|
||||||
|
__free_filter(filter_item->filter);
|
||||||
|
list_del(&filter_item->list);
|
||||||
|
kfree(filter_item);
|
||||||
|
}
|
||||||
|
return -ENOMEM;
|
||||||
}
|
}
|
||||||
|
|
||||||
int apply_event_filter(struct ftrace_event_call *call, char *filter_string)
|
int apply_event_filter(struct ftrace_event_call *call, char *filter_string)
|
||||||
{
|
{
|
||||||
int err;
|
|
||||||
struct filter_parse_state *ps;
|
struct filter_parse_state *ps;
|
||||||
|
struct event_filter *filter;
|
||||||
|
struct event_filter *tmp;
|
||||||
|
int err = 0;
|
||||||
|
|
||||||
mutex_lock(&event_mutex);
|
mutex_lock(&event_mutex);
|
||||||
|
|
||||||
err = init_filter(call);
|
|
||||||
if (err)
|
|
||||||
goto out_unlock;
|
|
||||||
|
|
||||||
if (!strcmp(strstrip(filter_string), "0")) {
|
if (!strcmp(strstrip(filter_string), "0")) {
|
||||||
filter_disable_preds(call);
|
filter_disable(call);
|
||||||
reset_preds(call->filter);
|
filter = call->filter;
|
||||||
|
if (!filter)
|
||||||
|
goto out_unlock;
|
||||||
|
call->filter = NULL;
|
||||||
/* Make sure the filter is not being used */
|
/* Make sure the filter is not being used */
|
||||||
synchronize_sched();
|
synchronize_sched();
|
||||||
__free_preds(call->filter);
|
__free_filter(filter);
|
||||||
remove_filter_string(call->filter);
|
|
||||||
goto out_unlock;
|
goto out_unlock;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1833,29 +1849,41 @@ int apply_event_filter(struct ftrace_event_call *call, char *filter_string)
|
|||||||
if (!ps)
|
if (!ps)
|
||||||
goto out_unlock;
|
goto out_unlock;
|
||||||
|
|
||||||
filter_disable_preds(call);
|
filter = __alloc_filter();
|
||||||
replace_filter_string(call->filter, filter_string);
|
if (!filter) {
|
||||||
|
kfree(ps);
|
||||||
|
goto out_unlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
replace_filter_string(filter, filter_string);
|
||||||
|
|
||||||
parse_init(ps, filter_ops, filter_string);
|
parse_init(ps, filter_ops, filter_string);
|
||||||
err = filter_parse(ps);
|
err = filter_parse(ps);
|
||||||
if (err) {
|
if (err) {
|
||||||
append_filter_err(ps, call->filter);
|
append_filter_err(ps, filter);
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
err = replace_preds(call, filter, ps, filter_string, false);
|
||||||
* Make sure all the pred counts are zero so that
|
if (err) {
|
||||||
* no task is using it when we reallocate the preds array.
|
filter_disable(call);
|
||||||
*/
|
append_filter_err(ps, filter);
|
||||||
reset_preds(call->filter);
|
} else
|
||||||
synchronize_sched();
|
|
||||||
|
|
||||||
err = replace_preds(call, call->filter, ps, filter_string, false);
|
|
||||||
if (err)
|
|
||||||
append_filter_err(ps, call->filter);
|
|
||||||
else
|
|
||||||
call->flags |= TRACE_EVENT_FL_FILTERED;
|
call->flags |= TRACE_EVENT_FL_FILTERED;
|
||||||
out:
|
out:
|
||||||
|
/*
|
||||||
|
* Always swap the call filter with the new filter
|
||||||
|
* even if there was an error. If there was an error
|
||||||
|
* in the filter, we disable the filter and show the error
|
||||||
|
* string
|
||||||
|
*/
|
||||||
|
tmp = call->filter;
|
||||||
|
call->filter = filter;
|
||||||
|
if (tmp) {
|
||||||
|
/* Make sure the call is done with the filter */
|
||||||
|
synchronize_sched();
|
||||||
|
__free_filter(tmp);
|
||||||
|
}
|
||||||
filter_opstack_clear(ps);
|
filter_opstack_clear(ps);
|
||||||
postfix_clear(ps);
|
postfix_clear(ps);
|
||||||
kfree(ps);
|
kfree(ps);
|
||||||
@@ -1868,18 +1896,21 @@ out_unlock:
|
|||||||
int apply_subsystem_event_filter(struct event_subsystem *system,
|
int apply_subsystem_event_filter(struct event_subsystem *system,
|
||||||
char *filter_string)
|
char *filter_string)
|
||||||
{
|
{
|
||||||
int err;
|
|
||||||
struct filter_parse_state *ps;
|
struct filter_parse_state *ps;
|
||||||
|
struct event_filter *filter;
|
||||||
|
int err = 0;
|
||||||
|
|
||||||
mutex_lock(&event_mutex);
|
mutex_lock(&event_mutex);
|
||||||
|
|
||||||
err = init_subsystem_preds(system);
|
|
||||||
if (err)
|
|
||||||
goto out_unlock;
|
|
||||||
|
|
||||||
if (!strcmp(strstrip(filter_string), "0")) {
|
if (!strcmp(strstrip(filter_string), "0")) {
|
||||||
filter_free_subsystem_preds(system);
|
filter_free_subsystem_preds(system);
|
||||||
remove_filter_string(system->filter);
|
remove_filter_string(system->filter);
|
||||||
|
filter = system->filter;
|
||||||
|
system->filter = NULL;
|
||||||
|
/* Ensure all filters are no longer used */
|
||||||
|
synchronize_sched();
|
||||||
|
filter_free_subsystem_filters(system);
|
||||||
|
__free_filter(filter);
|
||||||
goto out_unlock;
|
goto out_unlock;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1888,7 +1919,17 @@ int apply_subsystem_event_filter(struct event_subsystem *system,
|
|||||||
if (!ps)
|
if (!ps)
|
||||||
goto out_unlock;
|
goto out_unlock;
|
||||||
|
|
||||||
replace_filter_string(system->filter, filter_string);
|
filter = __alloc_filter();
|
||||||
|
if (!filter)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
replace_filter_string(filter, filter_string);
|
||||||
|
/*
|
||||||
|
* No event actually uses the system filter
|
||||||
|
* we can free it without synchronize_sched().
|
||||||
|
*/
|
||||||
|
__free_filter(system->filter);
|
||||||
|
system->filter = filter;
|
||||||
|
|
||||||
parse_init(ps, filter_ops, filter_string);
|
parse_init(ps, filter_ops, filter_string);
|
||||||
err = filter_parse(ps);
|
err = filter_parse(ps);
|
||||||
@@ -1945,7 +1986,7 @@ int ftrace_profile_set_filter(struct perf_event *event, int event_id,
|
|||||||
goto out_unlock;
|
goto out_unlock;
|
||||||
|
|
||||||
filter = __alloc_filter();
|
filter = __alloc_filter();
|
||||||
if (IS_ERR(filter)) {
|
if (!filter) {
|
||||||
err = PTR_ERR(filter);
|
err = PTR_ERR(filter);
|
||||||
goto out_unlock;
|
goto out_unlock;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user