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 | |
16 | struct 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 */ |
26 | static 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 | |
49 | static 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 | |
102 | out: |
103 | pr_debug("Not enough memory for adding a hist entry\n" ); |
104 | addr_location__exit(&al); |
105 | return TEST_FAIL; |
106 | } |
107 | |
108 | static 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 | |
116 | static 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 | |
332 | out: |
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 | |
342 | DEFINE_SUITE("Filter hist entries" , hists_filter); |
343 | |