diff options
Diffstat (limited to 'tools/perf/builtin-record.c')
| -rw-r--r-- | tools/perf/builtin-record.c | 482 |
1 files changed, 307 insertions, 175 deletions
diff --git a/tools/perf/builtin-record.c b/tools/perf/builtin-record.c index 5db1aedf48df..4a5eba498c02 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" @@ -39,7 +40,6 @@ #include "util/perf_api_probe.h" #include "util/trigger.h" #include "util/perf-hooks.h" -#include "util/cpu-set-sched.h" #include "util/synthetic-events.h" #include "util/time-utils.h" #include "util/units.h" @@ -51,9 +51,11 @@ #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" +#include "dwarf-regs.h" #include <errno.h> #include <inttypes.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,10 +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 */ @@ -448,7 +453,7 @@ static int record__aio_pushfn(struct mmap *map, void *to, void *buf, size_t size static int record__aio_push(struct record *rec, struct mmap *map, off_t *off) { int ret, idx; - int trace_fd = rec->session->data->file.fd; + int trace_fd = perf_data__fd(rec->session->data); struct record_aio aio = { .rec = rec, .size = 0 }; /* @@ -647,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++; @@ -712,8 +730,6 @@ static void record__sig_exit(void) raise(signr); } -#ifdef HAVE_AUXTRACE_SUPPORT - static int record__process_auxtrace(const struct perf_tool *tool, struct mmap *map, union perf_event *event, void *data1, @@ -757,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; @@ -773,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) @@ -867,40 +887,6 @@ static int record__auxtrace_init(struct record *rec) 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; @@ -961,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. @@ -1085,12 +1070,12 @@ static int record__thread_data_init_maps(struct record_thread *thread_data, stru thread_data->nr_mmaps = bitmap_weight(thread_data->mask->maps.bits, thread_data->mask->maps.nbits); if (mmap) { - thread_data->maps = zalloc(thread_data->nr_mmaps * sizeof(struct mmap *)); + thread_data->maps = calloc(thread_data->nr_mmaps, sizeof(struct mmap *)); if (!thread_data->maps) return -ENOMEM; } if (overwrite_mmap) { - thread_data->overwrite_maps = zalloc(thread_data->nr_mmaps * sizeof(struct mmap *)); + thread_data->overwrite_maps = calloc(thread_data->nr_mmaps, sizeof(struct mmap *)); if (!thread_data->overwrite_maps) { zfree(&thread_data->maps); return -ENOMEM; @@ -1235,7 +1220,7 @@ static int record__alloc_thread_data(struct record *rec, struct evlist *evlist) int t, ret; struct record_thread *thread_data; - rec->thread_data = zalloc(rec->nr_threads * sizeof(*(rec->thread_data))); + rec->thread_data = calloc(rec->nr_threads, sizeof(*(rec->thread_data))); if (!rec->thread_data) { pr_err("Failed to allocate thread data\n"); return -ENOMEM; @@ -1301,7 +1286,6 @@ static int record__mmap_evlist(struct record *rec, struct record_opts *opts = &rec->opts; bool auxtrace_overwrite = opts->auxtrace_snapshot_mode || opts->auxtrace_sample_mode; - char msg[512]; if (opts->affinity != PERF_AFFINITY_SYS) cpu__setup_cpunode_map(); @@ -1320,8 +1304,7 @@ static int record__mmap_evlist(struct record *rec, opts->mmap_pages, opts->auxtrace_mmap_pages); return -errno; } else { - pr_err("failed to mmap with %d (%s)\n", errno, - str_error_r(errno, msg, sizeof(msg))); + pr_err("failed to mmap: %m\n"); if (errno) return -errno; else @@ -1339,7 +1322,8 @@ static int record__mmap_evlist(struct record *rec, if (record__threads_enabled(rec)) { ret = perf_data__create_dir(&rec->data, evlist->core.nr_mmaps); if (ret) { - pr_err("Failed to create data directory: %s\n", strerror(-ret)); + errno = -ret; + pr_err("Failed to create data directory: %m\n"); return ret; } for (i = 0; i < evlist->core.nr_mmaps; i++) { @@ -1366,10 +1350,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); @@ -1381,15 +1382,73 @@ 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) { + evsel__open_strerror(pos, &opts->target, errno, msg, sizeof(msg)); + 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" @@ -1402,9 +1461,8 @@ try_again: } 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))); + pr_err("failed to set filter \"%s\" on event %s: %m\n", + pos->filter ?: "BPF", evsel__name(pos)); rc = -1; goto out; } @@ -1452,6 +1510,8 @@ static int process_buildids(struct record *rec) if (perf_data__size(&rec->data) == 0) return 0; + /* A single DSO is needed and not all inline frames. */ + symbol_conf.inline_name = false; /* * During this process, it'll load kernel map and replace the * dso->long_name to a real pathname it found. In this case @@ -1462,7 +1522,6 @@ static int process_buildids(struct record *rec) * $HOME/.debug/.build-id/f0/6e17aa50adf4d00b88925e03775de107611551 */ symbol_conf.ignore_vmlinux_buildid = true; - /* * If --buildid-all is given, it marks all DSO regardless of hits, * so no need to process samples. But if timestamp_boundary is enabled, @@ -1533,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) { @@ -1541,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; @@ -1551,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) @@ -1581,7 +1640,7 @@ static int record__mmap_read_evlist(struct record *rec, struct evlist *evlist, int rc = 0; int nr_mmaps; struct mmap **maps; - int trace_fd = rec->data.file.fd; + int trace_fd = perf_data__fd(&rec->data); off_t off = 0; if (!evlist) @@ -1689,8 +1748,7 @@ static void *record__thread(void *arg) err = write(thread->pipes.ack[1], &msg, sizeof(msg)); if (err == -1) - pr_warning("threads[%d]: failed to notify on start: %s\n", - thread->tid, strerror(errno)); + pr_warning("threads[%d]: failed to notify on start: %m\n", thread->tid); pr_debug("threads[%d]: started on cpu%d\n", thread->tid, sched_getcpu()); @@ -1733,8 +1791,7 @@ static void *record__thread(void *arg) err = write(thread->pipes.ack[1], &msg, sizeof(msg)); if (err == -1) - pr_warning("threads[%d]: failed to notify on termination: %s\n", - thread->tid, strerror(errno)); + pr_warning("threads[%d]: failed to notify on termination: %m\n", thread->tid); return NULL; } @@ -1788,21 +1845,23 @@ record__finish_output(struct record *rec) } rec->session->header.data_size += rec->bytes_written; - data->file.size = lseek(perf_data__fd(data), 0, SEEK_CUR); + data->file.size = perf_data__seek(data, 0, SEEK_CUR); if (record__threads_enabled(rec)) { - for (i = 0; i < data->dir.nr; i++) - data->dir.files[i].size = lseek(data->dir.files[i].fd, 0, SEEK_CUR); + for (i = 0; i < data->dir.nr; i++) { + data->dir.files[i].size = + perf_data_file__seek(&data->dir.files[i], 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) @@ -1822,7 +1881,7 @@ static int record__synthesize_workload(struct record *rec, bool tail) process_synthesized_event, &rec->session->machines.host, needs_mmap, - rec->opts.sample_address); + rec->opts.record_data_mmap); perf_thread_map__put(thread_map); return err; } @@ -1917,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); @@ -1931,6 +1991,7 @@ 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) @@ -2130,7 +2191,7 @@ static int record__synthesize(struct record *rec, bool tail) err = __machine__synthesize_threads(machine, tool, &opts->target, rec->evlist->core.threads, - f, needs_mmap, opts->sample_address, + f, needs_mmap, opts->record_data_mmap, rec->opts.nr_threads_synthesize); } @@ -2143,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; @@ -2174,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; } @@ -2193,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"); @@ -2216,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; } @@ -2268,7 +2338,7 @@ static int record__start_threads(struct record *rec) sigfillset(&full); if (sigprocmask(SIG_SETMASK, &full, &mask)) { - pr_err("Failed to block signals on threads start: %s\n", strerror(errno)); + pr_err("Failed to block signals on threads start: %m\n"); return -1; } @@ -2286,7 +2356,7 @@ static int record__start_threads(struct record *rec) if (pthread_create(&handle, &attrs, record__thread, &thread_data[t])) { for (tt = 1; tt < t; tt++) record__terminate_thread(&thread_data[t]); - pr_err("Failed to start threads: %s\n", strerror(errno)); + pr_err("Failed to start threads: %m\n"); ret = -1; goto out_err; } @@ -2309,7 +2379,7 @@ out_err: pthread_attr_destroy(&attrs); if (sigprocmask(SIG_SETMASK, &mask, NULL)) { - pr_err("Failed to unblock signals on threads start: %s\n", strerror(errno)); + pr_err("Failed to unblock signals on threads start: %m\n"); ret = -1; } @@ -2367,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); @@ -2408,7 +2479,7 @@ static int __cmd_record(struct record *rec, int argc, const char **argv) 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"); @@ -2442,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)) { @@ -2480,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); @@ -2492,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); @@ -2532,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; @@ -2563,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) { @@ -2774,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; } @@ -2808,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) { @@ -2832,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 { @@ -2889,64 +2978,32 @@ out_delete_session: return status; } -static void callchain_debug(struct callchain_param *callchain) -{ - static const char *str[CALLCHAIN_MAX] = { "NONE", "FP", "DWARF", "LBR" }; - - pr_debug("callchain: type %s\n", str[callchain->record_mode]); - - if (callchain->record_mode == CALLCHAIN_DWARF) - pr_debug("callchain: stack dump size %d\n", - callchain->dump_size); -} - -int record_opts__parse_callchain(struct record_opts *record, - struct callchain_param *callchain, - const char *arg, bool unset) -{ - int ret; - callchain->enabled = !unset; - - /* --no-call-graph */ - if (unset) { - callchain->record_mode = CALLCHAIN_NONE; - pr_debug("callchain: disabled\n"); - return 0; - } - - ret = parse_callchain_record_opt(arg, callchain); - if (!ret) { - /* Enable data address sampling for DWARF unwind. */ - if (callchain->record_mode == CALLCHAIN_DWARF) - record->sample_address = true; - callchain_debug(callchain); - } - - return ret; -} - -int record_parse_callchain_opt(const struct option *opt, +static int record_parse_callchain_opt(const struct option *opt, const char *arg, int unset) { return record_opts__parse_callchain(opt->value, &callchain_param, arg, unset); } -int record_callchain_opt(const struct option *opt, - const char *arg __maybe_unused, - int unset __maybe_unused) +static int record_callchain_opt(const struct option *opt, + const char *arg __maybe_unused, + int unset) { - struct callchain_param *callchain = opt->value; - - callchain->enabled = true; - - if (callchain->record_mode == CALLCHAIN_NONE) - callchain->record_mode = CALLCHAIN_FP; + /* + * The -g option only sets the callchain if not already configured by + * .perfconfig. It does, however, enable it. + */ + if (callchain_param.record_mode != CALLCHAIN_NONE) { + callchain_param.enabled = true; + return 0; + } - callchain_debug(callchain); - return 0; + return record_opts__parse_callchain(opt->value, &callchain_param, + EM_HOST != EM_S390 ? "fp" : "dwarf", + unset); } + static int perf_record_config(const char *var, const char *value, void *cb) { struct record *rec = cb; @@ -2957,9 +3014,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; @@ -3149,6 +3208,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) { } @@ -3342,7 +3423,9 @@ static struct record record = { .ctl_fd = -1, .ctl_fd_ack = -1, .synth = PERF_SYNTH_ALL, + .off_cpu_thresh_ns = OFFCPU_THRESH, }, + .buildid_mmap = true, }; const char record_callchain_help[] = CALLCHAIN_RECORD_HELP @@ -3371,6 +3454,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), @@ -3409,7 +3495,7 @@ static struct option __record_options[] = { OPT_CALLBACK(0, "mmap-flush", &record.opts, "number", "Minimal number of bytes that is extracted from mmap data pages (default: 1)", record__mmap_flush_parse), - OPT_CALLBACK_NOOPT('g', NULL, &callchain_param, + OPT_CALLBACK_NOOPT('g', NULL, &record.opts, NULL, "enables call-graph recording" , &record_callchain_opt), OPT_CALLBACK(0, "call-graph", &record.opts, @@ -3427,6 +3513,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"), @@ -3451,8 +3539,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", @@ -3471,7 +3558,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"), @@ -3505,8 +3592,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, @@ -3564,6 +3651,12 @@ static struct option __record_options[] = { 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_BOOLEAN_SET(0, "data-mmap", &record.opts.record_data_mmap, + &record.opts.record_data_mmap_set, + "Record mmap events for non-executable mappings"), OPT_END() }; @@ -3572,7 +3665,7 @@ struct option *record_options = __record_options; static int record__mmap_cpu_mask_init(struct mmap_cpu_mask *mask, struct perf_cpu_map *cpus) { struct perf_cpu cpu; - int idx; + unsigned int idx; if (cpu_map__is_dummy(cpus)) return 0; @@ -3619,7 +3712,7 @@ static int record__alloc_thread_masks(struct record *rec, int nr_threads, int nr { int t, ret; - rec->thread_masks = zalloc(nr_threads * sizeof(*(rec->thread_masks))); + rec->thread_masks = calloc(nr_threads, sizeof(*(rec->thread_masks))); if (!rec->thread_masks) { pr_err("Failed to allocate thread masks\n"); return -ENOMEM; @@ -3829,7 +3922,7 @@ static int record__init_thread_numa_masks(struct record *rec, struct perf_cpu_ma return -ENOMEM; } - spec = zalloc(topo->nr * sizeof(char *)); + spec = calloc(topo->nr, sizeof(char *)); if (!spec) { pr_err("Failed to allocate NUMA spec\n"); ret = -ENOMEM; @@ -4007,8 +4100,11 @@ int cmd_record(int argc, const char **argv) perf_debuginfod_setup(&record.debuginfod); - /* Make system wide (-a) the default target. */ - if (!argc && target__none(&rec->opts.target)) + /* + * Use system wide (-a) for the default target (ie. when no + * workload). User ID filtering also implies system-wide. + */ + if ((!argc && target__none(&rec->opts.target)) || rec->uid_str) rec->opts.target.system_wide = true; if (nr_cgroups && !rec->opts.target.system_wide) { @@ -4017,19 +4113,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()) { @@ -4105,6 +4223,13 @@ int cmd_record(int argc, const char **argv) goto out_opts; } + /* For backward compatibility, -d implies --mem-info and --data-mmap */ + if (rec->opts.sample_address) { + rec->opts.sample_data_src = true; + if (!rec->opts.record_data_mmap_set) + rec->opts.record_data_mmap = true; + } + /* * Allow aliases to facilitate the lookup of symbols for address * filters. Refer to auxtrace_parse_filters(). @@ -4122,7 +4247,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) { /* @@ -4157,9 +4282,14 @@ int cmd_record(int argc, const char **argv) record.opts.tail_synthesize = true; if (rec->evlist->core.nr_entries == 0) { - err = parse_event(rec->evlist, "cycles:P"); - if (err) + struct evlist *def_evlist = evlist__new_default(&rec->opts.target, + callchain_param.enabled); + + 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) @@ -4171,19 +4301,21 @@ 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); - - err = -saved_errno; - goto out; + 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; } - /* 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); |
