xref: /openbmc/linux/tools/perf/builtin-report.c (revision 7dd65feb)
1 /*
2  * builtin-report.c
3  *
4  * Builtin report command: Analyze the perf.data input file,
5  * look up and read DSOs and symbol information and display
6  * a histogram of results, along various sorting keys.
7  */
8 #include "builtin.h"
9 
10 #include "util/util.h"
11 
12 #include "util/color.h"
13 #include <linux/list.h>
14 #include "util/cache.h"
15 #include <linux/rbtree.h>
16 #include "util/symbol.h"
17 #include "util/string.h"
18 #include "util/callchain.h"
19 #include "util/strlist.h"
20 #include "util/values.h"
21 
22 #include "perf.h"
23 #include "util/debug.h"
24 #include "util/header.h"
25 #include "util/session.h"
26 
27 #include "util/parse-options.h"
28 #include "util/parse-events.h"
29 
30 #include "util/thread.h"
31 #include "util/sort.h"
32 #include "util/hist.h"
33 
34 static char		const *input_name = "perf.data";
35 
36 static int		force;
37 
38 static int		show_threads;
39 static struct perf_read_values	show_threads_values;
40 
41 static char		default_pretty_printing_style[] = "normal";
42 static char		*pretty_printing_style = default_pretty_printing_style;
43 
44 static char		callchain_default_opt[] = "fractal,0.5";
45 
46 static int perf_session__add_hist_entry(struct perf_session *self,
47 					struct addr_location *al,
48 					struct ip_callchain *chain, u64 count)
49 {
50 	struct symbol **syms = NULL, *parent = NULL;
51 	bool hit;
52 	struct hist_entry *he;
53 
54 	if ((sort__has_parent || symbol_conf.use_callchain) && chain)
55 		syms = perf_session__resolve_callchain(self, al->thread,
56 						       chain, &parent);
57 	he = __perf_session__add_hist_entry(self, al, parent, count, &hit);
58 	if (he == NULL)
59 		return -ENOMEM;
60 
61 	if (hit)
62 		he->count += count;
63 
64 	if (symbol_conf.use_callchain) {
65 		if (!hit)
66 			callchain_init(&he->callchain);
67 		append_chain(&he->callchain, chain, syms);
68 		free(syms);
69 	}
70 
71 	return 0;
72 }
73 
74 static int validate_chain(struct ip_callchain *chain, event_t *event)
75 {
76 	unsigned int chain_size;
77 
78 	chain_size = event->header.size;
79 	chain_size -= (unsigned long)&event->ip.__more_data - (unsigned long)event;
80 
81 	if (chain->nr*sizeof(u64) > chain_size)
82 		return -1;
83 
84 	return 0;
85 }
86 
87 static int process_sample_event(event_t *event, struct perf_session *session)
88 {
89 	struct sample_data data = { .period = 1, };
90 	struct addr_location al;
91 
92 	event__parse_sample(event, session->sample_type, &data);
93 
94 	dump_printf("(IP, %d): %d/%d: %p period: %Ld\n",
95 		event->header.misc,
96 		data.pid, data.tid,
97 		(void *)(long)data.ip,
98 		(long long)data.period);
99 
100 	if (session->sample_type & PERF_SAMPLE_CALLCHAIN) {
101 		unsigned int i;
102 
103 		dump_printf("... chain: nr:%Lu\n", data.callchain->nr);
104 
105 		if (validate_chain(data.callchain, event) < 0) {
106 			pr_debug("call-chain problem with event, "
107 				 "skipping it.\n");
108 			return 0;
109 		}
110 
111 		if (dump_trace) {
112 			for (i = 0; i < data.callchain->nr; i++)
113 				dump_printf("..... %2d: %016Lx\n",
114 					    i, data.callchain->ips[i]);
115 		}
116 	}
117 
118 	if (event__preprocess_sample(event, session, &al, NULL) < 0) {
119 		fprintf(stderr, "problem processing %d event, skipping it.\n",
120 			event->header.type);
121 		return -1;
122 	}
123 
124 	if (al.filtered)
125 		return 0;
126 
127 	if (perf_session__add_hist_entry(session, &al, data.callchain, data.period)) {
128 		pr_debug("problem incrementing symbol count, skipping event\n");
129 		return -1;
130 	}
131 
132 	session->events_stats.total += data.period;
133 	return 0;
134 }
135 
136 static int process_read_event(event_t *event, struct perf_session *session __used)
137 {
138 	struct perf_event_attr *attr;
139 
140 	attr = perf_header__find_attr(event->read.id, &session->header);
141 
142 	if (show_threads) {
143 		const char *name = attr ? __event_name(attr->type, attr->config)
144 				   : "unknown";
145 		perf_read_values_add_value(&show_threads_values,
146 					   event->read.pid, event->read.tid,
147 					   event->read.id,
148 					   name,
149 					   event->read.value);
150 	}
151 
152 	dump_printf(": %d %d %s %Lu\n", event->read.pid, event->read.tid,
153 		    attr ? __event_name(attr->type, attr->config) : "FAIL",
154 		    event->read.value);
155 
156 	return 0;
157 }
158 
159 static int sample_type_check(struct perf_session *session)
160 {
161 	if (!(session->sample_type & PERF_SAMPLE_CALLCHAIN)) {
162 		if (sort__has_parent) {
163 			fprintf(stderr, "selected --sort parent, but no"
164 					" callchain data. Did you call"
165 					" perf record without -g?\n");
166 			return -1;
167 		}
168 		if (symbol_conf.use_callchain) {
169 			fprintf(stderr, "selected -g but no callchain data."
170 					" Did you call perf record without"
171 					" -g?\n");
172 			return -1;
173 		}
174 	} else if (callchain_param.mode != CHAIN_NONE && !symbol_conf.use_callchain) {
175 			symbol_conf.use_callchain = true;
176 			if (register_callchain_param(&callchain_param) < 0) {
177 				fprintf(stderr, "Can't register callchain"
178 						" params\n");
179 				return -1;
180 			}
181 	}
182 
183 	return 0;
184 }
185 
186 static struct perf_event_ops event_ops = {
187 	.process_sample_event	= process_sample_event,
188 	.process_mmap_event	= event__process_mmap,
189 	.process_comm_event	= event__process_comm,
190 	.process_exit_event	= event__process_task,
191 	.process_fork_event	= event__process_task,
192 	.process_lost_event	= event__process_lost,
193 	.process_read_event	= process_read_event,
194 	.sample_type_check	= sample_type_check,
195 };
196 
197 
198 static int __cmd_report(void)
199 {
200 	int ret;
201 	struct perf_session *session;
202 
203 	session = perf_session__new(input_name, O_RDONLY, force);
204 	if (session == NULL)
205 		return -ENOMEM;
206 
207 	if (show_threads)
208 		perf_read_values_init(&show_threads_values);
209 
210 	ret = perf_session__process_events(session, &event_ops);
211 	if (ret)
212 		goto out_delete;
213 
214 	if (dump_trace) {
215 		event__print_totals();
216 		goto out_delete;
217 	}
218 
219 	if (verbose > 3)
220 		perf_session__fprintf(session, stdout);
221 
222 	if (verbose > 2)
223 		dsos__fprintf(stdout);
224 
225 	perf_session__collapse_resort(session);
226 	perf_session__output_resort(session, session->events_stats.total);
227 	fprintf(stdout, "# Samples: %Ld\n#\n", session->events_stats.total);
228 	perf_session__fprintf_hists(session, NULL, false, stdout);
229 	if (sort_order == default_sort_order &&
230 	    parent_pattern == default_parent_pattern)
231 		fprintf(stdout, "#\n# (For a higher level overview, try: perf report --sort comm,dso)\n#\n");
232 
233 	if (show_threads) {
234 		bool raw_printing_style = !strcmp(pretty_printing_style, "raw");
235 		perf_read_values_display(stdout, &show_threads_values,
236 					 raw_printing_style);
237 		perf_read_values_destroy(&show_threads_values);
238 	}
239 out_delete:
240 	perf_session__delete(session);
241 	return ret;
242 }
243 
244 static int
245 parse_callchain_opt(const struct option *opt __used, const char *arg,
246 		    int unset __used)
247 {
248 	char *tok;
249 	char *endptr;
250 
251 	symbol_conf.use_callchain = true;
252 
253 	if (!arg)
254 		return 0;
255 
256 	tok = strtok((char *)arg, ",");
257 	if (!tok)
258 		return -1;
259 
260 	/* get the output mode */
261 	if (!strncmp(tok, "graph", strlen(arg)))
262 		callchain_param.mode = CHAIN_GRAPH_ABS;
263 
264 	else if (!strncmp(tok, "flat", strlen(arg)))
265 		callchain_param.mode = CHAIN_FLAT;
266 
267 	else if (!strncmp(tok, "fractal", strlen(arg)))
268 		callchain_param.mode = CHAIN_GRAPH_REL;
269 
270 	else if (!strncmp(tok, "none", strlen(arg))) {
271 		callchain_param.mode = CHAIN_NONE;
272 		symbol_conf.use_callchain = true;
273 
274 		return 0;
275 	}
276 
277 	else
278 		return -1;
279 
280 	/* get the min percentage */
281 	tok = strtok(NULL, ",");
282 	if (!tok)
283 		goto setup;
284 
285 	callchain_param.min_percent = strtod(tok, &endptr);
286 	if (tok == endptr)
287 		return -1;
288 
289 setup:
290 	if (register_callchain_param(&callchain_param) < 0) {
291 		fprintf(stderr, "Can't register callchain params\n");
292 		return -1;
293 	}
294 	return 0;
295 }
296 
297 static const char * const report_usage[] = {
298 	"perf report [<options>] <command>",
299 	NULL
300 };
301 
302 static const struct option options[] = {
303 	OPT_STRING('i', "input", &input_name, "file",
304 		    "input file name"),
305 	OPT_BOOLEAN('v', "verbose", &verbose,
306 		    "be more verbose (show symbol address, etc)"),
307 	OPT_BOOLEAN('D', "dump-raw-trace", &dump_trace,
308 		    "dump raw trace in ASCII"),
309 	OPT_STRING('k', "vmlinux", &symbol_conf.vmlinux_name,
310 		   "file", "vmlinux pathname"),
311 	OPT_BOOLEAN('f', "force", &force, "don't complain, do it"),
312 	OPT_BOOLEAN('m', "modules", &symbol_conf.use_modules,
313 		    "load module symbols - WARNING: use only with -k and LIVE kernel"),
314 	OPT_BOOLEAN('n', "show-nr-samples", &symbol_conf.show_nr_samples,
315 		    "Show a column with the number of samples"),
316 	OPT_BOOLEAN('T', "threads", &show_threads,
317 		    "Show per-thread event counters"),
318 	OPT_STRING(0, "pretty", &pretty_printing_style, "key",
319 		   "pretty printing style key: normal raw"),
320 	OPT_STRING('s', "sort", &sort_order, "key[,key2...]",
321 		   "sort by key(s): pid, comm, dso, symbol, parent"),
322 	OPT_BOOLEAN('P', "full-paths", &event_ops.full_paths,
323 		    "Don't shorten the pathnames taking into account the cwd"),
324 	OPT_STRING('p', "parent", &parent_pattern, "regex",
325 		   "regex filter to identify parent, see: '--sort parent'"),
326 	OPT_BOOLEAN('x', "exclude-other", &symbol_conf.exclude_other,
327 		    "Only display entries with parent-match"),
328 	OPT_CALLBACK_DEFAULT('g', "call-graph", NULL, "output_type,min_percent",
329 		     "Display callchains using output_type and min percent threshold. "
330 		     "Default: fractal,0.5", &parse_callchain_opt, callchain_default_opt),
331 	OPT_STRING('d', "dsos", &symbol_conf.dso_list_str, "dso[,dso...]",
332 		   "only consider symbols in these dsos"),
333 	OPT_STRING('C', "comms", &symbol_conf.comm_list_str, "comm[,comm...]",
334 		   "only consider symbols in these comms"),
335 	OPT_STRING('S', "symbols", &symbol_conf.sym_list_str, "symbol[,symbol...]",
336 		   "only consider these symbols"),
337 	OPT_STRING('w', "column-widths", &symbol_conf.col_width_list_str,
338 		   "width[,width...]",
339 		   "don't try to adjust column width, use these fixed values"),
340 	OPT_STRING('t', "field-separator", &symbol_conf.field_sep, "separator",
341 		   "separator for columns, no spaces will be added between "
342 		   "columns '.' is reserved."),
343 	OPT_END()
344 };
345 
346 int cmd_report(int argc, const char **argv, const char *prefix __used)
347 {
348 	argc = parse_options(argc, argv, options, report_usage, 0);
349 
350 	setup_pager();
351 
352 	if (symbol__init() < 0)
353 		return -1;
354 
355 	setup_sorting(report_usage, options);
356 
357 	if (parent_pattern != default_parent_pattern) {
358 		sort_dimension__add("parent");
359 		sort_parent.elide = 1;
360 	} else
361 		symbol_conf.exclude_other = false;
362 
363 	/*
364 	 * Any (unrecognized) arguments left?
365 	 */
366 	if (argc)
367 		usage_with_options(report_usage, options);
368 
369 	sort_entry__setup_elide(&sort_dso, symbol_conf.dso_list, "dso", stdout);
370 	sort_entry__setup_elide(&sort_comm, symbol_conf.comm_list, "comm", stdout);
371 	sort_entry__setup_elide(&sort_sym, symbol_conf.sym_list, "symbol", stdout);
372 
373 	return __cmd_report();
374 }
375