| // SPDX-License-Identifier: LGPL-2.1 |
| /* |
| * Copyright (C) 2022, Google Inc, Steven Rostedt <rostedt@goodmis.org> |
| */ |
| #include <stdlib.h> |
| #include <trace-cmd.h> |
| #include <trace-cmd-local.h> |
| |
| struct filter { |
| struct tep_event_filter *filter; |
| }; |
| |
| struct tracecmd_filter { |
| struct tep_handle *tep; |
| struct filter **event_filters; |
| struct filter **event_notrace; |
| bool *last_printed; |
| int nr_cpus; |
| int nr_filters; |
| int nr_notrace; |
| int kernel_stacktrace_id; |
| int user_stacktrace_id; |
| }; |
| |
| static bool test_stacktrace(struct tracecmd_filter *filter, struct tep_record *record, |
| int stacktrace_id) |
| { |
| struct tep_handle *tep = filter->tep; |
| int id; |
| |
| if (stacktrace_id < 0) |
| return false; |
| |
| id = tep_data_type(tep, record); |
| if (id != stacktrace_id) |
| return false; |
| |
| return filter->last_printed[record->cpu]; |
| } |
| |
| static bool test_stacktraces(struct tracecmd_filter *filter, struct tep_record *record) |
| { |
| return test_stacktrace(filter, record, filter->kernel_stacktrace_id) || |
| test_stacktrace(filter, record, filter->user_stacktrace_id); |
| } |
| |
| __hidden enum tracecmd_filters tracecmd_filter_match(struct tracecmd_filter *filter, |
| struct tep_record *record) |
| { |
| bool is_stack = false; |
| bool found = false; |
| int ret; |
| int i; |
| |
| if (!filter) |
| return TRACECMD_FILTER_NONE; |
| |
| /* Setup stack traces. If a event is shown, still show stack traces */ |
| if (!filter->kernel_stacktrace_id) { |
| struct tep_handle *tep = filter->tep; |
| struct tep_event *event; |
| |
| /* In case the below logic fails, do not do this again */ |
| filter->kernel_stacktrace_id = -1; |
| |
| event = tep_find_event_by_name(tep, "ftrace", "kernel_stack"); |
| if (event) |
| filter->kernel_stacktrace_id = event->id; |
| |
| event = tep_find_event_by_name(tep, "ftrace", "user_stack"); |
| if (event) |
| filter->user_stacktrace_id = event->id; |
| |
| filter->nr_cpus = tep_get_cpus(tep); |
| filter->last_printed = calloc(filter->nr_cpus, sizeof(*filter->last_printed)); |
| if (!filter->last_printed) { |
| tracecmd_warning("Could not allocate last_printed array for stack trace filtering"); |
| filter->kernel_stacktrace_id = -1; |
| filter->user_stacktrace_id = -1; |
| } |
| } |
| |
| for (i = 0; i < filter->nr_filters; i++) { |
| ret = tep_filter_match(filter->event_filters[i]->filter, record); |
| switch (ret) { |
| case TRACECMD_FILTER_NONE: |
| case TRACECMD_FILTER_MATCH: |
| found = true; |
| } |
| if (found) |
| break; |
| } |
| |
| if (!found && filter->nr_filters) { |
| /* If this is a stack trace and the last event was printed continue */ |
| if (!test_stacktraces(filter, record)) |
| return TRACECMD_FILTER_MISS; |
| |
| is_stack = true; |
| } |
| |
| found = false; |
| /* We need to test all negative filters */ |
| for (i = 0; i < filter->nr_notrace; i++) { |
| ret = tep_filter_match(filter->event_notrace[i]->filter, record); |
| switch (ret) { |
| case TRACECMD_FILTER_NONE: |
| case TRACECMD_FILTER_MATCH: |
| found = true; |
| } |
| if (found) |
| break; |
| } |
| |
| if (filter->last_printed) |
| filter->last_printed[record->cpu] = !is_stack && !found; |
| |
| return found ? TRACECMD_FILTER_MISS : TRACECMD_FILTER_MATCH; |
| } |
| |
| struct tracecmd_filter *tracecmd_filter_add(struct tracecmd_input *handle, |
| const char *filter_str, bool neg) |
| { |
| struct tracecmd_filter *trace_filter; |
| struct tep_handle *tep; |
| struct filter ***filter_ptr; |
| struct filter **filters; |
| struct filter *filter; |
| int *nr; |
| int ret; |
| |
| filter = calloc(1, sizeof(*filter)); |
| if (!filter) |
| return NULL; |
| |
| tep = tracecmd_get_tep(handle); |
| |
| trace_filter = tracecmd_filter_get(handle); |
| if (!trace_filter) { |
| trace_filter = calloc(1, sizeof(*trace_filter)); |
| if (!trace_filter) |
| goto fail; |
| tracecmd_filter_set(handle, trace_filter); |
| trace_filter->tep = tep; |
| } |
| |
| filter->filter = tep_filter_alloc(tep); |
| if (!filter->filter) |
| goto fail; |
| |
| ret = tep_filter_add_filter_str(filter->filter, filter_str); |
| if (ret < 0) |
| goto fail; |
| |
| if (neg) { |
| filter_ptr = &trace_filter->event_notrace; |
| nr = &trace_filter->nr_notrace; |
| } else { |
| filter_ptr = &trace_filter->event_filters; |
| nr = &trace_filter->nr_filters; |
| } |
| |
| filters = realloc(*filter_ptr, sizeof(*filters) * (*nr + 1)); |
| if (!filters) |
| goto fail; |
| |
| *filter_ptr = filters; |
| filters[*nr] = filter; |
| (*nr)++; |
| return trace_filter; |
| fail: |
| if (filter) { |
| tep_filter_free(filter->filter); |
| free(filter); |
| } |
| return NULL; |
| } |
| |
| static void free_filters (struct filter **filter, int nr) |
| { |
| int i; |
| |
| for (i = 0; i < nr; i++) { |
| tep_filter_free(filter[i]->filter); |
| free(filter[i]); |
| } |
| |
| free(filter); |
| } |
| |
| __hidden void tracecmd_filter_free(struct tracecmd_filter *trace_filter) |
| { |
| if (!trace_filter) |
| return; |
| |
| free_filters(trace_filter->event_filters, trace_filter->nr_filters); |
| free_filters(trace_filter->event_notrace, trace_filter->nr_notrace); |
| |
| free(trace_filter); |
| } |