diff options
Diffstat (limited to 'tools')
66 files changed, 5352 insertions, 899 deletions
diff --git a/tools/perf/Documentation/perf-bench.txt b/tools/perf/Documentation/perf-bench.txt new file mode 100644 index 000000000000..ae525ac5a2ce --- /dev/null +++ b/tools/perf/Documentation/perf-bench.txt @@ -0,0 +1,120 @@ +perf-bench(1) +============ + +NAME +---- +perf-bench - General framework for benchmark suites + +SYNOPSIS +-------- +[verse] +'perf bench' [<common options>] <subsystem> <suite> [<options>] + +DESCRIPTION +----------- +This 'perf bench' command is general framework for benchmark suites. + +COMMON OPTIONS +-------------- +-f:: +--format=:: +Specify format style. +Current available format styles are, + +'default':: +Default style. This is mainly for human reading. +--------------------- +% perf bench sched pipe # with no style specify +(executing 1000000 pipe operations between two tasks) + Total time:5.855 sec + 5.855061 usecs/op + 170792 ops/sec +--------------------- + +'simple':: +This simple style is friendly for automated +processing by scripts. +--------------------- +% perf bench --format=simple sched pipe # specified simple +5.988 +--------------------- + +SUBSYSTEM +--------- + +'sched':: + Scheduler and IPC mechanisms. + +SUITES FOR 'sched' +~~~~~~~~~~~~~~~~~~ +*messaging*:: +Suite for evaluating performance of scheduler and IPC mechanisms. +Based on hackbench by Rusty Russell. + +Options of *pipe* +^^^^^^^^^^^^^^^^^ +-p:: +--pipe:: +Use pipe() instead of socketpair() + +-t:: +--thread:: +Be multi thread instead of multi process + +-g:: +--group=:: +Specify number of groups + +-l:: +--loop=:: +Specify number of loops + +Example of *messaging* +^^^^^^^^^^^^^^^^^^^^^^ + +--------------------- +% perf bench sched messaging # run with default +options (20 sender and receiver processes per group) +(10 groups == 400 processes run) + + Total time:0.308 sec + +% perf bench sched messaging -t -g 20 # be multi-thread,with 20 groups +(20 sender and receiver threads per group) +(20 groups == 800 threads run) + + Total time:0.582 sec +--------------------- + +*pipe*:: +Suite for pipe() system call. +Based on pipe-test-1m.c by Ingo Molnar. + +Options of *pipe* +^^^^^^^^^^^^^^^^^ +-l:: +--loop=:: +Specify number of loops. + +Example of *pipe* +^^^^^^^^^^^^^^^^^ + +--------------------- +% perf bench sched pipe +(executing 1000000 pipe operations between two tasks) + + Total time:8.091 sec + 8.091833 usecs/op + 123581 ops/sec + +% perf bench sched pipe -l 1000 # loop 1000 +(executing 1000 pipe operations between two tasks) + + Total time:0.016 sec + 16.948000 usecs/op + 59004 ops/sec +--------------------- + +SEE ALSO +-------- +linkperf:perf[1] diff --git a/tools/perf/Documentation/perf-buildid-list.txt b/tools/perf/Documentation/perf-buildid-list.txt new file mode 100644 index 000000000000..01b642c0bf8f --- /dev/null +++ b/tools/perf/Documentation/perf-buildid-list.txt @@ -0,0 +1,34 @@ +perf-buildid-list(1) +==================== + +NAME +---- +perf-buildid-list - List the buildids in a perf.data file + +SYNOPSIS +-------- +[verse] +'perf buildid-list <options>' + +DESCRIPTION +----------- +This command displays the buildids found in a perf.data file, so that other +tools can be used to fetch packages with matching symbol tables for use by +perf report. + +OPTIONS +------- +-i:: +--input=:: + Input file name. (default: perf.data) +-f:: +--force:: + Don't do ownership validation. +-v:: +--verbose:: + Be more verbose. + +SEE ALSO +-------- +linkperf:perf-record[1], linkperf:perf-top[1], +linkperf:perf-report[1] diff --git a/tools/perf/Documentation/perf-probe.txt b/tools/perf/Documentation/perf-probe.txt new file mode 100644 index 000000000000..9270594e6dfd --- /dev/null +++ b/tools/perf/Documentation/perf-probe.txt @@ -0,0 +1,49 @@ +perf-probe(1) +============= + +NAME +---- +perf-probe - Define new dynamic tracepoints + +SYNOPSIS +-------- +[verse] +'perf probe' [options] --add 'PROBE' [--add 'PROBE' ...] +or +'perf probe' [options] 'PROBE' ['PROBE' ...] + + +DESCRIPTION +----------- +This command defines dynamic tracepoint events, by symbol and registers +without debuginfo, or by C expressions (C line numbers, C function names, +and C local variables) with debuginfo. + + +OPTIONS +------- +-k:: +--vmlinux=PATH:: + Specify vmlinux path which has debuginfo (Dwarf binary). + +-v:: +--verbose:: + Be more verbose (show parsed arguments, etc). + +-a:: +--add:: + Define a probe point (see PROBE SYNTAX for detail) + +PROBE SYNTAX +------------ +Probe points are defined by following syntax. + + "FUNC[+OFFS|:RLN|%return][@SRC]|SRC:ALN [ARG ...]" + +'FUNC' specifies a probed function name, and it may have one of the following options; '+OFFS' is the offset from function entry address in bytes, 'RLN' is the relative-line number from function entry line, and '%return' means that it probes function return. In addition, 'SRC' specifies a source file which has that function. +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). + +SEE ALSO +-------- +linkperf:perf-trace[1], linkperf:perf-record[1] diff --git a/tools/perf/Documentation/perf-report.txt b/tools/perf/Documentation/perf-report.txt index 59f0b846cd71..9dccb180b7af 100644 --- a/tools/perf/Documentation/perf-report.txt +++ b/tools/perf/Documentation/perf-report.txt @@ -24,11 +24,11 @@ OPTIONS --dsos=:: Only consider symbols in these dsos. CSV that understands file://filename entries. --n ---show-nr-samples +-n:: +--show-nr-samples:: Show the number of samples for each symbol --T ---threads +-T:: +--threads:: Show per-thread event counters -C:: --comms=:: diff --git a/tools/perf/Documentation/perf-timechart.txt b/tools/perf/Documentation/perf-timechart.txt index a7910099d6fd..4b1788355eca 100644 --- a/tools/perf/Documentation/perf-timechart.txt +++ b/tools/perf/Documentation/perf-timechart.txt @@ -31,9 +31,12 @@ OPTIONS -w:: --width=:: Select the width of the SVG file (default: 1000) --p:: +-P:: --power-only:: Only output the CPU power section of the diagram +-p:: +--process:: + Select the processes to display, by name or PID SEE ALSO diff --git a/tools/perf/Makefile b/tools/perf/Makefile index 106c15055b50..d7198c54bb69 100644 --- a/tools/perf/Makefile +++ b/tools/perf/Makefile @@ -145,6 +145,8 @@ all:: # Define NO_EXTERNAL_GREP if you don't want "perf grep" to ever call # your external grep (e.g., if your system lacks grep, if its grep is # broken, or spawning external process is slower than built-in grep perf has). +# +# Define LDFLAGS=-static to build a static binary. PERF-VERSION-FILE: .FORCE-PERF-VERSION-FILE @$(SHELL_PATH) util/PERF-VERSION-GEN @@ -164,10 +166,12 @@ ifdef NO_64BIT MBITS := -m32 else # - # If we're on a 64-bit kernel, use -m64: + # If we're on a 64-bit kernel (except ia64), use -m64: # - ifneq ($(patsubst %64,%,$(uname_M)),$(uname_M)) - MBITS := -m64 + ifneq ($(uname_M),ia64) + ifneq ($(patsubst %64,%,$(uname_M)),$(uname_M)) + MBITS := -m64 + endif endif endif @@ -177,8 +181,7 @@ endif # Include saner warnings here, which can catch bugs: # -EXTRA_WARNINGS := -Wcast-align -EXTRA_WARNINGS := $(EXTRA_WARNINGS) -Wformat +EXTRA_WARNINGS := -Wformat EXTRA_WARNINGS := $(EXTRA_WARNINGS) -Wformat-security EXTRA_WARNINGS := $(EXTRA_WARNINGS) -Wformat-y2k EXTRA_WARNINGS := $(EXTRA_WARNINGS) -Wshadow @@ -201,8 +204,15 @@ EXTRA_WARNINGS := $(EXTRA_WARNINGS) -Wold-style-definition EXTRA_WARNINGS := $(EXTRA_WARNINGS) -Wstrict-prototypes EXTRA_WARNINGS := $(EXTRA_WARNINGS) -Wdeclaration-after-statement -CFLAGS = $(MBITS) -ggdb3 -Wall -Wextra -std=gnu99 -Werror -O6 -fstack-protector-all -D_FORTIFY_SOURCE=2 $(EXTRA_WARNINGS) -LDFLAGS = -lpthread -lrt -lelf -lm +ifeq ("$(origin DEBUG)", "command line") + PERF_DEBUG = $(DEBUG) +endif +ifndef PERF_DEBUG + CFLAGS_OPTIMIZE = -O6 +endif + +CFLAGS = $(MBITS) -ggdb3 -Wall -Wextra -std=gnu99 -Werror $(CFLAGS_OPTIMIZE) -D_FORTIFY_SOURCE=2 $(EXTRA_WARNINGS) +EXTLIBS = -lpthread -lrt -lelf -lm ALL_CFLAGS = $(CFLAGS) ALL_LDFLAGS = $(LDFLAGS) STRIP ?= strip @@ -253,6 +263,9 @@ PTHREAD_LIBS = -lpthread # explicitly what architecture to check for. Fix this up for yours.. SPARSE_FLAGS = -D__BIG_ENDIAN__ -D__powerpc__ +ifeq ($(shell sh -c "echo 'int foo(void) {char X[2]; return 3;}' | $(CC) -x c -c -Werror -fstack-protector-all - -o /dev/null >/dev/null 2>&1 && echo y"), y) + CFLAGS := $(CFLAGS) -fstack-protector-all +endif ### --- END CONFIGURATION SECTION --- @@ -328,8 +341,27 @@ LIB_FILE=libperf.a LIB_H += ../../include/linux/perf_event.h LIB_H += ../../include/linux/rbtree.h LIB_H += ../../include/linux/list.h +LIB_H += ../../include/linux/stringify.h +LIB_H += util/include/linux/bitmap.h +LIB_H += util/include/linux/bitops.h +LIB_H += util/include/linux/compiler.h +LIB_H += util/include/linux/ctype.h +LIB_H += util/include/linux/kernel.h LIB_H += util/include/linux/list.h +LIB_H += util/include/linux/module.h +LIB_H += util/include/linux/poison.h +LIB_H += util/include/linux/prefetch.h +LIB_H += util/include/linux/rbtree.h +LIB_H += util/include/linux/string.h +LIB_H += util/include/linux/types.h +LIB_H += util/include/asm/asm-offsets.h +LIB_H += util/include/asm/bitops.h +LIB_H += util/include/asm/byteorder.h +LIB_H += util/include/asm/swab.h +LIB_H += util/include/asm/system.h +LIB_H += util/include/asm/uaccess.h LIB_H += perf.h +LIB_H += util/debugfs.h LIB_H += util/event.h LIB_H += util/types.h LIB_H += util/levenshtein.h @@ -355,7 +387,9 @@ LIB_OBJS += util/abspath.o LIB_OBJS += util/alias.o LIB_OBJS += util/config.o LIB_OBJS += util/ctype.o +LIB_OBJS += util/debugfs.o LIB_OBJS += util/environment.o +LIB_OBJS += util/event.o LIB_OBJS += util/exec_cmd.o LIB_OBJS += util/help.o LIB_OBJS += util/levenshtein.o @@ -363,6 +397,9 @@ LIB_OBJS += util/parse-options.o LIB_OBJS += util/parse-events.o LIB_OBJS += util/path.o LIB_OBJS += util/rbtree.o +LIB_OBJS += util/bitmap.o +LIB_OBJS += util/hweight.o +LIB_OBJS += util/find_next_bit.o LIB_OBJS += util/run-command.o LIB_OBJS += util/quote.o LIB_OBJS += util/strbuf.o @@ -389,8 +426,16 @@ LIB_OBJS += util/hist.o LIB_OBJS += util/data_map.o BUILTIN_OBJS += builtin-annotate.o + +BUILTIN_OBJS += builtin-bench.o + +# Benchmark modules +BUILTIN_OBJS += bench/sched-messaging.o +BUILTIN_OBJS += bench/sched-pipe.o + BUILTIN_OBJS += builtin-help.o BUILTIN_OBJS += builtin-sched.o +BUILTIN_OBJS += builtin-buildid-list.o BUILTIN_OBJS += builtin-list.o BUILTIN_OBJS += builtin-record.o BUILTIN_OBJS += builtin-report.o @@ -398,6 +443,8 @@ BUILTIN_OBJS += builtin-stat.o BUILTIN_OBJS += builtin-timechart.o BUILTIN_OBJS += builtin-top.o BUILTIN_OBJS += builtin-trace.o +BUILTIN_OBJS += builtin-probe.o +BUILTIN_OBJS += builtin-kmem.o PERFLIBS = $(LIB_FILE) @@ -428,27 +475,44 @@ ifeq ($(uname_S),Darwin) PTHREAD_LIBS = endif -ifneq ($(shell sh -c "(echo '\#include <libelf.h>'; echo 'int main(void) { Elf * elf = elf_begin(0, ELF_C_READ_MMAP, 0); return (long)elf; }') | $(CC) -x c - $(ALL_CFLAGS) -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -o /dev/null $(ALL_LDFLAGS) > /dev/null 2>&1 && echo y"), y) +ifeq ($(shell sh -c "(echo '\#include <libelf.h>'; echo 'int main(void) { Elf * elf = elf_begin(0, ELF_C_READ, 0); return (long)elf; }') | $(CC) -x c - $(ALL_CFLAGS) -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -o /dev/null $(ALL_LDFLAGS) $(EXTLIBS) > /dev/null 2>&1 && echo y"), y) +ifneq ($(shell sh -c "(echo '\#include <gnu/libc-version.h>'; echo 'int main(void) { const char * version = gnu_get_libc_version(); return (long)version; }') | $(CC) -x c - $(ALL_CFLAGS) -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -o /dev/null $(ALL_LDFLAGS) $(EXTLIBS) > /dev/null 2>&1 && echo y"), y) + msg := $(error No gnu/libc-version.h found, please install glibc-dev[el]); +endif + + ifneq ($(shell sh -c "(echo '\#include <libelf.h>'; echo 'int main(void) { Elf * elf = elf_begin(0, ELF_C_READ_MMAP, 0); return (long)elf; }') | $(CC) -x c - $(ALL_CFLAGS) -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -o /dev/null $(ALL_LDFLAGS) $(EXTLIBS) > /dev/null 2>&1 && echo y"), y) + BASIC_CFLAGS += -DLIBELF_NO_MMAP + endif +else msg := $(error No libelf.h/libelf found, please install libelf-dev/elfutils-libelf-devel and glibc-dev[el]); endif +ifneq ($(shell sh -c "(echo '\#include <libdwarf/dwarf.h>'; echo '\#include <libdwarf/libdwarf.h>'; echo 'int main(void) { Dwarf_Debug dbg; Dwarf_Error err; Dwarf_Ranges *rng; dwarf_init(0, DW_DLC_READ, 0, 0, &dbg, &err); dwarf_get_ranges(dbg, 0, &rng, 0, 0, &err); return (long)dbg; }') | $(CC) -x c - $(ALL_CFLAGS) -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -ldwarf -lelf -o /dev/null $(ALL_LDFLAGS) $(EXTLIBS) > /dev/null 2>&1 && echo y"), y) + msg := $(warning No libdwarf.h found or old libdwarf.h found, disables dwarf support. Please install libdwarf-dev/libdwarf-devel >= 20081231); + BASIC_CFLAGS += -DNO_LIBDWARF +else + EXTLIBS += -lelf -ldwarf + LIB_H += util/probe-finder.h + LIB_OBJS += util/probe-finder.o +endif + ifdef NO_DEMANGLE BASIC_CFLAGS += -DNO_DEMANGLE else - has_bfd := $(shell sh -c "(echo '\#include <bfd.h>'; echo 'int main(void) { bfd_demangle(0, 0, 0); return 0; }') | $(CC) -x c - $(ALL_CFLAGS) -o /dev/null $(ALL_LDFLAGS) -lbfd > /dev/null 2>&1 && echo y") + has_bfd := $(shell sh -c "(echo '\#include <bfd.h>'; echo 'int main(void) { bfd_demangle(0, 0, 0); return 0; }') | $(CC) -x c - $(ALL_CFLAGS) -o /dev/null $(ALL_LDFLAGS) $(EXTLIBS) -lbfd > /dev/null 2>&1 && echo y") ifeq ($(has_bfd),y) EXTLIBS += -lbfd else - has_bfd_iberty := $(shell sh -c "(echo '\#include <bfd.h>'; echo 'int main(void) { bfd_demangle(0, 0, 0); return 0; }') | $(CC) -x c - $(ALL_CFLAGS) -o /dev/null $(ALL_LDFLAGS) -lbfd -liberty > /dev/null 2>&1 && echo y") + has_bfd_iberty := $(shell sh -c "(echo '\#include <bfd.h>'; echo 'int main(void) { bfd_demangle(0, 0, 0); return 0; }') | $(CC) -x c - $(ALL_CFLAGS) -o /dev/null $(ALL_LDFLAGS) $(EXTLIBS) -lbfd -liberty > /dev/null 2>&1 && echo y") ifeq ($(has_bfd_iberty),y) EXTLIBS += -lbfd -liberty else - has_bfd_iberty_z := $(shell sh -c "(echo '\#include <bfd.h>'; echo 'int main(void) { bfd_demangle(0, 0, 0); return 0; }') | $(CC) -x c - $(ALL_CFLAGS) -o /dev/null $(ALL_LDFLAGS) -lbfd -liberty -lz > /dev/null 2>&1 && echo y") + has_bfd_iberty_z := $(shell sh -c "(echo '\#include <bfd.h>'; echo 'int main(void) { bfd_demangle(0, 0, 0); return 0; }') | $(CC) -x c - $(ALL_CFLAGS) -o /dev/null $(ALL_LDFLAGS) $(EXTLIBS) -lbfd -liberty -lz > /dev/null 2>&1 && echo y") ifeq ($(has_bfd_iberty_z),y) EXTLIBS += -lbfd -liberty -lz else - has_cplus_demangle := $(shell sh -c "(echo 'extern char *cplus_demangle(const char *, int);'; echo 'int main(void) { cplus_demangle(0, 0); return 0; }') | $(CC) -x c - $(ALL_CFLAGS) -o /dev/null $(ALL_LDFLAGS) -liberty > /dev/null 2>&1 && echo y") + has_cplus_demangle := $(shell sh -c "(echo 'extern char *cplus_demangle(const char *, int);'; echo 'int main(void) { cplus_demangle(0, 0); return 0; }') | $(CC) -x c - $(ALL_CFLAGS) -o /dev/null $(ALL_LDFLAGS) $(EXTLIBS) -liberty > /dev/null 2>&1 && echo y") ifeq ($(has_cplus_demangle),y) EXTLIBS += -liberty BASIC_CFLAGS += -DHAVE_CPLUS_DEMANGLE @@ -790,6 +854,19 @@ util/config.o: util/config.c PERF-CFLAGS util/rbtree.o: ../../lib/rbtree.c PERF-CFLAGS $(QUIET_CC)$(CC) -o util/rbtree.o -c $(ALL_CFLAGS) -DETC_PERFCONFIG='"$(ETC_PERFCONFIG_SQ)"' $< +# some perf warning policies can't fit to lib/bitmap.c, eg: it warns about variable shadowing +# from <string.h> that comes from kernel headers wrapping. +KBITMAP_FLAGS=`echo $(ALL_CFLAGS) | sed s/-Wshadow// | sed s/-Wswitch-default// | sed s/-Wextra//` + +util/bitmap.o: ../../lib/bitmap.c PERF-CFLAGS + $(QUIET_CC)$(CC) -o util/bitmap.o -c $(KBITMAP_FLAGS) -DETC_PERFCONFIG='"$(ETC_PERFCONFIG_SQ)"' $< + +util/hweight.o: ../../lib/hweight.c PERF-CFLAGS + $(QUIET_CC)$(CC) -o util/hweight.o -c $(ALL_CFLAGS) -DETC_PERFCONFIG='"$(ETC_PERFCONFIG_SQ)"' $< + +util/find_next_bit.o: ../../lib/find_next_bit.c PERF-CFLAGS + $(QUIET_CC)$(CC) -o util/find_next_bit.o -c $(ALL_CFLAGS) -DETC_PERFCONFIG='"$(ETC_PERFCONFIG_SQ)"' $< + perf-%$X: %.o $(PERFLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) diff --git a/tools/perf/bench/bench.h b/tools/perf/bench/bench.h new file mode 100644 index 000000000000..9fbd8d745fa1 --- /dev/null +++ b/tools/perf/bench/bench.h @@ -0,0 +1,16 @@ +#ifndef BENCH_H +#define BENCH_H + +extern int bench_sched_messaging(int argc, const char **argv, const char *prefix); +extern int bench_sched_pipe(int argc, const char **argv, const char *prefix); + +#define BENCH_FORMAT_DEFAULT_STR "default" +#define BENCH_FORMAT_DEFAULT 0 +#define BENCH_FORMAT_SIMPLE_STR "simple" +#define BENCH_FORMAT_SIMPLE 1 + +#define BENCH_FORMAT_UNKNOWN -1 + +extern int bench_format; + +#endif diff --git a/tools/perf/bench/sched-messaging.c b/tools/perf/bench/sched-messaging.c new file mode 100644 index 000000000000..605a2a959aa8 --- /dev/null +++ b/tools/perf/bench/sched-messaging.c @@ -0,0 +1,336 @@ +/* + * + * builtin-bench-messaging.c + * + * messaging: Benchmark for scheduler and IPC mechanisms + * + * Based on hackbench by Rusty Russell <rusty@rustcorp.com.au> + * Ported to perf by Hitoshi Mitake <mitake@dcl.info.waseda.ac.jp> + * + */ + +#include "../perf.h" +#include "../util/util.h" +#include "../util/parse-options.h" +#include "../builtin.h" +#include "bench.h" + +/* Test groups of 20 processes spraying to 20 receivers */ +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <sys/time.h> +#include <sys/poll.h> +#include <limits.h> + +#define DATASIZE 100 + +static int use_pipes = 0; +static unsigned int loops = 100; +static unsigned int thread_mode = 0; +static unsigned int num_groups = 10; + +struct sender_context { + unsigned int num_fds; + int ready_out; + int wakefd; + int out_fds[0]; +}; + +struct receiver_context { + unsigned int num_packets; + int in_fds[2]; + int ready_out; + int wakefd; +}; + +static void barf(const char *msg) +{ + fprintf(stderr, "%s (error: %s)\n", msg, strerror(errno)); + exit(1); +} + +static void fdpair(int fds[2]) +{ + if (use_pipes) { + if (pipe(fds) == 0) + return; + } else { + if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == 0) + return; + } + + barf(use_pipes ? "pipe()" : "socketpair()"); +} + +/* Block until we're ready to go */ +static void ready(int ready_out, int wakefd) +{ + char dummy; + struct pollfd pollfd = { .fd = wakefd, .events = POLLIN }; + + /* Tell them we're ready. */ + if (write(ready_out, &dummy, 1) != 1) + barf("CLIENT: ready write"); + + /* Wait for "GO" signal */ + if (poll(&pollfd, 1, -1) != 1) + barf("poll"); +} + +/* Sender sprays loops messages down each file descriptor */ +static void *sender(struct sender_context *ctx) +{ + char data[DATASIZE]; + unsigned int i, j; + + ready(ctx->ready_out, ctx->wakefd); + + /* Now pump to every receiver. */ + for (i = 0; i < loops; i++) { + for (j = 0; j < ctx->num_fds; j++) { + int ret, done = 0; + +again: + ret = write(ctx->out_fds[j], data + done, + sizeof(data)-done); + if (ret < 0) + barf("SENDER: write"); + done += ret; + if (done < DATASIZE) + goto again; + } + } + + return NULL; +} + + +/* One receiver per fd */ +static void *receiver(struct receiver_context* ctx) +{ + unsigned int i; + + if (!thread_mode) + close(ctx->in_fds[1]); + + /* Wait for start... */ + ready(ctx->ready_out, ctx->wakefd); + + /* Receive them all */ + for (i = 0; i < ctx->num_packets; i++) { + char data[DATASIZE]; + int ret, done = 0; + +again: + ret = read(ctx->in_fds[0], data + done, DATASIZE - done); + if (ret < 0) + barf("SERVER: read"); + done += ret; + if (done < DATASIZE) + goto again; + } + + return NULL; +} + +static pthread_t create_worker(void *ctx, void *(*func)(void *)) +{ + pthread_attr_t attr; + pthread_t childid; + int err; + + if (!thread_mode) { + /* process mode */ + /* Fork the receiver. */ + switch (fork()) { + case -1: + barf("fork()"); + break; + case 0: + (*func) (ctx); + exit(0); + break; + default: + break; + } + + return (pthread_t)0; + } + + if (pthread_attr_init(&attr) != 0) + barf("pthread_attr_init:"); + +#ifndef __ia64__ + if (pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN) != 0) + barf("pthread_attr_setstacksize"); +#endif + + err = pthread_create(&childid, &attr, func, ctx); + if (err != 0) { + fprintf(stderr, "pthread_create failed: %s (%d)\n", + strerror(err), err); + exit(-1); + } + return childid; +} + +static void reap_worker(pthread_t id) +{ + int proc_status; + void *thread_status; + + if (!thread_mode) { + /* process mode */ + wait(&proc_status); + if (!WIFEXITED(proc_status)) + exit(1); + } else { + pthread_join(id, &thread_status); + } +} + +/* One group of senders and receivers */ +static unsigned int group(pthread_t *pth, + unsigned int num_fds, + int ready_out, + int wakefd) +{ + unsigned int i; + struct sender_context *snd_ctx = malloc(sizeof(struct sender_context) + + num_fds * sizeof(int)); + + if (!snd_ctx) + barf("malloc()"); + + for (i = 0; i < num_fds; i++) { + int fds[2]; + struct receiver_context *ctx = malloc(sizeof(*ctx)); + + if (!ctx) + barf("malloc()"); + + + /* Create the pipe between client and server */ + fdpair(fds); + + ctx->num_packets = num_fds * loops; + ctx->in_fds[0] = fds[0]; + ctx->in_fds[1] = fds[1]; + ctx->ready_out = ready_out; + ctx->wakefd = wakefd; + + pth[i] = create_worker(ctx, (void *)receiver); + + snd_ctx->out_fds[i] = fds[1]; + if (!thread_mode) + close(fds[0]); + } + + /* Now we have all the fds, fork the senders */ + for (i = 0; i < num_fds; i++) { + snd_ctx->ready_out = ready_out; + snd_ctx->wakefd = wakefd; + snd_ctx->num_fds = num_fds; + + pth[num_fds+i] = create_worker(snd_ctx, (void *)sender); + } + + /* Close the fds we have left */ + if (!thread_mode) + for (i = 0; i < num_fds; i++) + close(snd_ctx->out_fds[i]); + + /* Return number of children to reap */ + return num_fds * 2; +} + +static const struct option options[] = { + OPT_BOOLEAN('p', "pipe", &use_pipes, + "Use pipe() instead of socketpair()"), + OPT_BOOLEAN('t', "thread", &thread_mode, + "Be multi thread instead of multi process"), + OPT_INTEGER('g', "group", &num_groups, + "Specify number of groups"), + OPT_INTEGER('l', "loop", &loops, + "Specify number of loops"), + OPT_END() +}; + +static const char * const bench_sched_message_usage[] = { + "perf bench sched messaging <options>", + NULL +}; + +int bench_sched_messaging(int argc, const char **argv, + const char *prefix __used) +{ + unsigned int i, total_children; + struct timeval start, stop, diff; + unsigned int num_fds = 20; + int readyfds[2], wakefds[2]; + char dummy; + pthread_t *pth_tab; + + argc = parse_options(argc, argv, options, + bench_sched_message_usage, 0); + + pth_tab = malloc(num_fds * 2 * num_groups * sizeof(pthread_t)); + if (!pth_tab) + barf("main:malloc()"); + + fdpair(readyfds); + fdpair(wakefds); + + total_children = 0; + for (i = 0; i < num_groups; i++) + total_children += group(pth_tab+total_children, num_fds, + readyfds[1], wakefds[0]); + + /* Wait for everyone to be ready */ + for (i = 0; i < total_children; i++) + if (read(readyfds[0], &dummy, 1) != 1) + barf("Reading for readyfds"); + + gettimeofday(&start, NULL); + + /* Kick them off */ + if (write(wakefds[1], &dummy, 1) != 1) + barf("Writing to start them"); + + /* Reap them all */ + for (i = 0; i < total_children; i++) + reap_worker(pth_tab[i]); + + gettimeofday(&stop, NULL); + + timersub(&stop, &start, &diff); + + switch (bench_format) { + case BENCH_FORMAT_DEFAULT: + printf("# %d sender and receiver %s per group\n", + num_fds, thread_mode ? "threads" : "processes"); + printf("# %d groups == %d %s run\n\n", + num_groups, num_groups * 2 * num_fds, + thread_mode ? "threads" : "processes"); + printf(" %14s: %lu.%03lu [sec]\n", "Total time", + diff.tv_sec, diff.tv_usec/1000); + break; + case BENCH_FORMAT_SIMPLE: + printf("%lu.%03lu\n", diff.tv_sec, diff.tv_usec/1000); + break; + default: + /* reaching here is something disaster */ + fprintf(stderr, "Unknown format:%d\n", bench_format); + exit(1); + break; + } + + return 0; +} diff --git a/tools/perf/bench/sched-pipe.c b/tools/perf/bench/sched-pipe.c new file mode 100644 index 000000000000..238185f97977 --- /dev/null +++ b/tools/perf/bench/sched-pipe.c @@ -0,0 +1,124 @@ +/* + * + * builtin-bench-pipe.c + * + * pipe: Benchmark for pipe() + * + * Based on pipe-test-1m.c by Ingo Molnar <mingo@redhat.com> + * http://people.redhat.com/mingo/cfs-scheduler/tools/pipe-test-1m.c + * Ported to perf by Hitoshi Mitake <mitake@dcl.info.waseda.ac.jp> + * + */ + +#include "../perf.h" +#include "../util/util.h" +#include "../util/parse-options.h" +#include "../builtin.h" +#include "bench.h" + +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <signal.h> +#include <sys/wait.h> +#include <linux/unistd.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <sys/time.h> +#include <sys/types.h> + +#define LOOPS_DEFAULT 1000000 +static int loops = LOOPS_DEFAULT; + +static const struct option options[] = { + OPT_INTEGER('l', "loop", &loops, + "Specify number of loops"), + OPT_END() +}; + +static const char * const bench_sched_pipe_usage[] = { + "perf bench sched pipe <options>", + NULL +}; + +int bench_sched_pipe(int argc, const char **argv, + const char *prefix __used) +{ + int pipe_1[2], pipe_2[2]; + int m = 0, i; + struct timeval start, stop, diff; + unsigned long long result_usec = 0; + + /* + * why does "ret" exist? + * discarding returned value of read(), write() + * causes error in building environment for perf + */ + int ret, wait_stat; + pid_t pid, retpid; + + argc = parse_options(argc, argv, options, + bench_sched_pipe_usage, 0); + + assert(!pipe(pipe_1)); + assert(!pipe(pipe_2)); + + pid = fork(); + assert(pid >= 0); + + gettimeofday(&start, NULL); + + if (!pid) { + for (i = 0; i < loops; i++) { + ret = read(pipe_1[0], &m, sizeof(int)); + ret = write(pipe_2[1], &m, sizeof(int)); + } + } else { + for (i = 0; i < loops; i++) { + ret = write(pipe_1[1], &m, sizeof(int)); + ret = read(pipe_2[0], &m, sizeof(int)); + } + } + + gettimeofday(&stop, NULL); + timersub(&stop, &start, &diff); + + if (pid) { + retpid = waitpid(pid, &wait_stat, 0); + assert((retpid == pid) && WIFEXITED(wait_stat)); + return 0; + } + + switch (bench_format) { + case BENCH_FORMAT_DEFAULT: + printf("# Extecuted %d pipe operations between two tasks\n\n", + loops); + + result_usec = diff.tv_sec * 1000000; + result_usec += diff.tv_usec; + + printf(" %14s: %lu.%03lu [sec]\n\n", "Total time", + diff.tv_sec, diff.tv_usec/1000); + + printf(" %14lf usecs/op\n", + (double)result_usec / (double)loops); + printf(" %14d ops/sec\n", + (int)((double)loops / + ((double)result_usec / (double)1000000))); + break; + + case BENCH_FORMAT_SIMPLE: + printf("%lu.%03lu\n", + diff.tv_sec, diff.tv_usec / 1000); + break; + + default: + /* reaching here is something disaster */ + fprintf(stderr, "Unknown format:%d\n", bench_format); + exit(1); + break; + } + + return 0; +} diff --git a/tools/perf/builtin-annotate.c b/tools/perf/builtin-annotate.c index 56ba71658d70..77d50a6d6802 100644 --- a/tools/perf/builtin-annotate.c +++ b/tools/perf/builtin-annotate.c @@ -37,12 +37,45 @@ static int print_line; static unsigned long page_size; static unsigned long mmap_window = 32; +struct sym_hist { + u64 sum; + u64 ip[0]; +}; + struct sym_ext { struct rb_node node; double percent; char *path; }; +struct sym_priv { + struct sym_hist *hist; + struct sym_ext *ext; +}; + +static const char *sym_hist_filter; + +static int symbol_filter(struct map *map __used, struct symbol *sym) +{ + if (sym_hist_filter == NULL || + strcmp(sym->name, sym_hist_filter) == 0) { + struct sym_priv *priv = symbol__priv(sym); + const int size = (sizeof(*priv->hist) + + (sym->end - sym->start) * sizeof(u64)); + + priv->hist = malloc(size); + if (priv->hist) + memset(priv->hist, 0, size); + return 0; + } + /* + * FIXME: We should really filter it out, as we don't want to go thru symbols + * we're not interested, and if a DSO ends up with no symbols, delete it too, + * but right now the kernel loading routines in symbol.c bail out if no symbols + * are found, fix it later. + */ + return 0; +} /* * collect histogram counts @@ -51,28 +84,38 @@ static void hist_hit(struct hist_entry *he, u64 ip) { unsigned int sym_size, offset; struct symbol *sym = he->sym; + struct sym_priv *priv; + struct sym_hist *h; he->count++; - if (!sym || !sym->hist) + if (!sym || !he->map) + return; + + priv = symbol__priv(sym); + if (!priv->hist) return; sym_size = sym->end - sym->start; - ip = he->map->map_ip(he->map, ip); offset = ip - sym->start; + if (verbose) + fprintf(stderr, "%s: ip=%Lx\n", __func__, + he->map->unmap_ip(he->map, ip)); + if (offset >= sym_size) return; - sym->hist_sum++; - sym->hist[offset]++; + h = priv->hist; + h->sum++; + h->ip[offset]++; if (verbose >= 3) printf("%p %s: count++ [ip: %p, %08Lx] => %Ld\n", (void *)(unsigned long)he->sym->start, he->sym->name, (void *)(unsigned long)ip, ip - he->sym->start, - sym->hist[offset]); + h->ip[offset]); } static int hist_entry__add(struct thread *thread, struct map *map, @@ -83,8 +126,7 @@ static int hist_entry__add(struct thread *thread, struct map *map, count, level, &hit); if (he == NULL) return -ENOMEM; - if (hit) - hist_hit(he, ip); + hist_hit(he, ip); return 0; } @@ -123,7 +165,7 @@ process_sample_event(event_t *event, unsigned long offset, unsigned long head) if (map != NULL) { got_map: ip = map->map_ip(map, ip); - sym = map->dso->find_symbol(map->dso, ip); + sym = map__find_symbol(map, ip, symbol_filter); } else { /* * If this is outside of all known maps, @@ -260,14 +302,15 @@ process_event(event_t *event, unsigned long offset, unsigned long head) return 0; } -static int -parse_line(FILE *file, struct symbol *sym, u64 len) +static int parse_line(FILE *file, struct hist_entry *he, u64 len) { + struct symbol *sym = he->sym; char *line = NULL, *tmp, *tmp2; static const char *prev_line; static const char *prev_color; unsigned int offset; size_t line_len; + u64 start; s64 line_ip; int ret; char *c; @@ -304,22 +347,26 @@ parse_line(FILE *file, struct symbol *sym, u64 len) line_ip = -1; } + start = he->map->unmap_ip(he->map, sym->start); + if (line_ip != -1) { const char *path = NULL; unsigned int hits = 0; double percent = 0.0; const char *color; - struct sym_ext *sym_ext = sym->priv; + struct sym_priv *priv = symbol__priv(sym); + struct sym_ext *sym_ext = priv->ext; + struct sym_hist *h = priv->hist; - offset = line_ip - sym->start; + offset = line_ip - start; if (offset < len) - hits = sym->hist[offset]; + hits = h->ip[offset]; if (offset < len && sym_ext) { path = sym_ext[offset].path; percent = sym_ext[offset].percent; - } else if (sym->hist_sum) - percent = 100.0 * hits / sym->hist_sum; + } else if (h->sum) + percent = 100.0 * hits / h->sum; color = get_percent_color(percent); @@ -372,9 +419,10 @@ static void insert_source_line(struct sym_ext *sym_ext) rb_insert_color(&sym_ext->node, &root_sym_ext); } -static void free_source_line(struct symbol *sym, int len) +static void free_source_line(struct hist_entry *he, int len) { - struct sym_ext *sym_ext = sym->priv; + struct sym_priv *priv = symbol__priv(he->sym); + struct sym_ext *sym_ext = priv->ext; int i; if (!sym_ext) @@ -384,26 +432,30 @@ static void free_source_line(struct symbol *sym, int len) free(sym_ext[i].path); free(sym_ext); - sym->priv = NULL; + priv->ext = NULL; root_sym_ext = RB_ROOT; } /* Get the filename:line for the colored entries */ static void -get_source_line(struct symbol *sym, int len, const char *filename) +get_source_line(struct hist_entry *he, int len, const char *filename) { + struct symbol *sym = he->sym; + u64 start; int i; char cmd[PATH_MAX * 2]; struct sym_ext *sym_ext; + struct sym_priv *priv = symbol__priv(sym); + struct sym_hist *h = priv->hist; - if (!sym->hist_sum) + if (!h->sum) return; - sym->priv = calloc(len, sizeof(struct sym_ext)); - if (!sym->priv) + sym_ext = priv->ext = calloc(len, sizeof(struct sym_ext)); + if (!priv->ext) return; - sym_ext = sym->priv; + start = he->map->unmap_ip(he->map, sym->start); for (i = 0; i < len; i++) { char *path = NULL; @@ -411,11 +463,11 @@ get_source_line(struct symbol *sym, int len, const char *filename) u64 offset; FILE *fp; - sym_ext[i].percent = 100.0 * sym->hist[i] / sym->hist_sum; + sym_ext[i].percent = 100.0 * h->ip[i] / h->sum; if (sym_ext[i].percent <= 0.5) continue; - offset = sym->start + i; + offset = start + i; sprintf(cmd, "addr2line -e %s %016llx", filename, offset); fp = popen(cmd, "r"); if (!fp) @@ -465,8 +517,11 @@ static void print_summary(const char *filename) } } -static void annotate_sym(struct dso *dso, struct symbol *sym) +static void annotate_sym(struct hist_entry *he) { + struct map *map = he->map; + struct dso *dso = map->dso; + struct symbol *sym = he->sym; const char *filename = dso->long_name, *d_filename; u64 len; char command[PATH_MAX*2]; @@ -475,6 +530,12 @@ static void annotate_sym(struct dso *dso, struct symbol *sym) if (!filename) return; + if (verbose) + fprintf(stderr, "%s: filename=%s, sym=%s, start=%Lx, end=%Lx\n", + __func__, filename, sym->name, + map->unmap_ip(map, sym->start), + map->unmap_ip(map, sym->end)); + if (full_paths) d_filename = filename; else @@ -483,7 +544,7 @@ static void annotate_sym(struct dso *dso, struct symbol *sym) len = sym->end - sym->start; if (print_line) { - get_source_line(sym, len, filename); + get_source_line(he, len, filename); print_summary(filename); } @@ -496,7 +557,8 @@ static void annotate_sym(struct dso *dso, struct symbol *sym) dso, dso->long_name, sym, sym->name); sprintf(command, "objdump --start-address=0x%016Lx --stop-address=0x%016Lx -dS %s|grep -v %s", - sym->start, sym->end, filename, filename); + map->unmap_ip(map, sym->start), map->unmap_ip(map, sym->end), + filename, filename); if (verbose >= 3) printf("doing: %s\n", command); @@ -506,35 +568,38 @@ static void annotate_sym(struct dso *dso, struct symbol *sym) return; while (!feof(file)) { - if (parse_line(file, sym, len) < 0) + if (parse_line(file, he, len) < 0) break; } pclose(file); if (print_line) - free_source_line(sym, len); + free_source_line(he, len); } static void find_annotations(void) { struct rb_node *nd; - struct dso *dso; - int count = 0; - list_for_each_entry(dso, &dsos, node) { + for (nd = rb_first(&output_hists); nd; nd = rb_next(nd)) { + struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node); + struct sym_priv *priv; - for (nd = rb_first(&dso->syms); nd; nd = rb_next(nd)) { - struct symbol *sym = rb_entry(nd, struct symbol, rb_node); + if (he->sym == NULL) + continue; - if (sym->hist) { - annotate_sym(dso, sym); - count++; - } - } - } + priv = symbol__priv(he->sym); + if (priv->hist == NULL) + continue; - if (!count) - printf(" Error: symbol '%s' not present amongst the samples.\n", sym_hist_filter); + annotate_sym(he); + /* + * Since we have a hist_entry per IP for the same symbol, free + * he->sym->hist to signal we already processed this symbol. + */ + free(priv->hist); + priv->hist = NULL; + } } static int __cmd_annotate(void) @@ -571,7 +636,7 @@ static int __cmd_annotate(void) exit(0); } - if (load_kernel() < 0) { + if (load_kernel(symbol_filter) < 0) { perror("failed to load kernel symbols"); return EXIT_FAILURE; } @@ -703,7 +768,7 @@ static void setup_sorting(void) int cmd_annotate(int argc, const char **argv, const char *prefix __used) { - symbol__init(); + symbol__init(sizeof(struct sym_priv)); page_size = getpagesize(); @@ -722,9 +787,6 @@ int cmd_annotate(int argc, const char **argv, const char *prefix __used) sym_hist_filter = argv[0]; } - if (!sym_hist_filter) - usage_with_options(annotate_usage, options); - setup_pager(); if (field_sep && *field_sep == '.') { diff --git a/tools/perf/builtin-bench.c b/tools/perf/builtin-bench.c new file mode 100644 index 000000000000..90c39baae0de --- /dev/null +++ b/tools/perf/builtin-bench.c @@ -0,0 +1,183 @@ +/* + * + * builtin-bench.c + * + * General benchmarking subsystem provided by perf + * + * Copyright (C) 2009, Hitoshi Mitake <mitake@dcl.info.waseda.ac.jp> + * + */ + +/* + * + * Available subsystem list: + * sched ... scheduler and IPC mechanism + * + */ + +#include "perf.h" +#include "util/util.h" +#include "util/parse-options.h" +#include "builtin.h" +#include "bench/bench.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +struct bench_suite { + const char *name; + const char *summary; + int (*fn)(int, const char **, const char *); +}; + +static struct bench_suite sched_suites[] = { + { "messaging", + "Benchmark for scheduler and IPC mechanisms", + bench_sched_messaging }, + { "pipe", + "Flood of communication over pipe() between two processes", + bench_sched_pipe }, + { NULL, + NULL, + NULL } +}; + +struct bench_subsys { + const char *name; + const char *summary; + struct bench_suite *suites; +}; + +static struct bench_subsys subsystems[] = { + { "sched", + "scheduler and IPC mechanism", + sched_suites }, + { NULL, + NULL, + NULL } +}; + +static void dump_suites(int subsys_index) +{ + int i; + + printf("List of available suites for %s...\n\n", + subsystems[subsys_index].name); + + for (i = 0; subsystems[subsys_index].suites[i].name; i++) + printf("\t%s: %s\n", + subsystems[subsys_index].suites[i].name, + subsystems[subsys_index].suites[i].summary); + + printf("\n"); + return; +} + +static char *bench_format_str; +int bench_format = BENCH_FORMAT_DEFAULT; + +static const struct option bench_options[] = { + OPT_STRING('f', "format", &bench_format_str, "default", + "Specify format style"), + OPT_END() +}; + +static const char * const bench_usage[] = { + "perf bench [<common options>] <subsystem> <suite> [<options>]", + NULL +}; + +static void print_usage(void) +{ + int i; + + printf("Usage: \n"); + for (i = 0; bench_usage[i]; i++) + printf("\t%s\n", bench_usage[i]); + printf("\n"); + + printf("List of available subsystems...\n\n"); + + for (i = 0; subsystems[i].name; i++) + printf("\t%s: %s\n", + subsystems[i].name, subsystems[i].summary); + printf("\n"); +} + +static int bench_str2int(char *str) +{ + if (!str) + return BENCH_FORMAT_DEFAULT; + + if (!strcmp(str, BENCH_FORMAT_DEFAULT_STR)) + return BENCH_FORMAT_DEFAULT; + else if (!strcmp(str, BENCH_FORMAT_SIMPLE_STR)) + return BENCH_FORMAT_SIMPLE; + + return BENCH_FORMAT_UNKNOWN; +} + +int cmd_bench(int argc, const char **argv, const char *prefix __used) +{ + int i, j, status = 0; + + if (argc < 2) { + /* No subsystem specified. */ + print_usage(); + goto end; + } + + argc = parse_options(argc, argv, bench_options, bench_usage, + PARSE_OPT_STOP_AT_NON_OPTION); + + bench_format = bench_str2int(bench_format_str); + if (bench_format == BENCH_FORMAT_UNKNOWN) { + printf("Unknown format descriptor:%s\n", bench_format_str); + goto end; + } + + if (argc < 1) { + print_usage(); + goto end; + } + + for (i = 0; subsystems[i].name; i++) { + if (strcmp(subsystems[i].name, argv[0])) + continue; + + if (argc < 2) { + /* No suite specified. */ + dump_suites(i); + goto end; + } + + for (j = 0; subsystems[i].suites[j].name; j++) { + if (strcmp(subsystems[i].suites[j].name, argv[1])) + continue; + + if (bench_format == BENCH_FORMAT_DEFAULT) + printf("# Running %s/%s benchmark...\n", + subsystems[i].name, + subsystems[i].suites[j].name); + status = subsystems[i].suites[j].fn(argc - 1, + argv + 1, prefix); + goto end; + } + + if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) { + dump_suites(i); + goto end; + } + + printf("Unknown suite:%s for %s\n", argv[1], argv[0]); + status = 1; + goto end; + } + + printf("Unknown subsystem:%s\n", argv[0]); + status = 1; + +end: + return status; +} diff --git a/tools/perf/builtin-buildid-list.c b/tools/perf/builtin-buildid-list.c new file mode 100644 index 000000000000..7dee9d19ab7a --- /dev/null +++ b/tools/perf/builtin-buildid-list.c @@ -0,0 +1,116 @@ +/* + * builtin-buildid-list.c + * + * Builtin buildid-list command: list buildids in perf.data + * + * Copyright (C) 2009, Red Hat Inc. + * Copyright (C) 2009, Arnaldo Carvalho de Melo <acme@redhat.com> + */ +#include "builtin.h" +#include "perf.h" +#include "util/cache.h" +#include "util/data_map.h" +#include "util/debug.h" +#include "util/header.h" +#include "util/parse-options.h" +#include "util/symbol.h" + +static char const *input_name = "perf.data"; +static int force; + +static const char *const buildid_list_usage[] = { + "perf report [<options>]", + NULL +}; + +static const struct option options[] = { + OPT_STRING('i', "input", &input_name, "file", + "input file name"), + OPT_BOOLEAN('f', "force", &force, "don't complain, do it"), + OPT_BOOLEAN('v', "verbose", &verbose, + "be more verbose"), + OPT_END() +}; + +static int perf_file_section__process_buildids(struct perf_file_section *self, + int feat, int fd) +{ + if (feat != HEADER_BUILD_ID) + return 0; + + if (lseek(fd, self->offset, SEEK_SET) < 0) { + pr_warning("Failed to lseek to %Ld offset for buildids!\n", + self->offset); + return -1; + } + + if (perf_header__read_build_ids(fd, self->offset, self->size)) { + pr_warning("Failed to read buildids!\n"); + return -1; + } + + return 0; +} + +static int __cmd_buildid_list(void) +{ + int err = -1; + struct perf_header *header; + struct perf_file_header f_header; + struct stat input_stat; + int input = open(input_name, O_RDONLY); + + if (input < 0) { + pr_err("failed to open file: %s", input_name); + if (!strcmp(input_name, "perf.data")) + pr_err(" (try 'perf record' first)"); + pr_err("\n"); + goto out; + } + + err = fstat(input, &input_stat); + if (err < 0) { + perror("failed to stat file"); + goto out_close; + } + + if (!force && input_stat.st_uid && (input_stat.st_uid != geteuid())) { + pr_err("file %s not owned by current user or root\n", + input_name); + goto out_close; + } + + if (!input_stat.st_size) { + pr_info("zero-sized file, nothing to do!\n"); + goto out_close; + } + + err = -1; + header = perf_header__new(); + if (header == NULL) + goto out_close; + + if (perf_file_header__read(&f_header, header, input) < 0) { + pr_warning("incompatible file format"); + goto out_close; + } + + err = perf_header__process_sections(header, input, + perf_file_section__process_buildids); + + if (err < 0) + goto out_close; + + dsos__fprintf_buildid(stdout); +out_close: + close(input); +out: + return err; +} + +int cmd_buildid_list(int argc, const char **argv, const char *prefix __used) +{ + argc = parse_options(argc, argv, options, buildid_list_usage, 0); + setup_pager(); + return __cmd_buildid_list(); +} diff --git a/tools/perf/builtin-help.c b/tools/perf/builtin-help.c index 4fb8734a796e..768f9c826312 100644 --- a/tools/perf/builtin-help.c +++ b/tools/perf/builtin-help.c @@ -61,8 +61,7 @@ static const char *get_man_viewer_info(const char *name) { struct man_viewer_info_list *viewer; - for (viewer = man_viewer_info_list; viewer; viewer = viewer->next) - { + for (viewer = man_viewer_info_list; viewer; viewer = viewer->next) { if (!strcasecmp(name, viewer->name)) return viewer->info; } @@ -115,7 +114,7 @@ static int check_emacsclient_version(void) return 0; } -static void exec_woman_emacs(const char* path, const char *page) +static void exec_woman_emacs(const char *path, const char *page) { if (!check_emacsclient_version()) { /* This works only with emacsclient version >= 22. */ @@ -129,7 +128,7 @@ static void exec_woman_emacs(const char* path, const char *page) } } -static void exec_man_konqueror(const char* path, const char *page) +static void exec_man_konqueror(const char *path, const char *page) { const char *display = getenv("DISPLAY"); if (display && *display) { @@ -157,7 +156,7 @@ static void exec_man_konqueror(const char* path, const char *page) } } -static void exec_man_man(const char* path, const char *page) +static void exec_man_man(const char *path, const char *page) { if (!path) path = "man"; @@ -364,9 +363,8 @@ static void show_man_page(const char *perf_cmd) setup_man_path(); for (viewer = man_viewer_list; viewer; viewer = viewer->next) - { exec_viewer(viewer->name, page); /* will return when unable */ - } + if (fallback) exec_viewer(fallback, page); exec_viewer("man", page); diff --git a/tools/perf/builtin-kmem.c b/tools/perf/builtin-kmem.c new file mode 100644 index 000000000000..f315b052f819 --- /dev/null +++ b/tools/perf/builtin-kmem.c @@ -0,0 +1,578 @@ +#include "builtin.h" +#include "perf.h" + +#include "util/util.h" +#include "util/cache.h" +#include "util/symbol.h" +#include "util/thread.h" +#include "util/header.h" + +#include "util/parse-options.h" +#include "util/trace-event.h" + +#include "util/debug.h" +#include "util/data_map.h" + +#include <linux/rbtree.h> + +struct alloc_stat; +typedef int (*sort_fn_t)(struct alloc_stat *, struct alloc_stat *); + +static char const *input_name = "perf.data"; + +static struct perf_header *header; +static u64 sample_type; + +static int alloc_flag; +static int caller_flag; + +sort_fn_t alloc_sort_fn; +sort_fn_t caller_sort_fn; + +static int alloc_lines = -1; +static int caller_lines = -1; + +static char *cwd; +static int cwdlen; + +struct alloc_stat { + union { + struct { + char *name; + u64 call_site; + }; + u64 ptr; + }; + u64 bytes_req; + u64 bytes_alloc; + u32 hit; + + struct rb_node node; +}; + +static struct rb_root root_alloc_stat; +static struct rb_root root_alloc_sorted; +static struct rb_root root_caller_stat; +static struct rb_root root_caller_sorted; + +static unsigned long total_requested, total_allocated; + +struct raw_event_sample { + u32 size; + char data[0]; +}; + +static int +process_comm_event(event_t *event, unsigned long offset, unsigned long head) +{ + struct thread *thread = threads__findnew(event->comm.pid); + + dump_printf("%p [%p]: PERF_RECORD_COMM: %s:%d\n", + (void *)(offset + head), + (void *)(long)(event->header.size), + event->comm.comm, event->comm.pid); + + if (thread == NULL || + thread__set_comm(thread, event->comm.comm)) { + dump_printf("problem processing PERF_RECORD_COMM, skipping event.\n"); + return -1; + } + + return 0; +} + +static void insert_alloc_stat(unsigned long ptr, + int bytes_req, int bytes_alloc) +{ + struct rb_node **node = &root_alloc_stat.rb_node; + struct rb_node *parent = NULL; + struct alloc_stat *data = NULL; + + if (!alloc_flag) + return; + + while (*node) { + parent = *node; + data = rb_entry(*node, struct alloc_stat, node); + + if (ptr > data->ptr) + node = &(*node)->rb_right; + else if (ptr < data->ptr) + node = &(*node)->rb_left; + else + break; + } + + if (data && data->ptr == ptr) { + data->hit++; + data->bytes_req += bytes_req; + data->bytes_alloc += bytes_req; + } else { + data = malloc(sizeof(*data)); + data->ptr = ptr; + data->hit = 1; + data->bytes_req = bytes_req; + data->bytes_alloc = bytes_alloc; + + rb_link_node(&data->node, parent, node); + rb_insert_color(&data->node, &root_alloc_stat); + } +} + +static void insert_caller_stat(unsigned long call_site, + int bytes_req, int bytes_alloc) +{ + struct rb_node **node = &root_caller_stat.rb_node; + struct rb_node *parent = NULL; + struct alloc_stat *data = NULL; + + if (!caller_flag) + return; + + while (*node) { + parent = *node; + data = rb_entry(*node, struct alloc_stat, node); + + if (call_site > data->call_site) + node = &(*node)->rb_right; + else if (call_site < data->call_site) + node = &(*node)->rb_left; + else + break; + } + + if (data && data->call_site == call_site) { + data->hit++; + data->bytes_req += bytes_req; + data->bytes_alloc += bytes_req; + } else { + data = malloc(sizeof(*data)); + data->call_site = call_site; + data->hit = 1; + data->bytes_req = bytes_req; + data->bytes_alloc = bytes_alloc; + + rb_link_node(&data->node, parent, node); + rb_insert_color(&data->node, &root_caller_stat); + } +} + +static void process_alloc_event(struct raw_event_sample *raw, + struct event *event, + int cpu __used, + u64 timestamp __used, + struct thread *thread __used, + int node __used) +{ + unsigned long call_site; + unsigned long ptr; + int bytes_req; + int bytes_alloc; + + ptr = raw_field_value(event, "ptr", raw->data); + call_site = raw_field_value(event, "call_site", raw->data); + bytes_req = raw_field_value(event, "bytes_req", raw->data); + bytes_alloc = raw_field_value(event, "bytes_alloc", raw->data); + + insert_alloc_stat(ptr, bytes_req, bytes_alloc); + insert_caller_stat(call_site, bytes_req, bytes_alloc); + + total_requested += bytes_req; + total_allocated += bytes_alloc; +} + +static void process_free_event(struct raw_event_sample *raw __used, + struct event *event __used, + int cpu __used, + u64 timestamp __used, + struct thread *thread __used) +{ +} + +static void +process_raw_event(event_t *raw_event __used, void *more_data, + int cpu, u64 timestamp, struct thread *thread) +{ + struct raw_event_sample *raw = more_data; + struct event *event; + int type; + + type = trace_parse_common_type(raw->data); + event = trace_find_event(type); + + if (!strcmp(event->name, "kmalloc") || + !strcmp(event->name, "kmem_cache_alloc")) { + process_alloc_event(raw, event, cpu, timestamp, thread, 0); + return; + } + + if (!strcmp(event->name, "kmalloc_node") || + !strcmp(event->name, "kmem_cache_alloc_node")) { + process_alloc_event(raw, event, cpu, timestamp, thread, 1); + return; + } + + if (!strcmp(event->name, "kfree") || + !strcmp(event->name, "kmem_cache_free")) { + process_free_event(raw, event, cpu, timestamp, thread); + return; + } +} + +static int +process_sample_event(event_t *event, unsigned long offset, unsigned long head) +{ + u64 ip = event->ip.ip; + u64 timestamp = -1; + u32 cpu = -1; + u64 period = 1; + void *more_data = event->ip.__more_data; + struct thread *thread = threads__findnew(event->ip.pid); + + if (sample_type & PERF_SAMPLE_TIME) { + timestamp = *(u64 *)more_data; + more_data += sizeof(u64); + } + + if (sample_type & PERF_SAMPLE_CPU) { + cpu = *(u32 *)more_data; + more_data += sizeof(u32); + more_data += sizeof(u32); /* reserved */ + } + + if (sample_type & PERF_SAMPLE_PERIOD) { + period = *(u64 *)more_data; + more_data += sizeof(u64); + } + + dump_printf("%p [%p]: PERF_RECORD_SAMPLE (IP, %d): %d/%d: %p period: %Ld\n", + (void *)(offset + head), + (void *)(long)(event->header.size), + event->header.misc, + event->ip.pid, event->ip.tid, + (void *)(long)ip, + (long long)period); + + if (thread == NULL) { + pr_debug("problem processing %d event, skipping it.\n", + event->header.type); + return -1; + } + + dump_printf(" ... thread: %s:%d\n", thread->comm, thread->pid); + + process_raw_event(event, more_data, cpu, timestamp, thread); + + return 0; +} + +static int sample_type_check(u64 type) +{ + sample_type = type; + + if (!(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_file_handler file_handler = { + .process_sample_event = process_sample_event, + .process_comm_event = process_comm_event, + .sample_type_check = sample_type_check, +}; + +static int read_events(void) +{ + register_idle_thread(); + register_perf_file_handler(&file_handler); + + return mmap_dispatch_perf_file(&header, input_name, 0, 0, + &cwdlen, &cwd); +} + +static double fragmentation(unsigned long n_req, unsigned long n_alloc) +{ + if (n_alloc == 0) + return 0.0; + else + return 100.0 - (100.0 * n_req / n_alloc); +} + +static void __print_result(struct rb_root *root, int n_lines, int is_caller) +{ + struct rb_node *next; + + printf("\n ------------------------------------------------------------------------------\n"); + if (is_caller) + printf(" Callsite |"); + else + printf(" Alloc Ptr |"); + printf(" Total_alloc/Per | Total_req/Per | Hit | Fragmentation\n"); + printf(" ------------------------------------------------------------------------------\n"); + + next = rb_first(root); + + while (next && n_lines--) { + struct alloc_stat *data; + + data = rb_entry(next, struct alloc_stat, node); + + printf(" %-16p | %8llu/%-6lu | %8llu/%-6lu | %6lu | %8.3f%%\n", + is_caller ? (void *)(unsigned long)data->call_site : + (void *)(unsigned long)data->ptr, + (unsigned long long)data->bytes_alloc, + (unsigned long)data->bytes_alloc / data->hit, + (unsigned long long)data->bytes_req, + (unsigned long)data->bytes_req / data->hit, + (unsigned long)data->hit, + fragmentation(data->bytes_req, data->bytes_alloc)); + + next = rb_next(next); + } + + if (n_lines == -1) + printf(" ... | ... | ... | ... | ... \n"); + + printf(" ------------------------------------------------------------------------------\n"); +} + +static void print_summary(void) +{ + printf("\nSUMMARY\n=======\n"); + printf("Total bytes requested: %lu\n", total_requested); + printf("Total bytes allocated: %lu\n", total_allocated); + printf("Total bytes wasted on internal fragmentation: %lu\n", + total_allocated - total_requested); + printf("Internal fragmentation: %f%%\n", + fragmentation(total_requested, total_allocated)); +} + +static void print_result(void) +{ + if (caller_flag) + __print_result(&root_caller_sorted, caller_lines, 1); + if (alloc_flag) + __print_result(&root_alloc_sorted, alloc_lines, 0); + print_summary(); +} + +static void sort_insert(struct rb_root *root, struct alloc_stat *data, + sort_fn_t sort_fn) +{ + struct rb_node **new = &(root->rb_node); + struct rb_node *parent = NULL; + + while (*new) { + struct alloc_stat *this; + int cmp; + + this = rb_entry(*new, struct alloc_stat, node); + parent = *new; + + cmp = sort_fn(data, this); + + if (cmp > 0) + new = &((*new)->rb_left); + else + new = &((*new)->rb_right); + } + + rb_link_node(&data->node, parent, new); + rb_insert_color(&data->node, root); +} + +static void __sort_result(struct rb_root *root, struct rb_root *root_sorted, + sort_fn_t sort_fn) +{ + struct rb_node *node; + struct alloc_stat *data; + + for (;;) { + node = rb_first(root); + if (!node) + break; + + rb_erase(node, root); + data = rb_entry(node, struct alloc_stat, node); + sort_insert(root_sorted, data, sort_fn); + } +} + +static void sort_result(void) +{ + __sort_result(&root_alloc_stat, &root_alloc_sorted, alloc_sort_fn); + __sort_result(&root_caller_stat, &root_caller_sorted, caller_sort_fn); +} + +static int __cmd_kmem(void) +{ + setup_pager(); + read_events(); + sort_result(); + print_result(); + + return 0; +} + +static const char * const kmem_usage[] = { + "perf kmem [<options>] {record}", + NULL +}; + + +static int ptr_cmp(struct alloc_stat *l, struct alloc_stat *r) +{ + if (l->ptr < r->ptr) + return -1; + else if (l->ptr > r->ptr) + return 1; + return 0; +} + +static int callsite_cmp(struct alloc_stat *l, struct alloc_stat *r) +{ + if (l->call_site < r->call_site) + return -1; + else if (l->call_site > r->call_site) + return 1; + return 0; +} + +static int bytes_cmp(struct alloc_stat *l, struct alloc_stat *r) +{ + if (l->bytes_alloc < r->bytes_alloc) + return -1; + else if (l->bytes_alloc > r->bytes_alloc) + return 1; + return 0; +} + +static int parse_sort_opt(const struct option *opt __used, + const char *arg, int unset __used) +{ + sort_fn_t sort_fn; + + if (!arg) + return -1; + + if (strcmp(arg, "ptr") == 0) + sort_fn = ptr_cmp; + else if (strcmp(arg, "call_site") == 0) + sort_fn = callsite_cmp; + else if (strcmp(arg, "bytes") == 0) + sort_fn = bytes_cmp; + else + return -1; + + if (caller_flag > alloc_flag) + caller_sort_fn = sort_fn; + else + alloc_sort_fn = sort_fn; + + return 0; +} + +static int parse_stat_opt(const struct option *opt __used, + const char *arg, int unset __used) +{ + if (!arg) + return -1; + + if (strcmp(arg, "alloc") == 0) + alloc_flag = (caller_flag + 1); + else if (strcmp(arg, "caller") == 0) + caller_flag = (alloc_flag + 1); + else + return -1; + return 0; +} + +static int parse_line_opt(const struct option *opt __used, + const char *arg, int unset __used) +{ + int lines; + + if (!arg) + return -1; + + lines = strtoul(arg, NULL, 10); + + if (caller_flag > alloc_flag) + caller_lines = lines; + else + alloc_lines = lines; + + return 0; +} + +static const struct option kmem_options[] = { + OPT_STRING('i', "input", &input_name, "file", + "input file name"), + OPT_CALLBACK(0, "stat", NULL, "<alloc>|<caller>", + "stat selector, Pass 'alloc' or 'caller'.", + parse_stat_opt), + OPT_CALLBACK('s', "sort", NULL, "key", + "sort by key: ptr, call_site, hit, bytes", + parse_sort_opt), + OPT_CALLBACK('l', "line", NULL, "num", + "show n lins", + parse_line_opt), + OPT_END() +}; + +static const char *record_args[] = { + "record", + "-a", + "-R", + "-M", + "-f", + "-c", "1", + "-e", "kmem:kmalloc", + "-e", "kmem:kmalloc_node", + "-e", "kmem:kfree", + "-e", "kmem:kmem_cache_alloc", + "-e", "kmem:kmem_cache_alloc_node", + "-e", "kmem:kmem_cache_free", +}; + +static int __cmd_record(int argc, const char **argv) +{ + unsigned int rec_argc, i, j; + const char **rec_argv; + + rec_argc = ARRAY_SIZE(record_args) + argc - 1; + rec_argv = calloc(rec_argc + 1, sizeof(char *)); + + for (i = 0; i < ARRAY_SIZE(record_args); i++) + rec_argv[i] = strdup(record_args[i]); + + for (j = 1; j < (unsigned int)argc; j++, i++) + rec_argv[i] = argv[j]; + + return cmd_record(i, rec_argv, NULL); +} + +int cmd_kmem(int argc, const char **argv, const char *prefix __used) +{ + symbol__init(0); + + argc = parse_options(argc, argv, kmem_options, kmem_usage, 0); + + if (argc && !strncmp(argv[0], "rec", 3)) + return __cmd_record(argc, argv); + else if (argc) + usage_with_options(kmem_usage, kmem_options); + + if (!alloc_sort_fn) + alloc_sort_fn = bytes_cmp; + if (!caller_sort_fn) + caller_sort_fn = bytes_cmp; + + return __cmd_kmem(); +} + diff --git a/tools/perf/builtin-probe.c b/tools/perf/builtin-probe.c new file mode 100644 index 000000000000..d78a3d945492 --- /dev/null +++ b/tools/perf/builtin-probe.c @@ -0,0 +1,435 @@ +/* + * builtin-probe.c + * + * Builtin probe command: Set up probe events by C expression + * + * Written by Masami Hiramatsu <mhiramat@redhat.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ +#define _GNU_SOURCE +#include <sys/utsname.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <errno.h> +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> + +#undef _GNU_SOURCE +#include "perf.h" +#include "builtin.h" +#include "util/util.h" +#include "util/event.h" +#include "util/debug.h" +#include "util/parse-options.h" +#include "util/parse-events.h" /* For debugfs_path */ +#include "util/probe-finder.h" + +/* Default vmlinux search paths */ +#define NR_SEARCH_PATH 3 +const char *default_search_path[NR_SEARCH_PATH] = { +"/lib/modules/%s/build/vmlinux", /* Custom build kernel */ +"/usr/lib/debug/lib/modules/%s/vmlinux", /* Red Hat debuginfo */ +"/boot/vmlinux-debug-%s", /* Ubuntu */ +}; + +#define MAX_PATH_LEN 256 +#define MAX_PROBES 128 +#define MAX_PROBE_ARGS 128 +#define PERFPROBE_GROUP "probe" + +/* Session management structure */ +static struct { + char *vmlinux; + char *release; + int need_dwarf; + int nr_probe; + struct probe_point probes[MAX_PROBES]; +} session; + +#define semantic_error(msg ...) die("Semantic error :" msg) + +/* Parse probe point. Return 1 if return probe */ +static void parse_probe_point(char *arg, struct probe_point *pp) +{ + char *ptr, *tmp; + char c, nc = 0; + /* + * <Syntax> + * perf probe SRC:LN + * perf probe FUNC[+OFFS|%return][@SRC] + */ + + ptr = strpbrk(arg, ":+@%"); + if (ptr) { + nc = *ptr; + *ptr++ = '\0'; + } + + /* Check arg is function or file and copy it */ + if (strchr(arg, '.')) /* File */ + pp->file = strdup(arg); + else /* Function */ + pp->function = strdup(arg); + DIE_IF(pp->file == NULL && pp->function == NULL); + + /* Parse other options */ + while (ptr) { + arg = ptr; + c = nc; + ptr = strpbrk(arg, ":+@%"); + if (ptr) { + nc = *ptr; + *ptr++ = '\0'; + } + switch (c) { + case ':': /* Line number */ + pp->line = strtoul(arg, &tmp, 0); + if (*tmp != '\0') + semantic_error("There is non-digit charactor" + " in line number."); + break; + case '+': /* Byte offset from a symbol */ + pp->offset = strtoul(arg, &tmp, 0); + if (*tmp != '\0') + semantic_error("There is non-digit charactor" + " in offset."); + break; + case '@': /* File name */ + if (pp->file) + semantic_error("SRC@SRC is not allowed."); + pp->file = strdup(arg); + DIE_IF(pp->file == NULL); + if (ptr) + semantic_error("@SRC must be the last " + "option."); + break; + case '%': /* Probe places */ + if (strcmp(arg, "return") == 0) { + pp->retprobe = 1; + } else /* Others not supported yet */ + semantic_error("%%%s is not supported.", arg); + break; + default: + DIE_IF("Program has a bug."); + break; + } + } + + /* Exclusion check */ + if (pp->line && pp->offset) + semantic_error("Offset can't be used with line number."); + if (!pp->line && pp->file && !pp->function) + semantic_error("File always requires line number."); + if (pp->offset && !pp->function) + semantic_error("Offset requires an entry function."); + if (pp->retprobe && !pp->function) + semantic_error("Return probe requires an entry function."); + if ((pp->offset || pp->line) && pp->retprobe) + semantic_error("Offset/Line can't be used with return probe."); + + pr_debug("symbol:%s file:%s line:%d offset:%d, return:%d\n", + pp->function, pp->file, pp->line, pp->offset, pp->retprobe); +} + +/* Parse an event definition. Note that any error must die. */ +static void parse_probe_event(const char *str) +{ + char *argv[MAX_PROBE_ARGS + 2]; /* Event + probe + args */ + int argc, i; + struct probe_point *pp = &session.probes[session.nr_probe]; + + pr_debug("probe-definition(%d): %s\n", session.nr_probe, str); + if (++session.nr_probe == MAX_PROBES) + semantic_error("Too many probes"); + + /* Separate arguments, similar to argv_split */ + argc = 0; + do { + /* Skip separators */ + while (isspace(*str)) + str++; + + /* Add an argument */ + if (*str != '\0') { + const char *s = str; + + /* Skip the argument */ + while (!isspace(*str) && *str != '\0') + str++; + + /* Duplicate the argument */ + argv[argc] = strndup(s, str - s); + if (argv[argc] == NULL) + die("strndup"); + if (++argc == MAX_PROBE_ARGS) + semantic_error("Too many arguments"); + pr_debug("argv[%d]=%s\n", argc, argv[argc - 1]); + } + } while (*str != '\0'); + if (!argc) + semantic_error("An empty argument."); + + /* Parse probe point */ + parse_probe_point(argv[0], pp); + free(argv[0]); + if (pp->file || pp->line) + session.need_dwarf = 1; + + /* Copy arguments */ + pp->nr_args = argc - 1; + if (pp->nr_args > 0) { + pp->args = (char **)malloc(sizeof(char *) * pp->nr_args); + if (!pp->args) + die("malloc"); + memcpy(pp->args, &argv[1], sizeof(char *) * pp->nr_args); + } + + /* Ensure return probe has no C argument */ + for (i = 0; i < pp->nr_args; i++) + if (is_c_varname(pp->args[i])) { + if (pp->retprobe) + semantic_error("You can't specify local" + " variable for kretprobe"); + session.need_dwarf = 1; + } + + pr_debug("%d arguments\n", pp->nr_args); +} + +static int opt_add_probe_event(const struct option *opt __used, + const char *str, int unset __used) +{ + if (str) + parse_probe_event(str); + return 0; +} + +#ifndef NO_LIBDWARF +static int open_default_vmlinux(void) +{ + struct utsname uts; + char fname[MAX_PATH_LEN]; + int fd, ret, i; + + ret = uname(&uts); + if (ret) { + pr_debug("uname() failed.\n"); + return -errno; + } + session.release = uts.release; + for (i = 0; i < NR_SEARCH_PATH; i++) { + ret = snprintf(fname, MAX_PATH_LEN, + default_search_path[i], session.release); + if (ret >= MAX_PATH_LEN || ret < 0) { + pr_debug("Filename(%d,%s) is too long.\n", i, + uts.release); + errno = E2BIG; + return -E2BIG; + } + pr_debug("try to open %s\n", fname); + fd = open(fname, O_RDONLY); + if (fd >= 0) + break; + } + return fd; +} +#endif + +static const char * const probe_usage[] = { + "perf probe [<options>] 'PROBEDEF' ['PROBEDEF' ...]", + "perf probe [<options>] --add 'PROBEDEF' [--add 'PROBEDEF' ...]", + NULL +}; + +static const struct option options[] = { + OPT_BOOLEAN('v', "verbose", &verbose, + "be more verbose (show parsed arguments, etc)"), +#ifndef NO_LIBDWARF + OPT_STRING('k', "vmlinux", &session.vmlinux, "file", + "vmlinux/module pathname"), +#endif + OPT_CALLBACK('a', "add", NULL, +#ifdef NO_LIBDWARF + "FUNC[+OFFS|%return] [ARG ...]", +#else + "FUNC[+OFFS|%return|:RLN][@SRC]|SRC:ALN [ARG ...]", +#endif + "probe point definition, where\n" + "\t\tGRP:\tGroup name (optional)\n" + "\t\tNAME:\tEvent name\n" + "\t\tFUNC:\tFunction name\n" + "\t\tOFFS:\tOffset from function entry (in byte)\n" + "\t\t%return:\tPut the probe at function return\n" +#ifdef NO_LIBDWARF + "\t\tARG:\tProbe argument (only \n" +#else + "\t\tSRC:\tSource code path\n" + "\t\tRLN:\tRelative line number from function entry.\n" + "\t\tALN:\tAbsolute line number in file.\n" + "\t\tARG:\tProbe argument (local variable name or\n" +#endif + "\t\t\tkprobe-tracer argument format is supported.)\n", + opt_add_probe_event), + OPT_END() +}; + +static int write_new_event(int fd, const char *buf) +{ + int ret; + + ret = write(fd, buf, strlen(buf)); + if (ret <= 0) + die("Failed to create event."); + else + printf("Added new event: %s\n", buf); + + return ret; +} + +#define MAX_CMDLEN 256 + +static int synthesize_probe_event(struct probe_point *pp) +{ + char *buf; + int i, len, ret; + pp->probes[0] = buf = (char *)calloc(MAX_CMDLEN, sizeof(char)); + if (!buf) + die("Failed to allocate memory by calloc."); + ret = snprintf(buf, MAX_CMDLEN, "%s+%d", pp->function, pp->offset); + if (ret <= 0 || ret >= MAX_CMDLEN) + goto error; + len = ret; + + for (i = 0; i < pp->nr_args; i++) { + ret = snprintf(&buf[len], MAX_CMDLEN - len, " %s", + pp->args[i]); + if (ret <= 0 || ret >= MAX_CMDLEN - len) + goto error; + len += ret; + } + pp->found = 1; + return pp->found; +error: + free(pp->probes[0]); + if (ret > 0) + ret = -E2BIG; + return ret; +} + +int cmd_probe(int argc, const char **argv, const char *prefix __used) +{ + int i, j, fd, ret; + struct probe_point *pp; + char buf[MAX_CMDLEN]; + + argc = parse_options(argc, argv, options, probe_usage, + PARSE_OPT_STOP_AT_NON_OPTION); + for (i = 0; i < argc; i++) + parse_probe_event(argv[i]); + + if (session.nr_probe == 0) + usage_with_options(probe_usage, options); + + if (session.need_dwarf) +#ifdef NO_LIBDWARF + semantic_error("Debuginfo-analysis is not supported"); +#else /* !NO_LIBDWARF */ + pr_info("Some probes require debuginfo.\n"); + + if (session.vmlinux) + fd = open(session.vmlinux, O_RDONLY); + else + fd = open_default_vmlinux(); + if (fd < 0) { + if (session.need_dwarf) + die("Could not open vmlinux/module file."); + + pr_warning("Could not open vmlinux/module file." + " Try to use symbols.\n"); + goto end_dwarf; + } + + /* Searching probe points */ + for (j = 0; j < session.nr_probe; j++) { + pp = &session.probes[j]; + if (pp->found) + continue; + + lseek(fd, SEEK_SET, 0); + ret = find_probepoint(fd, pp); + if (ret < 0) { + if (session.need_dwarf) + die("Could not analyze debuginfo."); + + pr_warning("An error occurred in debuginfo analysis. Try to use symbols.\n"); + break; + } + if (ret == 0) /* No error but failed to find probe point. */ + die("No probe point found."); + } + close(fd); + +end_dwarf: +#endif /* !NO_LIBDWARF */ + + /* Synthesize probes without dwarf */ + for (j = 0; j < session.nr_probe; j++) { + pp = &session.probes[j]; + if (pp->found) /* This probe is already found. */ + continue; + + ret = synthesize_probe_event(pp); + if (ret == -E2BIG) + semantic_error("probe point is too long."); + else if (ret < 0) + die("Failed to synthesize a probe point."); + } + + /* Settng up probe points */ + snprintf(buf, MAX_CMDLEN, "%s/../kprobe_events", debugfs_path); + fd = open(buf, O_WRONLY, O_APPEND); + if (fd < 0) { + if (errno == ENOENT) + die("kprobe_events file does not exist - please rebuild with CONFIG_KPROBE_TRACER."); + else + die("Could not open kprobe_events file: %s", + strerror(errno)); + } + for (j = 0; j < session.nr_probe; j++) { + pp = &session.probes[j]; + if (pp->found == 1) { + snprintf(buf, MAX_CMDLEN, "%c:%s/%s_%x %s\n", + pp->retprobe ? 'r' : 'p', PERFPROBE_GROUP, + pp->function, pp->offset, pp->probes[0]); + write_new_event(fd, buf); + } else + for (i = 0; i < pp->found; i++) { + snprintf(buf, MAX_CMDLEN, "%c:%s/%s_%x_%d %s\n", + pp->retprobe ? 'r' : 'p', + PERFPROBE_GROUP, + pp->function, pp->offset, i, + pp->probes[0]); + write_new_event(fd, buf); + } + } + close(fd); + return 0; +} + diff --git a/tools/perf/builtin-record.c b/tools/perf/builtin-record.c index 8b2c860c49a2..87f98fdb0513 100644 --- a/tools/perf/builtin-record.c +++ b/tools/perf/builtin-record.c @@ -17,13 +17,11 @@ #include "util/header.h" #include "util/event.h" #include "util/debug.h" +#include "util/symbol.h" #include <unistd.h> #include <sched.h> -#define ALIGN(x, a) __ALIGN_MASK(x, (typeof(x))(a)-1) -#define __ALIGN_MASK(x, mask) (((x)+(mask))&~(mask)) - static int fd[MAX_NR_CPUS][MAX_COUNTERS]; static long default_interval = 0; @@ -112,6 +110,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); + + write_output(buf, size); +} + +static int process_synthesized_event(event_t *event) +{ + write_event(event, event->header.size); + return 0; +} + static void mmap_read(struct mmap_data *md) { unsigned int head = mmap_read_head(md); @@ -160,14 +176,14 @@ static void mmap_read(struct mmap_data *md) size = md->mask + 1 - (old & md->mask); old += size; - write_output(buf, size); + write_event(buf, size); } buf = &data[old & md->mask]; size = head - old; old += size; - write_output(buf, size); + write_event(buf, size); md->prev = old; mmap_write_tail(md, old); @@ -194,168 +210,6 @@ static void sig_atexit(void) kill(getpid(), signr); } -static pid_t pid_synthesize_comm_event(pid_t pid, int full) -{ - struct comm_event comm_ev; - char filename[PATH_MAX]; - char bf[BUFSIZ]; - FILE *fp; - size_t size = 0; - DIR *tasks; - struct dirent dirent, *next; - pid_t tgid = 0; - - snprintf(filename, sizeof(filename), "/proc/%d/status", pid); - - fp = fopen(filename, "r"); - if (fp == NULL) { - /* - * We raced with a task exiting - just return: - */ - if (verbose) - fprintf(stderr, "couldn't open %s\n", filename); - return 0; - } - - memset(&comm_ev, 0, sizeof(comm_ev)); - while (!comm_ev.comm[0] || !comm_ev.pid) { - if (fgets(bf, sizeof(bf), fp) == NULL) - goto out_failure; - - if (memcmp(bf, "Name:", 5) == 0) { - char *name = bf + 5; - while (*name && isspace(*name)) - ++name; - size = strlen(name) - 1; - memcpy(comm_ev.comm, name, size++); - } else if (memcmp(bf, "Tgid:", 5) == 0) { - char *tgids = bf + 5; - while (*tgids && isspace(*tgids)) - ++tgids; - tgid = comm_ev.pid = atoi(tgids); - } - } - - comm_ev.header.type = PERF_RECORD_COMM; - size = ALIGN(size, sizeof(u64)); - comm_ev.header.size = sizeof(comm_ev) - (sizeof(comm_ev.comm) - size); - - if (!full) { - comm_ev.tid = pid; - - write_output(&comm_ev, comm_ev.header.size); - goto out_fclose; - } - - snprintf(filename, sizeof(filename), "/proc/%d/task", pid); - - tasks = opendir(filename); - while (!readdir_r(tasks, &dirent, &next) && next) { - char *end; - pid = strtol(dirent.d_name, &end, 10); - if (*end) - continue; - - comm_ev.tid = pid; - - write_output(&comm_ev, comm_ev.header.size); - } - closedir(tasks); - -out_fclose: - fclose(fp); - return tgid; - -out_failure: - fprintf(stderr, "couldn't get COMM and pgid, malformed %s\n", - filename); - exit(EXIT_FAILURE); -} - -static void pid_synthesize_mmap_samples(pid_t pid, pid_t tgid) -{ - char filename[PATH_MAX]; - FILE *fp; - - snprintf(filename, sizeof(filename), "/proc/%d/maps", pid); - - fp = fopen(filename, "r"); - if (fp == NULL) { - /* - * We raced with a task exiting - just return: - */ - if (verbose) - fprintf(stderr, "couldn't open %s\n", filename); - return; - } - while (1) { - char bf[BUFSIZ], *pbf = bf; - struct mmap_event mmap_ev = { - .header = { .type = PERF_RECORD_MMAP }, - }; - int n; - size_t size; - if (fgets(bf, sizeof(bf), fp) == NULL) - break; - - /* 00400000-0040c000 r-xp 00000000 fd:01 41038 /bin/cat */ - n = hex2u64(pbf, &mmap_ev.start); - if (n < 0) - continue; - pbf += n + 1; - n = hex2u64(pbf, &mmap_ev.len); - if (n < 0) - continue; - pbf += n + 3; - if (*pbf == 'x') { /* vm_exec */ - char *execname = strchr(bf, '/'); - - /* Catch VDSO */ - if (execname == NULL) - execname = strstr(bf, "[vdso]"); - - if (execname == NULL) - continue; - - size = strlen(execname); - execname[size - 1] = '\0'; /* Remove \n */ - memcpy(mmap_ev.filename, execname, size); - size = ALIGN(size, sizeof(u64)); - mmap_ev.len -= mmap_ev.start; - mmap_ev.header.size = (sizeof(mmap_ev) - - (sizeof(mmap_ev.filename) - size)); - mmap_ev.pid = tgid; - mmap_ev.tid = pid; - - write_output(&mmap_ev, mmap_ev.header.size); - } - } - - fclose(fp); -} - -static void synthesize_all(void) -{ - DIR *proc; - struct dirent dirent, *next; - - proc = opendir("/proc"); - - while (!readdir_r(proc, &dirent, &next) && next) { - char *end; - pid_t pid, tgid; - - pid = strtol(dirent.d_name, &end, 10); - if (*end) /* only interested in proper numerical dirents */ - continue; - - tgid = pid_synthesize_comm_event(pid, 1); - pid_synthesize_mmap_samples(pid, tgid); - } - - closedir(proc); -} - static int group_fd; static struct perf_header_attr *get_header_attr(struct perf_event_attr *a, int nr) @@ -366,7 +220,11 @@ static struct perf_header_attr *get_header_attr(struct perf_event_attr *a, int n h_attr = header->attr[nr]; } else { h_attr = perf_header_attr__new(a); - perf_header__add_attr(header, h_attr); + if (h_attr != NULL) + if (perf_header__add_attr(header, h_attr) < 0) { + perf_header_attr__delete(h_attr); + h_attr = NULL; + } } return h_attr; @@ -427,7 +285,7 @@ try_again: if (fd[nr_cpu][counter] < 0) { int err = errno; - if (err == EPERM) + if (err == EPERM || err == EACCES) die("Permission error - are you root?\n"); else if (err == ENODEV && profile_cpu != -1) die("No such device - did you specify an out-of-range profile CPU?\n"); @@ -454,6 +312,8 @@ try_again: } h_attr = get_header_attr(attr, counter); + if (h_attr == NULL) + die("nomem\n"); if (!file_new) { if (memcmp(&h_attr->attr, attr, sizeof(*attr))) { @@ -467,7 +327,10 @@ try_again: exit(-1); } - perf_header_attr__add_id(h_attr, read_data.id); + if (perf_header_attr__add_id(h_attr, read_data.id) < 0) { + pr_warning("Not enough memory to add id\n"); + exit(-1); + } assert(fd[nr_cpu][counter] >= 0); fcntl(fd[nr_cpu][counter], F_SETFL, O_NONBLOCK); @@ -528,7 +391,7 @@ static void atexit_header(void) { header->data_size += bytes_written; - perf_header__write(header, output); + perf_header__write(header, output, true); } static int __cmd_record(int argc, const char **argv) @@ -537,7 +400,7 @@ static int __cmd_record(int argc, const char **argv) struct stat st; pid_t pid = 0; int flags; - int ret; + int err; unsigned long waking = 0; page_size = sysconf(_SC_PAGE_SIZE); @@ -571,17 +434,24 @@ static int __cmd_record(int argc, const char **argv) exit(-1); } - if (!file_new) - header = perf_header__read(output); - else - header = perf_header__new(); + header = perf_header__new(); + if (header == NULL) { + pr_err("Not enough memory for reading perf file header\n"); + return -1; + } + + if (!file_new) { + err = perf_header__read(header, output); + if (err < 0) + return err; + } if (raw_samples) { - perf_header__set_trace_info(); + perf_header__set_feat(header, HEADER_TRACE_INFO); } else { for (i = 0; i < nr_counters; i++) { if (attrs[i].sample_type & PERF_SAMPLE_RAW) { - perf_header__set_trace_info(); + perf_header__set_feat(header, HEADER_TRACE_INFO); break; } } @@ -604,25 +474,36 @@ static int __cmd_record(int argc, const char **argv) } } - if (file_new) - perf_header__write(header, output); + if (file_new) { + err = perf_header__write(header, output, false); + if (err < 0) + return err; + } - if (!system_wide) { - pid_t tgid = pid_synthesize_comm_event(pid, 0); - pid_synthesize_mmap_samples(pid, tgid); - } else - synthesize_all(); + if (!system_wide) + event__synthesize_thread(pid, process_synthesized_event); + else + event__synthesize_threads(process_synthesized_event); if (target_pid == -1 && argc) { pid = fork(); if (pid < 0) - perror("failed to fork"); + die("failed to fork"); if (!pid) { if (execvp(argv[0], (char **)argv)) { perror(argv[0]); exit(-1); } + } else { + /* + * Wait a bit for the execv'ed child to appear + * and be updated in /proc + * FIXME: Do you know a less heuristical solution? + */ + usleep(1000); + event__synthesize_thread(pid, + process_synthesized_event); } child_pid = pid; @@ -633,7 +514,7 @@ static int __cmd_record(int argc, const char **argv) param.sched_priority = realtime_prio; if (sched_setscheduler(0, SCHED_FIFO, ¶m)) { - printf("Could not set realtime priority.\n"); + pr_err("Could not set realtime priority.\n"); exit(-1); } } @@ -651,7 +532,7 @@ static int __cmd_record(int argc, const char **argv) if (hits == samples) { if (done) break; - ret = poll(event_array, nr_poll, -1); + err = poll(event_array, nr_poll, -1); waking++; } @@ -732,6 +613,8 @@ int cmd_record(int argc, const char **argv, const char *prefix __used) { int counter; + symbol__init(0); + argc = parse_options(argc, argv, options, record_usage, PARSE_OPT_STOP_AT_NON_OPTION); if (!argc && target_pid == -1 && !system_wide) diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c index a4f8cc209151..1a806d5f05cf 100644 --- a/tools/perf/builtin-report.c +++ b/tools/perf/builtin-report.c @@ -59,12 +59,28 @@ static struct perf_header *header; static u64 sample_type; -static size_t ipchain__fprintf_graph_line(FILE *fp, int depth, int depth_mask) + +static size_t +callchain__fprintf_left_margin(FILE *fp, int left_margin) +{ + int i; + int ret; + + ret = fprintf(fp, " "); + + for (i = 0; i < left_margin; i++) + ret += fprintf(fp, " "); + + return ret; +} + +static size_t ipchain__fprintf_graph_line(FILE *fp, int depth, int depth_mask, + int left_margin) { int i; size_t ret = 0; - ret += fprintf(fp, "%s", " "); + ret += callchain__fprintf_left_margin(fp, left_margin); for (i = 0; i < depth; i++) if (depth_mask & (1 << i)) @@ -79,12 +95,12 @@ static size_t ipchain__fprintf_graph_line(FILE *fp, int depth, int depth_mask) static size_t ipchain__fprintf_graph(FILE *fp, struct callchain_list *chain, int depth, int depth_mask, int count, u64 total_samples, - int hits) + int hits, int left_margin) { int i; size_t ret = 0; - ret += fprintf(fp, "%s", " "); + ret += callchain__fprintf_left_margin(fp, left_margin); for (i = 0; i < depth; i++) { if (depth_mask & (1 << i)) ret += fprintf(fp, "|"); @@ -122,8 +138,9 @@ static void init_rem_hits(void) } static size_t -callchain__fprintf_graph(FILE *fp, struct callchain_node *self, - u64 total_samples, int depth, int depth_mask) +__callchain__fprintf_graph(FILE *fp, struct callchain_node *self, + u64 total_samples, int depth, int depth_mask, + int left_margin) { struct rb_node *node, *next; struct callchain_node *child; @@ -164,7 +181,8 @@ callchain__fprintf_graph(FILE *fp, struct callchain_node *self, * But we keep the older depth mask for the line seperator * to keep the level link until we reach the last child */ - ret += ipchain__fprintf_graph_line(fp, depth, depth_mask); + ret += ipchain__fprintf_graph_line(fp, depth, depth_mask, + left_margin); i = 0; list_for_each_entry(chain, &child->val, list) { if (chain->ip >= PERF_CONTEXT_MAX) @@ -172,11 +190,13 @@ callchain__fprintf_graph(FILE *fp, struct callchain_node *self, ret += ipchain__fprintf_graph(fp, chain, depth, new_depth_mask, i++, new_total, - cumul); + cumul, + left_margin); } - ret += callchain__fprintf_graph(fp, child, new_total, - depth + 1, - new_depth_mask | (1 << depth)); + ret += __callchain__fprintf_graph(fp, child, new_total, + depth + 1, + new_depth_mask | (1 << depth), + left_margin); node = next; } @@ -190,9 +210,48 @@ callchain__fprintf_graph(FILE *fp, struct callchain_node *self, ret += ipchain__fprintf_graph(fp, &rem_hits, depth, new_depth_mask, 0, new_total, - remaining); + remaining, left_margin); + } + + return ret; +} + + +static size_t +callchain__fprintf_graph(FILE *fp, struct callchain_node *self, + u64 total_samples, int left_margin) +{ + struct callchain_list *chain; + bool printed = false; + int i = 0; + int ret = 0; + + list_for_each_entry(chain, &self->val, list) { + if (chain->ip >= PERF_CONTEXT_MAX) + continue; + + if (!i++ && sort__first_dimension == SORT_SYM) + continue; + + if (!printed) { + ret += callchain__fprintf_left_margin(fp, left_margin); + ret += fprintf(fp, "|\n"); + ret += callchain__fprintf_left_margin(fp, left_margin); + ret += fprintf(fp, "---"); + + left_margin += 3; + printed = true; + } else + ret += callchain__fprintf_left_margin(fp, left_margin); + + if (chain->sym) + ret += fprintf(fp, " %s\n", chain->sym->name); + else + ret += fprintf(fp, " %p\n", (void *)(long)chain->ip); } + ret += __callchain__fprintf_graph(fp, self, total_samples, 1, 1, left_margin); + return ret; } @@ -224,7 +283,7 @@ callchain__fprintf_flat(FILE *fp, struct callchain_node *self, static size_t hist_entry_callchain__fprintf(FILE *fp, struct hist_entry *self, - u64 total_samples) + u64 total_samples, int left_margin) { struct rb_node *rb_node; struct callchain_node *chain; @@ -244,8 +303,8 @@ hist_entry_callchain__fprintf(FILE *fp, struct hist_entry *self, break; case CHAIN_GRAPH_ABS: /* Falldown */ case CHAIN_GRAPH_REL: - ret += callchain__fprintf_graph(fp, chain, - total_samples, 1, 1); + ret += callchain__fprintf_graph(fp, chain, total_samples, + left_margin); case CHAIN_NONE: default: break; @@ -290,8 +349,19 @@ hist_entry__fprintf(FILE *fp, struct hist_entry *self, u64 total_samples) ret += fprintf(fp, "\n"); - if (callchain) - hist_entry_callchain__fprintf(fp, self, total_samples); + if (callchain) { + int left_margin = 0; + + if (sort__first_dimension == SORT_COMM) { + se = list_first_entry(&hist_entry__sort_list, typeof(*se), + list); + left_margin = se->width ? *se->width : 0; + left_margin -= thread__comm_len(self->thread); + } + + hist_entry_callchain__fprintf(fp, self, total_samples, + left_margin); + } return ret; } @@ -385,7 +455,7 @@ got_map: dump_printf(" ...... map: %Lx -> %Lx\n", *ipp, ip); *ipp = ip; - return map ? map->dso->find_symbol(map->dso, ip) : NULL; + return map ? map__find_symbol(map, ip, NULL) : NULL; } static int call__match(struct symbol *sym) @@ -619,7 +689,8 @@ process_sample_event(event_t *event, unsigned long offset, unsigned long head) dump_printf("... chain: nr:%Lu\n", chain->nr); if (validate_chain(chain, event) < 0) { - eprintf("call-chain problem with event, skipping it.\n"); + pr_debug("call-chain problem with event, " + "skipping it.\n"); return 0; } @@ -630,7 +701,7 @@ process_sample_event(event_t *event, unsigned long offset, unsigned long head) } if (thread == NULL) { - eprintf("problem processing %d event, skipping it.\n", + pr_debug("problem processing %d event, skipping it.\n", event->header.type); return -1; } @@ -668,7 +739,7 @@ process_sample_event(event_t *event, unsigned long offset, unsigned long head) if (hist_entry__add(thread, map, sym, ip, chain, level, period)) { - eprintf("problem incrementing symbol count, skipping event\n"); + pr_debug("problem incrementing symbol count, skipping event\n"); return -1; } @@ -1022,7 +1093,7 @@ static void setup_list(struct strlist **list, const char *list_str, int cmd_report(int argc, const char **argv, const char *prefix __used) { - symbol__init(); + symbol__init(0); argc = parse_options(argc, argv, options, report_usage, 0); diff --git a/tools/perf/builtin-sched.c b/tools/perf/builtin-sched.c index 57ad3f458ef5..df44b756cecc 100644 --- a/tools/perf/builtin-sched.c +++ b/tools/perf/builtin-sched.c @@ -38,8 +38,6 @@ static int cwdlen; #define PR_SET_NAME 15 /* Set process name */ #define MAX_CPUS 4096 -#define BUG_ON(x) assert(!(x)) - static u64 run_measurement_overhead; static u64 sleep_measurement_overhead; @@ -1668,8 +1666,8 @@ process_sample_event(event_t *event, unsigned long offset, unsigned long head) (long long)period); if (thread == NULL) { - eprintf("problem processing %d event, skipping it.\n", - event->header.type); + pr_debug("problem processing %d event, skipping it.\n", + event->header.type); return -1; } @@ -1939,7 +1937,7 @@ static int __cmd_record(int argc, const char **argv) int cmd_sched(int argc, const char **argv, const char *prefix __used) { - symbol__init(); + symbol__init(0); argc = parse_options(argc, argv, sched_options, sched_usage, PARSE_OPT_STOP_AT_NON_OPTION); diff --git a/tools/perf/builtin-stat.c b/tools/perf/builtin-stat.c index 3db31e7bf173..c70d72003557 100644 --- a/tools/perf/builtin-stat.c +++ b/tools/perf/builtin-stat.c @@ -50,15 +50,17 @@ static struct perf_event_attr default_attrs[] = { - { .type = PERF_TYPE_SOFTWARE, .config = PERF_COUNT_SW_TASK_CLOCK }, - { .type = PERF_TYPE_SOFTWARE, .config = PERF_COUNT_SW_CONTEXT_SWITCHES}, - { .type = PERF_TYPE_SOFTWARE, .config = PERF_COUNT_SW_CPU_MIGRATIONS }, - { .type = PERF_TYPE_SOFTWARE, .config = PERF_COUNT_SW_PAGE_FAULTS }, - - { .type = PERF_TYPE_HARDWARE, .config = PERF_COUNT_HW_CPU_CYCLES }, - { .type = PERF_TYPE_HARDWARE, .config = PERF_COUNT_HW_INSTRUCTIONS }, - { .type = PERF_TYPE_HARDWARE, .config = PERF_COUNT_HW_CACHE_REFERENCES}, - { .type = PERF_TYPE_HARDWARE, .config = PERF_COUNT_HW_CACHE_MISSES }, + { .type = PERF_TYPE_SOFTWARE, .config = PERF_COUNT_SW_TASK_CLOCK }, + { .type = PERF_TYPE_SOFTWARE, .config = PERF_COUNT_SW_CONTEXT_SWITCHES }, + { .type = PERF_TYPE_SOFTWARE, .config = PERF_COUNT_SW_CPU_MIGRATIONS }, + { .type = PERF_TYPE_SOFTWARE, .config = PERF_COUNT_SW_PAGE_FAULTS }, + + { .type = PERF_TYPE_HARDWARE, .config = PERF_COUNT_HW_CPU_CYCLES }, + { .type = PERF_TYPE_HARDWARE, .config = PERF_COUNT_HW_INSTRUCTIONS }, + { .type = PERF_TYPE_HARDWARE, .config = PERF_COUNT_HW_BRANCH_INSTRUCTIONS }, + { .type = PERF_TYPE_HARDWARE, .config = PERF_COUNT_HW_BRANCH_MISSES }, + { .type = PERF_TYPE_HARDWARE, .config = PERF_COUNT_HW_CACHE_REFERENCES }, + { .type = PERF_TYPE_HARDWARE, .config = PERF_COUNT_HW_CACHE_MISSES }, }; @@ -125,6 +127,7 @@ struct stats event_res_stats[MAX_COUNTERS][3]; struct stats runtime_nsecs_stats; struct stats walltime_nsecs_stats; struct stats runtime_cycles_stats; +struct stats runtime_branches_stats; #define MATCH_EVENT(t, c, counter) \ (attrs[counter].type == PERF_TYPE_##t && \ @@ -235,6 +238,8 @@ static void read_counter(int counter) update_stats(&runtime_nsecs_stats, count[0]); if (MATCH_EVENT(HARDWARE, HW_CPU_CYCLES, counter)) update_stats(&runtime_cycles_stats, count[0]); + if (MATCH_EVENT(HARDWARE, HW_BRANCH_INSTRUCTIONS, counter)) + update_stats(&runtime_branches_stats, count[0]); } static int run_perf_stat(int argc __used, const char **argv) @@ -352,7 +357,16 @@ static void abs_printout(int counter, double avg) ratio = avg / total; fprintf(stderr, " # %10.3f IPC ", ratio); - } else { + } else if (MATCH_EVENT(HARDWARE, HW_BRANCH_MISSES, counter) && + runtime_branches_stats.n != 0) { + total = avg_stats(&runtime_branches_stats); + + if (total) + ratio = avg * 100 / total; + + fprintf(stderr, " # %10.3f %% ", ratio); + + } else if (runtime_nsecs_stats.n != 0) { total = avg_stats(&runtime_nsecs_stats); if (total) diff --git a/tools/perf/builtin-timechart.c b/tools/perf/builtin-timechart.c index 702d8fe58fbc..dd4d82ac7aa4 100644 --- a/tools/perf/builtin-timechart.c +++ b/tools/perf/builtin-timechart.c @@ -153,6 +153,17 @@ static struct wake_event *wake_events; struct sample_wrapper *all_samples; + +struct process_filter; +struct process_filter { + char *name; + int pid; + struct process_filter *next; +}; + +static struct process_filter *process_filter; + + static struct per_pid *find_create_pid(int pid) { struct per_pid *cursor = all_data; @@ -763,21 +774,42 @@ static void draw_wakeups(void) c = p->all; while (c) { if (c->Y && c->start_time <= we->time && c->end_time >= we->time) { - if (p->pid == we->waker) { + if (p->pid == we->waker && !from) { from = c->Y; - task_from = c->comm; + task_from = strdup(c->comm); } - if (p->pid == we->wakee) { + if (p->pid == we->wakee && !to) { to = c->Y; - task_to = c->comm; + task_to = strdup(c->comm); } } c = c->next; } + c = p->all; + while (c) { + if (p->pid == we->waker && !from) { + from = c->Y; + task_from = strdup(c->comm); + } + if (p->pid == we->wakee && !to) { + to = c->Y; + task_to = strdup(c->comm); + } + c = c->next; + } } p = p->next; } + if (!task_from) { + task_from = malloc(40); + sprintf(task_from, "[%i]", we->waker); + } + if (!task_to) { + task_to = malloc(40); + sprintf(task_to, "[%i]", we->wakee); + } + if (we->waker == -1) svg_interrupt(we->time, to); else if (from && to && abs(from - to) == 1) @@ -785,6 +817,9 @@ static void draw_wakeups(void) else svg_partial_wakeline(we->time, from, task_from, to, task_to); we = we->next; + + free(task_from); + free(task_to); } } @@ -858,12 +893,89 @@ static void draw_process_bars(void) } } +static void add_process_filter(const char *string) +{ + struct process_filter *filt; + int pid; + + pid = strtoull(string, NULL, 10); + filt = malloc(sizeof(struct process_filter)); + if (!filt) + return; + + filt->name = strdup(string); + filt->pid = pid; + filt->next = process_filter; + + process_filter = filt; +} + +static int passes_filter(struct per_pid *p, struct per_pidcomm *c) +{ + struct process_filter *filt; + if (!process_filter) + return 1; + + filt = process_filter; + while (filt) { + if (filt->pid && p->pid == filt->pid) + return 1; + if (strcmp(filt->name, c->comm) == 0) + return 1; + filt = filt->next; + } + return 0; +} + +static int determine_display_tasks_filtered(void) +{ + struct per_pid *p; + struct per_pidcomm *c; + int count = 0; + + p = all_data; + while (p) { + p->display = 0; + if (p->start_time == 1) + p->start_time = first_time; + + /* no exit marker, task kept running to the end */ + if (p->end_time == 0) + p->end_time = last_time; + + c = p->all; + + while (c) { + c->display = 0; + + if (c->start_time == 1) + c->start_time = first_time; + + if (passes_filter(p, c)) { + c->display = 1; + p->display = 1; + count++; + } + + if (c->end_time == 0) + c->end_time = last_time; + + c = c->next; + } + p = p->next; + } + return count; +} + static int determine_display_tasks(u64 threshold) { struct per_pid *p; struct per_pidcomm *c; int count = 0; + if (process_filter) + return determine_display_tasks_filtered(); + p = all_data; while (p) { p->display = 0; @@ -981,7 +1093,7 @@ static void process_samples(void) static int __cmd_timechart(void) { - int ret, rc = EXIT_FAILURE; + int err, rc = EXIT_FAILURE; unsigned long offset = 0; unsigned long head, shift; struct stat statbuf; @@ -999,8 +1111,8 @@ static int __cmd_timechart(void) exit(-1); } - ret = fstat(input, &statbuf); - if (ret < 0) { + err = fstat(input, &statbuf); + if (err < 0) { perror("failed to stat file"); exit(-1); } @@ -1010,7 +1122,16 @@ static int __cmd_timechart(void) exit(0); } - header = perf_header__read(input); + header = perf_header__new(); + if (header == NULL) + return -ENOMEM; + + err = perf_header__read(header, input); + if (err < 0) { + perf_header__delete(header); + return err; + } + head = header->data_offset; sample_type = perf_header__sample_type(header); @@ -1050,12 +1171,10 @@ more: size = event->header.size; if (!size || process_event(event) < 0) { - - printf("%p [%p]: skipping unknown header type: %d\n", - (void *)(offset + head), - (void *)(long)(event->header.size), - event->header.type); - + pr_warning("%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'. @@ -1088,7 +1207,8 @@ done: write_svg_file(output_name); - printf("Written %2.1f seconds of trace to %s.\n", (last_time - first_time) / 1000000000.0, output_name); + pr_info("Written %2.1f seconds of trace to %s.\n", + (last_time - first_time) / 1000000000.0, output_name); return rc; } @@ -1129,6 +1249,14 @@ static int __cmd_record(int argc, const char **argv) return cmd_record(i, rec_argv, NULL); } +static int +parse_process(const struct option *opt __used, const char *arg, int __used unset) +{ + if (arg) + add_process_filter(arg); + return 0; +} + static const struct option options[] = { OPT_STRING('i', "input", &input_name, "file", "input file name"), @@ -1136,15 +1264,18 @@ static const struct option options[] = { "output file name"), OPT_INTEGER('w', "width", &svg_page_width, "page width"), - OPT_BOOLEAN('p', "power-only", &power_only, + OPT_BOOLEAN('P', "power-only", &power_only, "output power data only"), + OPT_CALLBACK('p', "process", NULL, "process", + "process selector. Pass a pid or process name.", + parse_process), OPT_END() }; int cmd_timechart(int argc, const char **argv, const char *prefix __used) { - symbol__init(); + symbol__init(0); page_size = getpagesize(); diff --git a/tools/perf/builtin-top.c b/tools/perf/builtin-top.c index 2d8806bac258..6d770ac7be0b 100644 --- a/tools/perf/builtin-top.c +++ b/tools/perf/builtin-top.c @@ -60,7 +60,7 @@ static int system_wide = 0; static int default_interval = 0; static int count_filter = 5; -static int print_entries = 15; +static int print_entries; static int target_pid = -1; static int inherit = 0; @@ -76,6 +76,17 @@ static int delay_secs = 2; static int zero = 0; static int dump_symtab = 0; +static bool hide_kernel_symbols = false; +static bool hide_user_symbols = false; +static struct winsize winsize; +static const char *graph_line = + "_____________________________________________________________________" + "_____________________________________________________________________"; +static const char *graph_dotted_line = + "---------------------------------------------------------------------" + "---------------------------------------------------------------------" + "---------------------------------------------------------------------"; + /* * Source */ @@ -97,27 +108,75 @@ static int display_weighted = -1; * Symbols */ +struct sym_entry_source { + struct source_line *source; + struct source_line *lines; + struct source_line **lines_tail; + pthread_mutex_t lock; +}; + struct sym_entry { struct rb_node rb_node; struct list_head node; - unsigned long count[MAX_COUNTERS]; unsigned long snap_count; double weight; int skip; + u16 name_len; + u8 origin; struct map *map; - struct source_line *source; - struct source_line *lines; - struct source_line **lines_tail; - pthread_mutex_t source_lock; + struct sym_entry_source *src; + unsigned long count[0]; }; /* * Source functions */ +static inline struct symbol *sym_entry__symbol(struct sym_entry *self) +{ + return ((void *)self) + symbol__priv_size; +} + +static void get_term_dimensions(struct winsize *ws) +{ + char *s = getenv("LINES"); + + if (s != NULL) { + ws->ws_row = atoi(s); + s = getenv("COLUMNS"); + if (s != NULL) { + ws->ws_col = atoi(s); + if (ws->ws_row && ws->ws_col) + return; + } + } +#ifdef TIOCGWINSZ + if (ioctl(1, TIOCGWINSZ, ws) == 0 && + ws->ws_row && ws->ws_col) + return; +#endif + ws->ws_row = 25; + ws->ws_col = 80; +} + +static void update_print_entries(struct winsize *ws) +{ + print_entries = ws->ws_row; + + if (print_entries > 9) + print_entries -= 9; +} + +static void sig_winch_handler(int sig __used) +{ + get_term_dimensions(&winsize); + update_print_entries(&winsize); +} + static void parse_source(struct sym_entry *syme) { struct symbol *sym; + struct sym_entry_source *source; struct map *map; FILE *file; char command[PATH_MAX*2]; @@ -127,12 +186,21 @@ static void parse_source(struct sym_entry *syme) if (!syme) return; - if (syme->lines) { - pthread_mutex_lock(&syme->source_lock); + if (syme->src == NULL) { + syme->src = calloc(1, sizeof(*source)); + if (syme->src == NULL) + return; + pthread_mutex_init(&syme->src->lock, NULL); + } + + source = syme->src; + + if (source->lines) { + pthread_mutex_lock(&source->lock); goto out_assign; } - sym = (struct symbol *)(syme + 1); + sym = sym_entry__symbol(syme); map = syme->map; path = map->dso->long_name; @@ -141,14 +209,15 @@ static void parse_source(struct sym_entry *syme) sprintf(command, "objdump --start-address=0x%016Lx " "--stop-address=0x%016Lx -dS %s", - sym->start, sym->end, path); + map->unmap_ip(map, sym->start), + map->unmap_ip(map, sym->end), path); file = popen(command, "r"); if (!file) return; - pthread_mutex_lock(&syme->source_lock); - syme->lines_tail = &syme->lines; + pthread_mutex_lock(&source->lock); + source->lines_tail = &source->lines; while (!feof(file)) { struct source_line *src; size_t dummy = 0; @@ -168,22 +237,22 @@ static void parse_source(struct sym_entry *syme) *c = 0; src->next = NULL; - *syme->lines_tail = src; - syme->lines_tail = &src->next; + *source->lines_tail = src; + source->lines_tail = &src->next; if (strlen(src->line)>8 && src->line[8] == ':') { src->eip = strtoull(src->line, NULL, 16); - src->eip += map->start; + src->eip = map->unmap_ip(map, src->eip); } if (strlen(src->line)>8 && src->line[16] == ':') { src->eip = strtoull(src->line, NULL, 16); - src->eip += map->start; + src->eip = map->unmap_ip(map, src->eip); } } pclose(file); out_assign: sym_filter_entry = syme; - pthread_mutex_unlock(&syme->source_lock); + pthread_mutex_unlock(&source->lock); } static void __zero_source_counters(struct sym_entry *syme) @@ -191,7 +260,7 @@ static void __zero_source_counters(struct sym_entry *syme) int i; struct source_line *line; - line = syme->lines; + line = syme->src->lines; while (line) { for (i = 0; i < nr_counters; i++) line->count[i] = 0; @@ -206,13 +275,13 @@ static void record_precise_ip(struct sym_entry *syme, int counter, u64 ip) if (syme != sym_filter_entry) return; - if (pthread_mutex_trylock(&syme->source_lock)) + if (pthread_mutex_trylock(&syme->src->lock)) return; - if (!syme->source) + if (syme->src == NULL || syme->src->source == NULL) goto out_unlock; - for (line = syme->lines; line; line = line->next) { + for (line = syme->src->lines; line; line = line->next) { if (line->eip == ip) { line->count[counter]++; break; @@ -221,25 +290,25 @@ static void record_precise_ip(struct sym_entry *syme, int counter, u64 ip) break; } out_unlock: - pthread_mutex_unlock(&syme->source_lock); + pthread_mutex_unlock(&syme->src->lock); } static void lookup_sym_source(struct sym_entry *syme) { - struct symbol *symbol = (struct symbol *)(syme + 1); + struct symbol *symbol = sym_entry__symbol(syme); struct source_line *line; char pattern[PATH_MAX]; sprintf(pattern, "<%s>:", symbol->name); - pthread_mutex_lock(&syme->source_lock); - for (line = syme->lines; line; line = line->next) { + pthread_mutex_lock(&syme->src->lock); + for (line = syme->src->lines; line; line = line->next) { if (strstr(line->line, pattern)) { - syme->source = line; + syme->src->source = line; break; } } - pthread_mutex_unlock(&syme->source_lock); + pthread_mutex_unlock(&syme->src->lock); } static void show_lines(struct source_line *queue, int count, int total) @@ -269,24 +338,24 @@ static void show_details(struct sym_entry *syme) if (!syme) return; - if (!syme->source) + if (!syme->src->source) lookup_sym_source(syme); - if (!syme->source) + if (!syme->src->source) return; - symbol = (struct symbol *)(syme + 1); + symbol = sym_entry__symbol(syme); printf("Showing %s for %s\n", event_name(sym_counter), symbol->name); printf(" Events Pcnt (>=%d%%)\n", sym_pcnt_filter); - pthread_mutex_lock(&syme->source_lock); - line = syme->source; + pthread_mutex_lock(&syme->src->lock); + line = syme->src->source; while (line) { total += line->count[sym_counter]; line = line->next; } - line = syme->source; + line = syme->src->source; while (line) { float pcnt = 0.0; @@ -311,13 +380,13 @@ static void show_details(struct sym_entry *syme) line->count[sym_counter] = zero ? 0 : line->count[sym_counter] * 7 / 8; line = line->next; } - pthread_mutex_unlock(&syme->source_lock); + pthread_mutex_unlock(&syme->src->lock); if (more) printf("%d lines not displayed, maybe increase display entries [e]\n", more); } /* - * Symbols will be added here in record_ip and will get out + * Symbols will be added here in event__process_sample and will get out * after decayed. */ static LIST_HEAD(active_symbols); @@ -388,6 +457,9 @@ static void print_sym_table(void) struct sym_entry *syme, *n; struct rb_root tmp = RB_ROOT; struct rb_node *nd; + int sym_width = 0, dso_width = 0; + const int win_width = winsize.ws_col - 1; + struct dso *unique_dso = NULL, *first_dso = NULL; samples = userspace_samples = 0; @@ -399,6 +471,14 @@ static void print_sym_table(void) list_for_each_entry_safe_from(syme, n, &active_symbols, node) { syme->snap_count = syme->count[snap]; if (syme->snap_count != 0) { + + if ((hide_user_symbols && + syme->origin == PERF_RECORD_MISC_USER) || + (hide_kernel_symbols && + syme->origin == PERF_RECORD_MISC_KERNEL)) { + list_remove_active_sym(syme); + continue; + } syme->weight = sym_weight(syme); rb_insert_active_sym(&tmp, syme); sum_ksamples += syme->snap_count; @@ -411,8 +491,7 @@ static void print_sym_table(void) puts(CONSOLE_CLEAR); - printf( -"------------------------------------------------------------------------------\n"); + printf("%-*.*s\n", win_width, win_width, graph_dotted_line); printf( " PerfTop:%8.0f irqs/sec kernel:%4.1f%% [", samples_per_sec, 100.0 - (100.0*((samples_per_sec-ksamples_per_sec)/samples_per_sec))); @@ -450,33 +529,70 @@ static void print_sym_table(void) printf(", %d CPUs)\n", nr_cpus); } - printf("------------------------------------------------------------------------------\n\n"); + printf("%-*.*s\n", win_width, win_width, graph_dotted_line); if (sym_filter_entry) { show_details(sym_filter_entry); return; } + /* + * Find the longest symbol name that will be displayed + */ + for (nd = rb_first(&tmp); nd; nd = rb_next(nd)) { + syme = rb_entry(nd, struct sym_entry, rb_node); + if (++printed > print_entries || + (int)syme->snap_count < count_filter) + continue; + + if (first_dso == NULL) + unique_dso = first_dso = syme->map->dso; + else if (syme->map->dso != first_dso) + unique_dso = NULL; + + if (syme->map->dso->long_name_len > dso_width) + dso_width = syme->map->dso->long_name_len; + + if (syme->name_len > sym_width) + sym_width = syme->name_len; + } + + printed = 0; + + if (unique_dso) + printf("DSO: %s\n", unique_dso->long_name); + else { + int max_dso_width = winsize.ws_col - sym_width - 29; + if (dso_width > max_dso_width) + dso_width = max_dso_width; + putchar('\n'); + } if (nr_counters == 1) - printf(" samples pcnt"); + printf(" samples pcnt"); else - printf(" weight samples pcnt"); + printf(" weight samples pcnt"); if (verbose) printf(" RIP "); - printf(" kernel function\n"); - printf(" %s _______ _____", + printf(" %-*.*s", sym_width, sym_width, "function"); + if (!unique_dso) + printf(" DSO"); + putchar('\n'); + printf(" %s _______ _____", nr_counters == 1 ? " " : "______"); if (verbose) - printf(" ________________"); - printf(" _______________\n\n"); + printf(" ________________"); + printf(" %-*.*s", sym_width, sym_width, graph_line); + if (!unique_dso) + printf(" %-*.*s", dso_width, dso_width, graph_line); + puts("\n"); for (nd = rb_first(&tmp); nd; nd = rb_next(nd)) { struct symbol *sym; double pcnt; syme = rb_entry(nd, struct sym_entry, rb_node); - sym = (struct symbol *)(syme + 1); + sym = sym_entry__symbol(syme); if (++printed > print_entries || (int)syme->snap_count < count_filter) continue; @@ -485,16 +601,19 @@ static void print_sym_table(void) sum_ksamples)); if (nr_counters == 1 || !display_weighted) - printf("%20.2f - ", syme->weight); + printf("%20.2f ", syme->weight); else - printf("%9.1f %10ld - ", syme->weight, syme->snap_count); + printf("%9.1f %10ld ", syme->weight, syme->snap_count); percent_color_fprintf(stdout, "%4.1f%%", pcnt); if (verbose) - printf(" - %016llx", sym->start); - printf(" : %s", sym->name); - if (syme->map->dso->name[0] == '[') - printf(" \t%s", syme->map->dso->name); + printf(" %016llx", sym->start); + printf(" %-*.*s", sym_width, sym_width, sym->name); + if (!unique_dso) + printf(" %-*.*s", dso_width, dso_width, + dso_width >= syme->map->dso->long_name_len ? + syme->map->dso->long_name : + syme->map->dso->short_name); printf("\n"); } } @@ -542,10 +661,10 @@ static void prompt_symbol(struct sym_entry **target, const char *msg) /* zero counters of active symbol */ if (syme) { - pthread_mutex_lock(&syme->source_lock); + pthread_mutex_lock(&syme->src->lock); __zero_source_counters(syme); *target = NULL; - pthread_mutex_unlock(&syme->source_lock); + pthread_mutex_unlock(&syme->src->lock); } fprintf(stdout, "\n%s: ", msg); @@ -561,7 +680,7 @@ static void prompt_symbol(struct sym_entry **target, const char *msg) pthread_mutex_unlock(&active_symbols_lock); list_for_each_entry_safe_from(syme, n, &active_symbols, node) { - struct symbol *sym = (struct symbol *)(syme + 1); + struct symbol *sym = sym_entry__symbol(syme); if (!strcmp(buf, sym->name)) { found = syme; @@ -585,7 +704,7 @@ static void print_mapped_keys(void) char *name = NULL; if (sym_filter_entry) { - struct symbol *sym = (struct symbol *)(sym_filter_entry+1); + struct symbol *sym = sym_entry__symbol(sym_filter_entry); name = sym->name; } @@ -607,6 +726,12 @@ static void print_mapped_keys(void) if (nr_counters > 1) fprintf(stdout, "\t[w] toggle display weighted/count[E]r. \t(%d)\n", display_weighted ? 1 : 0); + fprintf(stdout, + "\t[K] hide kernel_symbols symbols. \t(%s)\n", + hide_kernel_symbols ? "yes" : "no"); + fprintf(stdout, + "\t[U] hide user symbols. \t(%s)\n", + hide_user_symbols ? "yes" : "no"); fprintf(stdout, "\t[z] toggle sample zeroing. \t(%d)\n", zero ? 1 : 0); fprintf(stdout, "\t[qQ] quit.\n"); } @@ -620,6 +745,8 @@ static int key_mapped(int c) case 'z': case 'q': case 'Q': + case 'K': + case 'U': return 1; case 'E': case 'w': @@ -663,9 +790,16 @@ static void handle_keypress(int c) switch (c) { case 'd': prompt_integer(&delay_secs, "Enter display delay"); + if (delay_secs < 1) + delay_secs = 1; break; case 'e': prompt_integer(&print_entries, "Enter display entries (lines)"); + if (print_entries == 0) { + sig_winch_handler(SIGWINCH); + signal(SIGWINCH, sig_winch_handler); + } else + signal(SIGWINCH, SIG_DFL); break; case 'E': if (nr_counters > 1) { @@ -690,6 +824,9 @@ static void handle_keypress(int c) case 'F': prompt_percent(&sym_pcnt_filter, "Enter details display event filter (percent)"); break; + case 'K': + hide_kernel_symbols = !hide_kernel_symbols; + break; case 'q': case 'Q': printf("exiting.\n"); @@ -703,12 +840,15 @@ static void handle_keypress(int c) else { struct sym_entry *syme = sym_filter_entry; - pthread_mutex_lock(&syme->source_lock); + pthread_mutex_lock(&syme->src->lock); sym_filter_entry = NULL; __zero_source_counters(syme); - pthread_mutex_unlock(&syme->source_lock); + pthread_mutex_unlock(&syme->src->lock); } break; + case 'U': + hide_user_symbols = !hide_user_symbols; + break; case 'w': display_weighted = ~display_weighted; break; @@ -787,9 +927,9 @@ static int symbol_filter(struct map *map, struct symbol *sym) strstr(name, "_text_end")) return 1; - syme = dso__sym_priv(map->dso, sym); + syme = symbol__priv(sym); syme->map = map; - pthread_mutex_init(&syme->source_lock, NULL); + syme->src = NULL; if (!sym_filter_entry && sym_filter && !strcmp(name, sym_filter)) sym_filter_entry = syme; @@ -800,13 +940,20 @@ static int symbol_filter(struct map *map, struct symbol *sym) } } + if (!syme->skip) + syme->name_len = strlen(sym->name); + return 0; } static int parse_symbols(void) { - if (dsos__load_kernel(vmlinux_name, sizeof(struct sym_entry), - symbol_filter, verbose, 1) <= 0) + struct dso *kernel = dsos__load_kernel(); + + if (kernel == NULL) + return -1; + + if (dso__load_kernel_sym(kernel, symbol_filter, 1) <= 0) return -1; if (dump_symtab) @@ -815,41 +962,104 @@ static int parse_symbols(void) return 0; } -/* - * Binary search in the histogram table and record the hit: - */ -static void record_ip(u64 ip, int counter) +static void event__process_sample(const event_t *self, int counter) { + u64 ip = self->ip.ip; struct map *map; - struct symbol *sym = kernel_maps__find_symbol(ip, &map); - - if (sym != NULL) { - struct sym_entry *syme = dso__sym_priv(map->dso, sym); - - if (!syme->skip) { - syme->count[counter]++; - record_precise_ip(syme, counter, ip); - pthread_mutex_lock(&active_symbols_lock); - if (list_empty(&syme->node) || !syme->node.next) - __list_insert_active_sym(syme); - pthread_mutex_unlock(&active_symbols_lock); + struct sym_entry *syme; + struct symbol *sym; + u8 origin = self->header.misc & PERF_RECORD_MISC_CPUMODE_MASK; + + switch (origin) { + case PERF_RECORD_MISC_USER: { + struct thread *thread; + + if (hide_user_symbols) return; + + thread = threads__findnew(self->ip.pid); + if (thread == NULL) + return; + + map = thread__find_map(thread, ip); + if (map != NULL) { + ip = map->map_ip(map, ip); + sym = map__find_symbol(map, ip, symbol_filter); + if (sym == NULL) + return; + userspace_samples++; + break; } } + /* + * If this is outside of all known maps, + * and is a negative address, try to look it + * up in the kernel dso, as it might be a + * vsyscall or vdso (which executes in user-mode). + */ + if ((long long)ip >= 0) + return; + /* Fall thru */ + case PERF_RECORD_MISC_KERNEL: + if (hide_kernel_symbols) + return; + + sym = kernel_maps__find_symbol(ip, &map); + if (sym == NULL) + return; + break; + default: + return; + } - samples--; + syme = symbol__priv(sym); + + if (!syme->skip) { + syme->count[counter]++; + syme->origin = origin; + record_precise_ip(syme, counter, ip); + pthread_mutex_lock(&active_symbols_lock); + if (list_empty(&syme->node) || !syme->node.next) + __list_insert_active_sym(syme); + pthread_mutex_unlock(&active_symbols_lock); + ++samples; + return; + } } -static void process_event(u64 ip, int counter, int user) +static void event__process_mmap(event_t *self) { - samples++; + struct thread *thread = threads__findnew(self->mmap.pid); - if (user) { - userspace_samples++; - return; + if (thread != NULL) { + struct map *map = map__new(&self->mmap, NULL, 0); + if (map != NULL) + thread__insert_map(thread, map); + } +} + +static void event__process_comm(event_t *self) +{ + struct thread *thread = threads__findnew(self->comm.pid); + + if (thread != NULL) + thread__set_comm(thread, self->comm.comm); +} + +static int event__process(event_t *event) +{ + switch (event->header.type) { + case PERF_RECORD_COMM: + event__process_comm(event); + break; + case PERF_RECORD_MMAP: + event__process_mmap(event); + break; + default: + break; } - record_ip(ip, counter); + return 0; } struct mmap_data { @@ -922,13 +1132,11 @@ static void mmap_read_counter(struct mmap_data *md) event = &event_copy; } + if (event->header.type == PERF_RECORD_SAMPLE) + event__process_sample(event, md->counter); + else + event__process(event); old += size; - - if (event->header.type == PERF_RECORD_SAMPLE) { - int user = - (event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK) == PERF_RECORD_MISC_USER; - process_event(event->ip.ip, md->counter, user); - } } md->prev = old; @@ -970,6 +1178,7 @@ static void start_counter(int i, int counter) } attr->inherit = (cpu < 0) && inherit; + attr->mmap = 1; try_again: fd[i][counter] = sys_perf_event_open(attr, target_pid, cpu, group_fd, 0); @@ -977,7 +1186,7 @@ try_again: if (fd[i][counter] < 0) { int err = errno; - if (err == EPERM) + if (err == EPERM || err == EACCES) die("No permission - are you root?\n"); /* * If it's cycles then fall back to hrtimer @@ -1028,6 +1237,11 @@ static int __cmd_top(void) int i, counter; int ret; + if (target_pid != -1) + event__synthesize_thread(target_pid, event__process); + else + event__synthesize_threads(event__process); + for (i = 0; i < nr_cpus; i++) { group_fd = -1; for (counter = 0; counter < nr_counters; counter++) @@ -1084,6 +1298,8 @@ static const struct option options[] = { OPT_INTEGER('C', "CPU", &profile_cpu, "CPU to profile on"), OPT_STRING('k', "vmlinux", &vmlinux_name, "file", "vmlinux pathname"), + OPT_BOOLEAN('K', "hide_kernel_symbols", &hide_kernel_symbols, + "hide kernel symbols"), OPT_INTEGER('m', "mmap-pages", &mmap_pages, "number of mmap data pages"), OPT_INTEGER('r', "realtime", &realtime_prio, @@ -1106,6 +1322,8 @@ static const struct option options[] = { "profile at this frequency"), OPT_INTEGER('E', "entries", &print_entries, "display this many functions"), + OPT_BOOLEAN('U', "hide_user_symbols", &hide_user_symbols, + "hide user symbols"), OPT_BOOLEAN('v', "verbose", &verbose, "be more verbose (show counter open errors, etc)"), OPT_END() @@ -1115,8 +1333,6 @@ int cmd_top(int argc, const char **argv, const char *prefix __used) { int counter; - symbol__init(); - page_size = sysconf(_SC_PAGE_SIZE); argc = parse_options(argc, argv, options, top_usage, 0); @@ -1133,6 +1349,9 @@ int cmd_top(int argc, const char **argv, const char *prefix __used) if (!nr_counters) nr_counters = 1; + symbol__init(sizeof(struct sym_entry) + + (nr_counters + 1) * sizeof(unsigned long)); + if (delay_secs < 1) delay_secs = 1; @@ -1169,5 +1388,11 @@ int cmd_top(int argc, const char **argv, const char *prefix __used) if (target_pid != -1 || profile_cpu != -1) nr_cpus = 1; + get_term_dimensions(&winsize); + if (print_entries == 0) { + update_print_entries(&winsize); + signal(SIGWINCH, sig_winch_handler); + } + return __cmd_top(); } diff --git a/tools/perf/builtin-trace.c b/tools/perf/builtin-trace.c index 4c129ff0bb16..d042d656c561 100644 --- a/tools/perf/builtin-trace.c +++ b/tools/perf/builtin-trace.c @@ -81,8 +81,8 @@ process_sample_event(event_t *event, unsigned long offset, unsigned long head) (long long)period); if (thread == NULL) { - eprintf("problem processing %d event, skipping it.\n", - event->header.type); + pr_debug("problem processing %d event, skipping it.\n", + event->header.type); return -1; } @@ -151,7 +151,7 @@ static const struct option options[] = { int cmd_trace(int argc, const char **argv, const char *prefix __used) { - symbol__init(); + symbol__init(0); argc = parse_options(argc, argv, options, annotate_usage, 0); if (argc) { diff --git a/tools/perf/builtin.h b/tools/perf/builtin.h index e11d8d231c3b..a3d8bf65f26c 100644 --- a/tools/perf/builtin.h +++ b/tools/perf/builtin.h @@ -15,6 +15,8 @@ extern int read_line_with_nul(char *buf, int size, FILE *file); 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_list(int argc, const char **argv, const char *prefix); extern int cmd_help(int argc, const char **argv, const char *prefix); extern int cmd_sched(int argc, const char **argv, const char *prefix); extern int cmd_list(int argc, const char **argv, const char *prefix); @@ -25,5 +27,7 @@ extern int cmd_timechart(int argc, const char **argv, const char *prefix); extern int cmd_top(int argc, const char **argv, const char *prefix); extern int cmd_trace(int argc, const char **argv, const char *prefix); extern int cmd_version(int argc, const char **argv, const char *prefix); +extern int cmd_probe(int argc, const char **argv, const char *prefix); +extern int cmd_kmem(int argc, const char **argv, const char *prefix); #endif diff --git a/tools/perf/command-list.txt b/tools/perf/command-list.txt index 00326e230d87..d3a6e18e4a5e 100644 --- a/tools/perf/command-list.txt +++ b/tools/perf/command-list.txt @@ -3,6 +3,8 @@ # command name category [deprecated] [common] # perf-annotate mainporcelain common +perf-bench mainporcelain common +perf-buildid-list mainporcelain common perf-list mainporcelain common perf-sched mainporcelain common perf-record mainporcelain common @@ -11,3 +13,4 @@ perf-stat mainporcelain common perf-timechart mainporcelain common perf-top mainporcelain common perf-trace mainporcelain common +perf-probe mainporcelain common diff --git a/tools/perf/design.txt b/tools/perf/design.txt index fdd42a824c98..f000c30877ac 100644 --- a/tools/perf/design.txt +++ b/tools/perf/design.txt @@ -137,6 +137,8 @@ enum sw_event_ids { PERF_COUNT_SW_CPU_MIGRATIONS = 4, PERF_COUNT_SW_PAGE_FAULTS_MIN = 5, PERF_COUNT_SW_PAGE_FAULTS_MAJ = 6, + PERF_COUNT_SW_ALIGNMENT_FAULTS = 7, + PERF_COUNT_SW_EMULATION_FAULTS = 8, }; Counters of the type PERF_TYPE_TRACEPOINT are available when the ftrace event diff --git a/tools/perf/perf.c b/tools/perf/perf.c index 624e62d9d1e0..cf64049bc9bd 100644 --- a/tools/perf/perf.c +++ b/tools/perf/perf.c @@ -14,6 +14,7 @@ #include "util/run-command.h" #include "util/parse-events.h" #include "util/string.h" +#include "util/debugfs.h" const char perf_usage_string[] = "perf [--version] [--help] COMMAND [ARGS]"; @@ -284,17 +285,21 @@ static void handle_internal_command(int argc, const char **argv) { const char *cmd = argv[0]; static struct cmd_struct commands[] = { - { "help", cmd_help, 0 }, - { "list", cmd_list, 0 }, - { "record", cmd_record, 0 }, - { "report", cmd_report, 0 }, - { "stat", cmd_stat, 0 }, - { "timechart", cmd_timechart, 0 }, - { "top", cmd_top, 0 }, - { "annotate", cmd_annotate, 0 }, - { "version", cmd_version, 0 }, - { "trace", cmd_trace, 0 }, - { "sched", cmd_sched, 0 }, + { "buildid-list", cmd_buildid_list, 0 }, + { "help", cmd_help, 0 }, + { "list", cmd_list, 0 }, + { "record", cmd_record, 0 }, + { "report", cmd_report, 0 }, + { "bench", cmd_bench, 0 }, + { "stat", cmd_stat, 0 }, + { "timechart", cmd_timechart, 0 }, + { "top", cmd_top, 0 }, + { "annotate", cmd_annotate, 0 }, + { "version", cmd_version, 0 }, + { "trace", cmd_trace, 0 }, + { "sched", cmd_sched, 0 }, + { "probe", cmd_probe, 0 }, + { "kmem", cmd_kmem, 0 }, }; unsigned int i; static const char ext[] = STRIP_EXTENSION; @@ -382,45 +387,12 @@ 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) { - FILE *file; - char fs_type[100]; - char debugfs[MAXPATHLEN]; + const char *path = debugfs_find_mountpoint(); - /* - * try the standard location - */ - if (valid_debugfs_mount("/sys/kernel/debug/") == 0) { - strcpy(debugfs_mntpt, "/sys/kernel/debug/"); - return; - } - - /* - * try the sane location - */ - if (valid_debugfs_mount("/debug/") == 0) { - strcpy(debugfs_mntpt, "/debug/"); - return; - } - - /* - * give up and parse /proc/mounts - */ - file = fopen("/proc/mounts", "r"); - if (file == NULL) - return; - - while (fscanf(file, "%*s %" - STR(MAXPATHLEN) - "s %99s %*s %*d %*d\n", - debugfs, fs_type) == 2) { - if (strcmp(fs_type, "debugfs") == 0) - break; - } - fclose(file); - if (strcmp(fs_type, "debugfs") == 0) { - strncpy(debugfs_mntpt, debugfs, MAXPATHLEN); - debugfs_mntpt[MAXPATHLEN - 1] = '\0'; - } + if (path) + strncpy(debugfs_mntpt, path, sizeof(debugfs_mntpt)); + else + debugfs_mntpt[0] = '\0'; } int main(int argc, const char **argv) diff --git a/tools/perf/perf.h b/tools/perf/perf.h index 8cc4623afd6f..454d5d55f32d 100644 --- a/tools/perf/perf.h +++ b/tools/perf/perf.h @@ -47,6 +47,18 @@ #define cpu_relax() asm volatile("":::"memory") #endif +#ifdef __alpha__ +#include "../../arch/alpha/include/asm/unistd.h" +#define rmb() asm volatile("mb" ::: "memory") +#define cpu_relax() asm volatile("" ::: "memory") +#endif + +#ifdef __ia64__ +#include "../../arch/ia64/include/asm/unistd.h" +#define rmb() asm volatile ("mf" ::: "memory") +#define cpu_relax() asm volatile ("hint @pause" ::: "memory") +#endif + #include <time.h> #include <unistd.h> #include <sys/types.h> diff --git a/tools/perf/util/PERF-VERSION-GEN b/tools/perf/util/PERF-VERSION-GEN index c561d1538c03..54552a00a117 100755 --- a/tools/perf/util/PERF-VERSION-GEN +++ b/tools/perf/util/PERF-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=PERF-VERSION-FILE -DEF_VER=v0.0.1.PERF +DEF_VER=v0.0.2.PERF LF=' ' diff --git a/tools/perf/util/callchain.c b/tools/perf/util/callchain.c index 3b8380f1b478..b3b71258272a 100644 --- a/tools/perf/util/callchain.c +++ b/tools/perf/util/callchain.c @@ -206,7 +206,7 @@ fill_node(struct callchain_node *node, struct ip_callchain *chain, } node->val_nr = chain->nr - start; if (!node->val_nr) - printf("Warning: empty node in callchain tree\n"); + pr_warning("Warning: empty node in callchain tree\n"); } static void diff --git a/tools/perf/util/data_map.c b/tools/perf/util/data_map.c index 242b0555ab91..5543e7d0487d 100644 --- a/tools/perf/util/data_map.c +++ b/tools/perf/util/data_map.c @@ -70,6 +70,35 @@ process_event(event_t *event, unsigned long offset, unsigned long head) } } +int perf_header__read_build_ids(int input, off_t offset, off_t size) +{ + struct build_id_event bev; + char filename[PATH_MAX]; + off_t 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; +} + int mmap_dispatch_perf_file(struct perf_header **pheader, const char *input_name, int force, @@ -77,7 +106,7 @@ int mmap_dispatch_perf_file(struct perf_header **pheader, int *cwdlen, char **cwd) { - int ret, rc = EXIT_FAILURE; + int err; struct perf_header *header; unsigned long head, shift; unsigned long offset = 0; @@ -89,56 +118,69 @@ int mmap_dispatch_perf_file(struct perf_header **pheader, int input; char *buf; - if (!curr_handler) - die("Forgot to register perf file handler"); + if (curr_handler == NULL) { + pr_debug("Forgot to register perf file handler\n"); + return -EINVAL; + } page_size = getpagesize(); input = open(input_name, O_RDONLY); if (input < 0) { - fprintf(stderr, " failed to open file: %s", input_name); + pr_err("Failed to open file: %s", input_name); if (!strcmp(input_name, "perf.data")) - fprintf(stderr, " (try 'perf record' first)"); - fprintf(stderr, "\n"); - exit(-1); + pr_err(" (try 'perf record' first)"); + pr_err("\n"); + return -errno; } - ret = fstat(input, &input_stat); - if (ret < 0) { - perror("failed to stat file"); - exit(-1); + if (fstat(input, &input_stat) < 0) { + pr_err("failed to stat file"); + err = -errno; + goto out_close; } + err = -EACCES; if (!force && input_stat.st_uid && (input_stat.st_uid != geteuid())) { - fprintf(stderr, "file: %s not owned by current user or root\n", + pr_err("file: %s not owned by current user or root\n", input_name); - exit(-1); + goto out_close; } - if (!input_stat.st_size) { - fprintf(stderr, "zero-sized file, nothing to do!\n"); - exit(0); + if (input_stat.st_size == 0) { + pr_info("zero-sized file, nothing to do!\n"); + goto done; } - *pheader = perf_header__read(input); - header = *pheader; + err = -ENOMEM; + header = perf_header__new(); + if (header == NULL) + goto out_close; + + err = perf_header__read(header, input); + if (err < 0) + goto out_delete; + *pheader = header; head = header->data_offset; sample_type = perf_header__sample_type(header); - if (curr_handler->sample_type_check) - if (curr_handler->sample_type_check(sample_type) < 0) - exit(-1); + err = -EINVAL; + if (curr_handler->sample_type_check && + curr_handler->sample_type_check(sample_type) < 0) + goto out_delete; - if (load_kernel() < 0) { - perror("failed to load kernel symbols"); - return EXIT_FAILURE; + err = -ENOMEM; + if (load_kernel(NULL) < 0) { + pr_err("failed to load kernel symbols\n"); + goto out_delete; } if (!full_paths) { if (getcwd(__cwd, sizeof(__cwd)) == NULL) { - perror("failed to get the current directory"); - return EXIT_FAILURE; + pr_err("failed to get the current directory\n"); + err = -errno; + goto out_delete; } *cwd = __cwd; *cwdlen = strlen(*cwd); @@ -152,11 +194,12 @@ int mmap_dispatch_perf_file(struct perf_header **pheader, head -= shift; remap: - buf = (char *)mmap(NULL, page_size * mmap_window, PROT_READ, - MAP_SHARED, input, offset); + buf = mmap(NULL, page_size * mmap_window, PROT_READ, + MAP_SHARED, input, offset); if (buf == MAP_FAILED) { - perror("failed to mmap file"); - exit(-1); + pr_err("failed to mmap file\n"); + err = -errno; + goto out_delete; } more: @@ -213,10 +256,12 @@ more: goto more; done: - rc = EXIT_SUCCESS; + err = 0; +out_close: close(input); - return rc; + return err; +out_delete: + perf_header__delete(header); + goto out_close; } - - diff --git a/tools/perf/util/data_map.h b/tools/perf/util/data_map.h index 716d1053b074..ae036ecd7625 100644 --- a/tools/perf/util/data_map.h +++ b/tools/perf/util/data_map.h @@ -27,5 +27,6 @@ int mmap_dispatch_perf_file(struct perf_header **pheader, int full_paths, int *cwdlen, char **cwd); +int perf_header__read_build_ids(int input, off_t offset, off_t file_size); #endif diff --git a/tools/perf/util/debug.c b/tools/perf/util/debug.c index e8ca98fe0bd4..28d520d5a1fb 100644 --- a/tools/perf/util/debug.c +++ b/tools/perf/util/debug.c @@ -13,12 +13,12 @@ int verbose = 0; int dump_trace = 0; -int eprintf(const char *fmt, ...) +int eprintf(int level, const char *fmt, ...) { va_list args; int ret = 0; - if (verbose) { + if (verbose >= level) { va_start(args, fmt); ret = vfprintf(stderr, fmt, args); va_end(args); diff --git a/tools/perf/util/debug.h b/tools/perf/util/debug.h index 02d1fa1c2465..c6c24c522dea 100644 --- a/tools/perf/util/debug.h +++ b/tools/perf/util/debug.h @@ -2,10 +2,13 @@ #ifndef __PERF_DEBUG_H #define __PERF_DEBUG_H +#include "event.h" + extern int verbose; extern int dump_trace; -int eprintf(const char *fmt, ...) __attribute__((format(printf, 1, 2))); +int eprintf(int level, + const char *fmt, ...) __attribute__((format(printf, 2, 3))); int dump_printf(const char *fmt, ...) __attribute__((format(printf, 1, 2))); void trace_event(event_t *event); diff --git a/tools/perf/util/debugfs.c b/tools/perf/util/debugfs.c new file mode 100644 index 000000000000..06b73ee02c49 --- /dev/null +++ b/tools/perf/util/debugfs.c @@ -0,0 +1,241 @@ +#include "util.h" +#include "debugfs.h" +#include "cache.h" + +static int debugfs_premounted; +static char debugfs_mountpoint[MAX_PATH+1]; + +static const char *debugfs_known_mountpoints[] = { + "/sys/kernel/debug/", + "/debug/", + 0, +}; + +/* use this to force a umount */ +void debugfs_force_cleanup(void) +{ + debugfs_find_mountpoint(); + debugfs_premounted = 0; + debugfs_umount(); +} + +/* construct a full path to a debugfs element */ +int debugfs_make_path(const char *element, char *buffer, int size) +{ + int len; + + if (strlen(debugfs_mountpoint) == 0) { + buffer[0] = '\0'; + return -1; + } + + len = strlen(debugfs_mountpoint) + strlen(element) + 1; + if (len >= size) + return len+1; + + snprintf(buffer, size-1, "%s/%s", debugfs_mountpoint, element); + return 0; +} + +static int debugfs_found; + +/* find the path to the mounted debugfs */ +const char *debugfs_find_mountpoint(void) +{ + const char **ptr; + char type[100]; + FILE *fp; + + if (debugfs_found) + return (const char *) debugfs_mountpoint; + + ptr = debugfs_known_mountpoints; + while (*ptr) { + if (debugfs_valid_mountpoint(*ptr) == 0) { + debugfs_found = 1; + strcpy(debugfs_mountpoint, *ptr); + return debugfs_mountpoint; + } + ptr++; + } + + /* give up and parse /proc/mounts */ + fp = fopen("/proc/mounts", "r"); + if (fp == NULL) + die("Can't open /proc/mounts for read"); + + while (fscanf(fp, "%*s %" + STR(MAX_PATH) + "s %99s %*s %*d %*d\n", + debugfs_mountpoint, type) == 2) { + if (strcmp(type, "debugfs") == 0) + break; + } + fclose(fp); + + if (strcmp(type, "debugfs") != 0) + return NULL; + + debugfs_found = 1; + + return debugfs_mountpoint; +} + +/* verify that a mountpoint is actually a debugfs instance */ + +int debugfs_valid_mountpoint(const char *debugfs) +{ + struct statfs st_fs; + + if (statfs(debugfs, &st_fs) < 0) + return -ENOENT; + else if (st_fs.f_type != (long) DEBUGFS_MAGIC) + return -ENOENT; + + return 0; +} + + +int debugfs_valid_entry(const char *path) +{ + struct stat st; + + if (stat(path, &st)) + return -errno; + + return 0; +} + +/* mount the debugfs somewhere */ + +int debugfs_mount(const char *mountpoint) +{ + char mountcmd[128]; + + /* see if it's already mounted */ + if (debugfs_find_mountpoint()) { + debugfs_premounted = 1; + return 0; + } + + /* if not mounted and no argument */ + if (mountpoint == NULL) { + /* see if environment variable set */ + mountpoint = getenv(PERF_DEBUGFS_ENVIRONMENT); + /* if no environment variable, use default */ + if (mountpoint == NULL) + mountpoint = "/sys/kernel/debug"; + } + + /* save the mountpoint */ + strncpy(debugfs_mountpoint, mountpoint, sizeof(debugfs_mountpoint)); + + /* mount it */ + snprintf(mountcmd, sizeof(mountcmd), + "/bin/mount -t debugfs debugfs %s", mountpoint); + return system(mountcmd); +} + +/* umount the debugfs */ + +int debugfs_umount(void) +{ + char umountcmd[128]; + int ret; + + /* if it was already mounted, leave it */ + if (debugfs_premounted) + return 0; + + /* make sure it's a valid mount point */ + ret = debugfs_valid_mountpoint(debugfs_mountpoint); + if (ret) + return ret; + + snprintf(umountcmd, sizeof(umountcmd), + "/bin/umount %s", debugfs_mountpoint); + return system(umountcmd); +} + +int debugfs_write(const char *entry, const char *value) +{ + char path[MAX_PATH+1]; + int ret, count; + int fd; + + /* construct the path */ + snprintf(path, sizeof(path), "%s/%s", debugfs_mountpoint, entry); + + /* verify that it exists */ + ret = debugfs_valid_entry(path); + if (ret) + return ret; + + /* get how many chars we're going to write */ + count = strlen(value); + + /* open the debugfs entry */ + fd = open(path, O_RDWR); + if (fd < 0) + return -errno; + + while (count > 0) { + /* write it */ + ret = write(fd, value, count); + if (ret <= 0) { + if (ret == EAGAIN) + continue; + close(fd); + return -errno; + } + count -= ret; + } + + /* close it */ + close(fd); + + /* return success */ + return 0; +} + +/* + * read a debugfs entry + * returns the number of chars read or a negative errno + */ +int debugfs_read(const char *entry, char *buffer, size_t size) +{ + char path[MAX_PATH+1]; + int ret; + int fd; + + /* construct the path */ + snprintf(path, sizeof(path), "%s/%s", debugfs_mountpoint, entry); + + /* verify that it exists */ + ret = debugfs_valid_entry(path); + if (ret) + return ret; + + /* open the debugfs entry */ + fd = open(path, O_RDONLY); + if (fd < 0) + return -errno; + + do { + /* read it */ + ret = read(fd, buffer, size); + if (ret == 0) { + close(fd); + return EOF; + } + } while (ret < 0 && errno == EAGAIN); + + /* close it */ + close(fd); + + /* make *sure* there's a null character at the end */ + buffer[ret] = '\0'; + + /* return the number of chars read */ + return ret; +} diff --git a/tools/perf/util/debugfs.h b/tools/perf/util/debugfs.h new file mode 100644 index 000000000000..3cd14f9ae784 --- /dev/null +++ b/tools/perf/util/debugfs.h @@ -0,0 +1,25 @@ +#ifndef __DEBUGFS_H__ +#define __DEBUGFS_H__ + +#include <sys/mount.h> + +#ifndef MAX_PATH +# define MAX_PATH 256 +#endif + +#ifndef STR +# define _STR(x) #x +# define STR(x) _STR(x) +#endif + +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 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); +extern void debugfs_force_cleanup(void); +extern int debugfs_make_path(const char *element, char *buffer, int size); + +#endif /* __DEBUGFS_H__ */ diff --git a/tools/perf/util/event.c b/tools/perf/util/event.c new file mode 100644 index 000000000000..1dae7e3b400d --- /dev/null +++ b/tools/perf/util/event.c @@ -0,0 +1,177 @@ +#include <linux/types.h> +#include "event.h" +#include "debug.h" +#include "string.h" + +static pid_t event__synthesize_comm(pid_t pid, int full, + int (*process)(event_t *event)) +{ + event_t ev; + char filename[PATH_MAX]; + char bf[BUFSIZ]; + FILE *fp; + size_t size = 0; + DIR *tasks; + struct dirent dirent, *next; + pid_t tgid = 0; + + snprintf(filename, sizeof(filename), "/proc/%d/status", pid); + + fp = fopen(filename, "r"); + if (fp == NULL) { +out_race: + /* + * We raced with a task exiting - just return: + */ + pr_debug("couldn't open %s\n", filename); + return 0; + } + + memset(&ev.comm, 0, sizeof(ev.comm)); + while (!ev.comm.comm[0] || !ev.comm.pid) { + if (fgets(bf, sizeof(bf), fp) == NULL) + goto out_failure; + + if (memcmp(bf, "Name:", 5) == 0) { + char *name = bf + 5; + while (*name && isspace(*name)) + ++name; + size = strlen(name) - 1; + memcpy(ev.comm.comm, name, size++); + } else if (memcmp(bf, "Tgid:", 5) == 0) { + char *tgids = bf + 5; + while (*tgids && isspace(*tgids)) + ++tgids; + tgid = ev.comm.pid = atoi(tgids); + } + } + + ev.comm.header.type = PERF_RECORD_COMM; + size = ALIGN(size, sizeof(u64)); + ev.comm.header.size = sizeof(ev.comm) - (sizeof(ev.comm.comm) - size); + + if (!full) { + ev.comm.tid = pid; + + process(&ev); + goto out_fclose; + } + + snprintf(filename, sizeof(filename), "/proc/%d/task", pid); + + tasks = opendir(filename); + if (tasks == NULL) + goto out_race; + + while (!readdir_r(tasks, &dirent, &next) && next) { + char *end; + pid = strtol(dirent.d_name, &end, 10); + if (*end) + continue; + + ev.comm.tid = pid; + + process(&ev); + } + closedir(tasks); + +out_fclose: + fclose(fp); + return tgid; + +out_failure: + pr_warning("couldn't get COMM and pgid, malformed %s\n", filename); + return -1; +} + +static int event__synthesize_mmap_events(pid_t pid, pid_t tgid, + int (*process)(event_t *event)) +{ + char filename[PATH_MAX]; + FILE *fp; + + snprintf(filename, sizeof(filename), "/proc/%d/maps", pid); + + fp = fopen(filename, "r"); + if (fp == NULL) { + /* + * We raced with a task exiting - just return: + */ + pr_debug("couldn't open %s\n", filename); + return -1; + } + + while (1) { + char bf[BUFSIZ], *pbf = bf; + event_t ev = { + .header = { .type = PERF_RECORD_MMAP }, + }; + int n; + size_t size; + if (fgets(bf, sizeof(bf), fp) == NULL) + break; + + /* 00400000-0040c000 r-xp 00000000 fd:01 41038 /bin/cat */ + n = hex2u64(pbf, &ev.mmap.start); + if (n < 0) + continue; + pbf += n + 1; + n = hex2u64(pbf, &ev.mmap.len); + if (n < 0) + continue; + pbf += n + 3; + if (*pbf == 'x') { /* vm_exec */ + char *execname = strchr(bf, '/'); + + /* Catch VDSO */ + if (execname == NULL) + execname = strstr(bf, "[vdso]"); + + if (execname == NULL) + continue; + + size = strlen(execname); + execname[size - 1] = '\0'; /* Remove \n */ + memcpy(ev.mmap.filename, execname, size); + size = ALIGN(size, sizeof(u64)); + ev.mmap.len -= ev.mmap.start; + ev.mmap.header.size = (sizeof(ev.mmap) - + (sizeof(ev.mmap.filename) - size)); + ev.mmap.pid = tgid; + ev.mmap.tid = pid; + + process(&ev); + } + } + + fclose(fp); + return 0; +} + +int event__synthesize_thread(pid_t pid, int (*process)(event_t *event)) +{ + pid_t tgid = event__synthesize_comm(pid, 1, process); + if (tgid == -1) + return -1; + return event__synthesize_mmap_events(pid, tgid, process); +} + +void event__synthesize_threads(int (*process)(event_t *event)) +{ + DIR *proc; + struct dirent dirent, *next; + + proc = opendir("/proc"); + + while (!readdir_r(proc, &dirent, &next) && next) { + char *end; + pid_t pid = strtol(dirent.d_name, &end, 10); + + if (*end) /* only interested in proper numerical dirents */ + continue; + + event__synthesize_thread(pid, process); + } + + closedir(proc); +} diff --git a/tools/perf/util/event.h b/tools/perf/util/event.h index c2e62be62798..34c6fcb82d92 100644 --- a/tools/perf/util/event.h +++ b/tools/perf/util/event.h @@ -61,6 +61,13 @@ struct sample_event{ u64 array[]; }; +#define BUILD_ID_SIZE 20 + +struct build_id_event { + struct perf_event_header header; + u8 build_id[ALIGN(BUILD_ID_SIZE, sizeof(u64))]; + char filename[]; +}; typedef union event_union { struct perf_event_header header; @@ -82,6 +89,7 @@ struct map { u64 end; u64 pgoff; u64 (*map_ip)(struct map *, u64); + u64 (*unmap_ip)(struct map *, u64); struct dso *dso; }; @@ -90,14 +98,29 @@ static inline u64 map__map_ip(struct map *map, u64 ip) return ip - map->start + map->pgoff; } -static inline u64 vdso__map_ip(struct map *map __used, u64 ip) +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, u64 start, u64 end, u64 pgoff, + struct dso *dso); struct map *map__new(struct mmap_event *event, char *cwd, int cwdlen); 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 symbol *map__find_symbol(struct map *self, u64 ip, symbol_filter_t filter); + +int event__synthesize_thread(pid_t pid, int (*process)(event_t *event)); +void event__synthesize_threads(int (*process)(event_t *event)); #endif /* __PERF_RECORD_H */ diff --git a/tools/perf/util/header.c b/tools/perf/util/header.c index 9aae360c0f28..d5c81ebc0a84 100644 --- a/tools/perf/util/header.c +++ b/tools/perf/util/header.c @@ -2,11 +2,15 @@ #include <unistd.h> #include <stdio.h> #include <stdlib.h> +#include <linux/list.h> #include "util.h" #include "header.h" #include "../perf.h" #include "trace-event.h" +#include "symbol.h" +#include "data_map.h" +#include "debug.h" /* * Create new perf.data header attribute: @@ -15,32 +19,43 @@ struct perf_header_attr *perf_header_attr__new(struct perf_event_attr *attr) { struct perf_header_attr *self = malloc(sizeof(*self)); - if (!self) - die("nomem"); - - self->attr = *attr; - self->ids = 0; - self->size = 1; - self->id = malloc(sizeof(u64)); - - if (!self->id) - die("nomem"); + if (self != NULL) { + self->attr = *attr; + self->ids = 0; + self->size = 1; + self->id = malloc(sizeof(u64)); + if (self->id == NULL) { + free(self); + self = NULL; + } + } return self; } -void perf_header_attr__add_id(struct perf_header_attr *self, u64 id) +void perf_header_attr__delete(struct perf_header_attr *self) +{ + free(self->id); + free(self); +} + +int perf_header_attr__add_id(struct perf_header_attr *self, u64 id) { int pos = self->ids; self->ids++; if (self->ids > self->size) { - self->size *= 2; - self->id = realloc(self->id, self->size * sizeof(u64)); - if (!self->id) - die("nomem"); + int nsize = self->size * 2; + u64 *nid = realloc(self->id, nsize * sizeof(u64)); + + if (nid == NULL) + return -1; + + self->size = nsize; + self->id = nid; } self->id[pos] = id; + return 0; } /* @@ -48,44 +63,52 @@ void perf_header_attr__add_id(struct perf_header_attr *self, u64 id) */ struct perf_header *perf_header__new(void) { - struct perf_header *self = malloc(sizeof(*self)); + struct perf_header *self = calloc(sizeof(*self), 1); - if (!self) - die("nomem"); + if (self != NULL) { + self->size = 1; + self->attr = malloc(sizeof(void *)); - self->frozen = 0; + if (self->attr == NULL) { + free(self); + self = NULL; + } + } - self->attrs = 0; - self->size = 1; - self->attr = malloc(sizeof(void *)); + return self; +} - if (!self->attr) - die("nomem"); +void perf_header__delete(struct perf_header *self) +{ + int i; - self->data_offset = 0; - self->data_size = 0; - self->trace_info_offset = 0; - self->trace_info_size = 0; + for (i = 0; i < self->attrs; ++i) + perf_header_attr__delete(self->attr[i]); - return self; + free(self->attr); + free(self); } -void perf_header__add_attr(struct perf_header *self, - struct perf_header_attr *attr) +int perf_header__add_attr(struct perf_header *self, + struct perf_header_attr *attr) { - int pos = self->attrs; - if (self->frozen) - die("frozen"); + return -1; - self->attrs++; - if (self->attrs > self->size) { - self->size *= 2; - self->attr = realloc(self->attr, self->size * sizeof(void *)); - if (!self->attr) - die("nomem"); + if (self->attrs == self->size) { + int nsize = self->size * 2; + struct perf_header_attr **nattr; + + nattr = realloc(self->attr, nsize * sizeof(void *)); + if (nattr == NULL) + return -1; + + self->size = nsize; + self->attr = nattr; } - self->attr[pos] = attr; + + self->attr[self->attrs++] = attr; + return 0; } #define MAX_EVENT_NAME 64 @@ -101,7 +124,7 @@ static struct perf_trace_event_type *events; void perf_header__push_event(u64 id, const char *name) { if (strlen(name) > MAX_EVENT_NAME) - printf("Event %s will be truncated\n", name); + pr_warning("Event %s will be truncated\n", name); if (!events) { events = malloc(sizeof(struct perf_trace_event_type)); @@ -132,52 +155,135 @@ static const char *__perf_magic = "PERFFILE"; #define PERF_MAGIC (*(u64 *)__perf_magic) -struct perf_file_section { - u64 offset; - u64 size; -}; - struct perf_file_attr { struct perf_event_attr attr; struct perf_file_section ids; }; -struct perf_file_header { - u64 magic; - u64 size; - u64 attr_size; - struct perf_file_section attrs; - struct perf_file_section data; - struct perf_file_section event_types; - struct perf_file_section trace_info; -}; - -static int trace_info; +void perf_header__set_feat(struct perf_header *self, int feat) +{ + set_bit(feat, self->adds_features); +} -void perf_header__set_trace_info(void) +bool perf_header__has_feat(const struct perf_header *self, int feat) { - trace_info = 1; + return test_bit(feat, self->adds_features); } -static void do_write(int fd, void *buf, size_t size) +static int do_write(int fd, const void *buf, size_t size) { while (size) { int ret = write(fd, buf, size); if (ret < 0) - die("failed to write"); + return -errno; size -= ret; buf += ret; } + + return 0; +} + +static int dsos__write_buildid_table(int fd) +{ + struct dso *pos; + + list_for_each_entry(pos, &dsos, node) { + int err; + struct build_id_event b; + size_t len; + + if (!pos->has_build_id) + continue; + len = pos->long_name_len + 1; + len = ALIGN(len, 64); + memset(&b, 0, sizeof(b)); + memcpy(&b.build_id, pos->build_id, sizeof(pos->build_id)); + 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, len); + if (err < 0) + return err; + } + + return 0; } -void perf_header__write(struct perf_header *self, int fd) +static int perf_header__adds_write(struct perf_header *self, int fd) +{ + int nr_sections; + struct perf_file_section *feat_sec; + int sec_size; + u64 sec_start; + int idx = 0, err; + + if (dsos__read_build_ids()) + perf_header__set_feat(self, HEADER_BUILD_ID); + + nr_sections = bitmap_weight(self->adds_features, HEADER_FEAT_BITS); + if (!nr_sections) + return 0; + + feat_sec = calloc(sizeof(*feat_sec), nr_sections); + if (feat_sec == NULL) + return -ENOMEM; + + sec_size = sizeof(*feat_sec) * nr_sections; + + sec_start = self->data_offset + self->data_size; + lseek(fd, sec_start + sec_size, SEEK_SET); + + if (perf_header__has_feat(self, HEADER_TRACE_INFO)) { + struct perf_file_section *trace_sec; + + trace_sec = &feat_sec[idx++]; + + /* Write trace info */ + trace_sec->offset = lseek(fd, 0, SEEK_CUR); + read_tracing_data(fd, attrs, nr_counters); + trace_sec->size = lseek(fd, 0, SEEK_CUR) - trace_sec->offset; + } + + + if (perf_header__has_feat(self, HEADER_BUILD_ID)) { + struct perf_file_section *buildid_sec; + + buildid_sec = &feat_sec[idx++]; + + dsos__load_kernel(); + /* + * Read the list of loaded modules with its build_ids + */ + dsos__load_modules(); + + /* Write build-ids */ + buildid_sec->offset = lseek(fd, 0, SEEK_CUR); + err = dsos__write_buildid_table(fd); + if (err < 0) { + pr_debug("failed to write buildid table\n"); + goto out_free; + } + buildid_sec->size = lseek(fd, 0, SEEK_CUR) - buildid_sec->offset; + } + + lseek(fd, sec_start, SEEK_SET); + err = do_write(fd, feat_sec, sec_size); + if (err < 0) + pr_debug("failed to write feature section\n"); +out_free: + free(feat_sec); + return err; +} + +int perf_header__write(struct perf_header *self, int fd, bool at_exit) { struct perf_file_header f_header; struct perf_file_attr f_attr; struct perf_header_attr *attr; - int i; + int i, err; lseek(fd, sizeof(f_header), SEEK_SET); @@ -186,7 +292,11 @@ void perf_header__write(struct perf_header *self, int fd) attr = self->attr[i]; attr->id_offset = lseek(fd, 0, SEEK_CUR); - do_write(fd, attr->id, attr->ids * sizeof(u64)); + err = do_write(fd, attr->id, attr->ids * sizeof(u64)); + if (err < 0) { + pr_debug("failed to write perf header\n"); + return err; + } } @@ -202,34 +312,31 @@ void perf_header__write(struct perf_header *self, int fd) .size = attr->ids * sizeof(u64), } }; - do_write(fd, &f_attr, sizeof(f_attr)); + err = do_write(fd, &f_attr, sizeof(f_attr)); + if (err < 0) { + pr_debug("failed to write perf header attribute\n"); + return err; + } } self->event_offset = lseek(fd, 0, SEEK_CUR); self->event_size = event_count * sizeof(struct perf_trace_event_type); - if (events) - do_write(fd, events, self->event_size); - - if (trace_info) { - static int trace_info_written; - - /* - * Write it only once - */ - if (!trace_info_written) { - self->trace_info_offset = lseek(fd, 0, SEEK_CUR); - read_tracing_data(fd, attrs, nr_counters); - self->trace_info_size = lseek(fd, 0, SEEK_CUR) - - self->trace_info_offset; - trace_info_written = 1; - } else { - lseek(fd, self->trace_info_offset + - self->trace_info_size, SEEK_SET); + if (events) { + err = do_write(fd, events, self->event_size); + if (err < 0) { + pr_debug("failed to write perf header events\n"); + return err; } } self->data_offset = lseek(fd, 0, SEEK_CUR); + if (at_exit) { + err = perf_header__adds_write(self, fd); + if (err < 0) + return err; + } + f_header = (struct perf_file_header){ .magic = PERF_MAGIC, .size = sizeof(f_header), @@ -246,17 +353,20 @@ void perf_header__write(struct perf_header *self, int fd) .offset = self->event_offset, .size = self->event_size, }, - .trace_info = { - .offset = self->trace_info_offset, - .size = self->trace_info_size, - }, }; + memcpy(&f_header.adds_features, &self->adds_features, sizeof(self->adds_features)); + lseek(fd, 0, SEEK_SET); - do_write(fd, &f_header, sizeof(f_header)); + err = do_write(fd, &f_header, sizeof(f_header)); + if (err < 0) { + pr_debug("failed to write perf header\n"); + return err; + } lseek(fd, self->data_offset + self->data_size, SEEK_SET); self->frozen = 1; + return 0; } static void do_read(int fd, void *buf, size_t size) @@ -274,29 +384,110 @@ static void do_read(int fd, void *buf, size_t size) } } -struct perf_header *perf_header__read(int fd) +int perf_header__process_sections(struct perf_header *self, int fd, + int (*process)(struct perf_file_section *self, + int feat, int fd)) { - struct perf_header *self = perf_header__new(); - struct perf_file_header f_header; - struct perf_file_attr f_attr; - u64 f_id; + struct perf_file_section *feat_sec; + int nr_sections; + int sec_size; + int idx = 0; + int err = 0, feat = 1; - int nr_attrs, nr_ids, i, j; + nr_sections = bitmap_weight(self->adds_features, HEADER_FEAT_BITS); + if (!nr_sections) + return 0; + + feat_sec = calloc(sizeof(*feat_sec), nr_sections); + if (!feat_sec) + return -1; + + sec_size = sizeof(*feat_sec) * nr_sections; + lseek(fd, self->data_offset + self->data_size, SEEK_SET); + + do_read(fd, feat_sec, sec_size); + + 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); + if (err < 0) + break; + } + ++feat; + } + + 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, &f_header, sizeof(f_header)); + do_read(fd, self, sizeof(*self)); - if (f_header.magic != PERF_MAGIC || - f_header.attr_size != sizeof(f_attr)) - die("incompatible file format"); + if (self->magic != PERF_MAGIC || + self->attr_size != sizeof(struct perf_file_attr)) + return -1; - if (f_header.size != sizeof(f_header)) { + if (self->size != sizeof(*self)) { /* Support the previous format */ - if (f_header.size == offsetof(typeof(f_header), trace_info)) - f_header.trace_info.size = 0; + if (self->size == offsetof(typeof(*self), adds_features)) + bitmap_zero(self->adds_features, HEADER_FEAT_BITS); else - die("incompatible file format"); + return -1; } + + memcpy(&ph->adds_features, &self->adds_features, + sizeof(self->adds_features)); + + ph->event_offset = self->event_types.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, + int feat, int fd) +{ + if (lseek(fd, self->offset, SEEK_SET) < 0) { + pr_debug("Failed to lseek to %Ld offset for feature %d, " + "continuing...\n", self->offset, feat); + return 0; + } + + switch (feat) { + case HEADER_TRACE_INFO: + trace_report(fd); + break; + + case HEADER_BUILD_ID: + if (perf_header__read_build_ids(fd, self->offset, self->size)) + pr_debug("Failed to read buildids, continuing...\n"); + break; + default: + pr_debug("unknown feature %d, continuing...\n", feat); + } + + return 0; +} + +int perf_header__read(struct perf_header *self, int fd) +{ + struct perf_file_header f_header; + struct perf_file_attr f_attr; + u64 f_id; + int nr_attrs, nr_ids, i, j; + + if (perf_file_header__read(&f_header, self, fd) < 0) { + pr_debug("incompatible file format\n"); + return -EINVAL; + } + nr_attrs = f_header.attrs.size / sizeof(f_attr); lseek(fd, f_header.attrs.offset, SEEK_SET); @@ -308,6 +499,8 @@ struct perf_header *perf_header__read(int fd) tmp = lseek(fd, 0, SEEK_CUR); attr = perf_header_attr__new(&f_attr.attr); + if (attr == NULL) + return -ENOMEM; nr_ids = f_attr.ids.size / sizeof(u64); lseek(fd, f_attr.ids.offset, SEEK_SET); @@ -315,40 +508,34 @@ struct perf_header *perf_header__read(int fd) for (j = 0; j < nr_ids; j++) { do_read(fd, &f_id, sizeof(f_id)); - perf_header_attr__add_id(attr, f_id); + if (perf_header_attr__add_id(attr, f_id) < 0) { + perf_header_attr__delete(attr); + return -ENOMEM; + } + } + if (perf_header__add_attr(self, attr) < 0) { + perf_header_attr__delete(attr); + return -ENOMEM; } - perf_header__add_attr(self, attr); + lseek(fd, tmp, SEEK_SET); } if (f_header.event_types.size) { lseek(fd, f_header.event_types.offset, SEEK_SET); events = malloc(f_header.event_types.size); - if (!events) - die("nomem"); + if (events == NULL) + return -ENOMEM; do_read(fd, events, f_header.event_types.size); event_count = f_header.event_types.size / sizeof(struct perf_trace_event_type); } - self->trace_info_offset = f_header.trace_info.offset; - self->trace_info_size = f_header.trace_info.size; - - if (self->trace_info_size) { - lseek(fd, self->trace_info_offset, SEEK_SET); - trace_report(fd); - } - - self->event_offset = f_header.event_types.offset; - self->event_size = f_header.event_types.size; - - self->data_offset = f_header.data.offset; - self->data_size = f_header.data.size; + perf_header__process_sections(self, fd, perf_file_section__process); lseek(fd, self->data_offset, SEEK_SET); self->frozen = 1; - - return self; + return 0; } u64 perf_header__sample_type(struct perf_header *header) diff --git a/tools/perf/util/header.h b/tools/perf/util/header.h index 30aee5160dc0..d1dbe2b79c42 100644 --- a/tools/perf/util/header.h +++ b/tools/perf/util/header.h @@ -3,8 +3,11 @@ #include "../../../include/linux/perf_event.h" #include <sys/types.h> +#include <stdbool.h> #include "types.h" +#include <linux/bitmap.h> + struct perf_header_attr { struct perf_event_attr attr; int ids, size; @@ -12,38 +15,71 @@ struct perf_header_attr { off_t id_offset; }; +enum { + HEADER_TRACE_INFO = 1, + HEADER_BUILD_ID, + HEADER_LAST_FEATURE, +}; + +#define HEADER_FEAT_BITS 256 + +struct perf_file_section { + u64 offset; + u64 size; +}; + +struct perf_file_header { + u64 magic; + u64 size; + u64 attr_size; + struct perf_file_section attrs; + struct perf_file_section data; + struct perf_file_section event_types; + DECLARE_BITMAP(adds_features, HEADER_FEAT_BITS); +}; + +struct perf_header; + +int perf_file_header__read(struct perf_file_header *self, + struct perf_header *ph, int fd); + struct perf_header { - int frozen; - int attrs, size; + int frozen; + int attrs, size; struct perf_header_attr **attr; - s64 attr_offset; - u64 data_offset; - u64 data_size; - u64 event_offset; - u64 event_size; - u64 trace_info_offset; - u64 trace_info_size; + s64 attr_offset; + u64 data_offset; + u64 data_size; + u64 event_offset; + u64 event_size; + DECLARE_BITMAP(adds_features, HEADER_FEAT_BITS); }; -struct perf_header *perf_header__read(int fd); -void perf_header__write(struct perf_header *self, int fd); +struct perf_header *perf_header__new(void); +void perf_header__delete(struct perf_header *self); + +int perf_header__read(struct perf_header *self, int fd); +int perf_header__write(struct perf_header *self, int fd, bool at_exit); -void perf_header__add_attr(struct perf_header *self, - struct perf_header_attr *attr); +int perf_header__add_attr(struct perf_header *self, + struct perf_header_attr *attr); void 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); +void perf_header_attr__delete(struct perf_header_attr *self); -struct perf_header_attr * -perf_header_attr__new(struct perf_event_attr *attr); -void perf_header_attr__add_id(struct perf_header_attr *self, u64 id); +int perf_header_attr__add_id(struct perf_header_attr *self, u64 id); u64 perf_header__sample_type(struct perf_header *header); struct perf_event_attr * perf_header__find_attr(u64 id, struct perf_header *header); -void perf_header__set_trace_info(void); +void perf_header__set_feat(struct perf_header *self, int feat); +bool perf_header__has_feat(const struct perf_header *self, int feat); -struct perf_header *perf_header__new(void); +int perf_header__process_sections(struct perf_header *self, int fd, + int (*process)(struct perf_file_section *self, + int feat, int fd)); #endif /* __PERF_HEADER_H */ diff --git a/tools/perf/util/include/asm/asm-offsets.h b/tools/perf/util/include/asm/asm-offsets.h new file mode 100644 index 000000000000..ed538942523d --- /dev/null +++ b/tools/perf/util/include/asm/asm-offsets.h @@ -0,0 +1 @@ +/* stub */ diff --git a/tools/perf/util/include/asm/bitops.h b/tools/perf/util/include/asm/bitops.h new file mode 100644 index 000000000000..58e9817ffae0 --- /dev/null +++ b/tools/perf/util/include/asm/bitops.h @@ -0,0 +1,18 @@ +#ifndef _PERF_ASM_BITOPS_H_ +#define _PERF_ASM_BITOPS_H_ + +#include <sys/types.h> +#include "../../types.h" +#include <linux/compiler.h> + +/* CHECKME: Not sure both always match */ +#define BITS_PER_LONG __WORDSIZE + +#include "../../../../include/asm-generic/bitops/__fls.h" +#include "../../../../include/asm-generic/bitops/fls.h" +#include "../../../../include/asm-generic/bitops/fls64.h" +#include "../../../../include/asm-generic/bitops/__ffs.h" +#include "../../../../include/asm-generic/bitops/ffz.h" +#include "../../../../include/asm-generic/bitops/hweight.h" + +#endif diff --git a/tools/perf/util/include/asm/byteorder.h b/tools/perf/util/include/asm/byteorder.h new file mode 100644 index 000000000000..b722abe3a626 --- /dev/null +++ b/tools/perf/util/include/asm/byteorder.h @@ -0,0 +1,2 @@ +#include <asm/types.h> +#include "../../../../include/linux/swab.h" diff --git a/tools/perf/util/include/asm/swab.h b/tools/perf/util/include/asm/swab.h new file mode 100644 index 000000000000..ed538942523d --- /dev/null +++ b/tools/perf/util/include/asm/swab.h @@ -0,0 +1 @@ +/* stub */ diff --git a/tools/perf/util/include/asm/uaccess.h b/tools/perf/util/include/asm/uaccess.h new file mode 100644 index 000000000000..d0f72b8fcc35 --- /dev/null +++ b/tools/perf/util/include/asm/uaccess.h @@ -0,0 +1,14 @@ +#ifndef _PERF_ASM_UACCESS_H_ +#define _PERF_ASM_UACCESS_H_ + +#define __get_user(src, dest) \ +({ \ + (src) = *dest; \ + 0; \ +}) + +#define get_user __get_user + +#define access_ok(type, addr, size) 1 + +#endif diff --git a/tools/perf/util/include/linux/bitmap.h b/tools/perf/util/include/linux/bitmap.h new file mode 100644 index 000000000000..94507639a8c4 --- /dev/null +++ b/tools/perf/util/include/linux/bitmap.h @@ -0,0 +1,3 @@ +#include "../../../../include/linux/bitmap.h" +#include "../../../../include/asm-generic/bitops/find.h" +#include <linux/errno.h> diff --git a/tools/perf/util/include/linux/bitops.h b/tools/perf/util/include/linux/bitops.h new file mode 100644 index 000000000000..ace57c36d1d0 --- /dev/null +++ b/tools/perf/util/include/linux/bitops.h @@ -0,0 +1,27 @@ +#ifndef _PERF_LINUX_BITOPS_H_ +#define _PERF_LINUX_BITOPS_H_ + +#define __KERNEL__ + +#define CONFIG_GENERIC_FIND_NEXT_BIT +#define CONFIG_GENERIC_FIND_FIRST_BIT +#include "../../../../include/linux/bitops.h" + +static inline void set_bit(int nr, unsigned long *addr) +{ + addr[nr / BITS_PER_LONG] |= 1UL << (nr % BITS_PER_LONG); +} + +static __always_inline int test_bit(unsigned int nr, const unsigned long *addr) +{ + return ((1UL << (nr % BITS_PER_LONG)) & + (((unsigned long *)addr)[nr / BITS_PER_LONG])) != 0; +} + +unsigned long generic_find_next_zero_le_bit(const unsigned long *addr, unsigned + long size, unsigned long offset); + +unsigned long generic_find_next_le_bit(const unsigned long *addr, unsigned + long size, unsigned long offset); + +#endif diff --git a/tools/perf/util/include/linux/compiler.h b/tools/perf/util/include/linux/compiler.h new file mode 100644 index 000000000000..dfb0713ed47f --- /dev/null +++ b/tools/perf/util/include/linux/compiler.h @@ -0,0 +1,10 @@ +#ifndef _PERF_LINUX_COMPILER_H_ +#define _PERF_LINUX_COMPILER_H_ + +#ifndef __always_inline +#define __always_inline inline +#endif +#define __user +#define __attribute_const__ + +#endif diff --git a/tools/perf/util/include/linux/ctype.h b/tools/perf/util/include/linux/ctype.h new file mode 100644 index 000000000000..a53d4ee1e0b7 --- /dev/null +++ b/tools/perf/util/include/linux/ctype.h @@ -0,0 +1 @@ +#include "../util.h" diff --git a/tools/perf/util/include/linux/kernel.h b/tools/perf/util/include/linux/kernel.h index a6b87390cb52..21c0274c02fa 100644 --- a/tools/perf/util/include/linux/kernel.h +++ b/tools/perf/util/include/linux/kernel.h @@ -1,6 +1,16 @@ #ifndef PERF_LINUX_KERNEL_H_ #define PERF_LINUX_KERNEL_H_ +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> + +#define DIV_ROUND_UP(n,d) (((n) + (d) - 1) / (d)) + +#define ALIGN(x,a) __ALIGN_MASK(x,(typeof(x))(a)-1) +#define __ALIGN_MASK(x,mask) (((x)+(mask))&~(mask)) + #ifndef offsetof #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) #endif @@ -26,4 +36,70 @@ _max1 > _max2 ? _max1 : _max2; }) #endif +#ifndef min +#define min(x, y) ({ \ + typeof(x) _min1 = (x); \ + typeof(y) _min2 = (y); \ + (void) (&_min1 == &_min2); \ + _min1 < _min2 ? _min1 : _min2; }) +#endif + +#ifndef BUG_ON +#define BUG_ON(cond) assert(!(cond)) +#endif + +/* + * Both need more care to handle endianness + * (Don't use bitmap_copy_le() for now) + */ +#define cpu_to_le64(x) (x) +#define cpu_to_le32(x) (x) + +static inline int +vscnprintf(char *buf, size_t size, const char *fmt, va_list args) +{ + int i; + ssize_t ssize = size; + + i = vsnprintf(buf, size, fmt, args); + + return (i >= ssize) ? (ssize - 1) : i; +} + +static inline int scnprintf(char * buf, size_t size, const char * fmt, ...) +{ + va_list args; + ssize_t ssize = size; + int i; + + va_start(args, fmt); + i = vsnprintf(buf, size, fmt, args); + va_end(args); + + return (i >= ssize) ? (ssize - 1) : i; +} + +static inline unsigned long +simple_strtoul(const char *nptr, char **endptr, int base) +{ + return strtoul(nptr, endptr, base); +} + +#ifndef pr_fmt +#define pr_fmt(fmt) fmt +#endif + +#define pr_err(fmt, ...) \ + do { fprintf(stderr, pr_fmt(fmt), ##__VA_ARGS__); } while (0) +#define pr_warning(fmt, ...) \ + do { fprintf(stderr, pr_fmt(fmt), ##__VA_ARGS__); } while (0) +#define pr_info(fmt, ...) \ + do { fprintf(stderr, pr_fmt(fmt), ##__VA_ARGS__); } while (0) +#define pr_debug(fmt, ...) \ + eprintf(1, pr_fmt(fmt), ##__VA_ARGS__) +#define pr_debugN(n, fmt, ...) \ + eprintf(n, pr_fmt(fmt), ##__VA_ARGS__) +#define pr_debug2(fmt, ...) pr_debugN(2, pr_fmt(fmt), ##__VA_ARGS__) +#define pr_debug3(fmt, ...) pr_debugN(3, pr_fmt(fmt), ##__VA_ARGS__) + #endif diff --git a/tools/perf/util/include/linux/string.h b/tools/perf/util/include/linux/string.h new file mode 100644 index 000000000000..3b2f5900276f --- /dev/null +++ b/tools/perf/util/include/linux/string.h @@ -0,0 +1 @@ +#include <string.h> diff --git a/tools/perf/util/include/linux/types.h b/tools/perf/util/include/linux/types.h new file mode 100644 index 000000000000..196862a81a21 --- /dev/null +++ b/tools/perf/util/include/linux/types.h @@ -0,0 +1,9 @@ +#ifndef _PERF_LINUX_TYPES_H_ +#define _PERF_LINUX_TYPES_H_ + +#include <asm/types.h> + +#define DECLARE_BITMAP(name,bits) \ + unsigned long name[BITS_TO_LONGS(bits)] + +#endif diff --git a/tools/perf/util/map.c b/tools/perf/util/map.c index 804e02382739..94ca95073c40 100644 --- a/tools/perf/util/map.c +++ b/tools/perf/util/map.c @@ -3,6 +3,7 @@ #include <stdlib.h> #include <string.h> #include <stdio.h> +#include "debug.h" static inline int is_anon_memory(const char *filename) { @@ -19,13 +20,26 @@ static int strcommon(const char *pathname, char *cwd, int cwdlen) return n; } - struct map *map__new(struct mmap_event *event, char *cwd, int cwdlen) +void map__init(struct map *self, u64 start, u64 end, u64 pgoff, + struct dso *dso) +{ + self->start = start; + self->end = end; + self->pgoff = pgoff; + self->dso = dso; + self->map_ip = map__map_ip; + self->unmap_ip = map__unmap_ip; + RB_CLEAR_NODE(&self->rb_node); +} + +struct map *map__new(struct mmap_event *event, char *cwd, int cwdlen) { struct map *self = malloc(sizeof(*self)); if (self != NULL) { const char *filename = event->filename; char newfilename[PATH_MAX]; + struct dso *dso; int anon; if (cwd) { @@ -45,18 +59,15 @@ static int strcommon(const char *pathname, char *cwd, int cwdlen) filename = newfilename; } - self->start = event->start; - self->end = event->start + event->len; - self->pgoff = event->pgoff; - - self->dso = dsos__findnew(filename); - if (self->dso == NULL) + dso = dsos__findnew(filename); + if (dso == NULL) goto out_delete; + map__init(self, event->start, event->start + event->len, + event->pgoff, dso); + if (self->dso == vdso || anon) - self->map_ip = vdso__map_ip; - else - self->map_ip = map__map_ip; + self->map_ip = self->unmap_ip = identity__map_ip; } return self; out_delete: @@ -64,6 +75,47 @@ out_delete: return NULL; } +#define DSO__DELETED "(deleted)" + +struct symbol * +map__find_symbol(struct map *self, u64 ip, symbol_filter_t filter) +{ + if (!self->dso->loaded) { + int nr = dso__load(self->dso, self, filter); + + if (nr < 0) { + if (self->dso->has_build_id) { + char sbuild_id[BUILD_ID_SIZE * 2 + 1]; + + build_id__sprintf(self->dso->build_id, + sizeof(self->dso->build_id), + sbuild_id); + pr_warning("%s with build id %s not found", + self->dso->long_name, sbuild_id); + } else + pr_warning("Failed to open %s", + self->dso->long_name); + pr_warning(", continuing without symbols\n"); + return NULL; + } else if (nr == 0) { + const char *name = self->dso->long_name; + const size_t len = strlen(name); + const size_t real_len = len - sizeof(DSO__DELETED); + + if (len > sizeof(DSO__DELETED) && + strcmp(name + real_len + 1, DSO__DELETED) == 0) { + pr_warning("%.*s was updated, restart the long running apps that use it!\n", + (int)real_len, name); + } else { + pr_warning("no symbols found in %s, maybe install a debug package?\n", name); + } + return NULL; + } + } + + return self->dso->find_symbol(self->dso, ip); +} + struct map *map__clone(struct map *self) { struct map *map = malloc(sizeof(*self)); diff --git a/tools/perf/util/parse-events.c b/tools/perf/util/parse-events.c index b097570e9623..0faf4f2bb5ca 100644 --- a/tools/perf/util/parse-events.c +++ b/tools/perf/util/parse-events.c @@ -7,6 +7,7 @@ #include "string.h" #include "cache.h" #include "header.h" +#include "debugfs.h" int nr_counters; @@ -47,6 +48,8 @@ static struct event_symbol event_symbols[] = { { CSW(PAGE_FAULTS_MAJ), "major-faults", "" }, { CSW(CONTEXT_SWITCHES), "context-switches", "cs" }, { CSW(CPU_MIGRATIONS), "cpu-migrations", "migrations" }, + { CSW(ALIGNMENT_FAULTS), "alignment-faults", "" }, + { CSW(EMULATION_FAULTS), "emulation-faults", "" }, }; #define __PERF_EVENT_FIELD(config, name) \ @@ -75,6 +78,8 @@ static const char *sw_event_names[] = { "CPU-migrations", "minor-faults", "major-faults", + "alignment-faults", + "emulation-faults", }; #define MAX_ALIASES 8 @@ -149,16 +154,6 @@ static int tp_event_has_id(struct dirent *sys_dir, struct dirent *evt_dir) #define MAX_EVENT_LENGTH 512 -int valid_debugfs_mount(const char *debugfs) -{ - struct statfs st_fs; - - if (statfs(debugfs, &st_fs) < 0) - return -ENOENT; - else if (st_fs.f_type != (long) DEBUGFS_MAGIC) - return -ENOENT; - return 0; -} struct tracepoint_path *tracepoint_id_to_path(u64 config) { @@ -171,7 +166,7 @@ struct tracepoint_path *tracepoint_id_to_path(u64 config) char evt_path[MAXPATHLEN]; char dir_path[MAXPATHLEN]; - if (valid_debugfs_mount(debugfs_path)) + if (debugfs_valid_mountpoint(debugfs_path)) return NULL; sys_dir = opendir(debugfs_path); @@ -510,7 +505,7 @@ static enum event_result parse_tracepoint_event(const char **strp, char sys_name[MAX_EVENT_LENGTH]; unsigned int sys_length, evt_length; - if (valid_debugfs_mount(debugfs_path)) + if (debugfs_valid_mountpoint(debugfs_path)) return 0; evt_name = strchr(*strp, ':'); @@ -678,6 +673,8 @@ parse_event_symbols(const char **str, struct perf_event_attr *attr) if (ret != EVT_FAILED) goto modifier; + fprintf(stderr, "invalid or unsupported event: '%s'\n", *str); + fprintf(stderr, "Run 'perf list' for a list of valid events\n"); return EVT_FAILED; modifier: @@ -786,7 +783,7 @@ static void print_tracepoint_events(void) char evt_path[MAXPATHLEN]; char dir_path[MAXPATHLEN]; - if (valid_debugfs_mount(debugfs_path)) + if (debugfs_valid_mountpoint(debugfs_path)) return; sys_dir = opendir(debugfs_path); @@ -804,7 +801,7 @@ static void print_tracepoint_events(void) for_each_event(sys_dirent, evt_dir, evt_dirent, evt_next) { snprintf(evt_path, MAXPATHLEN, "%s:%s", sys_dirent.d_name, evt_dirent.d_name); - fprintf(stderr, " %-42s [%s]\n", evt_path, + printf(" %-42s [%s]\n", evt_path, event_type_descriptors[PERF_TYPE_TRACEPOINT+1]); } closedir(evt_dir); @@ -821,8 +818,8 @@ void print_events(void) unsigned int i, type, op, prev_type = -1; char name[40]; - fprintf(stderr, "\n"); - fprintf(stderr, "List of pre-defined events (to be used in -e):\n"); + printf("\n"); + 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; @@ -830,19 +827,19 @@ void print_events(void) type = 0; if (type != prev_type) - fprintf(stderr, "\n"); + printf("\n"); if (strlen(syms->alias)) sprintf(name, "%s OR %s", syms->symbol, syms->alias); else strcpy(name, syms->symbol); - fprintf(stderr, " %-42s [%s]\n", name, + printf(" %-42s [%s]\n", name, event_type_descriptors[type]); prev_type = type; } - fprintf(stderr, "\n"); + printf("\n"); for (type = 0; type < PERF_COUNT_HW_CACHE_MAX; type++) { for (op = 0; op < PERF_COUNT_HW_CACHE_OP_MAX; op++) { /* skip invalid cache type */ @@ -850,17 +847,17 @@ void print_events(void) continue; for (i = 0; i < PERF_COUNT_HW_CACHE_RESULT_MAX; i++) { - fprintf(stderr, " %-42s [%s]\n", + printf(" %-42s [%s]\n", event_cache_name(type, op, i), event_type_descriptors[4]); } } } - fprintf(stderr, "\n"); - fprintf(stderr, " %-42s [raw hardware event descriptor]\n", + printf("\n"); + printf(" %-42s [raw hardware event descriptor]\n", "rNNN"); - fprintf(stderr, "\n"); + printf("\n"); print_tracepoint_events(); diff --git a/tools/perf/util/probe-finder.c b/tools/perf/util/probe-finder.c new file mode 100644 index 000000000000..293cdfc1b8ca --- /dev/null +++ b/tools/perf/util/probe-finder.c @@ -0,0 +1,732 @@ +/* + * probe-finder.c : C expression to kprobe event converter + * + * Written by Masami Hiramatsu <mhiramat@redhat.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include <sys/utsname.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <errno.h> +#include <stdio.h> +#include <unistd.h> +#include <getopt.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <ctype.h> + +#include "event.h" +#include "debug.h" +#include "util.h" +#include "probe-finder.h" + + +/* Dwarf_Die Linkage to parent Die */ +struct die_link { + struct die_link *parent; /* Parent die */ + Dwarf_Die die; /* Current die */ +}; + +static Dwarf_Debug __dw_debug; +static Dwarf_Error __dw_error; + +/* + * Generic dwarf analysis helpers + */ + +#define X86_32_MAX_REGS 8 +const char *x86_32_regs_table[X86_32_MAX_REGS] = { + "%ax", + "%cx", + "%dx", + "%bx", + "$stack", /* Stack address instead of %sp */ + "%bp", + "%si", + "%di", +}; + +#define X86_64_MAX_REGS 16 +const char *x86_64_regs_table[X86_64_MAX_REGS] = { + "%ax", + "%dx", + "%cx", + "%bx", + "%si", + "%di", + "%bp", + "%sp", + "%r8", + "%r9", + "%r10", + "%r11", + "%r12", + "%r13", + "%r14", + "%r15", +}; + +/* TODO: switching by dwarf address size */ +#ifdef __x86_64__ +#define ARCH_MAX_REGS X86_64_MAX_REGS +#define arch_regs_table x86_64_regs_table +#else +#define ARCH_MAX_REGS X86_32_MAX_REGS +#define arch_regs_table x86_32_regs_table +#endif + +/* Return architecture dependent register string (for kprobe-tracer) */ +static const char *get_arch_regstr(unsigned int n) +{ + return (n <= ARCH_MAX_REGS) ? arch_regs_table[n] : NULL; +} + +/* + * Compare the tail of two strings. + * Return 0 if whole of either string is same as another's tail part. + */ +static int strtailcmp(const char *s1, const char *s2) +{ + int i1 = strlen(s1); + int i2 = strlen(s2); + while (--i1 > 0 && --i2 > 0) { + if (s1[i1] != s2[i2]) + return s1[i1] - s2[i2]; + } + return 0; +} + +/* Find the fileno of the target file. */ +static Dwarf_Unsigned cu_find_fileno(Dwarf_Die cu_die, const char *fname) +{ + Dwarf_Signed cnt, i; + Dwarf_Unsigned found = 0; + char **srcs; + int ret; + + if (!fname) + return 0; + + ret = dwarf_srcfiles(cu_die, &srcs, &cnt, &__dw_error); + if (ret == DW_DLV_OK) { + for (i = 0; i < cnt && !found; i++) { + if (strtailcmp(srcs[i], fname) == 0) + found = i + 1; + dwarf_dealloc(__dw_debug, srcs[i], DW_DLA_STRING); + } + for (; i < cnt; i++) + dwarf_dealloc(__dw_debug, srcs[i], DW_DLA_STRING); + dwarf_dealloc(__dw_debug, srcs, DW_DLA_LIST); + } + if (found) + pr_debug("found fno: %d\n", (int)found); + return found; +} + +/* Compare diename and tname */ +static int die_compare_name(Dwarf_Die dw_die, const char *tname) +{ + char *name; + int ret; + ret = dwarf_diename(dw_die, &name, &__dw_error); + DIE_IF(ret == DW_DLV_ERROR); + if (ret == DW_DLV_OK) { + ret = strcmp(tname, name); + dwarf_dealloc(__dw_debug, name, DW_DLA_STRING); + } else + ret = -1; + return ret; +} + +/* Check the address is in the subprogram(function). */ +static int die_within_subprogram(Dwarf_Die sp_die, Dwarf_Addr addr, + Dwarf_Signed *offs) +{ + Dwarf_Addr lopc, hipc; + int ret; + + /* TODO: check ranges */ + ret = dwarf_lowpc(sp_die, &lopc, &__dw_error); + DIE_IF(ret == DW_DLV_ERROR); + if (ret == DW_DLV_NO_ENTRY) + return 0; + ret = dwarf_highpc(sp_die, &hipc, &__dw_error); + DIE_IF(ret != DW_DLV_OK); + if (lopc <= addr && addr < hipc) { + *offs = addr - lopc; + return 1; + } else + return 0; +} + +/* Check the die is inlined function */ +static Dwarf_Bool die_inlined_subprogram(Dwarf_Die dw_die) +{ + /* TODO: check strictly */ + Dwarf_Bool inl; + int ret; + + ret = dwarf_hasattr(dw_die, DW_AT_inline, &inl, &__dw_error); + DIE_IF(ret == DW_DLV_ERROR); + return inl; +} + +/* Get the offset of abstruct_origin */ +static Dwarf_Off die_get_abstract_origin(Dwarf_Die dw_die) +{ + Dwarf_Attribute attr; + Dwarf_Off cu_offs; + int ret; + + ret = dwarf_attr(dw_die, DW_AT_abstract_origin, &attr, &__dw_error); + DIE_IF(ret != DW_DLV_OK); + ret = dwarf_formref(attr, &cu_offs, &__dw_error); + DIE_IF(ret != DW_DLV_OK); + dwarf_dealloc(__dw_debug, attr, DW_DLA_ATTR); + return cu_offs; +} + +/* Get entry pc(or low pc, 1st entry of ranges) of the die */ +static Dwarf_Addr die_get_entrypc(Dwarf_Die dw_die) +{ + Dwarf_Attribute attr; + Dwarf_Addr addr; + Dwarf_Off offs; + Dwarf_Ranges *ranges; + Dwarf_Signed cnt; + int ret; + + /* Try to get entry pc */ + ret = dwarf_attr(dw_die, DW_AT_entry_pc, &attr, &__dw_error); + DIE_IF(ret == DW_DLV_ERROR); + if (ret == DW_DLV_OK) { + ret = dwarf_formaddr(attr, &addr, &__dw_error); + DIE_IF(ret != DW_DLV_OK); + dwarf_dealloc(__dw_debug, attr, DW_DLA_ATTR); + return addr; + } + + /* Try to get low pc */ + ret = dwarf_lowpc(dw_die, &addr, &__dw_error); + DIE_IF(ret == DW_DLV_ERROR); + if (ret == DW_DLV_OK) + return addr; + + /* Try to get ranges */ + ret = dwarf_attr(dw_die, DW_AT_ranges, &attr, &__dw_error); + DIE_IF(ret != DW_DLV_OK); + ret = dwarf_formref(attr, &offs, &__dw_error); + DIE_IF(ret != DW_DLV_OK); + ret = dwarf_get_ranges(__dw_debug, offs, &ranges, &cnt, NULL, + &__dw_error); + DIE_IF(ret != DW_DLV_OK); + addr = ranges[0].dwr_addr1; + dwarf_ranges_dealloc(__dw_debug, ranges, cnt); + return addr; +} + +/* + * Search a Die from Die tree. + * Note: cur_link->die should be deallocated in this function. + */ +static int __search_die_tree(struct die_link *cur_link, + int (*die_cb)(struct die_link *, void *), + void *data) +{ + Dwarf_Die new_die; + struct die_link new_link; + int ret; + + if (!die_cb) + return 0; + + /* Check current die */ + while (!(ret = die_cb(cur_link, data))) { + /* Check child die */ + ret = dwarf_child(cur_link->die, &new_die, &__dw_error); + DIE_IF(ret == DW_DLV_ERROR); + if (ret == DW_DLV_OK) { + new_link.parent = cur_link; + new_link.die = new_die; + ret = __search_die_tree(&new_link, die_cb, data); + if (ret) + break; + } + + /* Move to next sibling */ + ret = dwarf_siblingof(__dw_debug, cur_link->die, &new_die, + &__dw_error); + DIE_IF(ret == DW_DLV_ERROR); + dwarf_dealloc(__dw_debug, cur_link->die, DW_DLA_DIE); + cur_link->die = new_die; + if (ret == DW_DLV_NO_ENTRY) + return 0; + } + dwarf_dealloc(__dw_debug, cur_link->die, DW_DLA_DIE); + return ret; +} + +/* Search a die in its children's die tree */ +static int search_die_from_children(Dwarf_Die parent_die, + int (*die_cb)(struct die_link *, void *), + void *data) +{ + struct die_link new_link; + int ret; + + new_link.parent = NULL; + ret = dwarf_child(parent_die, &new_link.die, &__dw_error); + DIE_IF(ret == DW_DLV_ERROR); + if (ret == DW_DLV_OK) + return __search_die_tree(&new_link, die_cb, data); + else + return 0; +} + +/* Find a locdesc corresponding to the address */ +static int attr_get_locdesc(Dwarf_Attribute attr, Dwarf_Locdesc *desc, + Dwarf_Addr addr) +{ + Dwarf_Signed lcnt; + Dwarf_Locdesc **llbuf; + int ret, i; + + ret = dwarf_loclist_n(attr, &llbuf, &lcnt, &__dw_error); + DIE_IF(ret != DW_DLV_OK); + ret = DW_DLV_NO_ENTRY; + for (i = 0; i < lcnt; ++i) { + if (llbuf[i]->ld_lopc <= addr && + llbuf[i]->ld_hipc > addr) { + memcpy(desc, llbuf[i], sizeof(Dwarf_Locdesc)); + desc->ld_s = + malloc(sizeof(Dwarf_Loc) * llbuf[i]->ld_cents); + DIE_IF(desc->ld_s == NULL); + memcpy(desc->ld_s, llbuf[i]->ld_s, + sizeof(Dwarf_Loc) * llbuf[i]->ld_cents); + ret = DW_DLV_OK; + break; + } + dwarf_dealloc(__dw_debug, llbuf[i]->ld_s, DW_DLA_LOC_BLOCK); + dwarf_dealloc(__dw_debug, llbuf[i], DW_DLA_LOCDESC); + } + /* Releasing loop */ + for (; i < lcnt; ++i) { + dwarf_dealloc(__dw_debug, llbuf[i]->ld_s, DW_DLA_LOC_BLOCK); + dwarf_dealloc(__dw_debug, llbuf[i], DW_DLA_LOCDESC); + } + dwarf_dealloc(__dw_debug, llbuf, DW_DLA_LIST); + return ret; +} + +/* Get decl_file attribute value (file number) */ +static Dwarf_Unsigned die_get_decl_file(Dwarf_Die sp_die) +{ + Dwarf_Attribute attr; + Dwarf_Unsigned fno; + int ret; + + ret = dwarf_attr(sp_die, DW_AT_decl_file, &attr, &__dw_error); + DIE_IF(ret != DW_DLV_OK); + dwarf_formudata(attr, &fno, &__dw_error); + DIE_IF(ret != DW_DLV_OK); + dwarf_dealloc(__dw_debug, attr, DW_DLA_ATTR); + return fno; +} + +/* Get decl_line attribute value (line number) */ +static Dwarf_Unsigned die_get_decl_line(Dwarf_Die sp_die) +{ + Dwarf_Attribute attr; + Dwarf_Unsigned lno; + int ret; + + ret = dwarf_attr(sp_die, DW_AT_decl_line, &attr, &__dw_error); + DIE_IF(ret != DW_DLV_OK); + dwarf_formudata(attr, &lno, &__dw_error); + DIE_IF(ret != DW_DLV_OK); + dwarf_dealloc(__dw_debug, attr, DW_DLA_ATTR); + return lno; +} + +/* + * Probe finder related functions + */ + +/* Show a location */ +static void show_location(Dwarf_Loc *loc, struct probe_finder *pf) +{ + Dwarf_Small op; + Dwarf_Unsigned regn; + Dwarf_Signed offs; + int deref = 0, ret; + const char *regs; + + op = loc->lr_atom; + + /* If this is based on frame buffer, set the offset */ + if (op == DW_OP_fbreg) { + deref = 1; + offs = (Dwarf_Signed)loc->lr_number; + op = pf->fbloc.ld_s[0].lr_atom; + loc = &pf->fbloc.ld_s[0]; + } else + offs = 0; + + if (op >= DW_OP_breg0 && op <= DW_OP_breg31) { + regn = op - DW_OP_breg0; + offs += (Dwarf_Signed)loc->lr_number; + deref = 1; + } else if (op >= DW_OP_reg0 && op <= DW_OP_reg31) { + regn = op - DW_OP_reg0; + } else if (op == DW_OP_bregx) { + regn = loc->lr_number; + offs += (Dwarf_Signed)loc->lr_number2; + deref = 1; + } else if (op == DW_OP_regx) { + regn = loc->lr_number; + } else + die("Dwarf_OP %d is not supported.\n", op); + + regs = get_arch_regstr(regn); + if (!regs) + die("%lld exceeds max register number.\n", regn); + + if (deref) + ret = snprintf(pf->buf, pf->len, + " %s=%+lld(%s)", pf->var, offs, regs); + else + ret = snprintf(pf->buf, pf->len, " %s=%s", pf->var, regs); + DIE_IF(ret < 0); + DIE_IF(ret >= pf->len); +} + +/* Show a variables in kprobe event format */ +static void show_variable(Dwarf_Die vr_die, struct probe_finder *pf) +{ + Dwarf_Attribute attr; + Dwarf_Locdesc ld; + int ret; + + ret = dwarf_attr(vr_die, DW_AT_location, &attr, &__dw_error); + if (ret != DW_DLV_OK) + goto error; + ret = attr_get_locdesc(attr, &ld, (pf->addr - pf->cu_base)); + if (ret != DW_DLV_OK) + goto error; + /* TODO? */ + DIE_IF(ld.ld_cents != 1); + show_location(&ld.ld_s[0], pf); + free(ld.ld_s); + dwarf_dealloc(__dw_debug, attr, DW_DLA_ATTR); + return ; +error: + die("Failed to find the location of %s at this address.\n" + " Perhaps, it has been optimized out.\n", pf->var); +} + +static int variable_callback(struct die_link *dlink, void *data) +{ + struct probe_finder *pf = (struct probe_finder *)data; + Dwarf_Half tag; + int ret; + + ret = dwarf_tag(dlink->die, &tag, &__dw_error); + DIE_IF(ret == DW_DLV_ERROR); + if ((tag == DW_TAG_formal_parameter || + tag == DW_TAG_variable) && + (die_compare_name(dlink->die, pf->var) == 0)) { + show_variable(dlink->die, pf); + return 1; + } + /* TODO: Support struct members and arrays */ + return 0; +} + +/* Find a variable in a subprogram die */ +static void find_variable(Dwarf_Die sp_die, struct probe_finder *pf) +{ + int ret; + + if (!is_c_varname(pf->var)) { + /* Output raw parameters */ + ret = snprintf(pf->buf, pf->len, " %s", pf->var); + DIE_IF(ret < 0); + DIE_IF(ret >= pf->len); + return ; + } + + pr_debug("Searching '%s' variable in context.\n", pf->var); + /* 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); +} + +/* Get a frame base on the address */ +static void get_current_frame_base(Dwarf_Die sp_die, struct probe_finder *pf) +{ + Dwarf_Attribute attr; + int ret; + + ret = dwarf_attr(sp_die, DW_AT_frame_base, &attr, &__dw_error); + DIE_IF(ret != DW_DLV_OK); + ret = attr_get_locdesc(attr, &pf->fbloc, (pf->addr - pf->cu_base)); + DIE_IF(ret != DW_DLV_OK); + dwarf_dealloc(__dw_debug, attr, DW_DLA_ATTR); +} + +static void free_current_frame_base(struct probe_finder *pf) +{ + free(pf->fbloc.ld_s); + memset(&pf->fbloc, 0, sizeof(Dwarf_Locdesc)); +} + +/* Show a probe point to output buffer */ +static void show_probepoint(Dwarf_Die sp_die, Dwarf_Signed offs, + struct probe_finder *pf) +{ + struct probe_point *pp = pf->pp; + char *name; + char tmp[MAX_PROBE_BUFFER]; + int ret, i, len; + + /* Output name of probe point */ + ret = dwarf_diename(sp_die, &name, &__dw_error); + DIE_IF(ret == DW_DLV_ERROR); + if (ret == DW_DLV_OK) { + ret = snprintf(tmp, MAX_PROBE_BUFFER, "%s+%u", name, + (unsigned int)offs); + /* Copy the function name if possible */ + if (!pp->function) { + pp->function = strdup(name); + pp->offset = offs; + } + dwarf_dealloc(__dw_debug, name, DW_DLA_STRING); + } else { + /* This function has no name. */ + ret = snprintf(tmp, MAX_PROBE_BUFFER, "0x%llx", pf->addr); + if (!pp->function) { + /* TODO: Use _stext */ + pp->function = strdup(""); + pp->offset = (int)pf->addr; + } + } + DIE_IF(ret < 0); + DIE_IF(ret >= MAX_PROBE_BUFFER); + len = ret; + pr_debug("Probe point found: %s\n", tmp); + + /* Find each argument */ + get_current_frame_base(sp_die, pf); + for (i = 0; i < pp->nr_args; i++) { + pf->var = pp->args[i]; + pf->buf = &tmp[len]; + pf->len = MAX_PROBE_BUFFER - len; + find_variable(sp_die, pf); + len += strlen(pf->buf); + } + free_current_frame_base(pf); + + pp->probes[pp->found] = strdup(tmp); + pp->found++; +} + +static int probeaddr_callback(struct die_link *dlink, void *data) +{ + struct probe_finder *pf = (struct probe_finder *)data; + Dwarf_Half tag; + Dwarf_Signed offs; + int ret; + + ret = dwarf_tag(dlink->die, &tag, &__dw_error); + DIE_IF(ret == DW_DLV_ERROR); + /* Check the address is in this subprogram */ + if (tag == DW_TAG_subprogram && + die_within_subprogram(dlink->die, pf->addr, &offs)) { + show_probepoint(dlink->die, offs, pf); + return 1; + } + return 0; +} + +/* Find probe point from its line number */ +static void find_by_line(struct probe_finder *pf) +{ + Dwarf_Signed cnt, i, clm; + Dwarf_Line *lines; + Dwarf_Unsigned lineno = 0; + Dwarf_Addr addr; + Dwarf_Unsigned fno; + int ret; + + ret = dwarf_srclines(pf->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 != pf->fno) + continue; + + ret = dwarf_lineno(lines[i], &lineno, &__dw_error); + DIE_IF(ret != DW_DLV_OK); + if (lineno != pf->lno) + continue; + + ret = dwarf_lineoff(lines[i], &clm, &__dw_error); + DIE_IF(ret != DW_DLV_OK); + + ret = dwarf_lineaddr(lines[i], &addr, &__dw_error); + DIE_IF(ret != DW_DLV_OK); + pr_debug("Probe line found: line[%d]:%u,%d addr:0x%llx\n", + (int)i, (unsigned)lineno, (int)clm, addr); + pf->addr = addr; + /* Search a real subprogram including this line, */ + ret = search_die_from_children(pf->cu_die, + probeaddr_callback, pf); + if (ret == 0) + die("Probe point is not found in subprograms.\n"); + /* Continuing, because target line might be inlined. */ + } + dwarf_srclines_dealloc(__dw_debug, lines, cnt); +} + +/* Search function from function name */ +static int probefunc_callback(struct die_link *dlink, void *data) +{ + struct probe_finder *pf = (struct probe_finder *)data; + struct probe_point *pp = pf->pp; + struct die_link *lk; + Dwarf_Signed offs; + Dwarf_Half tag; + int ret; + + ret = dwarf_tag(dlink->die, &tag, &__dw_error); + DIE_IF(ret == DW_DLV_ERROR); + if (tag == DW_TAG_subprogram) { + if (die_compare_name(dlink->die, pp->function) == 0) { + if (pp->line) { /* Function relative line */ + pf->fno = die_get_decl_file(dlink->die); + pf->lno = die_get_decl_line(dlink->die) + + pp->line; + find_by_line(pf); + return 1; + } + if (die_inlined_subprogram(dlink->die)) { + /* Inlined function, save it. */ + ret = dwarf_die_CU_offset(dlink->die, + &pf->inl_offs, + &__dw_error); + DIE_IF(ret != DW_DLV_OK); + pr_debug("inline definition offset %lld\n", + pf->inl_offs); + return 0; /* Continue to search */ + } + /* Get probe address */ + pf->addr = die_get_entrypc(dlink->die); + pf->addr += pp->offset; + /* TODO: Check the address in this function */ + show_probepoint(dlink->die, pp->offset, pf); + return 1; /* Exit; no same symbol in this CU. */ + } + } else if (tag == DW_TAG_inlined_subroutine && pf->inl_offs) { + if (die_get_abstract_origin(dlink->die) == pf->inl_offs) { + /* Get probe address */ + pf->addr = die_get_entrypc(dlink->die); + pf->addr += pp->offset; + pr_debug("found inline addr: 0x%llx\n", pf->addr); + /* Inlined function. Get a real subprogram */ + for (lk = dlink->parent; lk != NULL; lk = lk->parent) { + tag = 0; + dwarf_tag(lk->die, &tag, &__dw_error); + DIE_IF(ret == DW_DLV_ERROR); + if (tag == DW_TAG_subprogram && + !die_inlined_subprogram(lk->die)) + goto found; + } + die("Failed to find real subprogram.\n"); +found: + /* Get offset from subprogram */ + ret = die_within_subprogram(lk->die, pf->addr, &offs); + DIE_IF(!ret); + show_probepoint(lk->die, offs, pf); + /* Continue to search */ + } + } + return 0; +} + +static void find_by_func(struct probe_finder *pf) +{ + search_die_from_children(pf->cu_die, probefunc_callback, pf); +} + +/* Find a probe point */ +int find_probepoint(int fd, struct probe_point *pp) +{ + Dwarf_Half addr_size = 0; + Dwarf_Unsigned next_cuh = 0; + int cu_number = 0, ret; + struct probe_finder pf = {.pp = pp}; + + ret = dwarf_init(fd, DW_DLC_READ, 0, 0, &__dw_debug, &__dw_error); + if (ret != DW_DLV_OK) { + pr_warning("No dwarf info found in the vmlinux - please rebuild with CONFIG_DEBUG_INFO.\n"); + return -ENOENT; + } + + pp->found = 0; + while (++cu_number) { + /* 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, &pf.cu_die, &__dw_error); + DIE_IF(ret != DW_DLV_OK); + + /* Check if target file is included. */ + if (pp->file) + pf.fno = cu_find_fileno(pf.cu_die, pp->file); + + if (!pp->file || pf.fno) { + /* Save CU base address (for frame_base) */ + ret = dwarf_lowpc(pf.cu_die, &pf.cu_base, &__dw_error); + DIE_IF(ret == DW_DLV_ERROR); + if (ret == DW_DLV_NO_ENTRY) + pf.cu_base = 0; + if (pp->function) + find_by_func(&pf); + else { + pf.lno = pp->line; + find_by_line(&pf); + } + } + dwarf_dealloc(__dw_debug, pf.cu_die, DW_DLA_DIE); + } + ret = dwarf_finish(__dw_debug, &__dw_error); + DIE_IF(ret != DW_DLV_OK); + + return pp->found; +} + diff --git a/tools/perf/util/probe-finder.h b/tools/perf/util/probe-finder.h new file mode 100644 index 000000000000..bdebca6697d2 --- /dev/null +++ b/tools/perf/util/probe-finder.h @@ -0,0 +1,57 @@ +#ifndef _PROBE_FINDER_H +#define _PROBE_FINDER_H + +#define MAX_PATH_LEN 256 +#define MAX_PROBE_BUFFER 1024 +#define MAX_PROBES 128 + +static inline int is_c_varname(const char *name) +{ + /* TODO */ + return isalpha(name[0]) || name[0] == '_'; +} + +struct probe_point { + /* Inputs */ + char *file; /* File name */ + int line; /* Line number */ + + char *function; /* Function name */ + int offset; /* Offset bytes */ + + int nr_args; /* Number of arguments */ + char **args; /* Arguments */ + + int retprobe; /* Return probe */ + + /* Output */ + int found; /* Number of found probe points */ + char *probes[MAX_PROBES]; /* Output buffers (will be allocated)*/ +}; + +#ifndef NO_LIBDWARF +extern int find_probepoint(int fd, struct probe_point *pp); + +#include <libdwarf/dwarf.h> +#include <libdwarf/libdwarf.h> + +struct probe_finder { + struct probe_point *pp; /* Target probe point */ + + /* For function searching */ + Dwarf_Addr addr; /* Address */ + Dwarf_Unsigned fno; /* File number */ + Dwarf_Unsigned lno; /* Line number */ + Dwarf_Off inl_offs; /* Inline offset */ + Dwarf_Die cu_die; /* Current CU */ + + /* For variable searching */ + Dwarf_Addr cu_base; /* Current CU base address */ + Dwarf_Locdesc fbloc; /* Location of Current Frame Base */ + const char *var; /* Current variable name */ + char *buf; /* Current output buffer */ + int len; /* Length of output buffer */ +}; +#endif /* NO_LIBDWARF */ + +#endif /*_PROBE_FINDER_H */ diff --git a/tools/perf/util/sort.c b/tools/perf/util/sort.c index 40c9acd41cad..b490354d1b23 100644 --- a/tools/perf/util/sort.c +++ b/tools/perf/util/sort.c @@ -5,8 +5,10 @@ char default_parent_pattern[] = "^sys_|^do_page_fault"; char *parent_pattern = default_parent_pattern; char default_sort_order[] = "comm,dso,symbol"; char *sort_order = default_sort_order; -int sort__need_collapse = 0; -int sort__has_parent = 0; +int sort__need_collapse = 0; +int sort__has_parent = 0; + +enum sort_type sort__first_dimension; unsigned int dsos__col_width; unsigned int comms__col_width; @@ -265,6 +267,19 @@ int sort_dimension__add(const char *tok) sort__has_parent = 1; } + if (list_empty(&hist_entry__sort_list)) { + if (!strcmp(sd->name, "pid")) + sort__first_dimension = SORT_PID; + else if (!strcmp(sd->name, "comm")) + sort__first_dimension = SORT_COMM; + else if (!strcmp(sd->name, "dso")) + sort__first_dimension = SORT_DSO; + else if (!strcmp(sd->name, "symbol")) + sort__first_dimension = SORT_SYM; + else if (!strcmp(sd->name, "parent")) + sort__first_dimension = SORT_PARENT; + } + list_add_tail(&sd->entry->list, &hist_entry__sort_list); sd->taken = 1; @@ -273,4 +288,3 @@ int sort_dimension__add(const char *tok) return -ESRCH; } - diff --git a/tools/perf/util/sort.h b/tools/perf/util/sort.h index 13806d782af6..333e664ff45f 100644 --- a/tools/perf/util/sort.h +++ b/tools/perf/util/sort.h @@ -39,6 +39,7 @@ extern struct sort_entry sort_parent; extern unsigned int dsos__col_width; extern unsigned int comms__col_width; extern unsigned int threads__col_width; +extern enum sort_type sort__first_dimension; struct hist_entry { struct rb_node rb_node; @@ -53,6 +54,14 @@ struct hist_entry { struct rb_root sorted_chain; }; +enum sort_type { + SORT_PID, + SORT_COMM, + SORT_DSO, + SORT_SYM, + SORT_PARENT +}; + /* * configurable sorting bits */ diff --git a/tools/perf/util/string.c b/tools/perf/util/string.c index 04743d3e9039..227043577e06 100644 --- a/tools/perf/util/string.c +++ b/tools/perf/util/string.c @@ -1,5 +1,7 @@ #include <string.h> +#include <stdlib.h> #include "string.h" +#include "util.h" static int hex(char ch) { @@ -43,3 +45,85 @@ char *strxfrchar(char *s, char from, char to) return s; } + +#define K 1024LL +/* + * perf_atoll() + * Parse (\d+)(b|B|kb|KB|mb|MB|gb|GB|tb|TB) (e.g. "256MB") + * and return its numeric value + */ +s64 perf_atoll(const char *str) +{ + unsigned int i; + s64 length = -1, unit = 1; + + if (!isdigit(str[0])) + goto out_err; + + for (i = 1; i < strlen(str); i++) { + switch (str[i]) { + case 'B': + case 'b': + break; + case 'K': + if (str[i + 1] != 'B') + goto out_err; + else + goto kilo; + case 'k': + if (str[i + 1] != 'b') + goto out_err; +kilo: + unit = K; + break; + case 'M': + if (str[i + 1] != 'B') + goto out_err; + else + goto mega; + case 'm': + if (str[i + 1] != 'b') + goto out_err; +mega: + unit = K * K; + break; + case 'G': + if (str[i + 1] != 'B') + goto out_err; + else + goto giga; + case 'g': + if (str[i + 1] != 'b') + goto out_err; +giga: + unit = K * K * K; + break; + case 'T': + if (str[i + 1] != 'B') + goto out_err; + else + goto tera; + case 't': + if (str[i + 1] != 'b') + goto out_err; +tera: + unit = K * K * K * K; + break; + case '\0': /* only specified figures */ + unit = 1; + break; + default: + if (!isdigit(str[i])) + goto out_err; + break; + } + } + + length = atoll(str) * unit; + goto out; + +out_err: + length = -1; +out: + return length; +} diff --git a/tools/perf/util/string.h b/tools/perf/util/string.h index 2c84bf65ba0f..e50b07f80827 100644 --- a/tools/perf/util/string.h +++ b/tools/perf/util/string.h @@ -5,6 +5,7 @@ int hex2u64(const char *ptr, u64 *val); char *strxfrchar(char *s, char from, char to); +s64 perf_atoll(const char *str); #define _STR(x) #x #define STR(x) _STR(x) diff --git a/tools/perf/util/svghelper.c b/tools/perf/util/svghelper.c index 856655d8b0b8..b3637db025a2 100644 --- a/tools/perf/util/svghelper.c +++ b/tools/perf/util/svghelper.c @@ -103,7 +103,7 @@ void open_svg(const char *filename, int cpus, int rows, u64 start, u64 end) fprintf(svgfile, " rect.process2 { fill:rgb(180,180,180); fill-opacity:0.9; stroke-width:0; stroke:rgb( 0, 0, 0); } \n"); fprintf(svgfile, " rect.sample { fill:rgb( 0, 0,255); fill-opacity:0.8; stroke-width:0; stroke:rgb( 0, 0, 0); } \n"); fprintf(svgfile, " rect.blocked { fill:rgb(255, 0, 0); fill-opacity:0.5; stroke-width:0; stroke:rgb( 0, 0, 0); } \n"); - fprintf(svgfile, " rect.waiting { fill:rgb(214,214, 0); fill-opacity:0.3; stroke-width:0; stroke:rgb( 0, 0, 0); } \n"); + fprintf(svgfile, " rect.waiting { fill:rgb(224,214, 0); fill-opacity:0.8; stroke-width:0; stroke:rgb( 0, 0, 0); } \n"); fprintf(svgfile, " rect.WAITING { fill:rgb(255,214, 48); fill-opacity:0.6; stroke-width:0; stroke:rgb( 0, 0, 0); } \n"); fprintf(svgfile, " rect.cpu { fill:rgb(192,192,192); fill-opacity:0.2; stroke-width:0.5; stroke:rgb(128,128,128); } \n"); fprintf(svgfile, " rect.pstate { fill:rgb(128,128,128); fill-opacity:0.8; stroke-width:0; } \n"); diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c index faa84f5d4f54..4d75e745288f 100644 --- a/tools/perf/util/symbol.c +++ b/tools/perf/util/symbol.c @@ -9,10 +9,9 @@ #include <libelf.h> #include <gelf.h> #include <elf.h> +#include <limits.h> #include <sys/utsname.h> -const char *sym_hist_filter; - enum dso_origin { DSO__ORIG_KERNEL = 0, DSO__ORIG_JAVA_JIT, @@ -28,6 +27,7 @@ static void dsos__add(struct dso *dso); static struct dso *dsos__find(const char *name); static struct map *map__new2(u64 start, struct dso *dso); static void kernel_maps__insert(struct map *map); +unsigned int symbol__priv_size; static struct rb_root kernel_maps; @@ -77,39 +77,31 @@ static void kernel_maps__fixup_end(void) } } -static struct symbol *symbol__new(u64 start, u64 len, const char *name, - unsigned int priv_size, int v) +static struct symbol *symbol__new(u64 start, u64 len, const char *name) { size_t namelen = strlen(name) + 1; - struct symbol *self = calloc(1, priv_size + sizeof(*self) + namelen); - + struct symbol *self = calloc(1, (symbol__priv_size + + sizeof(*self) + namelen)); if (!self) return NULL; - if (v > 2) - printf("new symbol: %016Lx [%08lx]: %s, hist: %p\n", - start, (unsigned long)len, name, self->hist); - - self->hist = NULL; - self->hist_sum = 0; - - if (sym_hist_filter && !strcmp(name, sym_hist_filter)) - self->hist = calloc(sizeof(u64), len); - - if (priv_size) { - memset(self, 0, priv_size); - self = ((void *)self) + priv_size; + if (symbol__priv_size) { + memset(self, 0, symbol__priv_size); + self = ((void *)self) + symbol__priv_size; } self->start = start; self->end = len ? start + len - 1 : start; + + pr_debug3("%s: %s %#Lx-%#Lx\n", __func__, name, start, self->end); + memcpy(self->name, name, namelen); return self; } -static void symbol__delete(struct symbol *self, unsigned int priv_size) +static void symbol__delete(struct symbol *self) { - free(((void *)self) - priv_size); + free(((void *)self) - symbol__priv_size); } static size_t symbol__fprintf(struct symbol *self, FILE *fp) @@ -118,19 +110,31 @@ static size_t symbol__fprintf(struct symbol *self, FILE *fp) self->start, self->end, self->name); } -struct dso *dso__new(const char *name, unsigned int sym_priv_size) +static void dso__set_long_name(struct dso *self, char *name) +{ + self->long_name = name; + self->long_name_len = strlen(name); +} + +static void dso__set_basename(struct dso *self) +{ + self->short_name = basename(self->long_name); +} + +struct dso *dso__new(const char *name) { struct dso *self = malloc(sizeof(*self) + strlen(name) + 1); if (self != NULL) { strcpy(self->name, name); - self->long_name = self->name; + dso__set_long_name(self, self->name); self->short_name = self->name; self->syms = RB_ROOT; - self->sym_priv_size = sym_priv_size; self->find_symbol = dso__find_symbol; self->slen_calculated = 0; self->origin = DSO__ORIG_NOT_FOUND; + self->loaded = 0; + self->has_build_id = 0; } return self; @@ -145,7 +149,7 @@ static void dso__delete_symbols(struct dso *self) pos = rb_entry(next, struct symbol, rb_node); next = rb_next(&pos->rb_node); rb_erase(&pos->rb_node, &self->syms); - symbol__delete(pos, self->sym_priv_size); + symbol__delete(pos); } } @@ -157,6 +161,12 @@ void dso__delete(struct dso *self) free(self); } +void dso__set_build_id(struct dso *self, void *build_id) +{ + memcpy(self->build_id, build_id, sizeof(self->build_id)); + self->has_build_id = 1; +} + static void dso__insert_symbol(struct dso *self, struct symbol *sym) { struct rb_node **p = &self->syms.rb_node; @@ -199,11 +209,37 @@ struct symbol *dso__find_symbol(struct dso *self, u64 ip) return NULL; } -size_t dso__fprintf(struct dso *self, FILE *fp) +int build_id__sprintf(u8 *self, int len, char *bf) { - size_t ret = fprintf(fp, "dso: %s\n", self->short_name); + char *bid = bf; + u8 *raw = self; + int i; + + for (i = 0; i < len; ++i) { + sprintf(bid, "%02x", *raw); + ++raw; + bid += 2; + } + return raw - self; +} + +size_t dso__fprintf_buildid(struct dso *self, FILE *fp) +{ + char sbuild_id[BUILD_ID_SIZE * 2 + 1]; + + build_id__sprintf(self->build_id, sizeof(self->build_id), sbuild_id); + return fprintf(fp, "%s", sbuild_id); +} + +size_t dso__fprintf(struct dso *self, FILE *fp) +{ struct rb_node *nd; + size_t ret = fprintf(fp, "dso: %s (", self->short_name); + + ret += dso__fprintf_buildid(self, fp); + ret += fprintf(fp, ")\n"); + for (nd = rb_first(&self->syms); nd; nd = rb_next(nd)) { struct symbol *pos = rb_entry(nd, struct symbol, rb_node); ret += symbol__fprintf(pos, fp); @@ -217,7 +253,7 @@ size_t dso__fprintf(struct dso *self, FILE *fp) * so that we can in the next step set the symbol ->end address and then * call kernel_maps__split_kallsyms. */ -static int kernel_maps__load_all_kallsyms(int v) +static int kernel_maps__load_all_kallsyms(void) { char *line = NULL; size_t n; @@ -259,12 +295,16 @@ static int kernel_maps__load_all_kallsyms(int v) /* * Will fix up the end later, when we have all symbols sorted. */ - sym = symbol__new(start, 0, symbol_name, - kernel_map->dso->sym_priv_size, v); + sym = symbol__new(start, 0, symbol_name); if (sym == NULL) goto out_delete_line; + /* + * We will pass the symbols to the filter later, in + * kernel_maps__split_kallsyms, when we have split the + * maps per module + */ dso__insert_symbol(kernel_map->dso, sym); } @@ -308,8 +348,8 @@ static int kernel_maps__split_kallsyms(symbol_filter_t filter, int use_modules) if (strcmp(map->dso->name, module)) { map = kernel_maps__find_by_dso_name(module); if (!map) { - fputs("/proc/{kallsyms,modules} " - "inconsistency!\n", stderr); + pr_err("/proc/{kallsyms,modules} " + "inconsistency!\n"); return -1; } } @@ -326,8 +366,7 @@ static int kernel_maps__split_kallsyms(symbol_filter_t filter, int use_modules) snprintf(dso_name, sizeof(dso_name), "[kernel].%d", kernel_range++); - dso = dso__new(dso_name, - kernel_map->dso->sym_priv_size); + dso = dso__new(dso_name); if (dso == NULL) return -1; @@ -337,7 +376,7 @@ static int kernel_maps__split_kallsyms(symbol_filter_t filter, int use_modules) return -1; } - map->map_ip = vdso__map_ip; + map->map_ip = map->unmap_ip = identity__map_ip; kernel_maps__insert(map); ++kernel_range; } @@ -345,7 +384,7 @@ static int kernel_maps__split_kallsyms(symbol_filter_t filter, int use_modules) if (filter && filter(map, pos)) { delete_symbol: rb_erase(&pos->rb_node, &kernel_map->dso->syms); - symbol__delete(pos, kernel_map->dso->sym_priv_size); + symbol__delete(pos); } else { if (map != kernel_map) { rb_erase(&pos->rb_node, &kernel_map->dso->syms); @@ -359,10 +398,9 @@ delete_symbol: } -static int kernel_maps__load_kallsyms(symbol_filter_t filter, - int use_modules, int v) +static int kernel_maps__load_kallsyms(symbol_filter_t filter, int use_modules) { - if (kernel_maps__load_all_kallsyms(v)) + if (kernel_maps__load_all_kallsyms()) return -1; dso__fixup_sym_end(kernel_map->dso); @@ -370,9 +408,9 @@ static int kernel_maps__load_kallsyms(symbol_filter_t filter, return kernel_maps__split_kallsyms(filter, use_modules); } -static size_t kernel_maps__fprintf(FILE *fp, int v) +static size_t kernel_maps__fprintf(FILE *fp) { - size_t printed = fprintf(stderr, "Kernel maps:\n"); + size_t printed = fprintf(fp, "Kernel maps:\n"); struct rb_node *nd; for (nd = rb_first(&kernel_maps); nd; nd = rb_next(nd)) { @@ -380,17 +418,17 @@ static size_t kernel_maps__fprintf(FILE *fp, int v) printed += fprintf(fp, "Map:"); printed += map__fprintf(pos, fp); - if (v > 1) { + if (verbose > 1) { printed += dso__fprintf(pos->dso, fp); printed += fprintf(fp, "--\n"); } } - return printed + fprintf(stderr, "END kernel maps\n"); + return printed + fprintf(fp, "END kernel maps\n"); } static int dso__load_perf_map(struct dso *self, struct map *map, - symbol_filter_t filter, int v) + symbol_filter_t filter) { char *line = NULL; size_t n; @@ -427,14 +465,13 @@ static int dso__load_perf_map(struct dso *self, struct map *map, if (len + 2 >= line_len) continue; - sym = symbol__new(start, size, line + len, - self->sym_priv_size, v); + sym = symbol__new(start, size, line + len); if (sym == NULL) goto out_delete_line; if (filter && filter(map, sym)) - symbol__delete(sym, self->sym_priv_size); + symbol__delete(sym); else { dso__insert_symbol(self, sym); nr_syms++; @@ -542,7 +579,8 @@ static Elf_Scn *elf_section_by_name(Elf *elf, GElf_Ehdr *ep, * And always look at the original dso, not at debuginfo packages, that * have the PLT data stripped out (shdr_rel_plt.sh_type == SHT_NOBITS). */ -static int dso__synthesize_plt_symbols(struct dso *self, int v) +static int dso__synthesize_plt_symbols(struct dso *self, struct map *map, + symbol_filter_t filter) { uint32_t nr_rel_entries, idx; GElf_Sym sym; @@ -562,7 +600,7 @@ static int dso__synthesize_plt_symbols(struct dso *self, int v) if (fd < 0) goto out; - elf = elf_begin(fd, ELF_C_READ_MMAP, NULL); + elf = elf_begin(fd, PERF_ELF_C_READ_MMAP, NULL); if (elf == NULL) goto out_close; @@ -626,12 +664,16 @@ static int dso__synthesize_plt_symbols(struct dso *self, int v) "%s@plt", elf_sym__name(&sym, symstrs)); f = symbol__new(plt_offset, shdr_plt.sh_entsize, - sympltname, self->sym_priv_size, v); + sympltname); if (!f) goto out_elf_end; - dso__insert_symbol(self, f); - ++nr; + if (filter && filter(map, f)) + symbol__delete(f); + else { + dso__insert_symbol(self, f); + ++nr; + } } } else if (shdr_rel_plt.sh_type == SHT_REL) { GElf_Rel pos_mem, *pos; @@ -644,12 +686,16 @@ static int dso__synthesize_plt_symbols(struct dso *self, int v) "%s@plt", elf_sym__name(&sym, symstrs)); f = symbol__new(plt_offset, shdr_plt.sh_entsize, - sympltname, self->sym_priv_size, v); + sympltname); if (!f) goto out_elf_end; - dso__insert_symbol(self, f); - ++nr; + if (filter && filter(map, f)) + symbol__delete(f); + else { + dso__insert_symbol(self, f); + ++nr; + } } } @@ -662,14 +708,14 @@ out_close: if (err == 0) return nr; out: - fprintf(stderr, "%s: problems reading %s PLT info.\n", - __func__, self->long_name); + pr_warning("%s: problems reading %s PLT info.\n", + __func__, self->long_name); return 0; } static int dso__load_sym(struct dso *self, struct map *map, const char *name, int fd, symbol_filter_t filter, int kernel, - int kmodule, int v) + int kmodule) { struct map *curr_map = map; struct dso *curr_dso = self; @@ -686,17 +732,14 @@ static int dso__load_sym(struct dso *self, struct map *map, const char *name, Elf *elf; int nr = 0; - elf = elf_begin(fd, ELF_C_READ_MMAP, NULL); + elf = elf_begin(fd, PERF_ELF_C_READ_MMAP, NULL); if (elf == NULL) { - if (v) - fprintf(stderr, "%s: cannot read %s ELF file.\n", - __func__, name); + pr_err("%s: cannot read %s ELF file.\n", __func__, name); goto out_close; } if (gelf_getehdr(elf, &ehdr) == NULL) { - if (v) - fprintf(stderr, "%s: cannot get elf header.\n", __func__); + pr_err("%s: cannot get elf header.\n", __func__); goto out_elf_end; } @@ -782,7 +825,7 @@ static int dso__load_sym(struct dso *self, struct map *map, const char *name, if (kmodule) start += map->start + shdr.sh_offset; - curr_dso = dso__new(dso_name, self->sym_priv_size); + curr_dso = dso__new(dso_name); if (curr_dso == NULL) goto out_elf_end; curr_map = map__new2(start, curr_dso); @@ -790,7 +833,8 @@ static int dso__load_sym(struct dso *self, struct map *map, const char *name, dso__delete(curr_dso); goto out_elf_end; } - curr_map->map_ip = vdso__map_ip; + curr_map->map_ip = identity__map_ip; + curr_map->unmap_ip = identity__map_ip; curr_dso->origin = DSO__ORIG_KERNEL; kernel_maps__insert(curr_map); dsos__add(curr_dso); @@ -801,10 +845,9 @@ static int dso__load_sym(struct dso *self, struct map *map, const char *name, } if (curr_dso->adjust_symbols) { - if (v > 2) - printf("adjusting symbol: st_value: %Lx sh_addr: %Lx sh_offset: %Lx\n", - (u64)sym.st_value, (u64)shdr.sh_addr, (u64)shdr.sh_offset); - + pr_debug2("adjusting symbol: st_value: %Lx sh_addr: " + "%Lx sh_offset: %Lx\n", (u64)sym.st_value, + (u64)shdr.sh_addr, (u64)shdr.sh_offset); sym.st_value -= shdr.sh_addr - shdr.sh_offset; } /* @@ -816,14 +859,13 @@ static int dso__load_sym(struct dso *self, struct map *map, const char *name, if (demangled != NULL) elf_name = demangled; new_symbol: - f = symbol__new(sym.st_value, sym.st_size, elf_name, - curr_dso->sym_priv_size, v); + f = symbol__new(sym.st_value, sym.st_size, elf_name); free(demangled); if (!f) goto out_elf_end; if (filter && filter(curr_map, f)) - symbol__delete(f, curr_dso->sym_priv_size); + symbol__delete(f); else { dso__insert_symbol(curr_dso, f); nr++; @@ -842,63 +884,108 @@ out_close: return err; } -#define BUILD_ID_SIZE 128 +bool dsos__read_build_ids(void) +{ + bool have_build_id = false; + struct dso *pos; + + list_for_each_entry(pos, &dsos, node) + if (filename__read_build_id(pos->long_name, pos->build_id, + sizeof(pos->build_id)) > 0) { + have_build_id = true; + pos->has_build_id = true; + } + + return have_build_id; +} -static char *dso__read_build_id(struct dso *self, int v) +int filename__read_build_id(const char *filename, void *bf, size_t size) { - int i; + int fd, err = -1; GElf_Ehdr ehdr; GElf_Shdr shdr; Elf_Data *build_id_data; Elf_Scn *sec; - char *build_id = NULL, *bid; - unsigned char *raw; Elf *elf; - int fd = open(self->long_name, O_RDONLY); + if (size < BUILD_ID_SIZE) + goto out; + + fd = open(filename, O_RDONLY); if (fd < 0) goto out; - elf = elf_begin(fd, ELF_C_READ_MMAP, NULL); + elf = elf_begin(fd, PERF_ELF_C_READ_MMAP, NULL); if (elf == NULL) { - if (v) - fprintf(stderr, "%s: cannot read %s ELF file.\n", - __func__, self->long_name); + pr_debug2("%s: cannot read %s ELF file.\n", __func__, filename); goto out_close; } if (gelf_getehdr(elf, &ehdr) == NULL) { - if (v) - fprintf(stderr, "%s: cannot get elf header.\n", __func__); + pr_err("%s: cannot get elf header.\n", __func__); goto out_elf_end; } - sec = elf_section_by_name(elf, &ehdr, &shdr, ".note.gnu.build-id", NULL); + sec = elf_section_by_name(elf, &ehdr, &shdr, + ".note.gnu.build-id", NULL); if (sec == NULL) goto out_elf_end; build_id_data = elf_getdata(sec, NULL); if (build_id_data == NULL) goto out_elf_end; - build_id = malloc(BUILD_ID_SIZE); - if (build_id == NULL) - goto out_elf_end; - raw = build_id_data->d_buf + 16; - bid = build_id; - - for (i = 0; i < 20; ++i) { - sprintf(bid, "%02x", *raw); - ++raw; - bid += 2; - } - if (v >= 2) - printf("%s(%s): %s\n", __func__, self->long_name, build_id); + memcpy(bf, build_id_data->d_buf + 16, BUILD_ID_SIZE); + err = BUILD_ID_SIZE; out_elf_end: elf_end(elf); out_close: close(fd); out: - return build_id; + return err; +} + +int sysfs__read_build_id(const char *filename, void *build_id, size_t size) +{ + int fd, err = -1; + + if (size < BUILD_ID_SIZE) + goto out; + + fd = open(filename, O_RDONLY); + if (fd < 0) + goto out; + + while (1) { + char bf[BUFSIZ]; + GElf_Nhdr nhdr; + int namesz, descsz; + + if (read(fd, &nhdr, sizeof(nhdr)) != sizeof(nhdr)) + break; + + namesz = (nhdr.n_namesz + 3) & -4U; + descsz = (nhdr.n_descsz + 3) & -4U; + if (nhdr.n_type == NT_GNU_BUILD_ID && + nhdr.n_namesz == sizeof("GNU")) { + if (read(fd, bf, namesz) != namesz) + break; + if (memcmp(bf, "GNU", sizeof("GNU")) == 0) { + if (read(fd, build_id, + BUILD_ID_SIZE) == BUILD_ID_SIZE) { + err = 0; + break; + } + } else if (read(fd, bf, descsz) != descsz) + break; + } else { + int n = namesz + descsz; + if (read(fd, bf, n) != n) + break; + } + } + close(fd); +out: + return err; } char dso__symtab_origin(const struct dso *self) @@ -918,20 +1005,23 @@ char dso__symtab_origin(const struct dso *self) return origin[self->origin]; } -int dso__load(struct dso *self, struct map *map, symbol_filter_t filter, int v) +int dso__load(struct dso *self, struct map *map, symbol_filter_t filter) { int size = PATH_MAX; - char *name = malloc(size), *build_id = NULL; + char *name = malloc(size); + u8 build_id[BUILD_ID_SIZE]; int ret = -1; int fd; + self->loaded = 1; + if (!name) return -1; self->adjust_symbols = 0; if (strncmp(self->name, "/tmp/perf-", 10) == 0) { - ret = dso__load_perf_map(self, map, filter, v); + ret = dso__load_perf_map(self, map, filter); self->origin = ret > 0 ? DSO__ORIG_JAVA_JIT : DSO__ORIG_NOT_FOUND; return ret; @@ -952,12 +1042,17 @@ more: self->long_name); break; case DSO__ORIG_BUILDID: - build_id = dso__read_build_id(self, v); - if (build_id != NULL) { + 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, "/usr/lib/debug/.build-id/%.2s/%s.debug", - build_id, build_id + 2); - free(build_id); + build_id_hex, build_id_hex + 2); + if (self->has_build_id) + goto compare_build_id; break; } self->origin++; @@ -970,10 +1065,20 @@ more: goto out; } + if (self->has_build_id) { + if (filename__read_build_id(name, build_id, + sizeof(build_id)) < 0) + goto more; +compare_build_id: + if (memcmp(build_id, self->build_id, + sizeof(self->build_id)) != 0) + goto more; + } + fd = open(name, O_RDONLY); } while (fd < 0); - ret = dso__load_sym(self, map, name, fd, filter, 0, 0, v); + ret = dso__load_sym(self, map, name, fd, filter, 0, 0); close(fd); /* @@ -983,7 +1088,7 @@ more: goto more; if (ret > 0) { - int nr_plt = dso__synthesize_plt_symbols(self, v); + int nr_plt = dso__synthesize_plt_symbols(self, map, filter); if (nr_plt > 0) ret += nr_plt; } @@ -1031,34 +1136,31 @@ struct map *kernel_maps__find_by_dso_name(const char *name) } static int dso__load_module_sym(struct dso *self, struct map *map, - symbol_filter_t filter, int v) + symbol_filter_t filter) { int err = 0, fd = open(self->long_name, O_RDONLY); + self->loaded = 1; + if (fd < 0) { - if (v) - fprintf(stderr, "%s: cannot open %s\n", - __func__, self->long_name); + pr_err("%s: cannot open %s\n", __func__, self->long_name); return err; } - err = dso__load_sym(self, map, self->long_name, fd, filter, 0, 1, v); + err = dso__load_sym(self, map, self->long_name, fd, filter, 0, 1); close(fd); return err; } -static int dsos__load_modules_sym_dir(char *dirname, - symbol_filter_t filter, int v) +static int dsos__load_modules_sym_dir(char *dirname, symbol_filter_t filter) { struct dirent *dent; int nr_symbols = 0, err; DIR *dir = opendir(dirname); if (!dir) { - if (v) - fprintf(stderr, "%s: cannot open %s dir\n", __func__, - dirname); + pr_err("%s: cannot open %s dir\n", __func__, dirname); return -1; } @@ -1072,7 +1174,7 @@ static int dsos__load_modules_sym_dir(char *dirname, snprintf(path, sizeof(path), "%s/%s", dirname, dent->d_name); - err = dsos__load_modules_sym_dir(path, filter, v); + err = dsos__load_modules_sym_dir(path, filter); if (err < 0) goto failure; } else { @@ -1080,6 +1182,7 @@ static int dsos__load_modules_sym_dir(char *dirname, dso_name[PATH_MAX]; struct map *map; struct rb_node *last; + char *long_name; if (dot == NULL || strcmp(dot, ".ko")) continue; @@ -1094,11 +1197,13 @@ static int dsos__load_modules_sym_dir(char *dirname, snprintf(path, sizeof(path), "%s/%s", dirname, dent->d_name); - map->dso->long_name = strdup(path); - if (map->dso->long_name == NULL) + long_name = strdup(path); + if (long_name == NULL) goto failure; + dso__set_long_name(map->dso, long_name); + dso__set_basename(map->dso); - err = dso__load_module_sym(map->dso, map, filter, v); + err = dso__load_module_sym(map->dso, map, filter); if (err < 0) goto failure; last = rb_last(&map->dso->syms); @@ -1125,7 +1230,7 @@ failure: return -1; } -static int dsos__load_modules_sym(symbol_filter_t filter, int v) +static int dsos__load_modules_sym(symbol_filter_t filter) { struct utsname uts; char modules_path[PATH_MAX]; @@ -1136,7 +1241,7 @@ static int dsos__load_modules_sym(symbol_filter_t filter, int v) snprintf(modules_path, sizeof(modules_path), "/lib/modules/%s/kernel", uts.release); - return dsos__load_modules_sym_dir(modules_path, filter, v); + return dsos__load_modules_sym_dir(modules_path, filter); } /* @@ -1149,21 +1254,16 @@ static struct map *map__new2(u64 start, struct dso *dso) struct map *self = malloc(sizeof(*self)); if (self != NULL) { - self->start = start; /* - * Will be filled after we load all the symbols + * ->end will be filled after we load all the symbols */ - self->end = 0; - - self->pgoff = 0; - self->dso = dso; - self->map_ip = map__map_ip; - RB_CLEAR_NODE(&self->rb_node); + map__init(self, start, 0, 0, dso); } + return self; } -static int dsos__load_modules(unsigned int sym_priv_size) +int dsos__load_modules(void) { char *line = NULL; size_t n; @@ -1202,7 +1302,7 @@ static int dsos__load_modules(unsigned int sym_priv_size) *sep = '\0'; snprintf(name, sizeof(name), "[%s]", line); - dso = dso__new(name, sym_priv_size); + dso = dso__new(name); if (dso == NULL) goto out_delete_line; @@ -1213,6 +1313,12 @@ static int dsos__load_modules(unsigned int sym_priv_size) 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; kernel_maps__insert(map); dsos__add(dso); @@ -1230,85 +1336,77 @@ out_failure: } static int dso__load_vmlinux(struct dso *self, struct map *map, - const char *vmlinux, - symbol_filter_t filter, int v) + const char *vmlinux, symbol_filter_t filter) { int err, fd = open(vmlinux, O_RDONLY); + self->loaded = 1; + if (fd < 0) return -1; - err = dso__load_sym(self, map, self->long_name, fd, filter, 1, 0, v); + err = dso__load_sym(self, map, self->long_name, fd, filter, 1, 0); close(fd); return err; } -int dsos__load_kernel(const char *vmlinux, unsigned int sym_priv_size, - symbol_filter_t filter, int v, int use_modules) +int dso__load_kernel_sym(struct dso *self, symbol_filter_t filter, int use_modules) { int err = -1; - struct dso *dso = dso__new(vmlinux, sym_priv_size); - if (dso == NULL) - return -1; - - dso->short_name = "[kernel]"; - kernel_map = map__new2(0, dso); + kernel_map = map__new2(0, self); if (kernel_map == NULL) goto out_delete_dso; - kernel_map->map_ip = vdso__map_ip; + kernel_map->map_ip = kernel_map->unmap_ip = identity__map_ip; - if (use_modules && dsos__load_modules(sym_priv_size) < 0) { - fprintf(stderr, "Failed to load list of modules in use! " - "Continuing...\n"); + if (use_modules && dsos__load_modules() < 0) { + pr_warning("Failed to load list of modules in use! " + "Continuing...\n"); use_modules = 0; } - if (vmlinux) { - err = dso__load_vmlinux(dso, kernel_map, vmlinux, filter, v); - if (err > 0 && use_modules) { - int syms = dsos__load_modules_sym(filter, v); + err = dso__load_vmlinux(self, kernel_map, self->name, filter); + if (err > 0 && use_modules) { + int syms = dsos__load_modules_sym(filter); - if (syms < 0) - fprintf(stderr, "Failed to read module symbols!" - " Continuing...\n"); - else - err += syms; - } + if (syms < 0) + pr_warning("Failed to read module symbols!" + " Continuing...\n"); + else + err += syms; } if (err <= 0) - err = kernel_maps__load_kallsyms(filter, use_modules, v); + err = kernel_maps__load_kallsyms(filter, use_modules); if (err > 0) { - struct rb_node *node = rb_first(&dso->syms); + struct rb_node *node = rb_first(&self->syms); struct symbol *sym = rb_entry(node, struct symbol, rb_node); kernel_map->start = sym->start; - node = rb_last(&dso->syms); + node = rb_last(&self->syms); sym = rb_entry(node, struct symbol, rb_node); kernel_map->end = sym->end; - dso->origin = DSO__ORIG_KERNEL; + self->origin = DSO__ORIG_KERNEL; kernel_maps__insert(kernel_map); /* * Now that we have all sorted out, just set the ->end of all * maps: */ kernel_maps__fixup_end(); - dsos__add(dso); - if (v > 0) - kernel_maps__fprintf(stderr, v); + if (verbose) + kernel_maps__fprintf(stderr); } return err; out_delete_dso: - dso__delete(dso); + dso__delete(self); return -1; } @@ -1336,30 +1434,16 @@ static struct dso *dsos__find(const char *name) struct dso *dsos__findnew(const char *name) { struct dso *dso = dsos__find(name); - int nr; - - if (dso) - return dso; - - dso = dso__new(name, 0); - if (!dso) - goto out_delete_dso; - nr = dso__load(dso, NULL, NULL, verbose); - if (nr < 0) { - eprintf("Failed to open: %s\n", name); - goto out_delete_dso; + if (!dso) { + dso = dso__new(name); + if (dso != NULL) { + dsos__add(dso); + dso__set_basename(dso); + } } - if (!nr) - eprintf("No symbols found in: %s, maybe install a debug package?\n", name); - - dsos__add(dso); return dso; - -out_delete_dso: - dso__delete(dso); - return NULL; } void dsos__fprintf(FILE *fp) @@ -1370,21 +1454,52 @@ void dsos__fprintf(FILE *fp) dso__fprintf(pos, fp); } -int load_kernel(void) +size_t dsos__fprintf_buildid(FILE *fp) { - if (dsos__load_kernel(vmlinux_name, 0, NULL, verbose, modules) <= 0) - return -1; + struct dso *pos; + size_t ret = 0; + + list_for_each_entry(pos, &dsos, node) { + ret += dso__fprintf_buildid(pos, fp); + ret += fprintf(fp, " %s\n", pos->long_name); + } + return ret; +} + +struct dso *dsos__load_kernel(void) +{ + struct dso *kernel = dso__new(vmlinux_name); + + if (kernel == NULL) + return NULL; - vdso = dso__new("[vdso]", 0); + kernel->short_name = "[kernel]"; + vdso = dso__new("[vdso]"); if (!vdso) - return -1; + return NULL; + + if (sysfs__read_build_id("/sys/kernel/notes", kernel->build_id, + sizeof(kernel->build_id)) == 0) + kernel->has_build_id = true; + dsos__add(kernel); dsos__add(vdso); - return 0; + return kernel; +} + +int load_kernel(symbol_filter_t filter) +{ + struct dso *kernel = dsos__load_kernel(); + + if (kernel == NULL) + return -1; + + return dso__load_kernel_sym(kernel, filter, modules); } -void symbol__init(void) +void symbol__init(unsigned int priv_size) { elf_version(EV_CURRENT); + symbol__priv_size = priv_size; } diff --git a/tools/perf/util/symbol.h b/tools/perf/util/symbol.h index 2e4522edeb07..f0593a649c3d 100644 --- a/tools/perf/util/symbol.h +++ b/tools/perf/util/symbol.h @@ -2,6 +2,7 @@ #define __PERF_SYMBOL 1 #include <linux/types.h> +#include <stdbool.h> #include "types.h" #include <linux/list.h> #include <linux/rbtree.h> @@ -26,6 +27,16 @@ static inline char *bfd_demangle(void __used *v, const char __used *c, #endif #endif +/* + * libelf 0.8.x and earlier do not support ELF_C_READ_MMAP; + * for newer versions we can use mmap to reduce memory usage: + */ +#ifdef LIBELF_NO_MMAP +# define PERF_ELF_C_READ_MMAP ELF_C_READ +#else +# define PERF_ELF_C_READ_MMAP ELF_C_READ_MMAP +#endif + #ifndef DMGL_PARAMS #define DMGL_PARAMS (1 << 0) /* Include function args */ #define DMGL_ANSI (1 << 1) /* Include const, volatile, etc */ @@ -35,52 +46,58 @@ struct symbol { struct rb_node rb_node; u64 start; u64 end; - u64 hist_sum; - u64 *hist; - void *priv; char name[0]; }; +extern unsigned int symbol__priv_size; + +static inline void *symbol__priv(struct symbol *self) +{ + return ((void *)self) - symbol__priv_size; +} + struct dso { struct list_head node; struct rb_root syms; struct symbol *(*find_symbol)(struct dso *, u64 ip); - unsigned int sym_priv_size; - unsigned char adjust_symbols; - unsigned char slen_calculated; + u8 adjust_symbols:1; + u8 slen_calculated:1; + u8 loaded:1; + u8 has_build_id:1; unsigned char origin; + u8 build_id[BUILD_ID_SIZE]; + u16 long_name_len; const char *short_name; char *long_name; char name[0]; }; -extern const char *sym_hist_filter; - -typedef int (*symbol_filter_t)(struct map *map, struct symbol *sym); - -struct dso *dso__new(const char *name, unsigned int sym_priv_size); +struct dso *dso__new(const char *name); void dso__delete(struct dso *self); -static inline void *dso__sym_priv(struct dso *self, struct symbol *sym) -{ - return ((void *)sym) - self->sym_priv_size; -} - struct symbol *dso__find_symbol(struct dso *self, u64 ip); -int dsos__load_kernel(const char *vmlinux, unsigned int sym_priv_size, - symbol_filter_t filter, int verbose, int modules); -int dso__load(struct dso *self, struct map *map, symbol_filter_t filter, - int verbose); +int dsos__load_modules(void); struct dso *dsos__findnew(const char *name); +int dso__load(struct dso *self, struct map *map, symbol_filter_t filter); +int dso__load_kernel_sym(struct dso *self, symbol_filter_t filter, int modules); void dsos__fprintf(FILE *fp); +size_t dsos__fprintf_buildid(FILE *fp); +size_t dso__fprintf_buildid(struct dso *self, FILE *fp); size_t dso__fprintf(struct dso *self, FILE *fp); char dso__symtab_origin(const struct dso *self); +void dso__set_build_id(struct dso *self, void *build_id); + +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 load_kernel(void); +struct dso *dsos__load_kernel(void); +int load_kernel(symbol_filter_t filter); -void symbol__init(void); +void symbol__init(unsigned int priv_size); extern struct list_head dsos; extern struct map *kernel_map; diff --git a/tools/perf/util/thread.c b/tools/perf/util/thread.c index f53fad7c0a8d..0f6d78c9863a 100644 --- a/tools/perf/util/thread.c +++ b/tools/perf/util/thread.c @@ -33,6 +33,17 @@ int thread__set_comm(struct thread *self, const char *comm) return self->comm ? 0 : -ENOMEM; } +int thread__comm_len(struct thread *self) +{ + if (!self->comm_len) { + if (!self->comm) + return 0; + self->comm_len = strlen(self->comm); + } + + return self->comm_len; +} + static size_t thread__fprintf(struct thread *self, FILE *fp) { struct rb_node *nd; @@ -116,9 +127,9 @@ static void thread__remove_overlappings(struct thread *self, struct map *map) continue; if (verbose >= 2) { - printf("overlapping maps:\n"); - map__fprintf(map, stdout); - map__fprintf(pos, stdout); + fputs("overlapping maps:\n", stderr); + map__fprintf(map, stderr); + map__fprintf(pos, stderr); } rb_erase(&pos->rb_node, &self->maps); diff --git a/tools/perf/util/thread.h b/tools/perf/util/thread.h index 1abef3b7455d..53addd77ce8f 100644 --- a/tools/perf/util/thread.h +++ b/tools/perf/util/thread.h @@ -12,9 +12,11 @@ struct thread { pid_t pid; char shortname[3]; char *comm; + int comm_len; }; int thread__set_comm(struct thread *self, const char *comm); +int thread__comm_len(struct thread *self); struct thread *threads__findnew(pid_t pid); struct thread *register_idle_thread(void); void thread__insert_map(struct thread *self, struct map *map); diff --git a/tools/perf/util/trace-event-parse.c b/tools/perf/util/trace-event-parse.c index 4b61b497040e..eae560503086 100644 --- a/tools/perf/util/trace-event-parse.c +++ b/tools/perf/util/trace-event-parse.c @@ -286,16 +286,19 @@ void parse_ftrace_printk(char *file, unsigned int size __unused) char *line; char *next = NULL; char *addr_str; - char *fmt; int i; line = strtok_r(file, "\n", &next); while (line) { + addr_str = strsep(&line, ":"); + if (!line) { + warning("error parsing print strings"); + break; + } item = malloc_or_die(sizeof(*item)); - addr_str = strtok_r(line, ":", &fmt); item->addr = strtoull(addr_str, NULL, 16); /* fmt still has a space, skip it */ - item->printk = strdup(fmt+1); + item->printk = strdup(line+1); item->next = list; list = item; line = strtok_r(NULL, "\n", &next); diff --git a/tools/perf/util/util.h b/tools/perf/util/util.h index 9de2329dd44d..f2203a0946bc 100644 --- a/tools/perf/util/util.h +++ b/tools/perf/util/util.h @@ -134,6 +134,15 @@ extern void die(const char *err, ...) NORETURN __attribute__((format (printf, 1, extern int error(const char *err, ...) __attribute__((format (printf, 1, 2))); extern void warning(const char *err, ...) __attribute__((format (printf, 1, 2))); +#include "../../../include/linux/stringify.h" + +#define DIE_IF(cnd) \ + do { if (cnd) \ + die(" at (" __FILE__ ":" __stringify(__LINE__) "): " \ + __stringify(cnd) "\n"); \ + } while (0) + + extern void set_die_routine(void (*routine)(const char *err, va_list params) NORETURN); extern int prefixcmp(const char *str, const char *prefix); @@ -306,6 +315,7 @@ static inline int has_extension(const char *filename, const char *ext) #undef isascii #undef isspace #undef isdigit +#undef isxdigit #undef isalpha #undef isprint #undef isalnum @@ -323,6 +333,8 @@ extern unsigned char sane_ctype[256]; #define isascii(x) (((x) & ~0x7f) == 0) #define isspace(x) sane_istest(x,GIT_SPACE) #define isdigit(x) sane_istest(x,GIT_DIGIT) +#define isxdigit(x) \ + (sane_istest(toupper(x), GIT_ALPHA | GIT_DIGIT) && toupper(x) < 'G') #define isalpha(x) sane_istest(x,GIT_ALPHA) #define isalnum(x) sane_istest(x,GIT_ALPHA | GIT_DIGIT) #define isprint(x) sane_istest(x,GIT_PRINT) |