diff options
Diffstat (limited to 'tools/perf/ui/stdio/hist.c')
| -rw-r--r-- | tools/perf/ui/stdio/hist.c | 498 |
1 files changed, 498 insertions, 0 deletions
diff --git a/tools/perf/ui/stdio/hist.c b/tools/perf/ui/stdio/hist.c new file mode 100644 index 000000000000..882461a42830 --- /dev/null +++ b/tools/perf/ui/stdio/hist.c | |||
| @@ -0,0 +1,498 @@ | |||
| 1 | #include <stdio.h> | ||
| 2 | |||
| 3 | #include "../../util/util.h" | ||
| 4 | #include "../../util/hist.h" | ||
| 5 | #include "../../util/sort.h" | ||
| 6 | |||
| 7 | |||
| 8 | static size_t callchain__fprintf_left_margin(FILE *fp, int left_margin) | ||
| 9 | { | ||
| 10 | int i; | ||
| 11 | int ret = fprintf(fp, " "); | ||
| 12 | |||
| 13 | for (i = 0; i < left_margin; i++) | ||
| 14 | ret += fprintf(fp, " "); | ||
| 15 | |||
| 16 | return ret; | ||
| 17 | } | ||
| 18 | |||
| 19 | static size_t ipchain__fprintf_graph_line(FILE *fp, int depth, int depth_mask, | ||
| 20 | int left_margin) | ||
| 21 | { | ||
| 22 | int i; | ||
| 23 | size_t ret = callchain__fprintf_left_margin(fp, left_margin); | ||
| 24 | |||
| 25 | for (i = 0; i < depth; i++) | ||
| 26 | if (depth_mask & (1 << i)) | ||
| 27 | ret += fprintf(fp, "| "); | ||
| 28 | else | ||
| 29 | ret += fprintf(fp, " "); | ||
| 30 | |||
| 31 | ret += fprintf(fp, "\n"); | ||
| 32 | |||
| 33 | return ret; | ||
| 34 | } | ||
| 35 | |||
| 36 | static size_t ipchain__fprintf_graph(FILE *fp, struct callchain_list *chain, | ||
| 37 | int depth, int depth_mask, int period, | ||
| 38 | u64 total_samples, u64 hits, | ||
| 39 | int left_margin) | ||
| 40 | { | ||
| 41 | int i; | ||
| 42 | size_t ret = 0; | ||
| 43 | |||
| 44 | ret += callchain__fprintf_left_margin(fp, left_margin); | ||
| 45 | for (i = 0; i < depth; i++) { | ||
| 46 | if (depth_mask & (1 << i)) | ||
| 47 | ret += fprintf(fp, "|"); | ||
| 48 | else | ||
| 49 | ret += fprintf(fp, " "); | ||
| 50 | if (!period && i == depth - 1) { | ||
| 51 | double percent; | ||
| 52 | |||
| 53 | percent = hits * 100.0 / total_samples; | ||
| 54 | ret += percent_color_fprintf(fp, "--%2.2f%%-- ", percent); | ||
| 55 | } else | ||
| 56 | ret += fprintf(fp, "%s", " "); | ||
| 57 | } | ||
| 58 | if (chain->ms.sym) | ||
| 59 | ret += fprintf(fp, "%s\n", chain->ms.sym->name); | ||
| 60 | else | ||
| 61 | ret += fprintf(fp, "0x%0" PRIx64 "\n", chain->ip); | ||
| 62 | |||
| 63 | return ret; | ||
| 64 | } | ||
| 65 | |||
| 66 | static struct symbol *rem_sq_bracket; | ||
| 67 | static struct callchain_list rem_hits; | ||
| 68 | |||
| 69 | static void init_rem_hits(void) | ||
| 70 | { | ||
| 71 | rem_sq_bracket = malloc(sizeof(*rem_sq_bracket) + 6); | ||
| 72 | if (!rem_sq_bracket) { | ||
| 73 | fprintf(stderr, "Not enough memory to display remaining hits\n"); | ||
| 74 | return; | ||
| 75 | } | ||
| 76 | |||
| 77 | strcpy(rem_sq_bracket->name, "[...]"); | ||
| 78 | rem_hits.ms.sym = rem_sq_bracket; | ||
| 79 | } | ||
| 80 | |||
| 81 | static size_t __callchain__fprintf_graph(FILE *fp, struct rb_root *root, | ||
| 82 | u64 total_samples, int depth, | ||
| 83 | int depth_mask, int left_margin) | ||
| 84 | { | ||
| 85 | struct rb_node *node, *next; | ||
| 86 | struct callchain_node *child; | ||
| 87 | struct callchain_list *chain; | ||
| 88 | int new_depth_mask = depth_mask; | ||
| 89 | u64 remaining; | ||
| 90 | size_t ret = 0; | ||
| 91 | int i; | ||
| 92 | uint entries_printed = 0; | ||
| 93 | |||
| 94 | remaining = total_samples; | ||
| 95 | |||
| 96 | node = rb_first(root); | ||
| 97 | while (node) { | ||
| 98 | u64 new_total; | ||
| 99 | u64 cumul; | ||
| 100 | |||
| 101 | child = rb_entry(node, struct callchain_node, rb_node); | ||
| 102 | cumul = callchain_cumul_hits(child); | ||
| 103 | remaining -= cumul; | ||
| 104 | |||
| 105 | /* | ||
| 106 | * The depth mask manages the output of pipes that show | ||
| 107 | * the depth. We don't want to keep the pipes of the current | ||
| 108 | * level for the last child of this depth. | ||
| 109 | * Except if we have remaining filtered hits. They will | ||
| 110 | * supersede the last child | ||
| 111 | */ | ||
| 112 | next = rb_next(node); | ||
| 113 | if (!next && (callchain_param.mode != CHAIN_GRAPH_REL || !remaining)) | ||
| 114 | new_depth_mask &= ~(1 << (depth - 1)); | ||
| 115 | |||
| 116 | /* | ||
| 117 | * But we keep the older depth mask for the line separator | ||
| 118 | * to keep the level link until we reach the last child | ||
| 119 | */ | ||
| 120 | ret += ipchain__fprintf_graph_line(fp, depth, depth_mask, | ||
| 121 | left_margin); | ||
| 122 | i = 0; | ||
| 123 | list_for_each_entry(chain, &child->val, list) { | ||
| 124 | ret += ipchain__fprintf_graph(fp, chain, depth, | ||
| 125 | new_depth_mask, i++, | ||
| 126 | total_samples, | ||
| 127 | cumul, | ||
| 128 | left_margin); | ||
| 129 | } | ||
| 130 | |||
| 131 | if (callchain_param.mode == CHAIN_GRAPH_REL) | ||
| 132 | new_total = child->children_hit; | ||
| 133 | else | ||
| 134 | new_total = total_samples; | ||
| 135 | |||
| 136 | ret += __callchain__fprintf_graph(fp, &child->rb_root, new_total, | ||
| 137 | depth + 1, | ||
| 138 | new_depth_mask | (1 << depth), | ||
| 139 | left_margin); | ||
| 140 | node = next; | ||
| 141 | if (++entries_printed == callchain_param.print_limit) | ||
| 142 | break; | ||
| 143 | } | ||
| 144 | |||
| 145 | if (callchain_param.mode == CHAIN_GRAPH_REL && | ||
| 146 | remaining && remaining != total_samples) { | ||
| 147 | |||
| 148 | if (!rem_sq_bracket) | ||
| 149 | return ret; | ||
| 150 | |||
| 151 | new_depth_mask &= ~(1 << (depth - 1)); | ||
| 152 | ret += ipchain__fprintf_graph(fp, &rem_hits, depth, | ||
| 153 | new_depth_mask, 0, total_samples, | ||
| 154 | remaining, left_margin); | ||
| 155 | } | ||
| 156 | |||
| 157 | return ret; | ||
| 158 | } | ||
| 159 | |||
| 160 | static size_t callchain__fprintf_graph(FILE *fp, struct rb_root *root, | ||
| 161 | u64 total_samples, int left_margin) | ||
| 162 | { | ||
| 163 | struct callchain_node *cnode; | ||
| 164 | struct callchain_list *chain; | ||
| 165 | u32 entries_printed = 0; | ||
| 166 | bool printed = false; | ||
| 167 | struct rb_node *node; | ||
| 168 | int i = 0; | ||
| 169 | int ret = 0; | ||
| 170 | |||
| 171 | /* | ||
| 172 | * If have one single callchain root, don't bother printing | ||
| 173 | * its percentage (100 % in fractal mode and the same percentage | ||
| 174 | * than the hist in graph mode). This also avoid one level of column. | ||
| 175 | */ | ||
| 176 | node = rb_first(root); | ||
| 177 | if (node && !rb_next(node)) { | ||
| 178 | cnode = rb_entry(node, struct callchain_node, rb_node); | ||
| 179 | list_for_each_entry(chain, &cnode->val, list) { | ||
| 180 | /* | ||
| 181 | * If we sort by symbol, the first entry is the same than | ||
| 182 | * the symbol. No need to print it otherwise it appears as | ||
| 183 | * displayed twice. | ||
| 184 | */ | ||
| 185 | if (!i++ && sort__first_dimension == SORT_SYM) | ||
| 186 | continue; | ||
| 187 | if (!printed) { | ||
| 188 | ret += callchain__fprintf_left_margin(fp, left_margin); | ||
| 189 | ret += fprintf(fp, "|\n"); | ||
| 190 | ret += callchain__fprintf_left_margin(fp, left_margin); | ||
| 191 | ret += fprintf(fp, "---"); | ||
| 192 | left_margin += 3; | ||
| 193 | printed = true; | ||
| 194 | } else | ||
| 195 | ret += callchain__fprintf_left_margin(fp, left_margin); | ||
| 196 | |||
| 197 | if (chain->ms.sym) | ||
| 198 | ret += fprintf(fp, " %s\n", chain->ms.sym->name); | ||
| 199 | else | ||
| 200 | ret += fprintf(fp, " %p\n", (void *)(long)chain->ip); | ||
| 201 | |||
| 202 | if (++entries_printed == callchain_param.print_limit) | ||
| 203 | break; | ||
| 204 | } | ||
| 205 | root = &cnode->rb_root; | ||
| 206 | } | ||
| 207 | |||
| 208 | ret += __callchain__fprintf_graph(fp, root, total_samples, | ||
| 209 | 1, 1, left_margin); | ||
| 210 | ret += fprintf(fp, "\n"); | ||
| 211 | |||
| 212 | return ret; | ||
| 213 | } | ||
| 214 | |||
| 215 | static size_t __callchain__fprintf_flat(FILE *fp, | ||
| 216 | struct callchain_node *self, | ||
| 217 | u64 total_samples) | ||
| 218 | { | ||
| 219 | struct callchain_list *chain; | ||
| 220 | size_t ret = 0; | ||
| 221 | |||
| 222 | if (!self) | ||
| 223 | return 0; | ||
| 224 | |||
| 225 | ret += __callchain__fprintf_flat(fp, self->parent, total_samples); | ||
| 226 | |||
| 227 | |||
| 228 | list_for_each_entry(chain, &self->val, list) { | ||
| 229 | if (chain->ip >= PERF_CONTEXT_MAX) | ||
| 230 | continue; | ||
| 231 | if (chain->ms.sym) | ||
| 232 | ret += fprintf(fp, " %s\n", chain->ms.sym->name); | ||
| 233 | else | ||
| 234 | ret += fprintf(fp, " %p\n", | ||
| 235 | (void *)(long)chain->ip); | ||
| 236 | } | ||
| 237 | |||
| 238 | return ret; | ||
| 239 | } | ||
| 240 | |||
| 241 | static size_t callchain__fprintf_flat(FILE *fp, struct rb_root *self, | ||
| 242 | u64 total_samples) | ||
| 243 | { | ||
| 244 | size_t ret = 0; | ||
| 245 | u32 entries_printed = 0; | ||
| 246 | struct rb_node *rb_node; | ||
| 247 | struct callchain_node *chain; | ||
| 248 | |||
| 249 | rb_node = rb_first(self); | ||
| 250 | while (rb_node) { | ||
| 251 | double percent; | ||
| 252 | |||
| 253 | chain = rb_entry(rb_node, struct callchain_node, rb_node); | ||
| 254 | percent = chain->hit * 100.0 / total_samples; | ||
| 255 | |||
| 256 | ret = percent_color_fprintf(fp, " %6.2f%%\n", percent); | ||
| 257 | ret += __callchain__fprintf_flat(fp, chain, total_samples); | ||
| 258 | ret += fprintf(fp, "\n"); | ||
| 259 | if (++entries_printed == callchain_param.print_limit) | ||
| 260 | break; | ||
| 261 | |||
| 262 | rb_node = rb_next(rb_node); | ||
| 263 | } | ||
| 264 | |||
| 265 | return ret; | ||
| 266 | } | ||
| 267 | |||
| 268 | static size_t hist_entry_callchain__fprintf(struct hist_entry *he, | ||
| 269 | u64 total_samples, int left_margin, | ||
| 270 | FILE *fp) | ||
| 271 | { | ||
| 272 | switch (callchain_param.mode) { | ||
| 273 | case CHAIN_GRAPH_REL: | ||
| 274 | return callchain__fprintf_graph(fp, &he->sorted_chain, he->period, | ||
| 275 | left_margin); | ||
| 276 | break; | ||
| 277 | case CHAIN_GRAPH_ABS: | ||
| 278 | return callchain__fprintf_graph(fp, &he->sorted_chain, total_samples, | ||
| 279 | left_margin); | ||
| 280 | break; | ||
| 281 | case CHAIN_FLAT: | ||
| 282 | return callchain__fprintf_flat(fp, &he->sorted_chain, total_samples); | ||
| 283 | break; | ||
| 284 | case CHAIN_NONE: | ||
| 285 | break; | ||
| 286 | default: | ||
| 287 | pr_err("Bad callchain mode\n"); | ||
| 288 | } | ||
| 289 | |||
| 290 | return 0; | ||
| 291 | } | ||
| 292 | |||
| 293 | static size_t hist_entry__callchain_fprintf(struct hist_entry *he, | ||
| 294 | struct hists *hists, | ||
| 295 | u64 total_period, FILE *fp) | ||
| 296 | { | ||
| 297 | int left_margin = 0; | ||
| 298 | |||
| 299 | if (sort__first_dimension == SORT_COMM) { | ||
| 300 | struct sort_entry *se = list_first_entry(&hist_entry__sort_list, | ||
| 301 | typeof(*se), list); | ||
| 302 | left_margin = hists__col_len(hists, se->se_width_idx); | ||
| 303 | left_margin -= thread__comm_len(he->thread); | ||
| 304 | } | ||
| 305 | |||
| 306 | return hist_entry_callchain__fprintf(he, total_period, left_margin, fp); | ||
| 307 | } | ||
| 308 | |||
| 309 | static int hist_entry__fprintf(struct hist_entry *he, size_t size, | ||
| 310 | struct hists *hists, struct hists *pair_hists, | ||
| 311 | long displacement, u64 total_period, FILE *fp) | ||
| 312 | { | ||
| 313 | char bf[512]; | ||
| 314 | int ret; | ||
| 315 | struct perf_hpp hpp = { | ||
| 316 | .buf = bf, | ||
| 317 | .size = size, | ||
| 318 | .total_period = total_period, | ||
| 319 | .displacement = displacement, | ||
| 320 | .ptr = pair_hists, | ||
| 321 | }; | ||
| 322 | bool color = !symbol_conf.field_sep; | ||
| 323 | |||
| 324 | if (size == 0 || size > sizeof(bf)) | ||
| 325 | size = hpp.size = sizeof(bf); | ||
| 326 | |||
| 327 | ret = hist_entry__period_snprintf(&hpp, he, color); | ||
| 328 | hist_entry__sort_snprintf(he, bf + ret, size - ret, hists); | ||
| 329 | |||
| 330 | ret = fprintf(fp, "%s\n", bf); | ||
| 331 | |||
| 332 | if (symbol_conf.use_callchain) | ||
| 333 | ret += hist_entry__callchain_fprintf(he, hists, | ||
| 334 | total_period, fp); | ||
| 335 | |||
| 336 | return ret; | ||
| 337 | } | ||
| 338 | |||
| 339 | size_t hists__fprintf(struct hists *hists, struct hists *pair, | ||
| 340 | bool show_displacement, bool show_header, int max_rows, | ||
| 341 | int max_cols, FILE *fp) | ||
| 342 | { | ||
| 343 | struct sort_entry *se; | ||
| 344 | struct rb_node *nd; | ||
| 345 | size_t ret = 0; | ||
| 346 | u64 total_period; | ||
| 347 | unsigned long position = 1; | ||
| 348 | long displacement = 0; | ||
| 349 | unsigned int width; | ||
| 350 | const char *sep = symbol_conf.field_sep; | ||
| 351 | const char *col_width = symbol_conf.col_width_list_str; | ||
| 352 | int idx, nr_rows = 0; | ||
| 353 | char bf[64]; | ||
| 354 | struct perf_hpp dummy_hpp = { | ||
| 355 | .buf = bf, | ||
| 356 | .size = sizeof(bf), | ||
| 357 | .ptr = pair, | ||
| 358 | }; | ||
| 359 | |||
| 360 | init_rem_hits(); | ||
| 361 | |||
| 362 | if (!show_header) | ||
| 363 | goto print_entries; | ||
| 364 | |||
| 365 | fprintf(fp, "# "); | ||
| 366 | for (idx = 0; idx < PERF_HPP__MAX_INDEX; idx++) { | ||
| 367 | if (!perf_hpp__format[idx].cond) | ||
| 368 | continue; | ||
| 369 | |||
| 370 | if (idx) | ||
| 371 | fprintf(fp, "%s", sep ?: " "); | ||
| 372 | |||
| 373 | perf_hpp__format[idx].header(&dummy_hpp); | ||
| 374 | fprintf(fp, "%s", bf); | ||
| 375 | } | ||
| 376 | |||
| 377 | list_for_each_entry(se, &hist_entry__sort_list, list) { | ||
| 378 | if (se->elide) | ||
| 379 | continue; | ||
| 380 | if (sep) { | ||
| 381 | fprintf(fp, "%c%s", *sep, se->se_header); | ||
| 382 | continue; | ||
| 383 | } | ||
| 384 | width = strlen(se->se_header); | ||
| 385 | if (symbol_conf.col_width_list_str) { | ||
| 386 | if (col_width) { | ||
| 387 | hists__set_col_len(hists, se->se_width_idx, | ||
| 388 | atoi(col_width)); | ||
| 389 | col_width = strchr(col_width, ','); | ||
| 390 | if (col_width) | ||
| 391 | ++col_width; | ||
| 392 | } | ||
| 393 | } | ||
| 394 | if (!hists__new_col_len(hists, se->se_width_idx, width)) | ||
| 395 | width = hists__col_len(hists, se->se_width_idx); | ||
| 396 | fprintf(fp, " %*s", width, se->se_header); | ||
| 397 | } | ||
| 398 | |||
| 399 | fprintf(fp, "\n"); | ||
| 400 | if (max_rows && ++nr_rows >= max_rows) | ||
| 401 | goto out; | ||
| 402 | |||
| 403 | if (sep) | ||
| 404 | goto print_entries; | ||
| 405 | |||
| 406 | fprintf(fp, "# "); | ||
| 407 | for (idx = 0; idx < PERF_HPP__MAX_INDEX; idx++) { | ||
| 408 | unsigned int i; | ||
| 409 | |||
| 410 | if (!perf_hpp__format[idx].cond) | ||
| 411 | continue; | ||
| 412 | |||
| 413 | if (idx) | ||
| 414 | fprintf(fp, "%s", sep ?: " "); | ||
| 415 | |||
| 416 | width = perf_hpp__format[idx].width(&dummy_hpp); | ||
| 417 | for (i = 0; i < width; i++) | ||
| 418 | fprintf(fp, "."); | ||
| 419 | } | ||
| 420 | |||
| 421 | list_for_each_entry(se, &hist_entry__sort_list, list) { | ||
| 422 | unsigned int i; | ||
| 423 | |||
| 424 | if (se->elide) | ||
| 425 | continue; | ||
| 426 | |||
| 427 | fprintf(fp, " "); | ||
| 428 | width = hists__col_len(hists, se->se_width_idx); | ||
| 429 | if (width == 0) | ||
| 430 | width = strlen(se->se_header); | ||
| 431 | for (i = 0; i < width; i++) | ||
| 432 | fprintf(fp, "."); | ||
| 433 | } | ||
| 434 | |||
| 435 | fprintf(fp, "\n"); | ||
| 436 | if (max_rows && ++nr_rows >= max_rows) | ||
| 437 | goto out; | ||
| 438 | |||
| 439 | fprintf(fp, "#\n"); | ||
| 440 | if (max_rows && ++nr_rows >= max_rows) | ||
| 441 | goto out; | ||
| 442 | |||
| 443 | print_entries: | ||
| 444 | total_period = hists->stats.total_period; | ||
| 445 | |||
| 446 | for (nd = rb_first(&hists->entries); nd; nd = rb_next(nd)) { | ||
| 447 | struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); | ||
| 448 | |||
| 449 | if (h->filtered) | ||
| 450 | continue; | ||
| 451 | |||
| 452 | if (show_displacement) { | ||
| 453 | if (h->pair != NULL) | ||
| 454 | displacement = ((long)h->pair->position - | ||
| 455 | (long)position); | ||
| 456 | else | ||
| 457 | displacement = 0; | ||
| 458 | ++position; | ||
| 459 | } | ||
| 460 | ret += hist_entry__fprintf(h, max_cols, hists, pair, displacement, | ||
| 461 | total_period, fp); | ||
| 462 | |||
| 463 | if (max_rows && ++nr_rows >= max_rows) | ||
| 464 | goto out; | ||
| 465 | |||
| 466 | if (h->ms.map == NULL && verbose > 1) { | ||
| 467 | __map_groups__fprintf_maps(&h->thread->mg, | ||
| 468 | MAP__FUNCTION, verbose, fp); | ||
| 469 | fprintf(fp, "%.10s end\n", graph_dotted_line); | ||
| 470 | } | ||
| 471 | } | ||
| 472 | out: | ||
| 473 | free(rem_sq_bracket); | ||
| 474 | |||
| 475 | return ret; | ||
| 476 | } | ||
| 477 | |||
| 478 | size_t hists__fprintf_nr_events(struct hists *hists, FILE *fp) | ||
| 479 | { | ||
| 480 | int i; | ||
| 481 | size_t ret = 0; | ||
| 482 | |||
| 483 | for (i = 0; i < PERF_RECORD_HEADER_MAX; ++i) { | ||
| 484 | const char *name; | ||
| 485 | |||
| 486 | if (hists->stats.nr_events[i] == 0) | ||
| 487 | continue; | ||
| 488 | |||
| 489 | name = perf_event__name(i); | ||
| 490 | if (!strcmp(name, "UNKNOWN")) | ||
| 491 | continue; | ||
| 492 | |||
| 493 | ret += fprintf(fp, "%16s events: %10d\n", name, | ||
| 494 | hists->stats.nr_events[i]); | ||
| 495 | } | ||
| 496 | |||
| 497 | return ret; | ||
| 498 | } | ||
