diff options
Diffstat (limited to 'tools/perf/builtin-stat.c')
| -rw-r--r-- | tools/perf/builtin-stat.c | 297 |
1 files changed, 191 insertions, 106 deletions
diff --git a/tools/perf/builtin-stat.c b/tools/perf/builtin-stat.c index 7006f848f87a..ab40d85fb125 100644 --- a/tools/perf/builtin-stat.c +++ b/tools/perf/builtin-stat.c @@ -74,6 +74,7 @@ #include "util/intel-tpebs.h" #include "asm/bug.h" +#include <linux/list_sort.h> #include <linux/time64.h> #include <linux/zalloc.h> #include <api/fs/fs.h> @@ -96,9 +97,18 @@ #include <perf/evlist.h> #include <internal/threadmap.h> +#ifdef HAVE_BPF_SKEL +#include "util/bpf_skel/bperf_cgroup.h" +#endif + #define DEFAULT_SEPARATOR " " #define FREEZE_ON_SMI_PATH "bus/event_source/devices/cpu/freeze_on_smi" +struct rusage_stats { + struct stats ru_utime_usec_stat; + struct stats ru_stime_usec_stat; +}; + static void print_counters(struct timespec *ts, int argc, const char **argv); static struct evlist *evsel_list; @@ -128,6 +138,7 @@ static bool interval_count; static const char *output_name; static int output_fd; static char *metrics; +static struct rusage_stats ru_stats; struct perf_stat { bool record; @@ -228,7 +239,7 @@ static inline void diff_timespec(struct timespec *r, struct timespec *a, static void perf_stat__reset_stats(void) { evlist__reset_stats(evsel_list); - perf_stat__reset_shadow_stats(); + memset(stat_config.walltime_nsecs_stats, 0, sizeof(*stat_config.walltime_nsecs_stats)); } static int process_synthesized_event(const struct perf_tool *tool __maybe_unused, @@ -278,17 +289,27 @@ static int read_single_counter(struct evsel *counter, int cpu_map_idx, int threa if (err && cpu_map_idx == 0 && (evsel__tool_event(counter) == TOOL_PMU__EVENT_USER_TIME || evsel__tool_event(counter) == TOOL_PMU__EVENT_SYSTEM_TIME)) { - u64 val, *start_time; struct perf_counts_values *count = perf_counts(counter->counts, cpu_map_idx, thread); + struct perf_counts_values *old_count = NULL; + u64 val; + + if (counter->prev_raw_counts) + old_count = perf_counts(counter->prev_raw_counts, cpu_map_idx, thread); - start_time = xyarray__entry(counter->start_times, cpu_map_idx, thread); if (evsel__tool_event(counter) == TOOL_PMU__EVENT_USER_TIME) val = ru_stats.ru_utime_usec_stat.mean; else val = ru_stats.ru_stime_usec_stat.mean; - count->ena = count->run = *start_time + val; + count->val = val; + if (old_count) { + count->run = old_count->run + 1; + count->ena = old_count->ena + 1; + } else { + count->run++; + count->ena++; + } return 0; } return err; @@ -345,7 +366,7 @@ static int read_counter_cpu(struct evsel *counter, int cpu_map_idx) return 0; } -static int read_affinity_counters(void) +static int read_counters_with_affinity(void) { struct evlist_cpu_iterator evlist_cpu_itr; struct affinity saved_affinity, *affinity; @@ -366,6 +387,9 @@ static int read_affinity_counters(void) if (evsel__is_bpf(counter)) continue; + if (evsel__is_tool(counter)) + continue; + if (!counter->err) counter->err = read_counter_cpu(counter, evlist_cpu_itr.cpu_map_idx); } @@ -391,16 +415,46 @@ static int read_bpf_map_counters(void) return 0; } -static int read_counters(void) +static int read_tool_counters(void) { - if (!stat_config.stop_read_counter) { - if (read_bpf_map_counters() || - read_affinity_counters()) - return -1; + struct evsel *counter; + + evlist__for_each_entry(evsel_list, counter) { + int idx; + + if (!evsel__is_tool(counter)) + continue; + + perf_cpu_map__for_each_idx(idx, counter->core.cpus) { + if (!counter->err) + counter->err = read_counter_cpu(counter, idx); + } } return 0; } +static int read_counters(void) +{ + int ret; + + if (stat_config.stop_read_counter) + return 0; + + // Read all BPF counters first. + ret = read_bpf_map_counters(); + if (ret) + return ret; + + // Read non-BPF and non-tool counters next. + ret = read_counters_with_affinity(); + if (ret) + return ret; + + // Read the tool counters last. This way the duration_time counter + // should always be greater than any other counter's enabled time. + return read_tool_counters(); +} + static void process_counters(void) { struct evsel *counter; @@ -434,8 +488,8 @@ static void process_interval(void) pr_err("failed to write stat round event\n"); } - init_stats(&walltime_nsecs_stats); - update_stats(&walltime_nsecs_stats, stat_config.interval * 1000000ULL); + init_stats(stat_config.walltime_nsecs_stats); + update_stats(stat_config.walltime_nsecs_stats, stat_config.interval * 1000000ULL); print_counters(&rs, 0, NULL); } @@ -624,8 +678,9 @@ static enum counter_recovery stat_handle_error(struct evsel *counter, int err) */ if (err == EINVAL || err == ENOSYS || err == ENOENT || err == ENXIO) { if (verbose > 0) { - ui__warning("%s event is not supported by the kernel.\n", - evsel__name(counter)); + evsel__open_strerror(counter, &target, err, msg, sizeof(msg)); + ui__warning("%s event is not supported by the kernel.\n%s\n", + evsel__name(counter), msg); } return COUNTER_SKIP; } @@ -649,10 +704,11 @@ static enum counter_recovery stat_handle_error(struct evsel *counter, int err) } } if (verbose > 0) { + evsel__open_strerror(counter, &target, err, msg, sizeof(msg)); ui__warning(err == EOPNOTSUPP - ? "%s event is not supported by the kernel.\n" - : "skipping event %s that kernel failed to open.\n", - evsel__name(counter)); + ? "%s event is not supported by the kernel.\n%s\n" + : "skipping event %s that kernel failed to open.\n%s\n", + evsel__name(counter), msg); } return COUNTER_SKIP; } @@ -713,6 +769,17 @@ static int create_perf_stat_counter(struct evsel *evsel, evsel->core.threads); } +static void update_rusage_stats(const struct rusage *rusage) +{ + const u64 us_to_ns = 1000; + const u64 s_to_ns = 1000000000; + + update_stats(&ru_stats.ru_utime_usec_stat, + (rusage->ru_utime.tv_usec * us_to_ns + rusage->ru_utime.tv_sec * s_to_ns)); + update_stats(&ru_stats.ru_stime_usec_stat, + (rusage->ru_stime.tv_usec * us_to_ns + rusage->ru_stime.tv_sec * s_to_ns)); +} + static int __run_perf_stat(int argc, const char **argv, int run_idx) { int interval = stat_config.interval; @@ -856,9 +923,11 @@ static int __run_perf_stat(int argc, const char **argv, int run_idx) goto err_out; } } - if (!has_supported_counters) { - evsel__open_strerror(evlist__first(evsel_list), &target, open_err, - msg, sizeof(msg)); + if (!has_supported_counters && !stat_config.null_run) { + if (open_err) { + evsel__open_strerror(evlist__first(evsel_list), &target, open_err, + msg, sizeof(msg)); + } ui__error("No supported events found.\n%s\n", msg); if (child_pid != -1) @@ -938,10 +1007,20 @@ static int __run_perf_stat(int argc, const char **argv, int run_idx) goto err_out; } - if (WIFSIGNALED(status)) + if (WIFSIGNALED(status)) { + /* + * We want to indicate failure to stop a repeat run, + * hence negative. We want the value to be the exit code + * of perf, which for termination by a signal is 128 + * plus the signal number. + */ + err = 0 - (128 + WTERMSIG(status)); psignal(WTERMSIG(status), argv[0]); + } else { + err = WEXITSTATUS(status); + } } else { - status = dispatch_events(forks, timeout, interval, ×); + err = dispatch_events(forks, timeout, interval, ×); } disable_counters(); @@ -954,15 +1033,15 @@ static int __run_perf_stat(int argc, const char **argv, int run_idx) if (interval && stat_config.summary) { stat_config.interval = 0; stat_config.stop_read_counter = true; - init_stats(&walltime_nsecs_stats); - update_stats(&walltime_nsecs_stats, t1 - t0); + init_stats(stat_config.walltime_nsecs_stats); + update_stats(stat_config.walltime_nsecs_stats, t1 - t0); evlist__copy_prev_raw_counts(evsel_list); evlist__reset_prev_raw_counts(evsel_list); evlist__reset_aggr_stats(evsel_list); } else { - update_stats(&walltime_nsecs_stats, t1 - t0); - update_rusage_stats(&ru_stats, &stat_config.ru_data); + update_stats(stat_config.walltime_nsecs_stats, t1 - t0); + update_rusage_stats(&stat_config.ru_data); } /* @@ -981,7 +1060,7 @@ static int __run_perf_stat(int argc, const char **argv, int run_idx) if (!STAT_RECORD) evlist__close(evsel_list); - return WEXITSTATUS(status); + return err; err_out: if (forks) @@ -1851,6 +1930,35 @@ static int perf_stat_init_aggr_mode_file(struct perf_stat *st) return 0; } +static int default_evlist_evsel_cmp(void *priv __maybe_unused, + const struct list_head *l, + const struct list_head *r) +{ + const struct perf_evsel *lhs_core = container_of(l, struct perf_evsel, node); + const struct evsel *lhs = container_of(lhs_core, struct evsel, core); + const struct perf_evsel *rhs_core = container_of(r, struct perf_evsel, node); + const struct evsel *rhs = container_of(rhs_core, struct evsel, core); + + if (evsel__leader(lhs) == evsel__leader(rhs)) { + /* Within the same group, respect the original order. */ + return lhs_core->idx - rhs_core->idx; + } + + /* Sort default metrics evsels first, and default show events before those. */ + if (lhs->default_metricgroup != rhs->default_metricgroup) + return lhs->default_metricgroup ? -1 : 1; + + if (lhs->default_show_events != rhs->default_show_events) + return lhs->default_show_events ? -1 : 1; + + /* Sort by PMU type (prefers legacy types first). */ + if (lhs->pmu != rhs->pmu) + return lhs->pmu->type - rhs->pmu->type; + + /* Sort by name. */ + return strcmp(evsel__name((struct evsel *)lhs), evsel__name((struct evsel *)rhs)); +} + /* * Add default events, if there were no attributes specified or * if -d/--detailed, -d -d or -d -d -d is used: @@ -1973,48 +2081,39 @@ static int add_default_events(void) stat_config.topdown_level = 1; if (!evlist->core.nr_entries && !evsel_list->core.nr_entries) { - /* No events so add defaults. */ - if (target__has_cpu(&target)) - ret = parse_events(evlist, "cpu-clock", &err); - else - ret = parse_events(evlist, "task-clock", &err); - if (ret) - goto out; - - ret = parse_events(evlist, - "context-switches," - "cpu-migrations," - "page-faults," - "instructions," - "cycles," - "stalled-cycles-frontend," - "stalled-cycles-backend," - "branches," - "branch-misses", - &err); - if (ret) - goto out; - /* - * Add TopdownL1 metrics if they exist. To minimize - * multiplexing, don't request threshold computation. + * Add Default metrics. To minimize multiplexing, don't request + * threshold computation, but it will be computed if the events + * are present. */ - if (metricgroup__has_metric_or_groups(pmu, "Default")) { - struct evlist *metric_evlist = evlist__new(); + const char *default_metricgroup_names[] = { + "Default", "Default2", "Default3", "Default4", + }; + + for (size_t i = 0; i < ARRAY_SIZE(default_metricgroup_names); i++) { + struct evlist *metric_evlist; + if (!metricgroup__has_metric_or_groups(pmu, default_metricgroup_names[i])) + continue; + + if ((int)i > detailed_run) + break; + + metric_evlist = evlist__new(); if (!metric_evlist) { ret = -ENOMEM; - goto out; + break; } - if (metricgroup__parse_groups(metric_evlist, pmu, "Default", + if (metricgroup__parse_groups(metric_evlist, pmu, default_metricgroup_names[i], /*metric_no_group=*/false, /*metric_no_merge=*/false, /*metric_no_threshold=*/true, stat_config.user_requested_cpu_list, stat_config.system_wide, stat_config.hardware_aware_grouping) < 0) { + evlist__delete(metric_evlist); ret = -1; - goto out; + break; } evlist__for_each_entry(metric_evlist, evsel) @@ -2026,44 +2125,8 @@ static int add_default_events(void) &metric_evlist->metric_events); evlist__delete(metric_evlist); } - } - - /* Detailed events get appended to the event list: */ + list_sort(/*priv=*/NULL, &evlist->core.entries, default_evlist_evsel_cmp); - if (!ret && detailed_run >= 1) { - /* - * Detailed stats (-d), covering the L1 and last level data - * caches: - */ - ret = parse_events(evlist, - "L1-dcache-loads," - "L1-dcache-load-misses," - "LLC-loads," - "LLC-load-misses", - &err); - } - if (!ret && detailed_run >= 2) { - /* - * Very detailed stats (-d -d), covering the instruction cache - * and the TLB caches: - */ - ret = parse_events(evlist, - "L1-icache-loads," - "L1-icache-load-misses," - "dTLB-loads," - "dTLB-load-misses," - "iTLB-loads," - "iTLB-load-misses", - &err); - } - if (!ret && detailed_run >= 3) { - /* - * Very, very detailed stats (-d -d -d), adding prefetch events: - */ - ret = parse_events(evlist, - "L1-dcache-prefetches," - "L1-dcache-prefetch-misses", - &err); } out: if (!ret) { @@ -2072,7 +2135,7 @@ out: * Make at least one event non-skippable so fatal errors are visible. * 'cycles' always used to be default and non-skippable, so use that. */ - if (strcmp("cycles", evsel__name(evsel))) + if (!evsel__match(evsel, HARDWARE, HW_CPU_CYCLES)) evsel->skippable = true; } } @@ -2136,7 +2199,8 @@ static int __cmd_record(const struct option stat_options[], struct opt_aggr_mode return argc; } -static int process_stat_round_event(struct perf_session *session, +static int process_stat_round_event(const struct perf_tool *tool __maybe_unused, + struct perf_session *session, union perf_event *event) { struct perf_record_stat_round *stat_round = &event->stat_round; @@ -2148,7 +2212,7 @@ static int process_stat_round_event(struct perf_session *session, process_counters(); if (stat_round->type == PERF_STAT_ROUND_TYPE__FINAL) - update_stats(&walltime_nsecs_stats, stat_round->time); + update_stats(stat_config.walltime_nsecs_stats, stat_round->time); if (stat_config.interval && stat_round->time) { tsh.tv_sec = stat_round->time / NSEC_PER_SEC; @@ -2161,10 +2225,10 @@ static int process_stat_round_event(struct perf_session *session, } static -int process_stat_config_event(struct perf_session *session, +int process_stat_config_event(const struct perf_tool *tool, + struct perf_session *session, union perf_event *event) { - const struct perf_tool *tool = session->tool; struct perf_stat *st = container_of(tool, struct perf_stat, tool); perf_event__read_stat_config(&stat_config, &event->stat_config); @@ -2210,10 +2274,10 @@ static int set_maps(struct perf_stat *st) } static -int process_thread_map_event(struct perf_session *session, +int process_thread_map_event(const struct perf_tool *tool, + struct perf_session *session __maybe_unused, union perf_event *event) { - const struct perf_tool *tool = session->tool; struct perf_stat *st = container_of(tool, struct perf_stat, tool); if (st->threads) { @@ -2229,10 +2293,10 @@ int process_thread_map_event(struct perf_session *session, } static -int process_cpu_map_event(struct perf_session *session, +int process_cpu_map_event(const struct perf_tool *tool, + struct perf_session *session __maybe_unused, union perf_event *event) { - const struct perf_tool *tool = session->tool; struct perf_stat *st = container_of(tool, struct perf_stat, tool); struct perf_cpu_map *cpus; @@ -2540,6 +2604,7 @@ int cmd_stat(int argc, const char **argv) unsigned int interval, timeout; const char * const stat_subcommands[] = { "record", "report" }; char errbuf[BUFSIZ]; + struct evsel *counter; setlocale(LC_ALL, ""); @@ -2794,9 +2859,28 @@ int cmd_stat(int argc, const char **argv) goto out; } } - +#ifdef HAVE_BPF_SKEL + if (target.use_bpf && nr_cgroups && + (evsel_list->core.nr_entries / nr_cgroups) > BPERF_CGROUP__MAX_EVENTS) { + pr_warning("Disabling BPF counters due to more events (%d) than the max (%d)\n", + evsel_list->core.nr_entries / nr_cgroups, BPERF_CGROUP__MAX_EVENTS); + target.use_bpf = false; + } +#endif // HAVE_BPF_SKEL evlist__warn_user_requested_cpus(evsel_list, target.cpu_list); + evlist__for_each_entry(evsel_list, counter) { + /* + * Setup BPF counters to require CPUs as any(-1) isn't + * supported. evlist__create_maps below will propagate this + * information to the evsels. Note, evsel__is_bperf isn't yet + * set up, and this change must happen early, so directly use + * the bpf_counter variable and target information. + */ + if ((counter->bpf_counter || target.use_bpf) && !target__has_cpu(&target)) + counter->core.requires_cpu = true; + } + if (evlist__create_maps(evsel_list, &target) < 0) { if (target__has_task(&target)) { pr_err("Problems finding threads of monitor\n"); @@ -2895,7 +2979,7 @@ int cmd_stat(int argc, const char **argv) evlist__reset_prev_raw_counts(evsel_list); status = run_perf_stat(argc, argv, run_idx); - if (status == -1) + if (status < 0) break; if (forever && !interval) { @@ -2936,7 +3020,7 @@ int cmd_stat(int argc, const char **argv) } if (!interval) { - if (WRITE_STAT_ROUND_EVENT(walltime_nsecs_stats.max, FINAL)) + if (WRITE_STAT_ROUND_EVENT(stat_config.walltime_nsecs_stats->max, FINAL)) pr_err("failed to write stat round event\n"); } @@ -2965,5 +3049,6 @@ out: evlist__close_control(stat_config.ctl_fd, stat_config.ctl_fd_ack, &stat_config.ctl_fd_close); - return status; + /* Only the low byte of status becomes the exit code. */ + return abs(status); } |
