1 | // SPDX-License-Identifier: GPL-2.0 |
2 | #include <inttypes.h> |
3 | #include <sys/types.h> |
4 | #include <sys/stat.h> |
5 | #include <unistd.h> |
6 | #include "builtin.h" |
7 | |
8 | #include <subcmd/parse-options.h> |
9 | #include "util/auxtrace.h" |
10 | #include "util/trace-event.h" |
11 | #include "util/tool.h" |
12 | #include "util/session.h" |
13 | #include "util/data.h" |
14 | #include "util/map_symbol.h" |
15 | #include "util/mem-events.h" |
16 | #include "util/debug.h" |
17 | #include "util/dso.h" |
18 | #include "util/map.h" |
19 | #include "util/symbol.h" |
20 | #include "util/pmus.h" |
21 | #include "util/sample.h" |
22 | #include "util/string2.h" |
23 | #include "util/util.h" |
24 | #include <linux/err.h> |
25 | |
26 | #define MEM_OPERATION_LOAD 0x1 |
27 | #define MEM_OPERATION_STORE 0x2 |
28 | |
29 | struct perf_mem { |
30 | struct perf_tool tool; |
31 | char const *input_name; |
32 | bool hide_unresolved; |
33 | bool dump_raw; |
34 | bool force; |
35 | bool phys_addr; |
36 | bool data_page_size; |
37 | int operation; |
38 | const char *cpu_list; |
39 | DECLARE_BITMAP(cpu_bitmap, MAX_NR_CPUS); |
40 | }; |
41 | |
42 | static int parse_record_events(const struct option *opt, |
43 | const char *str, int unset __maybe_unused) |
44 | { |
45 | struct perf_mem *mem = *(struct perf_mem **)opt->value; |
46 | |
47 | if (!strcmp(str, "list" )) { |
48 | perf_mem_events__list(); |
49 | exit(0); |
50 | } |
51 | if (perf_mem_events__parse(str)) |
52 | exit(-1); |
53 | |
54 | mem->operation = 0; |
55 | return 0; |
56 | } |
57 | |
58 | static const char * const __usage[] = { |
59 | "perf mem record [<options>] [<command>]" , |
60 | "perf mem record [<options>] -- <command> [<options>]" , |
61 | NULL |
62 | }; |
63 | |
64 | static const char * const *record_mem_usage = __usage; |
65 | |
66 | static int __cmd_record(int argc, const char **argv, struct perf_mem *mem) |
67 | { |
68 | int rec_argc, i = 0, j, tmp_nr = 0; |
69 | int start, end; |
70 | const char **rec_argv; |
71 | char **rec_tmp; |
72 | int ret; |
73 | bool all_user = false, all_kernel = false; |
74 | struct perf_mem_event *e; |
75 | struct option options[] = { |
76 | OPT_CALLBACK('e', "event" , &mem, "event" , |
77 | "event selector. use 'perf mem record -e list' to list available events" , |
78 | parse_record_events), |
79 | OPT_UINTEGER(0, "ldlat" , &perf_mem_events__loads_ldlat, "mem-loads latency" ), |
80 | OPT_INCR('v', "verbose" , &verbose, |
81 | "be more verbose (show counter open errors, etc)" ), |
82 | OPT_BOOLEAN('U', "all-user" , &all_user, "collect only user level data" ), |
83 | OPT_BOOLEAN('K', "all-kernel" , &all_kernel, "collect only kernel level data" ), |
84 | OPT_END() |
85 | }; |
86 | |
87 | if (perf_mem_events__init()) { |
88 | pr_err("failed: memory events not supported\n" ); |
89 | return -1; |
90 | } |
91 | |
92 | argc = parse_options(argc, argv, options, record_mem_usage, |
93 | PARSE_OPT_KEEP_UNKNOWN); |
94 | |
95 | /* Max number of arguments multiplied by number of PMUs that can support them. */ |
96 | rec_argc = argc + 9 * perf_pmus__num_mem_pmus(); |
97 | |
98 | if (mem->cpu_list) |
99 | rec_argc += 2; |
100 | |
101 | rec_argv = calloc(rec_argc + 1, sizeof(char *)); |
102 | if (!rec_argv) |
103 | return -1; |
104 | |
105 | /* |
106 | * Save the allocated event name strings. |
107 | */ |
108 | rec_tmp = calloc(rec_argc + 1, sizeof(char *)); |
109 | if (!rec_tmp) { |
110 | free(rec_argv); |
111 | return -1; |
112 | } |
113 | |
114 | rec_argv[i++] = "record" ; |
115 | |
116 | e = perf_mem_events__ptr(i: PERF_MEM_EVENTS__LOAD_STORE); |
117 | |
118 | /* |
119 | * The load and store operations are required, use the event |
120 | * PERF_MEM_EVENTS__LOAD_STORE if it is supported. |
121 | */ |
122 | if (e->tag && |
123 | (mem->operation & MEM_OPERATION_LOAD) && |
124 | (mem->operation & MEM_OPERATION_STORE)) { |
125 | e->record = true; |
126 | rec_argv[i++] = "-W" ; |
127 | } else { |
128 | if (mem->operation & MEM_OPERATION_LOAD) { |
129 | e = perf_mem_events__ptr(i: PERF_MEM_EVENTS__LOAD); |
130 | e->record = true; |
131 | } |
132 | |
133 | if (mem->operation & MEM_OPERATION_STORE) { |
134 | e = perf_mem_events__ptr(i: PERF_MEM_EVENTS__STORE); |
135 | e->record = true; |
136 | } |
137 | } |
138 | |
139 | e = perf_mem_events__ptr(i: PERF_MEM_EVENTS__LOAD); |
140 | if (e->record) |
141 | rec_argv[i++] = "-W" ; |
142 | |
143 | rec_argv[i++] = "-d" ; |
144 | |
145 | if (mem->phys_addr) |
146 | rec_argv[i++] = "--phys-data" ; |
147 | |
148 | if (mem->data_page_size) |
149 | rec_argv[i++] = "--data-page-size" ; |
150 | |
151 | start = i; |
152 | ret = perf_mem_events__record_args(rec_argv, argv_nr: &i, rec_tmp, tmp_nr: &tmp_nr); |
153 | if (ret) |
154 | goto out; |
155 | end = i; |
156 | |
157 | if (all_user) |
158 | rec_argv[i++] = "--all-user" ; |
159 | |
160 | if (all_kernel) |
161 | rec_argv[i++] = "--all-kernel" ; |
162 | |
163 | if (mem->cpu_list) { |
164 | rec_argv[i++] = "-C" ; |
165 | rec_argv[i++] = mem->cpu_list; |
166 | } |
167 | |
168 | for (j = 0; j < argc; j++, i++) |
169 | rec_argv[i] = argv[j]; |
170 | |
171 | if (verbose > 0) { |
172 | pr_debug("calling: record " ); |
173 | |
174 | for (j = start; j < end; j++) |
175 | pr_debug("%s " , rec_argv[j]); |
176 | |
177 | pr_debug("\n" ); |
178 | } |
179 | |
180 | ret = cmd_record(argc: i, argv: rec_argv); |
181 | out: |
182 | for (i = 0; i < tmp_nr; i++) |
183 | free(rec_tmp[i]); |
184 | |
185 | free(rec_tmp); |
186 | free(rec_argv); |
187 | return ret; |
188 | } |
189 | |
190 | static int |
191 | dump_raw_samples(struct perf_tool *tool, |
192 | union perf_event *event, |
193 | struct perf_sample *sample, |
194 | struct machine *machine) |
195 | { |
196 | struct perf_mem *mem = container_of(tool, struct perf_mem, tool); |
197 | struct addr_location al; |
198 | const char *fmt, *field_sep; |
199 | char str[PAGE_SIZE_NAME_LEN]; |
200 | struct dso *dso = NULL; |
201 | |
202 | addr_location__init(al: &al); |
203 | if (machine__resolve(machine, al: &al, sample) < 0) { |
204 | fprintf(stderr, "problem processing %d event, skipping it.\n" , |
205 | event->header.type); |
206 | addr_location__exit(al: &al); |
207 | return -1; |
208 | } |
209 | |
210 | if (al.filtered || (mem->hide_unresolved && al.sym == NULL)) |
211 | goto out_put; |
212 | |
213 | if (al.map != NULL) { |
214 | dso = map__dso(map: al.map); |
215 | if (dso) |
216 | dso->hit = 1; |
217 | } |
218 | |
219 | field_sep = symbol_conf.field_sep; |
220 | if (field_sep) { |
221 | fmt = "%d%s%d%s0x%" PRIx64"%s0x%" PRIx64"%s" ; |
222 | } else { |
223 | fmt = "%5d%s%5d%s0x%016" PRIx64"%s0x016%" PRIx64"%s" ; |
224 | symbol_conf.field_sep = " " ; |
225 | } |
226 | printf(fmt, |
227 | sample->pid, |
228 | symbol_conf.field_sep, |
229 | sample->tid, |
230 | symbol_conf.field_sep, |
231 | sample->ip, |
232 | symbol_conf.field_sep, |
233 | sample->addr, |
234 | symbol_conf.field_sep); |
235 | |
236 | if (mem->phys_addr) { |
237 | printf("0x%016" PRIx64"%s" , |
238 | sample->phys_addr, |
239 | symbol_conf.field_sep); |
240 | } |
241 | |
242 | if (mem->data_page_size) { |
243 | printf("%s%s" , |
244 | get_page_size_name(size: sample->data_page_size, str), |
245 | symbol_conf.field_sep); |
246 | } |
247 | |
248 | if (field_sep) |
249 | fmt = "%" PRIu64"%s0x%" PRIx64"%s%s:%s\n" ; |
250 | else |
251 | fmt = "%5" PRIu64"%s0x%06" PRIx64"%s%s:%s\n" ; |
252 | |
253 | printf(fmt, |
254 | sample->weight, |
255 | symbol_conf.field_sep, |
256 | sample->data_src, |
257 | symbol_conf.field_sep, |
258 | dso ? dso->long_name : "???" , |
259 | al.sym ? al.sym->name : "???" ); |
260 | out_put: |
261 | addr_location__exit(al: &al); |
262 | return 0; |
263 | } |
264 | |
265 | static int process_sample_event(struct perf_tool *tool, |
266 | union perf_event *event, |
267 | struct perf_sample *sample, |
268 | struct evsel *evsel __maybe_unused, |
269 | struct machine *machine) |
270 | { |
271 | return dump_raw_samples(tool, event, sample, machine); |
272 | } |
273 | |
274 | static int report_raw_events(struct perf_mem *mem) |
275 | { |
276 | struct itrace_synth_opts itrace_synth_opts = { |
277 | .set = true, |
278 | .mem = true, /* Only enable memory event */ |
279 | .default_no_sample = true, |
280 | }; |
281 | |
282 | struct perf_data data = { |
283 | .path = input_name, |
284 | .mode = PERF_DATA_MODE_READ, |
285 | .force = mem->force, |
286 | }; |
287 | int ret; |
288 | struct perf_session *session = perf_session__new(data: &data, tool: &mem->tool); |
289 | |
290 | if (IS_ERR(ptr: session)) |
291 | return PTR_ERR(ptr: session); |
292 | |
293 | session->itrace_synth_opts = &itrace_synth_opts; |
294 | |
295 | if (mem->cpu_list) { |
296 | ret = perf_session__cpu_bitmap(session, cpu_list: mem->cpu_list, |
297 | cpu_bitmap: mem->cpu_bitmap); |
298 | if (ret < 0) |
299 | goto out_delete; |
300 | } |
301 | |
302 | ret = symbol__init(env: &session->header.env); |
303 | if (ret < 0) |
304 | goto out_delete; |
305 | |
306 | printf("# PID, TID, IP, ADDR, " ); |
307 | |
308 | if (mem->phys_addr) |
309 | printf("PHYS ADDR, " ); |
310 | |
311 | if (mem->data_page_size) |
312 | printf("DATA PAGE SIZE, " ); |
313 | |
314 | printf("LOCAL WEIGHT, DSRC, SYMBOL\n" ); |
315 | |
316 | ret = perf_session__process_events(session); |
317 | |
318 | out_delete: |
319 | perf_session__delete(session); |
320 | return ret; |
321 | } |
322 | static char *get_sort_order(struct perf_mem *mem) |
323 | { |
324 | bool = (mem->phys_addr | mem->data_page_size) ? true : false; |
325 | char sort[128]; |
326 | |
327 | /* |
328 | * there is no weight (cost) associated with stores, so don't print |
329 | * the column |
330 | */ |
331 | if (!(mem->operation & MEM_OPERATION_LOAD)) { |
332 | strcpy(p: sort, q: "--sort=mem,sym,dso,symbol_daddr," |
333 | "dso_daddr,tlb,locked" ); |
334 | } else if (has_extra_options) { |
335 | strcpy(p: sort, q: "--sort=local_weight,mem,sym,dso,symbol_daddr," |
336 | "dso_daddr,snoop,tlb,locked,blocked" ); |
337 | } else |
338 | return NULL; |
339 | |
340 | if (mem->phys_addr) |
341 | strcat(p: sort, q: ",phys_daddr" ); |
342 | |
343 | if (mem->data_page_size) |
344 | strcat(p: sort, q: ",data_page_size" ); |
345 | |
346 | return strdup(sort); |
347 | } |
348 | |
349 | static int report_events(int argc, const char **argv, struct perf_mem *mem) |
350 | { |
351 | const char **rep_argv; |
352 | int ret, i = 0, j, rep_argc; |
353 | char *new_sort_order; |
354 | |
355 | if (mem->dump_raw) |
356 | return report_raw_events(mem); |
357 | |
358 | rep_argc = argc + 3; |
359 | rep_argv = calloc(rep_argc + 1, sizeof(char *)); |
360 | if (!rep_argv) |
361 | return -1; |
362 | |
363 | rep_argv[i++] = "report" ; |
364 | rep_argv[i++] = "--mem-mode" ; |
365 | rep_argv[i++] = "-n" ; /* display number of samples */ |
366 | |
367 | new_sort_order = get_sort_order(mem); |
368 | if (new_sort_order) |
369 | rep_argv[i++] = new_sort_order; |
370 | |
371 | for (j = 1; j < argc; j++, i++) |
372 | rep_argv[i] = argv[j]; |
373 | |
374 | ret = cmd_report(argc: i, argv: rep_argv); |
375 | free(rep_argv); |
376 | return ret; |
377 | } |
378 | |
379 | struct mem_mode { |
380 | const char *name; |
381 | int mode; |
382 | }; |
383 | |
384 | #define MEM_OPT(n, m) \ |
385 | { .name = n, .mode = (m) } |
386 | |
387 | #define MEM_END { .name = NULL } |
388 | |
389 | static const struct mem_mode mem_modes[]={ |
390 | MEM_OPT("load" , MEM_OPERATION_LOAD), |
391 | MEM_OPT("store" , MEM_OPERATION_STORE), |
392 | MEM_END |
393 | }; |
394 | |
395 | static int |
396 | parse_mem_ops(const struct option *opt, const char *str, int unset) |
397 | { |
398 | int *mode = (int *)opt->value; |
399 | const struct mem_mode *m; |
400 | char *s, *os = NULL, *p; |
401 | int ret = -1; |
402 | |
403 | if (unset) |
404 | return 0; |
405 | |
406 | /* str may be NULL in case no arg is passed to -t */ |
407 | if (str) { |
408 | /* because str is read-only */ |
409 | s = os = strdup(str); |
410 | if (!s) |
411 | return -1; |
412 | |
413 | /* reset mode */ |
414 | *mode = 0; |
415 | |
416 | for (;;) { |
417 | p = strchr(s, ','); |
418 | if (p) |
419 | *p = '\0'; |
420 | |
421 | for (m = mem_modes; m->name; m++) { |
422 | if (!strcasecmp(s1: s, s2: m->name)) |
423 | break; |
424 | } |
425 | if (!m->name) { |
426 | fprintf(stderr, "unknown sampling op %s," |
427 | " check man page\n" , s); |
428 | goto error; |
429 | } |
430 | |
431 | *mode |= m->mode; |
432 | |
433 | if (!p) |
434 | break; |
435 | |
436 | s = p + 1; |
437 | } |
438 | } |
439 | ret = 0; |
440 | |
441 | if (*mode == 0) |
442 | *mode = MEM_OPERATION_LOAD; |
443 | error: |
444 | free(os); |
445 | return ret; |
446 | } |
447 | |
448 | int cmd_mem(int argc, const char **argv) |
449 | { |
450 | struct stat st; |
451 | struct perf_mem mem = { |
452 | .tool = { |
453 | .sample = process_sample_event, |
454 | .mmap = perf_event__process_mmap, |
455 | .mmap2 = perf_event__process_mmap2, |
456 | .comm = perf_event__process_comm, |
457 | .lost = perf_event__process_lost, |
458 | .fork = perf_event__process_fork, |
459 | .attr = perf_event__process_attr, |
460 | .build_id = perf_event__process_build_id, |
461 | .namespaces = perf_event__process_namespaces, |
462 | .auxtrace_info = perf_event__process_auxtrace_info, |
463 | .auxtrace = perf_event__process_auxtrace, |
464 | .auxtrace_error = perf_event__process_auxtrace_error, |
465 | .ordered_events = true, |
466 | }, |
467 | .input_name = "perf.data" , |
468 | /* |
469 | * default to both load an store sampling |
470 | */ |
471 | .operation = MEM_OPERATION_LOAD | MEM_OPERATION_STORE, |
472 | }; |
473 | const struct option mem_options[] = { |
474 | OPT_CALLBACK('t', "type" , &mem.operation, |
475 | "type" , "memory operations(load,store) Default load,store" , |
476 | parse_mem_ops), |
477 | OPT_BOOLEAN('D', "dump-raw-samples" , &mem.dump_raw, |
478 | "dump raw samples in ASCII" ), |
479 | OPT_BOOLEAN('U', "hide-unresolved" , &mem.hide_unresolved, |
480 | "Only display entries resolved to a symbol" ), |
481 | OPT_STRING('i', "input" , &input_name, "file" , |
482 | "input file name" ), |
483 | OPT_STRING('C', "cpu" , &mem.cpu_list, "cpu" , |
484 | "list of cpus to profile" ), |
485 | OPT_STRING_NOEMPTY('x', "field-separator" , &symbol_conf.field_sep, |
486 | "separator" , |
487 | "separator for columns, no spaces will be added" |
488 | " between columns '.' is reserved." ), |
489 | OPT_BOOLEAN('f', "force" , &mem.force, "don't complain, do it" ), |
490 | OPT_BOOLEAN('p', "phys-data" , &mem.phys_addr, "Record/Report sample physical addresses" ), |
491 | OPT_BOOLEAN(0, "data-page-size" , &mem.data_page_size, "Record/Report sample data address page size" ), |
492 | OPT_END() |
493 | }; |
494 | const char *const mem_subcommands[] = { "record" , "report" , NULL }; |
495 | const char *mem_usage[] = { |
496 | NULL, |
497 | NULL |
498 | }; |
499 | |
500 | argc = parse_options_subcommand(argc, argv, mem_options, mem_subcommands, |
501 | mem_usage, PARSE_OPT_KEEP_UNKNOWN); |
502 | |
503 | if (!argc || !(strncmp(argv[0], "rec" , 3) || mem.operation)) |
504 | usage_with_options(mem_usage, mem_options); |
505 | |
506 | if (!mem.input_name || !strlen(mem.input_name)) { |
507 | if (!fstat(STDIN_FILENO, &st) && S_ISFIFO(st.st_mode)) |
508 | mem.input_name = "-" ; |
509 | else |
510 | mem.input_name = "perf.data" ; |
511 | } |
512 | |
513 | if (strlen(argv[0]) > 2 && strstarts(str: "record" , prefix: argv[0])) |
514 | return __cmd_record(argc, argv, mem: &mem); |
515 | else if (strlen(argv[0]) > 2 && strstarts(str: "report" , prefix: argv[0])) |
516 | return report_events(argc, argv, mem: &mem); |
517 | else |
518 | usage_with_options(mem_usage, mem_options); |
519 | |
520 | return 0; |
521 | } |
522 | |