]>
Commit | Line | Data |
---|---|---|
7aef3bf3 JO |
1 | #include <linux/compiler.h> |
2 | #include <linux/kernel.h> | |
3 | #include "util.h" | |
4 | #include "debug.h" | |
5 | #include "builtin.h" | |
6 | #include <subcmd/parse-options.h> | |
39bcd4a4 | 7 | #include "mem-events.h" |
903a6f15 JO |
8 | #include "session.h" |
9 | #include "hist.h" | |
10 | #include "tool.h" | |
11 | #include "data.h" | |
12 | ||
c75540e3 JO |
13 | struct c2c_hists { |
14 | struct hists hists; | |
15 | struct perf_hpp_list list; | |
16 | }; | |
17 | ||
903a6f15 | 18 | struct perf_c2c { |
c75540e3 JO |
19 | struct perf_tool tool; |
20 | struct c2c_hists hists; | |
903a6f15 JO |
21 | }; |
22 | ||
23 | static struct perf_c2c c2c; | |
7aef3bf3 JO |
24 | |
25 | static const char * const c2c_usage[] = { | |
903a6f15 | 26 | "perf c2c {record|report}", |
7aef3bf3 JO |
27 | NULL |
28 | }; | |
29 | ||
903a6f15 JO |
30 | static const char * const __usage_report[] = { |
31 | "perf c2c report", | |
32 | NULL | |
33 | }; | |
34 | ||
35 | static const char * const *report_c2c_usage = __usage_report; | |
36 | ||
c75540e3 JO |
37 | #define C2C_HEADER_MAX 2 |
38 | ||
39 | struct c2c_header { | |
40 | struct { | |
41 | const char *text; | |
42 | int span; | |
43 | } line[C2C_HEADER_MAX]; | |
44 | }; | |
45 | ||
46 | struct c2c_dimension { | |
47 | struct c2c_header header; | |
48 | const char *name; | |
49 | int width; | |
50 | ||
51 | int64_t (*cmp)(struct perf_hpp_fmt *fmt, | |
52 | struct hist_entry *, struct hist_entry *); | |
53 | int (*entry)(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, | |
54 | struct hist_entry *he); | |
55 | int (*color)(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, | |
56 | struct hist_entry *he); | |
57 | }; | |
58 | ||
59 | struct c2c_fmt { | |
60 | struct perf_hpp_fmt fmt; | |
61 | struct c2c_dimension *dim; | |
62 | }; | |
63 | ||
64 | static int c2c_width(struct perf_hpp_fmt *fmt, | |
65 | struct perf_hpp *hpp __maybe_unused, | |
66 | struct hists *hists __maybe_unused) | |
67 | { | |
68 | struct c2c_fmt *c2c_fmt; | |
69 | ||
70 | c2c_fmt = container_of(fmt, struct c2c_fmt, fmt); | |
71 | return c2c_fmt->dim->width; | |
72 | } | |
73 | ||
74 | static int c2c_header(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, | |
75 | struct hists *hists __maybe_unused, int line, int *span) | |
76 | { | |
77 | struct c2c_fmt *c2c_fmt; | |
78 | struct c2c_dimension *dim; | |
79 | int len = c2c_width(fmt, hpp, hists); | |
80 | const char *text; | |
81 | ||
82 | c2c_fmt = container_of(fmt, struct c2c_fmt, fmt); | |
83 | dim = c2c_fmt->dim; | |
84 | ||
85 | text = dim->header.line[line].text; | |
86 | if (text == NULL) | |
87 | text = ""; | |
88 | ||
89 | if (*span) { | |
90 | (*span)--; | |
91 | return 0; | |
92 | } else { | |
93 | *span = dim->header.line[line].span; | |
94 | } | |
95 | ||
96 | return scnprintf(hpp->buf, hpp->size, "%*s", len, text); | |
97 | } | |
98 | ||
99 | static struct c2c_dimension *dimensions[] = { | |
100 | NULL, | |
101 | }; | |
102 | ||
103 | static void fmt_free(struct perf_hpp_fmt *fmt) | |
104 | { | |
105 | struct c2c_fmt *c2c_fmt; | |
106 | ||
107 | c2c_fmt = container_of(fmt, struct c2c_fmt, fmt); | |
108 | free(c2c_fmt); | |
109 | } | |
110 | ||
111 | static bool fmt_equal(struct perf_hpp_fmt *a, struct perf_hpp_fmt *b) | |
112 | { | |
113 | struct c2c_fmt *c2c_a = container_of(a, struct c2c_fmt, fmt); | |
114 | struct c2c_fmt *c2c_b = container_of(b, struct c2c_fmt, fmt); | |
115 | ||
116 | return c2c_a->dim == c2c_b->dim; | |
117 | } | |
118 | ||
119 | static struct c2c_dimension *get_dimension(const char *name) | |
120 | { | |
121 | unsigned int i; | |
122 | ||
123 | for (i = 0; dimensions[i]; i++) { | |
124 | struct c2c_dimension *dim = dimensions[i]; | |
125 | ||
126 | if (!strcmp(dim->name, name)) | |
127 | return dim; | |
128 | }; | |
129 | ||
130 | return NULL; | |
131 | } | |
132 | ||
133 | static struct c2c_fmt *get_format(const char *name) | |
134 | { | |
135 | struct c2c_dimension *dim = get_dimension(name); | |
136 | struct c2c_fmt *c2c_fmt; | |
137 | struct perf_hpp_fmt *fmt; | |
138 | ||
139 | if (!dim) | |
140 | return NULL; | |
141 | ||
142 | c2c_fmt = zalloc(sizeof(*c2c_fmt)); | |
143 | if (!c2c_fmt) | |
144 | return NULL; | |
145 | ||
146 | c2c_fmt->dim = dim; | |
147 | ||
148 | fmt = &c2c_fmt->fmt; | |
149 | INIT_LIST_HEAD(&fmt->list); | |
150 | INIT_LIST_HEAD(&fmt->sort_list); | |
151 | ||
152 | fmt->cmp = dim->cmp; | |
153 | fmt->sort = dim->cmp; | |
154 | fmt->entry = dim->entry; | |
155 | fmt->header = c2c_header; | |
156 | fmt->width = c2c_width; | |
157 | fmt->collapse = dim->cmp; | |
158 | fmt->equal = fmt_equal; | |
159 | fmt->free = fmt_free; | |
160 | ||
161 | return c2c_fmt; | |
162 | } | |
163 | ||
164 | static int c2c_hists__init_output(struct perf_hpp_list *hpp_list, char *name) | |
165 | { | |
166 | struct c2c_fmt *c2c_fmt = get_format(name); | |
167 | ||
168 | if (!c2c_fmt) | |
169 | return -1; | |
170 | ||
171 | perf_hpp_list__column_register(hpp_list, &c2c_fmt->fmt); | |
172 | return 0; | |
173 | } | |
174 | ||
175 | static int c2c_hists__init_sort(struct perf_hpp_list *hpp_list, char *name) | |
176 | { | |
177 | struct c2c_fmt *c2c_fmt = get_format(name); | |
178 | ||
179 | if (!c2c_fmt) | |
180 | return -1; | |
181 | ||
182 | perf_hpp_list__register_sort_field(hpp_list, &c2c_fmt->fmt); | |
183 | return 0; | |
184 | } | |
185 | ||
186 | #define PARSE_LIST(_list, _fn) \ | |
187 | do { \ | |
188 | char *tmp, *tok; \ | |
189 | ret = 0; \ | |
190 | \ | |
191 | if (!_list) \ | |
192 | break; \ | |
193 | \ | |
194 | for (tok = strtok_r((char *)_list, ", ", &tmp); \ | |
195 | tok; tok = strtok_r(NULL, ", ", &tmp)) { \ | |
196 | ret = _fn(hpp_list, tok); \ | |
197 | if (ret == -EINVAL) { \ | |
198 | error("Invalid --fields key: `%s'", tok); \ | |
199 | break; \ | |
200 | } else if (ret == -ESRCH) { \ | |
201 | error("Unknown --fields key: `%s'", tok); \ | |
202 | break; \ | |
203 | } \ | |
204 | } \ | |
205 | } while (0) | |
206 | ||
207 | static int hpp_list__parse(struct perf_hpp_list *hpp_list, | |
208 | const char *output_, | |
209 | const char *sort_) | |
210 | { | |
211 | char *output = output_ ? strdup(output_) : NULL; | |
212 | char *sort = sort_ ? strdup(sort_) : NULL; | |
213 | int ret; | |
214 | ||
215 | PARSE_LIST(output, c2c_hists__init_output); | |
216 | PARSE_LIST(sort, c2c_hists__init_sort); | |
217 | ||
218 | /* copy sort keys to output fields */ | |
219 | perf_hpp__setup_output_field(hpp_list); | |
220 | ||
221 | /* | |
222 | * We dont need other sorting keys other than those | |
223 | * we already specified. It also really slows down | |
224 | * the processing a lot with big number of output | |
225 | * fields, so switching this off for c2c. | |
226 | */ | |
227 | ||
228 | #if 0 | |
229 | /* and then copy output fields to sort keys */ | |
230 | perf_hpp__append_sort_keys(&hists->list); | |
231 | #endif | |
232 | ||
233 | free(output); | |
234 | free(sort); | |
235 | return ret; | |
236 | } | |
237 | ||
238 | static int c2c_hists__init(struct c2c_hists *hists, | |
239 | const char *sort) | |
240 | { | |
241 | __hists__init(&hists->hists, &hists->list); | |
242 | ||
243 | /* | |
244 | * Initialize only with sort fields, we need to resort | |
245 | * later anyway, and that's where we add output fields | |
246 | * as well. | |
247 | */ | |
248 | perf_hpp_list__init(&hists->list); | |
249 | ||
250 | return hpp_list__parse(&hists->list, NULL, sort); | |
251 | } | |
252 | ||
253 | __maybe_unused | |
254 | static int c2c_hists__reinit(struct c2c_hists *c2c_hists, | |
255 | const char *output, | |
256 | const char *sort) | |
257 | { | |
258 | perf_hpp__reset_output_field(&c2c_hists->list); | |
259 | return hpp_list__parse(&c2c_hists->list, output, sort); | |
260 | } | |
261 | ||
903a6f15 JO |
262 | static int perf_c2c__report(int argc, const char **argv) |
263 | { | |
264 | struct perf_session *session; | |
265 | struct perf_data_file file = { | |
266 | .mode = PERF_DATA_MODE_READ, | |
267 | }; | |
268 | const struct option c2c_options[] = { | |
269 | OPT_STRING('k', "vmlinux", &symbol_conf.vmlinux_name, | |
270 | "file", "vmlinux pathname"), | |
271 | OPT_INCR('v', "verbose", &verbose, | |
272 | "be more verbose (show counter open errors, etc)"), | |
273 | OPT_STRING('i', "input", &input_name, "file", | |
274 | "the input file to process"), | |
275 | OPT_END() | |
276 | }; | |
277 | int err = 0; | |
278 | ||
279 | argc = parse_options(argc, argv, c2c_options, report_c2c_usage, | |
280 | PARSE_OPT_STOP_AT_NON_OPTION); | |
281 | if (!argc) | |
282 | usage_with_options(report_c2c_usage, c2c_options); | |
283 | ||
284 | file.path = input_name; | |
285 | ||
c75540e3 JO |
286 | err = c2c_hists__init(&c2c.hists, "dcacheline"); |
287 | if (err) { | |
288 | pr_debug("Failed to initialize hists\n"); | |
289 | goto out; | |
290 | } | |
291 | ||
903a6f15 JO |
292 | session = perf_session__new(&file, 0, &c2c.tool); |
293 | if (session == NULL) { | |
294 | pr_debug("No memory for session\n"); | |
295 | goto out; | |
296 | } | |
297 | ||
298 | if (symbol__init(&session->header.env) < 0) | |
299 | goto out_session; | |
300 | ||
301 | /* No pipe support at the moment. */ | |
302 | if (perf_data_file__is_pipe(session->file)) { | |
303 | pr_debug("No pipe support at the moment.\n"); | |
304 | goto out_session; | |
305 | } | |
306 | ||
307 | out_session: | |
308 | perf_session__delete(session); | |
309 | out: | |
310 | return err; | |
311 | } | |
312 | ||
39bcd4a4 JO |
313 | static int parse_record_events(const struct option *opt __maybe_unused, |
314 | const char *str, int unset __maybe_unused) | |
315 | { | |
316 | bool *event_set = (bool *) opt->value; | |
317 | ||
318 | *event_set = true; | |
319 | return perf_mem_events__parse(str); | |
320 | } | |
321 | ||
322 | ||
323 | static const char * const __usage_record[] = { | |
324 | "perf c2c record [<options>] [<command>]", | |
325 | "perf c2c record [<options>] -- <command> [<options>]", | |
326 | NULL | |
327 | }; | |
328 | ||
329 | static const char * const *record_mem_usage = __usage_record; | |
330 | ||
331 | static int perf_c2c__record(int argc, const char **argv) | |
332 | { | |
333 | int rec_argc, i = 0, j; | |
334 | const char **rec_argv; | |
335 | int ret; | |
336 | bool all_user = false, all_kernel = false; | |
337 | bool event_set = false; | |
338 | struct option options[] = { | |
339 | OPT_CALLBACK('e', "event", &event_set, "event", | |
340 | "event selector. Use 'perf mem record -e list' to list available events", | |
341 | parse_record_events), | |
342 | OPT_INCR('v', "verbose", &verbose, | |
343 | "be more verbose (show counter open errors, etc)"), | |
344 | OPT_BOOLEAN('u', "all-user", &all_user, "collect only user level data"), | |
345 | OPT_BOOLEAN('k', "all-kernel", &all_kernel, "collect only kernel level data"), | |
346 | OPT_UINTEGER('l', "ldlat", &perf_mem_events__loads_ldlat, "setup mem-loads latency"), | |
347 | OPT_END() | |
348 | }; | |
349 | ||
350 | if (perf_mem_events__init()) { | |
351 | pr_err("failed: memory events not supported\n"); | |
352 | return -1; | |
353 | } | |
354 | ||
355 | argc = parse_options(argc, argv, options, record_mem_usage, | |
356 | PARSE_OPT_KEEP_UNKNOWN); | |
357 | ||
358 | rec_argc = argc + 10; /* max number of arguments */ | |
359 | rec_argv = calloc(rec_argc + 1, sizeof(char *)); | |
360 | if (!rec_argv) | |
361 | return -1; | |
362 | ||
363 | rec_argv[i++] = "record"; | |
364 | ||
365 | if (!event_set) { | |
366 | perf_mem_events[PERF_MEM_EVENTS__LOAD].record = true; | |
367 | perf_mem_events[PERF_MEM_EVENTS__STORE].record = true; | |
368 | } | |
369 | ||
370 | if (perf_mem_events[PERF_MEM_EVENTS__LOAD].record) | |
371 | rec_argv[i++] = "-W"; | |
372 | ||
373 | rec_argv[i++] = "-d"; | |
374 | rec_argv[i++] = "--sample-cpu"; | |
375 | ||
376 | for (j = 0; j < PERF_MEM_EVENTS__MAX; j++) { | |
377 | if (!perf_mem_events[j].record) | |
378 | continue; | |
379 | ||
380 | if (!perf_mem_events[j].supported) { | |
381 | pr_err("failed: event '%s' not supported\n", | |
382 | perf_mem_events[j].name); | |
383 | return -1; | |
384 | } | |
385 | ||
386 | rec_argv[i++] = "-e"; | |
387 | rec_argv[i++] = perf_mem_events__name(j); | |
388 | }; | |
389 | ||
390 | if (all_user) | |
391 | rec_argv[i++] = "--all-user"; | |
392 | ||
393 | if (all_kernel) | |
394 | rec_argv[i++] = "--all-kernel"; | |
395 | ||
396 | for (j = 0; j < argc; j++, i++) | |
397 | rec_argv[i] = argv[j]; | |
398 | ||
399 | if (verbose > 0) { | |
400 | pr_debug("calling: "); | |
401 | ||
402 | j = 0; | |
403 | ||
404 | while (rec_argv[j]) { | |
405 | pr_debug("%s ", rec_argv[j]); | |
406 | j++; | |
407 | } | |
408 | pr_debug("\n"); | |
409 | } | |
410 | ||
411 | ret = cmd_record(i, rec_argv, NULL); | |
412 | free(rec_argv); | |
413 | return ret; | |
414 | } | |
415 | ||
7aef3bf3 JO |
416 | int cmd_c2c(int argc, const char **argv, const char *prefix __maybe_unused) |
417 | { | |
418 | const struct option c2c_options[] = { | |
419 | OPT_INCR('v', "verbose", &verbose, "be more verbose"), | |
420 | OPT_END() | |
421 | }; | |
422 | ||
423 | argc = parse_options(argc, argv, c2c_options, c2c_usage, | |
424 | PARSE_OPT_STOP_AT_NON_OPTION); | |
39bcd4a4 JO |
425 | |
426 | if (!argc) | |
427 | usage_with_options(c2c_usage, c2c_options); | |
428 | ||
429 | if (!strncmp(argv[0], "rec", 3)) { | |
430 | return perf_c2c__record(argc, argv); | |
903a6f15 JO |
431 | } else if (!strncmp(argv[0], "rep", 3)) { |
432 | return perf_c2c__report(argc, argv); | |
39bcd4a4 JO |
433 | } else { |
434 | usage_with_options(c2c_usage, c2c_options); | |
435 | } | |
436 | ||
7aef3bf3 JO |
437 | return 0; |
438 | } |