diff options
Diffstat (limited to 'tools/perf/builtin-trace.c')
-rw-r--r-- | tools/perf/builtin-trace.c | 908 |
1 files changed, 735 insertions, 173 deletions
diff --git a/tools/perf/builtin-trace.c b/tools/perf/builtin-trace.c index 22ab8e67c760..adbf28183560 100644 --- a/tools/perf/builtin-trace.c +++ b/tools/perf/builtin-trace.c @@ -18,9 +18,11 @@ #include <traceevent/event-parse.h> #include <api/fs/tracing_path.h> +#include <bpf/bpf.h> #include "builtin.h" #include "util/cgroup.h" #include "util/color.h" +#include "util/config.h" #include "util/debug.h" #include "util/env.h" #include "util/event.h" @@ -58,6 +60,7 @@ #include <linux/stringify.h> #include <linux/time64.h> #include <fcntl.h> +#include <sys/sysmacros.h> #include "sane_ctype.h" @@ -75,6 +78,7 @@ struct trace { struct { int max; struct syscall *table; + struct bpf_map *map; struct { struct perf_evsel *sys_enter, *sys_exit, @@ -89,6 +93,8 @@ struct trace { u64 base_time; FILE *output; unsigned long nr_events; + unsigned long nr_events_printed; + unsigned long max_events; struct strlist *ev_qualifier; struct { size_t nr; @@ -97,6 +103,7 @@ struct trace { struct { size_t nr; pid_t *entries; + struct bpf_map *map; } filter_pids; double duration_filter; double runtime_ms; @@ -106,6 +113,9 @@ struct trace { } stats; unsigned int max_stack; unsigned int min_stack; + int raw_augmented_syscalls_args_size; + bool raw_augmented_syscalls; + bool sort_events; bool not_ev_qualifier; bool live; bool full_time; @@ -119,9 +129,19 @@ struct trace { bool show_tool_stats; bool trace_syscalls; bool kernel_syscallchains; + s16 args_alignment; + bool show_tstamp; + bool show_duration; + bool show_zeros; + bool show_arg_names; + bool show_string_prefix; bool force; bool vfs_getname; int trace_pgfaults; + struct { + struct ordered_events data; + u64 last; + } oe; }; struct tp_field { @@ -181,7 +201,7 @@ static int __tp_field__init_uint(struct tp_field *field, int size, int offset, b return 0; } -static int tp_field__init_uint(struct tp_field *field, struct format_field *format_field, bool needs_swap) +static int tp_field__init_uint(struct tp_field *field, struct tep_format_field *format_field, bool needs_swap) { return __tp_field__init_uint(field, format_field->size, format_field->offset, needs_swap); } @@ -198,7 +218,7 @@ static int __tp_field__init_ptr(struct tp_field *field, int offset) return 0; } -static int tp_field__init_ptr(struct tp_field *field, struct format_field *format_field) +static int tp_field__init_ptr(struct tp_field *field, struct tep_format_field *format_field) { return __tp_field__init_ptr(field, format_field->offset); } @@ -214,7 +234,7 @@ static int perf_evsel__init_tp_uint_field(struct perf_evsel *evsel, struct tp_field *field, const char *name) { - struct format_field *format_field = perf_evsel__field(evsel, name); + struct tep_format_field *format_field = perf_evsel__field(evsel, name); if (format_field == NULL) return -1; @@ -230,7 +250,7 @@ static int perf_evsel__init_tp_ptr_field(struct perf_evsel *evsel, struct tp_field *field, const char *name) { - struct format_field *format_field = perf_evsel__field(evsel, name); + struct tep_format_field *format_field = perf_evsel__field(evsel, name); if (format_field == NULL) return -1; @@ -253,7 +273,8 @@ static int perf_evsel__init_syscall_tp(struct perf_evsel *evsel) struct syscall_tp *sc = evsel->priv = malloc(sizeof(struct syscall_tp)); if (evsel->priv != NULL) { - if (perf_evsel__init_tp_uint_field(evsel, &sc->id, "__syscall_nr")) + if (perf_evsel__init_tp_uint_field(evsel, &sc->id, "__syscall_nr") && + perf_evsel__init_tp_uint_field(evsel, &sc->id, "nr")) goto out_delete; return 0; } @@ -264,12 +285,17 @@ out_delete: return -ENOENT; } -static int perf_evsel__init_augmented_syscall_tp(struct perf_evsel *evsel) +static int perf_evsel__init_augmented_syscall_tp(struct perf_evsel *evsel, struct perf_evsel *tp) { struct syscall_tp *sc = evsel->priv = malloc(sizeof(struct syscall_tp)); - if (evsel->priv != NULL) { /* field, sizeof_field, offsetof_field */ - if (__tp_field__init_uint(&sc->id, sizeof(long), sizeof(long long), evsel->needs_swap)) + if (evsel->priv != NULL) { + struct tep_format_field *syscall_id = perf_evsel__field(tp, "id"); + if (syscall_id == NULL) + syscall_id = perf_evsel__field(tp, "__syscall_nr"); + if (syscall_id == NULL) + goto out_delete; + if (__tp_field__init_uint(&sc->id, syscall_id->size, syscall_id->offset, evsel->needs_swap)) goto out_delete; return 0; @@ -288,6 +314,13 @@ static int perf_evsel__init_augmented_syscall_tp_args(struct perf_evsel *evsel) return __tp_field__init_ptr(&sc->args, sc->id.offset + sizeof(u64)); } +static int perf_evsel__init_augmented_syscall_tp_ret(struct perf_evsel *evsel) +{ + struct syscall_tp *sc = evsel->priv; + + return __tp_field__init_uint(&sc->ret, sizeof(u64), sc->id.offset + sizeof(u64), evsel->needs_swap); +} + static int perf_evsel__init_raw_syscall_tp(struct perf_evsel *evsel, void *handler) { evsel->priv = malloc(sizeof(struct syscall_tp)); @@ -335,21 +368,25 @@ out_delete: ({ struct syscall_tp *fields = evsel->priv; \ fields->name.pointer(&fields->name, sample); }) -size_t strarray__scnprintf(struct strarray *sa, char *bf, size_t size, const char *intfmt, int val) +size_t strarray__scnprintf(struct strarray *sa, char *bf, size_t size, const char *intfmt, bool show_prefix, int val) { int idx = val - sa->offset; - if (idx < 0 || idx >= sa->nr_entries || sa->entries[idx] == NULL) - return scnprintf(bf, size, intfmt, val); + if (idx < 0 || idx >= sa->nr_entries || sa->entries[idx] == NULL) { + size_t printed = scnprintf(bf, size, intfmt, val); + if (show_prefix) + printed += scnprintf(bf + printed, size - printed, " /* %s??? */", sa->prefix); + return printed; + } - return scnprintf(bf, size, "%s", sa->entries[idx]); + return scnprintf(bf, size, "%s%s", show_prefix ? sa->prefix : "", sa->entries[idx]); } static size_t __syscall_arg__scnprintf_strarray(char *bf, size_t size, const char *intfmt, struct syscall_arg *arg) { - return strarray__scnprintf(arg->parm, bf, size, intfmt, arg->val); + return strarray__scnprintf(arg->parm, bf, size, intfmt, arg->show_string_prefix, arg->val); } static size_t syscall_arg__scnprintf_strarray(char *bf, size_t size, @@ -360,34 +397,32 @@ static size_t syscall_arg__scnprintf_strarray(char *bf, size_t size, #define SCA_STRARRAY syscall_arg__scnprintf_strarray -struct strarrays { - int nr_entries; - struct strarray **entries; -}; - -#define DEFINE_STRARRAYS(array) struct strarrays strarrays__##array = { \ - .nr_entries = ARRAY_SIZE(array), \ - .entries = array, \ -} - -size_t syscall_arg__scnprintf_strarrays(char *bf, size_t size, - struct syscall_arg *arg) +size_t strarrays__scnprintf(struct strarrays *sas, char *bf, size_t size, const char *intfmt, bool show_prefix, int val) { - struct strarrays *sas = arg->parm; + size_t printed; int i; for (i = 0; i < sas->nr_entries; ++i) { struct strarray *sa = sas->entries[i]; - int idx = arg->val - sa->offset; + int idx = val - sa->offset; if (idx >= 0 && idx < sa->nr_entries) { if (sa->entries[idx] == NULL) break; - return scnprintf(bf, size, "%s", sa->entries[idx]); + return scnprintf(bf, size, "%s%s", show_prefix ? sa->prefix : "", sa->entries[idx]); } } - return scnprintf(bf, size, "%d", arg->val); + printed = scnprintf(bf, size, intfmt, val); + if (show_prefix) + printed += scnprintf(bf + printed, size - printed, " /* %s??? */", sas->entries[0]->prefix); + return printed; +} + +size_t syscall_arg__scnprintf_strarrays(char *bf, size_t size, + struct syscall_arg *arg) +{ + return strarrays__scnprintf(arg->parm, bf, size, "%d", arg->show_string_prefix, arg->val); } #ifndef AT_FDCWD @@ -398,9 +433,10 @@ static size_t syscall_arg__scnprintf_fd_at(char *bf, size_t size, struct syscall_arg *arg) { int fd = arg->val; + const char *prefix = "AT_FD"; if (fd == AT_FDCWD) - return scnprintf(bf, size, "CWD"); + return scnprintf(bf, size, "%s%s", arg->show_string_prefix ? prefix : "", "CWD"); return syscall_arg__scnprintf_fd(bf, size, arg); } @@ -417,6 +453,13 @@ size_t syscall_arg__scnprintf_hex(char *bf, size_t size, struct syscall_arg *arg return scnprintf(bf, size, "%#lx", arg->val); } +size_t syscall_arg__scnprintf_ptr(char *bf, size_t size, struct syscall_arg *arg) +{ + if (arg->val == 0) + return scnprintf(bf, size, "NULL"); + return syscall_arg__scnprintf_hex(bf, size, arg); +} + size_t syscall_arg__scnprintf_int(char *bf, size_t size, struct syscall_arg *arg) { return scnprintf(bf, size, "%d", arg->val); @@ -431,13 +474,13 @@ static const char *bpf_cmd[] = { "MAP_CREATE", "MAP_LOOKUP_ELEM", "MAP_UPDATE_ELEM", "MAP_DELETE_ELEM", "MAP_GET_NEXT_KEY", "PROG_LOAD", }; -static DEFINE_STRARRAY(bpf_cmd); +static DEFINE_STRARRAY(bpf_cmd, "BPF_"); static const char *epoll_ctl_ops[] = { "ADD", "DEL", "MOD", }; -static DEFINE_STRARRAY_OFFSET(epoll_ctl_ops, 1); +static DEFINE_STRARRAY_OFFSET(epoll_ctl_ops, "EPOLL_CTL_", 1); static const char *itimers[] = { "REAL", "VIRTUAL", "PROF", }; -static DEFINE_STRARRAY(itimers); +static DEFINE_STRARRAY(itimers, "ITIMER_"); static const char *keyctl_options[] = { "GET_KEYRING_ID", "JOIN_SESSION_KEYRING", "UPDATE", "REVOKE", "CHOWN", @@ -446,7 +489,7 @@ static const char *keyctl_options[] = { "ASSUME_AUTHORITY", "GET_SECURITY", "SESSION_TO_PARENT", "REJECT", "INSTANTIATE_IOV", "INVALIDATE", "GET_PERSISTENT", }; -static DEFINE_STRARRAY(keyctl_options); +static DEFINE_STRARRAY(keyctl_options, "KEYCTL_"); static const char *whences[] = { "SET", "CUR", "END", #ifdef SEEK_DATA @@ -456,7 +499,7 @@ static const char *whences[] = { "SET", "CUR", "END", "HOLE", #endif }; -static DEFINE_STRARRAY(whences); +static DEFINE_STRARRAY(whences, "SEEK_"); static const char *fcntl_cmds[] = { "DUPFD", "GETFD", "SETFD", "GETFL", "SETFL", "GETLK", "SETLK", @@ -464,7 +507,7 @@ static const char *fcntl_cmds[] = { "SETLK64", "SETLKW64", "SETOWN_EX", "GETOWN_EX", "GETOWNER_UIDS", }; -static DEFINE_STRARRAY(fcntl_cmds); +static DEFINE_STRARRAY(fcntl_cmds, "F_"); static const char *fcntl_linux_specific_cmds[] = { "SETLEASE", "GETLEASE", "NOTIFY", [5] = "CANCELLK", "DUPFD_CLOEXEC", @@ -472,7 +515,7 @@ static const char *fcntl_linux_specific_cmds[] = { "GET_RW_HINT", "SET_RW_HINT", "GET_FILE_RW_HINT", "SET_FILE_RW_HINT", }; -static DEFINE_STRARRAY_OFFSET(fcntl_linux_specific_cmds, F_LINUX_SPECIFIC_BASE); +static DEFINE_STRARRAY_OFFSET(fcntl_linux_specific_cmds, "F_", F_LINUX_SPECIFIC_BASE); static struct strarray *fcntl_cmds_arrays[] = { &strarray__fcntl_cmds, @@ -486,39 +529,31 @@ static const char *rlimit_resources[] = { "MEMLOCK", "AS", "LOCKS", "SIGPENDING", "MSGQUEUE", "NICE", "RTPRIO", "RTTIME", }; -static DEFINE_STRARRAY(rlimit_resources); +static DEFINE_STRARRAY(rlimit_resources, "RLIMIT_"); static const char *sighow[] = { "BLOCK", "UNBLOCK", "SETMASK", }; -static DEFINE_STRARRAY(sighow); +static DEFINE_STRARRAY(sighow, "SIG_"); static const char *clockid[] = { "REALTIME", "MONOTONIC", "PROCESS_CPUTIME_ID", "THREAD_CPUTIME_ID", "MONOTONIC_RAW", "REALTIME_COARSE", "MONOTONIC_COARSE", "BOOTTIME", "REALTIME_ALARM", "BOOTTIME_ALARM", "SGI_CYCLE", "TAI" }; -static DEFINE_STRARRAY(clockid); - -static const char *socket_families[] = { - "UNSPEC", "LOCAL", "INET", "AX25", "IPX", "APPLETALK", "NETROM", - "BRIDGE", "ATMPVC", "X25", "INET6", "ROSE", "DECnet", "NETBEUI", - "SECURITY", "KEY", "NETLINK", "PACKET", "ASH", "ECONET", "ATMSVC", - "RDS", "SNA", "IRDA", "PPPOX", "WANPIPE", "LLC", "IB", "CAN", "TIPC", - "BLUETOOTH", "IUCV", "RXRPC", "ISDN", "PHONET", "IEEE802154", "CAIF", - "ALG", "NFC", "VSOCK", -}; -static DEFINE_STRARRAY(socket_families); +static DEFINE_STRARRAY(clockid, "CLOCK_"); static size_t syscall_arg__scnprintf_access_mode(char *bf, size_t size, struct syscall_arg *arg) { + bool show_prefix = arg->show_string_prefix; + const char *suffix = "_OK"; size_t printed = 0; int mode = arg->val; if (mode == F_OK) /* 0 */ - return scnprintf(bf, size, "F"); + return scnprintf(bf, size, "F%s", show_prefix ? suffix : ""); #define P_MODE(n) \ if (mode & n##_OK) { \ - printed += scnprintf(bf + printed, size - printed, "%s", #n); \ + printed += scnprintf(bf + printed, size - printed, "%s%s", #n, show_prefix ? suffix : ""); \ mode &= ~n##_OK; \ } @@ -543,11 +578,13 @@ static size_t syscall_arg__scnprintf_filename(char *bf, size_t size, static size_t syscall_arg__scnprintf_pipe_flags(char *bf, size_t size, struct syscall_arg *arg) { + bool show_prefix = arg->show_string_prefix; + const char *prefix = "O_"; int printed = 0, flags = arg->val; #define P_FLAG(n) \ if (flags & O_##n) { \ - printed += scnprintf(bf + printed, size - printed, "%s%s", printed ? "|" : "", #n); \ + printed += scnprintf(bf + printed, size - printed, "%s%s%s", printed ? "|" : "", show_prefix ? prefix : "", #n); \ flags &= ~O_##n; \ } @@ -573,11 +610,13 @@ static size_t syscall_arg__scnprintf_pipe_flags(char *bf, size_t size, static size_t syscall_arg__scnprintf_getrandom_flags(char *bf, size_t size, struct syscall_arg *arg) { + bool show_prefix = arg->show_string_prefix; + const char *prefix = "GRND_"; int printed = 0, flags = arg->val; #define P_FLAG(n) \ if (flags & GRND_##n) { \ - printed += scnprintf(bf + printed, size - printed, "%s%s", printed ? "|" : "", #n); \ + printed += scnprintf(bf + printed, size - printed, "%s%s%s", printed ? "|" : "", show_prefix ? prefix : "", #n); \ flags &= ~GRND_##n; \ } @@ -615,6 +654,7 @@ static size_t syscall_arg__scnprintf_getrandom_flags(char *bf, size_t size, struct syscall_arg_fmt { size_t (*scnprintf)(char *bf, size_t size, struct syscall_arg *arg); + unsigned long (*mask_val)(struct syscall_arg *arg, unsigned long val); void *parm; const char *name; bool show_zero; @@ -631,10 +671,15 @@ static struct syscall_fmt { } syscall_fmts[] = { { .name = "access", .arg = { [1] = { .scnprintf = SCA_ACCMODE, /* mode */ }, }, }, + { .name = "arch_prctl", + .arg = { [0] = { .scnprintf = SCA_X86_ARCH_PRCTL_CODE, /* code */ }, + [1] = { .scnprintf = SCA_PTR, /* arg2 */ }, }, }, + { .name = "bind", + .arg = { [1] = { .scnprintf = SCA_SOCKADDR, /* umyaddr */ }, }, }, { .name = "bpf", .arg = { [0] = STRARRAY(cmd, bpf_cmd), }, }, { .name = "brk", .hexret = true, - .arg = { [0] = { .scnprintf = SCA_HEX, /* brk */ }, }, }, + .arg = { [0] = { .scnprintf = SCA_PTR, /* brk */ }, }, }, { .name = "clock_gettime", .arg = { [0] = STRARRAY(clk_id, clockid), }, }, { .name = "clone", .errpid = true, .nr_args = 5, @@ -645,6 +690,8 @@ static struct syscall_fmt { [4] = { .name = "tls", .scnprintf = SCA_HEX, }, }, }, { .name = "close", .arg = { [0] = { .scnprintf = SCA_CLOSE_FD, /* fd */ }, }, }, + { .name = "connect", + .arg = { [1] = { .scnprintf = SCA_SOCKADDR, /* servaddr */ }, }, }, { .name = "epoll_ctl", .arg = { [1] = STRARRAY(op, epoll_ctl_ops), }, }, { .name = "eventfd2", @@ -710,31 +757,25 @@ static struct syscall_fmt { .arg = { [0] = { .scnprintf = SCA_FDAT, /* fd */ }, }, }, { .name = "mknodat", .arg = { [0] = { .scnprintf = SCA_FDAT, /* fd */ }, }, }, - { .name = "mlock", - .arg = { [0] = { .scnprintf = SCA_HEX, /* addr */ }, }, }, - { .name = "mlockall", - .arg = { [0] = { .scnprintf = SCA_HEX, /* addr */ }, }, }, { .name = "mmap", .hexret = true, /* The standard mmap maps to old_mmap on s390x */ #if defined(__s390x__) .alias = "old_mmap", #endif - .arg = { [0] = { .scnprintf = SCA_HEX, /* addr */ }, - [2] = { .scnprintf = SCA_MMAP_PROT, /* prot */ }, - [3] = { .scnprintf = SCA_MMAP_FLAGS, /* flags */ }, }, }, + .arg = { [2] = { .scnprintf = SCA_MMAP_PROT, /* prot */ }, + [3] = { .scnprintf = SCA_MMAP_FLAGS, /* flags */ }, + [5] = { .scnprintf = SCA_HEX, /* offset */ }, }, }, + { .name = "mount", + .arg = { [0] = { .scnprintf = SCA_FILENAME, /* dev_name */ }, + [3] = { .scnprintf = SCA_MOUNT_FLAGS, /* flags */ + .mask_val = SCAMV_MOUNT_FLAGS, /* flags */ }, }, }, { .name = "mprotect", .arg = { [0] = { .scnprintf = SCA_HEX, /* start */ }, [2] = { .scnprintf = SCA_MMAP_PROT, /* prot */ }, }, }, { .name = "mq_unlink", .arg = { [0] = { .scnprintf = SCA_FILENAME, /* u_name */ }, }, }, { .name = "mremap", .hexret = true, - .arg = { [0] = { .scnprintf = SCA_HEX, /* addr */ }, - [3] = { .scnprintf = SCA_MREMAP_FLAGS, /* flags */ }, - [4] = { .scnprintf = SCA_HEX, /* new_addr */ }, }, }, - { .name = "munlock", - .arg = { [0] = { .scnprintf = SCA_HEX, /* addr */ }, }, }, - { .name = "munmap", - .arg = { [0] = { .scnprintf = SCA_HEX, /* addr */ }, }, }, + .arg = { [3] = { .scnprintf = SCA_MREMAP_FLAGS, /* flags */ }, }, }, { .name = "name_to_handle_at", .arg = { [0] = { .scnprintf = SCA_FDAT, /* dfd */ }, }, }, { .name = "newfstatat", @@ -763,7 +804,7 @@ static struct syscall_fmt { [3] = { .scnprintf = SCA_INT, /* pkey */ }, }, }, { .name = "poll", .timeout = true, }, { .name = "ppoll", .timeout = true, }, - { .name = "prctl", .alias = "arch_prctl", + { .name = "prctl", .arg = { [0] = { .scnprintf = SCA_PRCTL_OPTION, /* option */ }, [1] = { .scnprintf = SCA_PRCTL_ARG2, /* arg2 */ }, [2] = { .scnprintf = SCA_PRCTL_ARG3, /* arg3 */ }, }, }, @@ -781,7 +822,12 @@ static struct syscall_fmt { { .name = "recvmsg", .arg = { [2] = { .scnprintf = SCA_MSG_FLAGS, /* flags */ }, }, }, { .name = "renameat", - .arg = { [0] = { .scnprintf = SCA_FDAT, /* dfd */ }, }, }, + .arg = { [0] = { .scnprintf = SCA_FDAT, /* olddirfd */ }, + [2] = { .scnprintf = SCA_FDAT, /* newdirfd */ }, }, }, + { .name = "renameat2", + .arg = { [0] = { .scnprintf = SCA_FDAT, /* olddirfd */ }, + [2] = { .scnprintf = SCA_FDAT, /* newdirfd */ }, + [4] = { .scnprintf = SCA_RENAMEAT2_FLAGS, /* flags */ }, }, }, { .name = "rt_sigaction", .arg = { [0] = { .scnprintf = SCA_SIGNUM, /* sig */ }, }, }, { .name = "rt_sigprocmask", @@ -801,7 +847,8 @@ static struct syscall_fmt { { .name = "sendmsg", .arg = { [2] = { .scnprintf = SCA_MSG_FLAGS, /* flags */ }, }, }, { .name = "sendto", - .arg = { [3] = { .scnprintf = SCA_MSG_FLAGS, /* flags */ }, }, }, + .arg = { [3] = { .scnprintf = SCA_MSG_FLAGS, /* flags */ }, + [4] = { .scnprintf = SCA_SOCKADDR, /* addr */ }, }, }, { .name = "set_tid_address", .errpid = true, }, { .name = "setitimer", .arg = { [0] = STRARRAY(which, itimers), }, }, @@ -830,6 +877,8 @@ static struct syscall_fmt { .arg = { [2] = { .scnprintf = SCA_SIGNUM, /* sig */ }, }, }, { .name = "tkill", .arg = { [1] = { .scnprintf = SCA_SIGNUM, /* sig */ }, }, }, + { .name = "umount2", .alias = "umount", + .arg = { [0] = { .scnprintf = SCA_FILENAME, /* name */ }, }, }, { .name = "uname", .alias = "newuname", }, { .name = "unlinkat", .arg = { [0] = { .scnprintf = SCA_FDAT, /* dfd */ }, }, }, @@ -853,21 +902,39 @@ static struct syscall_fmt *syscall_fmt__find(const char *name) return bsearch(name, syscall_fmts, nmemb, sizeof(struct syscall_fmt), syscall_fmt__cmp); } +static struct syscall_fmt *syscall_fmt__find_by_alias(const char *alias) +{ + int i, nmemb = ARRAY_SIZE(syscall_fmts); + + for (i = 0; i < nmemb; ++i) { + if (syscall_fmts[i].alias && strcmp(syscall_fmts[i].alias, alias) == 0) + return &syscall_fmts[i]; + } + + return NULL; +} + /* * is_exit: is this "exit" or "exit_group"? * is_open: is this "open" or "openat"? To associate the fd returned in sys_exit with the pathname in sys_enter. + * args_size: sum of the sizes of the syscall arguments, anything after that is augmented stuff: pathname for openat, etc. */ struct syscall { - struct event_format *tp_format; + struct tep_event *tp_format; int nr_args; + int args_size; bool is_exit; bool is_open; - struct format_field *args; + struct tep_format_field *args; const char *name; struct syscall_fmt *fmt; struct syscall_arg_fmt *arg_fmt; }; +struct bpf_map_syscall_entry { + bool enabled; +}; + /* * We need to have this 'calculated' boolean because in some cases we really * don't know what is the duration of a syscall, for instance, when we start @@ -914,9 +981,9 @@ struct thread_trace { char *name; } filename; struct { - int max; - char **table; - } paths; + int max; + struct file *table; + } files; struct intlist *syscall_stats; }; @@ -926,7 +993,7 @@ static struct thread_trace *thread_trace__new(void) struct thread_trace *ttrace = zalloc(sizeof(struct thread_trace)); if (ttrace) - ttrace->paths.max = -1; + ttrace->files.max = -1; ttrace->syscall_stats = intlist__new(NULL); @@ -970,30 +1037,48 @@ void syscall_arg__set_ret_scnprintf(struct syscall_arg *arg, static const size_t trace__entry_str_size = 2048; -static int trace__set_fd_pathname(struct thread *thread, int fd, const char *pathname) +static struct file *thread_trace__files_entry(struct thread_trace *ttrace, int fd) { - struct thread_trace *ttrace = thread__priv(thread); - - if (fd > ttrace->paths.max) { - char **npath = realloc(ttrace->paths.table, (fd + 1) * sizeof(char *)); + if (fd > ttrace->files.max) { + struct file *nfiles = realloc(ttrace->files.table, (fd + 1) * sizeof(struct file)); - if (npath == NULL) - return -1; + if (nfiles == NULL) + return NULL; - if (ttrace->paths.max != -1) { - memset(npath + ttrace->paths.max + 1, 0, - (fd - ttrace->paths.max) * sizeof(char *)); + if (ttrace->files.max != -1) { + memset(nfiles + ttrace->files.max + 1, 0, + (fd - ttrace->files.max) * sizeof(struct file)); } else { - memset(npath, 0, (fd + 1) * sizeof(char *)); + memset(nfiles, 0, (fd + 1) * sizeof(struct file)); } - ttrace->paths.table = npath; - ttrace->paths.max = fd; + ttrace->files.table = nfiles; + ttrace->files.max = fd; } - ttrace->paths.table[fd] = strdup(pathname); + return ttrace->files.table + fd; +} - return ttrace->paths.table[fd] != NULL ? 0 : -1; +struct file *thread__files_entry(struct thread *thread, int fd) +{ + return thread_trace__files_entry(thread__priv(thread), fd); +} + +static int trace__set_fd_pathname(struct thread *thread, int fd, const char *pathname) +{ + struct thread_trace *ttrace = thread__priv(thread); + struct file *file = thread_trace__files_entry(ttrace, fd); + + if (file != NULL) { + struct stat st; + if (stat(pathname, &st) == 0) + file->dev_maj = major(st.st_rdev); + file->pathname = strdup(pathname); + if (file->pathname) + return 0; + } + + return -1; } static int thread__read_fd_path(struct thread *thread, int fd) @@ -1033,7 +1118,7 @@ static const char *thread__fd_path(struct thread *thread, int fd, if (fd < 0) return NULL; - if ((fd > ttrace->paths.max || ttrace->paths.table[fd] == NULL)) { + if ((fd > ttrace->files.max || ttrace->files.table[fd].pathname == NULL)) { if (!trace->live) return NULL; ++trace->stats.proc_getname; @@ -1041,7 +1126,7 @@ static const char *thread__fd_path(struct thread *thread, int fd, return NULL; } - return ttrace->paths.table[fd]; + return ttrace->files.table[fd].pathname; } size_t syscall_arg__scnprintf_fd(char *bf, size_t size, struct syscall_arg *arg) @@ -1080,8 +1165,8 @@ static size_t syscall_arg__scnprintf_close_fd(char *bf, size_t size, size_t printed = syscall_arg__scnprintf_fd(bf, size, arg); struct thread_trace *ttrace = thread__priv(arg->thread); - if (ttrace && fd >= 0 && fd <= ttrace->paths.max) - zfree(&ttrace->paths.table[fd]); + if (ttrace && fd >= 0 && fd <= ttrace->files.max) + zfree(&ttrace->files.table[fd].pathname); return printed; } @@ -1095,11 +1180,21 @@ static void thread__set_filename_pos(struct thread *thread, const char *bf, ttrace->filename.entry_str_pos = bf - ttrace->entry_str; } +static size_t syscall_arg__scnprintf_augmented_string(struct syscall_arg *arg, char *bf, size_t size) +{ + struct augmented_arg *augmented_arg = arg->augmented.args; + + return scnprintf(bf, size, "\"%.*s\"", augmented_arg->size, augmented_arg->value); +} + static size_t syscall_arg__scnprintf_filename(char *bf, size_t size, struct syscall_arg *arg) { unsigned long ptr = arg->val; + if (arg->augmented.args) + return syscall_arg__scnprintf_augmented_string(arg, bf, size); + if (!arg->trace->vfs_getname) return scnprintf(bf, size, "%#x", ptr); @@ -1142,11 +1237,9 @@ static void sig_handler(int sig) interrupted = sig == SIGINT; } -static size_t trace__fprintf_entry_head(struct trace *trace, struct thread *thread, - u64 duration, bool duration_calculated, u64 tstamp, FILE *fp) +static size_t trace__fprintf_comm_tid(struct trace *trace, struct thread *thread, FILE *fp) { - size_t printed = trace__fprintf_tstamp(trace, tstamp, fp); - printed += fprintf_duration(duration, duration_calculated, fp); + size_t printed = 0; if (trace->multiple_threads) { if (trace->show_comm) @@ -1157,6 +1250,18 @@ static size_t trace__fprintf_entry_head(struct trace *trace, struct thread *thre return printed; } +static size_t trace__fprintf_entry_head(struct trace *trace, struct thread *thread, + u64 duration, bool duration_calculated, u64 tstamp, FILE *fp) +{ + size_t printed = 0; + + if (trace->show_tstamp) + printed = trace__fprintf_tstamp(trace, tstamp, fp); + if (trace->show_duration) + printed += fprintf_duration(duration, duration_calculated, fp); + return printed + trace__fprintf_comm_tid(trace, thread, fp); +} + static int trace__process_event(struct trace *trace, struct machine *machine, union perf_event *event, struct perf_sample *sample) { @@ -1220,7 +1325,7 @@ static int trace__symbols_init(struct trace *trace, struct perf_evlist *evlist) err = __machine__synthesize_threads(trace->host, &trace->tool, &trace->opts.target, evlist->threads, trace__tool_process, false, - trace->opts.proc_map_timeout, 1); + 1); out: if (err) symbol__exit(); @@ -1258,10 +1363,12 @@ static int syscall__alloc_arg_fmts(struct syscall *sc, int nr_args) static int syscall__set_arg_fmts(struct syscall *sc) { - struct format_field *field; + struct tep_format_field *field, *last_field = NULL; int idx = 0, len; for (field = sc->args; field; field = field->next, ++idx) { + last_field = field; + if (sc->fmt && sc->fmt->arg[idx].scnprintf) continue; @@ -1270,8 +1377,8 @@ static int syscall__set_arg_fmts(struct syscall *sc) strcmp(field->name, "path") == 0 || strcmp(field->name, "pathname") == 0)) sc->arg_fmt[idx].scnprintf = SCA_FILENAME; - else if (field->flags & FIELD_IS_POINTER) - sc->arg_fmt[idx].scnprintf = syscall_arg__scnprintf_hex; + else if ((field->flags & TEP_FIELD_IS_POINTER) || strstr(field->name, "addr")) + sc->arg_fmt[idx].scnprintf = SCA_PTR; else if (strcmp(field->type, "pid_t") == 0) sc->arg_fmt[idx].scnprintf = SCA_PID; else if (strcmp(field->type, "umode_t") == 0) @@ -1292,6 +1399,9 @@ static int syscall__set_arg_fmts(struct syscall *sc) } } + if (last_field) + sc->args_size = last_field->offset + last_field->size; + return 0; } @@ -1459,6 +1569,19 @@ static size_t syscall__scnprintf_name(struct syscall *sc, char *bf, size_t size, return scnprintf(bf, size, "arg%d: ", arg->idx); } +/* + * Check if the value is in fact zero, i.e. mask whatever needs masking, such + * as mount 'flags' argument that needs ignoring some magic flag, see comment + * in tools/perf/trace/beauty/mount_flags.c + */ +static unsigned long syscall__mask_val(struct syscall *sc, struct syscall_arg *arg, unsigned long val) +{ + if (sc->arg_fmt && sc->arg_fmt[arg->idx].mask_val) + return sc->arg_fmt[arg->idx].mask_val(arg, val); + + return val; +} + static size_t syscall__scnprintf_val(struct syscall *sc, char *bf, size_t size, struct syscall_arg *arg, unsigned long val) { @@ -1472,18 +1595,23 @@ static size_t syscall__scnprintf_val(struct syscall *sc, char *bf, size_t size, } static size_t syscall__scnprintf_args(struct syscall *sc, char *bf, size_t size, - unsigned char *args, struct trace *trace, - struct thread *thread) + unsigned char *args, void *augmented_args, int augmented_args_size, + struct trace *trace, struct thread *thread) { size_t printed = 0; unsigned long val; u8 bit = 1; struct syscall_arg arg = { .args = args, + .augmented = { + .size = augmented_args_size, + .args = augmented_args, + }, .idx = 0, .mask = 0, .trace = trace, .thread = thread, + .show_string_prefix = trace->show_string_prefix, }; struct thread_trace *ttrace = thread__priv(thread); @@ -1495,7 +1623,7 @@ static size_t syscall__scnprintf_args(struct syscall *sc, char *bf, size_t size, ttrace->ret_scnprintf = NULL; if (sc->args != NULL) { - struct format_field *field; + struct tep_format_field *field; for (field = sc->args; field; field = field->next, ++arg.idx, bit <<= 1) { @@ -1503,6 +1631,11 @@ static size_t syscall__scnprintf_args(struct syscall *sc, char *bf, size_t size, continue; val = syscall_arg__val(&arg, arg.idx); + /* + * Some syscall args need some mask, most don't and + * return val untouched. + */ + val = syscall__mask_val(sc, &arg, val); /* * Suppress this argument if its value is zero and @@ -1510,6 +1643,7 @@ static size_t syscall__scnprintf_args(struct syscall *sc, char *bf, size_t size, * strarray for it. */ if (val == 0 && + !trace->show_zeros && !(sc->arg_fmt && (sc->arg_fmt[arg.idx].show_zero || sc->arg_fmt[arg.idx].scnprintf == SCA_STRARRAY || @@ -1517,8 +1651,11 @@ static size_t syscall__scnprintf_args(struct syscall *sc, char *bf, size_t size, sc->arg_fmt[arg.idx].parm)) continue; - printed += scnprintf(bf + printed, size - printed, - "%s%s: ", printed ? ", " : "", field->name); + printed += scnprintf(bf + printed, size - printed, "%s", printed ? ", " : ""); + + if (trace->show_arg_names) + printed += scnprintf(bf + printed, size - printed, "%s: ", field->name); + printed += syscall__scnprintf_val(sc, bf + printed, size - printed, &arg, val); } } else if (IS_ERR(sc->tp_format)) { @@ -1631,9 +1768,11 @@ static int trace__printf_interrupted_entry(struct trace *trace) return 0; printed = trace__fprintf_entry_head(trace, trace->current, 0, false, ttrace->entry_time, trace->output); - printed += fprintf(trace->output, "%-70s) ...\n", ttrace->entry_str); + printed += fprintf(trace->output, ")%-*s ...\n", trace->args_alignment, ttrace->entry_str); ttrace->entry_pending = false; + ++trace->nr_events_printed; + return printed; } @@ -1654,15 +1793,43 @@ static int trace__fprintf_sample(struct trace *trace, struct perf_evsel *evsel, return printed; } +static void *syscall__augmented_args(struct syscall *sc, struct perf_sample *sample, int *augmented_args_size, int raw_augmented_args_size) +{ + void *augmented_args = NULL; + /* + * For now with BPF raw_augmented we hook into raw_syscalls:sys_enter + * and there we get all 6 syscall args plus the tracepoint common fields + * that gets calculated at the start and the syscall_nr (another long). + * So we check if that is the case and if so don't look after the + * sc->args_size but always after the full raw_syscalls:sys_enter payload, + * which is fixed. + * + * We'll revisit this later to pass s->args_size to the BPF augmenter + * (now tools/perf/examples/bpf/augmented_raw_syscalls.c, so that it + * copies only what we need for each syscall, like what happens when we + * use syscalls:sys_enter_NAME, so that we reduce the kernel/userspace + * traffic to just what is needed for each syscall. + */ + int args_size = raw_augmented_args_size ?: sc->args_size; + + *augmented_args_size = sample->raw_size - args_size; + if (*augmented_args_size > 0) + augmented_args = sample->raw_data + args_size; + + return augmented_args; +} + static int trace__sys_enter(struct trace *trace, struct perf_evsel *evsel, union perf_event *event __maybe_unused, struct perf_sample *sample) { char *msg; void *args; - size_t printed = 0; + int printed = 0; struct thread *thread; int id = perf_evsel__sc_tp_uint(evsel, id, sample), err = -1; + int augmented_args_size = 0; + void *augmented_args = NULL; struct syscall *sc = trace__syscall_info(trace, evsel, id); struct thread_trace *ttrace; @@ -1686,18 +1853,34 @@ static int trace__sys_enter(struct trace *trace, struct perf_evsel *evsel, if (!(trace->duration_filter || trace->summary_only || trace->min_stack)) trace__printf_interrupted_entry(trace); - + /* + * If this is raw_syscalls.sys_enter, then it always comes with the 6 possible + * arguments, even if the syscall being handled, say "openat", uses only 4 arguments + * this breaks syscall__augmented_args() check for augmented args, as we calculate + * syscall->args_size using each syscalls:sys_enter_NAME tracefs format file, + * so when handling, say the openat syscall, we end up getting 6 args for the + * raw_syscalls:sys_enter event, when we expected just 4, we end up mistakenly + * thinking that the extra 2 u64 args are the augmented filename, so just check + * here and avoid using augmented syscalls when the evsel is the raw_syscalls one. + */ + if (evsel != trace->syscalls.events.sys_enter) + augmented_args = syscall__augmented_args(sc, sample, &augmented_args_size, trace->raw_augmented_syscalls_args_size); ttrace->entry_time = sample->time; msg = ttrace->entry_str; printed += scnprintf(msg + printed, trace__entry_str_size - printed, "%s(", sc->name); printed += syscall__scnprintf_args(sc, msg + printed, trace__entry_str_size - printed, - args, trace, thread); + args, augmented_args, augmented_args_size, trace, thread); if (sc->is_exit) { if (!(trace->duration_filter || trace->summary_only || trace->failure_only || trace->min_stack)) { + int alignment = 0; + trace__fprintf_entry_head(trace, thread, 0, false, ttrace->entry_time, trace->output); - fprintf(trace->output, "%-70s)\n", ttrace->entry_str); + printed = fprintf(trace->output, "%s)", ttrace->entry_str); + if (trace->args_alignment > printed) + alignment = trace->args_alignment - printed; + fprintf(trace->output, "%*s= ?\n", alignment, " "); } } else { ttrace->entry_pending = true; @@ -1723,7 +1906,8 @@ static int trace__fprintf_sys_enter(struct trace *trace, struct perf_evsel *evse int id = perf_evsel__sc_tp_uint(evsel, id, sample), err = -1; struct syscall *sc = trace__syscall_info(trace, evsel, id); char msg[1024]; - void *args; + void *args, *augmented_args = NULL; + int augmented_args_size; if (sc == NULL) return -1; @@ -1738,7 +1922,8 @@ static int trace__fprintf_sys_enter(struct trace *trace, struct perf_evsel *evse goto out_put; args = perf_evsel__sc_tp_ptr(evsel, args, sample); - syscall__scnprintf_args(sc, msg, sizeof(msg), args, trace, thread); + augmented_args = syscall__augmented_args(sc, sample, &augmented_args_size, trace->raw_augmented_syscalls_args_size); + syscall__scnprintf_args(sc, msg, sizeof(msg), args, augmented_args, augmented_args_size, trace, thread); fprintf(trace->output, "%s", msg); err = 0; out_put: @@ -1754,12 +1939,14 @@ static int trace__resolve_callchain(struct trace *trace, struct perf_evsel *evse int max_stack = evsel->attr.sample_max_stack ? evsel->attr.sample_max_stack : trace->max_stack; + int err; - if (machine__resolve(trace->host, &al, sample) < 0 || - thread__resolve_callchain(al.thread, cursor, evsel, sample, NULL, NULL, max_stack)) + if (machine__resolve(trace->host, &al, sample) < 0) return -1; - return 0; + err = thread__resolve_callchain(al.thread, cursor, evsel, sample, NULL, NULL, max_stack); + addr_location__put(&al); + return err; } static int trace__fprintf_callchain(struct trace *trace, struct perf_sample *sample) @@ -1788,7 +1975,8 @@ static int trace__sys_exit(struct trace *trace, struct perf_evsel *evsel, u64 duration = 0; bool duration_calculated = false; struct thread *thread; - int id = perf_evsel__sc_tp_uint(evsel, id, sample), err = -1, callchain_ret = 0; + int id = perf_evsel__sc_tp_uint(evsel, id, sample), err = -1, callchain_ret = 0, printed = 0; + int alignment = trace->args_alignment; struct syscall *sc = trace__syscall_info(trace, evsel, id); struct thread_trace *ttrace; @@ -1836,28 +2024,37 @@ static int trace__sys_exit(struct trace *trace, struct perf_evsel *evsel, trace__fprintf_entry_head(trace, thread, duration, duration_calculated, ttrace->entry_time, trace->output); if (ttrace->entry_pending) { - fprintf(trace->output, "%-70s", ttrace->entry_str); + printed = fprintf(trace->output, "%s", ttrace->entry_str); } else { fprintf(trace->output, " ... ["); color_fprintf(trace->output, PERF_COLOR_YELLOW, "continued"); fprintf(trace->output, "]: %s()", sc->name); } + printed++; /* the closing ')' */ + + if (alignment > printed) + alignment -= printed; + else + alignment = 0; + + fprintf(trace->output, ")%*s= ", alignment, " "); + if (sc->fmt == NULL) { if (ret < 0) goto errno_print; signed_print: - fprintf(trace->output, ") = %ld", ret); + fprintf(trace->output, "%ld", ret); } else if (ret < 0) { errno_print: { char bf[STRERR_BUFSIZE]; const char *emsg = str_error_r(-ret, bf, sizeof(bf)), *e = errno_to_name(evsel, -ret); - fprintf(trace->output, ") = -1 %s %s", e, emsg); + fprintf(trace->output, "-1 %s (%s)", e, emsg); } } else if (ret == 0 && sc->fmt->timeout) - fprintf(trace->output, ") = 0 Timeout"); + fprintf(trace->output, "0 (Timeout)"); else if (ttrace->ret_scnprintf) { char bf[1024]; struct syscall_arg arg = { @@ -1867,14 +2064,14 @@ errno_print: { }; ttrace->ret_scnprintf(bf, sizeof(bf), &arg); ttrace->ret_scnprintf = NULL; - fprintf(trace->output, ") = %s", bf); + fprintf(trace->output, "%s", bf); } else if (sc->fmt->hexret) - fprintf(trace->output, ") = %#lx", ret); + fprintf(trace->output, "%#lx", ret); else if (sc->fmt->errpid) { struct thread *child = machine__find_thread(trace->host, ret, ret); if (child != NULL) { - fprintf(trace->output, ") = %ld", ret); + fprintf(trace->output, "%ld", ret); if (child->comm_set) fprintf(trace->output, " (%s)", thread__comm_str(child)); thread__put(child); @@ -1884,6 +2081,13 @@ errno_print: { fputc('\n', trace->output); + /* + * We only consider an 'event' for the sake of --max-events a non-filtered + * sys_enter + sys_exit and other tracepoint events. + */ + if (++trace->nr_events_printed == trace->max_events && trace->max_events != ULONG_MAX) + interrupted = true; + if (callchain_ret > 0) trace__fprintf_callchain(trace, sample); else if (callchain_ret < 0) @@ -2016,13 +2220,25 @@ static void bpf_output__fprintf(struct trace *trace, { binary__fprintf(sample->raw_data, sample->raw_size, 8, bpf_output__printer, NULL, trace->output); + ++trace->nr_events_printed; } static int trace__event_handler(struct trace *trace, struct perf_evsel *evsel, union perf_event *event __maybe_unused, struct perf_sample *sample) { + struct thread *thread; int callchain_ret = 0; + /* + * Check if we called perf_evsel__disable(evsel) due to, for instance, + * this event's max_events having been hit and this is an entry coming + * from the ring buffer that we should discard, since the max events + * have already been considered/printed. + */ + if (evsel->disabled) + return 0; + + thread = machine__findnew_thread(trace->host, sample->pid, sample->tid); if (sample->callchain) { callchain_ret = trace__resolve_callchain(trace, evsel, sample, &callchain_cursor); @@ -2036,25 +2252,50 @@ static int trace__event_handler(struct trace *trace, struct perf_evsel *evsel, trace__printf_interrupted_entry(trace); trace__fprintf_tstamp(trace, sample->time, trace->output); - if (trace->trace_syscalls) + if (trace->trace_syscalls && trace->show_duration) fprintf(trace->output, "( ): "); + if (thread) + trace__fprintf_comm_tid(trace, thread, trace->output); + + if (evsel == trace->syscalls.events.augmented) { + int id = perf_evsel__sc_tp_uint(evsel, id, sample); + struct syscall *sc = trace__syscall_info(trace, evsel, id); + + if (sc) { + fprintf(trace->output, "%s(", sc->name); + trace__fprintf_sys_enter(trace, evsel, sample); + fputc(')', trace->output); + goto newline; + } + + /* + * XXX: Not having the associated syscall info or not finding/adding + * the thread should never happen, but if it does... + * fall thru and print it as a bpf_output event. + */ + } + fprintf(trace->output, "%s:", evsel->name); if (perf_evsel__is_bpf_output(evsel)) { - if (evsel == trace->syscalls.events.augmented) - trace__fprintf_sys_enter(trace, evsel, sample); - else - bpf_output__fprintf(trace, sample); + bpf_output__fprintf(trace, sample); } else if (evsel->tp_format) { if (strncmp(evsel->tp_format->name, "sys_enter_", 10) || trace__fprintf_sys_enter(trace, evsel, sample)) { event_format__fprintf(evsel->tp_format, sample->cpu, sample->raw_data, sample->raw_size, trace->output); + ++trace->nr_events_printed; + + if (evsel->max_events != ULONG_MAX && ++evsel->nr_events_printed == evsel->max_events) { + perf_evsel__disable(evsel); + perf_evsel__close(evsel); + } } } +newline: fprintf(trace->output, "\n"); if (callchain_ret > 0) @@ -2062,6 +2303,7 @@ static int trace__event_handler(struct trace *trace, struct perf_evsel *evsel, else if (callchain_ret < 0) pr_err("Problem processing %s callchain, skipping...\n", perf_evsel__name(evsel)); out: + thread__put(thread); return 0; } @@ -2148,6 +2390,8 @@ static int trace__pgfault(struct trace *trace, trace__fprintf_callchain(trace, sample); else if (callchain_ret < 0) pr_err("Problem processing %s callchain, skipping...\n", perf_evsel__name(evsel)); + + ++trace->nr_events_printed; out: err = 0; out_put: @@ -2325,6 +2569,9 @@ static void trace__handle_event(struct trace *trace, union perf_event *event, st tracepoint_handler handler = evsel->handler; handler(trace, evsel, event, sample); } + + if (trace->nr_events_printed >= trace->max_events && trace->max_events != ULONG_MAX) + interrupted = true; } static int trace__add_syscall_newtp(struct trace *trace) @@ -2376,7 +2623,7 @@ out_delete_sys_enter: goto out; } -static int trace__set_ev_qualifier_filter(struct trace *trace) +static int trace__set_ev_qualifier_tp_filter(struct trace *trace) { int err = -1; struct perf_evsel *sys_exit; @@ -2401,9 +2648,95 @@ out_enomem: goto out; } +#ifdef HAVE_LIBBPF_SUPPORT +static int trace__set_ev_qualifier_bpf_filter(struct trace *trace) +{ + int fd = bpf_map__fd(trace->syscalls.map); + struct bpf_map_syscall_entry value = { + .enabled = !trace->not_ev_qualifier, + }; + int err = 0; + size_t i; + + for (i = 0; i < trace->ev_qualifier_ids.nr; ++i) { + int key = trace->ev_qualifier_ids.entries[i]; + + err = bpf_map_update_elem(fd, &key, &value, BPF_EXIST); + if (err) + break; + } + + return err; +} + +static int __trace__init_syscalls_bpf_map(struct trace *trace, bool enabled) +{ + int fd = bpf_map__fd(trace->syscalls.map); + struct bpf_map_syscall_entry value = { + .enabled = enabled, + }; + int err = 0, key; + + for (key = 0; key < trace->sctbl->syscalls.nr_entries; ++key) { + err = bpf_map_update_elem(fd, &key, &value, BPF_ANY); + if (err) + break; + } + + return err; +} + +static int trace__init_syscalls_bpf_map(struct trace *trace) +{ + bool enabled = true; + + if (trace->ev_qualifier_ids.nr) + enabled = trace->not_ev_qualifier; + + return __trace__init_syscalls_bpf_map(trace, enabled); +} +#else +static int trace__set_ev_qualifier_bpf_filter(struct trace *trace __maybe_unused) +{ + return 0; +} + +static int trace__init_syscalls_bpf_map(struct trace *trace __maybe_unused) +{ + return 0; +} +#endif // HAVE_LIBBPF_SUPPORT + +static int trace__set_ev_qualifier_filter(struct trace *trace) +{ + if (trace->syscalls.map) + return trace__set_ev_qualifier_bpf_filter(trace); + if (trace->syscalls.events.sys_enter) + return trace__set_ev_qualifier_tp_filter(trace); + return 0; +} + +static int bpf_map__set_filter_pids(struct bpf_map *map __maybe_unused, + size_t npids __maybe_unused, pid_t *pids __maybe_unused) +{ + int err = 0; +#ifdef HAVE_LIBBPF_SUPPORT + bool value = true; + int map_fd = bpf_map__fd(map); + size_t i; + + for (i = 0; i < npids; ++i) { + err = bpf_map_update_elem(map_fd, &pids[i], &value, BPF_ANY); + if (err) + break; + } +#endif + return err; +} + static int trace__set_filter_loop_pids(struct trace *trace) { - unsigned int nr = 1; + unsigned int nr = 1, err; pid_t pids[32] = { getpid(), }; @@ -2422,7 +2755,92 @@ static int trace__set_filter_loop_pids(struct trace *trace) thread = parent; } - return perf_evlist__set_filter_pids(trace->evlist, nr, pids); + err = perf_evlist__set_tp_filter_pids(trace->evlist, nr, pids); + if (!err && trace->filter_pids.map) + err = bpf_map__set_filter_pids(trace->filter_pids.map, nr, pids); + + return err; +} + +static int trace__set_filter_pids(struct trace *trace) +{ + int err = 0; + /* + * Better not use !target__has_task() here because we need to cover the + * case where no threads were specified in the command line, but a + * workload was, and in that case we will fill in the thread_map when + * we fork the workload in perf_evlist__prepare_workload. + */ + if (trace->filter_pids.nr > 0) { + err = perf_evlist__set_tp_filter_pids(trace->evlist, trace->filter_pids.nr, + trace->filter_pids.entries); + if (!err && trace->filter_pids.map) { + err = bpf_map__set_filter_pids(trace->filter_pids.map, trace->filter_pids.nr, + trace->filter_pids.entries); + } + } else if (thread_map__pid(trace->evlist->threads, 0) == -1) { + err = trace__set_filter_loop_pids(trace); + } + + return err; +} + +static int __trace__deliver_event(struct trace *trace, union perf_event *event) +{ + struct perf_evlist *evlist = trace->evlist; + struct perf_sample sample; + int err; + + err = perf_evlist__parse_sample(evlist, event, &sample); + if (err) + fprintf(trace->output, "Can't parse sample, err = %d, skipping...\n", err); + else + trace__handle_event(trace, event, &sample); + + return 0; +} + +static int __trace__flush_events(struct trace *trace) +{ + u64 first = ordered_events__first_time(&trace->oe.data); + u64 flush = trace->oe.last - NSEC_PER_SEC; + + /* Is there some thing to flush.. */ + if (first && first < flush) + return ordered_events__flush_time(&trace->oe.data, flush); + + return 0; +} + +static int trace__flush_events(struct trace *trace) +{ + return !trace->sort_events ? 0 : __trace__flush_events(trace); +} + +static int trace__deliver_event(struct trace *trace, union perf_event *event) +{ + int err; + + if (!trace->sort_events) + return __trace__deliver_event(trace, event); + + err = perf_evlist__parse_sample_timestamp(trace->evlist, event, &trace->oe.last); + if (err && err != -1) + return err; + + err = ordered_events__queue(&trace->oe.data, event, trace->oe.last, 0); + if (err) + return err; + + return trace__flush_events(trace); +} + +static int ordered_events__deliver_event(struct ordered_events *oe, + struct ordered_event *event) +{ + struct trace *trace = container_of(oe, struct trace, oe.data); + + return __trace__deliver_event(trace, event->event); } static int trace__run(struct trace *trace, int argc, const char **argv) @@ -2436,11 +2854,13 @@ static int trace__run(struct trace *trace, int argc, const char **argv) trace->live = true; - if (trace->trace_syscalls && trace__add_syscall_newtp(trace)) - goto out_error_raw_syscalls; + if (!trace->raw_augmented_syscalls) { + if (trace->trace_syscalls && trace__add_syscall_newtp(trace)) + goto out_error_raw_syscalls; - if (trace->trace_syscalls) - trace->vfs_getname = perf_evlist__add_vfs_getname(evlist); + if (trace->trace_syscalls) + trace->vfs_getname = perf_evlist__add_vfs_getname(evlist); + } if ((trace->trace_pgfaults & TRACE_PFMAJ)) { pgfault_maj = perf_evsel__new_pgfault(PERF_COUNT_SW_PAGE_FAULTS_MAJ); @@ -2531,27 +2951,22 @@ static int trace__run(struct trace *trace, int argc, const char **argv) goto out_error_open; } - /* - * Better not use !target__has_task() here because we need to cover the - * case where no threads were specified in the command line, but a - * workload was, and in that case we will fill in the thread_map when - * we fork the workload in perf_evlist__prepare_workload. - */ - if (trace->filter_pids.nr > 0) - err = perf_evlist__set_filter_pids(evlist, trace->filter_pids.nr, trace->filter_pids.entries); - else if (thread_map__pid(evlist->threads, 0) == -1) - err = trace__set_filter_loop_pids(trace); - + err = trace__set_filter_pids(trace); if (err < 0) goto out_error_mem; + if (trace->syscalls.map) + trace__init_syscalls_bpf_map(trace); + if (trace->ev_qualifier_ids.nr > 0) { err = trace__set_ev_qualifier_filter(trace); if (err < 0) goto out_errno; - pr_debug("event qualifier tracepoint filter: %s\n", - trace->syscalls.events.sys_exit->filter); + if (trace->syscalls.events.sys_exit) { + pr_debug("event qualifier tracepoint filter: %s\n", + trace->syscalls.events.sys_exit->filter); + } } err = perf_evlist__apply_filters(evlist, &evsel); @@ -2581,7 +2996,7 @@ static int trace__run(struct trace *trace, int argc, const char **argv) * Now that we already used evsel->attr to ask the kernel to setup the * events, lets reuse evsel->attr.sample_max_stack as the limit in * trace__resolve_callchain(), allowing per-event max-stack settings - * to override an explicitely set --max-stack global setting. + * to override an explicitly set --max-stack global setting. */ evlist__for_each_entry(evlist, evsel) { if (evsel__has_callchain(evsel) && @@ -2600,18 +3015,12 @@ again: continue; while ((event = perf_mmap__read_event(md)) != NULL) { - struct perf_sample sample; - ++trace->nr_events; - err = perf_evlist__parse_sample(evlist, event, &sample); - if (err) { - fprintf(trace->output, "Can't parse sample, err = %d, skipping...\n", err); - goto next_event; - } + err = trace__deliver_event(trace, event); + if (err) + goto out_disable; - trace__handle_event(trace, event, &sample); -next_event: perf_mmap__consume(md); if (interrupted) @@ -2629,10 +3038,13 @@ next_event: int timeout = done ? 100 : -1; if (!draining && perf_evlist__poll(evlist, timeout) > 0) { - if (perf_evlist__filter_pollfd(evlist, POLLERR | POLLHUP) == 0) + if (perf_evlist__filter_pollfd(evlist, POLLERR | POLLHUP | POLLNVAL) == 0) draining = true; goto again; + } else { + if (trace__flush_events(trace)) + goto out_disable; } } else { goto again; @@ -2643,6 +3055,9 @@ out_disable: perf_evlist__disable(evlist); + if (trace->sort_events) + ordered_events__flush(&trace->oe.data, OE_FLUSH__FINAL); + if (!err) { if (trace->summary) trace__fprintf_thread_summary(trace, trace->output); @@ -2940,8 +3355,8 @@ static int trace__set_duration(const struct option *opt, const char *str, return 0; } -static int trace__set_filter_pids(const struct option *opt, const char *str, - int unset __maybe_unused) +static int trace__set_filter_pids_from_option(const struct option *opt, const char *str, + int unset __maybe_unused) { int ret = -1; size_t i; @@ -3061,6 +3476,7 @@ static int trace__parse_events_option(const struct option *opt, const char *str, int len = strlen(str) + 1, err = -1, list, idx; char *strace_groups_dir = system_path(STRACE_GROUPS_DIR); char group_name[PATH_MAX]; + struct syscall_fmt *fmt; if (strace_groups_dir == NULL) return -1; @@ -3078,12 +3494,19 @@ static int trace__parse_events_option(const struct option *opt, const char *str, if (syscalltbl__id(trace->sctbl, s) >= 0 || syscalltbl__strglobmatch_first(trace->sctbl, s, &idx) >= 0) { list = 1; + goto do_concat; + } + + fmt = syscall_fmt__find_by_alias(s); + if (fmt != NULL) { + list = 1; + s = fmt->name; } else { path__join(group_name, sizeof(group_name), strace_groups_dir, s); if (access(group_name, R_OK) == 0) list = 1; } - +do_concat: if (lists[list]) { sprintf(lists[list] + strlen(lists[list]), ",%s", s); } else { @@ -3143,6 +3566,68 @@ static int trace__parse_cgroups(const struct option *opt, const char *str, int u return 0; } +static struct bpf_map *bpf__find_map_by_name(const char *name) +{ + struct bpf_object *obj, *tmp; + + bpf_object__for_each_safe(obj, tmp) { + struct bpf_map *map = bpf_object__find_map_by_name(obj, name); + if (map) + return map; + + } + + return NULL; +} + +static void trace__set_bpf_map_filtered_pids(struct trace *trace) +{ + trace->filter_pids.map = bpf__find_map_by_name("pids_filtered"); +} + +static void trace__set_bpf_map_syscalls(struct trace *trace) +{ + trace->syscalls.map = bpf__find_map_by_name("syscalls"); +} + +static int trace__config(const char *var, const char *value, void *arg) +{ + struct trace *trace = arg; + int err = 0; + + if (!strcmp(var, "trace.add_events")) { + struct option o = OPT_CALLBACK('e', "event", &trace->evlist, "event", + "event selector. use 'perf list' to list available events", + parse_events_option); + err = parse_events_option(&o, value, 0); + } else if (!strcmp(var, "trace.show_timestamp")) { + trace->show_tstamp = perf_config_bool(var, value); + } else if (!strcmp(var, "trace.show_duration")) { + trace->show_duration = perf_config_bool(var, value); + } else if (!strcmp(var, "trace.show_arg_names")) { + trace->show_arg_names = perf_config_bool(var, value); + if (!trace->show_arg_names) + trace->show_zeros = true; + } else if (!strcmp(var, "trace.show_zeros")) { + bool new_show_zeros = perf_config_bool(var, value); + if (!trace->show_arg_names && !new_show_zeros) { + pr_warning("trace.show_zeros has to be set when trace.show_arg_names=no\n"); + goto out; + } + trace->show_zeros = new_show_zeros; + } else if (!strcmp(var, "trace.show_prefix")) { + trace->show_string_prefix = perf_config_bool(var, value); + } else if (!strcmp(var, "trace.no_inherit")) { + trace->opts.no_inherit = perf_config_bool(var, value); + } else if (!strcmp(var, "trace.args_alignment")) { + int args_alignment = 0; + if (perf_config_int(&args_alignment, var, value) == 0) + trace->args_alignment = args_alignment; + } +out: + return err; +} + int cmd_trace(int argc, const char **argv) { const char *trace_usage[] = { @@ -3165,13 +3650,17 @@ int cmd_trace(int argc, const char **argv) .user_interval = ULLONG_MAX, .no_buffering = true, .mmap_pages = UINT_MAX, - .proc_map_timeout = 500, }, .output = stderr, .show_comm = true, + .show_tstamp = true, + .show_duration = true, + .show_arg_names = true, + .args_alignment = 70, .trace_syscalls = false, .kernel_syscallchains = false, .max_stack = UINT_MAX, + .max_events = ULONG_MAX, }; const char *output_name = NULL; const struct option trace_options[] = { @@ -3190,7 +3679,7 @@ int cmd_trace(int argc, const char **argv) OPT_STRING('t', "tid", &trace.opts.target.tid, "tid", "trace events on existing thread id"), OPT_CALLBACK(0, "filter-pids", &trace, "CSV list of pids", - "pids to filter (by the kernel)", trace__set_filter_pids), + "pids to filter (by the kernel)", trace__set_filter_pids_from_option), OPT_BOOLEAN('a', "all-cpus", &trace.opts.target.system_wide, "system-wide collection from all CPUs"), OPT_STRING('C', "cpu", &trace.opts.target.cpu_list, "cpu", @@ -3224,6 +3713,8 @@ int cmd_trace(int argc, const char **argv) &record_parse_callchain_opt), OPT_BOOLEAN(0, "kernel-syscall-graph", &trace.kernel_syscallchains, "Show the kernel callchains on the syscall exit path"), + OPT_ULONG(0, "max-events", &trace.max_events, + "Set the maximum number of events to print, exit after that is reached. "), OPT_UINTEGER(0, "min-stack", &trace.min_stack, "Set the minimum stack depth when parsing the callchain, " "anything below the specified depth will be ignored."), @@ -3231,9 +3722,11 @@ int cmd_trace(int argc, const char **argv) "Set the maximum stack depth when parsing the callchain, " "anything beyond the specified depth will be ignored. " "Default: kernel.perf_event_max_stack or " __stringify(PERF_MAX_STACK_DEPTH)), + OPT_BOOLEAN(0, "sort-events", &trace.sort_events, + "Sort batch of events before processing, use if getting out of order events"), OPT_BOOLEAN(0, "print-sample", &trace.print_sample, "print the PERF_RECORD_SAMPLE PERF_SAMPLE_ info, for debugging"), - OPT_UINTEGER(0, "proc-map-timeout", &trace.opts.proc_map_timeout, + OPT_UINTEGER(0, "proc-map-timeout", &proc_map_timeout, "per thread proc mmap processing timeout in ms"), OPT_CALLBACK('G', "cgroup", &trace, "name", "monitor event in cgroup name only", trace__parse_cgroups), @@ -3261,6 +3754,10 @@ int cmd_trace(int argc, const char **argv) goto out; } + err = perf_config(trace__config, &trace); + if (err) + goto out; + argc = parse_options_subcommand(argc, argv, trace_options, trace_subcommands, trace_usage, PARSE_OPT_STOP_AT_NON_OPTION); @@ -3277,10 +3774,9 @@ int cmd_trace(int argc, const char **argv) } if (evsel) { - if (perf_evsel__init_augmented_syscall_tp(evsel) || - perf_evsel__init_augmented_syscall_tp_args(evsel)) - goto out; trace.syscalls.events.augmented = evsel; + trace__set_bpf_map_filtered_pids(&trace); + trace__set_bpf_map_syscalls(&trace); } err = bpf__setup_stdout(trace.evlist); @@ -3326,6 +3822,72 @@ int cmd_trace(int argc, const char **argv) } } + if (trace.sort_events) { + ordered_events__init(&trace.oe.data, ordered_events__deliver_event, &trace); + ordered_events__set_copy_on_queue(&trace.oe.data, true); + } + + /* + * If we are augmenting syscalls, then combine what we put in the + * __augmented_syscalls__ BPF map with what is in the + * syscalls:sys_exit_FOO tracepoints, i.e. just like we do without BPF, + * combining raw_syscalls:sys_enter with raw_syscalls:sys_exit. + * + * We'll switch to look at two BPF maps, one for sys_enter and the + * other for sys_exit when we start augmenting the sys_exit paths with + * buffers that are being copied from kernel to userspace, think 'read' + * syscall. + */ + if (trace.syscalls.events.augmented) { + evlist__for_each_entry(trace.evlist, evsel) { + bool raw_syscalls_sys_exit = strcmp(perf_evsel__name(evsel), "raw_syscalls:sys_exit") == 0; + + if (raw_syscalls_sys_exit) { + trace.raw_augmented_syscalls = true; + goto init_augmented_syscall_tp; + } + + if (strcmp(perf_evsel__name(evsel), "raw_syscalls:sys_enter") == 0) { + struct perf_evsel *augmented = trace.syscalls.events.augmented; + if (perf_evsel__init_augmented_syscall_tp(augmented, evsel) || + perf_evsel__init_augmented_syscall_tp_args(augmented)) + goto out; + augmented->handler = trace__sys_enter; + } + + if (strstarts(perf_evsel__name(evsel), "syscalls:sys_exit_")) { + struct syscall_tp *sc; +init_augmented_syscall_tp: + if (perf_evsel__init_augmented_syscall_tp(evsel, evsel)) + goto out; + sc = evsel->priv; + /* + * For now with BPF raw_augmented we hook into + * raw_syscalls:sys_enter and there we get all + * 6 syscall args plus the tracepoint common + * fields and the syscall_nr (another long). + * So we check if that is the case and if so + * don't look after the sc->args_size but + * always after the full raw_syscalls:sys_enter + * payload, which is fixed. + * + * We'll revisit this later to pass + * s->args_size to the BPF augmenter (now + * tools/perf/examples/bpf/augmented_raw_syscalls.c, + * so that it copies only what we need for each + * syscall, like what happens when we use + * syscalls:sys_enter_NAME, so that we reduce + * the kernel/userspace traffic to just what is + * needed for each syscall. + */ + if (trace.raw_augmented_syscalls) + trace.raw_augmented_syscalls_args_size = (6 + 1) * sizeof(long) + sc->id.offset; + perf_evsel__init_augmented_syscall_tp_ret(evsel); + evsel->handler = trace__sys_exit; + } + } + } + if ((argc >= 1) && (strcmp(argv[0], "record") == 0)) return trace__record(&trace, argc-1, &argv[1]); |