diff options
Diffstat (limited to 'tools/perf/util/hist.c')
-rw-r--r-- | tools/perf/util/hist.c | 384 |
1 files changed, 384 insertions, 0 deletions
diff --git a/tools/perf/util/hist.c b/tools/perf/util/hist.c index d9a5a19391dc..270eb8f3dcee 100644 --- a/tools/perf/util/hist.c +++ b/tools/perf/util/hist.c | |||
@@ -206,3 +206,387 @@ void perf_session__output_resort(struct perf_session *self, u64 total_samples) | |||
206 | 206 | ||
207 | self->hists = tmp; | 207 | self->hists = tmp; |
208 | } | 208 | } |
209 | |||
210 | static size_t callchain__fprintf_left_margin(FILE *fp, int left_margin) | ||
211 | { | ||
212 | int i; | ||
213 | int ret = fprintf(fp, " "); | ||
214 | |||
215 | for (i = 0; i < left_margin; i++) | ||
216 | ret += fprintf(fp, " "); | ||
217 | |||
218 | return ret; | ||
219 | } | ||
220 | |||
221 | static size_t ipchain__fprintf_graph_line(FILE *fp, int depth, int depth_mask, | ||
222 | int left_margin) | ||
223 | { | ||
224 | int i; | ||
225 | size_t ret = callchain__fprintf_left_margin(fp, left_margin); | ||
226 | |||
227 | for (i = 0; i < depth; i++) | ||
228 | if (depth_mask & (1 << i)) | ||
229 | ret += fprintf(fp, "| "); | ||
230 | else | ||
231 | ret += fprintf(fp, " "); | ||
232 | |||
233 | ret += fprintf(fp, "\n"); | ||
234 | |||
235 | return ret; | ||
236 | } | ||
237 | |||
238 | static size_t ipchain__fprintf_graph(FILE *fp, struct callchain_list *chain, | ||
239 | int depth, int depth_mask, int count, | ||
240 | u64 total_samples, int hits, | ||
241 | int left_margin) | ||
242 | { | ||
243 | int i; | ||
244 | size_t ret = 0; | ||
245 | |||
246 | ret += callchain__fprintf_left_margin(fp, left_margin); | ||
247 | for (i = 0; i < depth; i++) { | ||
248 | if (depth_mask & (1 << i)) | ||
249 | ret += fprintf(fp, "|"); | ||
250 | else | ||
251 | ret += fprintf(fp, " "); | ||
252 | if (!count && i == depth - 1) { | ||
253 | double percent; | ||
254 | |||
255 | percent = hits * 100.0 / total_samples; | ||
256 | ret += percent_color_fprintf(fp, "--%2.2f%%-- ", percent); | ||
257 | } else | ||
258 | ret += fprintf(fp, "%s", " "); | ||
259 | } | ||
260 | if (chain->sym) | ||
261 | ret += fprintf(fp, "%s\n", chain->sym->name); | ||
262 | else | ||
263 | ret += fprintf(fp, "%p\n", (void *)(long)chain->ip); | ||
264 | |||
265 | return ret; | ||
266 | } | ||
267 | |||
268 | static struct symbol *rem_sq_bracket; | ||
269 | static struct callchain_list rem_hits; | ||
270 | |||
271 | static void init_rem_hits(void) | ||
272 | { | ||
273 | rem_sq_bracket = malloc(sizeof(*rem_sq_bracket) + 6); | ||
274 | if (!rem_sq_bracket) { | ||
275 | fprintf(stderr, "Not enough memory to display remaining hits\n"); | ||
276 | return; | ||
277 | } | ||
278 | |||
279 | strcpy(rem_sq_bracket->name, "[...]"); | ||
280 | rem_hits.sym = rem_sq_bracket; | ||
281 | } | ||
282 | |||
283 | static size_t __callchain__fprintf_graph(FILE *fp, struct callchain_node *self, | ||
284 | u64 total_samples, int depth, | ||
285 | int depth_mask, int left_margin) | ||
286 | { | ||
287 | struct rb_node *node, *next; | ||
288 | struct callchain_node *child; | ||
289 | struct callchain_list *chain; | ||
290 | int new_depth_mask = depth_mask; | ||
291 | u64 new_total; | ||
292 | u64 remaining; | ||
293 | size_t ret = 0; | ||
294 | int i; | ||
295 | |||
296 | if (callchain_param.mode == CHAIN_GRAPH_REL) | ||
297 | new_total = self->children_hit; | ||
298 | else | ||
299 | new_total = total_samples; | ||
300 | |||
301 | remaining = new_total; | ||
302 | |||
303 | node = rb_first(&self->rb_root); | ||
304 | while (node) { | ||
305 | u64 cumul; | ||
306 | |||
307 | child = rb_entry(node, struct callchain_node, rb_node); | ||
308 | cumul = cumul_hits(child); | ||
309 | remaining -= cumul; | ||
310 | |||
311 | /* | ||
312 | * The depth mask manages the output of pipes that show | ||
313 | * the depth. We don't want to keep the pipes of the current | ||
314 | * level for the last child of this depth. | ||
315 | * Except if we have remaining filtered hits. They will | ||
316 | * supersede the last child | ||
317 | */ | ||
318 | next = rb_next(node); | ||
319 | if (!next && (callchain_param.mode != CHAIN_GRAPH_REL || !remaining)) | ||
320 | new_depth_mask &= ~(1 << (depth - 1)); | ||
321 | |||
322 | /* | ||
323 | * But we keep the older depth mask for the line seperator | ||
324 | * to keep the level link until we reach the last child | ||
325 | */ | ||
326 | ret += ipchain__fprintf_graph_line(fp, depth, depth_mask, | ||
327 | left_margin); | ||
328 | i = 0; | ||
329 | list_for_each_entry(chain, &child->val, list) { | ||
330 | if (chain->ip >= PERF_CONTEXT_MAX) | ||
331 | continue; | ||
332 | ret += ipchain__fprintf_graph(fp, chain, depth, | ||
333 | new_depth_mask, i++, | ||
334 | new_total, | ||
335 | cumul, | ||
336 | left_margin); | ||
337 | } | ||
338 | ret += __callchain__fprintf_graph(fp, child, new_total, | ||
339 | depth + 1, | ||
340 | new_depth_mask | (1 << depth), | ||
341 | left_margin); | ||
342 | node = next; | ||
343 | } | ||
344 | |||
345 | if (callchain_param.mode == CHAIN_GRAPH_REL && | ||
346 | remaining && remaining != new_total) { | ||
347 | |||
348 | if (!rem_sq_bracket) | ||
349 | return ret; | ||
350 | |||
351 | new_depth_mask &= ~(1 << (depth - 1)); | ||
352 | |||
353 | ret += ipchain__fprintf_graph(fp, &rem_hits, depth, | ||
354 | new_depth_mask, 0, new_total, | ||
355 | remaining, left_margin); | ||
356 | } | ||
357 | |||
358 | return ret; | ||
359 | } | ||
360 | |||
361 | static size_t callchain__fprintf_graph(FILE *fp, struct callchain_node *self, | ||
362 | u64 total_samples, int left_margin) | ||
363 | { | ||
364 | struct callchain_list *chain; | ||
365 | bool printed = false; | ||
366 | int i = 0; | ||
367 | int ret = 0; | ||
368 | |||
369 | list_for_each_entry(chain, &self->val, list) { | ||
370 | if (chain->ip >= PERF_CONTEXT_MAX) | ||
371 | continue; | ||
372 | |||
373 | if (!i++ && sort__first_dimension == SORT_SYM) | ||
374 | continue; | ||
375 | |||
376 | if (!printed) { | ||
377 | ret += callchain__fprintf_left_margin(fp, left_margin); | ||
378 | ret += fprintf(fp, "|\n"); | ||
379 | ret += callchain__fprintf_left_margin(fp, left_margin); | ||
380 | ret += fprintf(fp, "---"); | ||
381 | |||
382 | left_margin += 3; | ||
383 | printed = true; | ||
384 | } else | ||
385 | ret += callchain__fprintf_left_margin(fp, left_margin); | ||
386 | |||
387 | if (chain->sym) | ||
388 | ret += fprintf(fp, " %s\n", chain->sym->name); | ||
389 | else | ||
390 | ret += fprintf(fp, " %p\n", (void *)(long)chain->ip); | ||
391 | } | ||
392 | |||
393 | ret += __callchain__fprintf_graph(fp, self, total_samples, 1, 1, left_margin); | ||
394 | |||
395 | return ret; | ||
396 | } | ||
397 | |||
398 | static size_t callchain__fprintf_flat(FILE *fp, struct callchain_node *self, | ||
399 | u64 total_samples) | ||
400 | { | ||
401 | struct callchain_list *chain; | ||
402 | size_t ret = 0; | ||
403 | |||
404 | if (!self) | ||
405 | return 0; | ||
406 | |||
407 | ret += callchain__fprintf_flat(fp, self->parent, total_samples); | ||
408 | |||
409 | |||
410 | list_for_each_entry(chain, &self->val, list) { | ||
411 | if (chain->ip >= PERF_CONTEXT_MAX) | ||
412 | continue; | ||
413 | if (chain->sym) | ||
414 | ret += fprintf(fp, " %s\n", chain->sym->name); | ||
415 | else | ||
416 | ret += fprintf(fp, " %p\n", | ||
417 | (void *)(long)chain->ip); | ||
418 | } | ||
419 | |||
420 | return ret; | ||
421 | } | ||
422 | |||
423 | static size_t hist_entry_callchain__fprintf(FILE *fp, struct hist_entry *self, | ||
424 | u64 total_samples, int left_margin) | ||
425 | { | ||
426 | struct rb_node *rb_node; | ||
427 | struct callchain_node *chain; | ||
428 | size_t ret = 0; | ||
429 | |||
430 | rb_node = rb_first(&self->sorted_chain); | ||
431 | while (rb_node) { | ||
432 | double percent; | ||
433 | |||
434 | chain = rb_entry(rb_node, struct callchain_node, rb_node); | ||
435 | percent = chain->hit * 100.0 / total_samples; | ||
436 | switch (callchain_param.mode) { | ||
437 | case CHAIN_FLAT: | ||
438 | ret += percent_color_fprintf(fp, " %6.2f%%\n", | ||
439 | percent); | ||
440 | ret += callchain__fprintf_flat(fp, chain, total_samples); | ||
441 | break; | ||
442 | case CHAIN_GRAPH_ABS: /* Falldown */ | ||
443 | case CHAIN_GRAPH_REL: | ||
444 | ret += callchain__fprintf_graph(fp, chain, total_samples, | ||
445 | left_margin); | ||
446 | case CHAIN_NONE: | ||
447 | default: | ||
448 | break; | ||
449 | } | ||
450 | ret += fprintf(fp, "\n"); | ||
451 | rb_node = rb_next(rb_node); | ||
452 | } | ||
453 | |||
454 | return ret; | ||
455 | } | ||
456 | |||
457 | static size_t hist_entry__fprintf(FILE *fp, struct hist_entry *self, | ||
458 | struct perf_session *session) | ||
459 | { | ||
460 | struct sort_entry *se; | ||
461 | size_t ret; | ||
462 | |||
463 | if (symbol_conf.exclude_other && !self->parent) | ||
464 | return 0; | ||
465 | |||
466 | if (session->events_stats.total) | ||
467 | ret = percent_color_fprintf(fp, | ||
468 | symbol_conf.field_sep ? "%.2f" : " %6.2f%%", | ||
469 | (self->count * 100.0) / session->events_stats.total); | ||
470 | else | ||
471 | ret = fprintf(fp, symbol_conf.field_sep ? "%lld" : "%12lld ", self->count); | ||
472 | |||
473 | if (symbol_conf.show_nr_samples) { | ||
474 | if (symbol_conf.field_sep) | ||
475 | fprintf(fp, "%c%lld", *symbol_conf.field_sep, self->count); | ||
476 | else | ||
477 | fprintf(fp, "%11lld", self->count); | ||
478 | } | ||
479 | |||
480 | list_for_each_entry(se, &hist_entry__sort_list, list) { | ||
481 | if (se->elide) | ||
482 | continue; | ||
483 | |||
484 | fprintf(fp, "%s", symbol_conf.field_sep ?: " "); | ||
485 | ret += se->print(fp, self, se->width ? *se->width : 0); | ||
486 | } | ||
487 | |||
488 | ret += fprintf(fp, "\n"); | ||
489 | |||
490 | if (symbol_conf.use_callchain) { | ||
491 | int left_margin = 0; | ||
492 | |||
493 | if (sort__first_dimension == SORT_COMM) { | ||
494 | se = list_first_entry(&hist_entry__sort_list, typeof(*se), | ||
495 | list); | ||
496 | left_margin = se->width ? *se->width : 0; | ||
497 | left_margin -= thread__comm_len(self->thread); | ||
498 | } | ||
499 | |||
500 | hist_entry_callchain__fprintf(fp, self, session->events_stats.total, | ||
501 | left_margin); | ||
502 | } | ||
503 | |||
504 | return ret; | ||
505 | } | ||
506 | |||
507 | size_t perf_session__fprintf_hists(struct perf_session *self, FILE *fp) | ||
508 | { | ||
509 | struct hist_entry *pos; | ||
510 | struct sort_entry *se; | ||
511 | struct rb_node *nd; | ||
512 | size_t ret = 0; | ||
513 | unsigned int width; | ||
514 | char *col_width = symbol_conf.col_width_list_str; | ||
515 | |||
516 | init_rem_hits(); | ||
517 | |||
518 | fprintf(fp, "# Samples: %ld\n", self->events_stats.total); | ||
519 | fprintf(fp, "#\n"); | ||
520 | |||
521 | fprintf(fp, "# Overhead"); | ||
522 | if (symbol_conf.show_nr_samples) { | ||
523 | if (symbol_conf.field_sep) | ||
524 | fprintf(fp, "%cSamples", *symbol_conf.field_sep); | ||
525 | else | ||
526 | fputs(" Samples ", fp); | ||
527 | } | ||
528 | list_for_each_entry(se, &hist_entry__sort_list, list) { | ||
529 | if (se->elide) | ||
530 | continue; | ||
531 | if (symbol_conf.field_sep) { | ||
532 | fprintf(fp, "%c%s", *symbol_conf.field_sep, se->header); | ||
533 | continue; | ||
534 | } | ||
535 | width = strlen(se->header); | ||
536 | if (se->width) { | ||
537 | if (symbol_conf.col_width_list_str) { | ||
538 | if (col_width) { | ||
539 | *se->width = atoi(col_width); | ||
540 | col_width = strchr(col_width, ','); | ||
541 | if (col_width) | ||
542 | ++col_width; | ||
543 | } | ||
544 | } | ||
545 | width = *se->width = max(*se->width, width); | ||
546 | } | ||
547 | fprintf(fp, " %*s", width, se->header); | ||
548 | } | ||
549 | fprintf(fp, "\n"); | ||
550 | |||
551 | if (symbol_conf.field_sep) | ||
552 | goto print_entries; | ||
553 | |||
554 | fprintf(fp, "# ........"); | ||
555 | if (symbol_conf.show_nr_samples) | ||
556 | fprintf(fp, " .........."); | ||
557 | list_for_each_entry(se, &hist_entry__sort_list, list) { | ||
558 | unsigned int i; | ||
559 | |||
560 | if (se->elide) | ||
561 | continue; | ||
562 | |||
563 | fprintf(fp, " "); | ||
564 | if (se->width) | ||
565 | width = *se->width; | ||
566 | else | ||
567 | width = strlen(se->header); | ||
568 | for (i = 0; i < width; i++) | ||
569 | fprintf(fp, "."); | ||
570 | } | ||
571 | fprintf(fp, "\n"); | ||
572 | |||
573 | fprintf(fp, "#\n"); | ||
574 | |||
575 | print_entries: | ||
576 | for (nd = rb_first(&self->hists); nd; nd = rb_next(nd)) { | ||
577 | pos = rb_entry(nd, struct hist_entry, rb_node); | ||
578 | ret += hist_entry__fprintf(fp, pos, self); | ||
579 | } | ||
580 | |||
581 | if (sort_order == default_sort_order && | ||
582 | parent_pattern == default_parent_pattern) { | ||
583 | fprintf(fp, "#\n"); | ||
584 | fprintf(fp, "# (For a higher level overview, try: perf report --sort comm,dso)\n"); | ||
585 | fprintf(fp, "#\n"); | ||
586 | } | ||
587 | fprintf(fp, "\n"); | ||
588 | |||
589 | free(rem_sq_bracket); | ||
590 | |||
591 | return ret; | ||
592 | } | ||