xref: /openbmc/linux/tools/perf/ui/stdio/hist.c (revision 9726bfcd)
1 // SPDX-License-Identifier: GPL-2.0
2 #include <stdio.h>
3 #include <linux/string.h>
4 
5 #include "../../util/callchain.h"
6 #include "../../util/hist.h"
7 #include "../../util/map.h"
8 #include "../../util/map_groups.h"
9 #include "../../util/symbol.h"
10 #include "../../util/sort.h"
11 #include "../../util/evsel.h"
12 #include "../../util/srcline.h"
13 #include "../../util/string2.h"
14 #include "../../util/thread.h"
15 #include <linux/ctype.h>
16 #include <linux/zalloc.h>
17 
18 static size_t callchain__fprintf_left_margin(FILE *fp, int left_margin)
19 {
20 	int i;
21 	int ret = fprintf(fp, "            ");
22 
23 	for (i = 0; i < left_margin; i++)
24 		ret += fprintf(fp, " ");
25 
26 	return ret;
27 }
28 
29 static size_t ipchain__fprintf_graph_line(FILE *fp, int depth, int depth_mask,
30 					  int left_margin)
31 {
32 	int i;
33 	size_t ret = callchain__fprintf_left_margin(fp, left_margin);
34 
35 	for (i = 0; i < depth; i++)
36 		if (depth_mask & (1 << i))
37 			ret += fprintf(fp, "|          ");
38 		else
39 			ret += fprintf(fp, "           ");
40 
41 	ret += fprintf(fp, "\n");
42 
43 	return ret;
44 }
45 
46 static size_t ipchain__fprintf_graph(FILE *fp, struct callchain_node *node,
47 				     struct callchain_list *chain,
48 				     int depth, int depth_mask, int period,
49 				     u64 total_samples, int left_margin)
50 {
51 	int i;
52 	size_t ret = 0;
53 	char bf[1024], *alloc_str = NULL;
54 	char buf[64];
55 	const char *str;
56 
57 	ret += callchain__fprintf_left_margin(fp, left_margin);
58 	for (i = 0; i < depth; i++) {
59 		if (depth_mask & (1 << i))
60 			ret += fprintf(fp, "|");
61 		else
62 			ret += fprintf(fp, " ");
63 		if (!period && i == depth - 1) {
64 			ret += fprintf(fp, "--");
65 			ret += callchain_node__fprintf_value(node, fp, total_samples);
66 			ret += fprintf(fp, "--");
67 		} else
68 			ret += fprintf(fp, "%s", "          ");
69 	}
70 
71 	str = callchain_list__sym_name(chain, bf, sizeof(bf), false);
72 
73 	if (symbol_conf.show_branchflag_count) {
74 		callchain_list_counts__printf_value(chain, NULL,
75 						    buf, sizeof(buf));
76 
77 		if (asprintf(&alloc_str, "%s%s", str, buf) < 0)
78 			str = "Not enough memory!";
79 		else
80 			str = alloc_str;
81 	}
82 
83 	fputs(str, fp);
84 	fputc('\n', fp);
85 	free(alloc_str);
86 
87 	return ret;
88 }
89 
90 static struct symbol *rem_sq_bracket;
91 static struct callchain_list rem_hits;
92 
93 static void init_rem_hits(void)
94 {
95 	rem_sq_bracket = malloc(sizeof(*rem_sq_bracket) + 6);
96 	if (!rem_sq_bracket) {
97 		fprintf(stderr, "Not enough memory to display remaining hits\n");
98 		return;
99 	}
100 
101 	strcpy(rem_sq_bracket->name, "[...]");
102 	rem_hits.ms.sym = rem_sq_bracket;
103 }
104 
105 static size_t __callchain__fprintf_graph(FILE *fp, struct rb_root *root,
106 					 u64 total_samples, int depth,
107 					 int depth_mask, int left_margin)
108 {
109 	struct rb_node *node, *next;
110 	struct callchain_node *child = NULL;
111 	struct callchain_list *chain;
112 	int new_depth_mask = depth_mask;
113 	u64 remaining;
114 	size_t ret = 0;
115 	int i;
116 	uint entries_printed = 0;
117 	int cumul_count = 0;
118 
119 	remaining = total_samples;
120 
121 	node = rb_first(root);
122 	while (node) {
123 		u64 new_total;
124 		u64 cumul;
125 
126 		child = rb_entry(node, struct callchain_node, rb_node);
127 		cumul = callchain_cumul_hits(child);
128 		remaining -= cumul;
129 		cumul_count += callchain_cumul_counts(child);
130 
131 		/*
132 		 * The depth mask manages the output of pipes that show
133 		 * the depth. We don't want to keep the pipes of the current
134 		 * level for the last child of this depth.
135 		 * Except if we have remaining filtered hits. They will
136 		 * supersede the last child
137 		 */
138 		next = rb_next(node);
139 		if (!next && (callchain_param.mode != CHAIN_GRAPH_REL || !remaining))
140 			new_depth_mask &= ~(1 << (depth - 1));
141 
142 		/*
143 		 * But we keep the older depth mask for the line separator
144 		 * to keep the level link until we reach the last child
145 		 */
146 		ret += ipchain__fprintf_graph_line(fp, depth, depth_mask,
147 						   left_margin);
148 		i = 0;
149 		list_for_each_entry(chain, &child->val, list) {
150 			ret += ipchain__fprintf_graph(fp, child, chain, depth,
151 						      new_depth_mask, i++,
152 						      total_samples,
153 						      left_margin);
154 		}
155 
156 		if (callchain_param.mode == CHAIN_GRAPH_REL)
157 			new_total = child->children_hit;
158 		else
159 			new_total = total_samples;
160 
161 		ret += __callchain__fprintf_graph(fp, &child->rb_root, new_total,
162 						  depth + 1,
163 						  new_depth_mask | (1 << depth),
164 						  left_margin);
165 		node = next;
166 		if (++entries_printed == callchain_param.print_limit)
167 			break;
168 	}
169 
170 	if (callchain_param.mode == CHAIN_GRAPH_REL &&
171 		remaining && remaining != total_samples) {
172 		struct callchain_node rem_node = {
173 			.hit = remaining,
174 		};
175 
176 		if (!rem_sq_bracket)
177 			return ret;
178 
179 		if (callchain_param.value == CCVAL_COUNT && child && child->parent) {
180 			rem_node.count = child->parent->children_count - cumul_count;
181 			if (rem_node.count <= 0)
182 				return ret;
183 		}
184 
185 		new_depth_mask &= ~(1 << (depth - 1));
186 		ret += ipchain__fprintf_graph(fp, &rem_node, &rem_hits, depth,
187 					      new_depth_mask, 0, total_samples,
188 					      left_margin);
189 	}
190 
191 	return ret;
192 }
193 
194 /*
195  * If have one single callchain root, don't bother printing
196  * its percentage (100 % in fractal mode and the same percentage
197  * than the hist in graph mode). This also avoid one level of column.
198  *
199  * However when percent-limit applied, it's possible that single callchain
200  * node have different (non-100% in fractal mode) percentage.
201  */
202 static bool need_percent_display(struct rb_node *node, u64 parent_samples)
203 {
204 	struct callchain_node *cnode;
205 
206 	if (rb_next(node))
207 		return true;
208 
209 	cnode = rb_entry(node, struct callchain_node, rb_node);
210 	return callchain_cumul_hits(cnode) != parent_samples;
211 }
212 
213 static size_t callchain__fprintf_graph(FILE *fp, struct rb_root *root,
214 				       u64 total_samples, u64 parent_samples,
215 				       int left_margin)
216 {
217 	struct callchain_node *cnode;
218 	struct callchain_list *chain;
219 	u32 entries_printed = 0;
220 	bool printed = false;
221 	struct rb_node *node;
222 	int i = 0;
223 	int ret = 0;
224 	char bf[1024];
225 
226 	node = rb_first(root);
227 	if (node && !need_percent_display(node, parent_samples)) {
228 		cnode = rb_entry(node, struct callchain_node, rb_node);
229 		list_for_each_entry(chain, &cnode->val, list) {
230 			/*
231 			 * If we sort by symbol, the first entry is the same than
232 			 * the symbol. No need to print it otherwise it appears as
233 			 * displayed twice.
234 			 */
235 			if (!i++ && field_order == NULL &&
236 			    sort_order && strstarts(sort_order, "sym"))
237 				continue;
238 
239 			if (!printed) {
240 				ret += callchain__fprintf_left_margin(fp, left_margin);
241 				ret += fprintf(fp, "|\n");
242 				ret += callchain__fprintf_left_margin(fp, left_margin);
243 				ret += fprintf(fp, "---");
244 				left_margin += 3;
245 				printed = true;
246 			} else
247 				ret += callchain__fprintf_left_margin(fp, left_margin);
248 
249 			ret += fprintf(fp, "%s",
250 				       callchain_list__sym_name(chain, bf,
251 								sizeof(bf),
252 								false));
253 
254 			if (symbol_conf.show_branchflag_count)
255 				ret += callchain_list_counts__printf_value(
256 						chain, fp, NULL, 0);
257 			ret += fprintf(fp, "\n");
258 
259 			if (++entries_printed == callchain_param.print_limit)
260 				break;
261 		}
262 		root = &cnode->rb_root;
263 	}
264 
265 	if (callchain_param.mode == CHAIN_GRAPH_REL)
266 		total_samples = parent_samples;
267 
268 	ret += __callchain__fprintf_graph(fp, root, total_samples,
269 					  1, 1, left_margin);
270 	if (ret) {
271 		/* do not add a blank line if it printed nothing */
272 		ret += fprintf(fp, "\n");
273 	}
274 
275 	return ret;
276 }
277 
278 static size_t __callchain__fprintf_flat(FILE *fp, struct callchain_node *node,
279 					u64 total_samples)
280 {
281 	struct callchain_list *chain;
282 	size_t ret = 0;
283 	char bf[1024];
284 
285 	if (!node)
286 		return 0;
287 
288 	ret += __callchain__fprintf_flat(fp, node->parent, total_samples);
289 
290 
291 	list_for_each_entry(chain, &node->val, list) {
292 		if (chain->ip >= PERF_CONTEXT_MAX)
293 			continue;
294 		ret += fprintf(fp, "                %s\n", callchain_list__sym_name(chain,
295 					bf, sizeof(bf), false));
296 	}
297 
298 	return ret;
299 }
300 
301 static size_t callchain__fprintf_flat(FILE *fp, struct rb_root *tree,
302 				      u64 total_samples)
303 {
304 	size_t ret = 0;
305 	u32 entries_printed = 0;
306 	struct callchain_node *chain;
307 	struct rb_node *rb_node = rb_first(tree);
308 
309 	while (rb_node) {
310 		chain = rb_entry(rb_node, struct callchain_node, rb_node);
311 
312 		ret += fprintf(fp, "           ");
313 		ret += callchain_node__fprintf_value(chain, fp, total_samples);
314 		ret += fprintf(fp, "\n");
315 		ret += __callchain__fprintf_flat(fp, chain, total_samples);
316 		ret += fprintf(fp, "\n");
317 		if (++entries_printed == callchain_param.print_limit)
318 			break;
319 
320 		rb_node = rb_next(rb_node);
321 	}
322 
323 	return ret;
324 }
325 
326 static size_t __callchain__fprintf_folded(FILE *fp, struct callchain_node *node)
327 {
328 	const char *sep = symbol_conf.field_sep ?: ";";
329 	struct callchain_list *chain;
330 	size_t ret = 0;
331 	char bf[1024];
332 	bool first;
333 
334 	if (!node)
335 		return 0;
336 
337 	ret += __callchain__fprintf_folded(fp, node->parent);
338 
339 	first = (ret == 0);
340 	list_for_each_entry(chain, &node->val, list) {
341 		if (chain->ip >= PERF_CONTEXT_MAX)
342 			continue;
343 		ret += fprintf(fp, "%s%s", first ? "" : sep,
344 			       callchain_list__sym_name(chain,
345 						bf, sizeof(bf), false));
346 		first = false;
347 	}
348 
349 	return ret;
350 }
351 
352 static size_t callchain__fprintf_folded(FILE *fp, struct rb_root *tree,
353 					u64 total_samples)
354 {
355 	size_t ret = 0;
356 	u32 entries_printed = 0;
357 	struct callchain_node *chain;
358 	struct rb_node *rb_node = rb_first(tree);
359 
360 	while (rb_node) {
361 
362 		chain = rb_entry(rb_node, struct callchain_node, rb_node);
363 
364 		ret += callchain_node__fprintf_value(chain, fp, total_samples);
365 		ret += fprintf(fp, " ");
366 		ret += __callchain__fprintf_folded(fp, chain);
367 		ret += fprintf(fp, "\n");
368 		if (++entries_printed == callchain_param.print_limit)
369 			break;
370 
371 		rb_node = rb_next(rb_node);
372 	}
373 
374 	return ret;
375 }
376 
377 static size_t hist_entry_callchain__fprintf(struct hist_entry *he,
378 					    u64 total_samples, int left_margin,
379 					    FILE *fp)
380 {
381 	u64 parent_samples = he->stat.period;
382 
383 	if (symbol_conf.cumulate_callchain)
384 		parent_samples = he->stat_acc->period;
385 
386 	switch (callchain_param.mode) {
387 	case CHAIN_GRAPH_REL:
388 		return callchain__fprintf_graph(fp, &he->sorted_chain, total_samples,
389 						parent_samples, left_margin);
390 		break;
391 	case CHAIN_GRAPH_ABS:
392 		return callchain__fprintf_graph(fp, &he->sorted_chain, total_samples,
393 						parent_samples, left_margin);
394 		break;
395 	case CHAIN_FLAT:
396 		return callchain__fprintf_flat(fp, &he->sorted_chain, total_samples);
397 		break;
398 	case CHAIN_FOLDED:
399 		return callchain__fprintf_folded(fp, &he->sorted_chain, total_samples);
400 		break;
401 	case CHAIN_NONE:
402 		break;
403 	default:
404 		pr_err("Bad callchain mode\n");
405 	}
406 
407 	return 0;
408 }
409 
410 int __hist_entry__snprintf(struct hist_entry *he, struct perf_hpp *hpp,
411 			   struct perf_hpp_list *hpp_list)
412 {
413 	const char *sep = symbol_conf.field_sep;
414 	struct perf_hpp_fmt *fmt;
415 	char *start = hpp->buf;
416 	int ret;
417 	bool first = true;
418 
419 	if (symbol_conf.exclude_other && !he->parent)
420 		return 0;
421 
422 	perf_hpp_list__for_each_format(hpp_list, fmt) {
423 		if (perf_hpp__should_skip(fmt, he->hists))
424 			continue;
425 
426 		/*
427 		 * If there's no field_sep, we still need
428 		 * to display initial '  '.
429 		 */
430 		if (!sep || !first) {
431 			ret = scnprintf(hpp->buf, hpp->size, "%s", sep ?: "  ");
432 			advance_hpp(hpp, ret);
433 		} else
434 			first = false;
435 
436 		if (perf_hpp__use_color() && fmt->color)
437 			ret = fmt->color(fmt, hpp, he);
438 		else
439 			ret = fmt->entry(fmt, hpp, he);
440 
441 		ret = hist_entry__snprintf_alignment(he, hpp, fmt, ret);
442 		advance_hpp(hpp, ret);
443 	}
444 
445 	return hpp->buf - start;
446 }
447 
448 static int hist_entry__snprintf(struct hist_entry *he, struct perf_hpp *hpp)
449 {
450 	return __hist_entry__snprintf(he, hpp, he->hists->hpp_list);
451 }
452 
453 static int hist_entry__hierarchy_fprintf(struct hist_entry *he,
454 					 struct perf_hpp *hpp,
455 					 struct hists *hists,
456 					 FILE *fp)
457 {
458 	const char *sep = symbol_conf.field_sep;
459 	struct perf_hpp_fmt *fmt;
460 	struct perf_hpp_list_node *fmt_node;
461 	char *buf = hpp->buf;
462 	size_t size = hpp->size;
463 	int ret, printed = 0;
464 	bool first = true;
465 
466 	if (symbol_conf.exclude_other && !he->parent)
467 		return 0;
468 
469 	ret = scnprintf(hpp->buf, hpp->size, "%*s", he->depth * HIERARCHY_INDENT, "");
470 	advance_hpp(hpp, ret);
471 
472 	/* the first hpp_list_node is for overhead columns */
473 	fmt_node = list_first_entry(&hists->hpp_formats,
474 				    struct perf_hpp_list_node, list);
475 	perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) {
476 		/*
477 		 * If there's no field_sep, we still need
478 		 * to display initial '  '.
479 		 */
480 		if (!sep || !first) {
481 			ret = scnprintf(hpp->buf, hpp->size, "%s", sep ?: "  ");
482 			advance_hpp(hpp, ret);
483 		} else
484 			first = false;
485 
486 		if (perf_hpp__use_color() && fmt->color)
487 			ret = fmt->color(fmt, hpp, he);
488 		else
489 			ret = fmt->entry(fmt, hpp, he);
490 
491 		ret = hist_entry__snprintf_alignment(he, hpp, fmt, ret);
492 		advance_hpp(hpp, ret);
493 	}
494 
495 	if (!sep)
496 		ret = scnprintf(hpp->buf, hpp->size, "%*s",
497 				(hists->nr_hpp_node - 2) * HIERARCHY_INDENT, "");
498 	advance_hpp(hpp, ret);
499 
500 	printed += fprintf(fp, "%s", buf);
501 
502 	perf_hpp_list__for_each_format(he->hpp_list, fmt) {
503 		hpp->buf  = buf;
504 		hpp->size = size;
505 
506 		/*
507 		 * No need to call hist_entry__snprintf_alignment() since this
508 		 * fmt is always the last column in the hierarchy mode.
509 		 */
510 		if (perf_hpp__use_color() && fmt->color)
511 			fmt->color(fmt, hpp, he);
512 		else
513 			fmt->entry(fmt, hpp, he);
514 
515 		/*
516 		 * dynamic entries are right-aligned but we want left-aligned
517 		 * in the hierarchy mode
518 		 */
519 		printed += fprintf(fp, "%s%s", sep ?: "  ", skip_spaces(buf));
520 	}
521 	printed += putc('\n', fp);
522 
523 	if (he->leaf && hist_entry__has_callchains(he) && symbol_conf.use_callchain) {
524 		u64 total = hists__total_period(hists);
525 
526 		printed += hist_entry_callchain__fprintf(he, total, 0, fp);
527 		goto out;
528 	}
529 
530 out:
531 	return printed;
532 }
533 
534 static int hist_entry__block_fprintf(struct hist_entry *he,
535 				     char *bf, size_t size,
536 				     FILE *fp)
537 {
538 	struct block_hist *bh = container_of(he, struct block_hist, he);
539 	int ret = 0;
540 
541 	for (unsigned int i = 0; i < bh->block_hists.nr_entries; i++) {
542 		struct perf_hpp hpp = {
543 			.buf		= bf,
544 			.size		= size,
545 			.skip		= false,
546 		};
547 
548 		bh->block_idx = i;
549 		hist_entry__snprintf(he, &hpp);
550 
551 		if (!hpp.skip)
552 			ret += fprintf(fp, "%s\n", bf);
553 	}
554 
555 	return ret;
556 }
557 
558 static int hist_entry__fprintf(struct hist_entry *he, size_t size,
559 			       char *bf, size_t bfsz, FILE *fp,
560 			       bool ignore_callchains)
561 {
562 	int ret;
563 	int callchain_ret = 0;
564 	struct perf_hpp hpp = {
565 		.buf		= bf,
566 		.size		= size,
567 	};
568 	struct hists *hists = he->hists;
569 	u64 total_period = hists->stats.total_period;
570 
571 	if (size == 0 || size > bfsz)
572 		size = hpp.size = bfsz;
573 
574 	if (symbol_conf.report_hierarchy)
575 		return hist_entry__hierarchy_fprintf(he, &hpp, hists, fp);
576 
577 	if (symbol_conf.report_block)
578 		return hist_entry__block_fprintf(he, bf, size, fp);
579 
580 	hist_entry__snprintf(he, &hpp);
581 
582 	ret = fprintf(fp, "%s\n", bf);
583 
584 	if (hist_entry__has_callchains(he) && !ignore_callchains)
585 		callchain_ret = hist_entry_callchain__fprintf(he, total_period,
586 							      0, fp);
587 
588 	ret += callchain_ret;
589 
590 	return ret;
591 }
592 
593 static int print_hierarchy_indent(const char *sep, int indent,
594 				  const char *line, FILE *fp)
595 {
596 	int width;
597 
598 	if (sep != NULL || indent < 2)
599 		return 0;
600 
601 	width = (indent - 2) * HIERARCHY_INDENT;
602 
603 	return fprintf(fp, "%-*.*s", width, width, line);
604 }
605 
606 static int hists__fprintf_hierarchy_headers(struct hists *hists,
607 					    struct perf_hpp *hpp, FILE *fp)
608 {
609 	bool first_node, first_col;
610 	int indent;
611 	int depth;
612 	unsigned width = 0;
613 	unsigned header_width = 0;
614 	struct perf_hpp_fmt *fmt;
615 	struct perf_hpp_list_node *fmt_node;
616 	const char *sep = symbol_conf.field_sep;
617 
618 	indent = hists->nr_hpp_node;
619 
620 	/* preserve max indent depth for column headers */
621 	print_hierarchy_indent(sep, indent, " ", fp);
622 
623 	/* the first hpp_list_node is for overhead columns */
624 	fmt_node = list_first_entry(&hists->hpp_formats,
625 				    struct perf_hpp_list_node, list);
626 
627 	perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) {
628 		fmt->header(fmt, hpp, hists, 0, NULL);
629 		fprintf(fp, "%s%s", hpp->buf, sep ?: "  ");
630 	}
631 
632 	/* combine sort headers with ' / ' */
633 	first_node = true;
634 	list_for_each_entry_continue(fmt_node, &hists->hpp_formats, list) {
635 		if (!first_node)
636 			header_width += fprintf(fp, " / ");
637 		first_node = false;
638 
639 		first_col = true;
640 		perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) {
641 			if (perf_hpp__should_skip(fmt, hists))
642 				continue;
643 
644 			if (!first_col)
645 				header_width += fprintf(fp, "+");
646 			first_col = false;
647 
648 			fmt->header(fmt, hpp, hists, 0, NULL);
649 
650 			header_width += fprintf(fp, "%s", strim(hpp->buf));
651 		}
652 	}
653 
654 	fprintf(fp, "\n# ");
655 
656 	/* preserve max indent depth for initial dots */
657 	print_hierarchy_indent(sep, indent, dots, fp);
658 
659 	/* the first hpp_list_node is for overhead columns */
660 	fmt_node = list_first_entry(&hists->hpp_formats,
661 				    struct perf_hpp_list_node, list);
662 
663 	first_col = true;
664 	perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) {
665 		if (!first_col)
666 			fprintf(fp, "%s", sep ?: "..");
667 		first_col = false;
668 
669 		width = fmt->width(fmt, hpp, hists);
670 		fprintf(fp, "%.*s", width, dots);
671 	}
672 
673 	depth = 0;
674 	list_for_each_entry_continue(fmt_node, &hists->hpp_formats, list) {
675 		first_col = true;
676 		width = depth * HIERARCHY_INDENT;
677 
678 		perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) {
679 			if (perf_hpp__should_skip(fmt, hists))
680 				continue;
681 
682 			if (!first_col)
683 				width++;  /* for '+' sign between column header */
684 			first_col = false;
685 
686 			width += fmt->width(fmt, hpp, hists);
687 		}
688 
689 		if (width > header_width)
690 			header_width = width;
691 
692 		depth++;
693 	}
694 
695 	fprintf(fp, "%s%-.*s", sep ?: "  ", header_width, dots);
696 
697 	fprintf(fp, "\n#\n");
698 
699 	return 2;
700 }
701 
702 static void fprintf_line(struct hists *hists, struct perf_hpp *hpp,
703 			 int line, FILE *fp)
704 {
705 	struct perf_hpp_fmt *fmt;
706 	const char *sep = symbol_conf.field_sep;
707 	bool first = true;
708 	int span = 0;
709 
710 	hists__for_each_format(hists, fmt) {
711 		if (perf_hpp__should_skip(fmt, hists))
712 			continue;
713 
714 		if (!first && !span)
715 			fprintf(fp, "%s", sep ?: "  ");
716 		else
717 			first = false;
718 
719 		fmt->header(fmt, hpp, hists, line, &span);
720 
721 		if (!span)
722 			fprintf(fp, "%s", hpp->buf);
723 	}
724 }
725 
726 static int
727 hists__fprintf_standard_headers(struct hists *hists,
728 				struct perf_hpp *hpp,
729 				FILE *fp)
730 {
731 	struct perf_hpp_list *hpp_list = hists->hpp_list;
732 	struct perf_hpp_fmt *fmt;
733 	unsigned int width;
734 	const char *sep = symbol_conf.field_sep;
735 	bool first = true;
736 	int line;
737 
738 	for (line = 0; line < hpp_list->nr_header_lines; line++) {
739 		/* first # is displayed one level up */
740 		if (line)
741 			fprintf(fp, "# ");
742 		fprintf_line(hists, hpp, line, fp);
743 		fprintf(fp, "\n");
744 	}
745 
746 	if (sep)
747 		return hpp_list->nr_header_lines;
748 
749 	first = true;
750 
751 	fprintf(fp, "# ");
752 
753 	hists__for_each_format(hists, fmt) {
754 		unsigned int i;
755 
756 		if (perf_hpp__should_skip(fmt, hists))
757 			continue;
758 
759 		if (!first)
760 			fprintf(fp, "%s", sep ?: "  ");
761 		else
762 			first = false;
763 
764 		width = fmt->width(fmt, hpp, hists);
765 		for (i = 0; i < width; i++)
766 			fprintf(fp, ".");
767 	}
768 
769 	fprintf(fp, "\n");
770 	fprintf(fp, "#\n");
771 	return hpp_list->nr_header_lines + 2;
772 }
773 
774 int hists__fprintf_headers(struct hists *hists, FILE *fp)
775 {
776 	char bf[1024];
777 	struct perf_hpp dummy_hpp = {
778 		.buf	= bf,
779 		.size	= sizeof(bf),
780 	};
781 
782 	fprintf(fp, "# ");
783 
784 	if (symbol_conf.report_hierarchy)
785 		return hists__fprintf_hierarchy_headers(hists, &dummy_hpp, fp);
786 	else
787 		return hists__fprintf_standard_headers(hists, &dummy_hpp, fp);
788 
789 }
790 
791 size_t hists__fprintf(struct hists *hists, bool show_header, int max_rows,
792 		      int max_cols, float min_pcnt, FILE *fp,
793 		      bool ignore_callchains)
794 {
795 	struct rb_node *nd;
796 	size_t ret = 0;
797 	const char *sep = symbol_conf.field_sep;
798 	int nr_rows = 0;
799 	size_t linesz;
800 	char *line = NULL;
801 	unsigned indent;
802 
803 	init_rem_hits();
804 
805 	hists__reset_column_width(hists);
806 
807 	if (symbol_conf.col_width_list_str)
808 		perf_hpp__set_user_width(symbol_conf.col_width_list_str);
809 
810 	if (show_header)
811 		nr_rows += hists__fprintf_headers(hists, fp);
812 
813 	if (max_rows && nr_rows >= max_rows)
814 		goto out;
815 
816 	linesz = hists__sort_list_width(hists) + 3 + 1;
817 	linesz += perf_hpp__color_overhead();
818 	line = malloc(linesz);
819 	if (line == NULL) {
820 		ret = -1;
821 		goto out;
822 	}
823 
824 	indent = hists__overhead_width(hists) + 4;
825 
826 	for (nd = rb_first_cached(&hists->entries); nd;
827 	     nd = __rb_hierarchy_next(nd, HMD_FORCE_CHILD)) {
828 		struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
829 		float percent;
830 
831 		if (h->filtered)
832 			continue;
833 
834 		percent = hist_entry__get_percent_limit(h);
835 		if (percent < min_pcnt)
836 			continue;
837 
838 		ret += hist_entry__fprintf(h, max_cols, line, linesz, fp, ignore_callchains);
839 
840 		if (max_rows && ++nr_rows >= max_rows)
841 			break;
842 
843 		/*
844 		 * If all children are filtered out or percent-limited,
845 		 * display "no entry >= x.xx%" message.
846 		 */
847 		if (!h->leaf && !hist_entry__has_hierarchy_children(h, min_pcnt)) {
848 			int depth = hists->nr_hpp_node + h->depth + 1;
849 
850 			print_hierarchy_indent(sep, depth, " ", fp);
851 			fprintf(fp, "%*sno entry >= %.2f%%\n", indent, "", min_pcnt);
852 
853 			if (max_rows && ++nr_rows >= max_rows)
854 				break;
855 		}
856 
857 		if (h->ms.map == NULL && verbose > 1) {
858 			map_groups__fprintf(h->thread->mg, fp);
859 			fprintf(fp, "%.10s end\n", graph_dotted_line);
860 		}
861 	}
862 
863 	free(line);
864 out:
865 	zfree(&rem_sq_bracket);
866 
867 	return ret;
868 }
869 
870 size_t events_stats__fprintf(struct events_stats *stats, FILE *fp)
871 {
872 	int i;
873 	size_t ret = 0;
874 
875 	for (i = 0; i < PERF_RECORD_HEADER_MAX; ++i) {
876 		const char *name;
877 
878 		name = perf_event__name(i);
879 		if (!strcmp(name, "UNKNOWN"))
880 			continue;
881 
882 		ret += fprintf(fp, "%16s events: %10d\n", name, stats->nr_events[i]);
883 	}
884 
885 	return ret;
886 }
887