diff options
Diffstat (limited to 'tools')
48 files changed, 2386 insertions, 892 deletions
diff --git a/tools/perf/Documentation/perf-archive.txt b/tools/perf/Documentation/perf-archive.txt new file mode 100644 index 000000000000..fae174dc7d01 --- /dev/null +++ b/tools/perf/Documentation/perf-archive.txt @@ -0,0 +1,22 @@ +perf-archive(1) +=============== + +NAME +---- +perf-archive - Create archive with object files with build-ids found in perf.data file + +SYNOPSIS +-------- +[verse] +'perf archive' [file] + +DESCRIPTION +----------- +This command runs runs perf-buildid-list --with-hits, and collects the files +with the buildids found so that analisys of perf.data contents can be possible +on another machine. + + +SEE ALSO +-------- +linkperf:perf-record[1], linkperf:perf-buildid-list[1], linkperf:perf-report[1] diff --git a/tools/perf/Documentation/perf-buildid-cache.txt b/tools/perf/Documentation/perf-buildid-cache.txt new file mode 100644 index 000000000000..88bc3b519746 --- /dev/null +++ b/tools/perf/Documentation/perf-buildid-cache.txt @@ -0,0 +1,33 @@ +perf-buildid-cache(1) +===================== + +NAME +---- +perf-buildid-cache - Manage build-id cache. + +SYNOPSIS +-------- +[verse] +'perf buildid-list <options>' + +DESCRIPTION +----------- +This command manages the build-id cache. It can add and remove files to the +cache. In the future it should as well purge older entries, set upper limits +for the space used by the cache, etc. + +OPTIONS +------- +-a:: +--add=:: + Add specified file to the cache. +-r:: +--remove=:: + Remove specified file to the cache. +-v:: +--verbose:: + Be more verbose. + +SEE ALSO +-------- +linkperf:perf-record[1], linkperf:perf-report[1] diff --git a/tools/perf/Documentation/perf-probe.txt b/tools/perf/Documentation/perf-probe.txt index 250e391b4bc8..2de34075f6a4 100644 --- a/tools/perf/Documentation/perf-probe.txt +++ b/tools/perf/Documentation/perf-probe.txt @@ -15,6 +15,8 @@ or 'perf probe' [options] --del='[GROUP:]EVENT' [...] or 'perf probe' --list +or +'perf probe' --line='FUNC[:RLN[+NUM|:RLN2]]|SRC:ALN[+NUM|:ALN2]' DESCRIPTION ----------- @@ -45,6 +47,11 @@ OPTIONS --list:: List up current probe events. +-L:: +--line=:: + Show source code lines which can be probed. This needs an argument + which specifies a range of the source code. + PROBE SYNTAX ------------ Probe points are defined by following syntax. @@ -56,6 +63,19 @@ Probe points are defined by following syntax. It is also possible to specify a probe point by the source line number by using 'SRC:ALN' syntax, where 'SRC' is the source file path and 'ALN' is the line number. 'ARG' specifies the arguments of this probe point. You can use the name of local variable, or kprobe-tracer argument format (e.g. $retval, %ax, etc). +LINE SYNTAX +----------- +Line range is descripted by following syntax. + + "FUNC[:RLN[+NUM|:RLN2]]|SRC:ALN[+NUM|:ALN2]" + +FUNC specifies the function name of showing lines. 'RLN' is the start line +number from function entry line, and 'RLN2' is the end line number. As same as +probe syntax, 'SRC' means the source file path, 'ALN' is start line number, +and 'ALN2' is end line number in the file. It is also possible to specify how +many lines to show by using 'NUM'. +So, "source.c:100-120" shows lines between 100th to l20th in source.c file. And "func:10+20" shows 20 lines from 10th line of func function. + SEE ALSO -------- linkperf:perf-trace[1], linkperf:perf-record[1] diff --git a/tools/perf/Documentation/perf.txt b/tools/perf/Documentation/perf.txt index 69c832557199..0eeb247dc7d2 100644 --- a/tools/perf/Documentation/perf.txt +++ b/tools/perf/Documentation/perf.txt @@ -12,7 +12,7 @@ SYNOPSIS DESCRIPTION ----------- -Performance counters for Linux are are a new kernel-based subsystem +Performance counters for Linux are a new kernel-based subsystem that provide a framework for all things performance analysis. It covers hardware level (CPU/PMU, Performance Monitoring Unit) features and software features (software counters, tracepoints) as well. diff --git a/tools/perf/Makefile b/tools/perf/Makefile index 2e7fa3a06806..9b173e66fb41 100644 --- a/tools/perf/Makefile +++ b/tools/perf/Makefile @@ -286,11 +286,7 @@ SCRIPT_PERL = SCRIPT_SH = TEST_PROGRAMS = -# -# No scripts right now: -# - -# SCRIPT_SH += perf-am.sh +SCRIPT_SH += perf-archive.sh # # No Perl scripts right now: @@ -315,9 +311,6 @@ PROGRAMS += perf # List built-in command $C whose implementation cmd_$C() is not in # builtin-$C.o but is linked in as part of some other command. # -# None right now: -# -# BUILT_INS += perf-init $X # what 'all' will build and 'install' will install, in perfexecdir ALL_PROGRAMS = $(PROGRAMS) $(SCRIPTS) @@ -369,6 +362,7 @@ LIB_H += util/event.h LIB_H += util/exec_cmd.h LIB_H += util/types.h LIB_H += util/levenshtein.h +LIB_H += util/map.h LIB_H += util/parse-options.h LIB_H += util/parse-events.h LIB_H += util/quote.h @@ -435,8 +429,8 @@ LIB_OBJS += util/trace-event-perl.o LIB_OBJS += util/svghelper.o LIB_OBJS += util/sort.o LIB_OBJS += util/hist.o -LIB_OBJS += util/data_map.o LIB_OBJS += util/probe-event.o +LIB_OBJS += util/util.o BUILTIN_OBJS += builtin-annotate.o @@ -451,6 +445,7 @@ BUILTIN_OBJS += builtin-diff.o BUILTIN_OBJS += builtin-help.o BUILTIN_OBJS += builtin-sched.o BUILTIN_OBJS += builtin-buildid-list.o +BUILTIN_OBJS += builtin-buildid-cache.o BUILTIN_OBJS += builtin-list.o BUILTIN_OBJS += builtin-record.o BUILTIN_OBJS += builtin-report.o @@ -1009,6 +1004,7 @@ install: all $(INSTALL) perf$X '$(DESTDIR_SQ)$(bindir_SQ)' $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/scripts/perl/Perf-Trace-Util/lib/Perf/Trace' $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/scripts/perl/bin' + $(INSTALL) perf-archive -t '$(DESTDIR_SQ)$(perfexec_instdir_SQ)' $(INSTALL) scripts/perl/Perf-Trace-Util/lib/Perf/Trace/* -t '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/scripts/perl/Perf-Trace-Util/lib/Perf/Trace' $(INSTALL) scripts/perl/*.pl -t '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/scripts/perl' $(INSTALL) scripts/perl/bin/* -t '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/scripts/perl/bin' diff --git a/tools/perf/builtin-annotate.c b/tools/perf/builtin-annotate.c index 593ff25006de..73c202ee0882 100644 --- a/tools/perf/builtin-annotate.c +++ b/tools/perf/builtin-annotate.c @@ -132,8 +132,8 @@ static int process_sample_event(event_t *event, struct perf_session *session) { struct addr_location al; - dump_printf("(IP, %d): %d: %p\n", event->header.misc, - event->ip.pid, (void *)(long)event->ip.ip); + dump_printf("(IP, %d): %d: %#Lx\n", event->header.misc, + event->ip.pid, event->ip.ip); if (event__preprocess_sample(event, session, &al, symbol_filter) < 0) { fprintf(stderr, "problem processing %d event, skipping it.\n", @@ -451,10 +451,10 @@ static void perf_session__find_annotations(struct perf_session *self) } static struct perf_event_ops event_ops = { - .process_sample_event = process_sample_event, - .process_mmap_event = event__process_mmap, - .process_comm_event = event__process_comm, - .process_fork_event = event__process_task, + .sample = process_sample_event, + .mmap = event__process_mmap, + .comm = event__process_comm, + .fork = event__process_task, }; static int __cmd_annotate(void) diff --git a/tools/perf/builtin-buildid-cache.c b/tools/perf/builtin-buildid-cache.c new file mode 100644 index 000000000000..30a05f552c96 --- /dev/null +++ b/tools/perf/builtin-buildid-cache.c @@ -0,0 +1,133 @@ +/* + * builtin-buildid-cache.c + * + * Builtin buildid-cache command: Manages build-id cache + * + * Copyright (C) 2010, Red Hat Inc. + * Copyright (C) 2010, Arnaldo Carvalho de Melo <acme@redhat.com> + */ +#include "builtin.h" +#include "perf.h" +#include "util/cache.h" +#include "util/debug.h" +#include "util/header.h" +#include "util/parse-options.h" +#include "util/strlist.h" +#include "util/symbol.h" + +static char const *add_name_list_str, *remove_name_list_str; + +static const char * const buildid_cache_usage[] = { + "perf buildid-cache [<options>]", + NULL +}; + +static const struct option buildid_cache_options[] = { + OPT_STRING('a', "add", &add_name_list_str, + "file list", "file(s) to add"), + OPT_STRING('r', "remove", &remove_name_list_str, "file list", + "file(s) to remove"), + OPT_BOOLEAN('v', "verbose", &verbose, "be more verbose"), + OPT_END() +}; + +static int build_id_cache__add_file(const char *filename, const char *debugdir) +{ + char sbuild_id[BUILD_ID_SIZE * 2 + 1]; + u8 build_id[BUILD_ID_SIZE]; + int err; + + if (filename__read_build_id(filename, &build_id, sizeof(build_id)) < 0) { + pr_debug("Couldn't read a build-id in %s\n", filename); + return -1; + } + + build_id__sprintf(build_id, sizeof(build_id), sbuild_id); + err = build_id_cache__add_s(sbuild_id, debugdir, filename, false); + if (verbose) + pr_info("Adding %s %s: %s\n", sbuild_id, filename, + err ? "FAIL" : "Ok"); + return err; +} + +static int build_id_cache__remove_file(const char *filename __used, + const char *debugdir __used) +{ + u8 build_id[BUILD_ID_SIZE]; + char sbuild_id[BUILD_ID_SIZE * 2 + 1]; + + int err; + + if (filename__read_build_id(filename, &build_id, sizeof(build_id)) < 0) { + pr_debug("Couldn't read a build-id in %s\n", filename); + return -1; + } + + build_id__sprintf(build_id, sizeof(build_id), sbuild_id); + err = build_id_cache__remove_s(sbuild_id, debugdir); + if (verbose) + pr_info("Removing %s %s: %s\n", sbuild_id, filename, + err ? "FAIL" : "Ok"); + + return err; +} + +static int __cmd_buildid_cache(void) +{ + struct strlist *list; + struct str_node *pos; + char debugdir[PATH_MAX]; + + snprintf(debugdir, sizeof(debugdir), "%s/%s", getenv("HOME"), + DEBUG_CACHE_DIR); + + if (add_name_list_str) { + list = strlist__new(true, add_name_list_str); + if (list) { + strlist__for_each(pos, list) + if (build_id_cache__add_file(pos->s, debugdir)) { + if (errno == EEXIST) { + pr_debug("%s already in the cache\n", + pos->s); + continue; + } + pr_warning("Couldn't add %s: %s\n", + pos->s, strerror(errno)); + } + + strlist__delete(list); + } + } + + if (remove_name_list_str) { + list = strlist__new(true, remove_name_list_str); + if (list) { + strlist__for_each(pos, list) + if (build_id_cache__remove_file(pos->s, debugdir)) { + if (errno == ENOENT) { + pr_debug("%s wasn't in the cache\n", + pos->s); + continue; + } + pr_warning("Couldn't remove %s: %s\n", + pos->s, strerror(errno)); + } + + strlist__delete(list); + } + } + + return 0; +} + +int cmd_buildid_cache(int argc, const char **argv, const char *prefix __used) +{ + argc = parse_options(argc, argv, buildid_cache_options, + buildid_cache_usage, 0); + + if (symbol__init() < 0) + return -1; + + setup_pager(); + return __cmd_buildid_cache(); +} diff --git a/tools/perf/builtin-buildid-list.c b/tools/perf/builtin-buildid-list.c index 1e99ac806913..431f204bde64 100644 --- a/tools/perf/builtin-buildid-list.c +++ b/tools/perf/builtin-buildid-list.c @@ -16,6 +16,7 @@ static char const *input_name = "perf.data"; static int force; +static bool with_hits; static const char * const buildid_list_usage[] = { "perf buildid-list [<options>]", @@ -23,6 +24,7 @@ static const char * const buildid_list_usage[] = { }; static const struct option options[] = { + OPT_BOOLEAN('H', "with-hits", &with_hits, "Show only DSOs with hits"), OPT_STRING('i', "input", &input_name, "file", "input file name"), OPT_BOOLEAN('f', "force", &force, "don't complain, do it"), @@ -31,26 +33,34 @@ static const struct option options[] = { OPT_END() }; -static int perf_file_section__process_buildids(struct perf_file_section *self, - int feat, int fd) +static int build_id_list__process_event(event_t *event, + struct perf_session *session) { - if (feat != HEADER_BUILD_ID) - return 0; + struct addr_location al; + u8 cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK; + struct thread *thread = perf_session__findnew(session, event->ip.pid); - if (lseek(fd, self->offset, SEEK_SET) < 0) { - pr_warning("Failed to lseek to %Ld offset for buildids!\n", - self->offset); + if (thread == NULL) { + pr_err("problem processing %d event, skipping it.\n", + event->header.type); return -1; } - if (perf_header__read_build_ids(fd, self->offset, self->size)) { - pr_warning("Failed to read buildids!\n"); - return -1; - } + thread__find_addr_map(thread, session, cpumode, MAP__FUNCTION, + event->ip.ip, &al); + + if (al.map != NULL) + al.map->dso->hit = 1; return 0; } +static struct perf_event_ops build_id_list__event_ops = { + .sample = build_id_list__process_event, + .mmap = event__process_mmap, + .fork = event__process_task, +}; + static int __cmd_buildid_list(void) { int err = -1; @@ -60,10 +70,10 @@ static int __cmd_buildid_list(void) if (session == NULL) return -1; - err = perf_header__process_sections(&session->header, session->fd, - perf_file_section__process_buildids); - if (err >= 0) - dsos__fprintf_buildid(stdout); + if (with_hits) + perf_session__process_events(session, &build_id_list__event_ops); + + dsos__fprintf_buildid(stdout, with_hits); perf_session__delete(session); return err; diff --git a/tools/perf/builtin-diff.c b/tools/perf/builtin-diff.c index bd71b8ceafb7..18b3f505f9db 100644 --- a/tools/perf/builtin-diff.c +++ b/tools/perf/builtin-diff.c @@ -42,8 +42,8 @@ static int diff__process_sample_event(event_t *event, struct perf_session *sessi struct addr_location al; struct sample_data data = { .period = 1, }; - dump_printf("(IP, %d): %d: %p\n", event->header.misc, - event->ip.pid, (void *)(long)event->ip.ip); + dump_printf("(IP, %d): %d: %#Lx\n", event->header.misc, + event->ip.pid, event->ip.ip); if (event__preprocess_sample(event, session, &al, NULL) < 0) { pr_warning("problem processing %d event, skipping it.\n", @@ -51,12 +51,12 @@ static int diff__process_sample_event(event_t *event, struct perf_session *sessi return -1; } - if (al.filtered) + if (al.filtered || al.sym == NULL) return 0; event__parse_sample(event, session->sample_type, &data); - if (al.sym && perf_session__add_hist_entry(session, &al, data.period)) { + if (perf_session__add_hist_entry(session, &al, data.period)) { pr_warning("problem incrementing symbol count, skipping event\n"); return -1; } @@ -66,12 +66,12 @@ static int diff__process_sample_event(event_t *event, struct perf_session *sessi } static struct perf_event_ops event_ops = { - .process_sample_event = diff__process_sample_event, - .process_mmap_event = event__process_mmap, - .process_comm_event = event__process_comm, - .process_exit_event = event__process_task, - .process_fork_event = event__process_task, - .process_lost_event = event__process_lost, + .sample = diff__process_sample_event, + .mmap = event__process_mmap, + .comm = event__process_comm, + .exit = event__process_task, + .fork = event__process_task, + .lost = event__process_lost, }; static void perf_session__insert_hist_entry_by_name(struct rb_root *root, @@ -82,29 +82,19 @@ static void perf_session__insert_hist_entry_by_name(struct rb_root *root, struct hist_entry *iter; while (*p != NULL) { - int cmp; parent = *p; iter = rb_entry(parent, struct hist_entry, rb_node); - - cmp = strcmp(he->map->dso->name, iter->map->dso->name); - if (cmp > 0) + if (hist_entry__cmp(he, iter) < 0) p = &(*p)->rb_left; - else if (cmp < 0) + else p = &(*p)->rb_right; - else { - cmp = strcmp(he->sym->name, iter->sym->name); - if (cmp > 0) - p = &(*p)->rb_left; - else - p = &(*p)->rb_right; - } } rb_link_node(&he->rb_node, parent, p); rb_insert_color(&he->rb_node, root); } -static void perf_session__resort_by_name(struct perf_session *self) +static void perf_session__resort_hist_entries(struct perf_session *self) { unsigned long position = 1; struct rb_root tmp = RB_ROOT; @@ -122,29 +112,28 @@ static void perf_session__resort_by_name(struct perf_session *self) self->hists = tmp; } +static void perf_session__set_hist_entries_positions(struct perf_session *self) +{ + perf_session__output_resort(self, self->events_stats.total); + perf_session__resort_hist_entries(self); +} + static struct hist_entry * -perf_session__find_hist_entry_by_name(struct perf_session *self, - struct hist_entry *he) +perf_session__find_hist_entry(struct perf_session *self, + struct hist_entry *he) { struct rb_node *n = self->hists.rb_node; while (n) { struct hist_entry *iter = rb_entry(n, struct hist_entry, rb_node); - int cmp = strcmp(he->map->dso->name, iter->map->dso->name); + int64_t cmp = hist_entry__cmp(he, iter); - if (cmp > 0) + if (cmp < 0) n = n->rb_left; - else if (cmp < 0) + else if (cmp > 0) n = n->rb_right; - else { - cmp = strcmp(he->sym->name, iter->sym->name); - if (cmp > 0) - n = n->rb_left; - else if (cmp < 0) - n = n->rb_right; - else - return iter; - } + else + return iter; } return NULL; @@ -155,11 +144,9 @@ static void perf_session__match_hists(struct perf_session *old_session, { struct rb_node *nd; - perf_session__resort_by_name(old_session); - for (nd = rb_first(&new_session->hists); nd; nd = rb_next(nd)) { struct hist_entry *pos = rb_entry(nd, struct hist_entry, rb_node); - pos->pair = perf_session__find_hist_entry_by_name(old_session, pos); + pos->pair = perf_session__find_hist_entry(old_session, pos); } } @@ -177,9 +164,12 @@ static int __cmd_diff(void) ret = perf_session__process_events(session[i], &event_ops); if (ret) goto out_delete; - perf_session__output_resort(session[i], session[i]->events_stats.total); } + perf_session__output_resort(session[1], session[1]->events_stats.total); + if (show_displacement) + perf_session__set_hist_entries_positions(session[0]); + perf_session__match_hists(session[0], session[1]); perf_session__fprintf_hists(session[1], session[0], show_displacement, stdout); @@ -204,7 +194,7 @@ static const struct option options[] = { OPT_BOOLEAN('f', "force", &force, "don't complain, do it"), OPT_BOOLEAN('m', "modules", &symbol_conf.use_modules, "load module symbols - WARNING: use only with -k and LIVE kernel"), - OPT_BOOLEAN('P', "full-paths", &event_ops.full_paths, + OPT_BOOLEAN('P', "full-paths", &symbol_conf.full_paths, "Don't shorten the pathnames taking into account the cwd"), OPT_STRING('d', "dsos", &symbol_conf.dso_list_str, "dso[,dso...]", "only consider symbols in these dsos"), diff --git a/tools/perf/builtin-help.c b/tools/perf/builtin-help.c index 9f810b17c25c..215b584007b1 100644 --- a/tools/perf/builtin-help.c +++ b/tools/perf/builtin-help.c @@ -286,8 +286,7 @@ void list_common_cmds_help(void) puts(" The most commonly used perf commands are:"); for (i = 0; i < ARRAY_SIZE(common_cmds); i++) { - printf(" %s ", common_cmds[i].name); - mput_char(' ', longest - strlen(common_cmds[i].name)); + printf(" %-*s ", longest, common_cmds[i].name); puts(common_cmds[i].help); } } @@ -314,8 +313,6 @@ static const char *cmd_to_page(const char *perf_cmd) return "perf"; else if (!prefixcmp(perf_cmd, "perf")) return perf_cmd; - else if (is_perf_command(perf_cmd)) - return prepend("perf-", perf_cmd); else return prepend("perf-", perf_cmd); } diff --git a/tools/perf/builtin-kmem.c b/tools/perf/builtin-kmem.c index 93c67bf53d2c..5d5dc6b09617 100644 --- a/tools/perf/builtin-kmem.c +++ b/tools/perf/builtin-kmem.c @@ -92,23 +92,18 @@ static void setup_cpunode_map(void) if (!dir1) return; - while (true) { - dent1 = readdir(dir1); - if (!dent1) - break; - - if (sscanf(dent1->d_name, "node%u", &mem) < 1) + while ((dent1 = readdir(dir1)) != NULL) { + if (dent1->d_type != DT_DIR || + sscanf(dent1->d_name, "node%u", &mem) < 1) continue; snprintf(buf, PATH_MAX, "%s/%s", PATH_SYS_NODE, dent1->d_name); dir2 = opendir(buf); if (!dir2) continue; - while (true) { - dent2 = readdir(dir2); - if (!dent2) - break; - if (sscanf(dent2->d_name, "cpu%u", &cpu) < 1) + while ((dent2 = readdir(dir2)) != NULL) { + if (dent2->d_type != DT_LNK || + sscanf(dent2->d_name, "cpu%u", &cpu) < 1) continue; cpunode_map[cpu] = mem; } @@ -321,11 +316,8 @@ static int process_sample_event(event_t *event, struct perf_session *session) event__parse_sample(event, session->sample_type, &data); - dump_printf("(IP, %d): %d/%d: %p period: %Ld\n", - event->header.misc, - data.pid, data.tid, - (void *)(long)data.ip, - (long long)data.period); + dump_printf("(IP, %d): %d/%d: %#Lx period: %Ld\n", event->header.misc, + data.pid, data.tid, data.ip, data.period); thread = perf_session__findnew(session, event->ip.pid); if (thread == NULL) { @@ -342,22 +334,9 @@ static int process_sample_event(event_t *event, struct perf_session *session) return 0; } -static int sample_type_check(struct perf_session *session) -{ - if (!(session->sample_type & PERF_SAMPLE_RAW)) { - fprintf(stderr, - "No trace sample to read. Did you call perf record " - "without -R?"); - return -1; - } - - return 0; -} - static struct perf_event_ops event_ops = { - .process_sample_event = process_sample_event, - .process_comm_event = event__process_comm, - .sample_type_check = sample_type_check, + .sample = process_sample_event, + .comm = event__process_comm, }; static double fragmentation(unsigned long n_req, unsigned long n_alloc) @@ -504,11 +483,14 @@ static void sort_result(void) static int __cmd_kmem(void) { - int err; + int err = -EINVAL; struct perf_session *session = perf_session__new(input_name, O_RDONLY, 0); if (session == NULL) return -ENOMEM; + if (!perf_session__has_traces(session, "kmem record")) + goto out_delete; + setup_pager(); err = perf_session__process_events(session, &event_ops); if (err != 0) diff --git a/tools/perf/builtin-probe.c b/tools/perf/builtin-probe.c index c1e6774fd3ed..34f2acb1cc88 100644 --- a/tools/perf/builtin-probe.c +++ b/tools/perf/builtin-probe.c @@ -55,11 +55,13 @@ static struct { bool need_dwarf; bool list_events; bool force_add; + bool show_lines; int nr_probe; struct probe_point probes[MAX_PROBES]; struct strlist *dellist; struct perf_session *psession; struct map *kmap; + struct line_range line_range; } session; @@ -137,6 +139,16 @@ static int open_vmlinux(void) pr_debug("Try to open %s\n", session.kmap->dso->long_name); return open(session.kmap->dso->long_name, O_RDONLY); } + +static int opt_show_lines(const struct option *opt __used, + const char *str, int unset __used) +{ + if (str) + parse_line_range_desc(str, &session.line_range); + INIT_LIST_HEAD(&session.line_range.line_list); + session.show_lines = true; + return 0; +} #endif static const char * const probe_usage[] = { @@ -144,6 +156,7 @@ static const char * const probe_usage[] = { "perf probe [<options>] --add 'PROBEDEF' [--add 'PROBEDEF' ...]", "perf probe [<options>] --del '[GROUP:]EVENT' ...", "perf probe --list", + "perf probe --line 'LINEDESC'", NULL }; @@ -182,9 +195,32 @@ static const struct option options[] = { opt_add_probe_event), OPT_BOOLEAN('f', "force", &session.force_add, "forcibly add events" " with existing name"), +#ifndef NO_LIBDWARF + OPT_CALLBACK('L', "line", NULL, + "FUNC[:RLN[+NUM|:RLN2]]|SRC:ALN[+NUM|:ALN2]", + "Show source code lines.", opt_show_lines), +#endif OPT_END() }; +/* Initialize symbol maps for vmlinux */ +static void init_vmlinux(void) +{ + symbol_conf.sort_by_name = true; + if (symbol_conf.vmlinux_name == NULL) + symbol_conf.try_vmlinux_path = true; + else + pr_debug("Use vmlinux: %s\n", symbol_conf.vmlinux_name); + if (symbol__init() < 0) + die("Failed to init symbol map."); + session.psession = perf_session__new(NULL, O_WRONLY, false); + if (session.psession == NULL) + die("Failed to init perf_session."); + session.kmap = session.psession->vmlinux_maps[MAP__FUNCTION]; + if (!session.kmap) + die("Could not find kernel map.\n"); +} + int cmd_probe(int argc, const char **argv, const char *prefix __used) { int i, ret; @@ -203,7 +239,8 @@ int cmd_probe(int argc, const char **argv, const char *prefix __used) parse_probe_event_argv(argc, argv); } - if ((!session.nr_probe && !session.dellist && !session.list_events)) + if ((!session.nr_probe && !session.dellist && !session.list_events && + !session.show_lines)) usage_with_options(probe_usage, options); if (debugfs_valid_mountpoint(debugfs_path) < 0) @@ -215,10 +252,34 @@ int cmd_probe(int argc, const char **argv, const char *prefix __used) " --add/--del.\n"); usage_with_options(probe_usage, options); } + if (session.show_lines) { + pr_warning(" Error: Don't use --list with --line.\n"); + usage_with_options(probe_usage, options); + } show_perf_probe_events(); return 0; } +#ifndef NO_LIBDWARF + if (session.show_lines) { + if (session.nr_probe != 0 || session.dellist) { + pr_warning(" Error: Don't use --line with" + " --add/--del.\n"); + usage_with_options(probe_usage, options); + } + init_vmlinux(); + fd = open_vmlinux(); + if (fd < 0) + die("Could not open debuginfo file."); + ret = find_line_range(fd, &session.line_range); + if (ret <= 0) + die("Source line is not found.\n"); + close(fd); + show_line_range(&session.line_range); + return 0; + } +#endif + if (session.dellist) { del_trace_kprobe_events(session.dellist); strlist__delete(session.dellist); @@ -226,20 +287,8 @@ int cmd_probe(int argc, const char **argv, const char *prefix __used) return 0; } - /* Initialize symbol maps for vmlinux */ - symbol_conf.sort_by_name = true; - if (symbol_conf.vmlinux_name == NULL) - symbol_conf.try_vmlinux_path = true; - if (symbol__init() < 0) - die("Failed to init symbol map."); - session.psession = perf_session__new(NULL, O_WRONLY, false); - if (session.psession == NULL) - die("Failed to init perf_session."); - session.kmap = map_groups__find_by_name(&session.psession->kmaps, - MAP__FUNCTION, - "[kernel.kallsyms]"); - if (!session.kmap) - die("Could not find kernel map.\n"); + /* Add probes */ + init_vmlinux(); if (session.need_dwarf) #ifdef NO_LIBDWARF diff --git a/tools/perf/builtin-record.c b/tools/perf/builtin-record.c index 265425322734..90345223908c 100644 --- a/tools/perf/builtin-record.c +++ b/tools/perf/builtin-record.c @@ -113,12 +113,24 @@ static void write_output(void *buf, size_t size) static void write_event(event_t *buf, size_t size) { - /* - * Add it to the list of DSOs, so that when we finish this - * record session we can pick the available build-ids. - */ - if (buf->header.type == PERF_RECORD_MMAP) - dsos__findnew(buf->mmap.filename); + size_t processed_size = buf->header.size; + event_t *ev = buf; + + do { + /* + * Add it to the list of DSOs, so that when we finish this + * record session we can pick the available build-ids. + */ + if (ev->header.type == PERF_RECORD_MMAP) { + struct list_head *head = &dsos__user; + if (ev->header.misc == 1) + head = &dsos__kernel; + __dsos__findnew(head, ev->mmap.filename); + } + + ev = ((void *)ev) + ev->header.size; + processed_size += ev->header.size; + } while (processed_size < size); write_output(buf, size); } @@ -551,6 +563,19 @@ static int __cmd_record(int argc, const char **argv) return err; } + err = event__synthesize_kernel_mmap(process_synthesized_event, + session, "_text"); + if (err < 0) { + pr_err("Couldn't record kernel reference relocation symbol.\n"); + return err; + } + + err = event__synthesize_modules(process_synthesized_event, session); + if (err < 0) { + pr_err("Couldn't record kernel reference relocation symbol.\n"); + return err; + } + if (!system_wide && profile_cpu == -1) event__synthesize_thread(pid, process_synthesized_event, session); diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c index 860f1eeeea7d..cfc655d40bb7 100644 --- a/tools/perf/builtin-report.c +++ b/tools/perf/builtin-report.c @@ -34,6 +34,8 @@ static char const *input_name = "perf.data"; static int force; +static bool hide_unresolved; +static bool dont_use_callchains; static int show_threads; static struct perf_read_values show_threads_values; @@ -91,11 +93,8 @@ static int process_sample_event(event_t *event, struct perf_session *session) event__parse_sample(event, session->sample_type, &data); - dump_printf("(IP, %d): %d/%d: %p period: %Ld\n", - event->header.misc, - data.pid, data.tid, - (void *)(long)data.ip, - (long long)data.period); + dump_printf("(IP, %d): %d/%d: %#Lx period: %Ld\n", event->header.misc, + data.pid, data.tid, data.ip, data.period); if (session->sample_type & PERF_SAMPLE_CALLCHAIN) { unsigned int i; @@ -121,7 +120,7 @@ static int process_sample_event(event_t *event, struct perf_session *session) return -1; } - if (al.filtered) + if (al.filtered || (hide_unresolved && al.sym == NULL)) return 0; if (perf_session__add_hist_entry(session, &al, data.callchain, data.period)) { @@ -156,14 +155,14 @@ static int process_read_event(event_t *event, struct perf_session *session __use return 0; } -static int sample_type_check(struct perf_session *session) +static int perf_session__setup_sample_type(struct perf_session *self) { - if (!(session->sample_type & PERF_SAMPLE_CALLCHAIN)) { + if (!(self->sample_type & PERF_SAMPLE_CALLCHAIN)) { if (sort__has_parent) { fprintf(stderr, "selected --sort parent, but no" " callchain data. Did you call" " perf record without -g?\n"); - return -1; + return -EINVAL; } if (symbol_conf.use_callchain) { fprintf(stderr, "selected -g but no callchain data." @@ -171,12 +170,13 @@ static int sample_type_check(struct perf_session *session) " -g?\n"); return -1; } - } else if (callchain_param.mode != CHAIN_NONE && !symbol_conf.use_callchain) { + } else if (!dont_use_callchains && callchain_param.mode != CHAIN_NONE && + !symbol_conf.use_callchain) { symbol_conf.use_callchain = true; if (register_callchain_param(&callchain_param) < 0) { fprintf(stderr, "Can't register callchain" " params\n"); - return -1; + return -EINVAL; } } @@ -184,20 +184,18 @@ static int sample_type_check(struct perf_session *session) } static struct perf_event_ops event_ops = { - .process_sample_event = process_sample_event, - .process_mmap_event = event__process_mmap, - .process_comm_event = event__process_comm, - .process_exit_event = event__process_task, - .process_fork_event = event__process_task, - .process_lost_event = event__process_lost, - .process_read_event = process_read_event, - .sample_type_check = sample_type_check, + .sample = process_sample_event, + .mmap = event__process_mmap, + .comm = event__process_comm, + .exit = event__process_task, + .fork = event__process_task, + .lost = event__process_lost, + .read = process_read_event, }; - static int __cmd_report(void) { - int ret; + int ret = -EINVAL; struct perf_session *session; session = perf_session__new(input_name, O_RDONLY, force); @@ -207,6 +205,10 @@ static int __cmd_report(void) if (show_threads) perf_read_values_init(&show_threads_values); + ret = perf_session__setup_sample_type(session); + if (ret) + goto out_delete; + ret = perf_session__process_events(session, &event_ops); if (ret) goto out_delete; @@ -243,11 +245,19 @@ out_delete: static int parse_callchain_opt(const struct option *opt __used, const char *arg, - int unset __used) + int unset) { char *tok; char *endptr; + /* + * --no-call-graph + */ + if (unset) { + dont_use_callchains = true; + return 0; + } + symbol_conf.use_callchain = true; if (!arg) @@ -319,7 +329,7 @@ static const struct option options[] = { "pretty printing style key: normal raw"), OPT_STRING('s', "sort", &sort_order, "key[,key2...]", "sort by key(s): pid, comm, dso, symbol, parent"), - OPT_BOOLEAN('P', "full-paths", &event_ops.full_paths, + OPT_BOOLEAN('P', "full-paths", &symbol_conf.full_paths, "Don't shorten the pathnames taking into account the cwd"), OPT_STRING('p', "parent", &parent_pattern, "regex", "regex filter to identify parent, see: '--sort parent'"), @@ -340,6 +350,8 @@ static const struct option options[] = { OPT_STRING('t', "field-separator", &symbol_conf.field_sep, "separator", "separator for columns, no spaces will be added between " "columns '.' is reserved."), + OPT_BOOLEAN('U', "hide-unresolved", &hide_unresolved, + "Only display entries resolved to a symbol"), OPT_END() }; diff --git a/tools/perf/builtin-sched.c b/tools/perf/builtin-sched.c index 80209df6cfe8..4f5a03e43444 100644 --- a/tools/perf/builtin-sched.c +++ b/tools/perf/builtin-sched.c @@ -1621,11 +1621,8 @@ static int process_sample_event(event_t *event, struct perf_session *session) event__parse_sample(event, session->sample_type, &data); - dump_printf("(IP, %d): %d/%d: %p period: %Ld\n", - event->header.misc, - data.pid, data.tid, - (void *)(long)data.ip, - (long long)data.period); + dump_printf("(IP, %d): %d/%d: %#Lx period: %Ld\n", event->header.misc, + data.pid, data.tid, data.ip, data.period); thread = perf_session__findnew(session, data.pid); if (thread == NULL) { @@ -1653,33 +1650,22 @@ static int process_lost_event(event_t *event __used, return 0; } -static int sample_type_check(struct perf_session *session __used) -{ - if (!(session->sample_type & PERF_SAMPLE_RAW)) { - fprintf(stderr, - "No trace sample to read. Did you call perf record " - "without -R?"); - return -1; - } - - return 0; -} - static struct perf_event_ops event_ops = { - .process_sample_event = process_sample_event, - .process_comm_event = event__process_comm, - .process_lost_event = process_lost_event, - .sample_type_check = sample_type_check, + .sample = process_sample_event, + .comm = event__process_comm, + .lost = process_lost_event, }; static int read_events(void) { - int err; + int err = -EINVAL; struct perf_session *session = perf_session__new(input_name, O_RDONLY, 0); if (session == NULL) return -ENOMEM; - err = perf_session__process_events(session, &event_ops); + if (perf_session__has_traces(session, "record -R")) + err = perf_session__process_events(session, &event_ops); + perf_session__delete(session); return err; } diff --git a/tools/perf/builtin-stat.c b/tools/perf/builtin-stat.c index c70d72003557..e8c85d5aec41 100644 --- a/tools/perf/builtin-stat.c +++ b/tools/perf/builtin-stat.c @@ -44,6 +44,7 @@ #include "util/parse-events.h" #include "util/event.h" #include "util/debug.h" +#include "util/header.h" #include <sys/prctl.h> #include <math.h> @@ -79,6 +80,8 @@ static int fd[MAX_NR_CPUS][MAX_COUNTERS]; static int event_scaled[MAX_COUNTERS]; +static volatile int done = 0; + struct stats { double n, mean, M2; @@ -247,61 +250,64 @@ static int run_perf_stat(int argc __used, const char **argv) unsigned long long t0, t1; int status = 0; int counter; - int pid; + int pid = target_pid; int child_ready_pipe[2], go_pipe[2]; + const bool forks = (target_pid == -1 && argc > 0); char buf; if (!system_wide) nr_cpus = 1; - if (pipe(child_ready_pipe) < 0 || pipe(go_pipe) < 0) { + if (forks && (pipe(child_ready_pipe) < 0 || pipe(go_pipe) < 0)) { perror("failed to create pipes"); exit(1); } - if ((pid = fork()) < 0) - perror("failed to fork"); + if (forks) { + if ((pid = fork()) < 0) + perror("failed to fork"); + + if (!pid) { + close(child_ready_pipe[0]); + close(go_pipe[1]); + fcntl(go_pipe[0], F_SETFD, FD_CLOEXEC); + + /* + * Do a dummy execvp to get the PLT entry resolved, + * so we avoid the resolver overhead on the real + * execvp call. + */ + execvp("", (char **)argv); + + /* + * Tell the parent we're ready to go + */ + close(child_ready_pipe[1]); + + /* + * Wait until the parent tells us to go. + */ + if (read(go_pipe[0], &buf, 1) == -1) + perror("unable to read pipe"); + + execvp(argv[0], (char **)argv); + + perror(argv[0]); + exit(-1); + } - if (!pid) { - close(child_ready_pipe[0]); - close(go_pipe[1]); - fcntl(go_pipe[0], F_SETFD, FD_CLOEXEC); + child_pid = pid; /* - * Do a dummy execvp to get the PLT entry resolved, - * so we avoid the resolver overhead on the real - * execvp call. - */ - execvp("", (char **)argv); - - /* - * Tell the parent we're ready to go + * Wait for the child to be ready to exec. */ close(child_ready_pipe[1]); - - /* - * Wait until the parent tells us to go. - */ - if (read(go_pipe[0], &buf, 1) == -1) + close(go_pipe[0]); + if (read(child_ready_pipe[0], &buf, 1) == -1) perror("unable to read pipe"); - - execvp(argv[0], (char **)argv); - - perror(argv[0]); - exit(-1); + close(child_ready_pipe[0]); } - child_pid = pid; - - /* - * Wait for the child to be ready to exec. - */ - close(child_ready_pipe[1]); - close(go_pipe[0]); - if (read(child_ready_pipe[0], &buf, 1) == -1) - perror("unable to read pipe"); - close(child_ready_pipe[0]); - for (counter = 0; counter < nr_counters; counter++) create_perf_stat_counter(counter, pid); @@ -310,8 +316,12 @@ static int run_perf_stat(int argc __used, const char **argv) */ t0 = rdclock(); - close(go_pipe[1]); - wait(&status); + if (forks) { + close(go_pipe[1]); + wait(&status); + } else { + while(!done); + } t1 = rdclock(); @@ -417,10 +427,13 @@ static void print_stat(int argc, const char **argv) fflush(stdout); fprintf(stderr, "\n"); - fprintf(stderr, " Performance counter stats for \'%s", argv[0]); - - for (i = 1; i < argc; i++) - fprintf(stderr, " %s", argv[i]); + fprintf(stderr, " Performance counter stats for "); + if(target_pid == -1) { + fprintf(stderr, "\'%s", argv[0]); + for (i = 1; i < argc; i++) + fprintf(stderr, " %s", argv[i]); + }else + fprintf(stderr, "task pid \'%d", target_pid); fprintf(stderr, "\'"); if (run_count > 1) @@ -445,6 +458,9 @@ static volatile int signr = -1; static void skip_signal(int signo) { + if(target_pid != -1) + done = 1; + signr = signo; } @@ -461,7 +477,7 @@ static void sig_atexit(void) } static const char * const stat_usage[] = { - "perf stat [<options>] <command>", + "perf stat [<options>] [<command>]", NULL }; @@ -492,7 +508,7 @@ int cmd_stat(int argc, const char **argv, const char *prefix __used) argc = parse_options(argc, argv, options, stat_usage, PARSE_OPT_STOP_AT_NON_OPTION); - if (!argc) + if (!argc && target_pid == -1) usage_with_options(stat_usage, options); if (run_count <= 0) usage_with_options(stat_usage, options); diff --git a/tools/perf/builtin-timechart.c b/tools/perf/builtin-timechart.c index 3f8bbcfb1e9b..0d4d8ff7914b 100644 --- a/tools/perf/builtin-timechart.c +++ b/tools/perf/builtin-timechart.c @@ -1029,33 +1029,24 @@ static void process_samples(struct perf_session *session) } } -static int sample_type_check(struct perf_session *session) -{ - if (!(session->sample_type & PERF_SAMPLE_RAW)) { - fprintf(stderr, "No trace samples found in the file.\n" - "Have you used 'perf timechart record' to record it?\n"); - return -1; - } - - return 0; -} - static struct perf_event_ops event_ops = { - .process_comm_event = process_comm_event, - .process_fork_event = process_fork_event, - .process_exit_event = process_exit_event, - .process_sample_event = queue_sample_event, - .sample_type_check = sample_type_check, + .comm = process_comm_event, + .fork = process_fork_event, + .exit = process_exit_event, + .sample = queue_sample_event, }; static int __cmd_timechart(void) { struct perf_session *session = perf_session__new(input_name, O_RDONLY, 0); - int ret; + int ret = -EINVAL; if (session == NULL) return -ENOMEM; + if (!perf_session__has_traces(session, "timechart record")) + goto out_delete; + ret = perf_session__process_events(session, &event_ops); if (ret) goto out_delete; diff --git a/tools/perf/builtin-top.c b/tools/perf/builtin-top.c index ddc584b64871..1fc018e048e1 100644 --- a/tools/perf/builtin-top.c +++ b/tools/perf/builtin-top.c @@ -667,7 +667,7 @@ static void prompt_symbol(struct sym_entry **target, const char *msg) } if (!found) { - fprintf(stderr, "Sorry, %s is not active.\n", sym_filter); + fprintf(stderr, "Sorry, %s is not active.\n", buf); sleep(1); return; } else @@ -934,8 +934,11 @@ static void event__process_sample(const event_t *self, struct addr_location al; u8 origin = self->header.misc & PERF_RECORD_MISC_CPUMODE_MASK; + ++samples; + switch (origin) { case PERF_RECORD_MISC_USER: + ++userspace_samples; if (hide_user_symbols) return; break; @@ -948,9 +951,31 @@ static void event__process_sample(const event_t *self, } if (event__preprocess_sample(self, session, &al, symbol_filter) < 0 || - al.sym == NULL || al.filtered) + al.filtered) return; + if (al.sym == NULL) { + /* + * As we do lazy loading of symtabs we only will know if the + * specified vmlinux file is invalid when we actually have a + * hit in kernel space and then try to load it. So if we get + * here and there are _no_ symbols in the DSO backing the + * kernel map, bail out. + * + * We may never get here, for instance, if we use -K/ + * --hide-kernel-symbols, even if the user specifies an + * invalid --vmlinux ;-) + */ + if (al.map == session->vmlinux_maps[MAP__FUNCTION] && + RB_EMPTY_ROOT(&al.map->dso->symbols[MAP__FUNCTION])) { + pr_err("The %s file can't be used\n", + symbol_conf.vmlinux_name); + exit(1); + } + + return; + } + syme = symbol__priv(al.sym); if (!syme->skip) { syme->count[counter]++; @@ -960,9 +985,6 @@ static void event__process_sample(const event_t *self, if (list_empty(&syme->node) || !syme->node.next) __list_insert_active_sym(syme); pthread_mutex_unlock(&active_symbols_lock); - if (origin == PERF_RECORD_MISC_USER) - ++userspace_samples; - ++samples; } } @@ -975,6 +997,10 @@ static int event__process(event_t *event, struct perf_session *session) case PERF_RECORD_MMAP: event__process_mmap(event, session); break; + case PERF_RECORD_FORK: + case PERF_RECORD_EXIT: + event__process_task(event, session); + break; default: break; } diff --git a/tools/perf/builtin-trace.c b/tools/perf/builtin-trace.c index 574a215e800b..0b65779e3c10 100644 --- a/tools/perf/builtin-trace.c +++ b/tools/perf/builtin-trace.c @@ -75,11 +75,8 @@ static int process_sample_event(event_t *event, struct perf_session *session) event__parse_sample(event, session->sample_type, &data); - dump_printf("(IP, %d): %d/%d: %p period: %Ld\n", - event->header.misc, - data.pid, data.tid, - (void *)(long)data.ip, - (long long)data.period); + dump_printf("(IP, %d): %d/%d: %#Lx period: %Ld\n", event->header.misc, + data.pid, data.tid, data.ip, data.period); thread = perf_session__findnew(session, event->ip.pid); if (thread == NULL) { @@ -103,22 +100,9 @@ static int process_sample_event(event_t *event, struct perf_session *session) return 0; } -static int sample_type_check(struct perf_session *session) -{ - if (!(session->sample_type & PERF_SAMPLE_RAW)) { - fprintf(stderr, - "No trace sample to read. Did you call perf record " - "without -R?"); - return -1; - } - - return 0; -} - static struct perf_event_ops event_ops = { - .process_sample_event = process_sample_event, - .process_comm_event = event__process_comm, - .sample_type_check = sample_type_check, + .sample = process_sample_event, + .comm = event__process_comm, }; static int __cmd_trace(struct perf_session *session) @@ -531,6 +515,8 @@ static const struct option options[] = { parse_scriptname), OPT_STRING('g', "gen-script", &generate_script_lang, "lang", "generate perf-trace.xx script in specified language"), + OPT_STRING('i', "input", &input_name, "file", + "input file name"), OPT_END() }; @@ -592,6 +578,9 @@ int cmd_trace(int argc, const char **argv, const char *prefix __used) if (session == NULL) return -ENOMEM; + if (!perf_session__has_traces(session, "record -R")) + return -EINVAL; + if (generate_script_lang) { struct stat perf_stat; diff --git a/tools/perf/builtin.h b/tools/perf/builtin.h index 18035b1f16c7..dee97cfe3794 100644 --- a/tools/perf/builtin.h +++ b/tools/perf/builtin.h @@ -16,6 +16,7 @@ extern int check_pager_config(const char *cmd); extern int cmd_annotate(int argc, const char **argv, const char *prefix); extern int cmd_bench(int argc, const char **argv, const char *prefix); +extern int cmd_buildid_cache(int argc, const char **argv, const char *prefix); extern int cmd_buildid_list(int argc, const char **argv, const char *prefix); extern int cmd_diff(int argc, const char **argv, const char *prefix); extern int cmd_help(int argc, const char **argv, const char *prefix); diff --git a/tools/perf/command-list.txt b/tools/perf/command-list.txt index 71dc7c3fe7b2..9afcff2e3ae5 100644 --- a/tools/perf/command-list.txt +++ b/tools/perf/command-list.txt @@ -3,7 +3,9 @@ # command name category [deprecated] [common] # perf-annotate mainporcelain common +perf-archive mainporcelain common perf-bench mainporcelain common +perf-buildid-cache mainporcelain common perf-buildid-list mainporcelain common perf-diff mainporcelain common perf-list mainporcelain common diff --git a/tools/perf/design.txt b/tools/perf/design.txt index 8d0de5130db3..bd0bb1b1279b 100644 --- a/tools/perf/design.txt +++ b/tools/perf/design.txt @@ -101,10 +101,10 @@ enum hw_event_ids { */ PERF_COUNT_HW_CPU_CYCLES = 0, PERF_COUNT_HW_INSTRUCTIONS = 1, - PERF_COUNT_HW_CACHE_REFERENCES = 2, + PERF_COUNT_HW_CACHE_REFERENCES = 2, PERF_COUNT_HW_CACHE_MISSES = 3, PERF_COUNT_HW_BRANCH_INSTRUCTIONS = 4, - PERF_COUNT_HW_BRANCH_MISSES = 5, + PERF_COUNT_HW_BRANCH_MISSES = 5, PERF_COUNT_HW_BUS_CYCLES = 6, }; @@ -131,8 +131,8 @@ software events, selected by 'event_id': */ enum sw_event_ids { PERF_COUNT_SW_CPU_CLOCK = 0, - PERF_COUNT_SW_TASK_CLOCK = 1, - PERF_COUNT_SW_PAGE_FAULTS = 2, + PERF_COUNT_SW_TASK_CLOCK = 1, + PERF_COUNT_SW_PAGE_FAULTS = 2, PERF_COUNT_SW_CONTEXT_SWITCHES = 3, PERF_COUNT_SW_CPU_MIGRATIONS = 4, PERF_COUNT_SW_PAGE_FAULTS_MIN = 5, diff --git a/tools/perf/perf-archive.sh b/tools/perf/perf-archive.sh new file mode 100644 index 000000000000..45fbe2f07b15 --- /dev/null +++ b/tools/perf/perf-archive.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# perf archive +# Arnaldo Carvalho de Melo <acme@redhat.com> + +PERF_DATA=perf.data +if [ $# -ne 0 ] ; then + PERF_DATA=$1 +fi + +DEBUGDIR=~/.debug/ +BUILDIDS=$(mktemp /tmp/perf-archive-buildids.XXXXXX) + +perf buildid-list -i $PERF_DATA --with-hits > $BUILDIDS +if [ ! -s $BUILDIDS ] ; then + echo "perf archive: no build-ids found" + rm -f $BUILDIDS + exit 1 +fi + +MANIFEST=$(mktemp /tmp/perf-archive-manifest.XXXXXX) + +cut -d ' ' -f 1 $BUILDIDS | \ +while read build_id ; do + linkname=$DEBUGDIR.build-id/${build_id:0:2}/${build_id:2} + filename=$(readlink -f $linkname) + echo ${linkname#$DEBUGDIR} >> $MANIFEST + echo ${filename#$DEBUGDIR} >> $MANIFEST +done + +tar cfj $PERF_DATA.tar.bz2 -C $DEBUGDIR -T $MANIFEST +rm -f $MANIFEST $BUILDIDS +exit 0 diff --git a/tools/perf/perf.c b/tools/perf/perf.c index 873e55fab375..05c861c045d5 100644 --- a/tools/perf/perf.c +++ b/tools/perf/perf.c @@ -285,6 +285,7 @@ static void handle_internal_command(int argc, const char **argv) { const char *cmd = argv[0]; static struct cmd_struct commands[] = { + { "buildid-cache", cmd_buildid_cache, 0 }, { "buildid-list", cmd_buildid_list, 0 }, { "diff", cmd_diff, 0 }, { "help", cmd_help, 0 }, @@ -388,7 +389,7 @@ static int run_argv(int *argcp, const char ***argv) /* mini /proc/mounts parser: searching for "^blah /mount/point debugfs" */ static void get_debugfs_mntpt(void) { - const char *path = debugfs_find_mountpoint(); + const char *path = debugfs_mount(NULL); if (path) strncpy(debugfs_mntpt, path, sizeof(debugfs_mntpt)); diff --git a/tools/perf/util/data_map.c b/tools/perf/util/data_map.c deleted file mode 100644 index b557b836de3d..000000000000 --- a/tools/perf/util/data_map.c +++ /dev/null @@ -1,252 +0,0 @@ -#include "symbol.h" -#include "util.h" -#include "debug.h" -#include "thread.h" -#include "session.h" - -static int process_event_stub(event_t *event __used, - struct perf_session *session __used) -{ - dump_printf(": unhandled!\n"); - return 0; -} - -static void perf_event_ops__fill_defaults(struct perf_event_ops *handler) -{ - if (!handler->process_sample_event) - handler->process_sample_event = process_event_stub; - if (!handler->process_mmap_event) - handler->process_mmap_event = process_event_stub; - if (!handler->process_comm_event) - handler->process_comm_event = process_event_stub; - if (!handler->process_fork_event) - handler->process_fork_event = process_event_stub; - if (!handler->process_exit_event) - handler->process_exit_event = process_event_stub; - if (!handler->process_lost_event) - handler->process_lost_event = process_event_stub; - if (!handler->process_read_event) - handler->process_read_event = process_event_stub; - if (!handler->process_throttle_event) - handler->process_throttle_event = process_event_stub; - if (!handler->process_unthrottle_event) - handler->process_unthrottle_event = process_event_stub; -} - -static const char *event__name[] = { - [0] = "TOTAL", - [PERF_RECORD_MMAP] = "MMAP", - [PERF_RECORD_LOST] = "LOST", - [PERF_RECORD_COMM] = "COMM", - [PERF_RECORD_EXIT] = "EXIT", - [PERF_RECORD_THROTTLE] = "THROTTLE", - [PERF_RECORD_UNTHROTTLE] = "UNTHROTTLE", - [PERF_RECORD_FORK] = "FORK", - [PERF_RECORD_READ] = "READ", - [PERF_RECORD_SAMPLE] = "SAMPLE", -}; - -unsigned long event__total[PERF_RECORD_MAX]; - -void event__print_totals(void) -{ - int i; - for (i = 0; i < PERF_RECORD_MAX; ++i) - pr_info("%10s events: %10ld\n", - event__name[i], event__total[i]); -} - -static int process_event(event_t *event, struct perf_session *session, - struct perf_event_ops *ops, - unsigned long offset, unsigned long head) -{ - trace_event(event); - - if (event->header.type < PERF_RECORD_MAX) { - dump_printf("%p [%p]: PERF_RECORD_%s", - (void *)(offset + head), - (void *)(long)(event->header.size), - event__name[event->header.type]); - ++event__total[0]; - ++event__total[event->header.type]; - } - - switch (event->header.type) { - case PERF_RECORD_SAMPLE: - return ops->process_sample_event(event, session); - case PERF_RECORD_MMAP: - return ops->process_mmap_event(event, session); - case PERF_RECORD_COMM: - return ops->process_comm_event(event, session); - case PERF_RECORD_FORK: - return ops->process_fork_event(event, session); - case PERF_RECORD_EXIT: - return ops->process_exit_event(event, session); - case PERF_RECORD_LOST: - return ops->process_lost_event(event, session); - case PERF_RECORD_READ: - return ops->process_read_event(event, session); - case PERF_RECORD_THROTTLE: - return ops->process_throttle_event(event, session); - case PERF_RECORD_UNTHROTTLE: - return ops->process_unthrottle_event(event, session); - default: - ops->total_unknown++; - return -1; - } -} - -int perf_header__read_build_ids(int input, u64 offset, u64 size) -{ - struct build_id_event bev; - char filename[PATH_MAX]; - u64 limit = offset + size; - int err = -1; - - while (offset < limit) { - struct dso *dso; - ssize_t len; - - if (read(input, &bev, sizeof(bev)) != sizeof(bev)) - goto out; - - len = bev.header.size - sizeof(bev); - if (read(input, filename, len) != len) - goto out; - - dso = dsos__findnew(filename); - if (dso != NULL) - dso__set_build_id(dso, &bev.build_id); - - offset += bev.header.size; - } - err = 0; -out: - return err; -} - -static struct thread *perf_session__register_idle_thread(struct perf_session *self) -{ - struct thread *thread = perf_session__findnew(self, 0); - - if (!thread || thread__set_comm(thread, "swapper")) { - pr_err("problem inserting idle task.\n"); - thread = NULL; - } - - return thread; -} - -int perf_session__process_events(struct perf_session *self, - struct perf_event_ops *ops) -{ - int err; - unsigned long head, shift; - unsigned long offset = 0; - size_t page_size; - event_t *event; - uint32_t size; - char *buf; - - if (perf_session__register_idle_thread(self) == NULL) - return -ENOMEM; - - perf_event_ops__fill_defaults(ops); - - page_size = getpagesize(); - - head = self->header.data_offset; - self->sample_type = perf_header__sample_type(&self->header); - - err = -EINVAL; - if (ops->sample_type_check && ops->sample_type_check(self) < 0) - goto out_err; - - if (!ops->full_paths) { - char bf[PATH_MAX]; - - if (getcwd(bf, sizeof(bf)) == NULL) { - err = -errno; -out_getcwd_err: - pr_err("failed to get the current directory\n"); - goto out_err; - } - self->cwd = strdup(bf); - if (self->cwd == NULL) { - err = -ENOMEM; - goto out_getcwd_err; - } - self->cwdlen = strlen(self->cwd); - } - - shift = page_size * (head / page_size); - offset += shift; - head -= shift; - -remap: - buf = mmap(NULL, page_size * self->mmap_window, PROT_READ, - MAP_SHARED, self->fd, offset); - if (buf == MAP_FAILED) { - pr_err("failed to mmap file\n"); - err = -errno; - goto out_err; - } - -more: - event = (event_t *)(buf + head); - - size = event->header.size; - if (!size) - size = 8; - - if (head + event->header.size >= page_size * self->mmap_window) { - int munmap_ret; - - shift = page_size * (head / page_size); - - munmap_ret = munmap(buf, page_size * self->mmap_window); - assert(munmap_ret == 0); - - offset += shift; - head -= shift; - goto remap; - } - - size = event->header.size; - - dump_printf("\n%p [%p]: event: %d\n", - (void *)(offset + head), - (void *)(long)event->header.size, - event->header.type); - - if (!size || process_event(event, self, ops, offset, head) < 0) { - - dump_printf("%p [%p]: skipping unknown header type: %d\n", - (void *)(offset + head), - (void *)(long)(event->header.size), - event->header.type); - - /* - * assume we lost track of the stream, check alignment, and - * increment a single u64 in the hope to catch on again 'soon'. - */ - - if (unlikely(head & 7)) - head &= ~7ULL; - - size = 8; - } - - head += size; - - if (offset + head >= self->header.data_offset + self->header.data_size) - goto done; - - if (offset + head < self->size) - goto more; - -done: - err = 0; -out_err: - return err; -} diff --git a/tools/perf/util/debug.c b/tools/perf/util/debug.c index 28d520d5a1fb..0905600c3851 100644 --- a/tools/perf/util/debug.c +++ b/tools/perf/util/debug.c @@ -9,6 +9,7 @@ #include "color.h" #include "event.h" #include "debug.h" +#include "util.h" int verbose = 0; int dump_trace = 0; diff --git a/tools/perf/util/debugfs.c b/tools/perf/util/debugfs.c index 06b73ee02c49..a88fefc0cc0a 100644 --- a/tools/perf/util/debugfs.c +++ b/tools/perf/util/debugfs.c @@ -106,16 +106,14 @@ int debugfs_valid_entry(const char *path) return 0; } -/* mount the debugfs somewhere */ +/* mount the debugfs somewhere if it's not mounted */ -int debugfs_mount(const char *mountpoint) +char *debugfs_mount(const char *mountpoint) { - char mountcmd[128]; - /* see if it's already mounted */ if (debugfs_find_mountpoint()) { debugfs_premounted = 1; - return 0; + return debugfs_mountpoint; } /* if not mounted and no argument */ @@ -127,13 +125,14 @@ int debugfs_mount(const char *mountpoint) mountpoint = "/sys/kernel/debug"; } + if (mount(NULL, mountpoint, "debugfs", 0, NULL) < 0) + return NULL; + /* save the mountpoint */ strncpy(debugfs_mountpoint, mountpoint, sizeof(debugfs_mountpoint)); + debugfs_found = 1; - /* mount it */ - snprintf(mountcmd, sizeof(mountcmd), - "/bin/mount -t debugfs debugfs %s", mountpoint); - return system(mountcmd); + return debugfs_mountpoint; } /* umount the debugfs */ diff --git a/tools/perf/util/debugfs.h b/tools/perf/util/debugfs.h index 3cd14f9ae784..83a02879745f 100644 --- a/tools/perf/util/debugfs.h +++ b/tools/perf/util/debugfs.h @@ -15,7 +15,7 @@ extern const char *debugfs_find_mountpoint(void); extern int debugfs_valid_mountpoint(const char *debugfs); extern int debugfs_valid_entry(const char *path); -extern int debugfs_mount(const char *mountpoint); +extern char *debugfs_mount(const char *mountpoint); extern int debugfs_umount(void); extern int debugfs_write(const char *entry, const char *value); extern int debugfs_read(const char *entry, char *buffer, size_t size); diff --git a/tools/perf/util/event.c b/tools/perf/util/event.c index bb0fd6da2d56..bbaee61c1683 100644 --- a/tools/perf/util/event.c +++ b/tools/perf/util/event.c @@ -8,8 +8,7 @@ #include "thread.h" static pid_t event__synthesize_comm(pid_t pid, int full, - int (*process)(event_t *event, - struct perf_session *session), + event__handler_t process, struct perf_session *session) { event_t ev; @@ -91,8 +90,7 @@ out_failure: } static int event__synthesize_mmap_events(pid_t pid, pid_t tgid, - int (*process)(event_t *event, - struct perf_session *session), + event__handler_t process, struct perf_session *session) { char filename[PATH_MAX]; @@ -112,7 +110,10 @@ static int event__synthesize_mmap_events(pid_t pid, pid_t tgid, while (1) { char bf[BUFSIZ], *pbf = bf; event_t ev = { - .header = { .type = PERF_RECORD_MMAP }, + .header = { + .type = PERF_RECORD_MMAP, + .misc = 0, /* Just like the kernel, see kernel/perf_event.c __perf_event_mmap */ + }, }; int n; size_t size; @@ -156,9 +157,38 @@ static int event__synthesize_mmap_events(pid_t pid, pid_t tgid, return 0; } -int event__synthesize_thread(pid_t pid, - int (*process)(event_t *event, - struct perf_session *session), +int event__synthesize_modules(event__handler_t process, + struct perf_session *session) +{ + struct rb_node *nd; + + for (nd = rb_first(&session->kmaps.maps[MAP__FUNCTION]); + nd; nd = rb_next(nd)) { + event_t ev; + size_t size; + struct map *pos = rb_entry(nd, struct map, rb_node); + + if (pos->dso->kernel) + continue; + + size = ALIGN(pos->dso->long_name_len + 1, sizeof(u64)); + memset(&ev, 0, sizeof(ev)); + ev.mmap.header.misc = 1; /* kernel uses 0 for user space maps, see kernel/perf_event.c __perf_event_mmap */ + ev.mmap.header.type = PERF_RECORD_MMAP; + ev.mmap.header.size = (sizeof(ev.mmap) - + (sizeof(ev.mmap.filename) - size)); + ev.mmap.start = pos->start; + ev.mmap.len = pos->end - pos->start; + + memcpy(ev.mmap.filename, pos->dso->long_name, + pos->dso->long_name_len + 1); + process(&ev, session); + } + + return 0; +} + +int event__synthesize_thread(pid_t pid, event__handler_t process, struct perf_session *session) { pid_t tgid = event__synthesize_comm(pid, 1, process, session); @@ -167,8 +197,7 @@ int event__synthesize_thread(pid_t pid, return event__synthesize_mmap_events(pid, tgid, process, session); } -void event__synthesize_threads(int (*process)(event_t *event, - struct perf_session *session), +void event__synthesize_threads(event__handler_t process, struct perf_session *session) { DIR *proc; @@ -189,6 +218,59 @@ void event__synthesize_threads(int (*process)(event_t *event, closedir(proc); } +struct process_symbol_args { + const char *name; + u64 start; +}; + +static int find_symbol_cb(void *arg, const char *name, char type, u64 start) +{ + struct process_symbol_args *args = arg; + + /* + * Must be a function or at least an alias, as in PARISC64, where "_text" is + * an 'A' to the same address as "_stext". + */ + if (!(symbol_type__is_a(type, MAP__FUNCTION) || + type == 'A') || strcmp(name, args->name)) + return 0; + + args->start = start; + return 1; +} + +int event__synthesize_kernel_mmap(event__handler_t process, + struct perf_session *session, + const char *symbol_name) +{ + size_t size; + event_t ev = { + .header = { + .type = PERF_RECORD_MMAP, + .misc = 1, /* kernel uses 0 for user space maps, see kernel/perf_event.c __perf_event_mmap */ + }, + }; + /* + * We should get this from /sys/kernel/sections/.text, but till that is + * available use this, and after it is use this as a fallback for older + * kernels. + */ + struct process_symbol_args args = { .name = symbol_name, }; + + if (kallsyms__parse("/proc/kallsyms", &args, find_symbol_cb) <= 0) + return -ENOENT; + + size = snprintf(ev.mmap.filename, sizeof(ev.mmap.filename), + "[kernel.kallsyms.%s]", symbol_name) + 1; + size = ALIGN(size, sizeof(u64)); + ev.mmap.header.size = (sizeof(ev.mmap) - (sizeof(ev.mmap.filename) - size)); + ev.mmap.pgoff = args.start; + ev.mmap.start = session->vmlinux_maps[MAP__FUNCTION]->start; + ev.mmap.len = session->vmlinux_maps[MAP__FUNCTION]->end - ev.mmap.start ; + + return process(&ev, session); +} + static void thread__comm_adjust(struct thread *self) { char *comm = self->comm; @@ -240,22 +322,84 @@ int event__process_lost(event_t *self, struct perf_session *session) int event__process_mmap(event_t *self, struct perf_session *session) { - struct thread *thread = perf_session__findnew(session, self->mmap.pid); - struct map *map = map__new(&self->mmap, MAP__FUNCTION, - session->cwd, session->cwdlen); + struct thread *thread; + struct map *map; + + dump_printf(" %d/%d: [%#Lx(%#Lx) @ %#Lx]: %s\n", + self->mmap.pid, self->mmap.tid, self->mmap.start, + self->mmap.len, self->mmap.pgoff, self->mmap.filename); + + if (self->mmap.pid == 0) { + static const char kmmap_prefix[] = "[kernel.kallsyms."; + + if (self->mmap.filename[0] == '/') { + char short_module_name[1024]; + char *name = strrchr(self->mmap.filename, '/'), *dot; + + if (name == NULL) + goto out_problem; + + ++name; /* skip / */ + dot = strrchr(name, '.'); + if (dot == NULL) + goto out_problem; + + snprintf(short_module_name, sizeof(short_module_name), + "[%.*s]", (int)(dot - name), name); + strxfrchar(short_module_name, '-', '_'); + + map = perf_session__new_module_map(session, + self->mmap.start, + self->mmap.filename); + if (map == NULL) + goto out_problem; + + name = strdup(short_module_name); + if (name == NULL) + goto out_problem; + + map->dso->short_name = name; + map->end = map->start + self->mmap.len; + } else if (memcmp(self->mmap.filename, kmmap_prefix, + sizeof(kmmap_prefix) - 1) == 0) { + const char *symbol_name = (self->mmap.filename + + sizeof(kmmap_prefix) - 1); + /* + * Should be there already, from the build-id table in + * the header. + */ + struct dso *kernel = __dsos__findnew(&dsos__kernel, + "[kernel.kallsyms]"); + if (kernel == NULL) + goto out_problem; + + kernel->kernel = 1; + if (__map_groups__create_kernel_maps(&session->kmaps, + session->vmlinux_maps, + kernel) < 0) + goto out_problem; + + session->vmlinux_maps[MAP__FUNCTION]->start = self->mmap.start; + session->vmlinux_maps[MAP__FUNCTION]->end = self->mmap.start + self->mmap.len; + + perf_session__set_kallsyms_ref_reloc_sym(session, symbol_name, + self->mmap.pgoff); + } + return 0; + } - dump_printf(" %d/%d: [%p(%p) @ %p]: %s\n", - self->mmap.pid, self->mmap.tid, - (void *)(long)self->mmap.start, - (void *)(long)self->mmap.len, - (void *)(long)self->mmap.pgoff, - self->mmap.filename); + thread = perf_session__findnew(session, self->mmap.pid); + map = map__new(&self->mmap, MAP__FUNCTION, + session->cwd, session->cwdlen); if (thread == NULL || map == NULL) - dump_printf("problem processing PERF_RECORD_MMAP, skipping event.\n"); - else - thread__insert_map(thread, map); + goto out_problem; + thread__insert_map(thread, map); + return 0; + +out_problem: + dump_printf("problem processing PERF_RECORD_MMAP, skipping event.\n"); return 0; } @@ -284,11 +428,10 @@ int event__process_task(event_t *self, struct perf_session *session) return 0; } -void thread__find_addr_location(struct thread *self, - struct perf_session *session, u8 cpumode, - enum map_type type, u64 addr, - struct addr_location *al, - symbol_filter_t filter) +void thread__find_addr_map(struct thread *self, + struct perf_session *session, u8 cpumode, + enum map_type type, u64 addr, + struct addr_location *al) { struct map_groups *mg = &self->mg; @@ -303,7 +446,6 @@ void thread__find_addr_location(struct thread *self, else { al->level = 'H'; al->map = NULL; - al->sym = NULL; return; } try_again: @@ -322,11 +464,21 @@ try_again: mg = &session->kmaps; goto try_again; } - al->sym = NULL; - } else { + } else al->addr = al->map->map_ip(al->map, al->addr); +} + +void thread__find_addr_location(struct thread *self, + struct perf_session *session, u8 cpumode, + enum map_type type, u64 addr, + struct addr_location *al, + symbol_filter_t filter) +{ + thread__find_addr_map(self, session, cpumode, type, addr, al); + if (al->map != NULL) al->sym = map__find_symbol(al->map, session, al->addr, filter); - } + else + al->sym = NULL; } static void dso__calc_col_width(struct dso *self) diff --git a/tools/perf/util/event.h b/tools/perf/util/event.h index 690a96d0467c..50a7132887f5 100644 --- a/tools/perf/util/event.h +++ b/tools/perf/util/event.h @@ -1,10 +1,10 @@ #ifndef __PERF_RECORD_H #define __PERF_RECORD_H +#include <limits.h> + #include "../perf.h" -#include "util.h" -#include <linux/list.h> -#include <linux/rbtree.h> +#include "map.h" /* * PERF_SAMPLE_IP | PERF_SAMPLE_TID | * @@ -101,74 +101,19 @@ struct events_stats { void event__print_totals(void); -enum map_type { - MAP__FUNCTION = 0, - MAP__VARIABLE, -}; - -#define MAP__NR_TYPES (MAP__VARIABLE + 1) - -struct map { - union { - struct rb_node rb_node; - struct list_head node; - }; - u64 start; - u64 end; - enum map_type type; - u64 pgoff; - u64 (*map_ip)(struct map *, u64); - u64 (*unmap_ip)(struct map *, u64); - struct dso *dso; -}; - -static inline u64 map__map_ip(struct map *map, u64 ip) -{ - return ip - map->start + map->pgoff; -} - -static inline u64 map__unmap_ip(struct map *map, u64 ip) -{ - return ip + map->start - map->pgoff; -} - -static inline u64 identity__map_ip(struct map *map __used, u64 ip) -{ - return ip; -} - -struct symbol; - -typedef int (*symbol_filter_t)(struct map *map, struct symbol *sym); - -void map__init(struct map *self, enum map_type type, - u64 start, u64 end, u64 pgoff, struct dso *dso); -struct map *map__new(struct mmap_event *event, enum map_type, - char *cwd, int cwdlen); -void map__delete(struct map *self); -struct map *map__clone(struct map *self); -int map__overlap(struct map *l, struct map *r); -size_t map__fprintf(struct map *self, FILE *fp); - struct perf_session; -int map__load(struct map *self, struct perf_session *session, - symbol_filter_t filter); -struct symbol *map__find_symbol(struct map *self, struct perf_session *session, - u64 addr, symbol_filter_t filter); -struct symbol *map__find_symbol_by_name(struct map *self, const char *name, - struct perf_session *session, - symbol_filter_t filter); -void map__fixup_start(struct map *self); -void map__fixup_end(struct map *self); - -int event__synthesize_thread(pid_t pid, - int (*process)(event_t *event, - struct perf_session *session), +typedef int (*event__handler_t)(event_t *event, struct perf_session *session); + +int event__synthesize_thread(pid_t pid, event__handler_t process, struct perf_session *session); -void event__synthesize_threads(int (*process)(event_t *event, - struct perf_session *session), +void event__synthesize_threads(event__handler_t process, struct perf_session *session); +int event__synthesize_kernel_mmap(event__handler_t process, + struct perf_session *session, + const char *symbol_name); +int event__synthesize_modules(event__handler_t process, + struct perf_session *session); int event__process_comm(event_t *self, struct perf_session *session); int event__process_lost(event_t *self, struct perf_session *session); diff --git a/tools/perf/util/header.c b/tools/perf/util/header.c index 8a0bca55106f..2bb2bdb1f456 100644 --- a/tools/perf/util/header.c +++ b/tools/perf/util/header.c @@ -1,8 +1,10 @@ #include <sys/types.h> +#include <byteswap.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <linux/list.h> +#include <linux/kernel.h> #include "util.h" #include "header.h" @@ -105,24 +107,28 @@ struct perf_trace_event_type { static int event_count; static struct perf_trace_event_type *events; -void perf_header__push_event(u64 id, const char *name) +int perf_header__push_event(u64 id, const char *name) { if (strlen(name) > MAX_EVENT_NAME) pr_warning("Event %s will be truncated\n", name); if (!events) { events = malloc(sizeof(struct perf_trace_event_type)); - if (!events) - die("nomem"); + if (events == NULL) + return -ENOMEM; } else { - events = realloc(events, (event_count + 1) * sizeof(struct perf_trace_event_type)); - if (!events) - die("nomem"); + struct perf_trace_event_type *nevents; + + nevents = realloc(events, (event_count + 1) * sizeof(*events)); + if (nevents == NULL) + return -ENOMEM; + events = nevents; } memset(&events[event_count], 0, sizeof(struct perf_trace_event_type)); events[event_count].event_id = id; strncpy(events[event_count].name, name, MAX_EVENT_NAME - 1); event_count++; + return 0; } char *perf_header__find_event(u64 id) @@ -169,31 +175,45 @@ static int do_write(int fd, const void *buf, size_t size) return 0; } -static int __dsos__write_buildid_table(struct list_head *head, int fd) +#define NAME_ALIGN 64 + +static int write_padded(int fd, const void *bf, size_t count, + size_t count_aligned) { -#define NAME_ALIGN 64 - struct dso *pos; static const char zero_buf[NAME_ALIGN]; + int err = do_write(fd, bf, count); + + if (!err) + err = do_write(fd, zero_buf, count_aligned - count); + + return err; +} - list_for_each_entry(pos, head, node) { +#define dsos__for_each_with_build_id(pos, head) \ + list_for_each_entry(pos, head, node) \ + if (!pos->has_build_id) \ + continue; \ + else + +static int __dsos__write_buildid_table(struct list_head *head, u16 misc, int fd) +{ + struct dso *pos; + + dsos__for_each_with_build_id(pos, head) { int err; struct build_id_event b; - size_t len; + size_t len = pos->long_name_len + 1; - if (!pos->has_build_id) - continue; - len = pos->long_name_len + 1; len = ALIGN(len, NAME_ALIGN); memset(&b, 0, sizeof(b)); memcpy(&b.build_id, pos->build_id, sizeof(pos->build_id)); + b.header.misc = misc; b.header.size = sizeof(b) + len; err = do_write(fd, &b, sizeof(b)); if (err < 0) return err; - err = do_write(fd, pos->long_name, pos->long_name_len + 1); - if (err < 0) - return err; - err = do_write(fd, zero_buf, len - pos->long_name_len - 1); + err = write_padded(fd, pos->long_name, + pos->long_name_len + 1, len); if (err < 0) return err; } @@ -203,12 +223,143 @@ static int __dsos__write_buildid_table(struct list_head *head, int fd) static int dsos__write_buildid_table(int fd) { - int err = __dsos__write_buildid_table(&dsos__kernel, fd); + int err = __dsos__write_buildid_table(&dsos__kernel, + PERF_RECORD_MISC_KERNEL, fd); if (err == 0) - err = __dsos__write_buildid_table(&dsos__user, fd); + err = __dsos__write_buildid_table(&dsos__user, + PERF_RECORD_MISC_USER, fd); + return err; +} + +int build_id_cache__add_s(const char *sbuild_id, const char *debugdir, + const char *name, bool is_kallsyms) +{ + const size_t size = PATH_MAX; + char *filename = malloc(size), + *linkname = malloc(size), *targetname; + int len, err = -1; + + if (filename == NULL || linkname == NULL) + goto out_free; + + len = snprintf(filename, size, "%s%s%s", + debugdir, is_kallsyms ? "/" : "", name); + if (mkdir_p(filename, 0755)) + goto out_free; + + snprintf(filename + len, sizeof(filename) - len, "/%s", sbuild_id); + + if (access(filename, F_OK)) { + if (is_kallsyms) { + if (copyfile("/proc/kallsyms", filename)) + goto out_free; + } else if (link(name, filename) && copyfile(name, filename)) + goto out_free; + } + + len = snprintf(linkname, size, "%s/.build-id/%.2s", + debugdir, sbuild_id); + + if (access(linkname, X_OK) && mkdir_p(linkname, 0755)) + goto out_free; + + snprintf(linkname + len, size - len, "/%s", sbuild_id + 2); + targetname = filename + strlen(debugdir) - 5; + memcpy(targetname, "../..", 5); + + if (symlink(targetname, linkname) == 0) + err = 0; +out_free: + free(filename); + free(linkname); + return err; +} + +static int build_id_cache__add_b(const u8 *build_id, size_t build_id_size, + const char *name, const char *debugdir, + bool is_kallsyms) +{ + char sbuild_id[BUILD_ID_SIZE * 2 + 1]; + + build_id__sprintf(build_id, build_id_size, sbuild_id); + + return build_id_cache__add_s(sbuild_id, debugdir, name, is_kallsyms); +} + +int build_id_cache__remove_s(const char *sbuild_id, const char *debugdir) +{ + const size_t size = PATH_MAX; + char *filename = malloc(size), + *linkname = malloc(size); + int err = -1; + + if (filename == NULL || linkname == NULL) + goto out_free; + + snprintf(linkname, size, "%s/.build-id/%.2s/%s", + debugdir, sbuild_id, sbuild_id + 2); + + if (access(linkname, F_OK)) + goto out_free; + + if (readlink(linkname, filename, size) < 0) + goto out_free; + + if (unlink(linkname)) + goto out_free; + + /* + * Since the link is relative, we must make it absolute: + */ + snprintf(linkname, size, "%s/.build-id/%.2s/%s", + debugdir, sbuild_id, filename); + + if (unlink(linkname)) + goto out_free; + + err = 0; +out_free: + free(filename); + free(linkname); + return err; +} + +static int dso__cache_build_id(struct dso *self, const char *debugdir) +{ + bool is_kallsyms = self->kernel && self->long_name[0] != '/'; + + return build_id_cache__add_b(self->build_id, sizeof(self->build_id), + self->long_name, debugdir, is_kallsyms); +} + +static int __dsos__cache_build_ids(struct list_head *head, const char *debugdir) +{ + struct dso *pos; + int err = 0; + + dsos__for_each_with_build_id(pos, head) + if (dso__cache_build_id(pos, debugdir)) + err = -1; + return err; } +static int dsos__cache_build_ids(void) +{ + int err_kernel, err_user; + char debugdir[PATH_MAX]; + + snprintf(debugdir, sizeof(debugdir), "%s/%s", getenv("HOME"), + DEBUG_CACHE_DIR); + + if (mkdir(debugdir, 0755) != 0 && errno != EEXIST) + return -1; + + err_kernel = __dsos__cache_build_ids(&dsos__kernel, debugdir); + err_user = __dsos__cache_build_ids(&dsos__user, debugdir); + return err_kernel || err_user ? -1 : 0; +} + static int perf_header__adds_write(struct perf_header *self, int fd) { int nr_sections; @@ -258,6 +409,7 @@ static int perf_header__adds_write(struct perf_header *self, int fd) goto out_free; } buildid_sec->size = lseek(fd, 0, SEEK_CUR) - buildid_sec->offset; + dsos__cache_build_ids(); } lseek(fd, sec_start, SEEK_SET); @@ -360,30 +512,43 @@ int perf_header__write(struct perf_header *self, int fd, bool at_exit) return 0; } -static void do_read(int fd, void *buf, size_t size) +static int do_read(int fd, void *buf, size_t size) { while (size) { int ret = read(fd, buf, size); - if (ret < 0) - die("failed to read"); - if (ret == 0) - die("failed to read: missing data"); + if (ret <= 0) + return -1; size -= ret; buf += ret; } + + return 0; +} + +static int perf_header__getbuffer64(struct perf_header *self, + int fd, void *buf, size_t size) +{ + if (do_read(fd, buf, size)) + return -1; + + if (self->needs_swap) + mem_bswap_64(buf, size); + + return 0; } int perf_header__process_sections(struct perf_header *self, int fd, int (*process)(struct perf_file_section *self, + struct perf_header *ph, int feat, int fd)) { struct perf_file_section *feat_sec; int nr_sections; int sec_size; int idx = 0; - int err = 0, feat = 1; + int err = -1, feat = 1; nr_sections = bitmap_weight(self->adds_features, HEADER_FEAT_BITS); if (!nr_sections) @@ -397,33 +562,45 @@ int perf_header__process_sections(struct perf_header *self, int fd, lseek(fd, self->data_offset + self->data_size, SEEK_SET); - do_read(fd, feat_sec, sec_size); + if (perf_header__getbuffer64(self, fd, feat_sec, sec_size)) + goto out_free; + err = 0; while (idx < nr_sections && feat < HEADER_LAST_FEATURE) { if (perf_header__has_feat(self, feat)) { struct perf_file_section *sec = &feat_sec[idx++]; - err = process(sec, feat, fd); + err = process(sec, self, feat, fd); if (err < 0) break; } ++feat; } - +out_free: free(feat_sec); return err; -}; +} int perf_file_header__read(struct perf_file_header *self, struct perf_header *ph, int fd) { lseek(fd, 0, SEEK_SET); - do_read(fd, self, sizeof(*self)); - if (self->magic != PERF_MAGIC || - self->attr_size != sizeof(struct perf_file_attr)) + if (do_read(fd, self, sizeof(*self)) || + memcmp(&self->magic, __perf_magic, sizeof(self->magic))) return -1; + if (self->attr_size != sizeof(struct perf_file_attr)) { + u64 attr_size = bswap_64(self->attr_size); + + if (attr_size != sizeof(struct perf_file_attr)) + return -1; + + mem_bswap_64(self, offsetof(struct perf_file_header, + adds_features)); + ph->needs_swap = true; + } + if (self->size != sizeof(*self)) { /* Support the previous format */ if (self->size == offsetof(typeof(*self), adds_features)) @@ -433,16 +610,28 @@ int perf_file_header__read(struct perf_file_header *self, } memcpy(&ph->adds_features, &self->adds_features, - sizeof(self->adds_features)); + sizeof(ph->adds_features)); + /* + * FIXME: hack that assumes that if we need swap the perf.data file + * may be coming from an arch with a different word-size, ergo different + * DEFINE_BITMAP format, investigate more later, but for now its mostly + * safe to assume that we have a build-id section. Trace files probably + * have several other issues in this realm anyway... + */ + if (ph->needs_swap) { + memset(&ph->adds_features, 0, sizeof(ph->adds_features)); + perf_header__set_feat(ph, HEADER_BUILD_ID); + } ph->event_offset = self->event_types.offset; - ph->event_size = self->event_types.size; - ph->data_offset = self->data.offset; + ph->event_size = self->event_types.size; + ph->data_offset = self->data.offset; ph->data_size = self->data.size; return 0; } static int perf_file_section__process(struct perf_file_section *self, + struct perf_header *ph, int feat, int fd) { if (lseek(fd, self->offset, SEEK_SET) < 0) { @@ -457,7 +646,7 @@ static int perf_file_section__process(struct perf_file_section *self, break; case HEADER_BUILD_ID: - if (perf_header__read_build_ids(fd, self->offset, self->size)) + if (perf_header__read_build_ids(ph, fd, self->offset, self->size)) pr_debug("Failed to read buildids, continuing...\n"); break; default: @@ -469,7 +658,7 @@ static int perf_file_section__process(struct perf_file_section *self, int perf_header__read(struct perf_header *self, int fd) { - struct perf_file_header f_header; + struct perf_file_header f_header; struct perf_file_attr f_attr; u64 f_id; int nr_attrs, nr_ids, i, j; @@ -486,7 +675,9 @@ int perf_header__read(struct perf_header *self, int fd) struct perf_header_attr *attr; off_t tmp; - do_read(fd, &f_attr, sizeof(f_attr)); + if (perf_header__getbuffer64(self, fd, &f_attr, sizeof(f_attr))) + goto out_errno; + tmp = lseek(fd, 0, SEEK_CUR); attr = perf_header_attr__new(&f_attr.attr); @@ -497,7 +688,8 @@ int perf_header__read(struct perf_header *self, int fd) lseek(fd, f_attr.ids.offset, SEEK_SET); for (j = 0; j < nr_ids; j++) { - do_read(fd, &f_id, sizeof(f_id)); + if (perf_header__getbuffer64(self, fd, &f_id, sizeof(f_id))) + goto out_errno; if (perf_header_attr__add_id(attr, f_id) < 0) { perf_header_attr__delete(attr); @@ -517,7 +709,9 @@ int perf_header__read(struct perf_header *self, int fd) events = malloc(f_header.event_types.size); if (events == NULL) return -ENOMEM; - do_read(fd, events, f_header.event_types.size); + if (perf_header__getbuffer64(self, fd, events, + f_header.event_types.size)) + goto out_errno; event_count = f_header.event_types.size / sizeof(struct perf_trace_event_type); } @@ -527,6 +721,8 @@ int perf_header__read(struct perf_header *self, int fd) self->frozen = 1; return 0; +out_errno: + return -errno; } u64 perf_header__sample_type(struct perf_header *header) diff --git a/tools/perf/util/header.h b/tools/perf/util/header.h index d118d05d3abe..82a6af72d4cc 100644 --- a/tools/perf/util/header.h +++ b/tools/perf/util/header.h @@ -5,6 +5,7 @@ #include <sys/types.h> #include <stdbool.h> #include "types.h" +#include "event.h" #include <linux/bitmap.h> @@ -52,6 +53,7 @@ struct perf_header { u64 data_size; u64 event_offset; u64 event_size; + bool needs_swap; DECLARE_BITMAP(adds_features, HEADER_FEAT_BITS); }; @@ -64,7 +66,7 @@ int perf_header__write(struct perf_header *self, int fd, bool at_exit); int perf_header__add_attr(struct perf_header *self, struct perf_header_attr *attr); -void perf_header__push_event(u64 id, const char *name); +int perf_header__push_event(u64 id, const char *name); char *perf_header__find_event(u64 id); struct perf_header_attr *perf_header_attr__new(struct perf_event_attr *attr); @@ -80,6 +82,11 @@ bool perf_header__has_feat(const struct perf_header *self, int feat); int perf_header__process_sections(struct perf_header *self, int fd, int (*process)(struct perf_file_section *self, + struct perf_header *ph, int feat, int fd)); +int build_id_cache__add_s(const char *sbuild_id, const char *debugdir, + const char *name, bool is_kallsyms); +int build_id_cache__remove_s(const char *sbuild_id, const char *debugdir); + #endif /* __PERF_HEADER_H */ diff --git a/tools/perf/util/map.h b/tools/perf/util/map.h new file mode 100644 index 000000000000..72f0b6ab5ea5 --- /dev/null +++ b/tools/perf/util/map.h @@ -0,0 +1,73 @@ +#ifndef __PERF_MAP_H +#define __PERF_MAP_H + +#include <linux/compiler.h> +#include <linux/list.h> +#include <linux/rbtree.h> +#include <linux/types.h> + +enum map_type { + MAP__FUNCTION = 0, + MAP__VARIABLE, +}; + +#define MAP__NR_TYPES (MAP__VARIABLE + 1) + +struct dso; + +struct map { + union { + struct rb_node rb_node; + struct list_head node; + }; + u64 start; + u64 end; + enum map_type type; + u64 pgoff; + u64 (*map_ip)(struct map *, u64); + u64 (*unmap_ip)(struct map *, u64); + struct dso *dso; +}; + +static inline u64 map__map_ip(struct map *map, u64 ip) +{ + return ip - map->start + map->pgoff; +} + +static inline u64 map__unmap_ip(struct map *map, u64 ip) +{ + return ip + map->start - map->pgoff; +} + +static inline u64 identity__map_ip(struct map *map __used, u64 ip) +{ + return ip; +} + +struct symbol; +struct mmap_event; + +typedef int (*symbol_filter_t)(struct map *map, struct symbol *sym); + +void map__init(struct map *self, enum map_type type, + u64 start, u64 end, u64 pgoff, struct dso *dso); +struct map *map__new(struct mmap_event *event, enum map_type, + char *cwd, int cwdlen); +void map__delete(struct map *self); +struct map *map__clone(struct map *self); +int map__overlap(struct map *l, struct map *r); +size_t map__fprintf(struct map *self, FILE *fp); + +struct perf_session; + +int map__load(struct map *self, struct perf_session *session, + symbol_filter_t filter); +struct symbol *map__find_symbol(struct map *self, struct perf_session *session, + u64 addr, symbol_filter_t filter); +struct symbol *map__find_symbol_by_name(struct map *self, const char *name, + struct perf_session *session, + symbol_filter_t filter); +void map__fixup_start(struct map *self); +void map__fixup_end(struct map *self); + +#endif /* __PERF_MAP_H */ diff --git a/tools/perf/util/parse-events.c b/tools/perf/util/parse-events.c index e5bc0fb016b2..05d0c5c2030c 100644 --- a/tools/perf/util/parse-events.c +++ b/tools/perf/util/parse-events.c @@ -450,7 +450,8 @@ parse_single_tracepoint_event(char *sys_name, /* sys + ':' + event + ':' + flags*/ #define MAX_EVOPT_LEN (MAX_EVENT_LENGTH * 2 + 2 + 128) static enum event_result -parse_subsystem_tracepoint_event(char *sys_name, char *flags) +parse_multiple_tracepoint_event(char *sys_name, const char *evt_exp, + char *flags) { char evt_path[MAXPATHLEN]; struct dirent *evt_ent; @@ -474,6 +475,9 @@ parse_subsystem_tracepoint_event(char *sys_name, char *flags) || !strcmp(evt_ent->d_name, "filter")) continue; + if (!strglobmatch(evt_ent->d_name, evt_exp)) + continue; + len = snprintf(event_opt, MAX_EVOPT_LEN, "%s:%s%s%s", sys_name, evt_ent->d_name, flags ? ":" : "", flags ?: ""); @@ -522,9 +526,10 @@ static enum event_result parse_tracepoint_event(const char **strp, if (evt_length >= MAX_EVENT_LENGTH) return EVT_FAILED; - if (!strcmp(evt_name, "*")) { + if (strpbrk(evt_name, "*?")) { *strp = evt_name + evt_length; - return parse_subsystem_tracepoint_event(sys_name, flags); + return parse_multiple_tracepoint_event(sys_name, evt_name, + flags); } else return parse_single_tracepoint_event(sys_name, evt_name, evt_length, flags, @@ -753,11 +758,11 @@ modifier: return ret; } -static void store_event_type(const char *orgname) +static int store_event_type(const char *orgname) { char filename[PATH_MAX], *c; FILE *file; - int id; + int id, n; sprintf(filename, "%s/", debugfs_path); strncat(filename, orgname, strlen(orgname)); @@ -769,11 +774,14 @@ static void store_event_type(const char *orgname) file = fopen(filename, "r"); if (!file) - return; - if (fscanf(file, "%i", &id) < 1) - die("cannot store event ID"); + return 0; + n = fscanf(file, "%i", &id); fclose(file); - perf_header__push_event(id, orgname); + if (n < 1) { + pr_err("cannot store event ID\n"); + return -EINVAL; + } + return perf_header__push_event(id, orgname); } int parse_events(const struct option *opt __used, const char *str, int unset __used) @@ -782,7 +790,8 @@ int parse_events(const struct option *opt __used, const char *str, int unset __u enum event_result ret; if (strchr(str, ':')) - store_event_type(str); + if (store_event_type(str) < 0) + return -1; for (;;) { if (nr_counters == MAX_COUNTERS) @@ -835,11 +844,12 @@ int parse_filter(const struct option *opt __used, const char *str, } static const char * const event_type_descriptors[] = { - "", "Hardware event", "Software event", "Tracepoint event", "Hardware cache event", + "Raw hardware event descriptor", + "Hardware breakpoint", }; /* @@ -872,7 +882,7 @@ static void print_tracepoint_events(void) snprintf(evt_path, MAXPATHLEN, "%s:%s", sys_dirent.d_name, evt_dirent.d_name); printf(" %-42s [%s]\n", evt_path, - event_type_descriptors[PERF_TYPE_TRACEPOINT+1]); + event_type_descriptors[PERF_TYPE_TRACEPOINT]); } closedir(evt_dir); } @@ -892,9 +902,7 @@ void print_events(void) printf("List of pre-defined events (to be used in -e):\n"); for (i = 0; i < ARRAY_SIZE(event_symbols); i++, syms++) { - type = syms->type + 1; - if (type >= ARRAY_SIZE(event_type_descriptors)) - type = 0; + type = syms->type; if (type != prev_type) printf("\n"); @@ -919,17 +927,19 @@ void print_events(void) for (i = 0; i < PERF_COUNT_HW_CACHE_RESULT_MAX; i++) { printf(" %-42s [%s]\n", event_cache_name(type, op, i), - event_type_descriptors[4]); + event_type_descriptors[PERF_TYPE_HW_CACHE]); } } } printf("\n"); - printf(" %-42s [raw hardware event descriptor]\n", - "rNNN"); + printf(" %-42s [%s]\n", + "rNNN", event_type_descriptors[PERF_TYPE_RAW]); printf("\n"); - printf(" %-42s [hardware breakpoint]\n", "mem:<addr>[:access]"); + printf(" %-42s [%s]\n", + "mem:<addr>[:access]", + event_type_descriptors[PERF_TYPE_BREAKPOINT]); printf("\n"); print_tracepoint_events(); diff --git a/tools/perf/util/probe-event.c b/tools/perf/util/probe-event.c index 29465d440043..71b0dd590a37 100644 --- a/tools/perf/util/probe-event.c +++ b/tools/perf/util/probe-event.c @@ -37,6 +37,8 @@ #include "string.h" #include "strlist.h" #include "debug.h" +#include "cache.h" +#include "color.h" #include "parse-events.h" /* For debugfs_path */ #include "probe-event.h" @@ -62,6 +64,42 @@ static int e_snprintf(char *str, size_t size, const char *format, ...) return ret; } +void parse_line_range_desc(const char *arg, struct line_range *lr) +{ + const char *ptr; + char *tmp; + /* + * <Syntax> + * SRC:SLN[+NUM|-ELN] + * FUNC[:SLN[+NUM|-ELN]] + */ + ptr = strchr(arg, ':'); + if (ptr) { + lr->start = (unsigned int)strtoul(ptr + 1, &tmp, 0); + if (*tmp == '+') + lr->end = lr->start + (unsigned int)strtoul(tmp + 1, + &tmp, 0); + else if (*tmp == '-') + lr->end = (unsigned int)strtoul(tmp + 1, &tmp, 0); + else + lr->end = 0; + pr_debug("Line range is %u to %u\n", lr->start, lr->end); + if (lr->end && lr->start > lr->end) + semantic_error("Start line must be smaller" + " than end line."); + if (*tmp != '\0') + semantic_error("Tailing with invalid character '%d'.", + *tmp); + tmp = strndup(arg, (ptr - arg)); + } else + tmp = strdup(arg); + + if (strchr(tmp, '.')) + lr->file = tmp; + else + lr->function = tmp; +} + /* Check the name is good for event/group */ static bool check_event_name(const char *name) { @@ -368,7 +406,7 @@ static int open_kprobe_events(int flags, int mode) if (ret < 0) { if (errno == ENOENT) die("kprobe_events file does not exist -" - " please rebuild with CONFIG_KPROBE_TRACER."); + " please rebuild with CONFIG_KPROBE_EVENT."); else die("Could not open kprobe_events file: %s", strerror(errno)); @@ -455,6 +493,8 @@ void show_perf_probe_events(void) struct strlist *rawlist; struct str_node *ent; + setup_pager(); + fd = open_kprobe_events(O_RDONLY, 0); rawlist = get_trace_kprobe_event_rawlist(fd); close(fd); @@ -675,3 +715,66 @@ void del_trace_kprobe_events(struct strlist *dellist) close(fd); } +#define LINEBUF_SIZE 256 + +static void show_one_line(FILE *fp, unsigned int l, bool skip, bool show_num) +{ + char buf[LINEBUF_SIZE]; + const char *color = PERF_COLOR_BLUE; + + if (fgets(buf, LINEBUF_SIZE, fp) == NULL) + goto error; + if (!skip) { + if (show_num) + fprintf(stdout, "%7u %s", l, buf); + else + color_fprintf(stdout, color, " %s", buf); + } + + while (strlen(buf) == LINEBUF_SIZE - 1 && + buf[LINEBUF_SIZE - 2] != '\n') { + if (fgets(buf, LINEBUF_SIZE, fp) == NULL) + goto error; + if (!skip) { + if (show_num) + fprintf(stdout, "%s", buf); + else + color_fprintf(stdout, color, "%s", buf); + } + } + return; +error: + if (feof(fp)) + die("Source file is shorter than expected."); + else + die("File read error: %s", strerror(errno)); +} + +void show_line_range(struct line_range *lr) +{ + unsigned int l = 1; + struct line_node *ln; + FILE *fp; + + setup_pager(); + + if (lr->function) + fprintf(stdout, "<%s:%d>\n", lr->function, + lr->start - lr->offset); + else + fprintf(stdout, "<%s:%d>\n", lr->file, lr->start); + + fp = fopen(lr->path, "r"); + if (fp == NULL) + die("Failed to open %s: %s", lr->path, strerror(errno)); + /* Skip to starting line number */ + while (l < lr->start) + show_one_line(fp, l++, true, false); + + list_for_each_entry(ln, &lr->line_list, list) { + while (ln->line > l) + show_one_line(fp, (l++) - lr->offset, false, false); + show_one_line(fp, (l++) - lr->offset, false, true); + } + fclose(fp); +} diff --git a/tools/perf/util/probe-event.h b/tools/perf/util/probe-event.h index 7f1d499118c0..711287d4baea 100644 --- a/tools/perf/util/probe-event.h +++ b/tools/perf/util/probe-event.h @@ -5,6 +5,7 @@ #include "probe-finder.h" #include "strlist.h" +extern void parse_line_range_desc(const char *arg, struct line_range *lr); extern void parse_perf_probe_event(const char *str, struct probe_point *pp, bool *need_dwarf); extern int synthesize_perf_probe_point(struct probe_point *pp); @@ -15,6 +16,7 @@ extern void add_trace_kprobe_events(struct probe_point *probes, int nr_probes, bool force_add); extern void del_trace_kprobe_events(struct strlist *dellist); extern void show_perf_probe_events(void); +extern void show_line_range(struct line_range *lr); /* Maximum index number of event-name postfix */ #define MAX_EVENT_INDEX 1024 diff --git a/tools/perf/util/probe-finder.c b/tools/perf/util/probe-finder.c index 4b852c0d16a5..1b2124d12f68 100644 --- a/tools/perf/util/probe-finder.c +++ b/tools/perf/util/probe-finder.c @@ -140,6 +140,31 @@ static Dwarf_Unsigned cu_find_fileno(Dwarf_Die cu_die, const char *fname) return found; } +static int cu_get_filename(Dwarf_Die cu_die, Dwarf_Unsigned fno, char **buf) +{ + Dwarf_Signed cnt, i; + char **srcs; + int ret = 0; + + if (!buf || !fno) + return -EINVAL; + + ret = dwarf_srcfiles(cu_die, &srcs, &cnt, &__dw_error); + if (ret == DW_DLV_OK) { + if ((Dwarf_Unsigned)cnt > fno - 1) { + *buf = strdup(srcs[fno - 1]); + ret = 0; + pr_debug("found filename: %s\n", *buf); + } else + ret = -ENOENT; + for (i = 0; i < cnt; i++) + dwarf_dealloc(__dw_debug, srcs[i], DW_DLA_STRING); + dwarf_dealloc(__dw_debug, srcs, DW_DLA_LIST); + } else + ret = -EINVAL; + return ret; +} + /* Compare diename and tname */ static int die_compare_name(Dwarf_Die dw_die, const char *tname) { @@ -402,11 +427,11 @@ static void show_location(Dwarf_Loc *loc, struct probe_finder *pf) } else if (op == DW_OP_regx) { regn = loc->lr_number; } else - die("Dwarf_OP %d is not supported.\n", op); + die("Dwarf_OP %d is not supported.", op); regs = get_arch_regstr(regn); if (!regs) - die("%lld exceeds max register number.\n", regn); + die("%lld exceeds max register number.", regn); if (deref) ret = snprintf(pf->buf, pf->len, @@ -438,7 +463,7 @@ static void show_variable(Dwarf_Die vr_die, struct probe_finder *pf) return ; error: die("Failed to find the location of %s at this address.\n" - " Perhaps, it has been optimized out.\n", pf->var); + " Perhaps, it has been optimized out.", pf->var); } static int variable_callback(struct die_link *dlink, void *data) @@ -476,7 +501,7 @@ static void find_variable(Dwarf_Die sp_die, struct probe_finder *pf) /* Search child die for local variables and parameters. */ ret = search_die_from_children(sp_die, variable_callback, pf); if (!ret) - die("Failed to find '%s' in this function.\n", pf->var); + die("Failed to find '%s' in this function.", pf->var); } /* Get a frame base on the address */ @@ -567,7 +592,7 @@ static int probeaddr_callback(struct die_link *dlink, void *data) } /* Find probe point from its line number */ -static void find_by_line(struct probe_finder *pf) +static void find_probe_point_by_line(struct probe_finder *pf) { Dwarf_Signed cnt, i, clm; Dwarf_Line *lines; @@ -602,7 +627,7 @@ static void find_by_line(struct probe_finder *pf) ret = search_die_from_children(pf->cu_die, probeaddr_callback, pf); if (ret == 0) - die("Probe point is not found in subprograms.\n"); + die("Probe point is not found in subprograms."); /* Continuing, because target line might be inlined. */ } dwarf_srclines_dealloc(__dw_debug, lines, cnt); @@ -626,7 +651,7 @@ static int probefunc_callback(struct die_link *dlink, void *data) pf->fno = die_get_decl_file(dlink->die); pf->lno = die_get_decl_line(dlink->die) + pp->line; - find_by_line(pf); + find_probe_point_by_line(pf); return 1; } if (die_inlined_subprogram(dlink->die)) { @@ -661,7 +686,7 @@ static int probefunc_callback(struct die_link *dlink, void *data) !die_inlined_subprogram(lk->die)) goto found; } - die("Failed to find real subprogram.\n"); + die("Failed to find real subprogram."); found: /* Get offset from subprogram */ ret = die_within_subprogram(lk->die, pf->addr, &offs); @@ -673,7 +698,7 @@ found: return 0; } -static void find_by_func(struct probe_finder *pf) +static void find_probe_point_by_func(struct probe_finder *pf) { search_die_from_children(pf->cu_die, probefunc_callback, pf); } @@ -714,10 +739,10 @@ int find_probepoint(int fd, struct probe_point *pp) if (ret == DW_DLV_NO_ENTRY) pf.cu_base = 0; if (pp->function) - find_by_func(&pf); + find_probe_point_by_func(&pf); else { pf.lno = pp->line; - find_by_line(&pf); + find_probe_point_by_line(&pf); } } dwarf_dealloc(__dw_debug, pf.cu_die, DW_DLA_DIE); @@ -728,3 +753,159 @@ int find_probepoint(int fd, struct probe_point *pp) return pp->found; } + +static void line_range_add_line(struct line_range *lr, unsigned int line) +{ + struct line_node *ln; + struct list_head *p; + + /* Reverse search, because new line will be the last one */ + list_for_each_entry_reverse(ln, &lr->line_list, list) { + if (ln->line < line) { + p = &ln->list; + goto found; + } else if (ln->line == line) /* Already exist */ + return ; + } + /* List is empty, or the smallest entry */ + p = &lr->line_list; +found: + pr_debug("Debug: add a line %u\n", line); + ln = zalloc(sizeof(struct line_node)); + DIE_IF(ln == NULL); + ln->line = line; + INIT_LIST_HEAD(&ln->list); + list_add(&ln->list, p); +} + +/* Find line range from its line number */ +static void find_line_range_by_line(struct line_finder *lf) +{ + Dwarf_Signed cnt, i; + Dwarf_Line *lines; + Dwarf_Unsigned lineno = 0; + Dwarf_Unsigned fno; + Dwarf_Addr addr; + int ret; + + ret = dwarf_srclines(lf->cu_die, &lines, &cnt, &__dw_error); + DIE_IF(ret != DW_DLV_OK); + + for (i = 0; i < cnt; i++) { + ret = dwarf_line_srcfileno(lines[i], &fno, &__dw_error); + DIE_IF(ret != DW_DLV_OK); + if (fno != lf->fno) + continue; + + ret = dwarf_lineno(lines[i], &lineno, &__dw_error); + DIE_IF(ret != DW_DLV_OK); + if (lf->lno_s > lineno || lf->lno_e < lineno) + continue; + + /* Filter line in the function address range */ + if (lf->addr_s && lf->addr_e) { + ret = dwarf_lineaddr(lines[i], &addr, &__dw_error); + DIE_IF(ret != DW_DLV_OK); + if (lf->addr_s > addr || lf->addr_e <= addr) + continue; + } + line_range_add_line(lf->lr, (unsigned int)lineno); + } + dwarf_srclines_dealloc(__dw_debug, lines, cnt); + if (!list_empty(&lf->lr->line_list)) + lf->found = 1; +} + +/* Search function from function name */ +static int linefunc_callback(struct die_link *dlink, void *data) +{ + struct line_finder *lf = (struct line_finder *)data; + struct line_range *lr = lf->lr; + Dwarf_Half tag; + int ret; + + ret = dwarf_tag(dlink->die, &tag, &__dw_error); + DIE_IF(ret == DW_DLV_ERROR); + if (tag == DW_TAG_subprogram && + die_compare_name(dlink->die, lr->function) == 0) { + /* Get the address range of this function */ + ret = dwarf_highpc(dlink->die, &lf->addr_e, &__dw_error); + if (ret == DW_DLV_OK) + ret = dwarf_lowpc(dlink->die, &lf->addr_s, &__dw_error); + DIE_IF(ret == DW_DLV_ERROR); + if (ret == DW_DLV_NO_ENTRY) { + lf->addr_s = 0; + lf->addr_e = 0; + } + + lf->fno = die_get_decl_file(dlink->die); + lr->offset = die_get_decl_line(dlink->die);; + lf->lno_s = lr->offset + lr->start; + if (!lr->end) + lf->lno_e = (Dwarf_Unsigned)-1; + else + lf->lno_e = lr->offset + lr->end; + lr->start = lf->lno_s; + lr->end = lf->lno_e; + find_line_range_by_line(lf); + /* If we find a target function, this should be end. */ + lf->found = 1; + return 1; + } + return 0; +} + +static void find_line_range_by_func(struct line_finder *lf) +{ + search_die_from_children(lf->cu_die, linefunc_callback, lf); +} + +int find_line_range(int fd, struct line_range *lr) +{ + Dwarf_Half addr_size = 0; + Dwarf_Unsigned next_cuh = 0; + int ret; + struct line_finder lf = {.lr = lr}; + + ret = dwarf_init(fd, DW_DLC_READ, 0, 0, &__dw_debug, &__dw_error); + if (ret != DW_DLV_OK) + return -ENOENT; + + while (!lf.found) { + /* Search CU (Compilation Unit) */ + ret = dwarf_next_cu_header(__dw_debug, NULL, NULL, NULL, + &addr_size, &next_cuh, &__dw_error); + DIE_IF(ret == DW_DLV_ERROR); + if (ret == DW_DLV_NO_ENTRY) + break; + + /* Get the DIE(Debugging Information Entry) of this CU */ + ret = dwarf_siblingof(__dw_debug, 0, &lf.cu_die, &__dw_error); + DIE_IF(ret != DW_DLV_OK); + + /* Check if target file is included. */ + if (lr->file) + lf.fno = cu_find_fileno(lf.cu_die, lr->file); + + if (!lr->file || lf.fno) { + if (lr->function) + find_line_range_by_func(&lf); + else { + lf.lno_s = lr->start; + if (!lr->end) + lf.lno_e = (Dwarf_Unsigned)-1; + else + lf.lno_e = lr->end; + find_line_range_by_line(&lf); + } + /* Get the real file path */ + if (lf.found) + cu_get_filename(lf.cu_die, lf.fno, &lr->path); + } + dwarf_dealloc(__dw_debug, lf.cu_die, DW_DLA_DIE); + } + ret = dwarf_finish(__dw_debug, &__dw_error); + DIE_IF(ret != DW_DLV_OK); + return lf.found; +} + diff --git a/tools/perf/util/probe-finder.h b/tools/perf/util/probe-finder.h index a4086aaddb73..972b386116f1 100644 --- a/tools/perf/util/probe-finder.h +++ b/tools/perf/util/probe-finder.h @@ -1,6 +1,8 @@ #ifndef _PROBE_FINDER_H #define _PROBE_FINDER_H +#include "util.h" + #define MAX_PATH_LEN 256 #define MAX_PROBE_BUFFER 1024 #define MAX_PROBES 128 @@ -32,8 +34,26 @@ struct probe_point { char *probes[MAX_PROBES]; /* Output buffers (will be allocated)*/ }; +/* Line number container */ +struct line_node { + struct list_head list; + unsigned int line; +}; + +/* Line range */ +struct line_range { + char *file; /* File name */ + char *function; /* Function name */ + unsigned int start; /* Start line number */ + unsigned int end; /* End line number */ + unsigned int offset; /* Start line offset */ + char *path; /* Real path name */ + struct list_head line_list; /* Visible lines */ +}; + #ifndef NO_LIBDWARF extern int find_probepoint(int fd, struct probe_point *pp); +extern int find_line_range(int fd, struct line_range *lr); /* Workaround for undefined _MIPS_SZLONG bug in libdwarf.h: */ #ifndef _MIPS_SZLONG @@ -60,6 +80,19 @@ struct probe_finder { char *buf; /* Current output buffer */ int len; /* Length of output buffer */ }; + +struct line_finder { + struct line_range *lr; /* Target line range */ + + Dwarf_Unsigned fno; /* File number */ + Dwarf_Unsigned lno_s; /* Start line number */ + Dwarf_Unsigned lno_e; /* End line number */ + Dwarf_Addr addr_s; /* Start address */ + Dwarf_Addr addr_e; /* End address */ + Dwarf_Die cu_die; /* Current CU */ + int found; +}; + #endif /* NO_LIBDWARF */ #endif /*_PROBE_FINDER_H */ diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c index ce3a6c8abe76..8e7c1896eaa2 100644 --- a/tools/perf/util/session.c +++ b/tools/perf/util/session.c @@ -1,5 +1,6 @@ #include <linux/kernel.h> +#include <byteswap.h> #include <unistd.h> #include <sys/types.h> @@ -66,13 +67,22 @@ struct perf_session *perf_session__new(const char *filename, int mode, bool forc self->mmap_window = 32; self->cwd = NULL; self->cwdlen = 0; + self->unknown_events = 0; map_groups__init(&self->kmaps); - if (perf_session__create_kernel_maps(self) < 0) - goto out_delete; + if (mode == O_RDONLY) { + if (perf_session__open(self, force) < 0) + goto out_delete; + } else if (mode == O_WRONLY) { + /* + * In O_RDONLY mode this will be performed when reading the + * kernel MMAP event, in event__process_mmap(). + */ + if (perf_session__create_kernel_maps(self) < 0) + goto out_delete; + } - if (mode == O_RDONLY && perf_session__open(self, force) < 0) - goto out_delete; + self->sample_type = perf_header__sample_type(&self->header); out: return self; out_free: @@ -148,3 +158,389 @@ struct symbol **perf_session__resolve_callchain(struct perf_session *self, return syms; } + +static int process_event_stub(event_t *event __used, + struct perf_session *session __used) +{ + dump_printf(": unhandled!\n"); + return 0; +} + +static void perf_event_ops__fill_defaults(struct perf_event_ops *handler) +{ + if (handler->sample == NULL) + handler->sample = process_event_stub; + if (handler->mmap == NULL) + handler->mmap = process_event_stub; + if (handler->comm == NULL) + handler->comm = process_event_stub; + if (handler->fork == NULL) + handler->fork = process_event_stub; + if (handler->exit == NULL) + handler->exit = process_event_stub; + if (handler->lost == NULL) + handler->lost = process_event_stub; + if (handler->read == NULL) + handler->read = process_event_stub; + if (handler->throttle == NULL) + handler->throttle = process_event_stub; + if (handler->unthrottle == NULL) + handler->unthrottle = process_event_stub; +} + +static const char *event__name[] = { + [0] = "TOTAL", + [PERF_RECORD_MMAP] = "MMAP", + [PERF_RECORD_LOST] = "LOST", + [PERF_RECORD_COMM] = "COMM", + [PERF_RECORD_EXIT] = "EXIT", + [PERF_RECORD_THROTTLE] = "THROTTLE", + [PERF_RECORD_UNTHROTTLE] = "UNTHROTTLE", + [PERF_RECORD_FORK] = "FORK", + [PERF_RECORD_READ] = "READ", + [PERF_RECORD_SAMPLE] = "SAMPLE", +}; + +unsigned long event__total[PERF_RECORD_MAX]; + +void event__print_totals(void) +{ + int i; + for (i = 0; i < PERF_RECORD_MAX; ++i) + pr_info("%10s events: %10ld\n", + event__name[i], event__total[i]); +} + +void mem_bswap_64(void *src, int byte_size) +{ + u64 *m = src; + + while (byte_size > 0) { + *m = bswap_64(*m); + byte_size -= sizeof(u64); + ++m; + } +} + +static void event__all64_swap(event_t *self) +{ + struct perf_event_header *hdr = &self->header; + mem_bswap_64(hdr + 1, self->header.size - sizeof(*hdr)); +} + +static void event__comm_swap(event_t *self) +{ + self->comm.pid = bswap_32(self->comm.pid); + self->comm.tid = bswap_32(self->comm.tid); +} + +static void event__mmap_swap(event_t *self) +{ + self->mmap.pid = bswap_32(self->mmap.pid); + self->mmap.tid = bswap_32(self->mmap.tid); + self->mmap.start = bswap_64(self->mmap.start); + self->mmap.len = bswap_64(self->mmap.len); + self->mmap.pgoff = bswap_64(self->mmap.pgoff); +} + +static void event__task_swap(event_t *self) +{ + self->fork.pid = bswap_32(self->fork.pid); + self->fork.tid = bswap_32(self->fork.tid); + self->fork.ppid = bswap_32(self->fork.ppid); + self->fork.ptid = bswap_32(self->fork.ptid); + self->fork.time = bswap_64(self->fork.time); +} + +static void event__read_swap(event_t *self) +{ + self->read.pid = bswap_32(self->read.pid); + self->read.tid = bswap_32(self->read.tid); + self->read.value = bswap_64(self->read.value); + self->read.time_enabled = bswap_64(self->read.time_enabled); + self->read.time_running = bswap_64(self->read.time_running); + self->read.id = bswap_64(self->read.id); +} + +typedef void (*event__swap_op)(event_t *self); + +static event__swap_op event__swap_ops[] = { + [PERF_RECORD_MMAP] = event__mmap_swap, + [PERF_RECORD_COMM] = event__comm_swap, + [PERF_RECORD_FORK] = event__task_swap, + [PERF_RECORD_EXIT] = event__task_swap, + [PERF_RECORD_LOST] = event__all64_swap, + [PERF_RECORD_READ] = event__read_swap, + [PERF_RECORD_SAMPLE] = event__all64_swap, + [PERF_RECORD_MAX] = NULL, +}; + +static int perf_session__process_event(struct perf_session *self, + event_t *event, + struct perf_event_ops *ops, + u64 offset, u64 head) +{ + trace_event(event); + + if (event->header.type < PERF_RECORD_MAX) { + dump_printf("%#Lx [%#x]: PERF_RECORD_%s", + offset + head, event->header.size, + event__name[event->header.type]); + ++event__total[0]; + ++event__total[event->header.type]; + } + + if (self->header.needs_swap && event__swap_ops[event->header.type]) + event__swap_ops[event->header.type](event); + + switch (event->header.type) { + case PERF_RECORD_SAMPLE: + return ops->sample(event, self); + case PERF_RECORD_MMAP: + return ops->mmap(event, self); + case PERF_RECORD_COMM: + return ops->comm(event, self); + case PERF_RECORD_FORK: + return ops->fork(event, self); + case PERF_RECORD_EXIT: + return ops->exit(event, self); + case PERF_RECORD_LOST: + return ops->lost(event, self); + case PERF_RECORD_READ: + return ops->read(event, self); + case PERF_RECORD_THROTTLE: + return ops->throttle(event, self); + case PERF_RECORD_UNTHROTTLE: + return ops->unthrottle(event, self); + default: + self->unknown_events++; + return -1; + } +} + +void perf_event_header__bswap(struct perf_event_header *self) +{ + self->type = bswap_32(self->type); + self->misc = bswap_16(self->misc); + self->size = bswap_16(self->size); +} + +int perf_header__read_build_ids(struct perf_header *self, + int input, u64 offset, u64 size) +{ + struct build_id_event bev; + char filename[PATH_MAX]; + u64 limit = offset + size; + int err = -1; + + while (offset < limit) { + struct dso *dso; + ssize_t len; + struct list_head *head = &dsos__user; + + if (read(input, &bev, sizeof(bev)) != sizeof(bev)) + goto out; + + if (self->needs_swap) + perf_event_header__bswap(&bev.header); + + len = bev.header.size - sizeof(bev); + if (read(input, filename, len) != len) + goto out; + + if (bev.header.misc & PERF_RECORD_MISC_KERNEL) + head = &dsos__kernel; + + dso = __dsos__findnew(head, filename); + if (dso != NULL) { + dso__set_build_id(dso, &bev.build_id); + if (head == &dsos__kernel && filename[0] == '[') + dso->kernel = 1; + } + + offset += bev.header.size; + } + err = 0; +out: + return err; +} + +static struct thread *perf_session__register_idle_thread(struct perf_session *self) +{ + struct thread *thread = perf_session__findnew(self, 0); + + if (thread == NULL || thread__set_comm(thread, "swapper")) { + pr_err("problem inserting idle task.\n"); + thread = NULL; + } + + return thread; +} + +int perf_session__process_events(struct perf_session *self, + struct perf_event_ops *ops) +{ + int err, mmap_prot, mmap_flags; + u64 head, shift; + u64 offset = 0; + size_t page_size; + event_t *event; + uint32_t size; + char *buf; + + if (perf_session__register_idle_thread(self) == NULL) + return -ENOMEM; + + perf_event_ops__fill_defaults(ops); + + page_size = sysconf(_SC_PAGESIZE); + + head = self->header.data_offset; + + if (!symbol_conf.full_paths) { + char bf[PATH_MAX]; + + if (getcwd(bf, sizeof(bf)) == NULL) { + err = -errno; +out_getcwd_err: + pr_err("failed to get the current directory\n"); + goto out_err; + } + self->cwd = strdup(bf); + if (self->cwd == NULL) { + err = -ENOMEM; + goto out_getcwd_err; + } + self->cwdlen = strlen(self->cwd); + } + + shift = page_size * (head / page_size); + offset += shift; + head -= shift; + + mmap_prot = PROT_READ; + mmap_flags = MAP_SHARED; + + if (self->header.needs_swap) { + mmap_prot |= PROT_WRITE; + mmap_flags = MAP_PRIVATE; + } +remap: + buf = mmap(NULL, page_size * self->mmap_window, mmap_prot, + mmap_flags, self->fd, offset); + if (buf == MAP_FAILED) { + pr_err("failed to mmap file\n"); + err = -errno; + goto out_err; + } + +more: + event = (event_t *)(buf + head); + + if (self->header.needs_swap) + perf_event_header__bswap(&event->header); + size = event->header.size; + if (size == 0) + size = 8; + + if (head + event->header.size >= page_size * self->mmap_window) { + int munmap_ret; + + shift = page_size * (head / page_size); + + munmap_ret = munmap(buf, page_size * self->mmap_window); + assert(munmap_ret == 0); + + offset += shift; + head -= shift; + goto remap; + } + + size = event->header.size; + + dump_printf("\n%#Lx [%#x]: event: %d\n", + offset + head, event->header.size, event->header.type); + + if (size == 0 || + perf_session__process_event(self, event, ops, offset, head) < 0) { + dump_printf("%#Lx [%#x]: skipping unknown header type: %d\n", + offset + head, event->header.size, + event->header.type); + /* + * assume we lost track of the stream, check alignment, and + * increment a single u64 in the hope to catch on again 'soon'. + */ + if (unlikely(head & 7)) + head &= ~7ULL; + + size = 8; + } + + head += size; + + if (offset + head >= self->header.data_offset + self->header.data_size) + goto done; + + if (offset + head < self->size) + goto more; +done: + err = 0; +out_err: + return err; +} + +bool perf_session__has_traces(struct perf_session *self, const char *msg) +{ + if (!(self->sample_type & PERF_SAMPLE_RAW)) { + pr_err("No trace sample to read. Did you call 'perf %s'?\n", msg); + return false; + } + + return true; +} + +int perf_session__set_kallsyms_ref_reloc_sym(struct perf_session *self, + const char *symbol_name, + u64 addr) +{ + char *bracket; + + self->ref_reloc_sym.name = strdup(symbol_name); + if (self->ref_reloc_sym.name == NULL) + return -ENOMEM; + + bracket = strchr(self->ref_reloc_sym.name, ']'); + if (bracket) + *bracket = '\0'; + + self->ref_reloc_sym.addr = addr; + return 0; +} + +static u64 map__reloc_map_ip(struct map *map, u64 ip) +{ + return ip + (s64)map->pgoff; +} + +static u64 map__reloc_unmap_ip(struct map *map, u64 ip) +{ + return ip - (s64)map->pgoff; +} + +void perf_session__reloc_vmlinux_maps(struct perf_session *self, + u64 unrelocated_addr) +{ + enum map_type type; + s64 reloc = unrelocated_addr - self->ref_reloc_sym.addr; + + if (!reloc) + return; + + for (type = 0; type < MAP__NR_TYPES; ++type) { + struct map *map = self->vmlinux_maps[type]; + + map->map_ip = map__reloc_map_ip; + map->unmap_ip = map__reloc_unmap_ip; + map->pgoff = reloc; + } +} diff --git a/tools/perf/util/session.h b/tools/perf/util/session.h index 32eaa1bada06..36d1a80c0b6c 100644 --- a/tools/perf/util/session.h +++ b/tools/perf/util/session.h @@ -18,10 +18,16 @@ struct perf_session { struct map_groups kmaps; struct rb_root threads; struct thread *last_match; + struct map *vmlinux_maps[MAP__NR_TYPES]; struct events_stats events_stats; unsigned long event_total[PERF_RECORD_MAX]; + unsigned long unknown_events; struct rb_root hists; u64 sample_type; + struct { + const char *name; + u64 addr; + } ref_reloc_sym; int fd; int cwdlen; char *cwd; @@ -31,23 +37,22 @@ struct perf_session { typedef int (*event_op)(event_t *self, struct perf_session *session); struct perf_event_ops { - event_op process_sample_event; - event_op process_mmap_event; - event_op process_comm_event; - event_op process_fork_event; - event_op process_exit_event; - event_op process_lost_event; - event_op process_read_event; - event_op process_throttle_event; - event_op process_unthrottle_event; - int (*sample_type_check)(struct perf_session *session); - unsigned long total_unknown; - bool full_paths; + event_op sample, + mmap, + comm, + fork, + exit, + lost, + read, + throttle, + unthrottle; }; struct perf_session *perf_session__new(const char *filename, int mode, bool force); void perf_session__delete(struct perf_session *self); +void perf_event_header__bswap(struct perf_event_header *self); + int perf_session__process_events(struct perf_session *self, struct perf_event_ops *event_ops); @@ -56,6 +61,17 @@ struct symbol **perf_session__resolve_callchain(struct perf_session *self, struct ip_callchain *chain, struct symbol **parent); -int perf_header__read_build_ids(int input, u64 offset, u64 file_size); +bool perf_session__has_traces(struct perf_session *self, const char *msg); + +int perf_header__read_build_ids(struct perf_header *self, int input, + u64 offset, u64 file_size); + +int perf_session__set_kallsyms_ref_reloc_sym(struct perf_session *self, + const char *symbol_name, + u64 addr); +void perf_session__reloc_vmlinux_maps(struct perf_session *self, + u64 unrelocated_addr); + +void mem_bswap_64(void *src, int byte_size); #endif /* __PERF_SESSION_H */ diff --git a/tools/perf/util/string.c b/tools/perf/util/string.c index 5352d7dccc61..c397d4f6f748 100644 --- a/tools/perf/util/string.c +++ b/tools/perf/util/string.c @@ -227,16 +227,73 @@ fail: return NULL; } -/* Glob expression pattern matching */ +/* Character class matching */ +static bool __match_charclass(const char *pat, char c, const char **npat) +{ + bool complement = false, ret = true; + + if (*pat == '!') { + complement = true; + pat++; + } + if (*pat++ == c) /* First character is special */ + goto end; + + while (*pat && *pat != ']') { /* Matching */ + if (*pat == '-' && *(pat + 1) != ']') { /* Range */ + if (*(pat - 1) <= c && c <= *(pat + 1)) + goto end; + if (*(pat - 1) > *(pat + 1)) + goto error; + pat += 2; + } else if (*pat++ == c) + goto end; + } + if (!*pat) + goto error; + ret = false; + +end: + while (*pat && *pat != ']') /* Searching closing */ + pat++; + if (!*pat) + goto error; + *npat = pat + 1; + return complement ? !ret : ret; + +error: + return false; +} + +/** + * strglobmatch - glob expression pattern matching + * @str: the target string to match + * @pat: the pattern string to match + * + * This returns true if the @str matches @pat. @pat can includes wildcards + * ('*','?') and character classes ([CHARS], complementation and ranges are + * also supported). Also, this supports escape character ('\') to use special + * characters as normal character. + * + * Note: if @pat syntax is broken, this always returns false. + */ bool strglobmatch(const char *str, const char *pat) { while (*str && *pat && *pat != '*') { - if (*pat == '?') { + if (*pat == '?') { /* Matches any single character */ str++; pat++; - } else - if (*str++ != *pat++) + continue; + } else if (*pat == '[') /* Character classes/Ranges */ + if (__match_charclass(pat + 1, *str, &pat)) { + str++; + continue; + } else return false; + else if (*pat == '\\') /* Escaped char match as normal char */ + pat++; + if (*str++ != *pat++) + return false; } /* Check wild card */ if (*pat == '*') { diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c index ab92763edb03..f9049d12ead6 100644 --- a/tools/perf/util/symbol.c +++ b/tools/perf/util/symbol.c @@ -22,6 +22,7 @@ enum dso_origin { DSO__ORIG_KERNEL = 0, DSO__ORIG_JAVA_JIT, + DSO__ORIG_BUILD_ID_CACHE, DSO__ORIG_FEDORA, DSO__ORIG_UBUNTU, DSO__ORIG_BUILDID, @@ -63,7 +64,7 @@ static void dso__set_sorted_by_name(struct dso *self, enum map_type type) self->sorted_by_name |= (1 << type); } -static bool symbol_type__is_a(char symbol_type, enum map_type map_type) +bool symbol_type__is_a(char symbol_type, enum map_type map_type) { switch (map_type) { case MAP__FUNCTION: @@ -160,7 +161,7 @@ static size_t symbol__fprintf(struct symbol *self, FILE *fp) self->start, self->end, self->name); } -static void dso__set_long_name(struct dso *self, char *name) +void dso__set_long_name(struct dso *self, char *name) { if (name == NULL) return; @@ -175,7 +176,7 @@ static void dso__set_basename(struct dso *self) struct dso *dso__new(const char *name) { - struct dso *self = malloc(sizeof(*self) + strlen(name) + 1); + struct dso *self = zalloc(sizeof(*self) + strlen(name) + 1); if (self != NULL) { int i; @@ -344,10 +345,10 @@ void dso__sort_by_name(struct dso *self, enum map_type type) &self->symbols[type]); } -int build_id__sprintf(u8 *self, int len, char *bf) +int build_id__sprintf(const u8 *self, int len, char *bf) { char *bid = bf; - u8 *raw = self; + const u8 *raw = self; int i; for (i = 0; i < len; ++i) { @@ -382,24 +383,20 @@ size_t dso__fprintf(struct dso *self, enum map_type type, FILE *fp) return ret; } -/* - * Loads the function entries in /proc/kallsyms into kernel_map->dso, - * so that we can in the next step set the symbol ->end address and then - * call kernel_maps__split_kallsyms. - */ -static int dso__load_all_kallsyms(struct dso *self, struct map *map) +int kallsyms__parse(const char *filename, void *arg, + int (*process_symbol)(void *arg, const char *name, + char type, u64 start)) { char *line = NULL; size_t n; - struct rb_root *root = &self->symbols[map->type]; - FILE *file = fopen("/proc/kallsyms", "r"); + int err = 0; + FILE *file = fopen(filename, "r"); if (file == NULL) goto out_failure; while (!feof(file)) { u64 start; - struct symbol *sym; int line_len, len; char symbol_type; char *symbol_name; @@ -420,35 +417,63 @@ static int dso__load_all_kallsyms(struct dso *self, struct map *map) continue; symbol_type = toupper(line[len]); - if (!symbol_type__is_a(symbol_type, map->type)) - continue; - symbol_name = line + len + 2; - /* - * Will fix up the end later, when we have all symbols sorted. - */ - sym = symbol__new(start, 0, symbol_name); - if (sym == NULL) - goto out_delete_line; - /* - * We will pass the symbols to the filter later, in - * map__split_kallsyms, when we have split the maps per module - */ - symbols__insert(root, sym); + err = process_symbol(arg, symbol_name, symbol_type, start); + if (err) + break; } free(line); fclose(file); + return err; - return 0; - -out_delete_line: - free(line); out_failure: return -1; } +struct process_kallsyms_args { + struct map *map; + struct dso *dso; +}; + +static int map__process_kallsym_symbol(void *arg, const char *name, + char type, u64 start) +{ + struct symbol *sym; + struct process_kallsyms_args *a = arg; + struct rb_root *root = &a->dso->symbols[a->map->type]; + + if (!symbol_type__is_a(type, a->map->type)) + return 0; + + /* + * Will fix up the end later, when we have all symbols sorted. + */ + sym = symbol__new(start, 0, name); + + if (sym == NULL) + return -ENOMEM; + /* + * We will pass the symbols to the filter later, in + * map__split_kallsyms, when we have split the maps per module + */ + symbols__insert(root, sym); + return 0; +} + +/* + * Loads the function entries in /proc/kallsyms into kernel_map->dso, + * so that we can in the next step set the symbol ->end address and then + * call kernel_maps__split_kallsyms. + */ +static int dso__load_all_kallsyms(struct dso *self, const char *filename, + struct map *map) +{ + struct process_kallsyms_args args = { .map = map, .dso = self, }; + return kallsyms__parse(filename, &args, map__process_kallsym_symbol); +} + /* * Split the symbols into maps, making sure there are no overlaps, i.e. the * kernel range is broken in several maps, named [kernel].N, as we don't have @@ -477,13 +502,17 @@ static int dso__split_kallsyms(struct dso *self, struct map *map, *module++ = '\0'; - if (strcmp(self->name, module)) { + if (strcmp(curr_map->dso->short_name, module)) { curr_map = map_groups__find_by_name(&session->kmaps, map->type, module); if (curr_map == NULL) { pr_debug("/proc/{kallsyms,modules} " - "inconsistency!\n"); + "inconsistency while looking " + "for \"%s\" module!\n", module); return -1; } + + if (curr_map->dso->loaded) + goto discard_symbol; } /* * So that we look just like we get from .ko files, @@ -529,10 +558,10 @@ discard_symbol: rb_erase(&pos->rb_node, root); } -static int dso__load_kallsyms(struct dso *self, struct map *map, +static int dso__load_kallsyms(struct dso *self, const char *filename, struct map *map, struct perf_session *session, symbol_filter_t filter) { - if (dso__load_all_kallsyms(self, map) < 0) + if (dso__load_all_kallsyms(self, filename, map) < 0) return -1; symbols__fixup_end(&self->symbols[map->type]); @@ -933,11 +962,15 @@ static int dso__load_sym(struct dso *self, struct map *map, elf_symtab__for_each_symbol(syms, nr_syms, idx, sym) { struct symbol *f; - const char *elf_name; + const char *elf_name = elf_sym__name(&sym, symstrs); char *demangled = NULL; int is_label = elf_sym__is_label(&sym); const char *section_name; + if (kernel && session->ref_reloc_sym.name != NULL && + strcmp(elf_name, session->ref_reloc_sym.name) == 0) + perf_session__reloc_vmlinux_maps(session, sym.st_value); + if (!is_label && !elf_sym__is_a(&sym, map->type)) continue; @@ -950,7 +983,6 @@ static int dso__load_sym(struct dso *self, struct map *map, if (is_label && !elf_sec__is_a(&shdr, secstrs, map->type)) continue; - elf_name = elf_sym__name(&sym, symstrs); section_name = elf_sec__name(&shdr, secstrs); if (kernel || kmodule) { @@ -1191,6 +1223,7 @@ char dso__symtab_origin(const struct dso *self) static const char origin[] = { [DSO__ORIG_KERNEL] = 'k', [DSO__ORIG_JAVA_JIT] = 'j', + [DSO__ORIG_BUILD_ID_CACHE] = 'B', [DSO__ORIG_FEDORA] = 'f', [DSO__ORIG_UBUNTU] = 'u', [DSO__ORIG_BUILDID] = 'b', @@ -1209,6 +1242,7 @@ int dso__load(struct dso *self, struct map *map, struct perf_session *session, int size = PATH_MAX; char *name; u8 build_id[BUILD_ID_SIZE]; + char build_id_hex[BUILD_ID_SIZE * 2 + 1]; int ret = -1; int fd; @@ -1230,8 +1264,16 @@ int dso__load(struct dso *self, struct map *map, struct perf_session *session, return ret; } - self->origin = DSO__ORIG_FEDORA - 1; + self->origin = DSO__ORIG_BUILD_ID_CACHE; + if (self->has_build_id) { + build_id__sprintf(self->build_id, sizeof(self->build_id), + build_id_hex); + snprintf(name, size, "%s/%s/.build-id/%.2s/%s", + getenv("HOME"), DEBUG_CACHE_DIR, + build_id_hex, build_id_hex + 2); + goto open_file; + } more: do { self->origin++; @@ -1247,8 +1289,6 @@ more: case DSO__ORIG_BUILDID: if (filename__read_build_id(self->long_name, build_id, sizeof(build_id))) { - char build_id_hex[BUILD_ID_SIZE * 2 + 1]; - build_id__sprintf(build_id, sizeof(build_id), build_id_hex); snprintf(name, size, @@ -1276,7 +1316,7 @@ compare_build_id: if (!dso__build_id_equal(self, build_id)) goto more; } - +open_file: fd = open(name, O_RDONLY); } while (fd < 0); @@ -1309,13 +1349,33 @@ struct map *map_groups__find_by_name(struct map_groups *self, for (nd = rb_first(&self->maps[type]); nd; nd = rb_next(nd)) { struct map *map = rb_entry(nd, struct map, rb_node); - if (map->dso && strcmp(map->dso->name, name) == 0) + if (map->dso && strcmp(map->dso->short_name, name) == 0) return map; } return NULL; } +static int dso__kernel_module_get_build_id(struct dso *self) +{ + char filename[PATH_MAX]; + /* + * kernel module short names are of the form "[module]" and + * we need just "module" here. + */ + const char *name = self->short_name + 1; + + snprintf(filename, sizeof(filename), + "/sys/module/%.*s/notes/.note.gnu.build-id", + (int)strlen(name - 1), name); + + if (sysfs__read_build_id(filename, self->build_id, + sizeof(self->build_id)) == 0) + self->has_build_id = true; + + return 0; +} + static int perf_session__set_modules_path_dir(struct perf_session *self, char *dirname) { struct dirent *dent; @@ -1361,6 +1421,7 @@ static int perf_session__set_modules_path_dir(struct perf_session *self, char *d if (long_name == NULL) goto failure; dso__set_long_name(map->dso, long_name); + dso__kernel_module_get_build_id(map->dso); } } @@ -1403,6 +1464,24 @@ static struct map *map__new2(u64 start, struct dso *dso, enum map_type type) return self; } +struct map *perf_session__new_module_map(struct perf_session *self, u64 start, + const char *filename) +{ + struct map *map; + struct dso *dso = __dsos__findnew(&dsos__kernel, filename); + + if (dso == NULL) + return NULL; + + map = map__new2(start, dso, MAP__FUNCTION); + if (map == NULL) + return NULL; + + dso->origin = DSO__ORIG_KMODULE; + map_groups__insert(&self->kmaps, map); + return map; +} + static int perf_session__create_module_maps(struct perf_session *self) { char *line = NULL; @@ -1416,7 +1495,6 @@ static int perf_session__create_module_maps(struct perf_session *self) while (!feof(file)) { char name[PATH_MAX]; u64 start; - struct dso *dso; char *sep; int line_len; @@ -1442,26 +1520,10 @@ static int perf_session__create_module_maps(struct perf_session *self) *sep = '\0'; snprintf(name, sizeof(name), "[%s]", line); - dso = dso__new(name); - - if (dso == NULL) + map = perf_session__new_module_map(self, start, name); + if (map == NULL) goto out_delete_line; - - map = map__new2(start, dso, MAP__FUNCTION); - if (map == NULL) { - dso__delete(dso); - goto out_delete_line; - } - - snprintf(name, sizeof(name), - "/sys/module/%s/notes/.note.gnu.build-id", line); - if (sysfs__read_build_id(name, dso->build_id, - sizeof(dso->build_id)) == 0) - dso->has_build_id = true; - - dso->origin = DSO__ORIG_KMODULE; - map_groups__insert(&self->kmaps, map); - dsos__add(&dsos__kernel, dso); + dso__kernel_module_get_build_id(map->dso); } free(line); @@ -1510,51 +1572,117 @@ static int dso__load_vmlinux(struct dso *self, struct map *map, return -1; dso__set_loaded(self, map->type); - err = dso__load_sym(self, map, session, self->long_name, fd, filter, 1, 0); + err = dso__load_sym(self, map, session, vmlinux, fd, filter, 1, 0); close(fd); return err; } +int dso__load_vmlinux_path(struct dso *self, struct map *map, + struct perf_session *session, symbol_filter_t filter) +{ + int i, err = 0; + + pr_debug("Looking at the vmlinux_path (%d entries long)\n", + vmlinux_path__nr_entries); + + for (i = 0; i < vmlinux_path__nr_entries; ++i) { + err = dso__load_vmlinux(self, map, session, vmlinux_path[i], + filter); + if (err > 0) { + pr_debug("Using %s for symbols\n", vmlinux_path[i]); + dso__set_long_name(self, strdup(vmlinux_path[i])); + break; + } + } + + return err; +} + static int dso__load_kernel_sym(struct dso *self, struct map *map, struct perf_session *session, symbol_filter_t filter) { int err; - bool is_kallsyms; + const char *kallsyms_filename = NULL; + char *kallsyms_allocated_filename = NULL; + /* + * Step 1: if the user specified a vmlinux filename, use it and only + * it, reporting errors to the user if it cannot be used. + * + * For instance, try to analyse an ARM perf.data file _without_ a + * build-id, or if the user specifies the wrong path to the right + * vmlinux file, obviously we can't fallback to another vmlinux (a + * x86_86 one, on the machine where analysis is being performed, say), + * or worse, /proc/kallsyms. + * + * If the specified file _has_ a build-id and there is a build-id + * section in the perf.data file, we will still do the expected + * validation in dso__load_vmlinux and will bail out if they don't + * match. + */ + if (symbol_conf.vmlinux_name != NULL) { + err = dso__load_vmlinux(self, map, session, + symbol_conf.vmlinux_name, filter); + goto out_try_fixup; + } if (vmlinux_path != NULL) { - int i; - pr_debug("Looking at the vmlinux_path (%d entries long)\n", - vmlinux_path__nr_entries); - for (i = 0; i < vmlinux_path__nr_entries; ++i) { - err = dso__load_vmlinux(self, map, session, - vmlinux_path[i], filter); - if (err > 0) { - pr_debug("Using %s for symbols\n", - vmlinux_path[i]); - dso__set_long_name(self, - strdup(vmlinux_path[i])); - goto out_fixup; + err = dso__load_vmlinux_path(self, map, session, filter); + if (err > 0) + goto out_fixup; + } + + /* + * Say the kernel DSO was created when processing the build-id header table, + * we have a build-id, so check if it is the same as the running kernel, + * using it if it is. + */ + if (self->has_build_id) { + u8 kallsyms_build_id[BUILD_ID_SIZE]; + char sbuild_id[BUILD_ID_SIZE * 2 + 1]; + + if (sysfs__read_build_id("/sys/kernel/notes", kallsyms_build_id, + sizeof(kallsyms_build_id)) == 0) { + if (dso__build_id_equal(self, kallsyms_build_id)) { + kallsyms_filename = "/proc/kallsyms"; + goto do_kallsyms; } } - } + /* + * Now look if we have it on the build-id cache in + * $HOME/.debug/[kernel.kallsyms]. + */ + build_id__sprintf(self->build_id, sizeof(self->build_id), + sbuild_id); + + if (asprintf(&kallsyms_allocated_filename, + "%s/.debug/[kernel.kallsyms]/%s", + getenv("HOME"), sbuild_id) == -1) + return -1; - is_kallsyms = self->long_name[0] == '['; - if (is_kallsyms) - goto do_kallsyms; + kallsyms_filename = kallsyms_allocated_filename; - err = dso__load_vmlinux(self, map, session, self->long_name, filter); - if (err <= 0) { - pr_info("The file %s cannot be used, " - "trying to use /proc/kallsyms...", self->long_name); -do_kallsyms: - err = dso__load_kallsyms(self, map, session, filter); - if (err > 0 && !is_kallsyms) - dso__set_long_name(self, strdup("[kernel.kallsyms]")); + if (access(kallsyms_filename, F_OK)) { + free(kallsyms_allocated_filename); + return -1; + } + } else { + /* + * Last resort, if we don't have a build-id and couldn't find + * any vmlinux file, try the running kernel kallsyms table. + */ + kallsyms_filename = "/proc/kallsyms"; } +do_kallsyms: + err = dso__load_kallsyms(self, kallsyms_filename, map, session, filter); + free(kallsyms_allocated_filename); + +out_try_fixup: if (err > 0) { out_fixup: + if (kallsyms_filename != NULL) + dso__set_long_name(self, strdup("[kernel.kallsyms]")); map__fixup_start(map); map__fixup_end(map); } @@ -1576,19 +1704,19 @@ static struct dso *dsos__find(struct list_head *head, const char *name) struct dso *pos; list_for_each_entry(pos, head, node) - if (strcmp(pos->name, name) == 0) + if (strcmp(pos->long_name, name) == 0) return pos; return NULL; } -struct dso *dsos__findnew(const char *name) +struct dso *__dsos__findnew(struct list_head *head, const char *name) { - struct dso *dso = dsos__find(&dsos__user, name); + struct dso *dso = dsos__find(head, name); if (!dso) { dso = dso__new(name); if (dso != NULL) { - dsos__add(&dsos__user, dso); + dsos__add(head, dso); dso__set_basename(dso); } } @@ -1613,42 +1741,59 @@ void dsos__fprintf(FILE *fp) __dsos__fprintf(&dsos__user, fp); } -static size_t __dsos__fprintf_buildid(struct list_head *head, FILE *fp) +static size_t __dsos__fprintf_buildid(struct list_head *head, FILE *fp, + bool with_hits) { struct dso *pos; size_t ret = 0; list_for_each_entry(pos, head, node) { + if (with_hits && !pos->hit) + continue; ret += dso__fprintf_buildid(pos, fp); ret += fprintf(fp, " %s\n", pos->long_name); } return ret; } -size_t dsos__fprintf_buildid(FILE *fp) +size_t dsos__fprintf_buildid(FILE *fp, bool with_hits) { - return (__dsos__fprintf_buildid(&dsos__kernel, fp) + - __dsos__fprintf_buildid(&dsos__user, fp)); + return (__dsos__fprintf_buildid(&dsos__kernel, fp, with_hits) + + __dsos__fprintf_buildid(&dsos__user, fp, with_hits)); } -static struct dso *dsos__create_kernel( const char *vmlinux) +struct dso *dso__new_kernel(const char *name) { - struct dso *kernel = dso__new(vmlinux ?: "[kernel.kallsyms]"); + struct dso *self = dso__new(name ?: "[kernel.kallsyms]"); + + if (self != NULL) { + self->short_name = "[kernel]"; + self->kernel = 1; + } + + return self; +} + +void dso__read_running_kernel_build_id(struct dso *self) +{ + if (sysfs__read_build_id("/sys/kernel/notes", self->build_id, + sizeof(self->build_id)) == 0) + self->has_build_id = true; +} + +static struct dso *dsos__create_kernel(const char *vmlinux) +{ + struct dso *kernel = dso__new_kernel(vmlinux); if (kernel == NULL) return NULL; - kernel->short_name = "[kernel]"; - kernel->kernel = 1; - vdso = dso__new("[vdso]"); if (vdso == NULL) goto out_delete_kernel_dso; dso__set_loaded(vdso, MAP__FUNCTION); - if (sysfs__read_build_id("/sys/kernel/notes", kernel->build_id, - sizeof(kernel->build_id)) == 0) - kernel->has_build_id = true; + dso__read_running_kernel_build_id(kernel); dsos__add(&dsos__kernel, kernel); dsos__add(&dsos__user, vdso); @@ -1660,32 +1805,37 @@ out_delete_kernel_dso: return NULL; } -static int map_groups__create_kernel_maps(struct map_groups *self, const char *vmlinux) +int __map_groups__create_kernel_maps(struct map_groups *self, + struct map *vmlinux_maps[MAP__NR_TYPES], + struct dso *kernel) { - struct map *functions, *variables; - struct dso *kernel = dsos__create_kernel(vmlinux); - - if (kernel == NULL) - return -1; + enum map_type type; - functions = map__new2(0, kernel, MAP__FUNCTION); - if (functions == NULL) - return -1; + for (type = 0; type < MAP__NR_TYPES; ++type) { + vmlinux_maps[type] = map__new2(0, kernel, type); + if (vmlinux_maps[type] == NULL) + return -1; - variables = map__new2(0, kernel, MAP__VARIABLE); - if (variables == NULL) { - map__delete(functions); - return -1; + vmlinux_maps[type]->map_ip = + vmlinux_maps[type]->unmap_ip = identity__map_ip; + map_groups__insert(self, vmlinux_maps[type]); } - functions->map_ip = functions->unmap_ip = - variables->map_ip = variables->unmap_ip = identity__map_ip; - map_groups__insert(self, functions); - map_groups__insert(self, variables); - return 0; } +static int map_groups__create_kernel_maps(struct map_groups *self, + struct map *vmlinux_maps[MAP__NR_TYPES], + const char *vmlinux) +{ + struct dso *kernel = dsos__create_kernel(vmlinux); + + if (kernel == NULL) + return -1; + + return __map_groups__create_kernel_maps(self, vmlinux_maps, kernel); +} + static void vmlinux_path__exit(void) { while (--vmlinux_path__nr_entries >= 0) { @@ -1793,7 +1943,7 @@ out_free_comm_list: int perf_session__create_kernel_maps(struct perf_session *self) { - if (map_groups__create_kernel_maps(&self->kmaps, + if (map_groups__create_kernel_maps(&self->kmaps, self->vmlinux_maps, symbol_conf.vmlinux_name) < 0) return -1; diff --git a/tools/perf/util/symbol.h b/tools/perf/util/symbol.h index 8aded2356f79..124302778c09 100644 --- a/tools/perf/util/symbol.h +++ b/tools/perf/util/symbol.h @@ -8,6 +8,8 @@ #include <linux/rbtree.h> #include "event.h" +#define DEBUG_CACHE_DIR ".debug" + #ifdef HAVE_CPLUS_DEMANGLE extern char *cplus_demangle(const char *, int); @@ -58,7 +60,8 @@ struct symbol_conf { sort_by_name, show_nr_samples, use_callchain, - exclude_other; + exclude_other, + full_paths; const char *vmlinux_name, *field_sep; char *dso_list_str, @@ -94,6 +97,7 @@ struct dso { u8 slen_calculated:1; u8 has_build_id:1; u8 kernel:1; + u8 hit:1; unsigned char origin; u8 sorted_by_name; u8 loaded; @@ -105,6 +109,7 @@ struct dso { }; struct dso *dso__new(const char *name); +struct dso *dso__new_kernel(const char *name); void dso__delete(struct dso *self); bool dso__loaded(const struct dso *self, enum map_type type); @@ -112,18 +117,30 @@ bool dso__sorted_by_name(const struct dso *self, enum map_type type); void dso__sort_by_name(struct dso *self, enum map_type type); +extern struct list_head dsos__user, dsos__kernel; + +struct dso *__dsos__findnew(struct list_head *head, const char *name); + +static inline struct dso *dsos__findnew(const char *name) +{ + return __dsos__findnew(&dsos__user, name); +} + struct perf_session; -struct dso *dsos__findnew(const char *name); int dso__load(struct dso *self, struct map *map, struct perf_session *session, symbol_filter_t filter); +int dso__load_vmlinux_path(struct dso *self, struct map *map, + struct perf_session *session, symbol_filter_t filter); void dsos__fprintf(FILE *fp); -size_t dsos__fprintf_buildid(FILE *fp); +size_t dsos__fprintf_buildid(FILE *fp, bool with_hits); size_t dso__fprintf_buildid(struct dso *self, FILE *fp); size_t dso__fprintf(struct dso *self, enum map_type type, FILE *fp); char dso__symtab_origin(const struct dso *self); +void dso__set_long_name(struct dso *self, char *name); void dso__set_build_id(struct dso *self, void *build_id); +void dso__read_running_kernel_build_id(struct dso *self); struct symbol *dso__find_symbol(struct dso *self, enum map_type type, u64 addr); struct symbol *dso__find_symbol_by_name(struct dso *self, enum map_type type, const char *name); @@ -131,11 +148,17 @@ struct symbol *dso__find_symbol_by_name(struct dso *self, enum map_type type, 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); bool dsos__read_build_ids(void); -int build_id__sprintf(u8 *self, int len, char *bf); +int build_id__sprintf(const u8 *self, int len, char *bf); +int kallsyms__parse(const char *filename, void *arg, + int (*process_symbol)(void *arg, const char *name, + char type, u64 start)); int symbol__init(void); +bool symbol_type__is_a(char symbol_type, enum map_type map_type); + int perf_session__create_kernel_maps(struct perf_session *self); -extern struct list_head dsos__user, dsos__kernel; +struct map *perf_session__new_module_map(struct perf_session *self, u64 start, + const char *filename); extern struct dso *vdso; #endif /* __PERF_SYMBOL */ diff --git a/tools/perf/util/thread.h b/tools/perf/util/thread.h index c206f72c8881..e35653c1817c 100644 --- a/tools/perf/util/thread.h +++ b/tools/perf/util/thread.h @@ -48,6 +48,11 @@ static inline struct map *thread__find_map(struct thread *self, return self ? map_groups__find(&self->mg, type, addr) : NULL; } +void thread__find_addr_map(struct thread *self, + struct perf_session *session, u8 cpumode, + enum map_type type, u64 addr, + struct addr_location *al); + void thread__find_addr_location(struct thread *self, struct perf_session *session, u8 cpumode, enum map_type type, u64 addr, @@ -67,4 +72,8 @@ map_groups__find_function(struct map_groups *self, struct perf_session *session, struct map *map_groups__find_by_name(struct map_groups *self, enum map_type type, const char *name); + +int __map_groups__create_kernel_maps(struct map_groups *self, + struct map *vmlinux_maps[MAP__NR_TYPES], + struct dso *kernel); #endif /* __PERF_THREAD_H */ diff --git a/tools/perf/util/trace-event-info.c b/tools/perf/util/trace-event-info.c index cace35595530..5ea8973ad331 100644 --- a/tools/perf/util/trace-event-info.c +++ b/tools/perf/util/trace-event-info.c @@ -20,6 +20,7 @@ */ #define _GNU_SOURCE #include <dirent.h> +#include <mntent.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -37,6 +38,7 @@ #include "../perf.h" #include "trace-event.h" +#include "debugfs.h" #define VERSION "0.5" @@ -101,32 +103,12 @@ void *malloc_or_die(unsigned int size) static const char *find_debugfs(void) { - static char debugfs[MAX_PATH+1]; - static int debugfs_found; - char type[100]; - FILE *fp; - - if (debugfs_found) - return debugfs; - - if ((fp = fopen("/proc/mounts","r")) == NULL) - die("Can't open /proc/mounts for read"); - - while (fscanf(fp, "%*s %" - STR(MAX_PATH) - "s %99s %*s %*d %*d\n", - debugfs, type) == 2) { - if (strcmp(type, "debugfs") == 0) - break; - } - fclose(fp); - - if (strcmp(type, "debugfs") != 0) - die("debugfs not mounted, please mount"); + const char *path = debugfs_mount(NULL); - debugfs_found = 1; + if (!path) + die("Your kernel not support debugfs filesystem"); - return debugfs; + return path; } /* @@ -271,6 +253,8 @@ static void read_header_files(void) write_or_die("header_page", 12); write_or_die(&size, 8); check_size = copy_file_fd(fd); + close(fd); + if (size != check_size) die("wrong size for '%s' size=%lld read=%lld", path, size, check_size); @@ -289,6 +273,7 @@ static void read_header_files(void) if (size != check_size) die("wrong size for '%s'", path); put_tracing_file(path); + close(fd); } static bool name_in_tp_list(char *sys, struct tracepoint_path *tps) @@ -317,7 +302,8 @@ static void copy_event_system(const char *sys, struct tracepoint_path *tps) die("can't read directory '%s'", sys); while ((dent = readdir(dir))) { - if (strcmp(dent->d_name, ".") == 0 || + if (dent->d_type != DT_DIR || + strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0 || !name_in_tp_list(dent->d_name, tps)) continue; @@ -334,7 +320,8 @@ static void copy_event_system(const char *sys, struct tracepoint_path *tps) rewinddir(dir); while ((dent = readdir(dir))) { - if (strcmp(dent->d_name, ".") == 0 || + if (dent->d_type != DT_DIR || + strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0 || !name_in_tp_list(dent->d_name, tps)) continue; @@ -353,6 +340,7 @@ static void copy_event_system(const char *sys, struct tracepoint_path *tps) free(format); } + closedir(dir); } static void read_ftrace_files(struct tracepoint_path *tps) @@ -394,26 +382,21 @@ static void read_event_files(struct tracepoint_path *tps) die("can't read directory '%s'", path); while ((dent = readdir(dir))) { - if (strcmp(dent->d_name, ".") == 0 || + if (dent->d_type != DT_DIR || + strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0 || strcmp(dent->d_name, "ftrace") == 0 || !system_in_tp_list(dent->d_name, tps)) continue; - sys = malloc_or_die(strlen(path) + strlen(dent->d_name) + 2); - sprintf(sys, "%s/%s", path, dent->d_name); - ret = stat(sys, &st); - free(sys); - if (ret < 0) - continue; - if (S_ISDIR(st.st_mode)) - count++; + count++; } write_or_die(&count, 4); rewinddir(dir); while ((dent = readdir(dir))) { - if (strcmp(dent->d_name, ".") == 0 || + if (dent->d_type != DT_DIR || + strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0 || strcmp(dent->d_name, "ftrace") == 0 || !system_in_tp_list(dent->d_name, tps)) @@ -422,14 +405,13 @@ static void read_event_files(struct tracepoint_path *tps) sprintf(sys, "%s/%s", path, dent->d_name); ret = stat(sys, &st); if (ret >= 0) { - if (S_ISDIR(st.st_mode)) { - write_or_die(dent->d_name, strlen(dent->d_name) + 1); - copy_event_system(sys, tps); - } + write_or_die(dent->d_name, strlen(dent->d_name) + 1); + copy_event_system(sys, tps); } free(sys); } + closedir(dir); put_tracing_file(path); } @@ -533,7 +515,7 @@ int read_tracing_data(int fd, struct perf_event_attr *pattrs, int nb_events) write_or_die(buf, 1); /* save page_size */ - page_size = getpagesize(); + page_size = sysconf(_SC_PAGESIZE); write_or_die(&page_size, 4); read_header_files(); diff --git a/tools/perf/util/util.c b/tools/perf/util/util.c new file mode 100644 index 000000000000..f9b890fde681 --- /dev/null +++ b/tools/perf/util/util.c @@ -0,0 +1,94 @@ +#include "util.h" +#include <sys/mman.h> + +int mkdir_p(char *path, mode_t mode) +{ + struct stat st; + int err; + char *d = path; + + if (*d != '/') + return -1; + + if (stat(path, &st) == 0) + return 0; + + while (*++d == '/'); + + while ((d = strchr(d, '/'))) { + *d = '\0'; + err = stat(path, &st) && mkdir(path, mode); + *d++ = '/'; + if (err) + return -1; + while (*d == '/') + ++d; + } + return (stat(path, &st) && mkdir(path, mode)) ? -1 : 0; +} + +static int slow_copyfile(const char *from, const char *to) +{ + int err = 0; + char *line = NULL; + size_t n; + FILE *from_fp = fopen(from, "r"), *to_fp; + + if (from_fp == NULL) + goto out; + + to_fp = fopen(to, "w"); + if (to_fp == NULL) + goto out_fclose_from; + + while (getline(&line, &n, from_fp) > 0) + if (fputs(line, to_fp) == EOF) + goto out_fclose_to; + err = 0; +out_fclose_to: + fclose(to_fp); + free(line); +out_fclose_from: + fclose(from_fp); +out: + return err; +} + +int copyfile(const char *from, const char *to) +{ + int fromfd, tofd; + struct stat st; + void *addr; + int err = -1; + + if (stat(from, &st)) + goto out; + + if (st.st_size == 0) /* /proc? do it slowly... */ + return slow_copyfile(from, to); + + fromfd = open(from, O_RDONLY); + if (fromfd < 0) + goto out; + + tofd = creat(to, 0755); + if (tofd < 0) + goto out_close_from; + + addr = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fromfd, 0); + if (addr == MAP_FAILED) + goto out_close_to; + + if (write(tofd, addr, st.st_size) == st.st_size) + err = 0; + + munmap(addr, st.st_size); +out_close_to: + close(tofd); + if (err) + unlink(to); +out_close_from: + close(fromfd); +out: + return err; +} diff --git a/tools/perf/util/util.h b/tools/perf/util/util.h index c673d8825883..0f5b2a6f1080 100644 --- a/tools/perf/util/util.h +++ b/tools/perf/util/util.h @@ -403,4 +403,7 @@ void git_qsort(void *base, size_t nmemb, size_t size, #endif #endif +int mkdir_p(char *path, mode_t mode); +int copyfile(const char *from, const char *to); + #endif diff --git a/tools/perf/util/values.c b/tools/perf/util/values.c index 1c15e39f99e3..cfa55d686e3b 100644 --- a/tools/perf/util/values.c +++ b/tools/perf/util/values.c @@ -169,6 +169,7 @@ static void perf_read_values__display_pretty(FILE *fp, counterwidth[j], values->value[i][j]); fprintf(fp, "\n"); } + free(counterwidth); } static void perf_read_values__display_raw(FILE *fp, |