diff options
| -rw-r--r-- | tools/perf/builtin-report.c | 397 | ||||
| -rw-r--r-- | tools/perf/util/hist.c | 384 | ||||
| -rw-r--r-- | tools/perf/util/hist.h | 1 |
3 files changed, 385 insertions, 397 deletions
diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c index 26f4de6d9a51..24d20e7d125a 100644 --- a/tools/perf/builtin-report.c +++ b/tools/perf/builtin-report.c | |||
| @@ -43,316 +43,6 @@ static char *pretty_printing_style = default_pretty_printing_style; | |||
| 43 | 43 | ||
| 44 | static char callchain_default_opt[] = "fractal,0.5"; | 44 | static char callchain_default_opt[] = "fractal,0.5"; |
| 45 | 45 | ||
| 46 | static size_t | ||
| 47 | callchain__fprintf_left_margin(FILE *fp, int left_margin) | ||
| 48 | { | ||
| 49 | int i; | ||
| 50 | int ret; | ||
| 51 | |||
| 52 | ret = fprintf(fp, " "); | ||
| 53 | |||
| 54 | for (i = 0; i < left_margin; i++) | ||
| 55 | ret += fprintf(fp, " "); | ||
| 56 | |||
| 57 | return ret; | ||
| 58 | } | ||
| 59 | |||
| 60 | static size_t ipchain__fprintf_graph_line(FILE *fp, int depth, int depth_mask, | ||
| 61 | int left_margin) | ||
| 62 | { | ||
| 63 | int i; | ||
| 64 | size_t ret = 0; | ||
| 65 | |||
| 66 | ret += callchain__fprintf_left_margin(fp, left_margin); | ||
| 67 | |||
| 68 | for (i = 0; i < depth; i++) | ||
| 69 | if (depth_mask & (1 << i)) | ||
| 70 | ret += fprintf(fp, "| "); | ||
| 71 | else | ||
| 72 | ret += fprintf(fp, " "); | ||
| 73 | |||
| 74 | ret += fprintf(fp, "\n"); | ||
| 75 | |||
| 76 | return ret; | ||
| 77 | } | ||
| 78 | static size_t | ||
| 79 | ipchain__fprintf_graph(FILE *fp, struct callchain_list *chain, int depth, | ||
| 80 | int depth_mask, int count, u64 total_samples, | ||
| 81 | int hits, int left_margin) | ||
| 82 | { | ||
| 83 | int i; | ||
| 84 | size_t ret = 0; | ||
| 85 | |||
| 86 | ret += callchain__fprintf_left_margin(fp, left_margin); | ||
| 87 | for (i = 0; i < depth; i++) { | ||
| 88 | if (depth_mask & (1 << i)) | ||
| 89 | ret += fprintf(fp, "|"); | ||
| 90 | else | ||
| 91 | ret += fprintf(fp, " "); | ||
| 92 | if (!count && i == depth - 1) { | ||
| 93 | double percent; | ||
| 94 | |||
| 95 | percent = hits * 100.0 / total_samples; | ||
| 96 | ret += percent_color_fprintf(fp, "--%2.2f%%-- ", percent); | ||
| 97 | } else | ||
| 98 | ret += fprintf(fp, "%s", " "); | ||
| 99 | } | ||
| 100 | if (chain->sym) | ||
| 101 | ret += fprintf(fp, "%s\n", chain->sym->name); | ||
| 102 | else | ||
| 103 | ret += fprintf(fp, "%p\n", (void *)(long)chain->ip); | ||
| 104 | |||
| 105 | return ret; | ||
| 106 | } | ||
| 107 | |||
| 108 | static struct symbol *rem_sq_bracket; | ||
| 109 | static struct callchain_list rem_hits; | ||
| 110 | |||
| 111 | static void init_rem_hits(void) | ||
| 112 | { | ||
| 113 | rem_sq_bracket = malloc(sizeof(*rem_sq_bracket) + 6); | ||
| 114 | if (!rem_sq_bracket) { | ||
| 115 | fprintf(stderr, "Not enough memory to display remaining hits\n"); | ||
| 116 | return; | ||
| 117 | } | ||
| 118 | |||
| 119 | strcpy(rem_sq_bracket->name, "[...]"); | ||
| 120 | rem_hits.sym = rem_sq_bracket; | ||
| 121 | } | ||
| 122 | |||
| 123 | static size_t | ||
| 124 | __callchain__fprintf_graph(FILE *fp, struct callchain_node *self, | ||
| 125 | u64 total_samples, int depth, int depth_mask, | ||
| 126 | int left_margin) | ||
| 127 | { | ||
| 128 | struct rb_node *node, *next; | ||
| 129 | struct callchain_node *child; | ||
| 130 | struct callchain_list *chain; | ||
| 131 | int new_depth_mask = depth_mask; | ||
| 132 | u64 new_total; | ||
| 133 | u64 remaining; | ||
| 134 | size_t ret = 0; | ||
| 135 | int i; | ||
| 136 | |||
| 137 | if (callchain_param.mode == CHAIN_GRAPH_REL) | ||
| 138 | new_total = self->children_hit; | ||
| 139 | else | ||
| 140 | new_total = total_samples; | ||
| 141 | |||
| 142 | remaining = new_total; | ||
| 143 | |||
| 144 | node = rb_first(&self->rb_root); | ||
| 145 | while (node) { | ||
| 146 | u64 cumul; | ||
| 147 | |||
| 148 | child = rb_entry(node, struct callchain_node, rb_node); | ||
| 149 | cumul = cumul_hits(child); | ||
| 150 | remaining -= cumul; | ||
| 151 | |||
| 152 | /* | ||
| 153 | * The depth mask manages the output of pipes that show | ||
| 154 | * the depth. We don't want to keep the pipes of the current | ||
| 155 | * level for the last child of this depth. | ||
| 156 | * Except if we have remaining filtered hits. They will | ||
| 157 | * supersede the last child | ||
| 158 | */ | ||
| 159 | next = rb_next(node); | ||
| 160 | if (!next && (callchain_param.mode != CHAIN_GRAPH_REL || !remaining)) | ||
| 161 | new_depth_mask &= ~(1 << (depth - 1)); | ||
| 162 | |||
| 163 | /* | ||
| 164 | * But we keep the older depth mask for the line seperator | ||
| 165 | * to keep the level link until we reach the last child | ||
| 166 | */ | ||
| 167 | ret += ipchain__fprintf_graph_line(fp, depth, depth_mask, | ||
| 168 | left_margin); | ||
| 169 | i = 0; | ||
| 170 | list_for_each_entry(chain, &child->val, list) { | ||
| 171 | if (chain->ip >= PERF_CONTEXT_MAX) | ||
| 172 | continue; | ||
| 173 | ret += ipchain__fprintf_graph(fp, chain, depth, | ||
| 174 | new_depth_mask, i++, | ||
| 175 | new_total, | ||
| 176 | cumul, | ||
| 177 | left_margin); | ||
| 178 | } | ||
| 179 | ret += __callchain__fprintf_graph(fp, child, new_total, | ||
| 180 | depth + 1, | ||
| 181 | new_depth_mask | (1 << depth), | ||
| 182 | left_margin); | ||
| 183 | node = next; | ||
| 184 | } | ||
| 185 | |||
| 186 | if (callchain_param.mode == CHAIN_GRAPH_REL && | ||
| 187 | remaining && remaining != new_total) { | ||
| 188 | |||
| 189 | if (!rem_sq_bracket) | ||
| 190 | return ret; | ||
| 191 | |||
| 192 | new_depth_mask &= ~(1 << (depth - 1)); | ||
| 193 | |||
| 194 | ret += ipchain__fprintf_graph(fp, &rem_hits, depth, | ||
| 195 | new_depth_mask, 0, new_total, | ||
| 196 | remaining, left_margin); | ||
| 197 | } | ||
| 198 | |||
| 199 | return ret; | ||
| 200 | } | ||
| 201 | |||
| 202 | |||
| 203 | static size_t | ||
| 204 | callchain__fprintf_graph(FILE *fp, struct callchain_node *self, | ||
| 205 | u64 total_samples, int left_margin) | ||
| 206 | { | ||
| 207 | struct callchain_list *chain; | ||
| 208 | bool printed = false; | ||
| 209 | int i = 0; | ||
| 210 | int ret = 0; | ||
| 211 | |||
| 212 | list_for_each_entry(chain, &self->val, list) { | ||
| 213 | if (chain->ip >= PERF_CONTEXT_MAX) | ||
| 214 | continue; | ||
| 215 | |||
| 216 | if (!i++ && sort__first_dimension == SORT_SYM) | ||
| 217 | continue; | ||
| 218 | |||
| 219 | if (!printed) { | ||
| 220 | ret += callchain__fprintf_left_margin(fp, left_margin); | ||
| 221 | ret += fprintf(fp, "|\n"); | ||
| 222 | ret += callchain__fprintf_left_margin(fp, left_margin); | ||
| 223 | ret += fprintf(fp, "---"); | ||
| 224 | |||
| 225 | left_margin += 3; | ||
| 226 | printed = true; | ||
| 227 | } else | ||
| 228 | ret += callchain__fprintf_left_margin(fp, left_margin); | ||
| 229 | |||
| 230 | if (chain->sym) | ||
| 231 | ret += fprintf(fp, " %s\n", chain->sym->name); | ||
| 232 | else | ||
| 233 | ret += fprintf(fp, " %p\n", (void *)(long)chain->ip); | ||
| 234 | } | ||
| 235 | |||
| 236 | ret += __callchain__fprintf_graph(fp, self, total_samples, 1, 1, left_margin); | ||
| 237 | |||
| 238 | return ret; | ||
| 239 | } | ||
| 240 | |||
| 241 | static size_t | ||
| 242 | callchain__fprintf_flat(FILE *fp, struct callchain_node *self, | ||
| 243 | u64 total_samples) | ||
| 244 | { | ||
| 245 | struct callchain_list *chain; | ||
| 246 | size_t ret = 0; | ||
| 247 | |||
| 248 | if (!self) | ||
| 249 | return 0; | ||
| 250 | |||
| 251 | ret += callchain__fprintf_flat(fp, self->parent, total_samples); | ||
| 252 | |||
| 253 | |||
| 254 | list_for_each_entry(chain, &self->val, list) { | ||
| 255 | if (chain->ip >= PERF_CONTEXT_MAX) | ||
| 256 | continue; | ||
| 257 | if (chain->sym) | ||
| 258 | ret += fprintf(fp, " %s\n", chain->sym->name); | ||
| 259 | else | ||
| 260 | ret += fprintf(fp, " %p\n", | ||
| 261 | (void *)(long)chain->ip); | ||
| 262 | } | ||
| 263 | |||
| 264 | return ret; | ||
| 265 | } | ||
| 266 | |||
| 267 | static size_t | ||
| 268 | hist_entry_callchain__fprintf(FILE *fp, struct hist_entry *self, | ||
| 269 | u64 total_samples, int left_margin) | ||
| 270 | { | ||
| 271 | struct rb_node *rb_node; | ||
| 272 | struct callchain_node *chain; | ||
| 273 | size_t ret = 0; | ||
| 274 | |||
| 275 | rb_node = rb_first(&self->sorted_chain); | ||
| 276 | while (rb_node) { | ||
| 277 | double percent; | ||
| 278 | |||
| 279 | chain = rb_entry(rb_node, struct callchain_node, rb_node); | ||
| 280 | percent = chain->hit * 100.0 / total_samples; | ||
| 281 | switch (callchain_param.mode) { | ||
| 282 | case CHAIN_FLAT: | ||
| 283 | ret += percent_color_fprintf(fp, " %6.2f%%\n", | ||
| 284 | percent); | ||
| 285 | ret += callchain__fprintf_flat(fp, chain, total_samples); | ||
| 286 | break; | ||
| 287 | case CHAIN_GRAPH_ABS: /* Falldown */ | ||
| 288 | case CHAIN_GRAPH_REL: | ||
| 289 | ret += callchain__fprintf_graph(fp, chain, total_samples, | ||
| 290 | left_margin); | ||
| 291 | case CHAIN_NONE: | ||
| 292 | default: | ||
| 293 | break; | ||
| 294 | } | ||
| 295 | ret += fprintf(fp, "\n"); | ||
| 296 | rb_node = rb_next(rb_node); | ||
| 297 | } | ||
| 298 | |||
| 299 | return ret; | ||
| 300 | } | ||
| 301 | |||
| 302 | static size_t hist_entry__fprintf(FILE *fp, struct hist_entry *self, | ||
| 303 | struct perf_session *session) | ||
| 304 | { | ||
| 305 | struct sort_entry *se; | ||
| 306 | size_t ret; | ||
| 307 | |||
| 308 | if (symbol_conf.exclude_other && !self->parent) | ||
| 309 | return 0; | ||
| 310 | |||
| 311 | if (session->events_stats.total) | ||
| 312 | ret = percent_color_fprintf(fp, | ||
| 313 | symbol_conf.field_sep ? "%.2f" : " %6.2f%%", | ||
| 314 | (self->count * 100.0) / session->events_stats.total); | ||
| 315 | else | ||
| 316 | ret = fprintf(fp, symbol_conf.field_sep ? "%lld" : "%12lld ", self->count); | ||
| 317 | |||
| 318 | if (symbol_conf.show_nr_samples) { | ||
| 319 | if (symbol_conf.field_sep) | ||
| 320 | fprintf(fp, "%c%lld", *symbol_conf.field_sep, self->count); | ||
| 321 | else | ||
| 322 | fprintf(fp, "%11lld", self->count); | ||
| 323 | } | ||
| 324 | |||
| 325 | list_for_each_entry(se, &hist_entry__sort_list, list) { | ||
| 326 | if (se->elide) | ||
| 327 | continue; | ||
| 328 | |||
| 329 | fprintf(fp, "%s", symbol_conf.field_sep ?: " "); | ||
| 330 | ret += se->print(fp, self, se->width ? *se->width : 0); | ||
| 331 | } | ||
| 332 | |||
| 333 | ret += fprintf(fp, "\n"); | ||
| 334 | |||
| 335 | if (symbol_conf.use_callchain) { | ||
| 336 | int left_margin = 0; | ||
| 337 | |||
| 338 | if (sort__first_dimension == SORT_COMM) { | ||
| 339 | se = list_first_entry(&hist_entry__sort_list, typeof(*se), | ||
| 340 | list); | ||
| 341 | left_margin = se->width ? *se->width : 0; | ||
| 342 | left_margin -= thread__comm_len(self->thread); | ||
| 343 | } | ||
| 344 | |||
| 345 | hist_entry_callchain__fprintf(fp, self, session->events_stats.total, | ||
| 346 | left_margin); | ||
| 347 | } | ||
| 348 | |||
| 349 | return ret; | ||
| 350 | } | ||
| 351 | |||
| 352 | /* | ||
| 353 | * collect histogram counts | ||
| 354 | */ | ||
| 355 | |||
| 356 | static int perf_session__add_hist_entry(struct perf_session *self, | 46 | static int perf_session__add_hist_entry(struct perf_session *self, |
| 357 | struct addr_location *al, | 47 | struct addr_location *al, |
| 358 | struct ip_callchain *chain, u64 count) | 48 | struct ip_callchain *chain, u64 count) |
| @@ -381,93 +71,6 @@ static int perf_session__add_hist_entry(struct perf_session *self, | |||
| 381 | return 0; | 71 | return 0; |
| 382 | } | 72 | } |
| 383 | 73 | ||
| 384 | static size_t perf_session__fprintf_hists(struct perf_session *self, FILE *fp) | ||
| 385 | { | ||
| 386 | struct hist_entry *pos; | ||
| 387 | struct sort_entry *se; | ||
| 388 | struct rb_node *nd; | ||
| 389 | size_t ret = 0; | ||
| 390 | unsigned int width; | ||
| 391 | char *col_width = symbol_conf.col_width_list_str; | ||
| 392 | |||
| 393 | init_rem_hits(); | ||
| 394 | |||
| 395 | fprintf(fp, "# Samples: %ld\n", self->events_stats.total); | ||
| 396 | fprintf(fp, "#\n"); | ||
| 397 | |||
| 398 | fprintf(fp, "# Overhead"); | ||
| 399 | if (symbol_conf.show_nr_samples) { | ||
| 400 | if (symbol_conf.field_sep) | ||
| 401 | fprintf(fp, "%cSamples", *symbol_conf.field_sep); | ||
| 402 | else | ||
| 403 | fputs(" Samples ", fp); | ||
| 404 | } | ||
| 405 | list_for_each_entry(se, &hist_entry__sort_list, list) { | ||
| 406 | if (se->elide) | ||
| 407 | continue; | ||
| 408 | if (symbol_conf.field_sep) { | ||
| 409 | fprintf(fp, "%c%s", *symbol_conf.field_sep, se->header); | ||
| 410 | continue; | ||
| 411 | } | ||
| 412 | width = strlen(se->header); | ||
| 413 | if (se->width) { | ||
| 414 | if (symbol_conf.col_width_list_str) { | ||
| 415 | if (col_width) { | ||
| 416 | *se->width = atoi(col_width); | ||
| 417 | col_width = strchr(col_width, ','); | ||
| 418 | if (col_width) | ||
| 419 | ++col_width; | ||
| 420 | } | ||
| 421 | } | ||
| 422 | width = *se->width = max(*se->width, width); | ||
| 423 | } | ||
| 424 | fprintf(fp, " %*s", width, se->header); | ||
| 425 | } | ||
| 426 | fprintf(fp, "\n"); | ||
| 427 | |||
| 428 | if (symbol_conf.field_sep) | ||
| 429 | goto print_entries; | ||
| 430 | |||
| 431 | fprintf(fp, "# ........"); | ||
| 432 | if (symbol_conf.show_nr_samples) | ||
| 433 | fprintf(fp, " .........."); | ||
| 434 | list_for_each_entry(se, &hist_entry__sort_list, list) { | ||
| 435 | unsigned int i; | ||
| 436 | |||
| 437 | if (se->elide) | ||
| 438 | continue; | ||
| 439 | |||
| 440 | fprintf(fp, " "); | ||
| 441 | if (se->width) | ||
| 442 | width = *se->width; | ||
| 443 | else | ||
| 444 | width = strlen(se->header); | ||
| 445 | for (i = 0; i < width; i++) | ||
| 446 | fprintf(fp, "."); | ||
| 447 | } | ||
| 448 | fprintf(fp, "\n"); | ||
| 449 | |||
| 450 | fprintf(fp, "#\n"); | ||
| 451 | |||
| 452 | print_entries: | ||
| 453 | for (nd = rb_first(&self->hists); nd; nd = rb_next(nd)) { | ||
| 454 | pos = rb_entry(nd, struct hist_entry, rb_node); | ||
| 455 | ret += hist_entry__fprintf(fp, pos, self); | ||
| 456 | } | ||
| 457 | |||
| 458 | if (sort_order == default_sort_order && | ||
| 459 | parent_pattern == default_parent_pattern) { | ||
| 460 | fprintf(fp, "#\n"); | ||
| 461 | fprintf(fp, "# (For a higher level overview, try: perf report --sort comm,dso)\n"); | ||
| 462 | fprintf(fp, "#\n"); | ||
| 463 | } | ||
| 464 | fprintf(fp, "\n"); | ||
| 465 | |||
| 466 | free(rem_sq_bracket); | ||
| 467 | |||
| 468 | return ret; | ||
| 469 | } | ||
| 470 | |||
| 471 | static int validate_chain(struct ip_callchain *chain, event_t *event) | 74 | static int validate_chain(struct ip_callchain *chain, event_t *event) |
| 472 | { | 75 | { |
| 473 | unsigned int chain_size; | 76 | unsigned int chain_size; |
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 | } | ||
diff --git a/tools/perf/util/hist.h b/tools/perf/util/hist.h index 7efdb1b6d8c8..c7ac78d93b0c 100644 --- a/tools/perf/util/hist.h +++ b/tools/perf/util/hist.h | |||
| @@ -21,5 +21,6 @@ void hist_entry__free(struct hist_entry *); | |||
| 21 | 21 | ||
| 22 | void perf_session__output_resort(struct perf_session *self, u64 total_samples); | 22 | void perf_session__output_resort(struct perf_session *self, u64 total_samples); |
| 23 | void perf_session__collapse_resort(struct perf_session *self); | 23 | void perf_session__collapse_resort(struct perf_session *self); |
| 24 | size_t perf_session__fprintf_hists(struct perf_session *self, FILE *fp); | ||
| 24 | 25 | ||
| 25 | #endif /* __PERF_HIST_H */ | 26 | #endif /* __PERF_HIST_H */ |
