| 1 | // SPDX-License-Identifier: GPL-2.0 |
| 2 | #include <stdlib.h> |
| 3 | #include <string.h> |
| 4 | #include <linux/zalloc.h> |
| 5 | #include "block-info.h" |
| 6 | #include "sort.h" |
| 7 | #include "annotate.h" |
| 8 | #include "symbol.h" |
| 9 | #include "dso.h" |
| 10 | #include "map.h" |
| 11 | #include "srcline.h" |
| 12 | #include "evlist.h" |
| 13 | #include "hist.h" |
| 14 | #include "ui/browsers/hists.h" |
| 15 | |
| 16 | static struct block_header_column { |
| 17 | const char *name; |
| 18 | int width; |
| 19 | } block_columns[PERF_HPP_REPORT__BLOCK_MAX_INDEX] = { |
| 20 | [PERF_HPP_REPORT__BLOCK_TOTAL_CYCLES_PCT] = { |
| 21 | .name = "Sampled Cycles%" , |
| 22 | .width = 15, |
| 23 | }, |
| 24 | [PERF_HPP_REPORT__BLOCK_LBR_CYCLES] = { |
| 25 | .name = "Sampled Cycles" , |
| 26 | .width = 14, |
| 27 | }, |
| 28 | [PERF_HPP_REPORT__BLOCK_CYCLES_PCT] = { |
| 29 | .name = "Avg Cycles%" , |
| 30 | .width = 11, |
| 31 | }, |
| 32 | [PERF_HPP_REPORT__BLOCK_AVG_CYCLES] = { |
| 33 | .name = "Avg Cycles" , |
| 34 | .width = 10, |
| 35 | }, |
| 36 | [PERF_HPP_REPORT__BLOCK_RANGE] = { |
| 37 | .name = "[Program Block Range]" , |
| 38 | .width = 70, |
| 39 | }, |
| 40 | [PERF_HPP_REPORT__BLOCK_DSO] = { |
| 41 | .name = "Shared Object" , |
| 42 | .width = 20, |
| 43 | }, |
| 44 | [PERF_HPP_REPORT__BLOCK_BRANCH_COUNTER] = { |
| 45 | .name = "Branch Counter" , |
| 46 | .width = 30, |
| 47 | } |
| 48 | }; |
| 49 | |
| 50 | static struct block_info *block_info__new(unsigned int br_cntr_nr) |
| 51 | { |
| 52 | struct block_info *bi = zalloc(sizeof(struct block_info)); |
| 53 | |
| 54 | if (bi && br_cntr_nr) { |
| 55 | bi->br_cntr = calloc(br_cntr_nr, sizeof(u64)); |
| 56 | if (!bi->br_cntr) { |
| 57 | free(bi); |
| 58 | return NULL; |
| 59 | } |
| 60 | } |
| 61 | |
| 62 | return bi; |
| 63 | } |
| 64 | |
| 65 | void block_info__delete(struct block_info *bi) |
| 66 | { |
| 67 | if (bi) |
| 68 | free(bi->br_cntr); |
| 69 | free(bi); |
| 70 | } |
| 71 | |
| 72 | int64_t __block_info__cmp(struct hist_entry *left, struct hist_entry *right) |
| 73 | { |
| 74 | struct block_info *bi_l = left->block_info; |
| 75 | struct block_info *bi_r = right->block_info; |
| 76 | int cmp; |
| 77 | |
| 78 | if (!bi_l->sym || !bi_r->sym) { |
| 79 | if (!bi_l->sym && !bi_r->sym) |
| 80 | return -1; |
| 81 | else if (!bi_l->sym) |
| 82 | return -1; |
| 83 | else |
| 84 | return 1; |
| 85 | } |
| 86 | |
| 87 | cmp = strcmp(bi_l->sym->name, bi_r->sym->name); |
| 88 | if (cmp) |
| 89 | return cmp; |
| 90 | |
| 91 | if (bi_l->start != bi_r->start) |
| 92 | return (int64_t)(bi_r->start - bi_l->start); |
| 93 | |
| 94 | return (int64_t)(bi_r->end - bi_l->end); |
| 95 | } |
| 96 | |
| 97 | int64_t block_info__cmp(struct perf_hpp_fmt *fmt __maybe_unused, |
| 98 | struct hist_entry *left, struct hist_entry *right) |
| 99 | { |
| 100 | return __block_info__cmp(left, right); |
| 101 | } |
| 102 | |
| 103 | static void init_block_info(struct block_info *bi, struct symbol *sym, |
| 104 | struct cyc_hist *ch, int offset, |
| 105 | u64 total_cycles, unsigned int br_cntr_nr, |
| 106 | u64 *br_cntr, struct evsel *evsel) |
| 107 | { |
| 108 | bi->sym = sym; |
| 109 | bi->start = ch->start; |
| 110 | bi->end = offset; |
| 111 | bi->cycles = ch->cycles; |
| 112 | bi->cycles_aggr = ch->cycles_aggr; |
| 113 | bi->num = ch->num; |
| 114 | bi->num_aggr = ch->num_aggr; |
| 115 | bi->total_cycles = total_cycles; |
| 116 | |
| 117 | memcpy(bi->cycles_spark, ch->cycles_spark, |
| 118 | NUM_SPARKS * sizeof(u64)); |
| 119 | |
| 120 | if (br_cntr && br_cntr_nr) { |
| 121 | bi->br_cntr_nr = br_cntr_nr; |
| 122 | memcpy(bi->br_cntr, &br_cntr[offset * br_cntr_nr], |
| 123 | br_cntr_nr * sizeof(u64)); |
| 124 | } |
| 125 | bi->evsel = evsel; |
| 126 | } |
| 127 | |
| 128 | int block_info__process_sym(struct hist_entry *he, struct block_hist *bh, |
| 129 | u64 *block_cycles_aggr, u64 total_cycles, |
| 130 | unsigned int br_cntr_nr) |
| 131 | { |
| 132 | struct annotation *notes; |
| 133 | struct cyc_hist *ch; |
| 134 | static struct addr_location al; |
| 135 | u64 cycles = 0; |
| 136 | |
| 137 | if (!he->ms.map || !he->ms.sym) |
| 138 | return 0; |
| 139 | |
| 140 | memset(&al, 0, sizeof(al)); |
| 141 | al.map = he->ms.map; |
| 142 | al.sym = he->ms.sym; |
| 143 | |
| 144 | notes = symbol__annotation(sym: he->ms.sym); |
| 145 | if (!notes || !notes->branch || !notes->branch->cycles_hist) |
| 146 | return 0; |
| 147 | ch = notes->branch->cycles_hist; |
| 148 | for (unsigned int i = 0; i < symbol__size(sym: he->ms.sym); i++) { |
| 149 | if (ch[i].num_aggr) { |
| 150 | struct block_info *bi; |
| 151 | struct hist_entry *he_block; |
| 152 | |
| 153 | bi = block_info__new(br_cntr_nr); |
| 154 | if (!bi) |
| 155 | return -1; |
| 156 | |
| 157 | init_block_info(bi, sym: he->ms.sym, ch: &ch[i], offset: i, |
| 158 | total_cycles, br_cntr_nr, |
| 159 | br_cntr: notes->branch->br_cntr, |
| 160 | evsel: hists_to_evsel(hists: he->hists)); |
| 161 | cycles += bi->cycles_aggr / bi->num_aggr; |
| 162 | |
| 163 | he_block = hists__add_entry_block(hists: &bh->block_hists, |
| 164 | al: &al, bi); |
| 165 | if (!he_block) { |
| 166 | block_info__delete(bi); |
| 167 | return -1; |
| 168 | } |
| 169 | } |
| 170 | } |
| 171 | |
| 172 | if (block_cycles_aggr) |
| 173 | *block_cycles_aggr += cycles; |
| 174 | |
| 175 | return 0; |
| 176 | } |
| 177 | |
| 178 | static int block_column_header(struct perf_hpp_fmt *fmt, |
| 179 | struct perf_hpp *hpp, |
| 180 | struct hists *hists __maybe_unused, |
| 181 | int line __maybe_unused, |
| 182 | int *span __maybe_unused) |
| 183 | { |
| 184 | struct block_fmt *block_fmt = container_of(fmt, struct block_fmt, fmt); |
| 185 | |
| 186 | return scnprintf(buf: hpp->buf, size: hpp->size, fmt: "%*s" , block_fmt->width, |
| 187 | block_fmt->header); |
| 188 | } |
| 189 | |
| 190 | static int block_column_width(struct perf_hpp_fmt *fmt, |
| 191 | struct perf_hpp *hpp __maybe_unused, |
| 192 | struct hists *hists __maybe_unused) |
| 193 | { |
| 194 | struct block_fmt *block_fmt = container_of(fmt, struct block_fmt, fmt); |
| 195 | |
| 196 | return block_fmt->width; |
| 197 | } |
| 198 | |
| 199 | static int color_pct(struct perf_hpp *hpp, int width, double pct) |
| 200 | { |
| 201 | #ifdef HAVE_SLANG_SUPPORT |
| 202 | if (use_browser) { |
| 203 | return __hpp__slsmg_color_printf(hpp, "%*.2f%%" , |
| 204 | width - 1, pct); |
| 205 | } |
| 206 | #endif |
| 207 | return hpp_color_scnprintf(hpp, fmt: "%*.2f%%" , width - 1, pct); |
| 208 | } |
| 209 | |
| 210 | static int block_total_cycles_pct_entry(struct perf_hpp_fmt *fmt, |
| 211 | struct perf_hpp *hpp, |
| 212 | struct hist_entry *he) |
| 213 | { |
| 214 | struct block_fmt *block_fmt = container_of(fmt, struct block_fmt, fmt); |
| 215 | struct block_info *bi = he->block_info; |
| 216 | double ratio = 0.0; |
| 217 | |
| 218 | if (block_fmt->total_cycles) |
| 219 | ratio = (double)bi->cycles_aggr / (double)block_fmt->total_cycles; |
| 220 | |
| 221 | return color_pct(hpp, width: block_fmt->width, pct: 100.0 * ratio); |
| 222 | } |
| 223 | |
| 224 | static int64_t block_total_cycles_pct_sort(struct perf_hpp_fmt *fmt, |
| 225 | struct hist_entry *left, |
| 226 | struct hist_entry *right) |
| 227 | { |
| 228 | struct block_fmt *block_fmt = container_of(fmt, struct block_fmt, fmt); |
| 229 | struct block_info *bi_l = left->block_info; |
| 230 | struct block_info *bi_r = right->block_info; |
| 231 | double l, r; |
| 232 | |
| 233 | if (block_fmt->total_cycles) { |
| 234 | l = ((double)bi_l->cycles_aggr / |
| 235 | (double)block_fmt->total_cycles) * 100000.0; |
| 236 | r = ((double)bi_r->cycles_aggr / |
| 237 | (double)block_fmt->total_cycles) * 100000.0; |
| 238 | return (int64_t)l - (int64_t)r; |
| 239 | } |
| 240 | |
| 241 | return 0; |
| 242 | } |
| 243 | |
| 244 | static void cycles_string(u64 cycles, char *buf, int size) |
| 245 | { |
| 246 | if (cycles >= 1000000) |
| 247 | scnprintf(buf, size, fmt: "%.1fM" , (double)cycles / 1000000.0); |
| 248 | else if (cycles >= 1000) |
| 249 | scnprintf(buf, size, fmt: "%.1fK" , (double)cycles / 1000.0); |
| 250 | else |
| 251 | scnprintf(buf, size, fmt: "%1d" , cycles); |
| 252 | } |
| 253 | |
| 254 | static int block_cycles_lbr_entry(struct perf_hpp_fmt *fmt, |
| 255 | struct perf_hpp *hpp, struct hist_entry *he) |
| 256 | { |
| 257 | struct block_fmt *block_fmt = container_of(fmt, struct block_fmt, fmt); |
| 258 | struct block_info *bi = he->block_info; |
| 259 | char cycles_buf[16]; |
| 260 | |
| 261 | cycles_string(cycles: bi->cycles_aggr, buf: cycles_buf, size: sizeof(cycles_buf)); |
| 262 | |
| 263 | return scnprintf(buf: hpp->buf, size: hpp->size, fmt: "%*s" , block_fmt->width, |
| 264 | cycles_buf); |
| 265 | } |
| 266 | |
| 267 | static int block_cycles_pct_entry(struct perf_hpp_fmt *fmt, |
| 268 | struct perf_hpp *hpp, struct hist_entry *he) |
| 269 | { |
| 270 | struct block_fmt *block_fmt = container_of(fmt, struct block_fmt, fmt); |
| 271 | struct block_info *bi = he->block_info; |
| 272 | double ratio = 0.0; |
| 273 | u64 avg; |
| 274 | |
| 275 | if (block_fmt->block_cycles && bi->num_aggr) { |
| 276 | avg = bi->cycles_aggr / bi->num_aggr; |
| 277 | ratio = (double)avg / (double)block_fmt->block_cycles; |
| 278 | } |
| 279 | |
| 280 | return color_pct(hpp, width: block_fmt->width, pct: 100.0 * ratio); |
| 281 | } |
| 282 | |
| 283 | static int block_avg_cycles_entry(struct perf_hpp_fmt *fmt, |
| 284 | struct perf_hpp *hpp, |
| 285 | struct hist_entry *he) |
| 286 | { |
| 287 | struct block_fmt *block_fmt = container_of(fmt, struct block_fmt, fmt); |
| 288 | struct block_info *bi = he->block_info; |
| 289 | char cycles_buf[16]; |
| 290 | |
| 291 | cycles_string(cycles: bi->cycles_aggr / bi->num_aggr, buf: cycles_buf, |
| 292 | size: sizeof(cycles_buf)); |
| 293 | |
| 294 | return scnprintf(buf: hpp->buf, size: hpp->size, fmt: "%*s" , block_fmt->width, |
| 295 | cycles_buf); |
| 296 | } |
| 297 | |
| 298 | static int block_range_entry(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, |
| 299 | struct hist_entry *he) |
| 300 | { |
| 301 | struct block_fmt *block_fmt = container_of(fmt, struct block_fmt, fmt); |
| 302 | struct block_info *bi = he->block_info; |
| 303 | char buf[128]; |
| 304 | char *start_line, *end_line; |
| 305 | |
| 306 | symbol_conf.disable_add2line_warn = true; |
| 307 | |
| 308 | start_line = map__srcline(map: he->ms.map, addr: bi->sym->start + bi->start, |
| 309 | sym: he->ms.sym); |
| 310 | |
| 311 | end_line = map__srcline(map: he->ms.map, addr: bi->sym->start + bi->end, |
| 312 | sym: he->ms.sym); |
| 313 | |
| 314 | if (start_line != SRCLINE_UNKNOWN && |
| 315 | end_line != SRCLINE_UNKNOWN) { |
| 316 | scnprintf(buf, size: sizeof(buf), fmt: "[%s -> %s]" , |
| 317 | start_line, end_line); |
| 318 | } else { |
| 319 | scnprintf(buf, size: sizeof(buf), fmt: "[%7lx -> %7lx]" , |
| 320 | bi->start, bi->end); |
| 321 | } |
| 322 | |
| 323 | zfree_srcline(srcline: &start_line); |
| 324 | zfree_srcline(srcline: &end_line); |
| 325 | |
| 326 | return scnprintf(buf: hpp->buf, size: hpp->size, fmt: "%*s" , block_fmt->width, buf); |
| 327 | } |
| 328 | |
| 329 | static int block_dso_entry(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, |
| 330 | struct hist_entry *he) |
| 331 | { |
| 332 | struct block_fmt *block_fmt = container_of(fmt, struct block_fmt, fmt); |
| 333 | struct map *map = he->ms.map; |
| 334 | |
| 335 | if (map && map__dso(map)) { |
| 336 | return scnprintf(buf: hpp->buf, size: hpp->size, fmt: "%*s" , block_fmt->width, |
| 337 | dso__short_name(dso: map__dso(map))); |
| 338 | } |
| 339 | |
| 340 | return scnprintf(buf: hpp->buf, size: hpp->size, fmt: "%*s" , block_fmt->width, |
| 341 | "[unknown]" ); |
| 342 | } |
| 343 | |
| 344 | static void (struct block_fmt *block_fmt) |
| 345 | { |
| 346 | struct perf_hpp_fmt *fmt = &block_fmt->fmt; |
| 347 | |
| 348 | BUG_ON(block_fmt->idx >= PERF_HPP_REPORT__BLOCK_MAX_INDEX); |
| 349 | |
| 350 | block_fmt->header = block_columns[block_fmt->idx].name; |
| 351 | block_fmt->width = block_columns[block_fmt->idx].width; |
| 352 | |
| 353 | fmt->header = block_column_header; |
| 354 | fmt->width = block_column_width; |
| 355 | } |
| 356 | |
| 357 | static int block_branch_counter_entry(struct perf_hpp_fmt *fmt, |
| 358 | struct perf_hpp *hpp, |
| 359 | struct hist_entry *he) |
| 360 | { |
| 361 | struct block_fmt *block_fmt = container_of(fmt, struct block_fmt, fmt); |
| 362 | struct block_info *bi = he->block_info; |
| 363 | char *buf; |
| 364 | int ret; |
| 365 | |
| 366 | if (annotation_br_cntr_entry(str: &buf, br_cntr_nr: bi->br_cntr_nr, br_cntr: bi->br_cntr, |
| 367 | num_aggr: bi->num_aggr, evsel: bi->evsel)) |
| 368 | return 0; |
| 369 | |
| 370 | ret = scnprintf(buf: hpp->buf, size: hpp->size, fmt: "%*s" , block_fmt->width, buf); |
| 371 | free(buf); |
| 372 | return ret; |
| 373 | } |
| 374 | |
| 375 | static void hpp_register(struct block_fmt *block_fmt, int idx, |
| 376 | struct perf_hpp_list *hpp_list) |
| 377 | { |
| 378 | struct perf_hpp_fmt *fmt = &block_fmt->fmt; |
| 379 | |
| 380 | block_fmt->idx = idx; |
| 381 | INIT_LIST_HEAD(list: &fmt->list); |
| 382 | INIT_LIST_HEAD(list: &fmt->sort_list); |
| 383 | |
| 384 | switch (idx) { |
| 385 | case PERF_HPP_REPORT__BLOCK_TOTAL_CYCLES_PCT: |
| 386 | fmt->color = block_total_cycles_pct_entry; |
| 387 | fmt->cmp = block_info__cmp; |
| 388 | fmt->sort = block_total_cycles_pct_sort; |
| 389 | break; |
| 390 | case PERF_HPP_REPORT__BLOCK_LBR_CYCLES: |
| 391 | fmt->entry = block_cycles_lbr_entry; |
| 392 | break; |
| 393 | case PERF_HPP_REPORT__BLOCK_CYCLES_PCT: |
| 394 | fmt->color = block_cycles_pct_entry; |
| 395 | break; |
| 396 | case PERF_HPP_REPORT__BLOCK_AVG_CYCLES: |
| 397 | fmt->entry = block_avg_cycles_entry; |
| 398 | break; |
| 399 | case PERF_HPP_REPORT__BLOCK_RANGE: |
| 400 | fmt->entry = block_range_entry; |
| 401 | break; |
| 402 | case PERF_HPP_REPORT__BLOCK_DSO: |
| 403 | fmt->entry = block_dso_entry; |
| 404 | break; |
| 405 | case PERF_HPP_REPORT__BLOCK_BRANCH_COUNTER: |
| 406 | fmt->entry = block_branch_counter_entry; |
| 407 | break; |
| 408 | default: |
| 409 | return; |
| 410 | } |
| 411 | |
| 412 | init_block_header(block_fmt); |
| 413 | perf_hpp_list__column_register(list: hpp_list, format: fmt); |
| 414 | } |
| 415 | |
| 416 | static void register_block_columns(struct perf_hpp_list *hpp_list, |
| 417 | struct block_fmt *block_fmts, |
| 418 | int *block_hpps, int nr_hpps) |
| 419 | { |
| 420 | for (int i = 0; i < nr_hpps; i++) |
| 421 | hpp_register(block_fmt: &block_fmts[i], idx: block_hpps[i], hpp_list); |
| 422 | } |
| 423 | |
| 424 | static void init_block_hist(struct block_hist *bh, struct block_fmt *block_fmts, |
| 425 | int *block_hpps, int nr_hpps) |
| 426 | { |
| 427 | __hists__init(hists: &bh->block_hists, hpp_list: &bh->block_list); |
| 428 | perf_hpp_list__init(list: &bh->block_list); |
| 429 | bh->block_list.nr_header_lines = 1; |
| 430 | |
| 431 | register_block_columns(hpp_list: &bh->block_list, block_fmts, |
| 432 | block_hpps, nr_hpps); |
| 433 | |
| 434 | /* Sort by the first fmt */ |
| 435 | perf_hpp_list__register_sort_field(list: &bh->block_list, format: &block_fmts[0].fmt); |
| 436 | } |
| 437 | |
| 438 | static int process_block_report(struct hists *hists, |
| 439 | struct block_report *block_report, |
| 440 | u64 total_cycles, int *block_hpps, |
| 441 | int nr_hpps, unsigned int br_cntr_nr) |
| 442 | { |
| 443 | struct rb_node *next = rb_first_cached(&hists->entries); |
| 444 | struct block_hist *bh = &block_report->hist; |
| 445 | struct hist_entry *he; |
| 446 | |
| 447 | if (nr_hpps > PERF_HPP_REPORT__BLOCK_MAX_INDEX) |
| 448 | return -1; |
| 449 | |
| 450 | block_report->nr_fmts = nr_hpps; |
| 451 | init_block_hist(bh, block_fmts: block_report->fmts, block_hpps, nr_hpps); |
| 452 | |
| 453 | while (next) { |
| 454 | he = rb_entry(next, struct hist_entry, rb_node); |
| 455 | block_info__process_sym(he, bh, block_cycles_aggr: &block_report->cycles, |
| 456 | total_cycles, br_cntr_nr); |
| 457 | next = rb_next(&he->rb_node); |
| 458 | } |
| 459 | |
| 460 | for (int i = 0; i < nr_hpps; i++) { |
| 461 | block_report->fmts[i].total_cycles = total_cycles; |
| 462 | block_report->fmts[i].block_cycles = block_report->cycles; |
| 463 | } |
| 464 | |
| 465 | hists__output_resort(hists: &bh->block_hists, NULL); |
| 466 | return 0; |
| 467 | } |
| 468 | |
| 469 | struct block_report *block_info__create_report(struct evlist *evlist, |
| 470 | u64 total_cycles, |
| 471 | int *block_hpps, int nr_hpps, |
| 472 | int *nr_reps) |
| 473 | { |
| 474 | struct block_report *block_reports; |
| 475 | int nr_hists = evlist->core.nr_entries, i = 0; |
| 476 | struct evsel *pos; |
| 477 | |
| 478 | block_reports = calloc(nr_hists, sizeof(struct block_report)); |
| 479 | if (!block_reports) |
| 480 | return NULL; |
| 481 | |
| 482 | evlist__for_each_entry(evlist, pos) { |
| 483 | struct hists *hists = evsel__hists(evsel: pos); |
| 484 | |
| 485 | process_block_report(hists, block_report: &block_reports[i], total_cycles, |
| 486 | block_hpps, nr_hpps, br_cntr_nr: evlist->nr_br_cntr); |
| 487 | i++; |
| 488 | } |
| 489 | |
| 490 | *nr_reps = nr_hists; |
| 491 | return block_reports; |
| 492 | } |
| 493 | |
| 494 | void block_info__free_report(struct block_report *reps, int nr_reps) |
| 495 | { |
| 496 | for (int i = 0; i < nr_reps; i++) |
| 497 | hists__delete_entries(hists: &reps[i].hist.block_hists); |
| 498 | |
| 499 | free(reps); |
| 500 | } |
| 501 | |
| 502 | int report__browse_block_hists(struct block_hist *bh, float min_percent, |
| 503 | struct evsel *evsel, struct perf_env *env) |
| 504 | { |
| 505 | int ret; |
| 506 | |
| 507 | switch (use_browser) { |
| 508 | case 0: |
| 509 | symbol_conf.report_individual_block = true; |
| 510 | hists__fprintf(&bh->block_hists, true, 0, 0, min_percent, |
| 511 | stdout, true); |
| 512 | return 0; |
| 513 | case 1: |
| 514 | symbol_conf.report_individual_block = true; |
| 515 | ret = block_hists_tui_browse(bh, evsel, min_percent, env); |
| 516 | return ret; |
| 517 | default: |
| 518 | return -1; |
| 519 | } |
| 520 | |
| 521 | return 0; |
| 522 | } |
| 523 | |
| 524 | float block_info__total_cycles_percent(struct hist_entry *he) |
| 525 | { |
| 526 | struct block_info *bi = he->block_info; |
| 527 | |
| 528 | if (bi->total_cycles) |
| 529 | return bi->cycles * 100.0 / bi->total_cycles; |
| 530 | |
| 531 | return 0.0; |
| 532 | } |
| 533 | |