diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2014-06-03 13:18:00 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2014-06-03 13:18:00 -0700 |
commit | 3d521f9151dacab566904d1f57dcb3e7080cdd8f (patch) | |
tree | 160d15ff955541c6ca27a69c8291a0269f105bb3 /tools/perf/tests | |
parent | 776edb59317ada867dfcddde40b55648beeb0078 (diff) | |
parent | e450f90e8c7d0bf70519223c1b848446ae63f313 (diff) | |
download | linux-3d521f9151dacab566904d1f57dcb3e7080cdd8f.tar.gz linux-3d521f9151dacab566904d1f57dcb3e7080cdd8f.tar.bz2 linux-3d521f9151dacab566904d1f57dcb3e7080cdd8f.zip |
Merge branch 'perf-core-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip into next
Pull perf updates from Ingo Molnar:
"The tooling changes maintained by Jiri Olsa until Arnaldo is on
vacation:
User visible changes:
- Add -F option for specifying output fields (Namhyung Kim)
- Propagate exit status of a command line workload for record command
(Namhyung Kim)
- Use tid for finding thread (Namhyung Kim)
- Clarify the output of perf sched map plus small sched command
fixes (Dongsheng Yang)
- Wire up perf_regs and unwind support for ARM64 (Jean Pihet)
- Factor hists statistics counts processing which in turn also fixes
several bugs in TUI report command (Namhyung Kim)
- Add --percentage option to control absolute/relative percentage
output (Namhyung Kim)
- Add --list-cmds to 'kmem', 'mem', 'lock' and 'sched', for use by
completion scripts (Ramkumar Ramachandra)
Development/infrastructure changes and fixes:
- Android related fixes for pager and map dso resolving (Michael
Lentine)
- Add libdw DWARF post unwind support for ARM (Jean Pihet)
- Consolidate types.h for ARM and ARM64 (Jean Pihet)
- Fix possible null pointer dereference in session.c (Masanari Iida)
- Cleanup, remove unused variables in map_switch_event() (Dongsheng
Yang)
- Remove nr_state_machine_bugs in perf latency (Dongsheng Yang)
- Remove usage of trace_sched_wakeup(.success) (Peter Zijlstra)
- Cleanups for perf.h header (Jiri Olsa)
- Consolidate types.h and export.h within tools (Borislav Petkov)
- Move u64_swap union to its single user's header, evsel.h (Borislav
Petkov)
- Fix for s390 to properly parse tracepoints plus test code
(Alexander Yarygin)
- Handle EINTR error for readn/writen (Namhyung Kim)
- Add a test case for hists filtering (Namhyung Kim)
- Share map_groups among threads of the same group (Arnaldo Carvalho
de Melo, Jiri Olsa)
- Making some code (cpu node map and report parse callchain callback)
global to be usable by upcomming changes (Don Zickus)
- Fix pmu object compilation error (Jiri Olsa)
Kernel side changes:
- intrusive uprobes fixes from Oleg Nesterov. Since the interface is
admin-only, and the bug only affects user-space ("any probed
jmp/call can kill the application"), we queued these fixes via the
development tree, as a special exception.
- more fuzzer motivated race fixes and related refactoring and
robustization.
- allow PMU drivers to be built as modules. (No actual module yet,
because the x86 Intel uncore module wasn't ready in time for this)"
* 'perf-core-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip: (114 commits)
perf tools: Add automatic remapping of Android libraries
perf tools: Add cat as fallback pager
perf tests: Add a testcase for histogram output sorting
perf tests: Factor out print_hists_*()
perf tools: Introduce reset_output_field()
perf tools: Get rid of obsolete hist_entry__sort_list
perf hists: Reset width of output fields with header length
perf tools: Skip elided sort entries
perf top: Add --fields option to specify output fields
perf report/tui: Fix a bug when --fields/sort is given
perf tools: Add ->sort() member to struct sort_entry
perf report: Add -F option to specify output fields
perf tools: Call perf_hpp__init() before setting up GUI browsers
perf tools: Consolidate management of default sort orders
perf tools: Allow hpp fields to be sort keys
perf ui: Get rid of callback from __hpp__fmt()
perf tools: Consolidate output field handling to hpp format routines
perf tools: Use hpp formats to sort final output
perf tools: Support event grouping in hpp ->sort()
perf tools: Use hpp formats to sort hist entries
...
Diffstat (limited to 'tools/perf/tests')
-rw-r--r-- | tools/perf/tests/attr.c | 7 | ||||
-rw-r--r-- | tools/perf/tests/builtin-test.c | 18 | ||||
-rw-r--r-- | tools/perf/tests/code-reading.c | 5 | ||||
-rw-r--r-- | tools/perf/tests/dso-data.c | 2 | ||||
-rw-r--r-- | tools/perf/tests/dwarf-unwind.c | 2 | ||||
-rw-r--r-- | tools/perf/tests/evsel-tp-sched.c | 3 | ||||
-rw-r--r-- | tools/perf/tests/hists_common.c | 205 | ||||
-rw-r--r-- | tools/perf/tests/hists_common.h | 47 | ||||
-rw-r--r-- | tools/perf/tests/hists_filter.c | 290 | ||||
-rw-r--r-- | tools/perf/tests/hists_link.c | 173 | ||||
-rw-r--r-- | tools/perf/tests/hists_output.c | 618 | ||||
-rw-r--r-- | tools/perf/tests/keep-tracking.c | 2 | ||||
-rw-r--r-- | tools/perf/tests/mmap-thread-lookup.c | 233 | ||||
-rw-r--r-- | tools/perf/tests/parse-events.c | 142 | ||||
-rw-r--r-- | tools/perf/tests/parse-no-sample-id-all.c | 2 | ||||
-rw-r--r-- | tools/perf/tests/perf-time-to-tsc.c | 3 | ||||
-rw-r--r-- | tools/perf/tests/rdpmc.c | 2 | ||||
-rw-r--r-- | tools/perf/tests/sample-parsing.c | 2 | ||||
-rw-r--r-- | tools/perf/tests/tests.h | 6 | ||||
-rw-r--r-- | tools/perf/tests/thread-mg-share.c | 90 |
20 files changed, 1617 insertions, 235 deletions
diff --git a/tools/perf/tests/attr.c b/tools/perf/tests/attr.c index 00218f503b2e..2dfc9ad0e6f2 100644 --- a/tools/perf/tests/attr.c +++ b/tools/perf/tests/attr.c @@ -1,4 +1,3 @@ - /* * The struct perf_event_attr test support. * @@ -19,14 +18,8 @@ * permissions. All the event text files are stored there. */ -/* - * Powerpc needs __SANE_USERSPACE_TYPES__ before <linux/types.h> to select - * 'int-ll64.h' and avoid compile warnings when printing __u64 with %llu. - */ -#define __SANE_USERSPACE_TYPES__ #include <stdlib.h> #include <stdio.h> -#include <inttypes.h> #include <linux/types.h> #include <linux/kernel.h> #include "../perf.h" diff --git a/tools/perf/tests/builtin-test.c b/tools/perf/tests/builtin-test.c index b11bf8a08430..831f52cae197 100644 --- a/tools/perf/tests/builtin-test.c +++ b/tools/perf/tests/builtin-test.c @@ -115,7 +115,7 @@ static struct test { .desc = "Test parsing with no sample_id_all bit set", .func = test__parse_no_sample_id_all, }, -#if defined(__x86_64__) || defined(__i386__) +#if defined(__x86_64__) || defined(__i386__) || defined(__arm__) #ifdef HAVE_DWARF_UNWIND_SUPPORT { .desc = "Test dwarf unwind", @@ -124,6 +124,22 @@ static struct test { #endif #endif { + .desc = "Test filtering hist entries", + .func = test__hists_filter, + }, + { + .desc = "Test mmap thread lookup", + .func = test__mmap_thread_lookup, + }, + { + .desc = "Test thread mg sharing", + .func = test__thread_mg_share, + }, + { + .desc = "Test output sorting of hist entries", + .func = test__hists_output, + }, + { .func = NULL, }, }; diff --git a/tools/perf/tests/code-reading.c b/tools/perf/tests/code-reading.c index bfb186900ac0..67f2d6323558 100644 --- a/tools/perf/tests/code-reading.c +++ b/tools/perf/tests/code-reading.c @@ -1,8 +1,7 @@ -#include <sys/types.h> +#include <linux/types.h> #include <stdlib.h> #include <unistd.h> #include <stdio.h> -#include <inttypes.h> #include <ctype.h> #include <string.h> @@ -257,7 +256,7 @@ static int process_sample_event(struct machine *machine, return -1; } - thread = machine__findnew_thread(machine, sample.pid, sample.pid); + thread = machine__findnew_thread(machine, sample.pid, sample.tid); if (!thread) { pr_debug("machine__findnew_thread failed\n"); return -1; diff --git a/tools/perf/tests/dso-data.c b/tools/perf/tests/dso-data.c index 9cc81a3eb9b4..3e6cb171e3d3 100644 --- a/tools/perf/tests/dso-data.c +++ b/tools/perf/tests/dso-data.c @@ -1,7 +1,7 @@ #include "util.h" #include <stdlib.h> -#include <sys/types.h> +#include <linux/types.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> diff --git a/tools/perf/tests/dwarf-unwind.c b/tools/perf/tests/dwarf-unwind.c index c059ee81c038..108f0cd49f4e 100644 --- a/tools/perf/tests/dwarf-unwind.c +++ b/tools/perf/tests/dwarf-unwind.c @@ -1,5 +1,5 @@ #include <linux/compiler.h> -#include <sys/types.h> +#include <linux/types.h> #include <unistd.h> #include "tests.h" #include "debug.h" diff --git a/tools/perf/tests/evsel-tp-sched.c b/tools/perf/tests/evsel-tp-sched.c index 4774f7fbb758..35d7fdb2328d 100644 --- a/tools/perf/tests/evsel-tp-sched.c +++ b/tools/perf/tests/evsel-tp-sched.c @@ -74,9 +74,6 @@ int test__perf_evsel__tp_sched_test(void) if (perf_evsel__test_field(evsel, "prio", 4, true)) ret = -1; - if (perf_evsel__test_field(evsel, "success", 4, true)) - ret = -1; - if (perf_evsel__test_field(evsel, "target_cpu", 4, true)) ret = -1; diff --git a/tools/perf/tests/hists_common.c b/tools/perf/tests/hists_common.c new file mode 100644 index 000000000000..e4e01aadc3be --- /dev/null +++ b/tools/perf/tests/hists_common.c @@ -0,0 +1,205 @@ +#include "perf.h" +#include "util/debug.h" +#include "util/symbol.h" +#include "util/sort.h" +#include "util/evsel.h" +#include "util/evlist.h" +#include "util/machine.h" +#include "util/thread.h" +#include "tests/hists_common.h" + +static struct { + u32 pid; + const char *comm; +} fake_threads[] = { + { 100, "perf" }, + { 200, "perf" }, + { 300, "bash" }, +}; + +static struct { + u32 pid; + u64 start; + const char *filename; +} fake_mmap_info[] = { + { 100, 0x40000, "perf" }, + { 100, 0x50000, "libc" }, + { 100, 0xf0000, "[kernel]" }, + { 200, 0x40000, "perf" }, + { 200, 0x50000, "libc" }, + { 200, 0xf0000, "[kernel]" }, + { 300, 0x40000, "bash" }, + { 300, 0x50000, "libc" }, + { 300, 0xf0000, "[kernel]" }, +}; + +struct fake_sym { + u64 start; + u64 length; + const char *name; +}; + +static struct fake_sym perf_syms[] = { + { 700, 100, "main" }, + { 800, 100, "run_command" }, + { 900, 100, "cmd_record" }, +}; + +static struct fake_sym bash_syms[] = { + { 700, 100, "main" }, + { 800, 100, "xmalloc" }, + { 900, 100, "xfree" }, +}; + +static struct fake_sym libc_syms[] = { + { 700, 100, "malloc" }, + { 800, 100, "free" }, + { 900, 100, "realloc" }, +}; + +static struct fake_sym kernel_syms[] = { + { 700, 100, "schedule" }, + { 800, 100, "page_fault" }, + { 900, 100, "sys_perf_event_open" }, +}; + +static struct { + const char *dso_name; + struct fake_sym *syms; + size_t nr_syms; +} fake_symbols[] = { + { "perf", perf_syms, ARRAY_SIZE(perf_syms) }, + { "bash", bash_syms, ARRAY_SIZE(bash_syms) }, + { "libc", libc_syms, ARRAY_SIZE(libc_syms) }, + { "[kernel]", kernel_syms, ARRAY_SIZE(kernel_syms) }, +}; + +struct machine *setup_fake_machine(struct machines *machines) +{ + struct machine *machine = machines__find(machines, HOST_KERNEL_ID); + size_t i; + + if (machine == NULL) { + pr_debug("Not enough memory for machine setup\n"); + return NULL; + } + + for (i = 0; i < ARRAY_SIZE(fake_threads); i++) { + struct thread *thread; + + thread = machine__findnew_thread(machine, fake_threads[i].pid, + fake_threads[i].pid); + if (thread == NULL) + goto out; + + thread__set_comm(thread, fake_threads[i].comm, 0); + } + + for (i = 0; i < ARRAY_SIZE(fake_mmap_info); i++) { + union perf_event fake_mmap_event = { + .mmap = { + .header = { .misc = PERF_RECORD_MISC_USER, }, + .pid = fake_mmap_info[i].pid, + .tid = fake_mmap_info[i].pid, + .start = fake_mmap_info[i].start, + .len = 0x1000ULL, + .pgoff = 0ULL, + }, + }; + + strcpy(fake_mmap_event.mmap.filename, + fake_mmap_info[i].filename); + + machine__process_mmap_event(machine, &fake_mmap_event, NULL); + } + + for (i = 0; i < ARRAY_SIZE(fake_symbols); i++) { + size_t k; + struct dso *dso; + + dso = __dsos__findnew(&machine->user_dsos, + fake_symbols[i].dso_name); + if (dso == NULL) + goto out; + + /* emulate dso__load() */ + dso__set_loaded(dso, MAP__FUNCTION); + + for (k = 0; k < fake_symbols[i].nr_syms; k++) { + struct symbol *sym; + struct fake_sym *fsym = &fake_symbols[i].syms[k]; + + sym = symbol__new(fsym->start, fsym->length, + STB_GLOBAL, fsym->name); + if (sym == NULL) + goto out; + + symbols__insert(&dso->symbols[MAP__FUNCTION], sym); + } + } + + return machine; + +out: + pr_debug("Not enough memory for machine setup\n"); + machine__delete_threads(machine); + machine__delete(machine); + return NULL; +} + +void print_hists_in(struct hists *hists) +{ + int i = 0; + struct rb_root *root; + struct rb_node *node; + + if (sort__need_collapse) + root = &hists->entries_collapsed; + else + root = hists->entries_in; + + pr_info("----- %s --------\n", __func__); + node = rb_first(root); + while (node) { + struct hist_entry *he; + + he = rb_entry(node, struct hist_entry, rb_node_in); + + if (!he->filtered) { + pr_info("%2d: entry: %-8s [%-8s] %20s: period = %"PRIu64"\n", + i, thread__comm_str(he->thread), + he->ms.map->dso->short_name, + he->ms.sym->name, he->stat.period); + } + + i++; + node = rb_next(node); + } +} + +void print_hists_out(struct hists *hists) +{ + int i = 0; + struct rb_root *root; + struct rb_node *node; + + root = &hists->entries; + + pr_info("----- %s --------\n", __func__); + node = rb_first(root); + while (node) { + struct hist_entry *he; + + he = rb_entry(node, struct hist_entry, rb_node); + + if (!he->filtered) { + pr_info("%2d: entry: %8s:%5d [%-8s] %20s: period = %"PRIu64"\n", + i, thread__comm_str(he->thread), he->thread->tid, + he->ms.map->dso->short_name, + he->ms.sym->name, he->stat.period); + } + + i++; + node = rb_next(node); + } +} diff --git a/tools/perf/tests/hists_common.h b/tools/perf/tests/hists_common.h new file mode 100644 index 000000000000..1415ae69d7b6 --- /dev/null +++ b/tools/perf/tests/hists_common.h @@ -0,0 +1,47 @@ +#ifndef __PERF_TESTS__HISTS_COMMON_H__ +#define __PERF_TESTS__HISTS_COMMON_H__ + +struct machine; +struct machines; + +/* + * The setup_fake_machine() provides a test environment which consists + * of 3 processes that have 3 mappings and in turn, have 3 symbols + * respectively. See below table: + * + * Command: Pid Shared Object Symbol + * ............. ............. ................... + * perf: 100 perf main + * perf: 100 perf run_command + * perf: 100 perf comd_record + * perf: 100 libc malloc + * perf: 100 libc free + * perf: 100 libc realloc + * perf: 100 [kernel] schedule + * perf: 100 [kernel] page_fault + * perf: 100 [kernel] sys_perf_event_open + * perf: 200 perf main + * perf: 200 perf run_command + * perf: 200 perf comd_record + * perf: 200 libc malloc + * perf: 200 libc free + * perf: 200 libc realloc + * perf: 200 [kernel] schedule + * perf: 200 [kernel] page_fault + * perf: 200 [kernel] sys_perf_event_open + * bash: 300 bash main + * bash: 300 bash xmalloc + * bash: 300 bash xfree + * bash: 300 libc malloc + * bash: 300 libc free + * bash: 300 libc realloc + * bash: 300 [kernel] schedule + * bash: 300 [kernel] page_fault + * bash: 300 [kernel] sys_perf_event_open + */ +struct machine *setup_fake_machine(struct machines *machines); + +void print_hists_in(struct hists *hists); +void print_hists_out(struct hists *hists); + +#endif /* __PERF_TESTS__HISTS_COMMON_H__ */ diff --git a/tools/perf/tests/hists_filter.c b/tools/perf/tests/hists_filter.c new file mode 100644 index 000000000000..c5ba924a3581 --- /dev/null +++ b/tools/perf/tests/hists_filter.c @@ -0,0 +1,290 @@ +#include "perf.h" +#include "util/debug.h" +#include "util/symbol.h" +#include "util/sort.h" +#include "util/evsel.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" + +struct sample { + u32 pid; + u64 ip; + struct thread *thread; + struct map *map; + struct symbol *sym; +}; + +/* For the numbers, see hists_common.c */ +static struct sample fake_samples[] = { + /* perf [kernel] schedule() */ + { .pid = 100, .ip = 0xf0000 + 700, }, + /* perf [perf] main() */ + { .pid = 100, .ip = 0x40000 + 700, }, + /* perf [libc] malloc() */ + { .pid = 100, .ip = 0x50000 + 700, }, + /* perf [perf] main() */ + { .pid = 200, .ip = 0x40000 + 700, }, /* will be merged */ + /* perf [perf] cmd_record() */ + { .pid = 200, .ip = 0x40000 + 900, }, + /* perf [kernel] page_fault() */ + { .pid = 200, .ip = 0xf0000 + 800, }, + /* bash [bash] main() */ + { .pid = 300, .ip = 0x40000 + 700, }, + /* bash [bash] xmalloc() */ + { .pid = 300, .ip = 0x40000 + 800, }, + /* bash [libc] malloc() */ + { .pid = 300, .ip = 0x50000 + 700, }, + /* bash [kernel] page_fault() */ + { .pid = 300, .ip = 0xf0000 + 800, }, +}; + +static int add_hist_entries(struct perf_evlist *evlist, struct machine *machine) +{ + struct perf_evsel *evsel; + struct addr_location al; + struct hist_entry *he; + struct perf_sample sample = { .cpu = 0, }; + 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(evlist, evsel) { + for (i = 0; i < ARRAY_SIZE(fake_samples); i++) { + const union perf_event event = { + .header = { + .misc = PERF_RECORD_MISC_USER, + }, + }; + + /* make sure it has no filter at first */ + evsel->hists.thread_filter = NULL; + evsel->hists.dso_filter = NULL; + evsel->hists.symbol_filter_str = NULL; + + sample.pid = fake_samples[i].pid; + sample.tid = fake_samples[i].pid; + sample.ip = fake_samples[i].ip; + + if (perf_event__preprocess_sample(&event, machine, &al, + &sample) < 0) + goto out; + + he = __hists__add_entry(&evsel->hists, &al, NULL, + NULL, NULL, 100, 1, 0); + if (he == NULL) + goto out; + + fake_samples[i].thread = al.thread; + fake_samples[i].map = al.map; + fake_samples[i].sym = al.sym; + + hists__inc_nr_events(he->hists, PERF_RECORD_SAMPLE); + if (!he->filtered) + he->hists->stats.nr_non_filtered_samples++; + } + } + + return 0; + +out: + pr_debug("Not enough memory for adding a hist entry\n"); + return TEST_FAIL; +} + +int test__hists_filter(void) +{ + 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"); + if (err) + goto out; + err = parse_events(evlist, "task-clock"); + if (err) + goto out; + + /* default sort order (comm,dso,sym) will be used */ + if (setup_sorting() < 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(evlist, evsel) { + struct hists *hists = &evsel->hists; + + hists__collapse_resort(hists, NULL); + hists__output_resort(hists); + + 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' */ + evsel->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 */ + evsel->hists.thread_filter = NULL; + hists__filter_by_thread(hists); + + /* now applying dso filter for 'kernel' */ + evsel->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 */ + evsel->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. + */ + evsel->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); + + /* now applying all filters at once. */ + evsel->hists.thread_filter = fake_samples[1].thread; + evsel->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; +} diff --git a/tools/perf/tests/hists_link.c b/tools/perf/tests/hists_link.c index 7ccbc7b6ae77..5ffa2c3eb77d 100644 --- a/tools/perf/tests/hists_link.c +++ b/tools/perf/tests/hists_link.c @@ -8,145 +8,7 @@ #include "machine.h" #include "thread.h" #include "parse-events.h" - -static struct { - u32 pid; - const char *comm; -} fake_threads[] = { - { 100, "perf" }, - { 200, "perf" }, - { 300, "bash" }, -}; - -static struct { - u32 pid; - u64 start; - const char *filename; -} fake_mmap_info[] = { - { 100, 0x40000, "perf" }, - { 100, 0x50000, "libc" }, - { 100, 0xf0000, "[kernel]" }, - { 200, 0x40000, "perf" }, - { 200, 0x50000, "libc" }, - { 200, 0xf0000, "[kernel]" }, - { 300, 0x40000, "bash" }, - { 300, 0x50000, "libc" }, - { 300, 0xf0000, "[kernel]" }, -}; - -struct fake_sym { - u64 start; - u64 length; - const char *name; -}; - -static struct fake_sym perf_syms[] = { - { 700, 100, "main" }, - { 800, 100, "run_command" }, - { 900, 100, "cmd_record" }, -}; - -static struct fake_sym bash_syms[] = { - { 700, 100, "main" }, - { 800, 100, "xmalloc" }, - { 900, 100, "xfree" }, -}; - -static struct fake_sym libc_syms[] = { - { 700, 100, "malloc" }, - { 800, 100, "free" }, - { 900, 100, "realloc" }, -}; - -static struct fake_sym kernel_syms[] = { - { 700, 100, "schedule" }, - { 800, 100, "page_fault" }, - { 900, 100, "sys_perf_event_open" }, -}; - -static struct { - const char *dso_name; - struct fake_sym *syms; - size_t nr_syms; -} fake_symbols[] = { - { "perf", perf_syms, ARRAY_SIZE(perf_syms) }, - { "bash", bash_syms, ARRAY_SIZE(bash_syms) }, - { "libc", libc_syms, ARRAY_SIZE(libc_syms) }, - { "[kernel]", kernel_syms, ARRAY_SIZE(kernel_syms) }, -}; - -static struct machine *setup_fake_machine(struct machines *machines) -{ - struct machine *machine = machines__find(machines, HOST_KERNEL_ID); - size_t i; - - if (machine == NULL) { - pr_debug("Not enough memory for machine setup\n"); - return NULL; - } - - for (i = 0; i < ARRAY_SIZE(fake_threads); i++) { - struct thread *thread; - - thread = machine__findnew_thread(machine, fake_threads[i].pid, - fake_threads[i].pid); - if (thread == NULL) - goto out; - - thread__set_comm(thread, fake_threads[i].comm, 0); - } - - for (i = 0; i < ARRAY_SIZE(fake_mmap_info); i++) { - union perf_event fake_mmap_event = { - .mmap = { - .header = { .misc = PERF_RECORD_MISC_USER, }, - .pid = fake_mmap_info[i].pid, - .tid = fake_mmap_info[i].pid, - .start = fake_mmap_info[i].start, - .len = 0x1000ULL, - .pgoff = 0ULL, - }, - }; - - strcpy(fake_mmap_event.mmap.filename, - fake_mmap_info[i].filename); - - machine__process_mmap_event(machine, &fake_mmap_event, NULL); - } - - for (i = 0; i < ARRAY_SIZE(fake_symbols); i++) { - size_t k; - struct dso *dso; - - dso = __dsos__findnew(&machine->user_dsos, - fake_symbols[i].dso_name); - if (dso == NULL) - goto out; - - /* emulate dso__load() */ - dso__set_loaded(dso, MAP__FUNCTION); - - for (k = 0; k < fake_symbols[i].nr_syms; k++) { - struct symbol *sym; - struct fake_sym *fsym = &fake_symbols[i].syms[k]; - - sym = symbol__new(fsym->start, fsym->length, - STB_GLOBAL, fsym->name); - if (sym == NULL) - goto out; - - symbols__insert(&dso->symbols[MAP__FUNCTION], sym); - } - } - - return machine; - -out: - pr_debug("Not enough memory for machine setup\n"); - machine__delete_threads(machine); - machine__delete(machine); - return NULL; -} +#include "hists_common.h" struct sample { u32 pid; @@ -156,6 +18,7 @@ struct sample { struct symbol *sym; }; +/* For the numbers, see hists_common.c */ static struct sample fake_common_samples[] = { /* perf [kernel] schedule() */ { .pid = 100, .ip = 0xf0000 + 700, }, @@ -218,6 +81,7 @@ static int add_hist_entries(struct perf_evlist *evlist, struct machine *machine) }; sample.pid = fake_common_samples[k].pid; + sample.tid = fake_common_samples[k].pid; sample.ip = fake_common_samples[k].ip; if (perf_event__preprocess_sample(&event, machine, &al, &sample) < 0) @@ -241,6 +105,7 @@ static int add_hist_entries(struct perf_evlist *evlist, struct machine *machine) }; sample.pid = fake_samples[i][k].pid; + sample.tid = fake_samples[i][k].pid; sample.ip = fake_samples[i][k].ip; if (perf_event__preprocess_sample(&event, machine, &al, &sample) < 0) @@ -403,33 +268,6 @@ static int validate_link(struct hists *leader, struct hists *other) return __validate_link(leader, 0) || __validate_link(other, 1); } -static void print_hists(struct hists *hists) -{ - int i = 0; - struct rb_root *root; - struct rb_node *node; - - if (sort__need_collapse) - root = &hists->entries_collapsed; - else - root = hists->entries_in; - - pr_info("----- %s --------\n", __func__); - node = rb_first(root); - while (node) { - struct hist_entry *he; - - he = rb_entry(node, struct hist_entry, rb_node_in); - - pr_info("%2d: entry: %-8s [%-8s] %20s: period = %"PRIu64"\n", - i, thread__comm_str(he->thread), he->ms.map->dso->short_name, - he->ms.sym->name, he->stat.period); - - i++; - node = rb_next(node); - } -} - int test__hists_link(void) { int err = -1; @@ -471,7 +309,7 @@ int test__hists_link(void) hists__collapse_resort(&evsel->hists, NULL); if (verbose > 2) - print_hists(&evsel->hists); + print_hists_in(&evsel->hists); } first = perf_evlist__first(evlist); @@ -494,6 +332,7 @@ int test__hists_link(void) out: /* tear down everything */ perf_evlist__delete(evlist); + reset_output_field(); machines__exit(&machines); return err; diff --git a/tools/perf/tests/hists_output.c b/tools/perf/tests/hists_output.c new file mode 100644 index 000000000000..a16850551797 --- /dev/null +++ b/tools/perf/tests/hists_output.c @@ -0,0 +1,618 @@ +#include "perf.h" +#include "util/debug.h" +#include "util/symbol.h" +#include "util/sort.h" +#include "util/evsel.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" + +struct sample { + u32 cpu; + u32 pid; + u64 ip; + struct thread *thread; + struct map *map; + struct symbol *sym; +}; + +/* For the numbers, see hists_common.c */ +static struct sample fake_samples[] = { + /* perf [kernel] schedule() */ + { .cpu = 0, .pid = 100, .ip = 0xf0000 + 700, }, + /* perf [perf] main() */ + { .cpu = 1, .pid = 100, .ip = 0x40000 + 700, }, + /* perf [perf] cmd_record() */ + { .cpu = 1, .pid = 100, .ip = 0x40000 + 900, }, + /* perf [libc] malloc() */ + { .cpu = 1, .pid = 100, .ip = 0x50000 + 700, }, + /* perf [libc] free() */ + { .cpu = 2, .pid = 100, .ip = 0x50000 + 800, }, + /* perf [perf] main() */ + { .cpu = 2, .pid = 200, .ip = 0x40000 + 700, }, + /* perf [kernel] page_fault() */ + { .cpu = 2, .pid = 200, .ip = 0xf0000 + 800, }, + /* bash [bash] main() */ + { .cpu = 3, .pid = 300, .ip = 0x40000 + 700, }, + /* bash [bash] xmalloc() */ + { .cpu = 0, .pid = 300, .ip = 0x40000 + 800, }, + /* bash [kernel] page_fault() */ + { .cpu = 1, .pid = 300, .ip = 0xf0000 + 800, }, +}; + +static int add_hist_entries(struct hists *hists, struct machine *machine) +{ + struct addr_location al; + struct hist_entry *he; + struct perf_sample sample = { .period = 100, }; + size_t i; + + for (i = 0; i < ARRAY_SIZE(fake_samples); i++) { + const union perf_event event = { + .header = { + .misc = PERF_RECORD_MISC_USER, + }, + }; + + sample.cpu = fake_samples[i].cpu; + sample.pid = fake_samples[i].pid; + sample.tid = fake_samples[i].pid; + sample.ip = fake_samples[i].ip; + + if (perf_event__preprocess_sample(&event, machine, &al, + &sample) < 0) + goto out; + + he = __hists__add_entry(hists, &al, NULL, NULL, NULL, + sample.period, 1, 0); + if (he == NULL) + goto out; + + fake_samples[i].thread = al.thread; + fake_samples[i].map = al.map; + fake_samples[i].sym = al.sym; + } + + return TEST_OK; + +out: + pr_debug("Not enough memory for adding a hist entry\n"); + return TEST_FAIL; +} + +static void del_hist_entries(struct hists *hists) +{ + struct hist_entry *he; + struct rb_root *root_in; + struct rb_root *root_out; + struct rb_node *node; + + if (sort__need_collapse) + root_in = &hists->entries_collapsed; + else + root_in = hists->entries_in; + + root_out = &hists->entries; + + while (!RB_EMPTY_ROOT(root_out)) { + node = rb_first(root_out); + + he = rb_entry(node, struct hist_entry, rb_node); + rb_erase(node, root_out); + rb_erase(&he->rb_node_in, root_in); + hist_entry__free(he); + } +} + +typedef int (*test_fn_t)(struct perf_evsel *, struct machine *); + +#define COMM(he) (thread__comm_str(he->thread)) +#define DSO(he) (he->ms.map->dso->short_name) +#define SYM(he) (he->ms.sym->name) +#define CPU(he) (he->cpu) +#define PID(he) (he->thread->tid) + +/* default sort keys (no field) */ +static int test1(struct perf_evsel *evsel, struct machine *machine) +{ + int err; + struct hists *hists = &evsel->hists; + struct hist_entry *he; + struct rb_root *root; + struct rb_node *node; + + field_order = NULL; + sort_order = NULL; /* equivalent to sort_order = "comm,dso,sym" */ + + setup_sorting(); + + /* + * expected output: + * + * Overhead Command Shared Object Symbol + * ======== ======= ============= ============== + * 20.00% perf perf [.] main + * 10.00% bash [kernel] [k] page_fault + * 10.00% bash bash [.] main + * 10.00% bash bash [.] xmalloc + * 10.00% perf [kernel] [k] page_fault + * 10.00% perf [kernel] [k] schedule + * 10.00% perf libc [.] free + * 10.00% perf libc [.] malloc + * 10.00% perf perf [.] cmd_record + */ + err = add_hist_entries(hists, machine); + if (err < 0) + goto out; + + hists__collapse_resort(hists, NULL); + hists__output_resort(hists); + + if (verbose > 2) { + pr_info("[fields = %s, sort = %s]\n", field_order, sort_order); + print_hists_out(hists); + } + + root = &evsel->hists.entries; + node = rb_first(root); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "perf") && + !strcmp(SYM(he), "main") && he->stat.period == 200); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(COMM(he), "bash") && !strcmp(DSO(he), "[kernel]") && + !strcmp(SYM(he), "page_fault") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(COMM(he), "bash") && !strcmp(DSO(he), "bash") && + !strcmp(SYM(he), "main") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(COMM(he), "bash") && !strcmp(DSO(he), "bash") && + !strcmp(SYM(he), "xmalloc") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "[kernel]") && + !strcmp(SYM(he), "page_fault") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "[kernel]") && + !strcmp(SYM(he), "schedule") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "libc") && + !strcmp(SYM(he), "free") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "libc") && + !strcmp(SYM(he), "malloc") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "perf") && + !strcmp(SYM(he), "cmd_record") && he->stat.period == 100); + +out: + del_hist_entries(hists); + reset_output_field(); + return err; +} + +/* mixed fields and sort keys */ +static int test2(struct perf_evsel *evsel, struct machine *machine) +{ + int err; + struct hists *hists = &evsel->hists; + struct hist_entry *he; + struct rb_root *root; + struct rb_node *node; + + field_order = "overhead,cpu"; + sort_order = "pid"; + + setup_sorting(); + + /* + * expected output: + * + * Overhead CPU Command: Pid + * ======== === ============= + * 30.00% 1 perf : 100 + * 10.00% 0 perf : 100 + * 10.00% 2 perf : 100 + * 20.00% 2 perf : 200 + * 10.00% 0 bash : 300 + * 10.00% 1 bash : 300 + * 10.00% 3 bash : 300 + */ + err = add_hist_entries(hists, machine); + if (err < 0) + goto out; + + hists__collapse_resort(hists, NULL); + hists__output_resort(hists); + + if (verbose > 2) { + pr_info("[fields = %s, sort = %s]\n", field_order, sort_order); + print_hists_out(hists); + } + + root = &evsel->hists.entries; + node = rb_first(root); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + CPU(he) == 1 && PID(he) == 100 && he->stat.period == 300); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + CPU(he) == 0 && PID(he) == 100 && he->stat.period == 100); + +out: + del_hist_entries(hists); + reset_output_field(); + return err; +} + +/* fields only (no sort key) */ +static int test3(struct perf_evsel *evsel, struct machine *machine) +{ + int err; + struct hists *hists = &evsel->hists; + struct hist_entry *he; + struct rb_root *root; + struct rb_node *node; + + field_order = "comm,overhead,dso"; + sort_order = NULL; + + setup_sorting(); + + /* + * expected output: + * + * Command Overhead Shared Object + * ======= ======== ============= + * bash 20.00% bash + * bash 10.00% [kernel] + * perf 30.00% perf + * perf 20.00% [kernel] + * perf 20.00% libc + */ + err = add_hist_entries(hists, machine); + if (err < 0) + goto out; + + hists__collapse_resort(hists, NULL); + hists__output_resort(hists); + + if (verbose > 2) { + pr_info("[fields = %s, sort = %s]\n", field_order, sort_order); + print_hists_out(hists); + } + + root = &evsel->hists.entries; + node = rb_first(root); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(COMM(he), "bash") && !strcmp(DSO(he), "bash") && + he->stat.period == 200); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(COMM(he), "bash") && !strcmp(DSO(he), "[kernel]") && + he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "perf") && + he->stat.period == 300); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "[kernel]") && + he->stat.period == 200); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "libc") && + he->stat.period == 200); + +out: + del_hist_entries(hists); + reset_output_field(); + return err; +} + +/* handle duplicate 'dso' field */ +static int test4(struct perf_evsel *evsel, struct machine *machine) +{ + int err; + struct hists *hists = &evsel->hists; + struct hist_entry *he; + struct rb_root *root; + struct rb_node *node; + + field_order = "dso,sym,comm,overhead,dso"; + sort_order = "sym"; + + setup_sorting(); + + /* + * expected output: + * + * Shared Object Symbol Command Overhead + * ============= ============== ======= ======== + * perf [.] cmd_record perf 10.00% + * libc [.] free perf 10.00% + * bash [.] main bash 10.00% + * perf [.] main perf 20.00% + * libc [.] malloc perf 10.00% + * [kernel] [k] page_fault bash 10.00% + * [kernel] [k] page_fault perf 10.00% + * [kernel] [k] schedule perf 10.00% + * bash [.] xmalloc bash 10.00% + */ + err = add_hist_entries(hists, machine); + if (err < 0) + goto out; + + hists__collapse_resort(hists, NULL); + hists__output_resort(hists); + + if (verbose > 2) { + pr_info("[fields = %s, sort = %s]\n", field_order, sort_order); + print_hists_out(hists); + } + + root = &evsel->hists.entries; + node = rb_first(root); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(DSO(he), "perf") && !strcmp(SYM(he), "cmd_record") && + !strcmp(COMM(he), "perf") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(DSO(he), "libc") && !strcmp(SYM(he), "free") && + !strcmp(COMM(he), "perf") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(DSO(he), "bash") && !strcmp(SYM(he), "main") && + !strcmp(COMM(he), "bash") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(DSO(he), "perf") && !strcmp(SYM(he), "main") && + !strcmp(COMM(he), "perf") && he->stat.period == 200); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(DSO(he), "libc") && !strcmp(SYM(he), "malloc") && + !strcmp(COMM(he), "perf") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(DSO(he), "[kernel]") && !strcmp(SYM(he), "page_fault") && + !strcmp(COMM(he), "bash") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(DSO(he), "[kernel]") && !strcmp(SYM(he), "page_fault") && + !strcmp(COMM(he), "perf") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(DSO(he), "[kernel]") && !strcmp(SYM(he), "schedule") && + !strcmp(COMM(he), "perf") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(DSO(he), "bash") && !strcmp(SYM(he), "xmalloc") && + !strcmp(COMM(he), "bash") && he->stat.period == 100); + +out: + del_hist_entries(hists); + reset_output_field(); + return err; +} + +/* full sort keys w/o overhead field */ +static int test5(struct perf_evsel *evsel, struct machine *machine) +{ + int err; + struct hists *hists = &evsel->hists; + struct hist_entry *he; + struct rb_root *root; + struct rb_node *node; + + field_order = "cpu,pid,comm,dso,sym"; + sort_order = "dso,pid"; + + setup_sorting(); + + /* + * expected output: + * + * CPU Command: Pid Command Shared Object Symbol + * === ============= ======= ============= ============== + * 0 perf: 100 perf [kernel] [k] schedule + * 2 perf: 200 perf [kernel] [k] page_fault + * 1 bash: 300 bash [kernel] [k] page_fault + * 0 bash: 300 bash bash [.] xmalloc + * 3 bash: 300 bash bash [.] main + * 1 perf: 100 perf libc [.] malloc + * 2 perf: 100 perf libc [.] free + * 1 perf: 100 perf perf [.] cmd_record + * 1 perf: 100 perf perf [.] main + * 2 perf: 200 perf perf [.] main + */ + err = add_hist_entries(hists, machine); + if (err < 0) + goto out; + + hists__collapse_resort(hists, NULL); + hists__output_resort(hists); + + if (verbose > 2) { + pr_info("[fields = %s, sort = %s]\n", field_order, sort_order); + print_hists_out(hists); + } + + root = &evsel->hists.entries; + node = rb_first(root); + he = rb_entry(node, struct hist_entry, rb_node); + + TEST_ASSERT_VAL("Invalid hist entry", + CPU(he) == 0 && PID(he) == 100 && + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "[kernel]") && + !strcmp(SYM(he), "schedule") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + CPU(he) == 2 && PID(he) == 200 && + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "[kernel]") && + !strcmp(SYM(he), "page_fault") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + CPU(he) == 1 && PID(he) == 300 && + !strcmp(COMM(he), "bash") && !strcmp(DSO(he), "[kernel]") && + !strcmp(SYM(he), "page_fault") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + CPU(he) == 0 && PID(he) == 300 && + !strcmp(COMM(he), "bash") && !strcmp(DSO(he), "bash") && + !strcmp(SYM(he), "xmalloc") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + CPU(he) == 3 && PID(he) == 300 && + !strcmp(COMM(he), "bash") && !strcmp(DSO(he), "bash") && + !strcmp(SYM(he), "main") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + CPU(he) == 1 && PID(he) == 100 && + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "libc") && + !strcmp(SYM(he), "malloc") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + CPU(he) == 2 && PID(he) == 100 && + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "libc") && + !strcmp(SYM(he), "free") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + CPU(he) == 1 && PID(he) == 100 && + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "perf") && + !strcmp(SYM(he), "cmd_record") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + CPU(he) == 1 && PID(he) == 100 && + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "perf") && + !strcmp(SYM(he), "main") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + CPU(he) == 2 && PID(he) == 200 && + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "perf") && + !strcmp(SYM(he), "main") && he->stat.period == 100); + +out: + del_hist_entries(hists); + reset_output_field(); + return err; +} + +int test__hists_output(void) +{ + int err = TEST_FAIL; + struct machines machines; + struct machine *machine; + struct perf_evsel *evsel; + struct perf_evlist *evlist = perf_evlist__new(); + size_t i; + test_fn_t testcases[] = { + test1, + test2, + test3, + test4, + test5, + }; + + TEST_ASSERT_VAL("No memory", evlist); + + err = parse_events(evlist, "cpu-clock"); + if (err) + 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); + + evsel = perf_evlist__first(evlist); + + for (i = 0; i < ARRAY_SIZE(testcases); i++) { + err = testcases[i](evsel, machine); + if (err < 0) + break; + } + +out: + /* tear down everything */ + perf_evlist__delete(evlist); + machines__exit(&machines); + + return err; +} diff --git a/tools/perf/tests/keep-tracking.c b/tools/perf/tests/keep-tracking.c index 497957f269d8..7a5ab7b0b8f6 100644 --- a/tools/perf/tests/keep-tracking.c +++ b/tools/perf/tests/keep-tracking.c @@ -1,4 +1,4 @@ -#include <sys/types.h> +#include <linux/types.h> #include <unistd.h> #include <sys/prctl.h> diff --git a/tools/perf/tests/mmap-thread-lookup.c b/tools/perf/tests/mmap-thread-lookup.c new file mode 100644 index 000000000000..4a456fef66ca --- /dev/null +++ b/tools/perf/tests/mmap-thread-lookup.c @@ -0,0 +1,233 @@ +#include <unistd.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <sys/mman.h> +#include <pthread.h> +#include <stdlib.h> +#include <stdio.h> +#include "debug.h" +#include "tests.h" +#include "machine.h" +#include "thread_map.h" +#include "symbol.h" +#include "thread.h" + +#define THREADS 4 + +static int go_away; + +struct thread_data { + pthread_t pt; + pid_t tid; + void *map; + int ready[2]; +}; + +static struct thread_data threads[THREADS]; + +static int thread_init(struct thread_data *td) +{ + void *map; + + map = mmap(NULL, page_size, + PROT_READ|PROT_WRITE|PROT_EXEC, + MAP_SHARED|MAP_ANONYMOUS, -1, 0); + + if (map == MAP_FAILED) { + perror("mmap failed"); + return -1; + } + + td->map = map; + td->tid = syscall(SYS_gettid); + + pr_debug("tid = %d, map = %p\n", td->tid, map); + return 0; +} + +static void *thread_fn(void *arg) +{ + struct thread_data *td = arg; + ssize_t ret; + int go; + + if (thread_init(td)) + return NULL; + + /* Signal thread_create thread is initialized. */ + ret = write(td->ready[1], &go, sizeof(int)); + if (ret != sizeof(int)) { + pr_err("failed to notify\n"); + return NULL; + } + + while (!go_away) { + /* Waiting for main thread to kill us. */ + usleep(100); + } + + munmap(td->map, page_size); + return NULL; +} + +static int thread_create(int i) +{ + struct thread_data *td = &threads[i]; + int err, go; + + if (pipe(td->ready)) + return -1; + + err = pthread_create(&td->pt, NULL, thread_fn, td); + if (!err) { + /* Wait for thread initialization. */ + ssize_t ret = read(td->ready[0], &go, sizeof(int)); + err = ret != sizeof(int); + } + + close(td->ready[0]); + close(td->ready[1]); + return err; +} + +static int threads_create(void) +{ + struct thread_data *td0 = &threads[0]; + int i, err = 0; + + go_away = 0; + + /* 0 is main thread */ + if (thread_init(td0)) + return -1; + + for (i = 1; !err && i < THREADS; i++) + err = thread_create(i); + + return err; +} + +static int threads_destroy(void) +{ + struct thread_data *td0 = &threads[0]; + int i, err = 0; + + /* cleanup the main thread */ + munmap(td0->map, page_size); + + go_away = 1; + + for (i = 1; !err && i < THREADS; i++) + err = pthread_join(threads[i].pt, NULL); + + return err; +} + +typedef int (*synth_cb)(struct machine *machine); + +static int synth_all(struct machine *machine) +{ + return perf_event__synthesize_threads(NULL, + perf_event__process, + machine, 0); +} + +static int synth_process(struct machine *machine) +{ + struct thread_map *map; + int err; + + map = thread_map__new_by_pid(getpid()); + + err = perf_event__synthesize_thread_map(NULL, map, + perf_event__process, + machine, 0); + + thread_map__delete(map); + return err; +} + +static int mmap_events(synth_cb synth) +{ + struct machines machines; + struct machine *machine; + int err, i; + + /* + * The threads_create will not return before all threads + * are spawned and all created memory map. + * + * They will loop until threads_destroy is called, so we + * can safely run synthesizing function. + */ + TEST_ASSERT_VAL("failed to create threads", !threads_create()); + + machines__init(&machines); + machine = &machines.host; + + dump_trace = verbose > 1 ? 1 : 0; + + err = synth(machine); + + dump_trace = 0; + + TEST_ASSERT_VAL("failed to destroy threads", !threads_destroy()); + TEST_ASSERT_VAL("failed to synthesize maps", !err); + + /* + * All data is synthesized, try to find map for each + * thread object. + */ + for (i = 0; i < THREADS; i++) { + struct thread_data *td = &threads[i]; + struct addr_location al; + struct thread *thread; + + thread = machine__findnew_thread(machine, getpid(), td->tid); + + pr_debug("looking for map %p\n", td->map); + + thread__find_addr_map(thread, machine, + PERF_RECORD_MISC_USER, MAP__FUNCTION, + (unsigned long) (td->map + 1), &al); + + if (!al.map) { + pr_debug("failed, couldn't find map\n"); + err = -1; + break; + } + + pr_debug("map %p, addr %" PRIx64 "\n", al.map, al.map->start); + } + + machine__delete_threads(machine); + machines__exit(&machines); + return err; +} + +/* + * This test creates 'THREADS' number of threads (including + * main thread) and each thread creates memory map. + * + * When threads are created, we synthesize them with both + * (separate tests): + * perf_event__synthesize_thread_map (process based) + * perf_event__synthesize_threads (global) + * + * We test we can find all memory maps via: + * thread__find_addr_map + * + * by using all thread objects. + */ +int test__mmap_thread_lookup(void) +{ + /* perf_event__synthesize_threads synthesize */ + TEST_ASSERT_VAL("failed with sythesizing all", + !mmap_events(synth_all)); + + /* perf_event__synthesize_thread_map synthesize */ + TEST_ASSERT_VAL("failed with sythesizing process", + !mmap_events(synth_process)); + + return 0; +} diff --git a/tools/perf/tests/parse-events.c b/tools/perf/tests/parse-events.c index 8605ff5572ae..deba66955f8c 100644 --- a/tools/perf/tests/parse-events.c +++ b/tools/perf/tests/parse-events.c @@ -1174,188 +1174,240 @@ static int test__all_tracepoints(struct perf_evlist *evlist) struct evlist_test { const char *name; __u32 type; + const int id; int (*check)(struct perf_evlist *evlist); }; static struct evlist_test test__events[] = { - [0] = { + { .name = "syscalls:sys_enter_open", .check = test__checkevent_tracepoint, + .id = 0, }, - [1] = { + { .name = "syscalls:*", .check = test__checkevent_tracepoint_multi, + .id = 1, }, - [2] = { + { .name = "r1a", .check = test__checkevent_raw, + .id = 2, }, - [3] = { + { .name = "1:1", .check = test__checkevent_numeric, + .id = 3, }, - [4] = { + { .name = "instructions", .check = test__checkevent_symbolic_name, + .id = 4, }, - [5] = { + { .name = "cycles/period=100000,config2/", .check = test__checkevent_symbolic_name_config, + .id = 5, }, - [6] = { + { .name = "faults", .check = test__checkevent_symbolic_alias, + .id = 6, }, - [7] = { + { .name = "L1-dcache-load-miss", .check = test__checkevent_genhw, + .id = 7, }, - [8] = { + { .name = "mem:0", .check = test__checkevent_breakpoint, + .id = 8, }, - [9] = { + { .name = "mem:0:x", .check = test__checkevent_breakpoint_x, + .id = 9, }, - [10] = { + { .name = "mem:0:r", .check = test__checkevent_breakpoint_r, + .id = 10, }, - [11] = { + { .name = "mem:0:w", .check = test__checkevent_breakpoint_w, + .id = 11, }, - [12] = { + { .name = "syscalls:sys_enter_open:k", .check = test__checkevent_tracepoint_modifier, + .id = 12, }, - [13] = { + { .name = "syscalls:*:u", .check = test__checkevent_tracepoint_multi_modifier, + .id = 13, }, - [14] = { + { .name = "r1a:kp", .check = test__checkevent_raw_modifier, + .id = 14, }, - [15] = { + { .name = "1:1:hp", .check = test__checkevent_numeric_modifier, + .id = 15, }, - [16] = { + { .name = "instructions:h", .check = test__checkevent_symbolic_name_modifier, + .id = 16, }, - [17] = { + { .name = "faults:u", .check = test__checkevent_symbolic_alias_modifier, + .id = 17, }, - [18] = { + { .name = "L1-dcache-load-miss:kp", .check = test__checkevent_genhw_modifier, + .id = 18, }, - [19] = { + { .name = "mem:0:u", .check = test__checkevent_breakpoint_modifier, + .id = 19, }, - [20] = { + { .name = "mem:0:x:k", .check = test__checkevent_breakpoint_x_modifier, + .id = 20, }, - [21] = { + { .name = "mem:0:r:hp", .check = test__checkevent_breakpoint_r_modifier, + .id = 21, }, - [22] = { + { .name = "mem:0:w:up", .check = test__checkevent_breakpoint_w_modifier, + .id = 22, }, - [23] = { + { .name = "r1,syscalls:sys_enter_open:k,1:1:hp", .check = test__checkevent_list, + .id = 23, }, - [24] = { + { .name = "instructions:G", .check = test__checkevent_exclude_host_modifier, + .id = 24, }, - [25] = { + { .name = "instructions:H", .check = test__checkevent_exclude_guest_modifier, + .id = 25, }, - [26] = { + { .name = "mem:0:rw", .check = test__checkevent_breakpoint_rw, + .id = 26, }, - [27] = { + { .name = "mem:0:rw:kp", .check = test__checkevent_breakpoint_rw_modifier, + .id = 27, }, - [28] = { + { .name = "{instructions:k,cycles:upp}", .check = test__group1, + .id = 28, }, - [29] = { + { .name = "{faults:k,cache-references}:u,cycles:k", .check = test__group2, + .id = 29, }, - [30] = { + { .name = "group1{syscalls:sys_enter_open:H,cycles:kppp},group2{cycles,1:3}:G,instructions:u", .check = test__group3, + .id = 30, }, - [31] = { + { .name = "{cycles:u,instructions:kp}:p", .check = test__group4, + .id = 31, }, - [32] = { + { .name = "{cycles,instructions}:G,{cycles:G,instructions:G},cycles", .check = test__group5, + .id = 32, }, - [33] = { + { .name = "*:*", .check = test__all_tracepoints, + .id = 33, }, - [34] = { + { .name = "{cycles,cache-misses:G}:H", .check = test__group_gh1, + .id = 34, }, - [35] = { + { .name = "{cycles,cache-misses:H}:G", .check = test__group_gh2, + .id = 35, }, - [36] = { + { .name = "{cycles:G,cache-misses:H}:u", .check = test__group_gh3, + .id = 36, }, - [37] = { + { .name = "{cycles:G,cache-misses:H}:uG", .check = test__group_gh4, + .id = 37, }, - [38] = { + { .name = "{cycles,cache-misses,branch-misses}:S", .check = test__leader_sample1, + .id = 38, }, - [39] = { + { .name = "{instructions,branch-misses}:Su", .check = test__leader_sample2, + .id = 39, }, - [40] = { + { .name = "instructions:uDp", .check = test__checkevent_pinned_modifier, + .id = 40, }, - [41] = { + { .name = "{cycles,cache-misses,branch-misses}:D", .check = test__pinned_group, + .id = 41, + }, +#if defined(__s390x__) + { + .name = "kvm-s390:kvm_s390_create_vm", + .check = test__checkevent_tracepoint, + .id = 100, }, +#endif }; static struct evlist_test test__events_pmu[] = { - [0] = { + { .name = "cpu/config=10,config1,config2=3,period=1000/u", .check = test__checkevent_pmu, + .id = 0, }, - [1] = { + { .name = "cpu/config=1,name=krava/u,cpu/config=2/u", .check = test__checkevent_pmu_name, + .id = 1, }, }; @@ -1402,7 +1454,7 @@ static int test_events(struct evlist_test *events, unsigned cnt) for (i = 0; i < cnt; i++) { struct evlist_test *e = &events[i]; - pr_debug("running test %d '%s'\n", i, e->name); + pr_debug("running test %d '%s'\n", e->id, e->name); ret1 = test_event(e); if (ret1) ret2 = ret1; diff --git a/tools/perf/tests/parse-no-sample-id-all.c b/tools/perf/tests/parse-no-sample-id-all.c index e117b6c6a248..905019f9b740 100644 --- a/tools/perf/tests/parse-no-sample-id-all.c +++ b/tools/perf/tests/parse-no-sample-id-all.c @@ -1,4 +1,4 @@ -#include <sys/types.h> +#include <linux/types.h> #include <stddef.h> #include "tests.h" diff --git a/tools/perf/tests/perf-time-to-tsc.c b/tools/perf/tests/perf-time-to-tsc.c index 47146d388dbf..3b7cd4d32dcb 100644 --- a/tools/perf/tests/perf-time-to-tsc.c +++ b/tools/perf/tests/perf-time-to-tsc.c @@ -1,7 +1,6 @@ #include <stdio.h> -#include <sys/types.h> #include <unistd.h> -#include <inttypes.h> +#include <linux/types.h> #include <sys/prctl.h> #include "parse-events.h" diff --git a/tools/perf/tests/rdpmc.c b/tools/perf/tests/rdpmc.c index 46649c25fa5e..e59143fd9e71 100644 --- a/tools/perf/tests/rdpmc.c +++ b/tools/perf/tests/rdpmc.c @@ -2,7 +2,7 @@ #include <stdlib.h> #include <signal.h> #include <sys/mman.h> -#include "types.h" +#include <linux/types.h> #include "perf.h" #include "debug.h" #include "tests.h" diff --git a/tools/perf/tests/sample-parsing.c b/tools/perf/tests/sample-parsing.c index 0014d3c8c21c..7ae8d17db3d9 100644 --- a/tools/perf/tests/sample-parsing.c +++ b/tools/perf/tests/sample-parsing.c @@ -1,5 +1,5 @@ #include <stdbool.h> -#include <inttypes.h> +#include <linux/types.h> #include "util.h" #include "event.h" diff --git a/tools/perf/tests/tests.h b/tools/perf/tests/tests.h index a24795ca002d..d76c0e2e6635 100644 --- a/tools/perf/tests/tests.h +++ b/tools/perf/tests/tests.h @@ -41,8 +41,12 @@ int test__sample_parsing(void); int test__keep_tracking(void); int test__parse_no_sample_id_all(void); int test__dwarf_unwind(void); +int test__hists_filter(void); +int test__mmap_thread_lookup(void); +int test__thread_mg_share(void); +int test__hists_output(void); -#if defined(__x86_64__) || defined(__i386__) +#if defined(__x86_64__) || defined(__i386__) || defined(__arm__) #ifdef HAVE_DWARF_UNWIND_SUPPORT struct thread; struct perf_sample; diff --git a/tools/perf/tests/thread-mg-share.c b/tools/perf/tests/thread-mg-share.c new file mode 100644 index 000000000000..2b2e0dbe114f --- /dev/null +++ b/tools/perf/tests/thread-mg-share.c @@ -0,0 +1,90 @@ +#include "tests.h" +#include "machine.h" +#include "thread.h" +#include "map.h" + +int test__thread_mg_share(void) +{ + struct machines machines; + struct machine *machine; + + /* thread group */ + struct thread *leader; + struct thread *t1, *t2, *t3; + struct map_groups *mg; + + /* other process */ + struct thread *other, *other_leader; + struct map_groups *other_mg; + + /* + * This test create 2 processes abstractions (struct thread) + * with several threads and checks they properly share and + * maintain map groups info (struct map_groups). + * + * thread group (pid: 0, tids: 0, 1, 2, 3) + * other group (pid: 4, tids: 4, 5) + */ + + machines__init(&machines); + machine = &machines.host; + + /* create process with 4 threads */ + leader = machine__findnew_thread(machine, 0, 0); + t1 = machine__findnew_thread(machine, 0, 1); + t2 = machine__findnew_thread(machine, 0, 2); + t3 = machine__findnew_thread(machine, 0, 3); + + /* and create 1 separated process, without thread leader */ + other = machine__findnew_thread(machine, 4, 5); + + TEST_ASSERT_VAL("failed to create threads", + leader && t1 && t2 && t3 && other); + + mg = leader->mg; + TEST_ASSERT_VAL("wrong refcnt", mg->refcnt == 4); + + /* test the map groups pointer is shared */ + TEST_ASSERT_VAL("map groups don't match", mg == t1->mg); + TEST_ASSERT_VAL("map groups don't match", mg == t2->mg); + TEST_ASSERT_VAL("map groups don't match", mg == t3->mg); + + /* + * Verify the other leader was created by previous call. + * It should have shared map groups with no change in + * refcnt. + */ + other_leader = machine__find_thread(machine, 4, 4); + TEST_ASSERT_VAL("failed to find other leader", other_leader); + + other_mg = other->mg; + TEST_ASSERT_VAL("wrong refcnt", other_mg->refcnt == 2); + + TEST_ASSERT_VAL("map groups don't match", other_mg == other_leader->mg); + + /* release thread group */ + thread__delete(leader); + TEST_ASSERT_VAL("wrong refcnt", mg->refcnt == 3); + + thread__delete(t1); + TEST_ASSERT_VAL("wrong refcnt", mg->refcnt == 2); + + thread__delete(t2); + TEST_ASSERT_VAL("wrong refcnt", mg->refcnt == 1); + + thread__delete(t3); + + /* release other group */ + thread__delete(other_leader); + TEST_ASSERT_VAL("wrong refcnt", other_mg->refcnt == 1); + + thread__delete(other); + + /* + * Cannot call machine__delete_threads(machine) now, + * because we've already released all the threads. + */ + + machines__exit(&machines); + return 0; +} |