1// SPDX-License-Identifier: GPL-2.0
2#include "util/debug.h"
3#include "util/map.h"
4#include "util/symbol.h"
5#include "util/sort.h"
6#include "util/evsel.h"
7#include "util/event.h"
8#include "util/evlist.h"
9#include "util/machine.h"
10#include "util/parse-events.h"
11#include "util/thread.h"
12#include "tests/tests.h"
13#include "tests/hists_common.h"
14#include <linux/kernel.h>
15
16struct sample {
17	u32 pid;
18	u64 ip;
19	struct thread *thread;
20	struct map *map;
21	struct symbol *sym;
22	int socket;
23};
24
25/* For the numbers, see hists_common.c */
26static struct sample fake_samples[] = {
27	/* perf [kernel] schedule() */
28	{ .pid = FAKE_PID_PERF1, .ip = FAKE_IP_KERNEL_SCHEDULE, .socket = 0 },
29	/* perf [perf]   main() */
30	{ .pid = FAKE_PID_PERF1, .ip = FAKE_IP_PERF_MAIN, .socket = 0 },
31	/* perf [libc]   malloc() */
32	{ .pid = FAKE_PID_PERF1, .ip = FAKE_IP_LIBC_MALLOC, .socket = 0 },
33	/* perf [perf]   main() */
34	{ .pid = FAKE_PID_PERF2, .ip = FAKE_IP_PERF_MAIN, .socket = 0 }, /* will be merged */
35	/* perf [perf]   cmd_record() */
36	{ .pid = FAKE_PID_PERF2, .ip = FAKE_IP_PERF_CMD_RECORD, .socket = 1 },
37	/* perf [kernel] page_fault() */
38	{ .pid = FAKE_PID_PERF2, .ip = FAKE_IP_KERNEL_PAGE_FAULT, .socket = 1 },
39	/* bash [bash]   main() */
40	{ .pid = FAKE_PID_BASH,  .ip = FAKE_IP_BASH_MAIN, .socket = 2 },
41	/* bash [bash]   xmalloc() */
42	{ .pid = FAKE_PID_BASH,  .ip = FAKE_IP_BASH_XMALLOC, .socket = 2 },
43	/* bash [libc]   malloc() */
44	{ .pid = FAKE_PID_BASH,  .ip = FAKE_IP_LIBC_MALLOC, .socket = 3 },
45	/* bash [kernel] page_fault() */
46	{ .pid = FAKE_PID_BASH,  .ip = FAKE_IP_KERNEL_PAGE_FAULT, .socket = 3 },
47};
48
49static int add_hist_entries(struct evlist *evlist,
50			    struct machine *machine)
51{
52	struct evsel *evsel;
53	struct addr_location al;
54	struct perf_sample sample = { .period = 100, };
55	size_t i;
56
57	addr_location__init(&al);
58	/*
59	 * each evsel will have 10 samples but the 4th sample
60	 * (perf [perf] main) will be collapsed to an existing entry
61	 * so total 9 entries will be in the tree.
62	 */
63	evlist__for_each_entry(evlist, evsel) {
64		for (i = 0; i < ARRAY_SIZE(fake_samples); i++) {
65			struct hist_entry_iter iter = {
66				.evsel = evsel,
67				.sample = &sample,
68				.ops = &hist_iter_normal,
69				.hide_unresolved = false,
70			};
71			struct hists *hists = evsel__hists(evsel);
72
73			/* make sure it has no filter at first */
74			hists->thread_filter = NULL;
75			hists->dso_filter = NULL;
76			hists->symbol_filter_str = NULL;
77
78			sample.cpumode = PERF_RECORD_MISC_USER;
79			sample.pid = fake_samples[i].pid;
80			sample.tid = fake_samples[i].pid;
81			sample.ip = fake_samples[i].ip;
82
83			if (machine__resolve(machine, &al, &sample) < 0)
84				goto out;
85
86			al.socket = fake_samples[i].socket;
87			if (hist_entry_iter__add(&iter, &al,
88						 sysctl_perf_event_max_stack, NULL) < 0) {
89				goto out;
90			}
91
92			thread__put(fake_samples[i].thread);
93			fake_samples[i].thread = thread__get(al.thread);
94			map__put(fake_samples[i].map);
95			fake_samples[i].map = map__get(al.map);
96			fake_samples[i].sym = al.sym;
97		}
98	}
99	addr_location__exit(&al);
100	return 0;
101
102out:
103	pr_debug("Not enough memory for adding a hist entry\n");
104	addr_location__exit(&al);
105	return TEST_FAIL;
106}
107
108static void put_fake_samples(void)
109{
110	size_t i;
111
112	for (i = 0; i < ARRAY_SIZE(fake_samples); i++)
113		map__put(fake_samples[i].map);
114}
115
116static int test__hists_filter(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
117{
118	int err = TEST_FAIL;
119	struct machines machines;
120	struct machine *machine;
121	struct evsel *evsel;
122	struct evlist *evlist = evlist__new();
123
124	TEST_ASSERT_VAL("No memory", evlist);
125
126	err = parse_event(evlist, "cpu-clock");
127	if (err)
128		goto out;
129	err = parse_event(evlist, "task-clock");
130	if (err)
131		goto out;
132	err = TEST_FAIL;
133
134	/* default sort order (comm,dso,sym) will be used */
135	if (setup_sorting(NULL) < 0)
136		goto out;
137
138	machines__init(&machines);
139
140	/* setup threads/dso/map/symbols also */
141	machine = setup_fake_machine(&machines);
142	if (!machine)
143		goto out;
144
145	if (verbose > 1)
146		machine__fprintf(machine, stderr);
147
148	/* process sample events */
149	err = add_hist_entries(evlist, machine);
150	if (err < 0)
151		goto out;
152
153	evlist__for_each_entry(evlist, evsel) {
154		struct hists *hists = evsel__hists(evsel);
155
156		hists__collapse_resort(hists, NULL);
157		evsel__output_resort(evsel, NULL);
158
159		if (verbose > 2) {
160			pr_info("Normal histogram\n");
161			print_hists_out(hists);
162		}
163
164		TEST_ASSERT_VAL("Invalid nr samples",
165				hists->stats.nr_samples == 10);
166		TEST_ASSERT_VAL("Invalid nr hist entries",
167				hists->nr_entries == 9);
168		TEST_ASSERT_VAL("Invalid total period",
169				hists->stats.total_period == 1000);
170		TEST_ASSERT_VAL("Unmatched nr samples",
171				hists->stats.nr_samples ==
172				hists->stats.nr_non_filtered_samples);
173		TEST_ASSERT_VAL("Unmatched nr hist entries",
174				hists->nr_entries == hists->nr_non_filtered_entries);
175		TEST_ASSERT_VAL("Unmatched total period",
176				hists->stats.total_period ==
177				hists->stats.total_non_filtered_period);
178
179		/* now applying thread filter for 'bash' */
180		hists->thread_filter = fake_samples[9].thread;
181		hists__filter_by_thread(hists);
182
183		if (verbose > 2) {
184			pr_info("Histogram for thread filter\n");
185			print_hists_out(hists);
186		}
187
188		/* normal stats should be invariant */
189		TEST_ASSERT_VAL("Invalid nr samples",
190				hists->stats.nr_samples == 10);
191		TEST_ASSERT_VAL("Invalid nr hist entries",
192				hists->nr_entries == 9);
193		TEST_ASSERT_VAL("Invalid total period",
194				hists->stats.total_period == 1000);
195
196		/* but filter stats are changed */
197		TEST_ASSERT_VAL("Unmatched nr samples for thread filter",
198				hists->stats.nr_non_filtered_samples == 4);
199		TEST_ASSERT_VAL("Unmatched nr hist entries for thread filter",
200				hists->nr_non_filtered_entries == 4);
201		TEST_ASSERT_VAL("Unmatched total period for thread filter",
202				hists->stats.total_non_filtered_period == 400);
203
204		/* remove thread filter first */
205		hists->thread_filter = NULL;
206		hists__filter_by_thread(hists);
207
208		/* now applying dso filter for 'kernel' */
209		hists->dso_filter = map__dso(fake_samples[0].map);
210		hists__filter_by_dso(hists);
211
212		if (verbose > 2) {
213			pr_info("Histogram for dso filter\n");
214			print_hists_out(hists);
215		}
216
217		/* normal stats should be invariant */
218		TEST_ASSERT_VAL("Invalid nr samples",
219				hists->stats.nr_samples == 10);
220		TEST_ASSERT_VAL("Invalid nr hist entries",
221				hists->nr_entries == 9);
222		TEST_ASSERT_VAL("Invalid total period",
223				hists->stats.total_period == 1000);
224
225		/* but filter stats are changed */
226		TEST_ASSERT_VAL("Unmatched nr samples for dso filter",
227				hists->stats.nr_non_filtered_samples == 3);
228		TEST_ASSERT_VAL("Unmatched nr hist entries for dso filter",
229				hists->nr_non_filtered_entries == 3);
230		TEST_ASSERT_VAL("Unmatched total period for dso filter",
231				hists->stats.total_non_filtered_period == 300);
232
233		/* remove dso filter first */
234		hists->dso_filter = NULL;
235		hists__filter_by_dso(hists);
236
237		/*
238		 * now applying symbol filter for 'main'.  Also note that
239		 * there's 3 samples that have 'main' symbol but the 4th
240		 * entry of fake_samples was collapsed already so it won't
241		 * be counted as a separate entry but the sample count and
242		 * total period will be remained.
243		 */
244		hists->symbol_filter_str = "main";
245		hists__filter_by_symbol(hists);
246
247		if (verbose > 2) {
248			pr_info("Histogram for symbol filter\n");
249			print_hists_out(hists);
250		}
251
252		/* normal stats should be invariant */
253		TEST_ASSERT_VAL("Invalid nr samples",
254				hists->stats.nr_samples == 10);
255		TEST_ASSERT_VAL("Invalid nr hist entries",
256				hists->nr_entries == 9);
257		TEST_ASSERT_VAL("Invalid total period",
258				hists->stats.total_period == 1000);
259
260		/* but filter stats are changed */
261		TEST_ASSERT_VAL("Unmatched nr samples for symbol filter",
262				hists->stats.nr_non_filtered_samples == 3);
263		TEST_ASSERT_VAL("Unmatched nr hist entries for symbol filter",
264				hists->nr_non_filtered_entries == 2);
265		TEST_ASSERT_VAL("Unmatched total period for symbol filter",
266				hists->stats.total_non_filtered_period == 300);
267
268		/* remove symbol filter first */
269		hists->symbol_filter_str = NULL;
270		hists__filter_by_symbol(hists);
271
272		/* now applying socket filters */
273		hists->socket_filter = 2;
274		hists__filter_by_socket(hists);
275
276		if (verbose > 2) {
277			pr_info("Histogram for socket filters\n");
278			print_hists_out(hists);
279		}
280
281		/* normal stats should be invariant */
282		TEST_ASSERT_VAL("Invalid nr samples",
283				hists->stats.nr_samples == 10);
284		TEST_ASSERT_VAL("Invalid nr hist entries",
285				hists->nr_entries == 9);
286		TEST_ASSERT_VAL("Invalid total period",
287				hists->stats.total_period == 1000);
288
289		/* but filter stats are changed */
290		TEST_ASSERT_VAL("Unmatched nr samples for socket filter",
291				hists->stats.nr_non_filtered_samples == 2);
292		TEST_ASSERT_VAL("Unmatched nr hist entries for socket filter",
293				hists->nr_non_filtered_entries == 2);
294		TEST_ASSERT_VAL("Unmatched total period for socket filter",
295				hists->stats.total_non_filtered_period == 200);
296
297		/* remove socket filter first */
298		hists->socket_filter = -1;
299		hists__filter_by_socket(hists);
300
301		/* now applying all filters at once. */
302		hists->thread_filter = fake_samples[1].thread;
303		hists->dso_filter = map__dso(fake_samples[1].map);
304		hists__filter_by_thread(hists);
305		hists__filter_by_dso(hists);
306
307		if (verbose > 2) {
308			pr_info("Histogram for all filters\n");
309			print_hists_out(hists);
310		}
311
312		/* normal stats should be invariant */
313		TEST_ASSERT_VAL("Invalid nr samples",
314				hists->stats.nr_samples == 10);
315		TEST_ASSERT_VAL("Invalid nr hist entries",
316				hists->nr_entries == 9);
317		TEST_ASSERT_VAL("Invalid total period",
318				hists->stats.total_period == 1000);
319
320		/* but filter stats are changed */
321		TEST_ASSERT_VAL("Unmatched nr samples for all filter",
322				hists->stats.nr_non_filtered_samples == 2);
323		TEST_ASSERT_VAL("Unmatched nr hist entries for all filter",
324				hists->nr_non_filtered_entries == 1);
325		TEST_ASSERT_VAL("Unmatched total period for all filter",
326				hists->stats.total_non_filtered_period == 200);
327	}
328
329
330	err = TEST_OK;
331
332out:
333	/* tear down everything */
334	evlist__delete(evlist);
335	reset_output_field();
336	machines__exit(&machines);
337	put_fake_samples();
338
339	return err;
340}
341
342DEFINE_SUITE("Filter hist entries", hists_filter);
343