// SPDX-License-Identifier: GPL-2.0/* * builtin-annotate.c * * Builtin annotate command: Analyze the perf.data input file, * look up and read DSOs and symbol information and display * a histogram of results, along various sorting keys. */#include "builtin.h"#include "perf.h" #include "util/color.h"#include <linux/list.h>#include "util/cache.h"#include <linux/rbtree.h>#include "util/symbol.h" #include "util/debug.h" #include "util/evlist.h"#include "util/evsel.h"#include "util/annotate.h"#include "util/annotate-data.h"#include "util/event.h"#include <subcmd/parse-options.h>#include "util/parse-events.h"#include "util/sort.h"#include "util/hist.h"#include "util/dso.h"#include "util/machine.h"#include "util/map.h"#include "util/session.h"#include "util/tool.h"#include "util/data.h"#include "arch/common.h"#include "util/block-range.h"#include "util/map_symbol.h"#include "util/branch.h"#include "util/util.h"#include "ui/progress.h" #include <dlfcn.h>#include <errno.h>#include <linux/bitmap.h>#include <linux/err.h>#include <inttypes.h> struct perf_annotate { struct perf_tool tool; struct perf_session *session;#ifdef HAVE_SLANG_SUPPORT bool use_tui;#endif bool use_stdio, use_stdio2;#ifdef HAVE_GTK2_SUPPORT bool use_gtk;#endif bool skip_missing; bool has_br_stack; bool group_set; bool data_type; bool type_stat; bool insn_stat; float min_percent; const char *sym_hist_filter; const char *cpu_list; const char *target_data_type; DECLARE_BITMAP(cpu_bitmap, MAX_NR_CPUS);}; /* * Given one basic block: * * from to branch_i * * ----> * * | * | block * v * * ----> * * from to branch_i+1 * * where the horizontal are the branches and the vertical is the executed * block of instructions. * * We count, for each 'instruction', the number of blocks that covered it as * well as count the ratio each branch is taken. * * We can do this without knowing the actual instruction stream by keeping * track of the address ranges. We break down ranges such that there is no * overlap and iterate from the start until the end. * * @acme: once we parse the objdump output _before_ processing the samples, * we can easily fold the branch.cycles IPC bits in. */static void process_basic_block(struct addr_map_symbol *start, struct addr_map_symbol *end, struct branch_flags *flags){ struct symbol *sym = start->ms.sym; struct annotation *notes = sym ? symbol__annotation(sym) : NULL; struct block_range_iter iter; struct block_range *entry; struct annotated_branch *branch; /* * Sanity; NULL isn't executable and the CPU cannot execute backwards */ if (!start->addr || start->addr > end->addr) return; iter = block_range__create(start->addr, end->addr); if (!block_range_iter__valid(&iter)) return; branch = annotation__get_branch(notes); /* * First block in range is a branch target. */ entry = block_range_iter(&iter); assert(entry->is_target); entry->entry++; do { entry = block_range_iter(&iter); entry->coverage++; entry->sym = sym; if (branch) branch->max_coverage = max(branch->max_coverage, entry->coverage); } while (block_range_iter__next(&iter)); /* * Last block in rage is a branch. */ entry = block_range_iter(&iter); assert(entry->is_branch); entry->taken++; if (flags->predicted) entry->pred++;} static void process_branch_stack(struct branch_stack *bs, struct addr_location *al, struct perf_sample *sample){ struct addr_map_symbol *prev = NULL; struct branch_info *bi; int i; if (!bs || !bs->nr) return; bi = sample__resolve_bstack(sample, al); if (!bi) return; for (i = bs->nr - 1; i >= 0; i--) { /* * XXX filter against symbol */ if (prev) process_basic_block(prev, &bi[i].from, &bi[i].flags); prev = &bi[i].to; } free(bi);} static int hist_iter__branch_callback(struct hist_entry_iter *iter, struct addr_location *al __maybe_unused, bool single __maybe_unused, void *arg __maybe_unused){ struct hist_entry *he = iter->he; struct branch_info *bi; struct perf_sample *sample = iter->sample; int err; bi = he->branch_info; err = addr_map_symbol__inc_samples(&bi->from, sample); if (err) goto out; err = addr_map_symbol__inc_samples(&bi->to, sample); out: return err;} static int process_branch_callback(struct perf_sample *sample, struct addr_location *al, struct perf_annotate *ann, struct machine *machine){ struct hist_entry_iter iter = { .sample = sample, .add_entry_cb = hist_iter__branch_callback, .hide_unresolved = symbol_conf.hide_unresolved, .ops = &hist_iter_branch, }; struct addr_location a; int ret; addr_location__init(&a); if (machine__resolve(machine, &a, sample) < 0) { ret = -1; goto out; } if (a.sym == NULL) { ret = 0; goto out; } if (a.map != NULL) dso__set_hit(map__dso(a.map)); hist__account_cycles(sample->branch_stack, al, sample, /*nonany_branch_mode=*/false, /*total_cycles=*/NULL); ret = hist_entry_iter__add(&iter, &a, PERF_MAX_STACK_DEPTH, ann);out: addr_location__exit(&a); return ret;} static bool has_annotation(struct perf_annotate *ann){ return ui__has_annotation() || ann->use_stdio2;} static int add_sample(struct perf_sample *sample, struct addr_location *al, struct perf_annotate *ann, struct machine *machine){ struct hists *hists = evsel__hists(sample->evsel); struct hist_entry *he; int ret; if ((!ann->has_br_stack || !has_annotation(ann)) && ann->sym_hist_filter != NULL && (al->sym == NULL || strcmp(ann->sym_hist_filter, al->sym->name) != 0)) { /* We're only interested in a symbol named sym_hist_filter */ /* * FIXME: why isn't this done in the symbol_filter when loading * the DSO? */ if (al->sym != NULL) { struct dso *dso = map__dso(al->map); rb_erase_cached(&al->sym->rb_node, dso__symbols(dso)); symbol__delete(al->sym); dso__reset_find_symbol_cache(dso); } return 0; } /* * XXX filtered samples can still have branch entries pointing into our * symbol and are missed. */ process_branch_stack(sample->branch_stack, al, sample); if (ann->has_br_stack && has_annotation(ann)) return process_branch_callback(sample, al, ann, machine); he = hists__add_entry(hists, al, NULL, NULL, NULL, NULL, sample, true); if (he == NULL) return -ENOMEM; ret = hist_entry__inc_addr_samples(he, sample, al->addr); hists__inc_nr_samples(hists, true); return ret;} static int process_sample_event(const struct perf_tool *tool, union perf_event *event, struct perf_sample *sample, struct machine *machine){ struct perf_annotate *ann = container_of(tool, struct perf_annotate, tool); struct addr_location al; int ret = 0; addr_location__init(&al); if (machine__resolve(machine, &al, sample) < 0) { pr_warning("problem processing %s (%u) event at offset %#" PRIx64 ", skipping it.\n", perf_event__name(event->header.type), event->header.type, sample->file_offset); ret = -1; goto out_put; } if (ann->cpu_list && (sample->cpu >= MAX_NR_CPUS || !test_bit(sample->cpu, ann->cpu_bitmap))) goto out_put; if (!al.filtered && add_sample(sample, &al, ann, machine)) { pr_warning("problem incrementing symbol count, " "skipping event\n"); ret = -1; }out_put: addr_location__exit(&al); return ret;} static int hist_entry__stdio_annotate(struct hist_entry *he, struct evsel *evsel, struct perf_annotate *ann){ if (ann->use_stdio2) return hist_entry__tty_annotate2(he, evsel); return hist_entry__tty_annotate(he, evsel);} static void print_annotate_data_stat(struct annotated_data_stat *s){#define PRINT_STAT(fld) if (s->fld) printf("%10d : %s\n", s->fld, #fld) int bad = s->no_sym + s->no_insn + s->no_insn_ops + s->no_mem_ops + s->no_reg + s->no_dbginfo + s->no_cuinfo + s->no_var + s->no_typeinfo + s->invalid_size + s->bad_offset; int ok = s->total - bad; printf("Annotate data type stats:\n"); printf("total %d, ok %d (%.1f%%), bad %d (%.1f%%)\n", s->total, ok, 100.0 * ok / (s->total ?: 1), bad, 100.0 * bad / (s->total ?: 1)); printf("-----------------------------------------------------------\n"); PRINT_STAT(no_sym); PRINT_STAT(no_insn); PRINT_STAT(no_insn_ops); PRINT_STAT(no_mem_ops); PRINT_STAT(no_reg); PRINT_STAT(no_dbginfo); PRINT_STAT(no_cuinfo); PRINT_STAT(no_var); PRINT_STAT(no_typeinfo); PRINT_STAT(invalid_size); PRINT_STAT(bad_offset); PRINT_STAT(insn_track); printf("\n"); #undef PRINT_STAT} static void print_annotate_item_stat(struct list_head *head, const char *title){ struct annotated_item_stat *istat, *pos, *iter; int total_good, total_bad, total; int sum1, sum2; LIST_HEAD(tmp); /* sort the list by count */ list_splice_init(head, &tmp); total_good = total_bad = 0; list_for_each_entry_safe(istat, pos, &tmp, list) { total_good += istat->good; total_bad += istat->bad; sum1 = istat->good + istat->bad; list_for_each_entry(iter, head, list) { sum2 = iter->good + iter->bad; if (sum1 > sum2) break; } list_move_tail(&istat->list, &iter->list); } total = total_good + total_bad; printf("Annotate %s stats\n", title); printf("total %d, ok %d (%.1f%%), bad %d (%.1f%%)\n\n", total, total_good, 100.0 * total_good / (total ?: 1), total_bad, 100.0 * total_bad / (total ?: 1)); printf(" %-20s: %5s %5s\n", "Name/opcode", "Good", "Bad"); printf("-----------------------------------------------------------\n"); list_for_each_entry(istat, head, list) printf(" %-20s: %5d %5d\n", istat->name, istat->good, istat->bad); printf("\n");} static void hists__find_annotations(struct hists *hists, struct evsel *evsel, struct perf_annotate *ann){ struct rb_node *nd = rb_first_cached(&hists->entries), *next; int key = K_RIGHT; if (ann->type_stat) print_annotate_data_stat(&ann_data_stat); if (ann->insn_stat) print_annotate_item_stat(&ann_insn_stat, "Instruction"); while (nd) { struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node); struct annotation *notes; if (he->ms.sym == NULL || dso__annotate_warned(map__dso(he->ms.map))) goto find_next; if (ann->sym_hist_filter && (strcmp(he->ms.sym->name, ann->sym_hist_filter) != 0)) goto find_next; if (ann->min_percent) { float percent = 0; u64 total = hists__total_period(hists); if (total) percent = 100.0 * he->stat.period / total; if (percent < ann->min_percent) goto find_next; } notes = symbol__annotation(he->ms.sym); if (notes->src == NULL) {find_next: if (key == K_LEFT || key == '<') nd = rb_prev(nd); else nd = rb_next(nd); continue; } if (ann->data_type) { /* skip unknown type */ if (he->mem_type->histograms == NULL) goto find_next; if (ann->target_data_type) { const char *type_name = he->mem_type->self.type_name; /* skip 'struct ' prefix in the type name */ if (strncmp(ann->target_data_type, "struct ", 7) && !strncmp(type_name, "struct ", 7)) type_name += 7; /* skip 'union ' prefix in the type name */ if (strncmp(ann->target_data_type, "union ", 6) && !strncmp(type_name, "union ", 6)) type_name += 6; if (strcmp(ann->target_data_type, type_name)) goto find_next; } if (use_browser == 1) key = hist_entry__annotate_data_tui(he, evsel, NULL); else key = hist_entry__annotate_data_tty(he, evsel); switch (key) { case -1: if (!ann->skip_missing) return; /* fall through */ case K_RIGHT: case '>': next = rb_next(nd); break; case K_LEFT: case '<': next = rb_prev(nd); break; default: return; } if (use_browser == 0 || next != NULL) nd = next; continue; } if (use_browser == 2) { int ret; int (*annotate)(struct hist_entry *he, struct evsel *evsel, struct hist_browser_timer *hbt); annotate = dlsym(perf_gtk_handle, "hist_entry__gtk_annotate"); if (annotate == NULL) { ui__error("GTK browser not found!\n");Lines 1–500 of 953. Load more
// SPDX-License-Identifier: GPL-2.0 /* * builtin-annotate.c * * Builtin annotate command: Analyze the perf.data input file, * look up and read DSOs and symbol information and display * a histogram of results, along various sorting keys. */ #include "builtin.h" #include "perf.h" #include "util/color.h" #include <linux/list.h> #include "util/cache.h" #include <linux/rbtree.h> #include "util/symbol.h" #include "util/debug.h" #include "util/evlist.h" #include "util/evsel.h" #include "util/annotate.h" #include "util/annotate-data.h" #include "util/event.h" #include <subcmd/parse-options.h> #include "util/parse-events.h" #include "util/sort.h" #include "util/hist.h" #include "util/dso.h" #include "util/machine.h" #include "util/map.h" #include "util/session.h" #include "util/tool.h" #include "util/data.h" #include "arch/common.h" #include "util/block-range.h" #include "util/map_symbol.h" #include "util/branch.h" #include "util/util.h" #include "ui/progress.h" #include <dlfcn.h> #include <errno.h> #include <linux/bitmap.h> #include <linux/err.h> #include <inttypes.h> struct perf_annotate { struct perf_tool tool; struct perf_session *session; #ifdef HAVE_SLANG_SUPPORT bool use_tui; #endif bool use_stdio, use_stdio2; #ifdef HAVE_GTK2_SUPPORT bool use_gtk; #endif bool skip_missing; bool has_br_stack; bool group_set; bool data_type; bool type_stat; bool insn_stat; float min_percent; const char *sym_hist_filter; const char *cpu_list; const char *target_data_type; DECLARE_BITMAP(cpu_bitmap, MAX_NR_CPUS); }; /* * Given one basic block: * * from to branch_i * * ----> * * | * | block * v * * ----> * * from to branch_i+1 * * where the horizontal are the branches and the vertical is the executed * block of instructions. * * We count, for each 'instruction', the number of blocks that covered it as * well as count the ratio each branch is taken. * * We can do this without knowing the actual instruction stream by keeping * track of the address ranges. We break down ranges such that there is no * overlap and iterate from the start until the end. * * @acme: once we parse the objdump output _before_ processing the samples, * we can easily fold the branch.cycles IPC bits in. */ static void process_basic_block(struct addr_map_symbol *start, struct addr_map_symbol *end, struct branch_flags *flags) { struct symbol *sym = start->ms.sym; struct annotation *notes = sym ? symbol__annotation(sym) : NULL; struct block_range_iter iter; struct block_range *entry; struct annotated_branch *branch; /* * Sanity; NULL isn't executable and the CPU cannot execute backwards */ if (!start->addr || start->addr > end->addr) return; iter = block_range__create(start->addr, end->addr); if (!block_range_iter__valid(&iter)) return; branch = annotation__get_branch(notes); /* * First block in range is a branch target. */ entry = block_range_iter(&iter); assert(entry->is_target); entry->entry++; do { entry = block_range_iter(&iter); entry->coverage++; entry->sym = sym; if (branch) branch->max_coverage = max(branch->max_coverage, entry->coverage); } while (block_range_iter__next(&iter)); /* * Last block in rage is a branch. */ entry = block_range_iter(&iter); assert(entry->is_branch); entry->taken++; if (flags->predicted) entry->pred++; } static void process_branch_stack(struct branch_stack *bs, struct addr_location *al, struct perf_sample *sample) { struct addr_map_symbol *prev = NULL; struct branch_info *bi; int i; if (!bs || !bs->nr) return; bi = sample__resolve_bstack(sample, al); if (!bi) return; for (i = bs->nr - 1; i >= 0; i--) { /* * XXX filter against symbol */ if (prev) process_basic_block(prev, &bi[i].from, &bi[i].flags); prev = &bi[i].to; } free(bi); } static int hist_iter__branch_callback(struct hist_entry_iter *iter, struct addr_location *al __maybe_unused, bool single __maybe_unused, void *arg __maybe_unused) { struct hist_entry *he = iter->he; struct branch_info *bi; struct perf_sample *sample = iter->sample; int err; bi = he->branch_info; err = addr_map_symbol__inc_samples(&bi->from, sample); if (err) goto out; err = addr_map_symbol__inc_samples(&bi->to, sample); out: return err; } static int process_branch_callback(struct perf_sample *sample, struct addr_location *al, struct perf_annotate *ann, struct machine *machine) { struct hist_entry_iter iter = { .sample = sample, .add_entry_cb = hist_iter__branch_callback, .hide_unresolved = symbol_conf.hide_unresolved, .ops = &hist_iter_branch, }; struct addr_location a; int ret; addr_location__init(&a); if (machine__resolve(machine, &a, sample) < 0) { ret = -1; goto out; } if (a.sym == NULL) { ret = 0; goto out; } if (a.map != NULL) dso__set_hit(map__dso(a.map)); hist__account_cycles(sample->branch_stack, al, sample, /*nonany_branch_mode=*/false, /*total_cycles=*/NULL); ret = hist_entry_iter__add(&iter, &a, PERF_MAX_STACK_DEPTH, ann); out: addr_location__exit(&a); return ret; } static bool has_annotation(struct perf_annotate *ann) { return ui__has_annotation() || ann->use_stdio2; } static int add_sample(struct perf_sample *sample, struct addr_location *al, struct perf_annotate *ann, struct machine *machine) { struct hists *hists = evsel__hists(sample->evsel); struct hist_entry *he; int ret; if ((!ann->has_br_stack || !has_annotation(ann)) && ann->sym_hist_filter != NULL && (al->sym == NULL || strcmp(ann->sym_hist_filter, al->sym->name) != 0)) { /* We're only interested in a symbol named sym_hist_filter */ /* * FIXME: why isn't this done in the symbol_filter when loading * the DSO? */ if (al->sym != NULL) { struct dso *dso = map__dso(al->map); rb_erase_cached(&al->sym->rb_node, dso__symbols(dso)); symbol__delete(al->sym); dso__reset_find_symbol_cache(dso); } return 0; } /* * XXX filtered samples can still have branch entries pointing into our * symbol and are missed. */ process_branch_stack(sample->branch_stack, al, sample); if (ann->has_br_stack && has_annotation(ann)) return process_branch_callback(sample, al, ann, machine); he = hists__add_entry(hists, al, NULL, NULL, NULL, NULL, sample, true); if (he == NULL) return -ENOMEM; ret = hist_entry__inc_addr_samples(he, sample, al->addr); hists__inc_nr_samples(hists, true); return ret; } static int process_sample_event(const struct perf_tool *tool, union perf_event *event, struct perf_sample *sample, struct machine *machine) { struct perf_annotate *ann = container_of(tool, struct perf_annotate, tool); struct addr_location al; int ret = 0; addr_location__init(&al); if (machine__resolve(machine, &al, sample) < 0) { pr_warning("problem processing %s (%u) event at offset %#" PRIx64 ", skipping it.\n", perf_event__name(event->header.type), event->header.type, sample->file_offset); ret = -1; goto out_put; } if (ann->cpu_list && (sample->cpu >= MAX_NR_CPUS || !test_bit(sample->cpu, ann->cpu_bitmap))) goto out_put; if (!al.filtered && add_sample(sample, &al, ann, machine)) { pr_warning("problem incrementing symbol count, " "skipping event\n"); ret = -1; } out_put: addr_location__exit(&al); return ret; } static int hist_entry__stdio_annotate(struct hist_entry *he, struct evsel *evsel, struct perf_annotate *ann) { if (ann->use_stdio2) return hist_entry__tty_annotate2(he, evsel); return hist_entry__tty_annotate(he, evsel); } static void print_annotate_data_stat(struct annotated_data_stat *s) { #define PRINT_STAT(fld) if (s->fld) printf("%10d : %s\n", s->fld, #fld) int bad = s->no_sym + s->no_insn + s->no_insn_ops + s->no_mem_ops + s->no_reg + s->no_dbginfo + s->no_cuinfo + s->no_var + s->no_typeinfo + s->invalid_size + s->bad_offset; int ok = s->total - bad; printf("Annotate data type stats:\n"); printf("total %d, ok %d (%.1f%%), bad %d (%.1f%%)\n", s->total, ok, 100.0 * ok / (s->total ?: 1), bad, 100.0 * bad / (s->total ?: 1)); printf("-----------------------------------------------------------\n"); PRINT_STAT(no_sym); PRINT_STAT(no_insn); PRINT_STAT(no_insn_ops); PRINT_STAT(no_mem_ops); PRINT_STAT(no_reg); PRINT_STAT(no_dbginfo); PRINT_STAT(no_cuinfo); PRINT_STAT(no_var); PRINT_STAT(no_typeinfo); PRINT_STAT(invalid_size); PRINT_STAT(bad_offset); PRINT_STAT(insn_track); printf("\n"); #undef PRINT_STAT } static void print_annotate_item_stat(struct list_head *head, const char *title) { struct annotated_item_stat *istat, *pos, *iter; int total_good, total_bad, total; int sum1, sum2; LIST_HEAD(tmp); /* sort the list by count */ list_splice_init(head, &tmp); total_good = total_bad = 0; list_for_each_entry_safe(istat, pos, &tmp, list) { total_good += istat->good; total_bad += istat->bad; sum1 = istat->good + istat->bad; list_for_each_entry(iter, head, list) { sum2 = iter->good + iter->bad; if (sum1 > sum2) break; } list_move_tail(&istat->list, &iter->list); } total = total_good + total_bad; printf("Annotate %s stats\n", title); printf("total %d, ok %d (%.1f%%), bad %d (%.1f%%)\n\n", total, total_good, 100.0 * total_good / (total ?: 1), total_bad, 100.0 * total_bad / (total ?: 1)); printf(" %-20s: %5s %5s\n", "Name/opcode", "Good", "Bad"); printf("-----------------------------------------------------------\n"); list_for_each_entry(istat, head, list) printf(" %-20s: %5d %5d\n", istat->name, istat->good, istat->bad); printf("\n"); } static void hists__find_annotations(struct hists *hists, struct evsel *evsel, struct perf_annotate *ann) { struct rb_node *nd = rb_first_cached(&hists->entries), *next; int key = K_RIGHT; if (ann->type_stat) print_annotate_data_stat(&ann_data_stat); if (ann->insn_stat) print_annotate_item_stat(&ann_insn_stat, "Instruction"); while (nd) { struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node); struct annotation *notes; if (he->ms.sym == NULL || dso__annotate_warned(map__dso(he->ms.map))) goto find_next; if (ann->sym_hist_filter && (strcmp(he->ms.sym->name, ann->sym_hist_filter) != 0)) goto find_next; if (ann->min_percent) { float percent = 0; u64 total = hists__total_period(hists); if (total) percent = 100.0 * he->stat.period / total; if (percent < ann->min_percent) goto find_next; } notes = symbol__annotation(he->ms.sym); if (notes->src == NULL) { find_next: if (key == K_LEFT || key == '<') nd = rb_prev(nd); else nd = rb_next(nd); continue; } if (ann->data_type) { /* skip unknown type */ if (he->mem_type->histograms == NULL) goto find_next; if (ann->target_data_type) { const char *type_name = he->mem_type->self.type_name; /* skip 'struct ' prefix in the type name */ if (strncmp(ann->target_data_type, "struct ", 7) && !strncmp(type_name, "struct ", 7)) type_name += 7; /* skip 'union ' prefix in the type name */ if (strncmp(ann->target_data_type, "union ", 6) && !strncmp(type_name, "union ", 6)) type_name += 6; if (strcmp(ann->target_data_type, type_name)) goto find_next; } if (use_browser == 1) key = hist_entry__annotate_data_tui(he, evsel, NULL); else key = hist_entry__annotate_data_tty(he, evsel); switch (key) { case -1: if (!ann->skip_missing) return; /* fall through */ case K_RIGHT: case '>': next = rb_next(nd); break; case K_LEFT: case '<': next = rb_prev(nd); break; default: return; } if (use_browser == 0 || next != NULL) nd = next; continue; } if (use_browser == 2) { int ret; int (*annotate)(struct hist_entry *he, struct evsel *evsel, struct hist_browser_timer *hbt); annotate = dlsym(perf_gtk_handle, "hist_entry__gtk_annotate"); if (annotate == NULL) { ui__error("GTK browser not found!\n");