| // SPDX-License-Identifier: GPL-2.0 |
| #include "perf.h" |
| #include "util/debug.h" |
| #include "util/symbol.h" |
| #include "util/sort.h" |
| #include "util/evsel.h" |
| #include "util/event.h" |
| #include "util/evlist.h" |
| #include "util/machine.h" |
| #include "util/thread.h" |
| #include "util/parse-events.h" |
| #include "tests/tests.h" |
| #include "tests/hists_common.h" |
| #include <linux/kernel.h> |
| |
| struct sample { |
| u32 pid; |
| u64 ip; |
| struct thread *thread; |
| struct map *map; |
| struct symbol *sym; |
| int socket; |
| }; |
| |
| /* For the numbers, see hists_common.c */ |
| static struct sample fake_samples[] = { |
| /* perf [kernel] schedule() */ |
| { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_KERNEL_SCHEDULE, .socket = 0 }, |
| /* perf [perf] main() */ |
| { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_PERF_MAIN, .socket = 0 }, |
| /* perf [libc] malloc() */ |
| { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_LIBC_MALLOC, .socket = 0 }, |
| /* perf [perf] main() */ |
| { .pid = FAKE_PID_PERF2, .ip = FAKE_IP_PERF_MAIN, .socket = 0 }, /* will be merged */ |
| /* perf [perf] cmd_record() */ |
| { .pid = FAKE_PID_PERF2, .ip = FAKE_IP_PERF_CMD_RECORD, .socket = 1 }, |
| /* perf [kernel] page_fault() */ |
| { .pid = FAKE_PID_PERF2, .ip = FAKE_IP_KERNEL_PAGE_FAULT, .socket = 1 }, |
| /* bash [bash] main() */ |
| { .pid = FAKE_PID_BASH, .ip = FAKE_IP_BASH_MAIN, .socket = 2 }, |
| /* bash [bash] xmalloc() */ |
| { .pid = FAKE_PID_BASH, .ip = FAKE_IP_BASH_XMALLOC, .socket = 2 }, |
| /* bash [libc] malloc() */ |
| { .pid = FAKE_PID_BASH, .ip = FAKE_IP_LIBC_MALLOC, .socket = 3 }, |
| /* bash [kernel] page_fault() */ |
| { .pid = FAKE_PID_BASH, .ip = FAKE_IP_KERNEL_PAGE_FAULT, .socket = 3 }, |
| }; |
| |
| static int add_hist_entries(struct perf_evlist *evlist, |
| struct machine *machine) |
| { |
| struct perf_evsel *evsel; |
| struct addr_location al; |
| struct perf_sample sample = { .period = 100, }; |
| size_t i; |
| |
| /* |
| * each evsel will have 10 samples but the 4th sample |
| * (perf [perf] main) will be collapsed to an existing entry |
| * so total 9 entries will be in the tree. |
| */ |
| evlist__for_each_entry(evlist, evsel) { |
| for (i = 0; i < ARRAY_SIZE(fake_samples); i++) { |
| struct hist_entry_iter iter = { |
| .evsel = evsel, |
| .sample = &sample, |
| .ops = &hist_iter_normal, |
| .hide_unresolved = false, |
| }; |
| struct hists *hists = evsel__hists(evsel); |
| |
| /* make sure it has no filter at first */ |
| hists->thread_filter = NULL; |
| hists->dso_filter = NULL; |
| hists->symbol_filter_str = NULL; |
| |
| sample.cpumode = PERF_RECORD_MISC_USER; |
| sample.pid = fake_samples[i].pid; |
| sample.tid = fake_samples[i].pid; |
| sample.ip = fake_samples[i].ip; |
| |
| if (machine__resolve(machine, &al, &sample) < 0) |
| goto out; |
| |
| al.socket = fake_samples[i].socket; |
| if (hist_entry_iter__add(&iter, &al, |
| sysctl_perf_event_max_stack, NULL) < 0) { |
| addr_location__put(&al); |
| goto out; |
| } |
| |
| fake_samples[i].thread = al.thread; |
| fake_samples[i].map = al.map; |
| fake_samples[i].sym = al.sym; |
| } |
| } |
| |
| return 0; |
| |
| out: |
| pr_debug("Not enough memory for adding a hist entry\n"); |
| return TEST_FAIL; |
| } |
| |
| int test__hists_filter(struct test *test __maybe_unused, int subtest __maybe_unused) |
| { |
| int err = TEST_FAIL; |
| struct machines machines; |
| struct machine *machine; |
| struct perf_evsel *evsel; |
| struct perf_evlist *evlist = perf_evlist__new(); |
| |
| TEST_ASSERT_VAL("No memory", evlist); |
| |
| err = parse_events(evlist, "cpu-clock", NULL); |
| if (err) |
| goto out; |
| err = parse_events(evlist, "task-clock", NULL); |
| if (err) |
| goto out; |
| err = TEST_FAIL; |
| |
| /* default sort order (comm,dso,sym) will be used */ |
| if (setup_sorting(NULL) < 0) |
| goto out; |
| |
| machines__init(&machines); |
| |
| /* setup threads/dso/map/symbols also */ |
| machine = setup_fake_machine(&machines); |
| if (!machine) |
| goto out; |
| |
| if (verbose > 1) |
| machine__fprintf(machine, stderr); |
| |
| /* process sample events */ |
| err = add_hist_entries(evlist, machine); |
| if (err < 0) |
| goto out; |
| |
| evlist__for_each_entry(evlist, evsel) { |
| struct hists *hists = evsel__hists(evsel); |
| |
| hists__collapse_resort(hists, NULL); |
| perf_evsel__output_resort(evsel, NULL); |
| |
| if (verbose > 2) { |
| pr_info("Normal histogram\n"); |
| print_hists_out(hists); |
| } |
| |
| TEST_ASSERT_VAL("Invalid nr samples", |
| hists->stats.nr_events[PERF_RECORD_SAMPLE] == 10); |
| TEST_ASSERT_VAL("Invalid nr hist entries", |
| hists->nr_entries == 9); |
| TEST_ASSERT_VAL("Invalid total period", |
| hists->stats.total_period == 1000); |
| TEST_ASSERT_VAL("Unmatched nr samples", |
| hists->stats.nr_events[PERF_RECORD_SAMPLE] == |
| hists->stats.nr_non_filtered_samples); |
| TEST_ASSERT_VAL("Unmatched nr hist entries", |
| hists->nr_entries == hists->nr_non_filtered_entries); |
| TEST_ASSERT_VAL("Unmatched total period", |
| hists->stats.total_period == |
| hists->stats.total_non_filtered_period); |
| |
| /* now applying thread filter for 'bash' */ |
| hists->thread_filter = fake_samples[9].thread; |
| hists__filter_by_thread(hists); |
| |
| if (verbose > 2) { |
| pr_info("Histogram for thread filter\n"); |
| print_hists_out(hists); |
| } |
| |
| /* normal stats should be invariant */ |
| TEST_ASSERT_VAL("Invalid nr samples", |
| hists->stats.nr_events[PERF_RECORD_SAMPLE] == 10); |
| TEST_ASSERT_VAL("Invalid nr hist entries", |
| hists->nr_entries == 9); |
| TEST_ASSERT_VAL("Invalid total period", |
| hists->stats.total_period == 1000); |
| |
| /* but filter stats are changed */ |
| TEST_ASSERT_VAL("Unmatched nr samples for thread filter", |
| hists->stats.nr_non_filtered_samples == 4); |
| TEST_ASSERT_VAL("Unmatched nr hist entries for thread filter", |
| hists->nr_non_filtered_entries == 4); |
| TEST_ASSERT_VAL("Unmatched total period for thread filter", |
| hists->stats.total_non_filtered_period == 400); |
| |
| /* remove thread filter first */ |
| hists->thread_filter = NULL; |
| hists__filter_by_thread(hists); |
| |
| /* now applying dso filter for 'kernel' */ |
| hists->dso_filter = fake_samples[0].map->dso; |
| hists__filter_by_dso(hists); |
| |
| if (verbose > 2) { |
| pr_info("Histogram for dso filter\n"); |
| print_hists_out(hists); |
| } |
| |
| /* normal stats should be invariant */ |
| TEST_ASSERT_VAL("Invalid nr samples", |
| hists->stats.nr_events[PERF_RECORD_SAMPLE] == 10); |
| TEST_ASSERT_VAL("Invalid nr hist entries", |
| hists->nr_entries == 9); |
| TEST_ASSERT_VAL("Invalid total period", |
| hists->stats.total_period == 1000); |
| |
| /* but filter stats are changed */ |
| TEST_ASSERT_VAL("Unmatched nr samples for dso filter", |
| hists->stats.nr_non_filtered_samples == 3); |
| TEST_ASSERT_VAL("Unmatched nr hist entries for dso filter", |
| hists->nr_non_filtered_entries == 3); |
| TEST_ASSERT_VAL("Unmatched total period for dso filter", |
| hists->stats.total_non_filtered_period == 300); |
| |
| /* remove dso filter first */ |
| hists->dso_filter = NULL; |
| hists__filter_by_dso(hists); |
| |
| /* |
| * now applying symbol filter for 'main'. Also note that |
| * there's 3 samples that have 'main' symbol but the 4th |
| * entry of fake_samples was collapsed already so it won't |
| * be counted as a separate entry but the sample count and |
| * total period will be remained. |
| */ |
| hists->symbol_filter_str = "main"; |
| hists__filter_by_symbol(hists); |
| |
| if (verbose > 2) { |
| pr_info("Histogram for symbol filter\n"); |
| print_hists_out(hists); |
| } |
| |
| /* normal stats should be invariant */ |
| TEST_ASSERT_VAL("Invalid nr samples", |
| hists->stats.nr_events[PERF_RECORD_SAMPLE] == 10); |
| TEST_ASSERT_VAL("Invalid nr hist entries", |
| hists->nr_entries == 9); |
| TEST_ASSERT_VAL("Invalid total period", |
| hists->stats.total_period == 1000); |
| |
| /* but filter stats are changed */ |
| TEST_ASSERT_VAL("Unmatched nr samples for symbol filter", |
| hists->stats.nr_non_filtered_samples == 3); |
| TEST_ASSERT_VAL("Unmatched nr hist entries for symbol filter", |
| hists->nr_non_filtered_entries == 2); |
| TEST_ASSERT_VAL("Unmatched total period for symbol filter", |
| hists->stats.total_non_filtered_period == 300); |
| |
| /* remove symbol filter first */ |
| hists->symbol_filter_str = NULL; |
| hists__filter_by_symbol(hists); |
| |
| /* now applying socket filters */ |
| hists->socket_filter = 2; |
| hists__filter_by_socket(hists); |
| |
| if (verbose > 2) { |
| pr_info("Histogram for socket filters\n"); |
| print_hists_out(hists); |
| } |
| |
| /* normal stats should be invariant */ |
| TEST_ASSERT_VAL("Invalid nr samples", |
| hists->stats.nr_events[PERF_RECORD_SAMPLE] == 10); |
| TEST_ASSERT_VAL("Invalid nr hist entries", |
| hists->nr_entries == 9); |
| TEST_ASSERT_VAL("Invalid total period", |
| hists->stats.total_period == 1000); |
| |
| /* but filter stats are changed */ |
| TEST_ASSERT_VAL("Unmatched nr samples for socket filter", |
| hists->stats.nr_non_filtered_samples == 2); |
| TEST_ASSERT_VAL("Unmatched nr hist entries for socket filter", |
| hists->nr_non_filtered_entries == 2); |
| TEST_ASSERT_VAL("Unmatched total period for socket filter", |
| hists->stats.total_non_filtered_period == 200); |
| |
| /* remove socket filter first */ |
| hists->socket_filter = -1; |
| hists__filter_by_socket(hists); |
| |
| /* now applying all filters at once. */ |
| hists->thread_filter = fake_samples[1].thread; |
| hists->dso_filter = fake_samples[1].map->dso; |
| hists__filter_by_thread(hists); |
| hists__filter_by_dso(hists); |
| |
| if (verbose > 2) { |
| pr_info("Histogram for all filters\n"); |
| print_hists_out(hists); |
| } |
| |
| /* normal stats should be invariant */ |
| TEST_ASSERT_VAL("Invalid nr samples", |
| hists->stats.nr_events[PERF_RECORD_SAMPLE] == 10); |
| TEST_ASSERT_VAL("Invalid nr hist entries", |
| hists->nr_entries == 9); |
| TEST_ASSERT_VAL("Invalid total period", |
| hists->stats.total_period == 1000); |
| |
| /* but filter stats are changed */ |
| TEST_ASSERT_VAL("Unmatched nr samples for all filter", |
| hists->stats.nr_non_filtered_samples == 2); |
| TEST_ASSERT_VAL("Unmatched nr hist entries for all filter", |
| hists->nr_non_filtered_entries == 1); |
| TEST_ASSERT_VAL("Unmatched total period for all filter", |
| hists->stats.total_non_filtered_period == 200); |
| } |
| |
| |
| err = TEST_OK; |
| |
| out: |
| /* tear down everything */ |
| perf_evlist__delete(evlist); |
| reset_output_field(); |
| machines__exit(&machines); |
| |
| return err; |
| } |