diff options
Diffstat (limited to 'tools/perf/builtin-trace.c')
-rw-r--r-- | tools/perf/builtin-trace.c | 993 |
1 files changed, 834 insertions, 159 deletions
diff --git a/tools/perf/builtin-trace.c b/tools/perf/builtin-trace.c index bb5130d02155..46a72ecac427 100644 --- a/tools/perf/builtin-trace.c +++ b/tools/perf/builtin-trace.c @@ -77,6 +77,7 @@ #include <sys/sysmacros.h> #include <linux/ctype.h> +#include <perf/mmap.h> #ifndef O_CLOEXEC # define O_CLOEXEC 02000000 @@ -86,6 +87,33 @@ # define F_LINUX_SPECIFIC_BASE 1024 #endif +/* + * strtoul: Go from a string to a value, i.e. for msr: MSR_FS_BASE to 0xc0000100 + */ +struct syscall_arg_fmt { + size_t (*scnprintf)(char *bf, size_t size, struct syscall_arg *arg); + bool (*strtoul)(char *bf, size_t size, struct syscall_arg *arg, u64 *val); + unsigned long (*mask_val)(struct syscall_arg *arg, unsigned long val); + void *parm; + const char *name; + u16 nr_entries; // for arrays + bool show_zero; +}; + +struct syscall_fmt { + const char *name; + const char *alias; + struct { + const char *sys_enter, + *sys_exit; + } bpf_prog_name; + struct syscall_arg_fmt arg[6]; + u8 nr_args; + bool errpid; + bool timeout; + bool hexret; +}; + struct trace { struct perf_tool tool; struct syscalltbl *sctbl; @@ -147,11 +175,13 @@ struct trace { bool multiple_threads; bool summary; bool summary_only; + bool errno_summary; bool failure_only; bool show_comm; bool print_sample; bool show_tool_stats; bool trace_syscalls; + bool libtraceevent_print; bool kernel_syscallchains; s16 args_alignment; bool show_tstamp; @@ -162,6 +192,7 @@ struct trace { bool force; bool vfs_getname; int trace_pgfaults; + char *perfconfig_events; struct { struct ordered_events data; u64 last; @@ -254,6 +285,87 @@ struct syscall_tp { }; }; +/* + * The evsel->priv as used by 'perf trace' + * sc: for raw_syscalls:sys_{enter,exit} and syscalls:sys_{enter,exit}_SYSCALLNAME + * fmt: for all the other tracepoints + */ +struct evsel_trace { + struct syscall_tp sc; + struct syscall_arg_fmt *fmt; +}; + +static struct evsel_trace *evsel_trace__new(void) +{ + return zalloc(sizeof(struct evsel_trace)); +} + +static void evsel_trace__delete(struct evsel_trace *et) +{ + if (et == NULL) + return; + + zfree(&et->fmt); + free(et); +} + +/* + * Used with raw_syscalls:sys_{enter,exit} and with the + * syscalls:sys_{enter,exit}_SYSCALL tracepoints + */ +static inline struct syscall_tp *__evsel__syscall_tp(struct evsel *evsel) +{ + struct evsel_trace *et = evsel->priv; + + return &et->sc; +} + +static struct syscall_tp *evsel__syscall_tp(struct evsel *evsel) +{ + if (evsel->priv == NULL) { + evsel->priv = evsel_trace__new(); + if (evsel->priv == NULL) + return NULL; + } + + return __evsel__syscall_tp(evsel); +} + +/* + * Used with all the other tracepoints. + */ +static inline struct syscall_arg_fmt *__evsel__syscall_arg_fmt(struct evsel *evsel) +{ + struct evsel_trace *et = evsel->priv; + + return et->fmt; +} + +static struct syscall_arg_fmt *evsel__syscall_arg_fmt(struct evsel *evsel) +{ + struct evsel_trace *et = evsel->priv; + + if (evsel->priv == NULL) { + et = evsel->priv = evsel_trace__new(); + + if (et == NULL) + return NULL; + } + + if (et->fmt == NULL) { + et->fmt = calloc(evsel->tp_format->format.nr_fields, sizeof(struct syscall_arg_fmt)); + if (et->fmt == NULL) + goto out_delete; + } + + return __evsel__syscall_arg_fmt(evsel); + +out_delete: + evsel_trace__delete(evsel->priv); + evsel->priv = NULL; + return NULL; +} + static int perf_evsel__init_tp_uint_field(struct evsel *evsel, struct tp_field *field, const char *name) @@ -267,7 +379,7 @@ static int perf_evsel__init_tp_uint_field(struct evsel *evsel, } #define perf_evsel__init_sc_tp_uint_field(evsel, name) \ - ({ struct syscall_tp *sc = evsel->priv;\ + ({ struct syscall_tp *sc = __evsel__syscall_tp(evsel);\ perf_evsel__init_tp_uint_field(evsel, &sc->name, #name); }) static int perf_evsel__init_tp_ptr_field(struct evsel *evsel, @@ -283,7 +395,7 @@ static int perf_evsel__init_tp_ptr_field(struct evsel *evsel, } #define perf_evsel__init_sc_tp_ptr_field(evsel, name) \ - ({ struct syscall_tp *sc = evsel->priv;\ + ({ struct syscall_tp *sc = __evsel__syscall_tp(evsel);\ perf_evsel__init_tp_ptr_field(evsel, &sc->name, #name); }) static void evsel__delete_priv(struct evsel *evsel) @@ -294,73 +406,61 @@ static void evsel__delete_priv(struct evsel *evsel) static int perf_evsel__init_syscall_tp(struct evsel *evsel) { - struct syscall_tp *sc = evsel->priv = malloc(sizeof(struct syscall_tp)); + struct syscall_tp *sc = evsel__syscall_tp(evsel); - if (evsel->priv != NULL) { + if (sc != NULL) { 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 -ENOENT; return 0; } return -ENOMEM; -out_delete: - zfree(&evsel->priv); - return -ENOENT; } static int perf_evsel__init_augmented_syscall_tp(struct evsel *evsel, struct evsel *tp) { - struct syscall_tp *sc = evsel->priv = malloc(sizeof(struct syscall_tp)); + struct syscall_tp *sc = evsel__syscall_tp(evsel); - if (evsel->priv != NULL) { + if (sc != 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; + if (syscall_id == NULL || + __tp_field__init_uint(&sc->id, syscall_id->size, syscall_id->offset, evsel->needs_swap)) + return -EINVAL; return 0; } return -ENOMEM; -out_delete: - zfree(&evsel->priv); - return -EINVAL; } static int perf_evsel__init_augmented_syscall_tp_args(struct evsel *evsel) { - struct syscall_tp *sc = evsel->priv; + struct syscall_tp *sc = __evsel__syscall_tp(evsel); return __tp_field__init_ptr(&sc->args, sc->id.offset + sizeof(u64)); } static int perf_evsel__init_augmented_syscall_tp_ret(struct evsel *evsel) { - struct syscall_tp *sc = evsel->priv; + struct syscall_tp *sc = __evsel__syscall_tp(evsel); 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 evsel *evsel, void *handler) { - evsel->priv = malloc(sizeof(struct syscall_tp)); - if (evsel->priv != NULL) { + if (evsel__syscall_tp(evsel) != NULL) { if (perf_evsel__init_sc_tp_uint_field(evsel, id)) - goto out_delete; + return -ENOENT; evsel->handler = handler; return 0; } return -ENOMEM; - -out_delete: - zfree(&evsel->priv); - return -ENOENT; } static struct evsel *perf_evsel__raw_syscall_newtp(const char *direction, void *handler) @@ -385,13 +485,27 @@ out_delete: } #define perf_evsel__sc_tp_uint(evsel, name, sample) \ - ({ struct syscall_tp *fields = evsel->priv; \ + ({ struct syscall_tp *fields = __evsel__syscall_tp(evsel); \ fields->name.integer(&fields->name, sample); }) #define perf_evsel__sc_tp_ptr(evsel, name, sample) \ - ({ struct syscall_tp *fields = evsel->priv; \ + ({ struct syscall_tp *fields = __evsel__syscall_tp(evsel); \ fields->name.pointer(&fields->name, sample); }) +size_t strarray__scnprintf_suffix(struct strarray *sa, char *bf, size_t size, const char *intfmt, bool show_suffix, int val) +{ + int idx = val - sa->offset; + + if (idx < 0 || idx >= sa->nr_entries || sa->entries[idx] == NULL) { + size_t printed = scnprintf(bf, size, intfmt, val); + if (show_suffix) + printed += scnprintf(bf + printed, size - printed, " /* %s??? */", sa->prefix); + return printed; + } + + return scnprintf(bf, size, "%s%s", sa->entries[idx], show_suffix ? sa->prefix : ""); +} + 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; @@ -421,6 +535,21 @@ static size_t syscall_arg__scnprintf_strarray(char *bf, size_t size, #define SCA_STRARRAY syscall_arg__scnprintf_strarray +bool syscall_arg__strtoul_strarray(char *bf, size_t size, struct syscall_arg *arg, u64 *ret) +{ + return strarray__strtoul(arg->parm, bf, size, ret); +} + +bool syscall_arg__strtoul_strarray_flags(char *bf, size_t size, struct syscall_arg *arg, u64 *ret) +{ + return strarray__strtoul_flags(arg->parm, bf, size, ret); +} + +bool syscall_arg__strtoul_strarrays(char *bf, size_t size, struct syscall_arg *arg, u64 *ret) +{ + return strarrays__strtoul(arg->parm, bf, size, ret); +} + size_t syscall_arg__scnprintf_strarray_flags(char *bf, size_t size, struct syscall_arg *arg) { return strarray__scnprintf_flags(arg->parm, bf, size, arg->show_string_prefix, arg->val); @@ -448,6 +577,77 @@ size_t strarrays__scnprintf(struct strarrays *sas, char *bf, size_t size, const return printed; } +bool strarray__strtoul(struct strarray *sa, char *bf, size_t size, u64 *ret) +{ + int i; + + for (i = 0; i < sa->nr_entries; ++i) { + if (sa->entries[i] && strncmp(sa->entries[i], bf, size) == 0 && sa->entries[i][size] == '\0') { + *ret = sa->offset + i; + return true; + } + } + + return false; +} + +bool strarray__strtoul_flags(struct strarray *sa, char *bf, size_t size, u64 *ret) +{ + u64 val = 0; + char *tok = bf, *sep, *end; + + *ret = 0; + + while (size != 0) { + int toklen = size; + + sep = memchr(tok, '|', size); + if (sep != NULL) { + size -= sep - tok + 1; + + end = sep - 1; + while (end > tok && isspace(*end)) + --end; + + toklen = end - tok + 1; + } + + while (isspace(*tok)) + ++tok; + + if (isalpha(*tok) || *tok == '_') { + if (!strarray__strtoul(sa, tok, toklen, &val)) + return false; + } else { + bool is_hexa = tok[0] == 0 && (tok[1] = 'x' || tok[1] == 'X'); + + val = strtoul(tok, NULL, is_hexa ? 16 : 0); + } + + *ret |= (1 << (val - 1)); + + if (sep == NULL) + break; + tok = sep + 1; + } + + return true; +} + +bool strarrays__strtoul(struct strarrays *sas, char *bf, size_t size, u64 *ret) +{ + int i; + + for (i = 0; i < sas->nr_entries; ++i) { + struct strarray *sa = sas->entries[i]; + + if (strarray__strtoul(sa, bf, size, ret)) + return true; + } + + return false; +} + size_t syscall_arg__scnprintf_strarrays(char *bf, size_t size, struct syscall_arg *arg) { @@ -499,6 +699,16 @@ size_t syscall_arg__scnprintf_long(char *bf, size_t size, struct syscall_arg *ar return scnprintf(bf, size, "%ld", arg->val); } +static size_t syscall_arg__scnprintf_char_array(char *bf, size_t size, struct syscall_arg *arg) +{ + // XXX Hey, maybe for sched:sched_switch prev/next comm fields we can + // fill missing comms using thread__set_comm()... + // here or in a special syscall_arg__scnprintf_pid_sched_tp... + return scnprintf(bf, size, "\"%-.*s\"", arg->fmt->nr_entries ?: arg->len, arg->val); +} + +#define SCA_CHAR_ARRAY syscall_arg__scnprintf_char_array + static const char *bpf_cmd[] = { "MAP_CREATE", "MAP_LOOKUP_ELEM", "MAP_UPDATE_ELEM", "MAP_DELETE_ELEM", "MAP_GET_NEXT_KEY", "PROG_LOAD", @@ -672,10 +882,12 @@ static size_t syscall_arg__scnprintf_getrandom_flags(char *bf, size_t size, #define STRARRAY(name, array) \ { .scnprintf = SCA_STRARRAY, \ + .strtoul = STUL_STRARRAY, \ .parm = &strarray__##array, } #define STRARRAY_FLAGS(name, array) \ { .scnprintf = SCA_STRARRAY_FLAGS, \ + .strtoul = STUL_STRARRAY_FLAGS, \ .parm = &strarray__##array, } #include "trace/beauty/arch_errno_names.c" @@ -694,27 +906,7 @@ static size_t syscall_arg__scnprintf_getrandom_flags(char *bf, size_t size, #include "trace/beauty/socket_type.c" #include "trace/beauty/waitid_options.c" -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; -}; - -static struct syscall_fmt { - const char *name; - const char *alias; - struct { - const char *sys_enter, - *sys_exit; - } bpf_prog_name; - struct syscall_arg_fmt arg[6]; - u8 nr_args; - bool errpid; - bool timeout; - bool hexret; -} syscall_fmts[] = { +static struct syscall_fmt syscall_fmts[] = { { .name = "access", .arg = { [1] = { .scnprintf = SCA_ACCMODE, /* mode */ }, }, }, { .name = "arch_prctl", @@ -751,7 +943,8 @@ static struct syscall_fmt { { .name = "fchownat", .arg = { [0] = { .scnprintf = SCA_FDAT, /* fd */ }, }, }, { .name = "fcntl", - .arg = { [1] = { .scnprintf = SCA_FCNTL_CMD, /* cmd */ + .arg = { [1] = { .scnprintf = SCA_FCNTL_CMD, /* cmd */ + .strtoul = STUL_STRARRAYS, .parm = &strarrays__fcntl_cmds_arrays, .show_zero = true, }, [2] = { .scnprintf = SCA_FCNTL_ARG, /* arg */ }, }, }, @@ -822,7 +1015,9 @@ static struct syscall_fmt { .alias = "old_mmap", #endif .arg = { [2] = { .scnprintf = SCA_MMAP_PROT, /* prot */ }, - [3] = { .scnprintf = SCA_MMAP_FLAGS, /* flags */ }, + [3] = { .scnprintf = SCA_MMAP_FLAGS, /* flags */ + .strtoul = STUL_STRARRAY_FLAGS, + .parm = &strarray__mmap_flags, }, [5] = { .scnprintf = SCA_HEX, /* offset */ }, }, }, { .name = "mount", .arg = { [0] = { .scnprintf = SCA_FILENAME, /* dev_name */ }, @@ -964,24 +1159,35 @@ static int syscall_fmt__cmp(const void *name, const void *fmtp) return strcmp(name, fmt->name); } +static struct syscall_fmt *__syscall_fmt__find(struct syscall_fmt *fmts, const int nmemb, const char *name) +{ + return bsearch(name, fmts, nmemb, sizeof(struct syscall_fmt), syscall_fmt__cmp); +} + static struct syscall_fmt *syscall_fmt__find(const char *name) { const int nmemb = ARRAY_SIZE(syscall_fmts); - return bsearch(name, syscall_fmts, nmemb, sizeof(struct syscall_fmt), syscall_fmt__cmp); + return __syscall_fmt__find(syscall_fmts, nmemb, name); } -static struct syscall_fmt *syscall_fmt__find_by_alias(const char *alias) +static struct syscall_fmt *__syscall_fmt__find_by_alias(struct syscall_fmt *fmts, const int nmemb, const char *alias) { - int i, nmemb = ARRAY_SIZE(syscall_fmts); + int i; for (i = 0; i < nmemb; ++i) { - if (syscall_fmts[i].alias && strcmp(syscall_fmts[i].alias, alias) == 0) - return &syscall_fmts[i]; + if (fmts[i].alias && strcmp(fmts[i].alias, alias) == 0) + return &fmts[i]; } return NULL; } +static struct syscall_fmt *syscall_fmt__find_by_alias(const char *alias) +{ + const int nmemb = ARRAY_SIZE(syscall_fmts); + return __syscall_fmt__find_by_alias(syscall_fmts, nmemb, alias); +} + /* * 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. @@ -1453,15 +1659,39 @@ static int syscall__alloc_arg_fmts(struct syscall *sc, int nr_args) return 0; } -static int syscall__set_arg_fmts(struct syscall *sc) +static struct syscall_arg_fmt syscall_arg_fmts__by_name[] = { + { .name = "msr", .scnprintf = SCA_X86_MSR, .strtoul = STUL_X86_MSR, }, + { .name = "vector", .scnprintf = SCA_X86_IRQ_VECTORS, .strtoul = STUL_X86_IRQ_VECTORS, }, +}; + +static int syscall_arg_fmt__cmp(const void *name, const void *fmtp) +{ + const struct syscall_arg_fmt *fmt = fmtp; + return strcmp(name, fmt->name); +} + +static struct syscall_arg_fmt * +__syscall_arg_fmt__find_by_name(struct syscall_arg_fmt *fmts, const int nmemb, const char *name) +{ + return bsearch(name, fmts, nmemb, sizeof(struct syscall_arg_fmt), syscall_arg_fmt__cmp); +} + +static struct syscall_arg_fmt *syscall_arg_fmt__find_by_name(const char *name) { - struct tep_format_field *field, *last_field = NULL; - int idx = 0, len; + const int nmemb = ARRAY_SIZE(syscall_arg_fmts__by_name); + return __syscall_arg_fmt__find_by_name(syscall_arg_fmts__by_name, nmemb, name); +} - for (field = sc->args; field; field = field->next, ++idx) { +static struct tep_format_field * +syscall_arg_fmt__init_array(struct syscall_arg_fmt *arg, struct tep_format_field *field) +{ + struct tep_format_field *last_field = NULL; + int len; + + for (; field; field = field->next, ++arg) { last_field = field; - if (sc->fmt && sc->fmt->arg[idx].scnprintf) + if (arg->scnprintf) continue; len = strlen(field->name); @@ -1469,14 +1699,17 @@ static int syscall__set_arg_fmts(struct syscall *sc) if (strcmp(field->type, "const char *") == 0 && ((len >= 4 && strcmp(field->name + len - 4, "name") == 0) || strstr(field->name, "path") != NULL)) - sc->arg_fmt[idx].scnprintf = SCA_FILENAME; + arg->scnprintf = SCA_FILENAME; else if ((field->flags & TEP_FIELD_IS_POINTER) || strstr(field->name, "addr")) - sc->arg_fmt[idx].scnprintf = SCA_PTR; + arg->scnprintf = SCA_PTR; else if (strcmp(field->type, "pid_t") == 0) - sc->arg_fmt[idx].scnprintf = SCA_PID; + arg->scnprintf = SCA_PID; else if (strcmp(field->type, "umode_t") == 0) - sc->arg_fmt[idx].scnprintf = SCA_MODE_T; - else if ((strcmp(field->type, "int") == 0 || + arg->scnprintf = SCA_MODE_T; + else if ((field->flags & TEP_FIELD_IS_ARRAY) && strstr(field->type, "char")) { + arg->scnprintf = SCA_CHAR_ARRAY; + arg->nr_entries = field->arraylen; + } else if ((strcmp(field->type, "int") == 0 || strcmp(field->type, "unsigned int") == 0 || strcmp(field->type, "long") == 0) && len >= 2 && strcmp(field->name + len - 2, "fd") == 0) { @@ -1487,10 +1720,24 @@ static int syscall__set_arg_fmts(struct syscall *sc) * 23 unsigned int * 7 unsigned long */ - sc->arg_fmt[idx].scnprintf = SCA_FD; + arg->scnprintf = SCA_FD; + } else { + struct syscall_arg_fmt *fmt = syscall_arg_fmt__find_by_name(field->name); + + if (fmt) { + arg->scnprintf = fmt->scnprintf; + arg->strtoul = fmt->strtoul; + } } } + return last_field; +} + +static int syscall__set_arg_fmts(struct syscall *sc) +{ + struct tep_format_field *last_field = syscall_arg_fmt__init_array(sc->arg_fmt, sc->args); + if (last_field) sc->args_size = last_field->offset + last_field->size; @@ -1552,6 +1799,18 @@ static int trace__read_syscall_info(struct trace *trace, int id) return syscall__set_arg_fmts(sc); } +static int perf_evsel__init_tp_arg_scnprintf(struct evsel *evsel) +{ + struct syscall_arg_fmt *fmt = evsel__syscall_arg_fmt(evsel); + + if (fmt != NULL) { + syscall_arg_fmt__init_array(fmt, evsel->tp_format->format.fields); + return 0; + } + + return -ENOMEM; +} + static int intcmp(const void *a, const void *b) { const int *one = a, *another = b; @@ -1680,22 +1939,22 @@ static size_t syscall__scnprintf_name(struct syscall *sc, char *bf, size_t size, * 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) +static unsigned long syscall_arg_fmt__mask_val(struct syscall_arg_fmt *fmt, 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); + if (fmt && fmt->mask_val) + return fmt->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) +static size_t syscall_arg_fmt__scnprintf_val(struct syscall_arg_fmt *fmt, char *bf, size_t size, + struct syscall_arg *arg, unsigned long val) { - if (sc->arg_fmt && sc->arg_fmt[arg->idx].scnprintf) { + if (fmt && fmt->scnprintf) { arg->val = val; - if (sc->arg_fmt[arg->idx].parm) - arg->parm = sc->arg_fmt[arg->idx].parm; - return sc->arg_fmt[arg->idx].scnprintf(bf, size, arg); + if (fmt->parm) + arg->parm = fmt->parm; + return fmt->scnprintf(bf, size, arg); } return scnprintf(bf, size, "%ld", val); } @@ -1736,12 +1995,13 @@ static size_t syscall__scnprintf_args(struct syscall *sc, char *bf, size_t size, if (arg.mask & bit) continue; + arg.fmt = &sc->arg_fmt[arg.idx]; 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); + val = syscall_arg_fmt__mask_val(&sc->arg_fmt[arg.idx], &arg, val); /* * Suppress this argument if its value is zero and @@ -1762,7 +2022,8 @@ static size_t syscall__scnprintf_args(struct syscall *sc, char *bf, size_t size, 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); + printed += syscall_arg_fmt__scnprintf_val(&sc->arg_fmt[arg.idx], + bf + printed, size - printed, &arg, val); } } else if (IS_ERR(sc->tp_format)) { /* @@ -1777,7 +2038,7 @@ static size_t syscall__scnprintf_args(struct syscall *sc, char *bf, size_t size, if (printed) printed += scnprintf(bf + printed, size - printed, ", "); printed += syscall__scnprintf_name(sc, bf + printed, size - printed, &arg); - printed += syscall__scnprintf_val(sc, bf + printed, size - printed, &arg, val); + printed += syscall_arg_fmt__scnprintf_val(&sc->arg_fmt[arg.idx], bf + printed, size - printed, &arg, val); next_arg: ++arg.idx; bit <<= 1; @@ -1844,11 +2105,18 @@ out_cant_read: return NULL; } -static void thread__update_stats(struct thread_trace *ttrace, - int id, struct perf_sample *sample) +struct syscall_stats { + struct stats stats; + u64 nr_failures; + int max_errno; + u32 *errnos; +}; + +static void thread__update_stats(struct thread *thread, struct thread_trace *ttrace, + int id, struct perf_sample *sample, long err, bool errno_summary) { struct int_node *inode; - struct stats *stats; + struct syscall_stats *stats; u64 duration = 0; inode = intlist__findnew(ttrace->syscall_stats, id); @@ -1857,17 +2125,46 @@ static void thread__update_stats(struct thread_trace *ttrace, stats = inode->priv; if (stats == NULL) { - stats = malloc(sizeof(struct stats)); + stats = malloc(sizeof(*stats)); if (stats == NULL) return; - init_stats(stats); + + stats->nr_failures = 0; + stats->max_errno = 0; + stats->errnos = NULL; + init_stats(&stats->stats); inode->priv = stats; } if (ttrace->entry_time && sample->time > ttrace->entry_time) duration = sample->time - ttrace->entry_time; - update_stats(stats, duration); + update_stats(&stats->stats, duration); + + if (err < 0) { + ++stats->nr_failures; + + if (!errno_summary) + return; + + err = -err; + if (err > stats->max_errno) { + u32 *new_errnos = realloc(stats->errnos, err * sizeof(u32)); + + if (new_errnos) { + memset(new_errnos + stats->max_errno, 0, (err - stats->max_errno) * sizeof(u32)); + } else { + pr_debug("Not enough memory for errno stats for thread \"%s\"(%d/%d), results will be incomplete\n", + thread__comm_str(thread), thread->pid_, thread->tid); + return; + } + + stats->errnos = new_errnos; + stats->max_errno = err; + } + + ++stats->errnos[err - 1]; + } } static int trace__printf_interrupted_entry(struct trace *trace) @@ -2112,11 +2409,11 @@ static int trace__sys_exit(struct trace *trace, struct evsel *evsel, trace__fprintf_sample(trace, evsel, sample, thread); - if (trace->summary) - thread__update_stats(ttrace, id, sample); - ret = perf_evsel__sc_tp_uint(evsel, ret, sample); + if (trace->summary) + thread__update_stats(thread, ttrace, id, sample, ret, trace->errno_summary); + if (!trace->fd_path_disabled && sc->is_open && ret >= 0 && ttrace->filename.pending_open) { trace__set_fd_pathname(thread, ret, ttrace->filename.name); ttrace->filename.pending_open = false; @@ -2346,6 +2643,80 @@ static void bpf_output__fprintf(struct trace *trace, ++trace->nr_events_printed; } +static size_t trace__fprintf_tp_fields(struct trace *trace, struct evsel *evsel, struct perf_sample *sample, + struct thread *thread, void *augmented_args, int augmented_args_size) +{ + char bf[2048]; + size_t size = sizeof(bf); + struct tep_format_field *field = evsel->tp_format->format.fields; + struct syscall_arg_fmt *arg = __evsel__syscall_arg_fmt(evsel); + size_t printed = 0; + unsigned long val; + u8 bit = 1; + struct syscall_arg syscall_arg = { + .augmented = { + .size = augmented_args_size, + .args = augmented_args, + }, + .idx = 0, + .mask = 0, + .trace = trace, + .thread = thread, + .show_string_prefix = trace->show_string_prefix, + }; + + for (; field && arg; field = field->next, ++syscall_arg.idx, bit <<= 1, ++arg) { + if (syscall_arg.mask & bit) + continue; + + syscall_arg.len = 0; + syscall_arg.fmt = arg; + if (field->flags & TEP_FIELD_IS_ARRAY) { + int offset = field->offset; + + if (field->flags & TEP_FIELD_IS_DYNAMIC) { + offset = format_field__intval(field, sample, evsel->needs_swap); + syscall_arg.len = offset >> 16; + offset &= 0xffff; + } + + val = (uintptr_t)(sample->raw_data + offset); + } else + val = format_field__intval(field, sample, evsel->needs_swap); + /* + * Some syscall args need some mask, most don't and + * return val untouched. + */ + val = syscall_arg_fmt__mask_val(arg, &syscall_arg, val); + + /* + * Suppress this argument if its value is zero and + * and we don't have a string associated in an + * strarray for it. + */ + if (val == 0 && + !trace->show_zeros && + !((arg->show_zero || + arg->scnprintf == SCA_STRARRAY || + arg->scnprintf == SCA_STRARRAYS) && + arg->parm)) + continue; + + printed += scnprintf(bf + printed, size - printed, "%s", printed ? ", " : ""); + + /* + * XXX Perhaps we should have a show_tp_arg_names, + * leaving show_arg_names just for syscalls? + */ + if (1 || trace->show_arg_names) + printed += scnprintf(bf + printed, size - printed, "%s: ", field->name); + + printed += syscall_arg_fmt__scnprintf_val(arg, bf + printed, size - printed, &syscall_arg, val); + } + + return printed + fprintf(trace->output, "%s", bf); +} + static int trace__event_handler(struct trace *trace, struct evsel *evsel, union perf_event *event __maybe_unused, struct perf_sample *sample) @@ -2399,32 +2770,37 @@ static int trace__event_handler(struct trace *trace, struct evsel *evsel, */ } - fprintf(trace->output, "%s:", evsel->name); + fprintf(trace->output, "%s(", evsel->name); if (perf_evsel__is_bpf_output(evsel)) { 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) { - evsel__disable(evsel); - evsel__close(evsel); + if (trace->libtraceevent_print) { + event_format__fprintf(evsel->tp_format, sample->cpu, + sample->raw_data, sample->raw_size, + trace->output); + } else { + trace__fprintf_tp_fields(trace, evsel, sample, thread, NULL, 0); } } } newline: - fprintf(trace->output, "\n"); + fprintf(trace->output, ")\n"); if (callchain_ret > 0) 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; + + if (evsel->max_events != ULONG_MAX && ++evsel->nr_events_printed == evsel->max_events) { + evsel__disable(evsel); + evsel__close(evsel); + } out: thread__put(thread); return 0; @@ -2576,21 +2952,23 @@ static int trace__record(struct trace *trace, int argc, const char **argv) "-m", "1024", "-c", "1", }; - + pid_t pid = getpid(); + char *filter = asprintf__tp_filter_pids(1, &pid); const char * const sc_args[] = { "-e", }; unsigned int sc_args_nr = ARRAY_SIZE(sc_args); const char * const majpf_args[] = { "-e", "major-faults" }; unsigned int majpf_args_nr = ARRAY_SIZE(majpf_args); const char * const minpf_args[] = { "-e", "minor-faults" }; unsigned int minpf_args_nr = ARRAY_SIZE(minpf_args); + int err = -1; - /* +1 is for the event string below */ - rec_argc = ARRAY_SIZE(record_args) + sc_args_nr + 1 + + /* +3 is for the event string below and the pid filter */ + rec_argc = ARRAY_SIZE(record_args) + sc_args_nr + 3 + majpf_args_nr + minpf_args_nr + argc; rec_argv = calloc(rec_argc + 1, sizeof(char *)); - if (rec_argv == NULL) - return -ENOMEM; + if (rec_argv == NULL || filter == NULL) + goto out_free; j = 0; for (i = 0; i < ARRAY_SIZE(record_args); i++) @@ -2607,11 +2985,13 @@ static int trace__record(struct trace *trace, int argc, const char **argv) rec_argv[j++] = "syscalls:sys_enter,syscalls:sys_exit"; else { pr_err("Neither raw_syscalls nor syscalls events exist.\n"); - free(rec_argv); - return -1; + goto out_free; } } + rec_argv[j++] = "--filter"; + rec_argv[j++] = filter; + if (trace->trace_pgfaults & TRACE_PFMAJ) for (i = 0; i < majpf_args_nr; i++) rec_argv[j++] = majpf_args[i]; @@ -2623,7 +3003,11 @@ static int trace__record(struct trace *trace, int argc, const char **argv) for (i = 0; i < (unsigned int)argc; i++) rec_argv[j++] = argv[i]; - return cmd_record(j, rec_argv); + err = cmd_record(j, rec_argv); +out_free: + free(filter); + free(rec_argv); + return err; } static size_t trace__fprintf_thread_summary(struct trace *trace, FILE *fp); @@ -2632,11 +3016,18 @@ static bool evlist__add_vfs_getname(struct evlist *evlist) { bool found = false; struct evsel *evsel, *tmp; - struct parse_events_error err = { .idx = 0, }; - int ret = parse_events(evlist, "probe:vfs_getname*", &err); + struct parse_events_error err; + int ret; - if (ret) + bzero(&err, sizeof(err)); + ret = parse_events(evlist, "probe:vfs_getname*", &err); + if (ret) { + free(err.str); + free(err.help); + free(err.first_str); + free(err.first_help); return false; + } evlist__for_each_entry_safe(evlist, evsel, tmp) { if (!strstarts(perf_evsel__name(evsel), "probe:vfs_getname")) @@ -3103,7 +3494,27 @@ static int trace__init_syscalls_bpf_prog_array_maps(struct trace *trace) return err; } -#else + +static void trace__delete_augmented_syscalls(struct trace *trace) +{ + struct evsel *evsel, *tmp; + + evlist__remove(trace->evlist, trace->syscalls.events.augmented); + evsel__delete(trace->syscalls.events.augmented); + trace->syscalls.events.augmented = NULL; + + evlist__for_each_entry_safe(trace->evlist, tmp, evsel) { + if (evsel->bpf_obj == trace->bpf_obj) { + evlist__remove(trace->evlist, evsel); + evsel__delete(evsel); + } + + } + + bpf_object__close(trace->bpf_obj); + trace->bpf_obj = NULL; +} +#else // HAVE_LIBBPF_SUPPORT static int trace__set_ev_qualifier_bpf_filter(struct trace *trace __maybe_unused) { return 0; @@ -3124,8 +3535,27 @@ static int trace__init_syscalls_bpf_prog_array_maps(struct trace *trace __maybe_ { return 0; } + +static void trace__delete_augmented_syscalls(struct trace *trace __maybe_unused) +{ +} #endif // HAVE_LIBBPF_SUPPORT +static bool trace__only_augmented_syscalls_evsels(struct trace *trace) +{ + struct evsel *evsel; + + evlist__for_each_entry(trace->evlist, evsel) { + if (evsel == trace->syscalls.events.augmented || + evsel->bpf_obj == trace->bpf_obj) + continue; + + return false; + } + + return true; +} + static int trace__set_ev_qualifier_filter(struct trace *trace) { if (trace->syscalls.map) @@ -3175,7 +3605,7 @@ static int trace__set_filter_loop_pids(struct trace *trace) thread = parent; } - err = perf_evlist__set_tp_filter_pids(trace->evlist, nr, pids); + err = perf_evlist__append_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); @@ -3192,8 +3622,8 @@ static int trace__set_filter_pids(struct trace *trace) * 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); + err = perf_evlist__append_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); @@ -3263,6 +3693,137 @@ static int ordered_events__deliver_event(struct ordered_events *oe, return __trace__deliver_event(trace, event->event); } +static struct syscall_arg_fmt *perf_evsel__syscall_arg_fmt(struct evsel *evsel, char *arg) +{ + struct tep_format_field *field; + struct syscall_arg_fmt *fmt = __evsel__syscall_arg_fmt(evsel); + + if (evsel->tp_format == NULL || fmt == NULL) + return NULL; + + for (field = evsel->tp_format->format.fields; field; field = field->next, ++fmt) + if (strcmp(field->name, arg) == 0) + return fmt; + + return NULL; +} + +static int trace__expand_filter(struct trace *trace __maybe_unused, struct evsel *evsel) +{ + char *tok, *left = evsel->filter, *new_filter = evsel->filter; + + while ((tok = strpbrk(left, "=<>!")) != NULL) { + char *right = tok + 1, *right_end; + + if (*right == '=') + ++right; + + while (isspace(*right)) + ++right; + + if (*right == '\0') + break; + + while (!isalpha(*left)) + if (++left == tok) { + /* + * Bail out, can't find the name of the argument that is being + * used in the filter, let it try to set this filter, will fail later. + */ + return 0; + } + + right_end = right + 1; + while (isalnum(*right_end) || *right_end == '_' || *right_end == '|') + ++right_end; + + if (isalpha(*right)) { + struct syscall_arg_fmt *fmt; + int left_size = tok - left, + right_size = right_end - right; + char arg[128]; + + while (isspace(left[left_size - 1])) + --left_size; + + scnprintf(arg, sizeof(arg), "%.*s", left_size, left); + + fmt = perf_evsel__syscall_arg_fmt(evsel, arg); + if (fmt == NULL) { + pr_err("\"%s\" not found in \"%s\", can't set filter \"%s\"\n", + arg, evsel->name, evsel->filter); + return -1; + } + + pr_debug2("trying to expand \"%s\" \"%.*s\" \"%.*s\" -> ", + arg, (int)(right - tok), tok, right_size, right); + + if (fmt->strtoul) { + u64 val; + struct syscall_arg syscall_arg = { + .parm = fmt->parm, + }; + + if (fmt->strtoul(right, right_size, &syscall_arg, &val)) { + char *n, expansion[19]; + int expansion_lenght = scnprintf(expansion, sizeof(expansion), "%#" PRIx64, val); + int expansion_offset = right - new_filter; + + pr_debug("%s", expansion); + + if (asprintf(&n, "%.*s%s%s", expansion_offset, new_filter, expansion, right_end) < 0) { + pr_debug(" out of memory!\n"); + free(new_filter); + return -1; + } + if (new_filter != evsel->filter) + free(new_filter); + left = n + expansion_offset + expansion_lenght; + new_filter = n; + } else { + pr_err("\"%.*s\" not found for \"%s\" in \"%s\", can't set filter \"%s\"\n", + right_size, right, arg, evsel->name, evsel->filter); + return -1; + } + } else { + pr_err("No resolver (strtoul) for \"%s\" in \"%s\", can't set filter \"%s\"\n", + arg, evsel->name, evsel->filter); + return -1; + } + + pr_debug("\n"); + } else { + left = right_end; + } + } + + if (new_filter != evsel->filter) { + pr_debug("New filter for %s: %s\n", evsel->name, new_filter); + perf_evsel__set_filter(evsel, new_filter); + free(new_filter); + } + + return 0; +} + +static int trace__expand_filters(struct trace *trace, struct evsel **err_evsel) +{ + struct evlist *evlist = trace->evlist; + struct evsel *evsel; + + evlist__for_each_entry(evlist, evsel) { + if (evsel->filter == NULL) + continue; + + if (trace__expand_filter(trace, evsel)) { + *err_evsel = evsel; + return -1; + } + } + + return 0; +} + static int trace__run(struct trace *trace, int argc, const char **argv) { struct evlist *evlist = trace->evlist; @@ -3302,7 +3863,6 @@ static int trace__run(struct trace *trace, int argc, const char **argv) perf_evlist__add_newtp(evlist, "sched", "sched_stat_runtime", trace__sched_stat_runtime)) goto out_error_sched_stat_runtime; - /* * If a global cgroup was set, apply it to all the events without an * explicit cgroup. I.e.: @@ -3405,6 +3965,9 @@ static int trace__run(struct trace *trace, int argc, const char **argv) */ trace->fd_path_disabled = !trace__syscall_enabled(trace, syscalltbl__id(trace->sctbl, "close")); + err = trace__expand_filters(trace, &evsel); + if (err) + goto out_delete_evlist; err = perf_evlist__apply_filters(evlist, &evsel); if (err < 0) goto out_error_apply_filters; @@ -3450,17 +4013,17 @@ again: struct mmap *md; md = &evlist->mmap[i]; - if (perf_mmap__read_init(md) < 0) + if (perf_mmap__read_init(&md->core) < 0) continue; - while ((event = perf_mmap__read_event(md)) != NULL) { + while ((event = perf_mmap__read_event(&md->core)) != NULL) { ++trace->nr_events; err = trace__deliver_event(trace, event); if (err) goto out_disable; - perf_mmap__consume(md); + perf_mmap__consume(&md->core); if (interrupted) goto out_disable; @@ -3470,7 +4033,7 @@ again: draining = true; } } - perf_mmap__read_done(md); + perf_mmap__read_done(&md->core); } if (trace->nr_events == before) { @@ -3665,17 +4228,17 @@ static size_t trace__fprintf_threads_header(FILE *fp) } DEFINE_RESORT_RB(syscall_stats, a->msecs > b->msecs, - struct stats *stats; - double msecs; - int syscall; + struct syscall_stats *stats; + double msecs; + int syscall; ) { struct int_node *source = rb_entry(nd, struct int_node, rb_node); - struct stats *stats = source->priv; + struct syscall_stats *stats = source->priv; entry->syscall = source->i; entry->stats = stats; - entry->msecs = stats ? (u64)stats->n * (avg_stats(stats) / NSEC_PER_MSEC) : 0; + entry->msecs = stats ? (u64)stats->stats.n * (avg_stats(&stats->stats) / NSEC_PER_MSEC) : 0; } static size_t thread__dump_stats(struct thread_trace *ttrace, @@ -3691,27 +4254,37 @@ static size_t thread__dump_stats(struct thread_trace *ttrace, printed += fprintf(fp, "\n"); - printed += fprintf(fp, " syscall calls total min avg max stddev\n"); - printed += fprintf(fp, " (msec) (msec) (msec) (msec) (%%)\n"); - printed += fprintf(fp, " --------------- -------- --------- --------- --------- --------- ------\n"); + printed += fprintf(fp, " syscall calls errors total min avg max stddev\n"); + printed += fprintf(fp, " (msec) (msec) (msec) (msec) (%%)\n"); + printed += fprintf(fp, " --------------- -------- ------ -------- --------- --------- --------- ------\n"); resort_rb__for_each_entry(nd, syscall_stats) { - struct stats *stats = syscall_stats_entry->stats; + struct syscall_stats *stats = syscall_stats_entry->stats; if (stats) { - double min = (double)(stats->min) / NSEC_PER_MSEC; - double max = (double)(stats->max) / NSEC_PER_MSEC; - double avg = avg_stats(stats); + double min = (double)(stats->stats.min) / NSEC_PER_MSEC; + double max = (double)(stats->stats.max) / NSEC_PER_MSEC; + double avg = avg_stats(&stats->stats); double pct; - u64 n = (u64) stats->n; + u64 n = (u64)stats->stats.n; - pct = avg ? 100.0 * stddev_stats(stats)/avg : 0.0; + pct = avg ? 100.0 * stddev_stats(&stats->stats) / avg : 0.0; avg /= NSEC_PER_MSEC; sc = &trace->syscalls.table[syscall_stats_entry->syscall]; printed += fprintf(fp, " %-15s", sc->name); - printed += fprintf(fp, " %8" PRIu64 " %9.3f %9.3f %9.3f", - n, syscall_stats_entry->msecs, min, avg); + printed += fprintf(fp, " %8" PRIu64 " %6" PRIu64 " %9.3f %9.3f %9.3f", + n, stats->nr_failures, syscall_stats_entry->msecs, min, avg); printed += fprintf(fp, " %9.3f %9.2f%%\n", max, pct); + + if (trace->errno_summary && stats->nr_failures) { + const char *arch_name = perf_env__arch(trace->host->env); + int e; + + for (e = 0; e < stats->max_errno; ++e) { + if (stats->errnos[e] != 0) + fprintf(fp, "\t\t\t\t%s: %d\n", arch_syscalls__strerrno(arch_name, e + 1), stats->errnos[e]); + } + } } } @@ -3858,12 +4431,33 @@ static int parse_pagefaults(const struct option *opt, const char *str, return 0; } -static void evlist__set_evsel_handler(struct evlist *evlist, void *handler) +static void evlist__set_default_evsel_handler(struct evlist *evlist, void *handler) { struct evsel *evsel; - evlist__for_each_entry(evlist, evsel) - evsel->handler = handler; + evlist__for_each_entry(evlist, evsel) { + if (evsel->handler == NULL) + evsel->handler = handler; + } +} + +static void evsel__set_syscall_arg_fmt(struct evsel *evsel, const char *name) +{ + struct syscall_arg_fmt *fmt = evsel__syscall_arg_fmt(evsel); + + if (fmt) { + struct syscall_fmt *scfmt = syscall_fmt__find(name); + + if (scfmt) { + int skip = 0; + + if (strcmp(evsel->tp_format->format.fields->name, "__syscall_nr") == 0 || + strcmp(evsel->tp_format->format.fields->name, "nr") == 0) + ++skip; + + memcpy(fmt + skip, scfmt->arg, (evsel->tp_format->format.nr_fields - skip) * sizeof(*fmt)); + } + } } static int evlist__set_syscall_tp_fields(struct evlist *evlist) @@ -3874,22 +4468,28 @@ static int evlist__set_syscall_tp_fields(struct evlist *evlist) if (evsel->priv || !evsel->tp_format) continue; - if (strcmp(evsel->tp_format->system, "syscalls")) + if (strcmp(evsel->tp_format->system, "syscalls")) { + perf_evsel__init_tp_arg_scnprintf(evsel); continue; + } if (perf_evsel__init_syscall_tp(evsel)) return -1; if (!strncmp(evsel->tp_format->name, "sys_enter_", 10)) { - struct syscall_tp *sc = evsel->priv; + struct syscall_tp *sc = __evsel__syscall_tp(evsel); if (__tp_field__init_ptr(&sc->args, sc->id.offset + sizeof(u64))) return -1; + + evsel__set_syscall_arg_fmt(evsel, evsel->tp_format->name + sizeof("sys_enter_") - 1); } else if (!strncmp(evsel->tp_format->name, "sys_exit_", 9)) { - struct syscall_tp *sc = evsel->priv; + struct syscall_tp *sc = __evsel__syscall_tp(evsel); if (__tp_field__init_uint(&sc->ret, sizeof(u64), sc->id.offset + sizeof(u64), evsel->needs_swap)) return -1; + + evsel__set_syscall_arg_fmt(evsel, evsel->tp_format->name + sizeof("sys_exit_") - 1); } } @@ -4029,15 +4629,11 @@ static int trace__config(const char *var, const char *value, void *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); - /* - * We can't propagate parse_event_option() return, as it is 1 - * for failure while perf_config() expects -1. - */ - if (parse_events_option(&o, value, 0)) - err = -1; + trace->perfconfig_events = strdup(value); + if (trace->perfconfig_events == NULL) { + pr_err("Not enough memory for %s\n", "trace.add_events"); + return -1; + } } else if (!strcmp(var, "trace.show_timestamp")) { trace->show_tstamp = perf_config_bool(var, value); } else if (!strcmp(var, "trace.show_duration")) { @@ -4061,6 +4657,11 @@ static int trace__config(const char *var, const char *value, void *arg) int args_alignment = 0; if (perf_config_int(&args_alignment, var, value) == 0) trace->args_alignment = args_alignment; + } else if (!strcmp(var, "trace.tracepoint_beautifiers")) { + if (strcasecmp(value, "libtraceevent") == 0) + trace->libtraceevent_print = true; + else if (strcasecmp(value, "libbeauty") == 0) + trace->libtraceevent_print = false; } out: return err; @@ -4103,6 +4704,8 @@ int cmd_trace(int argc, const char **argv) OPT_CALLBACK('e', "event", &trace, "event", "event/syscall selector. use 'perf list' to list available events", trace__parse_events_option), + OPT_CALLBACK(0, "filter", &trace.evlist, "filter", + "event filter", parse_filter), OPT_BOOLEAN(0, "comm", &trace.show_comm, "show the thread COMM next to its id"), OPT_BOOLEAN(0, "tool_stats", &trace.show_tool_stats, "show tool stats"), @@ -4143,6 +4746,8 @@ int cmd_trace(int argc, const char **argv) "Show only syscall summary with statistics"), OPT_BOOLEAN('S', "with-summary", &trace.summary, "Show all syscalls and summary with statistics"), + OPT_BOOLEAN(0, "errno-summary", &trace.errno_summary, + "Show errno stats per syscall, use with -s or -S"), OPT_CALLBACK_DEFAULT('F', "pf", &trace.trace_pgfaults, "all|maj|min", "Trace pagefaults", parse_pagefaults, "maj"), OPT_BOOLEAN(0, "syscalls", &trace.trace_syscalls, "Trace syscalls"), @@ -4150,6 +4755,8 @@ int cmd_trace(int argc, const char **argv) OPT_CALLBACK(0, "call-graph", &trace.opts, "record_mode[,record_size]", record_callchain_help, &record_parse_callchain_opt), + OPT_BOOLEAN(0, "libtraceevent_print", &trace.libtraceevent_print, + "Use libtraceevent to print the tracepoint arguments."), 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, @@ -4210,6 +4817,38 @@ int cmd_trace(int argc, const char **argv) argc = parse_options_subcommand(argc, argv, trace_options, trace_subcommands, trace_usage, PARSE_OPT_STOP_AT_NON_OPTION); + /* + * Here we already passed thru trace__parse_events_option() and it has + * already figured out if -e syscall_name, if not but if --event + * foo:bar was used, the user is interested _just_ in those, say, + * tracepoint events, not in the strace-like syscall-name-based mode. + * + * This is important because we need to check if strace-like mode is + * needed to decided if we should filter out the eBPF + * __augmented_syscalls__ code, if it is in the mix, say, via + * .perfconfig trace.add_events, and filter those out. + */ + if (!trace.trace_syscalls && !trace.trace_pgfaults && + trace.evlist->core.nr_entries == 0 /* Was --events used? */) { + trace.trace_syscalls = true; + } + /* + * Now that we have --verbose figured out, lets see if we need to parse + * events from .perfconfig, so that if those events fail parsing, say some + * BPF program fails, then we'll be able to use --verbose to see what went + * wrong in more detail. + */ + if (trace.perfconfig_events != NULL) { + struct parse_events_error parse_err; + + bzero(&parse_err, sizeof(parse_err)); + err = parse_events(trace.evlist, trace.perfconfig_events, &parse_err); + if (err) { + parse_events_print_error(&parse_err, trace.perfconfig_events); + goto out; + } + } + if ((nr_cgroups || trace.cgroup) && !trace.opts.target.system_wide) { usage_with_options_msg(trace_usage, trace_options, "cgroup monitoring only available in system-wide mode"); @@ -4238,9 +4877,45 @@ int cmd_trace(int argc, const char **argv) trace.bpf_obj = evsel->bpf_obj; - trace__set_bpf_map_filtered_pids(&trace); - trace__set_bpf_map_syscalls(&trace); - trace.syscalls.unaugmented_prog = trace__find_bpf_program_by_title(&trace, "!raw_syscalls:unaugmented"); + /* + * If we have _just_ the augmenter event but don't have a + * explicit --syscalls, then assume we want all strace-like + * syscalls: + */ + if (!trace.trace_syscalls && trace__only_augmented_syscalls_evsels(&trace)) + trace.trace_syscalls = true; + /* + * So, if we have a syscall augmenter, but trace_syscalls, aka + * strace-like syscall tracing is not set, then we need to trow + * away the augmenter, i.e. all the events that were created + * from that BPF object file. + * + * This is more to fix the current .perfconfig trace.add_events + * style of setting up the strace-like eBPF based syscall point + * payload augmenter. + * + * All this complexity will be avoided by adding an alternative + * to trace.add_events in the form of + * trace.bpf_augmented_syscalls, that will be only parsed if we + * need it. + * + * .perfconfig trace.add_events is still useful if we want, for + * instance, have msr_write.msr in some .perfconfig profile based + * 'perf trace --config determinism.profile' mode, where for some + * particular goal/workload type we want a set of events and + * output mode (with timings, etc) instead of having to add + * all via the command line. + * + * Also --config to specify an alternate .perfconfig file needs + * to be implemented. + */ + if (!trace.trace_syscalls) { + trace__delete_augmented_syscalls(&trace); + } else { + trace__set_bpf_map_filtered_pids(&trace); + trace__set_bpf_map_syscalls(&trace); + trace.syscalls.unaugmented_prog = trace__find_bpf_program_by_title(&trace, "!raw_syscalls:unaugmented"); + } } err = bpf__setup_stdout(trace.evlist); @@ -4287,7 +4962,7 @@ int cmd_trace(int argc, const char **argv) } if (trace.evlist->core.nr_entries > 0) { - evlist__set_evsel_handler(trace.evlist, trace__event_handler); + evlist__set_default_evsel_handler(trace.evlist, trace__event_handler); if (evlist__set_syscall_tp_fields(trace.evlist)) { perror("failed to set syscalls:* tracepoint fields"); goto out; @@ -4348,7 +5023,7 @@ int cmd_trace(int argc, const char **argv) init_augmented_syscall_tp: if (perf_evsel__init_augmented_syscall_tp(evsel, evsel)) goto out; - sc = evsel->priv; + sc = __evsel__syscall_tp(evsel); /* * For now with BPF raw_augmented we hook into * raw_syscalls:sys_enter and there we get all @@ -4379,15 +5054,14 @@ init_augmented_syscall_tp: if ((argc >= 1) && (strcmp(argv[0], "record") == 0)) return trace__record(&trace, argc-1, &argv[1]); + /* Using just --errno-summary will trigger --summary */ + if (trace.errno_summary && !trace.summary && !trace.summary_only) + trace.summary_only = true; + /* summary_only implies summary option, but don't overwrite summary if set */ if (trace.summary_only) trace.summary = trace.summary_only; - if (!trace.trace_syscalls && !trace.trace_pgfaults && - trace.evlist->core.nr_entries == 0 /* Was --events used? */) { - trace.trace_syscalls = true; - } - if (output_name != NULL) { err = trace__open_output(&trace, output_name); if (err < 0) { @@ -4426,5 +5100,6 @@ out_close: if (output_name != NULL) fclose(trace.output); out: + zfree(&trace.perfconfig_events); return err; } |