diff options
Diffstat (limited to 'tools/perf/builtin-record.c')
| -rw-r--r-- | tools/perf/builtin-record.c | 462 |
1 files changed, 311 insertions, 151 deletions
diff --git a/tools/perf/builtin-record.c b/tools/perf/builtin-record.c index 0a8ba1323d64..2584d0d8bc82 100644 --- a/tools/perf/builtin-record.c +++ b/tools/perf/builtin-record.c @@ -26,6 +26,7 @@ #include "util/target.h" #include "util/session.h" #include "util/tool.h" +#include "util/stat.h" #include "util/symbol.h" #include "util/record.h" #include "util/cpumap.h" @@ -51,6 +52,7 @@ #include "util/clockid.h" #include "util/off_cpu.h" #include "util/bpf-filter.h" +#include "util/strbuf.h" #include "asm/bug.h" #include "perf.h" #include "cputopo.h" @@ -161,6 +163,7 @@ struct record { struct evlist *sb_evlist; pthread_t thread_id; int realtime_prio; + bool latency; bool switch_output_event_set; bool no_buildid; bool no_buildid_set; @@ -168,9 +171,12 @@ struct record { bool no_buildid_cache_set; bool buildid_all; bool buildid_mmap; + bool buildid_mmap_set; bool timestamp_filename; bool timestamp_boundary; bool off_cpu; + const char *filter_action; + const char *uid_str; struct switch_output switch_output; unsigned long long samples; unsigned long output_max_size; /* = 0: unlimited */ @@ -193,6 +199,15 @@ static const char *affinity_tags[PERF_AFFINITY_MAX] = { "SYS", "NODE", "CPU" }; +static int build_id__process_mmap(const struct perf_tool *tool, union perf_event *event, + struct perf_sample *sample, struct machine *machine); +static int build_id__process_mmap2(const struct perf_tool *tool, union perf_event *event, + struct perf_sample *sample, struct machine *machine); +static int process_timestamp_boundary(const struct perf_tool *tool, + union perf_event *event, + struct perf_sample *sample, + struct machine *machine); + #ifndef HAVE_GETTID static inline pid_t gettid(void) { @@ -608,7 +623,7 @@ static int record__comp_enabled(struct record *rec) return rec->opts.comp_level > 0; } -static int process_synthesized_event(struct perf_tool *tool, +static int process_synthesized_event(const struct perf_tool *tool, union perf_event *event, struct perf_sample *sample __maybe_unused, struct machine *machine __maybe_unused) @@ -619,7 +634,7 @@ static int process_synthesized_event(struct perf_tool *tool, static struct mutex synth_lock; -static int process_locked_synthesized_event(struct perf_tool *tool, +static int process_locked_synthesized_event(const struct perf_tool *tool, union perf_event *event, struct perf_sample *sample __maybe_unused, struct machine *machine __maybe_unused) @@ -637,14 +652,27 @@ static int record__pushfn(struct mmap *map, void *to, void *bf, size_t size) struct record *rec = to; if (record__comp_enabled(rec)) { + struct perf_record_compressed2 *event = map->data; + size_t padding = 0; + u8 pad[8] = {0}; ssize_t compressed = zstd_compress(rec->session, map, map->data, mmap__mmap_len(map), bf, size); if (compressed < 0) return (int)compressed; - size = compressed; - bf = map->data; + bf = event; + thread->samples++; + + /* + * The record from `zstd_compress` is not 8 bytes aligned, which would cause asan + * error. We make it aligned here. + */ + event->data_size = compressed - sizeof(struct perf_record_compressed2); + event->header.size = PERF_ALIGN(compressed, sizeof(u64)); + padding = event->header.size - compressed; + return record__write(rec, map, bf, compressed) || + record__write(rec, map, &pad, padding); } thread->samples++; @@ -702,9 +730,7 @@ static void record__sig_exit(void) raise(signr); } -#ifdef HAVE_AUXTRACE_SUPPORT - -static int record__process_auxtrace(struct perf_tool *tool, +static int record__process_auxtrace(const struct perf_tool *tool, struct mmap *map, union perf_event *event, void *data1, size_t len1, void *data2, size_t len2) @@ -747,7 +773,9 @@ static int record__auxtrace_mmap_read(struct record *rec, { int ret; - ret = auxtrace_mmap__read(map, rec->itr, &rec->tool, + ret = auxtrace_mmap__read(map, rec->itr, + perf_session__env(rec->session), + &rec->tool, record__process_auxtrace); if (ret < 0) return ret; @@ -763,7 +791,9 @@ static int record__auxtrace_mmap_read_snapshot(struct record *rec, { int ret; - ret = auxtrace_mmap__read_snapshot(map, rec->itr, &rec->tool, + ret = auxtrace_mmap__read_snapshot(map, rec->itr, + perf_session__env(rec->session), + &rec->tool, record__process_auxtrace, rec->opts.auxtrace_snapshot_size); if (ret < 0) @@ -850,45 +880,13 @@ static int record__auxtrace_init(struct record *rec) if (err) return err; - auxtrace_regroup_aux_output(rec->evlist); + err = auxtrace_parse_aux_action(rec->evlist); + if (err) + return err; return auxtrace_parse_filters(rec->evlist); } -#else - -static inline -int record__auxtrace_mmap_read(struct record *rec __maybe_unused, - struct mmap *map __maybe_unused) -{ - return 0; -} - -static inline -void record__read_auxtrace_snapshot(struct record *rec __maybe_unused, - bool on_exit __maybe_unused) -{ -} - -static inline -int auxtrace_record__snapshot_start(struct auxtrace_record *itr __maybe_unused) -{ - return 0; -} - -static inline -int record__auxtrace_snapshot_exit(struct record *rec __maybe_unused) -{ - return 0; -} - -static int record__auxtrace_init(struct record *rec __maybe_unused) -{ - return 0; -} - -#endif - static int record__config_text_poke(struct evlist *evlist) { struct evsel *evsel; @@ -949,7 +947,6 @@ static int record__config_tracking_events(struct record *rec) */ if (opts->target.initial_delay || target__has_cpu(&opts->target) || perf_pmus__num_core_pmus() > 1) { - /* * User space tasks can migrate between CPUs, so when tracing * selected CPUs, sideband for all CPUs is still needed. @@ -1354,10 +1351,27 @@ static int record__open(struct record *rec) struct perf_session *session = rec->session; struct record_opts *opts = &rec->opts; int rc = 0; + bool skipped = false; + bool removed_tracking = false; evlist__for_each_entry(evlist, pos) { + if (removed_tracking) { + /* + * Normally the head of the list has tracking enabled + * for sideband data like mmaps. If this event is + * removed, make sure to add tracking to the next + * processed event. + */ + if (!pos->tracking) { + pos->tracking = true; + evsel__config(pos, opts, &callchain_param); + } + removed_tracking = false; + } try_again: if (evsel__open(pos, pos->core.cpus, pos->core.threads) < 0) { + bool report_error = true; + if (evsel__fallback(pos, &opts->target, errno, msg, sizeof(msg))) { if (verbose > 0) ui__warning("%s\n", msg); @@ -1369,15 +1383,72 @@ try_again: pos = evlist__reset_weak_group(evlist, pos, true); goto try_again; } - rc = -errno; - evsel__open_strerror(pos, &opts->target, errno, msg, sizeof(msg)); - ui__error("%s\n", msg); - goto out; +#if defined(__aarch64__) || defined(__arm__) + if (strstr(evsel__name(pos), "cycles")) { + struct evsel *pos2; + /* + * Unfortunately ARM has many events named + * "cycles" on PMUs like the system-level (L3) + * cache which don't support sampling. Only + * display such failures to open when there is + * only 1 cycles event or verbose is enabled. + */ + evlist__for_each_entry(evlist, pos2) { + if (pos2 == pos) + continue; + if (strstr(evsel__name(pos2), "cycles")) { + report_error = false; + break; + } + } + } +#endif + if (report_error || verbose > 0) { + ui__error("Failure to open event '%s' on PMU '%s' which will be " + "removed.\n%s\n", + evsel__name(pos), evsel__pmu_name(pos), msg); + } + if (pos->tracking) + removed_tracking = true; + pos->skippable = true; + skipped = true; } - - pos->supported = true; } + if (skipped) { + struct evsel *tmp; + int idx = 0; + bool evlist_empty = true; + + /* Remove evsels that failed to open and update indices. */ + evlist__for_each_entry_safe(evlist, tmp, pos) { + if (pos->skippable) { + evlist__remove(evlist, pos); + continue; + } + + /* + * Note, dummy events may be command line parsed or + * added by the tool. We care about supporting `perf + * record -e dummy` which may be used as a permission + * check. Dummy events that are added to the command + * line and opened along with other events that fail, + * will still fail as if the dummy events were tool + * added events for the sake of code simplicity. + */ + if (!evsel__is_dummy_event(pos)) + evlist_empty = false; + } + evlist__for_each_entry(evlist, pos) { + pos->core.idx = idx++; + } + /* If list is empty then fail. */ + if (evlist_empty) { + ui__error("Failure to open any events for recording.\n"); + rc = -1; + goto out; + } + } if (symbol_conf.kptr_restrict && !evlist__exclude_kernel(evlist)) { pr_warning( "WARNING: Kernel address maps (/proc/{kallsyms,modules}) are restricted,\n" @@ -1389,7 +1460,7 @@ try_again: "even with a suitable vmlinux or kallsyms file.\n\n"); } - if (evlist__apply_filters(evlist, &pos)) { + if (evlist__apply_filters(evlist, &pos, &opts->target)) { pr_err("failed to set filter \"%s\" on event %s with %d (%s)\n", pos->filter ?: "BPF", evsel__name(pos), errno, str_error_r(errno, msg, sizeof(msg))); @@ -1416,7 +1487,7 @@ static void set_timestamp_boundary(struct record *rec, u64 sample_time) rec->evlist->last_sample_time = sample_time; } -static int process_sample_event(struct perf_tool *tool, +static int process_sample_event(const struct perf_tool *tool, union perf_event *event, struct perf_sample *sample, struct evsel *evsel, @@ -1458,7 +1529,7 @@ static int process_buildids(struct record *rec) * first/last samples. */ if (rec->buildid_all && !rec->timestamp_boundary) - rec->tool.sample = NULL; + rec->tool.sample = process_event_sample_stub; return perf_session__process_events(session); } @@ -1521,7 +1592,7 @@ static void record__adjust_affinity(struct record *rec, struct mmap *map) static size_t process_comp_header(void *record, size_t increment) { - struct perf_record_compressed *event = record; + struct perf_record_compressed2 *event = record; size_t size = sizeof(*event); if (increment) { @@ -1529,7 +1600,7 @@ static size_t process_comp_header(void *record, size_t increment) return increment; } - event->header.type = PERF_RECORD_COMPRESSED; + event->header.type = PERF_RECORD_COMPRESSED2; event->header.size = size; return size; @@ -1539,7 +1610,7 @@ static ssize_t zstd_compress(struct perf_session *session, struct mmap *map, void *dst, size_t dst_size, void *src, size_t src_size) { ssize_t compressed; - size_t max_record_size = PERF_SAMPLE_MAX_SIZE - sizeof(struct perf_record_compressed) - 1; + size_t max_record_size = PERF_SAMPLE_MAX_SIZE - sizeof(struct perf_record_compressed2) - 1; struct zstd_data *zstd_data = &session->zstd_data; if (map && map->file) @@ -1738,10 +1809,8 @@ static void record__init_features(struct record *rec) if (rec->no_buildid) perf_header__clear_feat(&session->header, HEADER_BUILD_ID); -#ifdef HAVE_LIBTRACEEVENT if (!have_tracepoints(&rec->evlist->core.entries)) perf_header__clear_feat(&session->header, HEADER_TRACING_DATA); -#endif if (!rec->opts.branch_stack) perf_header__clear_feat(&session->header, HEADER_BRANCH_STACK); @@ -1784,15 +1853,15 @@ record__finish_output(struct record *rec) data->dir.files[i].size = lseek(data->dir.files[i].fd, 0, SEEK_CUR); } - if (!rec->no_buildid) { + /* Buildid scanning disabled or build ID in kernel and synthesized map events. */ + if (!rec->no_buildid || !rec->no_buildid_cache) { process_buildids(rec); if (rec->buildid_all) perf_session__dsos_hit_all(rec->session); } perf_session__write_header(rec->session, rec->evlist, fd, true); - - return; + perf_session__cache_build_ids(rec->session); } static int record__synthesize_workload(struct record *rec, bool tail) @@ -1907,9 +1976,10 @@ static void __record__save_lost_samples(struct record *rec, struct evsel *evsel, u16 misc_flag) { struct perf_sample_id *sid; - struct perf_sample sample = {}; + struct perf_sample sample; int id_hdr_size; + perf_sample__init(&sample, /*all=*/true); lost->lost = lost_count; if (evsel->core.ids) { sid = xyarray__entry(evsel->core.sample_id, cpu_idx, thread_idx); @@ -1921,12 +1991,13 @@ static void __record__save_lost_samples(struct record *rec, struct evsel *evsel, lost->header.size = sizeof(*lost) + id_hdr_size; lost->header.misc = misc_flag; record__write(rec, NULL, lost, lost->header.size); + perf_sample__exit(&sample); } static void record__read_lost_samples(struct record *rec) { struct perf_session *session = rec->session; - struct perf_record_lost_samples *lost = NULL; + struct perf_record_lost_samples_and_ids lost; struct evsel *evsel; /* there was an error during record__open */ @@ -1951,19 +2022,13 @@ static void record__read_lost_samples(struct record *rec) if (perf_evsel__read(&evsel->core, x, y, &count) < 0) { pr_debug("read LOST count failed\n"); - goto out; + return; } if (count.lost) { - if (!lost) { - lost = zalloc(PERF_SAMPLE_MAX_SIZE); - if (!lost) { - pr_debug("Memory allocation failed\n"); - return; - } - lost->header.type = PERF_RECORD_LOST_SAMPLES; - } - __record__save_lost_samples(rec, evsel, lost, + memset(&lost, 0, sizeof(lost)); + lost.lost.header.type = PERF_RECORD_LOST_SAMPLES; + __record__save_lost_samples(rec, evsel, &lost.lost, x, y, count.lost, 0); } } @@ -1971,20 +2036,12 @@ static void record__read_lost_samples(struct record *rec) lost_count = perf_bpf_filter__lost_count(evsel); if (lost_count) { - if (!lost) { - lost = zalloc(PERF_SAMPLE_MAX_SIZE); - if (!lost) { - pr_debug("Memory allocation failed\n"); - return; - } - lost->header.type = PERF_RECORD_LOST_SAMPLES; - } - __record__save_lost_samples(rec, evsel, lost, 0, 0, lost_count, + memset(&lost, 0, sizeof(lost)); + lost.lost.header.type = PERF_RECORD_LOST_SAMPLES; + __record__save_lost_samples(rec, evsel, &lost.lost, 0, 0, lost_count, PERF_RECORD_MISC_LOST_SAMPLES_BPF); } } -out: - free(lost); } static volatile sig_atomic_t workload_exec_errno; @@ -2147,6 +2204,14 @@ out: return err; } +static void record__synthesize_final_bpf_metadata(struct record *rec __maybe_unused) +{ +#ifdef HAVE_LIBBPF_SUPPORT + perf_event__synthesize_final_bpf_metadata(rec->session, + process_synthesized_event); +#endif +} + static int record__process_signal_event(union perf_event *event __maybe_unused, void *data) { struct record *rec = data; @@ -2178,7 +2243,7 @@ static int record__setup_sb_evlist(struct record *rec) } } - if (evlist__add_bpf_sb_event(rec->sb_evlist, &rec->session->header.env)) { + if (evlist__add_bpf_sb_event(rec->sb_evlist, perf_session__env(rec->session))) { pr_err("Couldn't ask for PERF_RECORD_BPF_EVENT side band events.\n."); return -1; } @@ -2197,15 +2262,16 @@ static int record__init_clock(struct record *rec) struct perf_session *session = rec->session; struct timespec ref_clockid; struct timeval ref_tod; + struct perf_env *env = perf_session__env(session); u64 ref; if (!rec->opts.use_clockid) return 0; if (rec->opts.use_clockid && rec->opts.clockid_res_ns) - session->header.env.clock.clockid_res_ns = rec->opts.clockid_res_ns; + env->clock.clockid_res_ns = rec->opts.clockid_res_ns; - session->header.env.clock.clockid = rec->opts.clockid; + env->clock.clockid = rec->opts.clockid; if (gettimeofday(&ref_tod, NULL) != 0) { pr_err("gettimeofday failed, cannot set reference time.\n"); @@ -2220,12 +2286,12 @@ static int record__init_clock(struct record *rec) ref = (u64) ref_tod.tv_sec * NSEC_PER_SEC + (u64) ref_tod.tv_usec * NSEC_PER_USEC; - session->header.env.clock.tod_ns = ref; + env->clock.tod_ns = ref; ref = (u64) ref_clockid.tv_sec * NSEC_PER_SEC + (u64) ref_clockid.tv_nsec; - session->header.env.clock.clockid_ns = ref; + env->clock.clockid_ns = ref; return 0; } @@ -2371,6 +2437,7 @@ static int __cmd_record(struct record *rec, int argc, const char **argv) int fd; float ratio = 0; enum evlist_ctl_cmd cmd = EVLIST_CTL_CMD_UNSUPPORTED; + struct perf_env *env; atexit(record__sig_exit); signal(SIGCHLD, sig_handler); @@ -2378,13 +2445,8 @@ static int __cmd_record(struct record *rec, int argc, const char **argv) signal(SIGTERM, sig_handler); signal(SIGSEGV, sigsegv_handler); - if (rec->opts.record_namespaces) - tool->namespace_events = true; - if (rec->opts.record_cgroup) { -#ifdef HAVE_FILE_HANDLE - tool->cgroup_events = true; -#else +#ifndef HAVE_FILE_HANDLE pr_err("cgroup tracking is not supported\n"); return -1; #endif @@ -2400,12 +2462,24 @@ static int __cmd_record(struct record *rec, int argc, const char **argv) signal(SIGUSR2, SIG_IGN); } + perf_tool__init(tool, /*ordered_events=*/true); + tool->sample = process_sample_event; + tool->fork = perf_event__process_fork; + tool->exit = perf_event__process_exit; + tool->comm = perf_event__process_comm; + tool->namespaces = perf_event__process_namespaces; + tool->mmap = build_id__process_mmap; + tool->mmap2 = build_id__process_mmap2; + tool->itrace_start = process_timestamp_boundary; + tool->aux = process_timestamp_boundary; + tool->namespace_events = rec->opts.record_namespaces; + tool->cgroup_events = rec->opts.record_cgroup; session = perf_session__new(data, tool); if (IS_ERR(session)) { pr_err("Perf session creation failed.\n"); return PTR_ERR(session); } - + env = perf_session__env(session); if (record__threads_enabled(rec)) { if (perf_data__is_pipe(&rec->data)) { pr_err("Parallel trace streaming is not available in pipe mode.\n"); @@ -2439,8 +2513,8 @@ static int __cmd_record(struct record *rec, int argc, const char **argv) } #endif // HAVE_EVENTFD_SUPPORT - session->header.env.comp_type = PERF_COMP_ZSTD; - session->header.env.comp_level = rec->opts.comp_level; + env->comp_type = PERF_COMP_ZSTD; + env->comp_level = rec->opts.comp_level; if (rec->opts.kcore && !record__kcore_readable(&session->machines.host)) { @@ -2477,7 +2551,11 @@ static int __cmd_record(struct record *rec, int argc, const char **argv) pr_warning("WARNING: --timestamp-filename option is not available in pipe mode.\n"); } - evlist__uniquify_name(rec->evlist); + /* + * Use global stat_config that is zero meaning aggr_mode is AGGR_NONE + * and hybrid_merge is false. + */ + evlist__uniquify_evsel_names(rec->evlist, &stat_config); evlist__config(rec->evlist, opts, &callchain_param); @@ -2489,7 +2567,7 @@ static int __cmd_record(struct record *rec, int argc, const char **argv) } /* Debug message used by test scripts */ pr_debug3("perf record done opening and mmapping events\n"); - session->header.env.comp_mmap_len = session->evlist->core.mmap_len; + env->comp_mmap_len = session->evlist->core.mmap_len; if (rec->opts.kcore) { err = record__kcore_copy(&session->machines.host, data); @@ -2529,6 +2607,9 @@ static int __cmd_record(struct record *rec, int argc, const char **argv) goto out_free_threads; } + if (!evlist__needs_bpf_sb_event(rec->evlist)) + opts->no_bpf_event = true; + err = record__setup_sb_evlist(rec); if (err) goto out_free_threads; @@ -2560,6 +2641,13 @@ static int __cmd_record(struct record *rec, int argc, const char **argv) evlist__enable(rec->evlist); /* + * offcpu-time does not call execve, so enable_on_exe wouldn't work + * when recording a workload, do it manually + */ + if (rec->off_cpu) + evlist__enable_evsel(rec->evlist, (char *)OFFCPU_EVENT); + + /* * Let the child rip */ if (forks) { @@ -2771,17 +2859,21 @@ static int __cmd_record(struct record *rec, int argc, const char **argv) trigger_off(&auxtrace_snapshot_trigger); trigger_off(&switch_output_trigger); + record__synthesize_final_bpf_metadata(rec); + if (opts->auxtrace_snapshot_on_exit) record__auxtrace_snapshot_exit(rec); if (forks && workload_exec_errno) { - char msg[STRERR_BUFSIZE], strevsels[2048]; + char msg[STRERR_BUFSIZE]; const char *emsg = str_error_r(workload_exec_errno, msg, sizeof(msg)); + struct strbuf sb = STRBUF_INIT; - evlist__scnprintf_evsels(rec->evlist, sizeof(strevsels), strevsels); + evlist__format_evsels(rec->evlist, &sb, 2048); pr_err("Failed to collect '%s' for the '%s' workload: %s\n", - strevsels, argv[0], emsg); + sb.buf, argv[0], emsg); + strbuf_release(&sb); err = -1; goto out_child; } @@ -2805,7 +2897,7 @@ out_free_threads: if (rec->session->bytes_transferred && rec->session->bytes_compressed) { ratio = (float)rec->session->bytes_transferred/(float)rec->session->bytes_compressed; - session->header.env.comp_ratio = ratio + 0.5; + env->comp_ratio = ratio + 0.5; } if (forks) { @@ -2829,11 +2921,11 @@ out_free_threads: rec->bytes_written += off_cpu_write(rec->session); record__read_lost_samples(rec); - record__synthesize(rec, true); /* this will be recalculated during process_buildids() */ rec->samples = 0; if (!err) { + record__synthesize(rec, true); if (!rec->timestamp_filename) { record__finish_output(rec); } else { @@ -2954,9 +3046,11 @@ static int perf_record_config(const char *var, const char *value, void *cb) else if (!strcmp(value, "no-cache")) rec->no_buildid_cache = true; else if (!strcmp(value, "skip")) - rec->no_buildid = true; + rec->no_buildid = rec->no_buildid_cache = true; else if (!strcmp(value, "mmap")) rec->buildid_mmap = true; + else if (!strcmp(value, "no-mmap")) + rec->buildid_mmap = false; else return -1; return 0; @@ -3146,6 +3240,28 @@ out_free: return ret; } +static int record__parse_off_cpu_thresh(const struct option *opt, + const char *str, + int unset __maybe_unused) +{ + struct record_opts *opts = opt->value; + char *endptr; + u64 off_cpu_thresh_ms; + + if (!str) + return -EINVAL; + + off_cpu_thresh_ms = strtoull(str, &endptr, 10); + + /* the threshold isn't string "0", yet strtoull() returns 0, parsing failed */ + if (*endptr || (off_cpu_thresh_ms == 0 && strcmp(str, "0"))) + return -EINVAL; + else + opts->off_cpu_thresh_ns = off_cpu_thresh_ms * NSEC_PER_MSEC; + + return 0; +} + void __weak arch__add_leaf_frame_record_opts(struct record_opts *opts __maybe_unused) { } @@ -3196,7 +3312,7 @@ static int switch_output_setup(struct record *rec) unsigned long val; /* - * If we're using --switch-output-events, then we imply its + * If we're using --switch-output-events, then we imply its * --switch-output=signal, as we'll send a SIGUSR2 from the side band * thread to its parent. */ @@ -3257,7 +3373,7 @@ static const char * const __record_usage[] = { }; const char * const *record_usage = __record_usage; -static int build_id__process_mmap(struct perf_tool *tool, union perf_event *event, +static int build_id__process_mmap(const struct perf_tool *tool, union perf_event *event, struct perf_sample *sample, struct machine *machine) { /* @@ -3269,7 +3385,7 @@ static int build_id__process_mmap(struct perf_tool *tool, union perf_event *even return perf_event__process_mmap(tool, event, sample, machine); } -static int build_id__process_mmap2(struct perf_tool *tool, union perf_event *event, +static int build_id__process_mmap2(const struct perf_tool *tool, union perf_event *event, struct perf_sample *sample, struct machine *machine) { /* @@ -3282,7 +3398,7 @@ static int build_id__process_mmap2(struct perf_tool *tool, union perf_event *eve return perf_event__process_mmap2(tool, event, sample, machine); } -static int process_timestamp_boundary(struct perf_tool *tool, +static int process_timestamp_boundary(const struct perf_tool *tool, union perf_event *event __maybe_unused, struct perf_sample *sample, struct machine *machine __maybe_unused) @@ -3339,19 +3455,9 @@ static struct record record = { .ctl_fd = -1, .ctl_fd_ack = -1, .synth = PERF_SYNTH_ALL, + .off_cpu_thresh_ns = OFFCPU_THRESH, }, - .tool = { - .sample = process_sample_event, - .fork = perf_event__process_fork, - .exit = perf_event__process_exit, - .comm = perf_event__process_comm, - .namespaces = perf_event__process_namespaces, - .mmap = build_id__process_mmap, - .mmap2 = build_id__process_mmap2, - .itrace_start = process_timestamp_boundary, - .aux = process_timestamp_boundary, - .ordered_events = true, - }, + .buildid_mmap = true, }; const char record_callchain_help[] = CALLCHAIN_RECORD_HELP @@ -3380,6 +3486,9 @@ static struct option __record_options[] = { parse_events_option), OPT_CALLBACK(0, "filter", &record.evlist, "filter", "event filter", parse_filter), + OPT_BOOLEAN(0, "latency", &record.latency, + "Enable data collection for latency profiling.\n" + "\t\t\t Use perf report --latency for latency-centric profile."), OPT_CALLBACK_NOOPT(0, "exclude-perf", &record.evlist, NULL, "don't record events from perf itself", exclude_perf), @@ -3436,6 +3545,8 @@ static struct option __record_options[] = { "Record the sampled data address data page size"), OPT_BOOLEAN(0, "code-page-size", &record.opts.sample_code_page_size, "Record the sampled code address (ip) page size"), + OPT_BOOLEAN(0, "sample-mem-info", &record.opts.sample_data_src, + "Record the data source for memory operations"), OPT_BOOLEAN(0, "sample-cpu", &record.opts.sample_cpu, "Record the sample cpu"), OPT_BOOLEAN(0, "sample-identifier", &record.opts.sample_identifier, "Record the sample identifier"), @@ -3460,8 +3571,7 @@ static struct option __record_options[] = { "or ranges of time to enable events e.g. '-D 10-20,30-40'", record__parse_event_enable_time), OPT_BOOLEAN(0, "kcore", &record.opts.kcore, "copy /proc/kcore"), - OPT_STRING('u', "uid", &record.opts.target.uid_str, "user", - "user to profile"), + OPT_STRING('u', "uid", &record.uid_str, "user", "user to profile"), OPT_CALLBACK_NOOPT('b', "branch-any", &record.opts.branch_stack, "branch any", "sample any taken branches", @@ -3480,7 +3590,7 @@ static struct option __record_options[] = { "sample selected machine registers on interrupt," " use '-I?' to list register names", parse_intr_regs), OPT_CALLBACK_OPTARG(0, "user-regs", &record.opts.sample_user_regs, NULL, "any register", - "sample selected machine registers on interrupt," + "sample selected machine registers in user space," " use '--user-regs=?' to list register names", parse_user_regs), OPT_BOOLEAN(0, "running-time", &record.opts.running_time, "Record running/enabled time of read (:S) events"), @@ -3514,8 +3624,8 @@ static struct option __record_options[] = { "file", "vmlinux pathname"), OPT_BOOLEAN(0, "buildid-all", &record.buildid_all, "Record build-id of all DSOs regardless of hits"), - OPT_BOOLEAN(0, "buildid-mmap", &record.buildid_mmap, - "Record build-id in map events"), + OPT_BOOLEAN_SET(0, "buildid-mmap", &record.buildid_mmap, &record.buildid_mmap_set, + "Record build-id in mmap events and skip build-id processing."), OPT_BOOLEAN(0, "timestamp-filename", &record.timestamp_filename, "append timestamp to output filename"), OPT_BOOLEAN(0, "timestamp-boundary", &record.timestamp_boundary, @@ -3571,6 +3681,11 @@ static struct option __record_options[] = { "write collected trace data into several data files using parallel threads", record__parse_threads), OPT_BOOLEAN(0, "off-cpu", &record.off_cpu, "Enable off-cpu analysis"), + OPT_STRING(0, "setup-filter", &record.filter_action, "pin|unpin", + "BPF filter action"), + OPT_CALLBACK(0, "off-cpu-thresh", &record.opts, "ms", + "Dump off-cpu samples if off-cpu time exceeds this threshold (in milliseconds). (Default: 500ms)", + record__parse_off_cpu_thresh), OPT_END() }; @@ -4024,19 +4139,41 @@ int cmd_record(int argc, const char **argv) } - if (rec->buildid_mmap) { - if (!perf_can_record_build_id()) { - pr_err("Failed: no support to record build id in mmap events, update your kernel.\n"); + if (record.latency) { + /* + * There is no fundamental reason why latency profiling + * can't work for system-wide mode, but exact semantics + * and details are to be defined. + * See the following thread for details: + * https://lore.kernel.org/all/Z4XDJyvjiie3howF@google.com/ + */ + if (record.opts.target.system_wide) { + pr_err("Failed: latency profiling is not supported with system-wide collection.\n"); err = -EINVAL; goto out_opts; } - pr_debug("Enabling build id in mmap2 events.\n"); - /* Enable mmap build id synthesizing. */ - symbol_conf.buildid_mmap2 = true; + record.opts.record_switch_events = true; + } + + if (rec->buildid_mmap && !perf_can_record_build_id()) { + pr_warning("Missing support for build id in kernel mmap events.\n" + "Disable this warning with --no-buildid-mmap\n"); + rec->buildid_mmap = false; + } + + if (rec->buildid_mmap) { /* Enable perf_event_attr::build_id bit. */ rec->opts.build_id = true; - /* Disable build id cache. */ + /* Disable build-ID table in the header. */ rec->no_buildid = true; + } else { + pr_debug("Disabling build id in synthesized mmap2 events.\n"); + symbol_conf.no_buildid_mmap2 = true; + } + + if (rec->no_buildid_set && rec->no_buildid) { + /* -B implies -N for historic reasons. */ + rec->no_buildid_cache = true; } if (rec->opts.record_cgroup && !perf_can_record_cgroup()) { @@ -4100,6 +4237,22 @@ int cmd_record(int argc, const char **argv) pr_warning("WARNING: --timestamp-filename option is not available in parallel streaming mode.\n"); } + if (rec->filter_action) { + if (!strcmp(rec->filter_action, "pin")) + err = perf_bpf_filter__pin(); + else if (!strcmp(rec->filter_action, "unpin")) + err = perf_bpf_filter__unpin(); + else { + pr_warning("Unknown BPF filter action: %s\n", rec->filter_action); + err = -EINVAL; + } + goto out_opts; + } + + /* For backward compatibility, -d implies --mem-info */ + if (rec->opts.sample_address) + rec->opts.sample_data_src = true; + /* * Allow aliases to facilitate the lookup of symbols for address * filters. Refer to auxtrace_parse_filters(). @@ -4117,7 +4270,7 @@ int cmd_record(int argc, const char **argv) err = -ENOMEM; - if (rec->no_buildid_cache || rec->no_buildid) { + if (rec->no_buildid_cache) { disable_buildid_cache(); } else if (rec->switch_output.enabled) { /* @@ -4152,11 +4305,13 @@ int cmd_record(int argc, const char **argv) record.opts.tail_synthesize = true; if (rec->evlist->core.nr_entries == 0) { - bool can_profile_kernel = perf_event_paranoid_check(1); + struct evlist *def_evlist = evlist__new_default(); - err = parse_event(rec->evlist, can_profile_kernel ? "cycles:P" : "cycles:Pu"); - if (err) + if (!def_evlist) goto out; + + evlist__splice_list_tail(rec->evlist, &def_evlist->core.entries); + evlist__delete(def_evlist); } if (rec->opts.target.tid && !rec->opts.no_inherit_set) @@ -4168,19 +4323,24 @@ int cmd_record(int argc, const char **argv) ui__warning("%s\n", errbuf); } - err = target__parse_uid(&rec->opts.target); - if (err) { - int saved_errno = errno; + if (rec->uid_str) { + uid_t uid = parse_uid(rec->uid_str); - target__strerror(&rec->opts.target, err, errbuf, BUFSIZ); - ui__error("%s", errbuf); + if (uid == UINT_MAX) { + ui__error("Invalid User: %s", rec->uid_str); + err = -EINVAL; + goto out; + } + err = parse_uid_filter(rec->evlist, uid); + if (err) + goto out; - err = -saved_errno; - goto out; + /* User ID filtering implies system wide. */ + rec->opts.target.system_wide = true; } - /* Enable ignoring missing threads when -u/-p option is defined. */ - rec->opts.ignore_missing_thread = rec->opts.target.uid != UINT_MAX || rec->opts.target.pid; + /* Enable ignoring missing threads when -p option is defined. */ + rec->opts.ignore_missing_thread = rec->opts.target.pid; evlist__warn_user_requested_cpus(rec->evlist, rec->opts.target.cpu_list); @@ -4256,13 +4416,13 @@ int cmd_record(int argc, const char **argv) err = __cmd_record(&record, argc, argv); out: - evlist__delete(rec->evlist); + record__free_thread_masks(rec, rec->nr_threads); + rec->nr_threads = 0; symbol__exit(); auxtrace_record__free(rec->itr); out_opts: - record__free_thread_masks(rec, rec->nr_threads); - rec->nr_threads = 0; evlist__close_control(rec->opts.ctl_fd, rec->opts.ctl_fd_ack, &rec->opts.ctl_fd_close); + evlist__delete(rec->evlist); return err; } |
