| // SPDX-License-Identifier: GPL-2.0 | |
| #include "debug.h" | |
| #include "evlist.h" | |
| #include "evsel.h" | |
| #include "evsel_config.h" | |
| #include "parse-events.h" | |
| #include <errno.h> | |
| #include <limits.h> | |
| #include <stdlib.h> | |
| #include <api/fs/fs.h> | |
| #include <subcmd/parse-options.h> | |
| #include <perf/cpumap.h> | |
| #include "cloexec.h" | |
| #include "util/perf_api_probe.h" | |
| #include "record.h" | |
| #include "../perf-sys.h" | |
| #include "topdown.h" | |
| #include "map_symbol.h" | |
| #include "mem-events.h" | |
| /* | |
| * evsel__config_leader_sampling() uses special rules for leader sampling. | |
| * However, if the leader is an AUX area event, then assume the event to sample | |
| * is the next event. | |
| */ | |
| static struct evsel *evsel__read_sampler(struct evsel *evsel, struct evlist *evlist) | |
| { | |
| struct evsel *leader = evsel__leader(evsel); | |
| if (evsel__is_aux_event(leader) || arch_topdown_sample_read(leader) || | |
| is_mem_loads_aux_event(leader)) { | |
| evlist__for_each_entry(evlist, evsel) { | |
| if (evsel__leader(evsel) == leader && evsel != evsel__leader(evsel)) | |
| return evsel; | |
| } | |
| } | |
| return leader; | |
| } | |
| static u64 evsel__config_term_mask(struct evsel *evsel) | |
| { | |
| struct evsel_config_term *term; | |
| struct list_head *config_terms = &evsel->config_terms; | |
| u64 term_types = 0; | |
| list_for_each_entry(term, config_terms, list) { | |
| term_types |= 1 << term->type; | |
| } | |
| return term_types; | |
| } | |
| static void evsel__config_leader_sampling(struct evsel *evsel, struct evlist *evlist) | |
| { | |
| struct perf_event_attr *attr = &evsel->core.attr; | |
| struct evsel *leader = evsel__leader(evsel); | |
| struct evsel *read_sampler; | |
| u64 term_types, freq_mask; | |
| if (!leader->sample_read) | |
| return; | |
| read_sampler = evsel__read_sampler(evsel, evlist); | |
| if (evsel == read_sampler) | |
| return; | |
| term_types = evsel__config_term_mask(evsel); | |
| /* | |
| * Disable sampling for all group members except those with explicit | |
| * config terms or the leader. In the case of an AUX area event, the 2nd | |
| * event in the group is the one that 'leads' the sampling. | |
| */ | |
| freq_mask = (1 << EVSEL__CONFIG_TERM_FREQ) | (1 << EVSEL__CONFIG_TERM_PERIOD); | |
| if ((term_types & freq_mask) == 0) { | |
| attr->freq = 0; | |
| attr->sample_freq = 0; | |
| attr->sample_period = 0; | |
| } | |
| if ((term_types & (1 << EVSEL__CONFIG_TERM_OVERWRITE)) == 0) | |
| attr->write_backward = 0; | |
| /* | |
| * We don't get a sample for slave events, we make them when delivering | |
| * the group leader sample. Set the slave event to follow the master | |
| * sample_type to ease up reporting. | |
| * An AUX area event also has sample_type requirements, so also include | |
| * the sample type bits from the leader's sample_type to cover that | |
| * case. | |
| */ | |
| attr->sample_type = read_sampler->core.attr.sample_type | | |
| leader->core.attr.sample_type; | |
| } | |
| void evlist__config(struct evlist *evlist, struct record_opts *opts, struct callchain_param *callchain) | |
| { | |
| struct evsel *evsel; | |
| bool use_sample_identifier = false; | |
| bool use_comm_exec; | |
| bool sample_id = opts->sample_id; | |
| if (perf_cpu_map__cpu(evlist->core.user_requested_cpus, 0).cpu < 0) | |
| opts->no_inherit = true; | |
| use_comm_exec = perf_can_comm_exec(); | |
| evlist__for_each_entry(evlist, evsel) { | |
| evsel__config(evsel, opts, callchain); | |
| if (evsel->tracking && use_comm_exec) | |
| evsel->core.attr.comm_exec = 1; | |
| } | |
| /* Configure leader sampling here now that the sample type is known */ | |
| evlist__for_each_entry(evlist, evsel) | |
| evsel__config_leader_sampling(evsel, evlist); | |
| if (opts->full_auxtrace || opts->sample_identifier) { | |
| /* | |
| * Need to be able to synthesize and parse selected events with | |
| * arbitrary sample types, which requires always being able to | |
| * match the id. | |
| */ | |
| use_sample_identifier = perf_can_sample_identifier(); | |
| sample_id = true; | |
| } else if (evlist->core.nr_entries > 1) { | |
| struct evsel *first = evlist__first(evlist); | |
| evlist__for_each_entry(evlist, evsel) { | |
| if (evsel->core.attr.sample_type == first->core.attr.sample_type) | |
| continue; | |
| use_sample_identifier = perf_can_sample_identifier(); | |
| break; | |
| } | |
| sample_id = true; | |
| } | |
| if (sample_id) { | |
| evlist__for_each_entry(evlist, evsel) | |
| evsel__set_sample_id(evsel, use_sample_identifier); | |
| } | |
| evlist__set_id_pos(evlist); | |
| } | |
| static int get_max_rate(unsigned int *rate) | |
| { | |
| return sysctl__read_int("kernel/perf_event_max_sample_rate", (int *)rate); | |
| } | |
| static int record_opts__config_freq(struct record_opts *opts) | |
| { | |
| bool user_freq = opts->user_freq != UINT_MAX; | |
| bool user_interval = opts->user_interval != ULLONG_MAX; | |
| unsigned int max_rate; | |
| if (user_interval && user_freq) { | |
| pr_err("cannot set frequency and period at the same time\n"); | |
| return -1; | |
| } | |
| if (user_interval) | |
| opts->default_interval = opts->user_interval; | |
| if (user_freq) | |
| opts->freq = opts->user_freq; | |
| /* | |
| * User specified count overrides default frequency. | |
| */ | |
| if (opts->default_interval) | |
| opts->freq = 0; | |
| else if (opts->freq) { | |
| opts->default_interval = opts->freq; | |
| } else { | |
| pr_err("frequency and count are zero, aborting\n"); | |
| return -1; | |
| } | |
| if (get_max_rate(&max_rate)) | |
| return 0; | |
| /* | |
| * User specified frequency is over current maximum. | |
| */ | |
| if (user_freq && (max_rate < opts->freq)) { | |
| if (opts->strict_freq) { | |
| pr_err("error: Maximum frequency rate (%'u Hz) exceeded.\n" | |
| " Please use -F freq option with a lower value or consider\n" | |
| " tweaking /proc/sys/kernel/perf_event_max_sample_rate.\n", | |
| max_rate); | |
| return -1; | |
| } else { | |
| pr_warning("warning: Maximum frequency rate (%'u Hz) exceeded, throttling from %'u Hz to %'u Hz.\n" | |
| " The limit can be raised via /proc/sys/kernel/perf_event_max_sample_rate.\n" | |
| " The kernel will lower it when perf's interrupts take too long.\n" | |
| " Use --strict-freq to disable this throttling, refusing to record.\n", | |
| max_rate, opts->freq, max_rate); | |
| opts->freq = max_rate; | |
| } | |
| } | |
| /* | |
| * Default frequency is over current maximum. | |
| */ | |
| if (max_rate < opts->freq) { | |
| pr_warning("Lowering default frequency rate from %u to %u.\n" | |
| "Please consider tweaking " | |
| "/proc/sys/kernel/perf_event_max_sample_rate.\n", | |
| opts->freq, max_rate); | |
| opts->freq = max_rate; | |
| } | |
| return 0; | |
| } | |
| int record_opts__config(struct record_opts *opts) | |
| { | |
| return record_opts__config_freq(opts); | |
| } | |
| bool evlist__can_select_event(struct evlist *evlist, const char *str) | |
| { | |
| struct evlist *temp_evlist; | |
| struct evsel *evsel; | |
| int err, fd; | |
| struct perf_cpu cpu = { .cpu = 0 }; | |
| bool ret = false; | |
| pid_t pid = -1; | |
| temp_evlist = evlist__new(); | |
| if (!temp_evlist) | |
| return false; | |
| err = parse_event(temp_evlist, str); | |
| if (err) | |
| goto out_delete; | |
| evsel = evlist__last(temp_evlist); | |
| if (!evlist || perf_cpu_map__is_any_cpu_or_is_empty(evlist->core.user_requested_cpus)) { | |
| struct perf_cpu_map *cpus = perf_cpu_map__new_online_cpus(); | |
| if (cpus) | |
| cpu = perf_cpu_map__cpu(cpus, 0); | |
| perf_cpu_map__put(cpus); | |
| } else { | |
| cpu = perf_cpu_map__cpu(evlist->core.user_requested_cpus, 0); | |
| } | |
| while (1) { | |
| fd = sys_perf_event_open(&evsel->core.attr, pid, cpu.cpu, -1, | |
| perf_event_open_cloexec_flag()); | |
| if (fd < 0) { | |
| if (pid == -1 && errno == EACCES) { | |
| pid = 0; | |
| continue; | |
| } | |
| goto out_delete; | |
| } | |
| break; | |
| } | |
| close(fd); | |
| ret = true; | |
| out_delete: | |
| evlist__delete(temp_evlist); | |
| return ret; | |
| } | |
| int record__parse_freq(const struct option *opt, const char *str, int unset __maybe_unused) | |
| { | |
| unsigned int freq; | |
| struct record_opts *opts = opt->value; | |
| if (!str) | |
| return -EINVAL; | |
| if (strcasecmp(str, "max") == 0) { | |
| if (get_max_rate(&freq)) { | |
| pr_err("couldn't read /proc/sys/kernel/perf_event_max_sample_rate\n"); | |
| return -1; | |
| } | |
| pr_info("info: Using a maximum frequency rate of %'d Hz\n", freq); | |
| } else { | |
| freq = atoi(str); | |
| } | |
| opts->user_freq = freq; | |
| return 0; | |
| } |