diff options
Diffstat (limited to 'tools/perf/util')
56 files changed, 1874 insertions, 243 deletions
diff --git a/tools/perf/util/Build b/tools/perf/util/Build index cd5e41960e64..e2563d0154eb 100644 --- a/tools/perf/util/Build +++ b/tools/perf/util/Build @@ -101,6 +101,8 @@ perf-y += call-path.o perf-y += rwsem.o perf-y += thread-stack.o perf-y += spark.o +perf-y += topdown.o +perf-y += stream.o perf-$(CONFIG_AUXTRACE) += auxtrace.o perf-$(CONFIG_AUXTRACE) += intel-pt-decoder/ perf-$(CONFIG_AUXTRACE) += intel-pt.o diff --git a/tools/perf/util/annotate.c b/tools/perf/util/annotate.c index 0a1fcf787538..6c8575e182ed 100644 --- a/tools/perf/util/annotate.c +++ b/tools/perf/util/annotate.c @@ -1578,8 +1578,7 @@ int symbol__strerror_disassemble(struct map_symbol *ms, int errnum, char *buf, s char *build_id_msg = NULL; if (dso->has_build_id) { - build_id__sprintf(dso->build_id, - sizeof(dso->build_id), bf + 15); + build_id__sprintf(&dso->bid, bf + 15); build_id_msg = bf; } scnprintf(buf, buflen, @@ -3127,6 +3126,8 @@ static int annotation__config(const char *var, const char *value, void *data) value); } else if (!strcmp(var, "annotate.use_offset")) { opt->use_offset = perf_config_bool("use_offset", value); + } else if (!strcmp(var, "annotate.disassembler_style")) { + opt->disassembler_style = value; } else { pr_debug("%s variable unknown, ignoring...", var); } diff --git a/tools/perf/util/build-id.c b/tools/perf/util/build-id.c index 31207b6e2066..8763772f1095 100644 --- a/tools/perf/util/build-id.c +++ b/tools/perf/util/build-id.c @@ -37,6 +37,7 @@ #include <linux/ctype.h> #include <linux/zalloc.h> +#include <asm/bug.h> static bool no_buildid_cache; @@ -95,13 +96,13 @@ struct perf_tool build_id__mark_dso_hit_ops = { .ordered_events = true, }; -int build_id__sprintf(const u8 *build_id, int len, char *bf) +int build_id__sprintf(const struct build_id *build_id, char *bf) { char *bid = bf; - const u8 *raw = build_id; - int i; + const u8 *raw = build_id->data; + size_t i; - for (i = 0; i < len; ++i) { + for (i = 0; i < build_id->size; ++i) { sprintf(bid, "%02x", *raw); ++raw; bid += 2; @@ -113,7 +114,7 @@ int build_id__sprintf(const u8 *build_id, int len, char *bf) int sysfs__sprintf_build_id(const char *root_dir, char *sbuild_id) { char notes[PATH_MAX]; - u8 build_id[BUILD_ID_SIZE]; + struct build_id bid; int ret; if (!root_dir) @@ -121,25 +122,23 @@ int sysfs__sprintf_build_id(const char *root_dir, char *sbuild_id) scnprintf(notes, sizeof(notes), "%s/sys/kernel/notes", root_dir); - ret = sysfs__read_build_id(notes, build_id, sizeof(build_id)); + ret = sysfs__read_build_id(notes, &bid); if (ret < 0) return ret; - return build_id__sprintf(build_id, sizeof(build_id), sbuild_id); + return build_id__sprintf(&bid, sbuild_id); } int filename__sprintf_build_id(const char *pathname, char *sbuild_id) { - u8 build_id[BUILD_ID_SIZE]; + struct build_id bid; int ret; - ret = filename__read_build_id(pathname, build_id, sizeof(build_id)); + ret = filename__read_build_id(pathname, &bid); if (ret < 0) return ret; - else if (ret != sizeof(build_id)) - return -EINVAL; - return build_id__sprintf(build_id, sizeof(build_id), sbuild_id); + return build_id__sprintf(&bid, sbuild_id); } /* asnprintf consolidates asprintf and snprintf */ @@ -272,7 +271,7 @@ char *dso__build_id_filename(const struct dso *dso, char *bf, size_t size, if (!dso->has_build_id) return NULL; - build_id__sprintf(dso->build_id, sizeof(dso->build_id), sbuild_id); + build_id__sprintf(&dso->bid, sbuild_id); linkname = build_id_cache__linkname(sbuild_id, NULL, 0); if (!linkname) return NULL; @@ -297,7 +296,7 @@ char *dso__build_id_filename(const struct dso *dso, char *bf, size_t size, continue; \ else -static int write_buildid(const char *name, size_t name_len, u8 *build_id, +static int write_buildid(const char *name, size_t name_len, struct build_id *bid, pid_t pid, u16 misc, struct feat_fd *fd) { int err; @@ -308,7 +307,9 @@ static int write_buildid(const char *name, size_t name_len, u8 *build_id, len = PERF_ALIGN(len, NAME_ALIGN); memset(&b, 0, sizeof(b)); - memcpy(&b.build_id, build_id, BUILD_ID_SIZE); + memcpy(&b.data, bid->data, bid->size); + b.size = (u8) bid->size; + misc |= PERF_RECORD_MISC_BUILD_ID_SIZE; b.pid = pid; b.header.misc = misc; b.header.size = sizeof(b) + len; @@ -355,7 +356,7 @@ static int machine__write_buildid_table(struct machine *machine, in_kernel = pos->kernel || is_kernel_module(name, PERF_RECORD_MISC_CPUMODE_UNKNOWN); - err = write_buildid(name, name_len, pos->build_id, machine->pid, + err = write_buildid(name, name_len, &pos->bid, machine->pid, in_kernel ? kmisc : umisc, fd); if (err) break; @@ -769,13 +770,13 @@ out_free: return err; } -static int build_id_cache__add_b(const u8 *build_id, size_t build_id_size, +static int build_id_cache__add_b(const struct build_id *bid, const char *name, struct nsinfo *nsi, bool is_kallsyms, bool is_vdso) { char sbuild_id[SBUILD_ID_SIZE]; - build_id__sprintf(build_id, build_id_size, sbuild_id); + build_id__sprintf(bid, sbuild_id); return build_id_cache__add_s(sbuild_id, name, nsi, is_kallsyms, is_vdso); @@ -841,8 +842,8 @@ static int dso__cache_build_id(struct dso *dso, struct machine *machine) is_kallsyms = true; name = machine->mmap_name; } - return build_id_cache__add_b(dso->build_id, sizeof(dso->build_id), name, - dso->nsinfo, is_kallsyms, is_vdso); + return build_id_cache__add_b(&dso->bid, name, dso->nsinfo, + is_kallsyms, is_vdso); } static int __dsos__cache_build_ids(struct list_head *head, @@ -902,3 +903,10 @@ bool perf_session__read_build_ids(struct perf_session *session, bool with_hits) return ret; } + +void build_id__init(struct build_id *bid, const u8 *data, size_t size) +{ + WARN_ON(size > BUILD_ID_SIZE); + memcpy(bid->data, data, size); + bid->size = size; +} diff --git a/tools/perf/util/build-id.h b/tools/perf/util/build-id.h index aad419bb165c..f293f99d5dba 100644 --- a/tools/perf/util/build-id.h +++ b/tools/perf/util/build-id.h @@ -8,13 +8,19 @@ #include "tool.h" #include <linux/types.h> +struct build_id { + u8 data[BUILD_ID_SIZE]; + size_t size; +}; + struct nsinfo; extern struct perf_tool build_id__mark_dso_hit_ops; struct dso; struct feat_fd; -int build_id__sprintf(const u8 *build_id, int len, char *bf); +void build_id__init(struct build_id *bid, const u8 *data, size_t size); +int build_id__sprintf(const struct build_id *build_id, char *bf); int sysfs__sprintf_build_id(const char *root_dir, char *sbuild_id); int filename__sprintf_build_id(const char *pathname, char *sbuild_id); char *build_id_cache__kallsyms_path(const char *sbuild_id, char *bf, @@ -29,6 +35,10 @@ int build_id__mark_dso_hit(struct perf_tool *tool, union perf_event *event, int dsos__hit_all(struct perf_session *session); +int perf_event__inject_buildid(struct perf_tool *tool, union perf_event *event, + struct perf_sample *sample, struct evsel *evsel, + struct machine *machine); + bool perf_session__read_build_ids(struct perf_session *session, bool with_hits); int perf_session__write_buildid_table(struct perf_session *session, struct feat_fd *fd); diff --git a/tools/perf/util/callchain.c b/tools/perf/util/callchain.c index 2775b752f2fa..1b60985690bb 100644 --- a/tools/perf/util/callchain.c +++ b/tools/perf/util/callchain.c @@ -1613,3 +1613,102 @@ void callchain_param_setup(u64 sample_type) callchain_param.record_mode = CALLCHAIN_FP; } } + +static bool chain_match(struct callchain_list *base_chain, + struct callchain_list *pair_chain) +{ + enum match_result match; + + match = match_chain_strings(base_chain->srcline, + pair_chain->srcline); + if (match != MATCH_ERROR) + return match == MATCH_EQ; + + match = match_chain_dso_addresses(base_chain->ms.map, + base_chain->ip, + pair_chain->ms.map, + pair_chain->ip); + + return match == MATCH_EQ; +} + +bool callchain_cnode_matched(struct callchain_node *base_cnode, + struct callchain_node *pair_cnode) +{ + struct callchain_list *base_chain, *pair_chain; + bool match = false; + + pair_chain = list_first_entry(&pair_cnode->val, + struct callchain_list, + list); + + list_for_each_entry(base_chain, &base_cnode->val, list) { + if (&pair_chain->list == &pair_cnode->val) + return false; + + if (!base_chain->srcline || !pair_chain->srcline) { + pair_chain = list_next_entry(pair_chain, list); + continue; + } + + match = chain_match(base_chain, pair_chain); + if (!match) + return false; + + pair_chain = list_next_entry(pair_chain, list); + } + + /* + * Say chain1 is ABC, chain2 is ABCD, we consider they are + * not fully matched. + */ + if (pair_chain && (&pair_chain->list != &pair_cnode->val)) + return false; + + return match; +} + +static u64 count_callchain_hits(struct hist_entry *he) +{ + struct rb_root *root = &he->sorted_chain; + struct rb_node *rb_node = rb_first(root); + struct callchain_node *node; + u64 chain_hits = 0; + + while (rb_node) { + node = rb_entry(rb_node, struct callchain_node, rb_node); + chain_hits += node->hit; + rb_node = rb_next(rb_node); + } + + return chain_hits; +} + +u64 callchain_total_hits(struct hists *hists) +{ + struct rb_node *next = rb_first_cached(&hists->entries); + u64 chain_hits = 0; + + while (next) { + struct hist_entry *he = rb_entry(next, struct hist_entry, + rb_node); + + chain_hits += count_callchain_hits(he); + next = rb_next(&he->rb_node); + } + + return chain_hits; +} + +s64 callchain_avg_cycles(struct callchain_node *cnode) +{ + struct callchain_list *chain; + s64 cycles = 0; + + list_for_each_entry(chain, &cnode->val, list) { + if (chain->srcline && chain->branch_count) + cycles += chain->cycles_count / chain->branch_count; + } + + return cycles; +} diff --git a/tools/perf/util/callchain.h b/tools/perf/util/callchain.h index fe36a9e5ccd1..5824134f983b 100644 --- a/tools/perf/util/callchain.h +++ b/tools/perf/util/callchain.h @@ -13,6 +13,7 @@ struct ip_callchain; struct map; struct perf_sample; struct thread; +struct hists; #define HELP_PAD "\t\t\t\t" @@ -298,4 +299,12 @@ int callchain_branch_counts(struct callchain_root *root, u64 *abort_count, u64 *cycles_count); void callchain_param_setup(u64 sample_type); + +bool callchain_cnode_matched(struct callchain_node *base_cnode, + struct callchain_node *pair_cnode); + +u64 callchain_total_hits(struct hists *hists); + +s64 callchain_avg_cycles(struct callchain_node *cnode); + #endif /* __PERF_CALLCHAIN_H */ diff --git a/tools/perf/util/cgroup.c b/tools/perf/util/cgroup.c index 050dea9f1e88..b81324a13a2b 100644 --- a/tools/perf/util/cgroup.c +++ b/tools/perf/util/cgroup.c @@ -3,6 +3,9 @@ #include "evsel.h" #include "cgroup.h" #include "evlist.h" +#include "rblist.h" +#include "metricgroup.h" +#include "stat.h" #include <linux/zalloc.h> #include <sys/types.h> #include <sys/stat.h> @@ -48,7 +51,7 @@ static struct cgroup *evlist__find_cgroup(struct evlist *evlist, const char *str return NULL; } -static struct cgroup *cgroup__new(const char *name) +static struct cgroup *cgroup__new(const char *name, bool do_open) { struct cgroup *cgroup = zalloc(sizeof(*cgroup)); @@ -58,9 +61,14 @@ static struct cgroup *cgroup__new(const char *name) cgroup->name = strdup(name); if (!cgroup->name) goto out_err; - cgroup->fd = open_cgroup(name); - if (cgroup->fd == -1) - goto out_free_name; + + if (do_open) { + cgroup->fd = open_cgroup(name); + if (cgroup->fd == -1) + goto out_free_name; + } else { + cgroup->fd = -1; + } } return cgroup; @@ -76,7 +84,7 @@ struct cgroup *evlist__findnew_cgroup(struct evlist *evlist, const char *name) { struct cgroup *cgroup = evlist__find_cgroup(evlist, name); - return cgroup ?: cgroup__new(name); + return cgroup ?: cgroup__new(name, true); } static int add_cgroup(struct evlist *evlist, const char *str) @@ -193,6 +201,103 @@ int parse_cgroups(const struct option *opt, const char *str, return 0; } +int evlist__expand_cgroup(struct evlist *evlist, const char *str, + struct rblist *metric_events, bool open_cgroup) +{ + struct evlist *orig_list, *tmp_list; + struct evsel *pos, *evsel, *leader; + struct rblist orig_metric_events; + struct cgroup *cgrp = NULL; + const char *p, *e, *eos = str + strlen(str); + int ret = -1; + + if (evlist->core.nr_entries == 0) { + fprintf(stderr, "must define events before cgroups\n"); + return -EINVAL; + } + + orig_list = evlist__new(); + tmp_list = evlist__new(); + if (orig_list == NULL || tmp_list == NULL) { + fprintf(stderr, "memory allocation failed\n"); + return -ENOMEM; + } + + /* save original events and init evlist */ + perf_evlist__splice_list_tail(orig_list, &evlist->core.entries); + evlist->core.nr_entries = 0; + + if (metric_events) { + orig_metric_events = *metric_events; + rblist__init(metric_events); + } else { + rblist__init(&orig_metric_events); + } + + for (;;) { + p = strchr(str, ','); + e = p ? p : eos; + + /* allow empty cgroups, i.e., skip */ + if (e - str) { + /* termination added */ + char *name = strndup(str, e - str); + if (!name) + goto out_err; + + cgrp = cgroup__new(name, open_cgroup); + free(name); + if (cgrp == NULL) + goto out_err; + } else { + cgrp = NULL; + } + + leader = NULL; + evlist__for_each_entry(orig_list, pos) { + evsel = evsel__clone(pos); + if (evsel == NULL) + goto out_err; + + cgroup__put(evsel->cgrp); + evsel->cgrp = cgroup__get(cgrp); + + if (evsel__is_group_leader(pos)) + leader = evsel; + evsel->leader = leader; + + evlist__add(tmp_list, evsel); + } + /* cgroup__new() has a refcount, release it here */ + cgroup__put(cgrp); + nr_cgroups++; + + if (metric_events) { + perf_stat__collect_metric_expr(tmp_list); + if (metricgroup__copy_metric_events(tmp_list, cgrp, + metric_events, + &orig_metric_events) < 0) + break; + } + + perf_evlist__splice_list_tail(evlist, &tmp_list->core.entries); + tmp_list->core.nr_entries = 0; + + if (!p) { + ret = 0; + break; + } + str = p+1; + } + +out_err: + evlist__delete(orig_list); + evlist__delete(tmp_list); + rblist__exit(&orig_metric_events); + + return ret; +} + static struct cgroup *__cgroup__findnew(struct rb_root *root, uint64_t id, bool create, const char *path) { diff --git a/tools/perf/util/cgroup.h b/tools/perf/util/cgroup.h index e98d5975fe55..162906f3412a 100644 --- a/tools/perf/util/cgroup.h +++ b/tools/perf/util/cgroup.h @@ -22,8 +22,11 @@ struct cgroup *cgroup__get(struct cgroup *cgroup); void cgroup__put(struct cgroup *cgroup); struct evlist; +struct rblist; struct cgroup *evlist__findnew_cgroup(struct evlist *evlist, const char *name); +int evlist__expand_cgroup(struct evlist *evlist, const char *cgroups, + struct rblist *metric_events, bool open_cgroup); void evlist__set_default_cgroup(struct evlist *evlist, struct cgroup *cgroup); diff --git a/tools/perf/util/config.c b/tools/perf/util/config.c index 20be0504fb95..6969f82843ee 100644 --- a/tools/perf/util/config.c +++ b/tools/perf/util/config.c @@ -489,7 +489,7 @@ int perf_default_config(const char *var, const char *value, return 0; } -static int perf_config_from_file(config_fn_t fn, const char *filename, void *data) +int perf_config_from_file(config_fn_t fn, const char *filename, void *data) { int ret; FILE *f = fopen(filename, "r"); diff --git a/tools/perf/util/config.h b/tools/perf/util/config.h index c10b66dde2f3..8c881e3a3ec3 100644 --- a/tools/perf/util/config.h +++ b/tools/perf/util/config.h @@ -26,6 +26,8 @@ struct perf_config_set { extern const char *config_exclusive_filename; typedef int (*config_fn_t)(const char *, const char *, void *); + +int perf_config_from_file(config_fn_t fn, const char *filename, void *data); int perf_default_config(const char *, const char *, void *); int perf_config(config_fn_t fn, void *); int perf_config_int(int *dest, const char *, const char *); diff --git a/tools/perf/util/dso.c b/tools/perf/util/dso.c index 5a3b4755f0b3..55c11e854fe4 100644 --- a/tools/perf/util/dso.c +++ b/tools/perf/util/dso.c @@ -172,9 +172,7 @@ int dso__read_binary_type_filename(const struct dso *dso, break; } - build_id__sprintf(dso->build_id, - sizeof(dso->build_id), - build_id_hex); + build_id__sprintf(&dso->bid, build_id_hex); len = __symbol__join_symfs(filename, size, "/usr/lib/debug/.build-id/"); snprintf(filename + len, size - len, "%.2s/%s.debug", build_id_hex, build_id_hex + 2); @@ -1328,15 +1326,16 @@ void dso__put(struct dso *dso) dso__delete(dso); } -void dso__set_build_id(struct dso *dso, void *build_id) +void dso__set_build_id(struct dso *dso, struct build_id *bid) { - memcpy(dso->build_id, build_id, sizeof(dso->build_id)); + dso->bid = *bid; dso->has_build_id = 1; } -bool dso__build_id_equal(const struct dso *dso, u8 *build_id) +bool dso__build_id_equal(const struct dso *dso, struct build_id *bid) { - return memcmp(dso->build_id, build_id, sizeof(dso->build_id)) == 0; + return dso->bid.size == bid->size && + memcmp(dso->bid.data, bid->data, dso->bid.size) == 0; } void dso__read_running_kernel_build_id(struct dso *dso, struct machine *machine) @@ -1346,8 +1345,7 @@ void dso__read_running_kernel_build_id(struct dso *dso, struct machine *machine) if (machine__is_default_guest(machine)) return; sprintf(path, "%s/sys/kernel/notes", machine->root_dir); - if (sysfs__read_build_id(path, dso->build_id, - sizeof(dso->build_id)) == 0) + if (sysfs__read_build_id(path, &dso->bid) == 0) dso->has_build_id = true; } @@ -1365,18 +1363,17 @@ int dso__kernel_module_get_build_id(struct dso *dso, "%s/sys/module/%.*s/notes/.note.gnu.build-id", root_dir, (int)strlen(name) - 1, name); - if (sysfs__read_build_id(filename, dso->build_id, - sizeof(dso->build_id)) == 0) + if (sysfs__read_build_id(filename, &dso->bid) == 0) dso->has_build_id = true; return 0; } -size_t dso__fprintf_buildid(struct dso *dso, FILE *fp) +static size_t dso__fprintf_buildid(struct dso *dso, FILE *fp) { char sbuild_id[SBUILD_ID_SIZE]; - build_id__sprintf(dso->build_id, sizeof(dso->build_id), sbuild_id); + build_id__sprintf(&dso->bid, sbuild_id); return fprintf(fp, "%s", sbuild_id); } diff --git a/tools/perf/util/dso.h b/tools/perf/util/dso.h index 8ad17f395a19..d8cb4f5680a4 100644 --- a/tools/perf/util/dso.h +++ b/tools/perf/util/dso.h @@ -176,7 +176,7 @@ struct dso { bool sorted_by_name; bool loaded; u8 rel; - u8 build_id[BUILD_ID_SIZE]; + struct build_id bid; u64 text_offset; const char *short_name; const char *long_name; @@ -260,8 +260,8 @@ bool dso__sorted_by_name(const struct dso *dso); void dso__set_sorted_by_name(struct dso *dso); void dso__sort_by_name(struct dso *dso); -void dso__set_build_id(struct dso *dso, void *build_id); -bool dso__build_id_equal(const struct dso *dso, u8 *build_id); +void dso__set_build_id(struct dso *dso, struct build_id *bid); +bool dso__build_id_equal(const struct dso *dso, struct build_id *bid); void dso__read_running_kernel_build_id(struct dso *dso, struct machine *machine); int dso__kernel_module_get_build_id(struct dso *dso, const char *root_dir); @@ -362,7 +362,6 @@ struct dso *machine__findnew_kernel(struct machine *machine, const char *name, void dso__reset_find_symbol_cache(struct dso *dso); -size_t dso__fprintf_buildid(struct dso *dso, FILE *fp); size_t dso__fprintf_symbols_by_name(struct dso *dso, FILE *fp); size_t dso__fprintf(struct dso *dso, FILE *fp); diff --git a/tools/perf/util/dsos.c b/tools/perf/util/dsos.c index 939471731ea6..183a81d5b2f9 100644 --- a/tools/perf/util/dsos.c +++ b/tools/perf/util/dsos.c @@ -73,8 +73,7 @@ bool __dsos__read_build_ids(struct list_head *head, bool with_hits) continue; } nsinfo__mountns_enter(pos->nsinfo, &nsc); - if (filename__read_build_id(pos->long_name, pos->build_id, - sizeof(pos->build_id)) > 0) { + if (filename__read_build_id(pos->long_name, &pos->bid) > 0) { have_build_id = true; pos->has_build_id = true; } @@ -288,10 +287,12 @@ size_t __dsos__fprintf_buildid(struct list_head *head, FILE *fp, size_t ret = 0; list_for_each_entry(pos, head, node) { + char sbuild_id[SBUILD_ID_SIZE]; + if (skip && skip(pos, parm)) continue; - ret += dso__fprintf_buildid(pos, fp); - ret += fprintf(fp, " %s\n", pos->long_name); + build_id__sprintf(&pos->bid, sbuild_id); + ret += fprintf(fp, "%-40s %s\n", sbuild_id, pos->long_name); } return ret; } diff --git a/tools/perf/util/event.c b/tools/perf/util/event.c index 317a26571845..05616d4138a9 100644 --- a/tools/perf/util/event.c +++ b/tools/perf/util/event.c @@ -398,7 +398,7 @@ size_t perf_event__fprintf_switch(union perf_event *event, FILE *fp) if (event->header.type == PERF_RECORD_SWITCH) return fprintf(fp, " %s\n", in_out); - return fprintf(fp, " %s %s pid/tid: %5u/%-5u\n", + return fprintf(fp, " %s %s pid/tid: %5d/%-5d\n", in_out, out ? "next" : "prev", event->context_switch.next_prev_pid, event->context_switch.next_prev_tid); diff --git a/tools/perf/util/evlist.c b/tools/perf/util/evlist.c index c0768c61eb43..8bdf3d2c907c 100644 --- a/tools/perf/util/evlist.c +++ b/tools/perf/util/evlist.c @@ -1732,6 +1732,91 @@ struct evsel *perf_evlist__reset_weak_group(struct evlist *evsel_list, return leader; } +static int evlist__parse_control_fifo(const char *str, int *ctl_fd, int *ctl_fd_ack, bool *ctl_fd_close) +{ + char *s, *p; + int ret = 0, fd; + + if (strncmp(str, "fifo:", 5)) + return -EINVAL; + + str += 5; + if (!*str || *str == ',') + return -EINVAL; + + s = strdup(str); + if (!s) + return -ENOMEM; + + p = strchr(s, ','); + if (p) + *p = '\0'; + + /* + * O_RDWR avoids POLLHUPs which is necessary to allow the other + * end of a FIFO to be repeatedly opened and closed. + */ + fd = open(s, O_RDWR | O_NONBLOCK | O_CLOEXEC); + if (fd < 0) { + pr_err("Failed to open '%s'\n", s); + ret = -errno; + goto out_free; + } + *ctl_fd = fd; + *ctl_fd_close = true; + + if (p && *++p) { + /* O_RDWR | O_NONBLOCK means the other end need not be open */ + fd = open(p, O_RDWR | O_NONBLOCK | O_CLOEXEC); + if (fd < 0) { + pr_err("Failed to open '%s'\n", p); + ret = -errno; + goto out_free; + } + *ctl_fd_ack = fd; + } + +out_free: + free(s); + return ret; +} + +int evlist__parse_control(const char *str, int *ctl_fd, int *ctl_fd_ack, bool *ctl_fd_close) +{ + char *comma = NULL, *endptr = NULL; + + *ctl_fd_close = false; + + if (strncmp(str, "fd:", 3)) + return evlist__parse_control_fifo(str, ctl_fd, ctl_fd_ack, ctl_fd_close); + + *ctl_fd = strtoul(&str[3], &endptr, 0); + if (endptr == &str[3]) + return -EINVAL; + + comma = strchr(str, ','); + if (comma) { + if (endptr != comma) + return -EINVAL; + + *ctl_fd_ack = strtoul(comma + 1, &endptr, 0); + if (endptr == comma + 1 || *endptr != '\0') + return -EINVAL; + } + + return 0; +} + +void evlist__close_control(int ctl_fd, int ctl_fd_ack, bool *ctl_fd_close) +{ + if (*ctl_fd_close) { + *ctl_fd_close = false; + close(ctl_fd); + if (ctl_fd_ack >= 0) + close(ctl_fd_ack); + } +} + int evlist__initialize_ctlfd(struct evlist *evlist, int fd, int ack) { if (fd == -1) { @@ -1783,6 +1868,7 @@ static int evlist__ctlfd_recv(struct evlist *evlist, enum evlist_ctl_cmd *cmd, char c; size_t bytes_read = 0; + *cmd = EVLIST_CTL_CMD_UNSUPPORTED; memset(cmd_data, 0, data_size); data_size--; @@ -1794,30 +1880,39 @@ static int evlist__ctlfd_recv(struct evlist *evlist, enum evlist_ctl_cmd *cmd, cmd_data[bytes_read++] = c; if (bytes_read == data_size) break; - } else { - if (err == -1) + continue; + } else if (err == -1) { + if (errno == EINTR) + continue; + if (errno == EAGAIN || errno == EWOULDBLOCK) + err = 0; + else pr_err("Failed to read from ctlfd %d: %m\n", evlist->ctl_fd.fd); - break; } + break; } while (1); pr_debug("Message from ctl_fd: \"%s%s\"\n", cmd_data, bytes_read == data_size ? "" : c == '\n' ? "\\n" : "\\0"); - if (err > 0) { + if (bytes_read > 0) { if (!strncmp(cmd_data, EVLIST_CTL_CMD_ENABLE_TAG, (sizeof(EVLIST_CTL_CMD_ENABLE_TAG)-1))) { *cmd = EVLIST_CTL_CMD_ENABLE; } else if (!strncmp(cmd_data, EVLIST_CTL_CMD_DISABLE_TAG, (sizeof(EVLIST_CTL_CMD_DISABLE_TAG)-1))) { *cmd = EVLIST_CTL_CMD_DISABLE; + } else if (!strncmp(cmd_data, EVLIST_CTL_CMD_SNAPSHOT_TAG, + (sizeof(EVLIST_CTL_CMD_SNAPSHOT_TAG)-1))) { + *cmd = EVLIST_CTL_CMD_SNAPSHOT; + pr_debug("is snapshot\n"); } } - return err; + return bytes_read ? (int)bytes_read : err; } -static int evlist__ctlfd_ack(struct evlist *evlist) +int evlist__ctlfd_ack(struct evlist *evlist) { int err; @@ -1853,13 +1948,16 @@ int evlist__ctlfd_process(struct evlist *evlist, enum evlist_ctl_cmd *cmd) case EVLIST_CTL_CMD_DISABLE: evlist__disable(evlist); break; + case EVLIST_CTL_CMD_SNAPSHOT: + break; case EVLIST_CTL_CMD_ACK: case EVLIST_CTL_CMD_UNSUPPORTED: default: pr_debug("ctlfd: unsupported %d\n", *cmd); break; } - if (!(*cmd == EVLIST_CTL_CMD_ACK || *cmd == EVLIST_CTL_CMD_UNSUPPORTED)) + if (!(*cmd == EVLIST_CTL_CMD_ACK || *cmd == EVLIST_CTL_CMD_UNSUPPORTED || + *cmd == EVLIST_CTL_CMD_SNAPSHOT)) evlist__ctlfd_ack(evlist); } } @@ -1871,3 +1969,14 @@ int evlist__ctlfd_process(struct evlist *evlist, enum evlist_ctl_cmd *cmd) return err; } + +struct evsel *evlist__find_evsel(struct evlist *evlist, int idx) +{ + struct evsel *evsel; + + evlist__for_each_entry(evlist, evsel) { + if (evsel->idx == idx) + return evsel; + } + return NULL; +} diff --git a/tools/perf/util/evlist.h b/tools/perf/util/evlist.h index c73f7f7f120b..e1a450322bc5 100644 --- a/tools/perf/util/evlist.h +++ b/tools/perf/util/evlist.h @@ -363,6 +363,7 @@ struct evsel *perf_evlist__reset_weak_group(struct evlist *evlist, #define EVLIST_CTL_CMD_ENABLE_TAG "enable" #define EVLIST_CTL_CMD_DISABLE_TAG "disable" #define EVLIST_CTL_CMD_ACK_TAG "ack\n" +#define EVLIST_CTL_CMD_SNAPSHOT_TAG "snapshot" #define EVLIST_CTL_CMD_MAX_LEN 64 @@ -370,15 +371,20 @@ enum evlist_ctl_cmd { EVLIST_CTL_CMD_UNSUPPORTED = 0, EVLIST_CTL_CMD_ENABLE, EVLIST_CTL_CMD_DISABLE, - EVLIST_CTL_CMD_ACK + EVLIST_CTL_CMD_ACK, + EVLIST_CTL_CMD_SNAPSHOT, }; +int evlist__parse_control(const char *str, int *ctl_fd, int *ctl_fd_ack, bool *ctl_fd_close); +void evlist__close_control(int ctl_fd, int ctl_fd_ack, bool *ctl_fd_close); int evlist__initialize_ctlfd(struct evlist *evlist, int ctl_fd, int ctl_fd_ack); int evlist__finalize_ctlfd(struct evlist *evlist); bool evlist__ctlfd_initialized(struct evlist *evlist); int evlist__ctlfd_process(struct evlist *evlist, enum evlist_ctl_cmd *cmd); +int evlist__ctlfd_ack(struct evlist *evlist); #define EVLIST_ENABLED_MSG "Events enabled\n" #define EVLIST_DISABLED_MSG "Events disabled\n" +struct evsel *evlist__find_evsel(struct evlist *evlist, int idx); #endif /* __PERF_EVLIST_H */ diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c index 459b51e90063..1cad6051d8b0 100644 --- a/tools/perf/util/evsel.c +++ b/tools/perf/util/evsel.c @@ -331,6 +331,110 @@ error_free: goto out; } +static int evsel__copy_config_terms(struct evsel *dst, struct evsel *src) +{ + struct evsel_config_term *pos, *tmp; + + list_for_each_entry(pos, &src->config_terms, list) { + tmp = malloc(sizeof(*tmp)); + if (tmp == NULL) + return -ENOMEM; + + *tmp = *pos; + if (tmp->free_str) { + tmp->val.str = strdup(pos->val.str); + if (tmp->val.str == NULL) { + free(tmp); + return -ENOMEM; + } + } + list_add_tail(&tmp->list, &dst->config_terms); + } + return 0; +} + +/** + * evsel__clone - create a new evsel copied from @orig + * @orig: original evsel + * + * The assumption is that @orig is not configured nor opened yet. + * So we only care about the attributes that can be set while it's parsed. + */ +struct evsel *evsel__clone(struct evsel *orig) +{ + struct evsel *evsel; + + BUG_ON(orig->core.fd); + BUG_ON(orig->counts); + BUG_ON(orig->priv); + BUG_ON(orig->per_pkg_mask); + + /* cannot handle BPF objects for now */ + if (orig->bpf_obj) + return NULL; + + evsel = evsel__new(&orig->core.attr); + if (evsel == NULL) + return NULL; + + evsel->core.cpus = perf_cpu_map__get(orig->core.cpus); + evsel->core.own_cpus = perf_cpu_map__get(orig->core.own_cpus); + evsel->core.threads = perf_thread_map__get(orig->core.threads); + evsel->core.nr_members = orig->core.nr_members; + evsel->core.system_wide = orig->core.system_wide; + + if (orig->name) { + evsel->name = strdup(orig->name); + if (evsel->name == NULL) + goto out_err; + } + if (orig->group_name) { + evsel->group_name = strdup(orig->group_name); + if (evsel->group_name == NULL) + goto out_err; + } + if (orig->pmu_name) { + evsel->pmu_name = strdup(orig->pmu_name); + if (evsel->pmu_name == NULL) + goto out_err; + } + if (orig->filter) { + evsel->filter = strdup(orig->filter); + if (evsel->filter == NULL) + goto out_err; + } + evsel->cgrp = cgroup__get(orig->cgrp); + evsel->tp_format = orig->tp_format; + evsel->handler = orig->handler; + evsel->leader = orig->leader; + + evsel->max_events = orig->max_events; + evsel->tool_event = orig->tool_event; + evsel->unit = orig->unit; + evsel->scale = orig->scale; + evsel->snapshot = orig->snapshot; + evsel->per_pkg = orig->per_pkg; + evsel->percore = orig->percore; + evsel->precise_max = orig->precise_max; + evsel->use_uncore_alias = orig->use_uncore_alias; + evsel->is_libpfm_event = orig->is_libpfm_event; + + evsel->exclude_GH = orig->exclude_GH; + evsel->sample_read = orig->sample_read; + evsel->auto_merge_stats = orig->auto_merge_stats; + evsel->collect_stat = orig->collect_stat; + evsel->weak_group = orig->weak_group; + + if (evsel__copy_config_terms(evsel, orig) < 0) + goto out_err; + + return evsel; + +out_err: + evsel__delete(evsel); + return NULL; +} + /* * Returns pointer with encoded error via <linux/err.h> interface. */ @@ -1684,6 +1788,11 @@ retry_open: FD(evsel, cpu, thread) = fd; + if (unlikely(test_attr__enabled)) { + test_attr__open(&evsel->core.attr, pid, cpus->map[cpu], + fd, group_fd, flags); + } + if (fd < 0) { err = -errno; diff --git a/tools/perf/util/evsel.h b/tools/perf/util/evsel.h index 35e3f6d66085..79a860d8e3ee 100644 --- a/tools/perf/util/evsel.h +++ b/tools/perf/util/evsel.h @@ -42,65 +42,79 @@ enum perf_tool_event { */ struct evsel { struct perf_evsel core; - struct evlist *evlist; - char *filter; + struct evlist *evlist; + off_t id_offset; + int idx; + int id_pos; + int is_pos; + unsigned int sample_size; + + /* + * These fields can be set in the parse-events code or similar. + * Please check evsel__clone() to copy them properly so that + * they can be released properly. + */ + struct { + char *name; + char *group_name; + const char *pmu_name; + struct tep_event *tp_format; + char *filter; + unsigned long max_events; + double scale; + const char *unit; + struct cgroup *cgrp; + enum perf_tool_event tool_event; + /* parse modifier helper */ + int exclude_GH; + int sample_read; + bool snapshot; + bool per_pkg; + bool percore; + bool precise_max; + bool use_uncore_alias; + bool is_libpfm_event; + bool auto_merge_stats; + bool collect_stat; + bool weak_group; + int bpf_fd; + struct bpf_object *bpf_obj; + }; + + /* + * metric fields are similar, but needs more care as they can have + * references to other metric (evsel). + */ + const char * metric_expr; + const char * metric_name; + struct evsel **metric_events; + struct evsel *metric_leader; + + void *handler; struct perf_counts *counts; struct perf_counts *prev_raw_counts; - int idx; - unsigned long max_events; unsigned long nr_events_printed; - char *name; - double scale; - const char *unit; - struct tep_event *tp_format; - off_t id_offset; struct perf_stat_evsel *stats; void *priv; u64 db_id; - struct cgroup *cgrp; - void *handler; - unsigned int sample_size; - int id_pos; - int is_pos; - enum perf_tool_event tool_event; bool uniquified_name; - bool snapshot; bool supported; bool needs_swap; bool disabled; bool no_aux_samples; bool immediate; bool tracking; - bool per_pkg; - bool precise_max; bool ignore_missing_thread; bool forced_leader; - bool use_uncore_alias; - bool is_libpfm_event; - /* parse modifier helper */ - int exclude_GH; - int sample_read; - unsigned long *per_pkg_mask; - struct evsel *leader; - char *group_name; bool cmdline_group_boundary; - struct list_head config_terms; - struct bpf_object *bpf_obj; - int bpf_fd; - int err; - bool auto_merge_stats; bool merged_stat; - const char * metric_expr; - const char * metric_name; - struct evsel **metric_events; - struct evsel *metric_leader; - bool collect_stat; - bool weak_group; bool reset_group; bool errored; - bool percore; + unsigned long *per_pkg_mask; + struct evsel *leader; + struct list_head config_terms; + int err; int cpu_iter; - const char *pmu_name; struct { evsel__sb_cb_t *cb; void *data; @@ -169,6 +183,7 @@ static inline struct evsel *evsel__new(struct perf_event_attr *attr) return evsel__new_idx(attr, 0); } +struct evsel *evsel__clone(struct evsel *orig); struct evsel *evsel__newtp_idx(const char *sys, const char *name, int idx); /* diff --git a/tools/perf/util/group.h b/tools/perf/util/group.h deleted file mode 100644 index f36c7e31780a..000000000000 --- a/tools/perf/util/group.h +++ /dev/null @@ -1,8 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef GROUP_H -#define GROUP_H 1 - -bool arch_topdown_check_group(bool *warn); -void arch_topdown_group_warn(void); - -#endif diff --git a/tools/perf/util/header.c b/tools/perf/util/header.c index 9cf4efdcbbbd..be850e9f8852 100644 --- a/tools/perf/util/header.c +++ b/tools/perf/util/header.c @@ -2082,8 +2082,14 @@ static int __event_process_build_id(struct perf_record_header_build_id *bev, dso = machine__findnew_dso(machine, filename); if (dso != NULL) { char sbuild_id[SBUILD_ID_SIZE]; + struct build_id bid; + size_t size = BUILD_ID_SIZE; - dso__set_build_id(dso, &bev->build_id); + if (bev->header.misc & PERF_RECORD_MISC_BUILD_ID_SIZE) + size = bev->size; + + build_id__init(&bid, bev->data, size); + dso__set_build_id(dso, &bid); if (dso_space != DSO_SPACE__USER) { struct kmod_path m = { .name = NULL, }; @@ -2095,10 +2101,9 @@ static int __event_process_build_id(struct perf_record_header_build_id *bev, free(m.name); } - build_id__sprintf(dso->build_id, sizeof(dso->build_id), - sbuild_id); - pr_debug("build id event received for %s: %s\n", - dso->long_name, sbuild_id); + build_id__sprintf(&dso->bid, sbuild_id); + pr_debug("build id event received for %s: %s [%zu]\n", + dso->long_name, sbuild_id, size); dso__put(dso); } diff --git a/tools/perf/util/intel-pt.c b/tools/perf/util/intel-pt.c index 0af4e81c46e2..3a0348caec7d 100644 --- a/tools/perf/util/intel-pt.c +++ b/tools/perf/util/intel-pt.c @@ -1101,6 +1101,8 @@ static void intel_pt_set_pid_tid_cpu(struct intel_pt *pt, if (queue->tid == -1 || pt->have_sched_switch) { ptq->tid = machine__get_current_tid(pt->machine, ptq->cpu); + if (ptq->tid == -1) + ptq->pid = -1; thread__zput(ptq->thread); } @@ -2603,10 +2605,8 @@ static int intel_pt_context_switch(struct intel_pt *pt, union perf_event *event, tid = sample->tid; } - if (tid == -1) { - pr_err("context_switch event has no tid\n"); - return -EINVAL; - } + if (tid == -1) + intel_pt_log("context_switch event has no tid\n"); ret = intel_pt_sync_switch(pt, cpu, tid, sample->time); if (ret <= 0) diff --git a/tools/perf/util/jitdump.c b/tools/perf/util/jitdump.c index 0804308ef285..055bab7a92b3 100644 --- a/tools/perf/util/jitdump.c +++ b/tools/perf/util/jitdump.c @@ -374,11 +374,15 @@ static uint64_t convert_timestamp(struct jit_buf_desc *jd, uint64_t timestamp) if (!jd->use_arch_timestamp) return timestamp; - tc.time_shift = jd->session->time_conv.time_shift; - tc.time_mult = jd->session->time_conv.time_mult; - tc.time_zero = jd->session->time_conv.time_zero; - - if (!tc.time_mult) + tc.time_shift = jd->session->time_conv.time_shift; + tc.time_mult = jd->session->time_conv.time_mult; + tc.time_zero = jd->session->time_conv.time_zero; + tc.time_cycles = jd->session->time_conv.time_cycles; + tc.time_mask = jd->session->time_conv.time_mask; + tc.cap_user_time_zero = jd->session->time_conv.cap_user_time_zero; + tc.cap_user_time_short = jd->session->time_conv.cap_user_time_short; + + if (!tc.cap_user_time_zero) return 0; return tsc_to_perf_time(timestamp, &tc); diff --git a/tools/perf/util/machine.c b/tools/perf/util/machine.c index 85587de027a5..7d4194ffc5b0 100644 --- a/tools/perf/util/machine.c +++ b/tools/perf/util/machine.c @@ -3100,3 +3100,15 @@ char *machine__resolve_kernel_addr(void *vmachine, unsigned long long *addrp, ch *addrp = map->unmap_ip(map, sym->start); return sym->name; } + +int machine__for_each_dso(struct machine *machine, machine__dso_t fn, void *priv) +{ + struct dso *pos; + int err = 0; + + list_for_each_entry(pos, &machine->dsos.head, node) { + if (fn(pos, machine, priv)) + err = -1; + } + return err; +} diff --git a/tools/perf/util/machine.h b/tools/perf/util/machine.h index 062c36a8433c..26368d3c1754 100644 --- a/tools/perf/util/machine.h +++ b/tools/perf/util/machine.h @@ -250,6 +250,10 @@ void machines__destroy_kernel_maps(struct machines *machines); size_t machine__fprintf_vmlinux_path(struct machine *machine, FILE *fp); +typedef int (*machine__dso_t)(struct dso *dso, struct machine *machine, void *priv); + +int machine__for_each_dso(struct machine *machine, machine__dso_t fn, + void *priv); int machine__for_each_thread(struct machine *machine, int (*fn)(struct thread *thread, void *p), void *priv); diff --git a/tools/perf/util/map.c b/tools/perf/util/map.c index cc0faf8f1321..f44ede437dc7 100644 --- a/tools/perf/util/map.c +++ b/tools/perf/util/map.c @@ -27,21 +27,6 @@ static void __maps__insert(struct maps *maps, struct map *map); -static inline int is_anon_memory(const char *filename, u32 flags) -{ - return flags & MAP_HUGETLB || - !strcmp(filename, "//anon") || - !strncmp(filename, "/dev/zero", sizeof("/dev/zero") - 1) || - !strncmp(filename, "/anon_hugepage", sizeof("/anon_hugepage") - 1); -} - -static inline int is_no_dso_memory(const char *filename) -{ - return !strncmp(filename, "[stack", 6) || - !strncmp(filename, "/SYSV",5) || - !strcmp(filename, "[heap]"); -} - static inline int is_android_lib(const char *filename) { return strstarts(filename, "/data/app-lib/") || @@ -158,7 +143,7 @@ struct map *map__new(struct machine *machine, u64 start, u64 len, int anon, no_dso, vdso, android; android = is_android_lib(filename); - anon = is_anon_memory(filename, flags); + anon = is_anon_memory(filename) || flags & MAP_HUGETLB; vdso = is_vdso_map(filename); no_dso = is_no_dso_memory(filename); map->prot = prot; @@ -346,9 +331,7 @@ int map__load(struct map *map) if (map->dso->has_build_id) { char sbuild_id[SBUILD_ID_SIZE]; - build_id__sprintf(map->dso->build_id, - sizeof(map->dso->build_id), - sbuild_id); + build_id__sprintf(&map->dso->bid, sbuild_id); pr_debug("%s with build id %s not found", name, sbuild_id); } else pr_debug("Failed to open %s", name); diff --git a/tools/perf/util/map.h b/tools/perf/util/map.h index c2f5d28fe73a..b1c0686db1b7 100644 --- a/tools/perf/util/map.h +++ b/tools/perf/util/map.h @@ -171,4 +171,18 @@ static inline bool is_bpf_image(const char *name) return strncmp(name, "bpf_trampoline_", sizeof("bpf_trampoline_") - 1) == 0 || strncmp(name, "bpf_dispatcher_", sizeof("bpf_dispatcher_") - 1) == 0; } + +static inline int is_anon_memory(const char *filename) +{ + return !strcmp(filename, "//anon") || + !strncmp(filename, "/dev/zero", sizeof("/dev/zero") - 1) || + !strncmp(filename, "/anon_hugepage", sizeof("/anon_hugepage") - 1); +} + +static inline int is_no_dso_memory(const char *filename) +{ + return !strncmp(filename, "[stack", 6) || + !strncmp(filename, "/SYSV", 5) || + !strcmp(filename, "[heap]"); +} #endif /* __PERF_MAP_H */ diff --git a/tools/perf/util/metricgroup.c b/tools/perf/util/metricgroup.c index ab5030fcfed4..060454a17293 100644 --- a/tools/perf/util/metricgroup.c +++ b/tools/perf/util/metricgroup.c @@ -15,7 +15,6 @@ #include "rblist.h" #include <string.h> #include <errno.h> -#include "pmu-events/pmu-events.h" #include "strlist.h" #include <assert.h> #include <linux/ctype.h> @@ -25,6 +24,7 @@ #include <api/fs/fs.h> #include "util.h" #include <asm/bug.h> +#include "cgroup.h" struct metric_event *metricgroup__lookup(struct rblist *metric_events, struct evsel *evsel, @@ -150,8 +150,20 @@ static void expr_ids__exit(struct expr_ids *ids) free(ids->id[i].id); } +static bool contains_event(struct evsel **metric_events, int num_events, + const char *event_name) +{ + int i; + + for (i = 0; i < num_events; i++) { + if (!strcmp(metric_events[i]->name, event_name)) + return true; + } + return false; +} + /** - * Find a group of events in perf_evlist that correpond to those from a parsed + * Find a group of events in perf_evlist that correspond to those from a parsed * metric expression. Note, as find_evsel_group is called in the same order as * perf_evlist was constructed, metric_no_merge doesn't need to test for * underfilling a group. @@ -180,7 +192,11 @@ static struct evsel *find_evsel_group(struct evlist *perf_evlist, int i = 0, matched_events = 0, events_to_match; const int idnum = (int)hashmap__size(&pctx->ids); - /* duration_time is grouped separately. */ + /* + * duration_time is always grouped separately, when events are grouped + * (ie has_constraint is false) then ignore it in the matching loop and + * add it to metric_events at the end. + */ if (!has_constraint && hashmap__find(&pctx->ids, "duration_time", (void **)&val_ptr)) events_to_match = idnum - 1; @@ -207,23 +223,20 @@ static struct evsel *find_evsel_group(struct evlist *perf_evlist, sizeof(struct evsel *) * idnum); current_leader = ev->leader; } - if (hashmap__find(&pctx->ids, ev->name, (void **)&val_ptr)) { - if (has_constraint) { - /* - * Events aren't grouped, ensure the same event - * isn't matched from two groups. - */ - for (i = 0; i < matched_events; i++) { - if (!strcmp(ev->name, - metric_events[i]->name)) { - break; - } - } - if (i != matched_events) - continue; - } + /* + * Check for duplicate events with the same name. For example, + * uncore_imc/cas_count_read/ will turn into 6 events per socket + * on skylakex. Only the first such event is placed in + * metric_events. If events aren't grouped then this also + * ensures that the same event in different sibling groups + * aren't both added to metric_events. + */ + if (contains_event(metric_events, matched_events, ev->name)) + continue; + /* Does this event belong to the parse context? */ + if (hashmap__find(&pctx->ids, ev->name, (void **)&val_ptr)) metric_events[matched_events++] = ev; - } + if (matched_events == events_to_match) break; } @@ -239,7 +252,7 @@ static struct evsel *find_evsel_group(struct evlist *perf_evlist, } if (matched_events != idnum) { - /* Not whole match */ + /* Not a whole match */ return NULL; } @@ -247,8 +260,32 @@ static struct evsel *find_evsel_group(struct evlist *perf_evlist, for (i = 0; i < idnum; i++) { ev = metric_events[i]; - ev->metric_leader = ev; + /* Don't free the used events. */ set_bit(ev->idx, evlist_used); + /* + * The metric leader points to the identically named event in + * metric_events. + */ + ev->metric_leader = ev; + /* + * Mark two events with identical names in the same group (or + * globally) as being in use as uncore events may be duplicated + * for each pmu. Set the metric leader of such events to be the + * event that appears in metric_events. + */ + evlist__for_each_entry_continue(perf_evlist, ev) { + /* + * If events are grouped then the search can terminate + * when then group is left. + */ + if (!has_constraint && + ev->leader != metric_events[i]->leader) + break; + if (!strcmp(metric_events[i]->name, ev->name)) { + set_bit(ev->idx, evlist_used); + ev->metric_leader = metric_events[i]; + } + } } return metric_events[0]; @@ -540,10 +577,12 @@ void metricgroup__print(bool metrics, bool metricgroups, char *filter, } } - if (metricgroups && !raw) - printf("\nMetric Groups:\n\n"); - else if (metrics && !raw) - printf("\nMetrics:\n\n"); + if (!filter || !rblist__empty(&groups)) { + if (metricgroups && !raw) + printf("\nMetric Groups:\n\n"); + else if (metrics && !raw) + printf("\nMetrics:\n\n"); + } for (node = rb_first_cached(&groups.entries); node; node = next) { struct mep *me = container_of(node, struct mep, nd); @@ -639,7 +678,7 @@ static bool metricgroup__has_constraint(struct pmu_event *pe) return false; } -int __weak arch_get_runtimeparam(void) +int __weak arch_get_runtimeparam(struct pmu_event *pe __maybe_unused) { return 1; } @@ -910,7 +949,7 @@ static int add_metric(struct list_head *metric_list, } else { int j, count; - count = arch_get_runtimeparam(); + count = arch_get_runtimeparam(pe); /* This loop is added to create multiple * events depend on count value and add @@ -1119,3 +1158,87 @@ bool metricgroup__has_metric(const char *metric) } return false; } + +int metricgroup__copy_metric_events(struct evlist *evlist, struct cgroup *cgrp, + struct rblist *new_metric_events, + struct rblist *old_metric_events) +{ + unsigned i; + + for (i = 0; i < rblist__nr_entries(old_metric_events); i++) { + struct rb_node *nd; + struct metric_event *old_me, *new_me; + struct metric_expr *old_expr, *new_expr; + struct evsel *evsel; + size_t alloc_size; + int idx, nr; + + nd = rblist__entry(old_metric_events, i); + old_me = container_of(nd, struct metric_event, nd); + + evsel = evlist__find_evsel(evlist, old_me->evsel->idx); + if (!evsel) + return -EINVAL; + new_me = metricgroup__lookup(new_metric_events, evsel, true); + if (!new_me) + return -ENOMEM; + + pr_debug("copying metric event for cgroup '%s': %s (idx=%d)\n", + cgrp ? cgrp->name : "root", evsel->name, evsel->idx); + + list_for_each_entry(old_expr, &old_me->head, nd) { + new_expr = malloc(sizeof(*new_expr)); + if (!new_expr) + return -ENOMEM; + + new_expr->metric_expr = old_expr->metric_expr; + new_expr->metric_name = old_expr->metric_name; + new_expr->metric_unit = old_expr->metric_unit; + new_expr->runtime = old_expr->runtime; + + if (old_expr->metric_refs) { + /* calculate number of metric_events */ + for (nr = 0; old_expr->metric_refs[nr].metric_name; nr++) + continue; + alloc_size = sizeof(*new_expr->metric_refs); + new_expr->metric_refs = calloc(nr + 1, alloc_size); + if (!new_expr->metric_refs) { + free(new_expr); + return -ENOMEM; + } + + memcpy(new_expr->metric_refs, old_expr->metric_refs, + nr * alloc_size); + } else { + new_expr->metric_refs = NULL; + } + + /* calculate number of metric_events */ + for (nr = 0; old_expr->metric_events[nr]; nr++) + continue; + alloc_size = sizeof(*new_expr->metric_events); + new_expr->metric_events = calloc(nr + 1, alloc_size); + if (!new_expr->metric_events) { + free(new_expr->metric_refs); + free(new_expr); + return -ENOMEM; + } + + /* copy evsel in the same position */ + for (idx = 0; idx < nr; idx++) { + evsel = old_expr->metric_events[idx]; + evsel = evlist__find_evsel(evlist, evsel->idx); + if (evsel == NULL) { + free(new_expr->metric_events); + free(new_expr->metric_refs); + free(new_expr); + return -EINVAL; + } + new_expr->metric_events[idx] = evsel; + } + + list_add(&new_expr->nd, &new_me->head); + } + } + return 0; +} diff --git a/tools/perf/util/metricgroup.h b/tools/perf/util/metricgroup.h index 62623a39cbec..ed1b9392e624 100644 --- a/tools/perf/util/metricgroup.h +++ b/tools/perf/util/metricgroup.h @@ -5,12 +5,15 @@ #include <linux/list.h> #include <linux/rbtree.h> #include <stdbool.h> +#include "pmu-events/pmu-events.h" +struct evlist; struct evsel; struct evlist; struct option; struct rblist; struct pmu_events_map; +struct cgroup; struct metric_event { struct rb_node nd; @@ -52,6 +55,10 @@ int metricgroup__parse_groups_test(struct evlist *evlist, void metricgroup__print(bool metrics, bool groups, char *filter, bool raw, bool details); bool metricgroup__has_metric(const char *metric); -int arch_get_runtimeparam(void); +int arch_get_runtimeparam(struct pmu_event *pe __maybe_unused); void metricgroup__rblist_exit(struct rblist *metric_events); + +int metricgroup__copy_metric_events(struct evlist *evlist, struct cgroup *cgrp, + struct rblist *new_metric_events, + struct rblist *old_metric_events); #endif diff --git a/tools/perf/util/parse-events.c b/tools/perf/util/parse-events.c index 667cbca1547a..3b273580fb84 100644 --- a/tools/perf/util/parse-events.c +++ b/tools/perf/util/parse-events.c @@ -353,18 +353,20 @@ __add_event(struct list_head *list, int *idx, const char *cpu_list) { struct evsel *evsel; - struct perf_cpu_map *cpus = pmu ? pmu->cpus : + struct perf_cpu_map *cpus = pmu ? perf_cpu_map__get(pmu->cpus) : cpu_list ? perf_cpu_map__new(cpu_list) : NULL; if (init_attr) event_attr_init(attr); evsel = evsel__new_idx(attr, *idx); - if (!evsel) + if (!evsel) { + perf_cpu_map__put(cpus); return NULL; + } (*idx)++; - evsel->core.cpus = perf_cpu_map__get(cpus); + evsel->core.cpus = cpus; evsel->core.own_cpus = perf_cpu_map__get(cpus); evsel->core.system_wide = pmu ? pmu->is_uncore : false; evsel->auto_merge_stats = auto_merge_stats; @@ -940,12 +942,12 @@ do { \ } int parse_events_add_breakpoint(struct list_head *list, int *idx, - void *ptr, char *type, u64 len) + u64 addr, char *type, u64 len) { struct perf_event_attr attr; memset(&attr, 0, sizeof(attr)); - attr.bp_addr = (unsigned long) ptr; + attr.bp_addr = addr; if (parse_breakpoint_type(type, &attr)) return -EINVAL; @@ -1773,6 +1775,7 @@ struct event_modifier { int sample_read; int pinned; int weak; + int exclusive; }; static int get_event_modifier(struct event_modifier *mod, char *str, @@ -1788,6 +1791,7 @@ static int get_event_modifier(struct event_modifier *mod, char *str, int precise_max = 0; int sample_read = 0; int pinned = evsel ? evsel->core.attr.pinned : 0; + int exclusive = evsel ? evsel->core.attr.exclusive : 0; int exclude = eu | ek | eh; int exclude_GH = evsel ? evsel->exclude_GH : 0; @@ -1831,6 +1835,8 @@ static int get_event_modifier(struct event_modifier *mod, char *str, sample_read = 1; } else if (*str == 'D') { pinned = 1; + } else if (*str == 'e') { + exclusive = 1; } else if (*str == 'W') { weak = 1; } else @@ -1864,6 +1870,7 @@ static int get_event_modifier(struct event_modifier *mod, char *str, mod->sample_read = sample_read; mod->pinned = pinned; mod->weak = weak; + mod->exclusive = exclusive; return 0; } @@ -1877,7 +1884,7 @@ static int check_modifier(char *str) char *p = str; /* The sizeof includes 0 byte as well. */ - if (strlen(str) > (sizeof("ukhGHpppPSDIW") - 1)) + if (strlen(str) > (sizeof("ukhGHpppPSDIWe") - 1)) return -1; while (*p) { @@ -1919,8 +1926,10 @@ int parse_events__modifier_event(struct list_head *list, char *str, bool add) evsel->precise_max = mod.precise_max; evsel->weak_group = mod.weak; - if (evsel__is_group_leader(evsel)) + if (evsel__is_group_leader(evsel)) { evsel->core.attr.pinned = mod.pinned; + evsel->core.attr.exclusive = mod.exclusive; + } } return 0; diff --git a/tools/perf/util/parse-events.h b/tools/perf/util/parse-events.h index 00cde7d2e30c..e80c9b74f2f2 100644 --- a/tools/perf/util/parse-events.h +++ b/tools/perf/util/parse-events.h @@ -190,7 +190,7 @@ int parse_events_add_cache(struct list_head *list, int *idx, struct parse_events_error *error, struct list_head *head_config); int parse_events_add_breakpoint(struct list_head *list, int *idx, - void *ptr, char *type, u64 len); + u64 addr, char *type, u64 len); int parse_events_add_pmu(struct parse_events_state *parse_state, struct list_head *list, char *name, struct list_head *head_config, diff --git a/tools/perf/util/parse-events.l b/tools/perf/util/parse-events.l index 3ca5fd2829ca..9db5097317f4 100644 --- a/tools/perf/util/parse-events.l +++ b/tools/perf/util/parse-events.l @@ -210,7 +210,7 @@ name_tag [\'][a-zA-Z_*?\[\]][a-zA-Z0-9_*?\-,\.\[\]:=]*[\'] name_minus [a-zA-Z_*?][a-zA-Z0-9\-_*?.:]* drv_cfg_term [a-zA-Z0-9_\.]+(=[a-zA-Z0-9_*?\.:]+)? /* If you add a modifier you need to update check_modifier() */ -modifier_event [ukhpPGHSDIW]+ +modifier_event [ukhpPGHSDIWe]+ modifier_bp [rwx]{1,3} %% diff --git a/tools/perf/util/parse-events.y b/tools/perf/util/parse-events.y index 645bf4f1859f..d5b6aff82f21 100644 --- a/tools/perf/util/parse-events.y +++ b/tools/perf/util/parse-events.y @@ -511,7 +511,7 @@ PE_PREFIX_MEM PE_VALUE '/' PE_VALUE ':' PE_MODIFIER_BP sep_dc list = alloc_list(); ABORT_ON(!list); err = parse_events_add_breakpoint(list, &parse_state->idx, - (void *)(uintptr_t) $2, $6, $4); + $2, $6, $4); free($6); if (err) { free(list); @@ -528,7 +528,7 @@ PE_PREFIX_MEM PE_VALUE '/' PE_VALUE sep_dc list = alloc_list(); ABORT_ON(!list); if (parse_events_add_breakpoint(list, &parse_state->idx, - (void *)(uintptr_t) $2, NULL, $4)) { + $2, NULL, $4)) { free(list); YYABORT; } @@ -544,7 +544,7 @@ PE_PREFIX_MEM PE_VALUE ':' PE_MODIFIER_BP sep_dc list = alloc_list(); ABORT_ON(!list); err = parse_events_add_breakpoint(list, &parse_state->idx, - (void *)(uintptr_t) $2, $4, 0); + $2, $4, 0); free($4); if (err) { free(list); @@ -561,7 +561,7 @@ PE_PREFIX_MEM PE_VALUE sep_dc list = alloc_list(); ABORT_ON(!list); if (parse_events_add_breakpoint(list, &parse_state->idx, - (void *)(uintptr_t) $2, NULL, 0)) { + $2, NULL, 0)) { free(list); YYABORT; } diff --git a/tools/perf/util/print_binary.c b/tools/perf/util/print_binary.c index 599a1543871d..13fdc51c61d9 100644 --- a/tools/perf/util/print_binary.c +++ b/tools/perf/util/print_binary.c @@ -50,7 +50,7 @@ int is_printable_array(char *p, unsigned int len) len--; - for (i = 0; i < len; i++) { + for (i = 0; i < len && p[i]; i++) { if (!isprint(p[i]) && !isspace(p[i])) return 0; } diff --git a/tools/perf/util/probe-event.c b/tools/perf/util/probe-event.c index 99d36ac77c08..8eae2afff71a 100644 --- a/tools/perf/util/probe-event.c +++ b/tools/perf/util/probe-event.c @@ -43,6 +43,10 @@ #include <linux/ctype.h> #include <linux/zalloc.h> +#ifdef HAVE_DEBUGINFOD_SUPPORT +#include <elfutils/debuginfod.h> +#endif + #define PERFPROBE_GROUP "probe" bool probe_event_dry_run; /* Dry run flag */ @@ -129,9 +133,10 @@ static int kernel_get_symbol_address_by_name(const char *name, u64 *addr, struct map *map; /* ref_reloc_sym is just a label. Need a special fix*/ - reloc_sym = kernel_get_ref_reloc_sym(NULL); + reloc_sym = kernel_get_ref_reloc_sym(&map); if (reloc_sym && strcmp(name, reloc_sym->name) == 0) - *addr = (reloc) ? reloc_sym->addr : reloc_sym->unrelocated_addr; + *addr = (!map->reloc || reloc) ? reloc_sym->addr : + reloc_sym->unrelocated_addr; else { sym = machine__find_kernel_symbol_by_name(host_machine, name, &map); if (!sym) @@ -337,6 +342,8 @@ static int kernel_get_module_dso(const char *module, struct dso **pdso) map = machine__kernel_map(host_machine); dso = map->dso; + if (!dso->has_build_id) + dso__read_running_kernel_build_id(dso, host_machine); vmlinux_name = symbol_conf.vmlinux_name; dso->load_errno = 0; @@ -452,6 +459,49 @@ static int get_alternative_line_range(struct debuginfo *dinfo, return ret; } +#ifdef HAVE_DEBUGINFOD_SUPPORT +static struct debuginfo *open_from_debuginfod(struct dso *dso, struct nsinfo *nsi, + bool silent) +{ + debuginfod_client *c = debuginfod_begin(); + char sbuild_id[SBUILD_ID_SIZE + 1]; + struct debuginfo *ret = NULL; + struct nscookie nsc; + char *path; + int fd; + + if (!c) + return NULL; + + build_id__sprintf(&dso->bid, sbuild_id); + fd = debuginfod_find_debuginfo(c, (const unsigned char *)sbuild_id, + 0, &path); + if (fd >= 0) + close(fd); + debuginfod_end(c); + if (fd < 0) { + if (!silent) + pr_debug("Failed to find debuginfo in debuginfod.\n"); + return NULL; + } + if (!silent) + pr_debug("Load debuginfo from debuginfod (%s)\n", path); + + nsinfo__mountns_enter(nsi, &nsc); + ret = debuginfo__new((const char *)path); + nsinfo__mountns_exit(&nsc); + return ret; +} +#else +static inline +struct debuginfo *open_from_debuginfod(struct dso *dso __maybe_unused, + struct nsinfo *nsi __maybe_unused, + bool silent __maybe_unused) +{ + return NULL; +} +#endif + /* Open new debuginfo of given module */ static struct debuginfo *open_debuginfo(const char *module, struct nsinfo *nsi, bool silent) @@ -471,6 +521,10 @@ static struct debuginfo *open_debuginfo(const char *module, struct nsinfo *nsi, strcpy(reason, "(unknown)"); } else dso__strerror_load(dso, reason, STRERR_BUFSIZE); + if (dso) + ret = open_from_debuginfod(dso, nsi, silent); + if (ret) + return ret; if (!silent) { if (module) pr_err("Module %s is not loaded, please specify its full path name.\n", module); @@ -795,7 +849,8 @@ post_process_kernel_probe_trace_events(struct probe_trace_event *tevs, free(tevs[i].point.symbol); tevs[i].point.symbol = tmp; tevs[i].point.offset = tevs[i].point.address - - reloc_sym->unrelocated_addr; + (map->reloc ? reloc_sym->unrelocated_addr : + reloc_sym->addr); } return skipped; } @@ -950,6 +1005,7 @@ static int _show_one_line(FILE *fp, int l, bool skip, bool show_num) static int __show_line_range(struct line_range *lr, const char *module, bool user) { + struct build_id bid; int l = 1; struct int_node *ln; struct debuginfo *dinfo; @@ -957,6 +1013,7 @@ static int __show_line_range(struct line_range *lr, const char *module, int ret; char *tmp; char sbuf[STRERR_BUFSIZE]; + char sbuild_id[SBUILD_ID_SIZE] = ""; /* Search a line range */ dinfo = open_debuginfo(module, NULL, false); @@ -969,6 +1026,10 @@ static int __show_line_range(struct line_range *lr, const char *module, if (!ret) ret = debuginfo__find_line_range(dinfo, lr); } + if (dinfo->build_id) { + build_id__init(&bid, dinfo->build_id, BUILD_ID_SIZE); + build_id__sprintf(&bid, sbuild_id); + } debuginfo__delete(dinfo); if (ret == 0 || ret == -ENOENT) { pr_warning("Specified source line is not found.\n"); @@ -980,7 +1041,7 @@ static int __show_line_range(struct line_range *lr, const char *module, /* Convert source file path */ tmp = lr->path; - ret = get_real_path(tmp, lr->comp_dir, &lr->path); + ret = find_source_path(tmp, sbuild_id, lr->comp_dir, &lr->path); /* Free old path when new path is assigned */ if (tmp != lr->path) diff --git a/tools/perf/util/probe-finder.c b/tools/perf/util/probe-finder.c index 659024342e9a..2c4061035f77 100644 --- a/tools/perf/util/probe-finder.c +++ b/tools/perf/util/probe-finder.c @@ -31,6 +31,10 @@ #include "probe-file.h" #include "string2.h" +#ifdef HAVE_DEBUGINFOD_SUPPORT +#include <elfutils/debuginfod.h> +#endif + /* Kprobe tracer basic type is up to u64 */ #define MAX_BASIC_TYPE_BITS 64 @@ -51,6 +55,7 @@ static const Dwfl_Callbacks offline_callbacks = { static int debuginfo__init_offline_dwarf(struct debuginfo *dbg, const char *path) { + GElf_Addr dummy; int fd; fd = open(path, O_RDONLY); @@ -70,6 +75,8 @@ static int debuginfo__init_offline_dwarf(struct debuginfo *dbg, if (!dbg->dbg) goto error; + dwfl_module_build_id(dbg->mod, &dbg->build_id, &dummy); + dwfl_report_end(dbg->dwfl, NULL, NULL); return 0; @@ -942,6 +949,8 @@ static int probe_point_lazy_walker(const char *fname, int lineno, /* Find probe points from lazy pattern */ static int find_probe_point_lazy(Dwarf_Die *sp_die, struct probe_finder *pf) { + struct build_id bid; + char sbuild_id[SBUILD_ID_SIZE] = ""; int ret = 0; char *fpath; @@ -949,7 +958,11 @@ static int find_probe_point_lazy(Dwarf_Die *sp_die, struct probe_finder *pf) const char *comp_dir; comp_dir = cu_get_comp_dir(&pf->cu_die); - ret = get_real_path(pf->fname, comp_dir, &fpath); + if (pf->dbg->build_id) { + build_id__init(&bid, pf->dbg->build_id, BUILD_ID_SIZE); + build_id__sprintf(&bid, sbuild_id); + } + ret = find_source_path(pf->fname, sbuild_id, comp_dir, &fpath); if (ret < 0) { pr_warning("Failed to find source file path.\n"); return ret; @@ -1448,7 +1461,7 @@ int debuginfo__find_trace_events(struct debuginfo *dbg, struct probe_trace_event **tevs) { struct trace_event_finder tf = { - .pf = {.pev = pev, .callback = add_probe_trace_event}, + .pf = {.pev = pev, .dbg = dbg, .callback = add_probe_trace_event}, .max_tevs = probe_conf.max_probes, .mod = dbg->mod}; int ret, i; @@ -1618,7 +1631,7 @@ int debuginfo__find_available_vars_at(struct debuginfo *dbg, struct variable_list **vls) { struct available_var_finder af = { - .pf = {.pev = pev, .callback = add_available_vars}, + .pf = {.pev = pev, .dbg = dbg, .callback = add_available_vars}, .mod = dbg->mod, .max_vls = probe_conf.max_probes}; int ret; @@ -1973,17 +1986,57 @@ found: return (ret < 0) ? ret : lf.found; } +#ifdef HAVE_DEBUGINFOD_SUPPORT +/* debuginfod doesn't require the comp_dir but buildid is required */ +static int get_source_from_debuginfod(const char *raw_path, + const char *sbuild_id, char **new_path) +{ + debuginfod_client *c = debuginfod_begin(); + const char *p = raw_path; + int fd; + + if (!c) + return -ENOMEM; + + fd = debuginfod_find_source(c, (const unsigned char *)sbuild_id, + 0, p, new_path); + pr_debug("Search %s from debuginfod -> %d\n", p, fd); + if (fd >= 0) + close(fd); + debuginfod_end(c); + if (fd < 0) { + pr_debug("Failed to find %s in debuginfod (%s)\n", + raw_path, sbuild_id); + return -ENOENT; + } + pr_debug("Got a source %s\n", *new_path); + + return 0; +} +#else +static inline int get_source_from_debuginfod(const char *raw_path __maybe_unused, + const char *sbuild_id __maybe_unused, + char **new_path __maybe_unused) +{ + return -ENOTSUP; +} +#endif /* * Find a src file from a DWARF tag path. Prepend optional source path prefix * and chop off leading directories that do not exist. Result is passed back as * a newly allocated path on success. * Return 0 if file was found and readable, -errno otherwise. */ -int get_real_path(const char *raw_path, const char *comp_dir, - char **new_path) +int find_source_path(const char *raw_path, const char *sbuild_id, + const char *comp_dir, char **new_path) { const char *prefix = symbol_conf.source_prefix; + if (sbuild_id && !prefix) { + if (!get_source_from_debuginfod(raw_path, sbuild_id, new_path)) + return 0; + } + if (!prefix) { if (raw_path[0] != '/' && comp_dir) /* If not an absolute path, try to use comp_dir */ diff --git a/tools/perf/util/probe-finder.h b/tools/perf/util/probe-finder.h index 11be10080613..2febb5875678 100644 --- a/tools/perf/util/probe-finder.h +++ b/tools/perf/util/probe-finder.h @@ -4,6 +4,7 @@ #include <stdbool.h> #include "intlist.h" +#include "build-id.h" #include "probe-event.h" #include <linux/ctype.h> @@ -32,6 +33,7 @@ struct debuginfo { Dwfl_Module *mod; Dwfl *dwfl; Dwarf_Addr bias; + const unsigned char *build_id; }; /* This also tries to open distro debuginfo */ @@ -59,11 +61,12 @@ int debuginfo__find_available_vars_at(struct debuginfo *dbg, struct variable_list **vls); /* Find a src file from a DWARF tag path */ -int get_real_path(const char *raw_path, const char *comp_dir, - char **new_path); +int find_source_path(const char *raw_path, const char *sbuild_id, + const char *comp_dir, char **new_path); struct probe_finder { struct perf_probe_event *pev; /* Target probe event */ + struct debuginfo *dbg; /* Callback when a probe point is found */ int (*callback)(Dwarf_Die *sc_die, struct probe_finder *pf); diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c index 75a9b1d62bba..ae8edde7c50e 100644 --- a/tools/perf/util/python.c +++ b/tools/perf/util/python.c @@ -15,9 +15,11 @@ #include "thread_map.h" #include "trace-event.h" #include "mmap.h" +#include "stat.h" +#include "metricgroup.h" #include "util/env.h" #include <internal/lib.h> -#include "../perf-sys.h" +#include "util.h" #if PY_MAJOR_VERSION < 3 #define _PyUnicode_FromString(arg) \ @@ -61,6 +63,23 @@ int parse_callchain_record(const char *arg __maybe_unused, struct perf_env perf_env; /* + * Add this one here not to drag util/stat-shadow.c + */ +void perf_stat__collect_metric_expr(struct evlist *evsel_list) +{ +} + +/* + * Add this one here not to drag util/metricgroup.c + */ +int metricgroup__copy_metric_events(struct evlist *evlist, struct cgroup *cgrp, + struct rblist *new_metric_events, + struct rblist *old_metric_events) +{ + return 0; +} + +/* * Support debug printing even though util/debug.c is not linked. That means * implementing 'verbose' and 'eprintf'. */ diff --git a/tools/perf/util/record.c b/tools/perf/util/record.c index ea9aa1d7cf50..07e4b96a6625 100644 --- a/tools/perf/util/record.c +++ b/tools/perf/util/record.c @@ -14,6 +14,7 @@ #include "util/perf_api_probe.h" #include "record.h" #include "../perf-sys.h" +#include "topdown.h" /* * evsel__config_leader_sampling() uses special rules for leader sampling. @@ -24,7 +25,7 @@ static struct evsel *evsel__read_sampler(struct evsel *evsel, struct evlist *evl { struct evsel *leader = evsel->leader; - if (evsel__is_aux_event(leader)) { + if (evsel__is_aux_event(leader) || arch_topdown_sample_read(leader)) { evlist__for_each_entry(evlist, evsel) { if (evsel->leader == leader && evsel != evsel->leader) return evsel; diff --git a/tools/perf/util/record.h b/tools/perf/util/record.h index 03678ff25539..266760ac9143 100644 --- a/tools/perf/util/record.h +++ b/tools/perf/util/record.h @@ -73,6 +73,7 @@ struct record_opts { unsigned int nr_threads_synthesize; int ctl_fd; int ctl_fd_ack; + bool ctl_fd_close; }; extern const char * const *record_usage; diff --git a/tools/perf/util/scripting-engines/trace-event-python.c b/tools/perf/util/scripting-engines/trace-event-python.c index 739516fdf6e3..7cbd024e3e63 100644 --- a/tools/perf/util/scripting-engines/trace-event-python.c +++ b/tools/perf/util/scripting-engines/trace-event-python.c @@ -1064,7 +1064,7 @@ static int python_export_dso(struct db_export *dbe, struct dso *dso, char sbuild_id[SBUILD_ID_SIZE]; PyObject *t; - build_id__sprintf(dso->build_id, sizeof(dso->build_id), sbuild_id); + build_id__sprintf(&dso->bid, sbuild_id); t = tuple_new(5); diff --git a/tools/perf/util/stat-display.c b/tools/perf/util/stat-display.c index 493ec372fdec..4b57c0c07632 100644 --- a/tools/perf/util/stat-display.c +++ b/tools/perf/util/stat-display.c @@ -946,7 +946,6 @@ static void print_metric_headers(struct perf_stat_config *config, out.print_metric = print_metric_header; out.new_line = new_line_metric; out.force_header = true; - os.evsel = counter; perf_stat__print_shadow_stats(config, counter, 0, 0, &out, diff --git a/tools/perf/util/stat-shadow.c b/tools/perf/util/stat-shadow.c index 924b54d15d54..901265127e36 100644 --- a/tools/perf/util/stat-shadow.c +++ b/tools/perf/util/stat-shadow.c @@ -241,6 +241,18 @@ void perf_stat__update_shadow_stats(struct evsel *counter, u64 count, else if (perf_stat_evsel__is(counter, TOPDOWN_RECOVERY_BUBBLES)) update_runtime_stat(st, STAT_TOPDOWN_RECOVERY_BUBBLES, ctx, cpu, count); + else if (perf_stat_evsel__is(counter, TOPDOWN_RETIRING)) + update_runtime_stat(st, STAT_TOPDOWN_RETIRING, + ctx, cpu, count); + else if (perf_stat_evsel__is(counter, TOPDOWN_BAD_SPEC)) + update_runtime_stat(st, STAT_TOPDOWN_BAD_SPEC, + ctx, cpu, count); + else if (perf_stat_evsel__is(counter, TOPDOWN_FE_BOUND)) + update_runtime_stat(st, STAT_TOPDOWN_FE_BOUND, + ctx, cpu, count); + else if (perf_stat_evsel__is(counter, TOPDOWN_BE_BOUND)) + update_runtime_stat(st, STAT_TOPDOWN_BE_BOUND, + ctx, cpu, count); else if (evsel__match(counter, HARDWARE, HW_STALLED_CYCLES_FRONTEND)) update_runtime_stat(st, STAT_STALLED_CYCLES_FRONT, ctx, cpu, count); @@ -705,6 +717,47 @@ static double td_be_bound(int ctx, int cpu, struct runtime_stat *st) return sanitize_val(1.0 - sum); } +/* + * Kernel reports metrics multiplied with slots. To get back + * the ratios we need to recreate the sum. + */ + +static double td_metric_ratio(int ctx, int cpu, + enum stat_type type, + struct runtime_stat *stat) +{ + double sum = runtime_stat_avg(stat, STAT_TOPDOWN_RETIRING, ctx, cpu) + + runtime_stat_avg(stat, STAT_TOPDOWN_FE_BOUND, ctx, cpu) + + runtime_stat_avg(stat, STAT_TOPDOWN_BE_BOUND, ctx, cpu) + + runtime_stat_avg(stat, STAT_TOPDOWN_BAD_SPEC, ctx, cpu); + double d = runtime_stat_avg(stat, type, ctx, cpu); + + if (sum) + return d / sum; + return 0; +} + +/* + * ... but only if most of the values are actually available. + * We allow two missing. + */ + +static bool full_td(int ctx, int cpu, + struct runtime_stat *stat) +{ + int c = 0; + + if (runtime_stat_avg(stat, STAT_TOPDOWN_RETIRING, ctx, cpu) > 0) + c++; + if (runtime_stat_avg(stat, STAT_TOPDOWN_BE_BOUND, ctx, cpu) > 0) + c++; + if (runtime_stat_avg(stat, STAT_TOPDOWN_FE_BOUND, ctx, cpu) > 0) + c++; + if (runtime_stat_avg(stat, STAT_TOPDOWN_BAD_SPEC, ctx, cpu) > 0) + c++; + return c >= 2; +} + static void print_smi_cost(struct perf_stat_config *config, int cpu, struct evsel *evsel, struct perf_stat_output_ctx *out, @@ -1073,6 +1126,42 @@ void perf_stat__print_shadow_stats(struct perf_stat_config *config, be_bound * 100.); else print_metric(config, ctxp, NULL, NULL, name, 0); + } else if (perf_stat_evsel__is(evsel, TOPDOWN_RETIRING) && + full_td(ctx, cpu, st)) { + double retiring = td_metric_ratio(ctx, cpu, + STAT_TOPDOWN_RETIRING, st); + + if (retiring > 0.7) + color = PERF_COLOR_GREEN; + print_metric(config, ctxp, color, "%8.1f%%", "retiring", + retiring * 100.); + } else if (perf_stat_evsel__is(evsel, TOPDOWN_FE_BOUND) && + full_td(ctx, cpu, st)) { + double fe_bound = td_metric_ratio(ctx, cpu, + STAT_TOPDOWN_FE_BOUND, st); + + if (fe_bound > 0.2) + color = PERF_COLOR_RED; + print_metric(config, ctxp, color, "%8.1f%%", "frontend bound", + fe_bound * 100.); + } else if (perf_stat_evsel__is(evsel, TOPDOWN_BE_BOUND) && + full_td(ctx, cpu, st)) { + double be_bound = td_metric_ratio(ctx, cpu, + STAT_TOPDOWN_BE_BOUND, st); + + if (be_bound > 0.2) + color = PERF_COLOR_RED; + print_metric(config, ctxp, color, "%8.1f%%", "backend bound", + be_bound * 100.); + } else if (perf_stat_evsel__is(evsel, TOPDOWN_BAD_SPEC) && + full_td(ctx, cpu, st)) { + double bad_spec = td_metric_ratio(ctx, cpu, + STAT_TOPDOWN_BAD_SPEC, st); + + if (bad_spec > 0.1) + color = PERF_COLOR_RED; + print_metric(config, ctxp, color, "%8.1f%%", "bad speculation", + bad_spec * 100.); } else if (evsel->metric_expr) { generic_metric(config, evsel->metric_expr, evsel->metric_events, NULL, evsel->name, evsel->metric_name, NULL, 1, cpu, out, st); diff --git a/tools/perf/util/stat.c b/tools/perf/util/stat.c index cdb154381a87..bd0decd6d753 100644 --- a/tools/perf/util/stat.c +++ b/tools/perf/util/stat.c @@ -95,6 +95,10 @@ static const char *id_str[PERF_STAT_EVSEL_ID__MAX] = { ID(TOPDOWN_SLOTS_RETIRED, topdown-slots-retired), ID(TOPDOWN_FETCH_BUBBLES, topdown-fetch-bubbles), ID(TOPDOWN_RECOVERY_BUBBLES, topdown-recovery-bubbles), + ID(TOPDOWN_RETIRING, topdown-retiring), + ID(TOPDOWN_BAD_SPEC, topdown-bad-spec), + ID(TOPDOWN_FE_BOUND, topdown-fe-bound), + ID(TOPDOWN_BE_BOUND, topdown-be-bound), ID(SMI_NUM, msr/smi/), ID(APERF, msr/aperf/), }; diff --git a/tools/perf/util/stat.h b/tools/perf/util/stat.h index aa3bed48511b..487010c624be 100644 --- a/tools/perf/util/stat.h +++ b/tools/perf/util/stat.h @@ -28,6 +28,10 @@ enum perf_stat_evsel_id { PERF_STAT_EVSEL_ID__TOPDOWN_SLOTS_RETIRED, PERF_STAT_EVSEL_ID__TOPDOWN_FETCH_BUBBLES, PERF_STAT_EVSEL_ID__TOPDOWN_RECOVERY_BUBBLES, + PERF_STAT_EVSEL_ID__TOPDOWN_RETIRING, + PERF_STAT_EVSEL_ID__TOPDOWN_BAD_SPEC, + PERF_STAT_EVSEL_ID__TOPDOWN_FE_BOUND, + PERF_STAT_EVSEL_ID__TOPDOWN_BE_BOUND, PERF_STAT_EVSEL_ID__SMI_NUM, PERF_STAT_EVSEL_ID__APERF, PERF_STAT_EVSEL_ID__MAX, @@ -82,6 +86,10 @@ enum stat_type { STAT_TOPDOWN_SLOTS_RETIRED, STAT_TOPDOWN_FETCH_BUBBLES, STAT_TOPDOWN_RECOVERY_BUBBLES, + STAT_TOPDOWN_RETIRING, + STAT_TOPDOWN_BAD_SPEC, + STAT_TOPDOWN_FE_BOUND, + STAT_TOPDOWN_BE_BOUND, STAT_SMI_NUM, STAT_APERF, STAT_MAX @@ -136,6 +144,8 @@ struct perf_stat_config { struct rblist metric_events; int ctl_fd; int ctl_fd_ack; + bool ctl_fd_close; + const char *cgroup_list; }; void perf_stat__set_big_num(int set); diff --git a/tools/perf/util/stream.c b/tools/perf/util/stream.c new file mode 100644 index 000000000000..4bd5e5a00aa5 --- /dev/null +++ b/tools/perf/util/stream.c @@ -0,0 +1,342 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Compare and figure out the top N hottest streams + * Copyright (c) 2020, Intel Corporation. + * Author: Jin Yao + */ + +#include <inttypes.h> +#include <stdlib.h> +#include <linux/zalloc.h> +#include "debug.h" +#include "hist.h" +#include "sort.h" +#include "stream.h" +#include "evlist.h" + +static void evsel_streams__delete(struct evsel_streams *es, int nr_evsel) +{ + for (int i = 0; i < nr_evsel; i++) + zfree(&es[i].streams); + + free(es); +} + +void evlist_streams__delete(struct evlist_streams *els) +{ + evsel_streams__delete(els->ev_streams, els->nr_evsel); + free(els); +} + +static struct evlist_streams *evlist_streams__new(int nr_evsel, + int nr_streams_max) +{ + struct evlist_streams *els; + struct evsel_streams *es; + + els = zalloc(sizeof(*els)); + if (!els) + return NULL; + + es = calloc(nr_evsel, sizeof(struct evsel_streams)); + if (!es) { + free(els); + return NULL; + } + + for (int i = 0; i < nr_evsel; i++) { + struct evsel_streams *s = &es[i]; + + s->streams = calloc(nr_streams_max, sizeof(struct stream)); + if (!s->streams) + goto err; + + s->nr_streams_max = nr_streams_max; + s->evsel_idx = -1; + } + + els->ev_streams = es; + els->nr_evsel = nr_evsel; + return els; + +err: + evsel_streams__delete(es, nr_evsel); + return NULL; +} + +/* + * The cnodes with high hit number are hot callchains. + */ +static void evsel_streams__set_hot_cnode(struct evsel_streams *es, + struct callchain_node *cnode) +{ + int i, idx = 0; + u64 hit; + + if (es->nr_streams < es->nr_streams_max) { + i = es->nr_streams; + es->streams[i].cnode = cnode; + es->nr_streams++; + return; + } + + /* + * Considering a few number of hot streams, only use simple + * way to find the cnode with smallest hit number and replace. + */ + hit = (es->streams[0].cnode)->hit; + for (i = 1; i < es->nr_streams; i++) { + if ((es->streams[i].cnode)->hit < hit) { + hit = (es->streams[i].cnode)->hit; + idx = i; + } + } + + if (cnode->hit > hit) + es->streams[idx].cnode = cnode; +} + +static void update_hot_callchain(struct hist_entry *he, + struct evsel_streams *es) +{ + struct rb_root *root = &he->sorted_chain; + struct rb_node *rb_node = rb_first(root); + struct callchain_node *cnode; + + while (rb_node) { + cnode = rb_entry(rb_node, struct callchain_node, rb_node); + evsel_streams__set_hot_cnode(es, cnode); + rb_node = rb_next(rb_node); + } +} + +static void init_hot_callchain(struct hists *hists, struct evsel_streams *es) +{ + struct rb_node *next = rb_first_cached(&hists->entries); + + while (next) { + struct hist_entry *he; + + he = rb_entry(next, struct hist_entry, rb_node); + update_hot_callchain(he, es); + next = rb_next(&he->rb_node); + } + + es->streams_hits = callchain_total_hits(hists); +} + +static int evlist__init_callchain_streams(struct evlist *evlist, + struct evlist_streams *els) +{ + struct evsel_streams *es = els->ev_streams; + struct evsel *pos; + int i = 0; + + BUG_ON(els->nr_evsel < evlist->core.nr_entries); + + evlist__for_each_entry(evlist, pos) { + struct hists *hists = evsel__hists(pos); + + hists__output_resort(hists, NULL); + init_hot_callchain(hists, &es[i]); + es[i].evsel_idx = pos->idx; + i++; + } + + return 0; +} + +struct evlist_streams *evlist__create_streams(struct evlist *evlist, + int nr_streams_max) +{ + int nr_evsel = evlist->core.nr_entries, ret = -1; + struct evlist_streams *els = evlist_streams__new(nr_evsel, + nr_streams_max); + + if (!els) + return NULL; + + ret = evlist__init_callchain_streams(evlist, els); + if (ret) { + evlist_streams__delete(els); + return NULL; + } + + return els; +} + +struct evsel_streams *evsel_streams__entry(struct evlist_streams *els, + int evsel_idx) +{ + struct evsel_streams *es = els->ev_streams; + + for (int i = 0; i < els->nr_evsel; i++) { + if (es[i].evsel_idx == evsel_idx) + return &es[i]; + } + + return NULL; +} + +static struct stream *stream__callchain_match(struct stream *base_stream, + struct evsel_streams *es_pair) +{ + for (int i = 0; i < es_pair->nr_streams; i++) { + struct stream *pair_stream = &es_pair->streams[i]; + + if (callchain_cnode_matched(base_stream->cnode, + pair_stream->cnode)) { + return pair_stream; + } + } + + return NULL; +} + +static struct stream *stream__match(struct stream *base_stream, + struct evsel_streams *es_pair) +{ + return stream__callchain_match(base_stream, es_pair); +} + +static void stream__link(struct stream *base_stream, struct stream *pair_stream) +{ + base_stream->pair_cnode = pair_stream->cnode; + pair_stream->pair_cnode = base_stream->cnode; +} + +void evsel_streams__match(struct evsel_streams *es_base, + struct evsel_streams *es_pair) +{ + for (int i = 0; i < es_base->nr_streams; i++) { + struct stream *base_stream = &es_base->streams[i]; + struct stream *pair_stream; + + pair_stream = stream__match(base_stream, es_pair); + if (pair_stream) + stream__link(base_stream, pair_stream); + } +} + +static void print_callchain_pair(struct stream *base_stream, int idx, + struct evsel_streams *es_base, + struct evsel_streams *es_pair) +{ + struct callchain_node *base_cnode = base_stream->cnode; + struct callchain_node *pair_cnode = base_stream->pair_cnode; + struct callchain_list *base_chain, *pair_chain; + char buf1[512], buf2[512], cbuf1[256], cbuf2[256]; + char *s1, *s2; + double pct; + + printf("\nhot chain pair %d:\n", idx); + + pct = (double)base_cnode->hit / (double)es_base->streams_hits; + scnprintf(buf1, sizeof(buf1), "cycles: %ld, hits: %.2f%%", + callchain_avg_cycles(base_cnode), pct * 100.0); + + pct = (double)pair_cnode->hit / (double)es_pair->streams_hits; + scnprintf(buf2, sizeof(buf2), "cycles: %ld, hits: %.2f%%", + callchain_avg_cycles(pair_cnode), pct * 100.0); + + printf("%35s\t%35s\n", buf1, buf2); + + printf("%35s\t%35s\n", + "---------------------------", + "--------------------------"); + + pair_chain = list_first_entry(&pair_cnode->val, + struct callchain_list, + list); + + list_for_each_entry(base_chain, &base_cnode->val, list) { + if (&pair_chain->list == &pair_cnode->val) + return; + + s1 = callchain_list__sym_name(base_chain, cbuf1, sizeof(cbuf1), + false); + s2 = callchain_list__sym_name(pair_chain, cbuf2, sizeof(cbuf2), + false); + + scnprintf(buf1, sizeof(buf1), "%35s\t%35s", s1, s2); + printf("%s\n", buf1); + pair_chain = list_next_entry(pair_chain, list); + } +} + +static void print_stream_callchain(struct stream *stream, int idx, + struct evsel_streams *es, bool pair) +{ + struct callchain_node *cnode = stream->cnode; + struct callchain_list *chain; + char buf[512], cbuf[256], *s; + double pct; + + printf("\nhot chain %d:\n", idx); + + pct = (double)cnode->hit / (double)es->streams_hits; + scnprintf(buf, sizeof(buf), "cycles: %ld, hits: %.2f%%", + callchain_avg_cycles(cnode), pct * 100.0); + + if (pair) { + printf("%35s\t%35s\n", "", buf); + printf("%35s\t%35s\n", + "", "--------------------------"); + } else { + printf("%35s\n", buf); + printf("%35s\n", "--------------------------"); + } + + list_for_each_entry(chain, &cnode->val, list) { + s = callchain_list__sym_name(chain, cbuf, sizeof(cbuf), false); + + if (pair) + scnprintf(buf, sizeof(buf), "%35s\t%35s", "", s); + else + scnprintf(buf, sizeof(buf), "%35s", s); + + printf("%s\n", buf); + } +} + +static void callchain_streams_report(struct evsel_streams *es_base, + struct evsel_streams *es_pair) +{ + struct stream *base_stream; + int i, idx = 0; + + printf("[ Matched hot streams ]\n"); + for (i = 0; i < es_base->nr_streams; i++) { + base_stream = &es_base->streams[i]; + if (base_stream->pair_cnode) { + print_callchain_pair(base_stream, ++idx, + es_base, es_pair); + } + } + + idx = 0; + printf("\n[ Hot streams in old perf data only ]\n"); + for (i = 0; i < es_base->nr_streams; i++) { + base_stream = &es_base->streams[i]; + if (!base_stream->pair_cnode) { + print_stream_callchain(base_stream, ++idx, + es_base, false); + } + } + + idx = 0; + printf("\n[ Hot streams in new perf data only ]\n"); + for (i = 0; i < es_pair->nr_streams; i++) { + base_stream = &es_pair->streams[i]; + if (!base_stream->pair_cnode) { + print_stream_callchain(base_stream, ++idx, + es_pair, true); + } + } +} + +void evsel_streams__report(struct evsel_streams *es_base, + struct evsel_streams *es_pair) +{ + return callchain_streams_report(es_base, es_pair); +} diff --git a/tools/perf/util/stream.h b/tools/perf/util/stream.h new file mode 100644 index 000000000000..bee768874fea --- /dev/null +++ b/tools/perf/util/stream.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __PERF_STREAM_H +#define __PERF_STREAM_H + +#include "callchain.h" + +struct stream { + struct callchain_node *cnode; + struct callchain_node *pair_cnode; +}; + +struct evsel_streams { + struct stream *streams; + int nr_streams_max; + int nr_streams; + int evsel_idx; + u64 streams_hits; +}; + +struct evlist_streams { + struct evsel_streams *ev_streams; + int nr_evsel; +}; + +struct evlist; + +void evlist_streams__delete(struct evlist_streams *els); + +struct evlist_streams *evlist__create_streams(struct evlist *evlist, + int nr_streams_max); + +struct evsel_streams *evsel_streams__entry(struct evlist_streams *els, + int evsel_idx); + +void evsel_streams__match(struct evsel_streams *es_base, + struct evsel_streams *es_pair); + +void evsel_streams__report(struct evsel_streams *es_base, + struct evsel_streams *es_pair); + +#endif /* __PERF_STREAM_H */ diff --git a/tools/perf/util/symbol-elf.c b/tools/perf/util/symbol-elf.c index 8cc4b0059fb0..44dd86a4f25f 100644 --- a/tools/perf/util/symbol-elf.c +++ b/tools/perf/util/symbol-elf.c @@ -50,6 +50,10 @@ typedef Elf64_Nhdr GElf_Nhdr; #define DMGL_ANSI (1 << 1) /* Include const, volatile, etc */ #endif +#ifdef HAVE_LIBBFD_SUPPORT +#define PACKAGE 'perf' +#include <bfd.h> +#else #ifdef HAVE_CPLUS_DEMANGLE_SUPPORT extern char *cplus_demangle(const char *, int); @@ -65,9 +69,7 @@ static inline char *bfd_demangle(void __maybe_unused *v, { return NULL; } -#else -#define PACKAGE 'perf' -#include <bfd.h> +#endif #endif #endif @@ -530,8 +532,40 @@ out: return err; } -int filename__read_build_id(const char *filename, void *bf, size_t size) +#ifdef HAVE_LIBBFD_BUILDID_SUPPORT + +int filename__read_build_id(const char *filename, struct build_id *bid) +{ + size_t size = sizeof(bid->data); + int err = -1; + bfd *abfd; + + abfd = bfd_openr(filename, NULL); + if (!abfd) + return -1; + + if (!bfd_check_format(abfd, bfd_object)) { + pr_debug2("%s: cannot read %s bfd file.\n", __func__, filename); + goto out_close; + } + + if (!abfd->build_id || abfd->build_id->size > size) + goto out_close; + + memcpy(bid->data, abfd->build_id->data, abfd->build_id->size); + memset(bid->data + abfd->build_id->size, 0, size - abfd->build_id->size); + err = bid->size = abfd->build_id->size; + +out_close: + bfd_close(abfd); + return err; +} + +#else // HAVE_LIBBFD_BUILDID_SUPPORT + +int filename__read_build_id(const char *filename, struct build_id *bid) { + size_t size = sizeof(bid->data); int fd, err = -1; Elf *elf; @@ -548,7 +582,9 @@ int filename__read_build_id(const char *filename, void *bf, size_t size) goto out_close; } - err = elf_read_build_id(elf, bf, size); + err = elf_read_build_id(elf, bid->data, size); + if (err > 0) + bid->size = err; elf_end(elf); out_close: @@ -557,13 +593,13 @@ out: return err; } -int sysfs__read_build_id(const char *filename, void *build_id, size_t size) +#endif // HAVE_LIBBFD_BUILDID_SUPPORT + +int sysfs__read_build_id(const char *filename, struct build_id *bid) { + size_t size = sizeof(bid->data); int fd, err = -1; - if (size < BUILD_ID_SIZE) - goto out; - fd = open(filename, O_RDONLY); if (fd < 0) goto out; @@ -584,8 +620,9 @@ int sysfs__read_build_id(const char *filename, void *build_id, size_t size) break; if (memcmp(bf, "GNU", sizeof("GNU")) == 0) { size_t sz = min(descsz, size); - if (read(fd, build_id, sz) == (ssize_t)sz) { - memset(build_id + sz, 0, size - sz); + if (read(fd, bid->data, sz) == (ssize_t)sz) { + memset(bid->data + sz, 0, size - sz); + bid->size = sz; err = 0; break; } @@ -608,6 +645,44 @@ out: return err; } +#ifdef HAVE_LIBBFD_SUPPORT + +int filename__read_debuglink(const char *filename, char *debuglink, + size_t size) +{ + int err = -1; + asection *section; + bfd *abfd; + + abfd = bfd_openr(filename, NULL); + if (!abfd) + return -1; + + if (!bfd_check_format(abfd, bfd_object)) { + pr_debug2("%s: cannot read %s bfd file.\n", __func__, filename); + goto out_close; + } + + section = bfd_get_section_by_name(abfd, ".gnu_debuglink"); + if (!section) + goto out_close; + + if (section->size > size) + goto out_close; + + if (!bfd_get_section_contents(abfd, section, debuglink, 0, + section->size)) + goto out_close; + + err = 0; + +out_close: + bfd_close(abfd); + return err; +} + +#else + int filename__read_debuglink(const char *filename, char *debuglink, size_t size) { @@ -660,6 +735,8 @@ out: return err; } +#endif + static int dso__swap_init(struct dso *dso, unsigned char eidata) { static unsigned int const endian = 1; @@ -757,13 +834,17 @@ int symsrc__init(struct symsrc *ss, struct dso *dso, const char *name, /* Always reject images with a mismatched build-id: */ if (dso->has_build_id && !symbol_conf.ignore_vmlinux_buildid) { u8 build_id[BUILD_ID_SIZE]; + struct build_id bid; + int size; - if (elf_read_build_id(elf, build_id, BUILD_ID_SIZE) < 0) { + size = elf_read_build_id(elf, build_id, BUILD_ID_SIZE); + if (size <= 0) { dso->load_errno = DSO_LOAD_ERRNO__CANNOT_READ_BUILDID; goto out_elf_end; } - if (!dso__build_id_equal(dso, build_id)) { + build_id__init(&bid, build_id, size); + if (!dso__build_id_equal(dso, &bid)) { pr_debug("%s: build id mismatch for %s.\n", __func__, name); dso->load_errno = DSO_LOAD_ERRNO__MISMATCHING_BUILDID; goto out_elf_end; diff --git a/tools/perf/util/symbol-minimal.c b/tools/perf/util/symbol-minimal.c index d6e99af263ec..f9eb0bee7f15 100644 --- a/tools/perf/util/symbol-minimal.c +++ b/tools/perf/util/symbol-minimal.c @@ -31,9 +31,10 @@ static bool check_need_swap(int file_endian) #define NT_GNU_BUILD_ID 3 -static int read_build_id(void *note_data, size_t note_len, void *bf, - size_t size, bool need_swap) +static int read_build_id(void *note_data, size_t note_len, struct build_id *bid, + bool need_swap) { + size_t size = sizeof(bid->data); struct { u32 n_namesz; u32 n_descsz; @@ -63,8 +64,9 @@ static int read_build_id(void *note_data, size_t note_len, void *bf, nhdr->n_namesz == sizeof("GNU")) { if (memcmp(name, "GNU", sizeof("GNU")) == 0) { size_t sz = min(size, descsz); - memcpy(bf, ptr, sz); - memset(bf + sz, 0, size - sz); + memcpy(bid->data, ptr, sz); + memset(bid->data + sz, 0, size - sz); + bid->size = sz; return 0; } } @@ -84,7 +86,7 @@ int filename__read_debuglink(const char *filename __maybe_unused, /* * Just try PT_NOTE header otherwise fails */ -int filename__read_build_id(const char *filename, void *bf, size_t size) +int filename__read_build_id(const char *filename, struct build_id *bid) { FILE *fp; int ret = -1; @@ -156,9 +158,9 @@ int filename__read_build_id(const char *filename, void *bf, size_t size) if (fread(buf, buf_size, 1, fp) != 1) goto out_free; - ret = read_build_id(buf, buf_size, bf, size, need_swap); + ret = read_build_id(buf, buf_size, bid, need_swap); if (ret == 0) - ret = size; + ret = bid->size; break; } } else { @@ -207,9 +209,9 @@ int filename__read_build_id(const char *filename, void *bf, size_t size) if (fread(buf, buf_size, 1, fp) != 1) goto out_free; - ret = read_build_id(buf, buf_size, bf, size, need_swap); + ret = read_build_id(buf, buf_size, bid, need_swap); if (ret == 0) - ret = size; + ret = bid->size; break; } } @@ -220,7 +222,7 @@ out: return ret; } -int sysfs__read_build_id(const char *filename, void *build_id, size_t size) +int sysfs__read_build_id(const char *filename, struct build_id *bid) { int fd; int ret = -1; @@ -243,7 +245,7 @@ int sysfs__read_build_id(const char *filename, void *build_id, size_t size) if (read(fd, buf, buf_size) != (ssize_t) buf_size) goto out_free; - ret = read_build_id(buf, buf_size, build_id, size, false); + ret = read_build_id(buf, buf_size, bid, false); out_free: free(buf); out: @@ -339,16 +341,15 @@ int dso__load_sym(struct dso *dso, struct map *map __maybe_unused, struct symsrc *runtime_ss __maybe_unused, int kmodule __maybe_unused) { - unsigned char build_id[BUILD_ID_SIZE]; + struct build_id bid; int ret; ret = fd__is_64_bit(ss->fd); if (ret >= 0) dso->is_64_bit = ret; - if (filename__read_build_id(ss->name, build_id, BUILD_ID_SIZE) > 0) { - dso__set_build_id(dso, build_id); - } + if (filename__read_build_id(ss->name, &bid) > 0) + dso__set_build_id(dso, &bid); return 0; } diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c index 5151a8c0b791..6138866665df 100644 --- a/tools/perf/util/symbol.c +++ b/tools/perf/util/symbol.c @@ -1526,6 +1526,138 @@ out_failure: return -1; } +#ifdef HAVE_LIBBFD_SUPPORT +#define PACKAGE 'perf' +#include <bfd.h> + +static int bfd_symbols__cmpvalue(const void *a, const void *b) +{ + const asymbol *as = *(const asymbol **)a, *bs = *(const asymbol **)b; + + if (bfd_asymbol_value(as) != bfd_asymbol_value(bs)) + return bfd_asymbol_value(as) - bfd_asymbol_value(bs); + + return bfd_asymbol_name(as)[0] - bfd_asymbol_name(bs)[0]; +} + +static int bfd2elf_binding(asymbol *symbol) +{ + if (symbol->flags & BSF_WEAK) + return STB_WEAK; + if (symbol->flags & BSF_GLOBAL) + return STB_GLOBAL; + if (symbol->flags & BSF_LOCAL) + return STB_LOCAL; + return -1; +} + +int dso__load_bfd_symbols(struct dso *dso, const char *debugfile) +{ + int err = -1; + long symbols_size, symbols_count; + asection *section; + asymbol **symbols, *sym; + struct symbol *symbol; + bfd *abfd; + u_int i; + u64 start, len; + + abfd = bfd_openr(dso->long_name, NULL); + if (!abfd) + return -1; + + if (!bfd_check_format(abfd, bfd_object)) { + pr_debug2("%s: cannot read %s bfd file.\n", __func__, + dso->long_name); + goto out_close; + } + + if (bfd_get_flavour(abfd) == bfd_target_elf_flavour) + goto out_close; + + section = bfd_get_section_by_name(abfd, ".text"); + if (section) + dso->text_offset = section->vma - section->filepos; + + bfd_close(abfd); + + abfd = bfd_openr(debugfile, NULL); + if (!abfd) + return -1; + + if (!bfd_check_format(abfd, bfd_object)) { + pr_debug2("%s: cannot read %s bfd file.\n", __func__, + debugfile); + goto out_close; + } + + if (bfd_get_flavour(abfd) == bfd_target_elf_flavour) + goto out_close; + + symbols_size = bfd_get_symtab_upper_bound(abfd); + if (symbols_size == 0) { + bfd_close(abfd); + return 0; + } + + if (symbols_size < 0) + goto out_close; + + symbols = malloc(symbols_size); + if (!symbols) + goto out_close; + + symbols_count = bfd_canonicalize_symtab(abfd, symbols); + if (symbols_count < 0) + goto out_free; + + qsort(symbols, symbols_count, sizeof(asymbol *), bfd_symbols__cmpvalue); + +#ifdef bfd_get_section +#define bfd_asymbol_section bfd_get_section +#endif + for (i = 0; i < symbols_count; ++i) { + sym = symbols[i]; + section = bfd_asymbol_section(sym); + if (bfd2elf_binding(sym) < 0) + continue; + + while (i + 1 < symbols_count && + bfd_asymbol_section(symbols[i + 1]) == section && + bfd2elf_binding(symbols[i + 1]) < 0) + i++; + + if (i + 1 < symbols_count && + bfd_asymbol_section(symbols[i + 1]) == section) + len = symbols[i + 1]->value - sym->value; + else + len = section->size - sym->value; + + start = bfd_asymbol_value(sym) - dso->text_offset; + symbol = symbol__new(start, len, bfd2elf_binding(sym), STT_FUNC, + bfd_asymbol_name(sym)); + if (!symbol) + goto out_free; + + symbols__insert(&dso->symbols, symbol); + } +#ifdef bfd_get_section +#undef bfd_asymbol_section +#endif + + symbols__fixup_end(&dso->symbols); + symbols__fixup_duplicate(&dso->symbols); + dso->adjust_symbols = 1; + + err = 0; +out_free: + free(symbols); +out_close: + bfd_close(abfd); + return err; +} +#endif + static bool dso__is_compatible_symtab_type(struct dso *dso, bool kmod, enum dso_binary_type type) { @@ -1623,7 +1755,7 @@ int dso__load(struct dso *dso, struct map *map) struct symsrc *syms_ss = NULL, *runtime_ss = NULL; bool kmod; bool perfmap; - unsigned char build_id[BUILD_ID_SIZE]; + struct build_id bid; struct nscookie nsc; char newmapname[PATH_MAX]; const char *map_path = dso->long_name; @@ -1685,8 +1817,8 @@ int dso__load(struct dso *dso, struct map *map) if (!dso->has_build_id && is_regular_file(dso->long_name)) { __symbol__join_symfs(name, PATH_MAX, dso->long_name); - if (filename__read_build_id(name, build_id, BUILD_ID_SIZE) > 0) - dso__set_build_id(dso, build_id); + if (filename__read_build_id(name, &bid) > 0) + dso__set_build_id(dso, &bid); } /* @@ -1699,6 +1831,7 @@ int dso__load(struct dso *dso, struct map *map) bool next_slot = false; bool is_reg; bool nsexit; + int bfdrc = -1; int sirc = -1; enum dso_binary_type symtab_type = binary_type_symtab[i]; @@ -1717,12 +1850,19 @@ int dso__load(struct dso *dso, struct map *map) nsinfo__mountns_exit(&nsc); is_reg = is_regular_file(name); +#ifdef HAVE_LIBBFD_SUPPORT if (is_reg) + bfdrc = dso__load_bfd_symbols(dso, name); +#endif + if (is_reg && bfdrc < 0) sirc = symsrc__init(ss, dso, name, symtab_type); if (nsexit) nsinfo__mountns_enter(dso->nsinfo, &nsc); + if (bfdrc == 0) + break; + if (!is_reg || sirc < 0) continue; @@ -1982,7 +2122,7 @@ static bool filename__readable(const char *file) static char *dso__find_kallsyms(struct dso *dso, struct map *map) { - u8 host_build_id[BUILD_ID_SIZE]; + struct build_id bid; char sbuild_id[SBUILD_ID_SIZE]; bool is_host = false; char path[PATH_MAX]; @@ -1995,9 +2135,8 @@ static char *dso__find_kallsyms(struct dso *dso, struct map *map) goto proc_kallsyms; } - if (sysfs__read_build_id("/sys/kernel/notes", host_build_id, - sizeof(host_build_id)) == 0) - is_host = dso__build_id_equal(dso, host_build_id); + if (sysfs__read_build_id("/sys/kernel/notes", &bid) == 0) + is_host = dso__build_id_equal(dso, &bid); /* Try a fast path for /proc/kallsyms if possible */ if (is_host) { @@ -2013,7 +2152,7 @@ static char *dso__find_kallsyms(struct dso *dso, struct map *map) goto proc_kallsyms; } - build_id__sprintf(dso->build_id, sizeof(dso->build_id), sbuild_id); + build_id__sprintf(&dso->bid, sbuild_id); /* Find kallsyms in build-id cache with kcore */ scnprintf(path, sizeof(path), "%s/%s/%s", diff --git a/tools/perf/util/symbol.h b/tools/perf/util/symbol.h index 03e264a27cd3..f4801c488def 100644 --- a/tools/perf/util/symbol.h +++ b/tools/perf/util/symbol.h @@ -23,6 +23,7 @@ struct dso; struct map; struct maps; struct option; +struct build_id; /* * libelf 0.8.x and earlier do not support ELF_C_READ_MMAP; @@ -142,8 +143,8 @@ struct symbol *dso__next_symbol(struct symbol *sym); enum dso_type dso__type_fd(int fd); -int filename__read_build_id(const char *filename, void *bf, size_t size); -int sysfs__read_build_id(const char *filename, void *bf, size_t size); +int filename__read_build_id(const char *filename, struct build_id *id); +int sysfs__read_build_id(const char *filename, struct build_id *bid); int modules__parse(const char *filename, void *arg, int (*process_module)(void *arg, const char *name, u64 start, u64 size)); @@ -175,6 +176,10 @@ int symbol__config_symfs(const struct option *opt __maybe_unused, struct symsrc; +#ifdef HAVE_LIBBFD_SUPPORT +int dso__load_bfd_symbols(struct dso *dso, const char *debugfile); +#endif + int dso__load_sym(struct dso *dso, struct map *map, struct symsrc *syms_ss, struct symsrc *runtime_ss, int kmodule); int dso__synthesize_plt_symbols(struct dso *dso, struct symsrc *ss); diff --git a/tools/perf/util/synthetic-events.c b/tools/perf/util/synthetic-events.c index 89b390623b63..8a23391558cf 100644 --- a/tools/perf/util/synthetic-events.c +++ b/tools/perf/util/synthetic-events.c @@ -1961,7 +1961,7 @@ int perf_event__synthesize_build_id(struct perf_tool *tool, struct dso *pos, u16 len = pos->long_name_len + 1; len = PERF_ALIGN(len, NAME_ALIGN); - memcpy(&ev.build_id.build_id, pos->build_id, sizeof(pos->build_id)); + memcpy(&ev.build_id.build_id, pos->bid.data, sizeof(pos->bid.data)); ev.build_id.header.type = PERF_RECORD_HEADER_BUILD_ID; ev.build_id.header.misc = misc; ev.build_id.pid = machine->pid; @@ -2006,14 +2006,6 @@ int perf_event__synthesize_stat_events(struct perf_stat_config *config, struct p return 0; } -int __weak perf_event__synth_time_conv(const struct perf_event_mmap_page *pc __maybe_unused, - struct perf_tool *tool __maybe_unused, - perf_event__handler_t process __maybe_unused, - struct machine *machine __maybe_unused) -{ - return 0; -} - extern const struct perf_header_feature_ops feat_ops[HEADER_LAST_FEATURE]; int perf_event__synthesize_features(struct perf_tool *tool, struct perf_session *session, diff --git a/tools/perf/util/topdown.c b/tools/perf/util/topdown.c new file mode 100644 index 000000000000..1081b20f9891 --- /dev/null +++ b/tools/perf/util/topdown.c @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <stdio.h> +#include "pmu.h" +#include "topdown.h" + +int topdown_filter_events(const char **attr, char **str, bool use_group) +{ + int off = 0; + int i; + int len = 0; + char *s; + + for (i = 0; attr[i]; i++) { + if (pmu_have_event("cpu", attr[i])) { + len += strlen(attr[i]) + 1; + attr[i - off] = attr[i]; + } else + off++; + } + attr[i - off] = NULL; + + *str = malloc(len + 1 + 2); + if (!*str) + return -1; + s = *str; + if (i - off == 0) { + *s = 0; + return 0; + } + if (use_group) + *s++ = '{'; + for (i = 0; attr[i]; i++) { + strcpy(s, attr[i]); + s += strlen(s); + *s++ = ','; + } + if (use_group) { + s[-1] = '}'; + *s = 0; + } else + s[-1] = 0; + return 0; +} + +__weak bool arch_topdown_check_group(bool *warn) +{ + *warn = false; + return false; +} + +__weak void arch_topdown_group_warn(void) +{ +} + +__weak bool arch_topdown_sample_read(struct evsel *leader __maybe_unused) +{ + return false; +} diff --git a/tools/perf/util/topdown.h b/tools/perf/util/topdown.h new file mode 100644 index 000000000000..2f0d0b887639 --- /dev/null +++ b/tools/perf/util/topdown.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef TOPDOWN_H +#define TOPDOWN_H 1 +#include "evsel.h" + +bool arch_topdown_check_group(bool *warn); +void arch_topdown_group_warn(void); +bool arch_topdown_sample_read(struct evsel *leader); + +int topdown_filter_events(const char **attr, char **str, bool use_group); + +#endif diff --git a/tools/perf/util/tsc.c b/tools/perf/util/tsc.c index bfa782421cbd..62b4c75c966c 100644 --- a/tools/perf/util/tsc.c +++ b/tools/perf/util/tsc.c @@ -1,7 +1,16 @@ // SPDX-License-Identifier: GPL-2.0 +#include <errno.h> + #include <linux/compiler.h> +#include <linux/perf_event.h> +#include <linux/stddef.h> #include <linux/types.h> +#include <asm/barrier.h> + +#include "event.h" +#include "synthetic-events.h" +#include "debug.h" #include "tsc.h" u64 perf_time_to_tsc(u64 ns, struct perf_tsc_conversion *tc) @@ -19,12 +28,84 @@ u64 tsc_to_perf_time(u64 cyc, struct perf_tsc_conversion *tc) { u64 quot, rem; + if (tc->cap_user_time_short) + cyc = tc->time_cycles + + ((cyc - tc->time_cycles) & tc->time_mask); + quot = cyc >> tc->time_shift; rem = cyc & (((u64)1 << tc->time_shift) - 1); return tc->time_zero + quot * tc->time_mult + ((rem * tc->time_mult) >> tc->time_shift); } +int perf_read_tsc_conversion(const struct perf_event_mmap_page *pc, + struct perf_tsc_conversion *tc) +{ + u32 seq; + int i = 0; + + while (1) { + seq = pc->lock; + rmb(); + tc->time_mult = pc->time_mult; + tc->time_shift = pc->time_shift; + tc->time_zero = pc->time_zero; + tc->time_cycles = pc->time_cycles; + tc->time_mask = pc->time_mask; + tc->cap_user_time_zero = pc->cap_user_time_zero; + tc->cap_user_time_short = pc->cap_user_time_short; + rmb(); + if (pc->lock == seq && !(seq & 1)) + break; + if (++i > 10000) { + pr_debug("failed to get perf_event_mmap_page lock\n"); + return -EINVAL; + } + } + + if (!tc->cap_user_time_zero) + return -EOPNOTSUPP; + + return 0; +} + +int perf_event__synth_time_conv(const struct perf_event_mmap_page *pc, + struct perf_tool *tool, + perf_event__handler_t process, + struct machine *machine) +{ + union perf_event event = { + .time_conv = { + .header = { + .type = PERF_RECORD_TIME_CONV, + .size = sizeof(struct perf_record_time_conv), + }, + }, + }; + struct perf_tsc_conversion tc; + int err; + + if (!pc) + return 0; + err = perf_read_tsc_conversion(pc, &tc); + if (err == -EOPNOTSUPP) + return 0; + if (err) + return err; + + pr_debug2("Synthesizing TSC conversion information\n"); + + event.time_conv.time_mult = tc.time_mult; + event.time_conv.time_shift = tc.time_shift; + event.time_conv.time_zero = tc.time_zero; + event.time_conv.time_cycles = tc.time_cycles; + event.time_conv.time_mask = tc.time_mask; + event.time_conv.cap_user_time_zero = tc.cap_user_time_zero; + event.time_conv.cap_user_time_short = tc.cap_user_time_short; + + return process(tool, &event, NULL, machine); +} + u64 __weak rdtsc(void) { return 0; diff --git a/tools/perf/util/tsc.h b/tools/perf/util/tsc.h index 3c5a632ee57c..72a15419f3b3 100644 --- a/tools/perf/util/tsc.h +++ b/tools/perf/util/tsc.h @@ -8,6 +8,11 @@ struct perf_tsc_conversion { u16 time_shift; u32 time_mult; u64 time_zero; + u64 time_cycles; + u64 time_mask; + + bool cap_user_time_zero; + bool cap_user_time_short; }; struct perf_event_mmap_page; diff --git a/tools/perf/util/util.h b/tools/perf/util/util.h index f486fdd3a538..ad737052e597 100644 --- a/tools/perf/util/util.h +++ b/tools/perf/util/util.h @@ -62,4 +62,10 @@ char *perf_exe(char *buf, int len); #endif #endif +extern bool test_attr__enabled; +void test_attr__ready(void); +void test_attr__init(void); +struct perf_event_attr; +void test_attr__open(struct perf_event_attr *attr, pid_t pid, int cpu, + int fd, int group_fd, unsigned long flags); #endif /* GIT_COMPAT_UTIL_H */ |