| /* SPDX-License-Identifier: GPL-2.0 */ |
| |
| #include "perf-sys.h" |
| #include "util/cloexec.h" |
| #include "util/evlist.h" |
| #include "util/evsel.h" |
| #include "util/parse-events.h" |
| #include "util/perf_api_probe.h" |
| #include <perf/cpumap.h> |
| #include <errno.h> |
| |
| typedef void (*setup_probe_fn_t)(struct evsel *evsel); |
| |
| static int perf_do_probe_api(setup_probe_fn_t fn, int cpu, const char *str) |
| { |
| struct evlist *evlist; |
| struct evsel *evsel; |
| unsigned long flags = perf_event_open_cloexec_flag(); |
| int err = -EAGAIN, fd; |
| static pid_t pid = -1; |
| |
| evlist = evlist__new(); |
| if (!evlist) |
| return -ENOMEM; |
| |
| if (parse_events(evlist, str, NULL)) |
| goto out_delete; |
| |
| evsel = evlist__first(evlist); |
| |
| while (1) { |
| fd = sys_perf_event_open(&evsel->core.attr, pid, cpu, -1, flags); |
| if (fd < 0) { |
| if (pid == -1 && errno == EACCES) { |
| pid = 0; |
| continue; |
| } |
| goto out_delete; |
| } |
| break; |
| } |
| close(fd); |
| |
| fn(evsel); |
| |
| fd = sys_perf_event_open(&evsel->core.attr, pid, cpu, -1, flags); |
| if (fd < 0) { |
| if (errno == EINVAL) |
| err = -EINVAL; |
| goto out_delete; |
| } |
| close(fd); |
| err = 0; |
| |
| out_delete: |
| evlist__delete(evlist); |
| return err; |
| } |
| |
| static bool perf_probe_api(setup_probe_fn_t fn) |
| { |
| const char *try[] = {"cycles:u", "instructions:u", "cpu-clock:u", NULL}; |
| struct perf_cpu_map *cpus; |
| int cpu, ret, i = 0; |
| |
| cpus = perf_cpu_map__new(NULL); |
| if (!cpus) |
| return false; |
| cpu = cpus->map[0]; |
| perf_cpu_map__put(cpus); |
| |
| do { |
| ret = perf_do_probe_api(fn, cpu, try[i++]); |
| if (!ret) |
| return true; |
| } while (ret == -EAGAIN && try[i]); |
| |
| return false; |
| } |
| |
| static void perf_probe_sample_identifier(struct evsel *evsel) |
| { |
| evsel->core.attr.sample_type |= PERF_SAMPLE_IDENTIFIER; |
| } |
| |
| static void perf_probe_comm_exec(struct evsel *evsel) |
| { |
| evsel->core.attr.comm_exec = 1; |
| } |
| |
| static void perf_probe_context_switch(struct evsel *evsel) |
| { |
| evsel->core.attr.context_switch = 1; |
| } |
| |
| bool perf_can_sample_identifier(void) |
| { |
| return perf_probe_api(perf_probe_sample_identifier); |
| } |
| |
| bool perf_can_comm_exec(void) |
| { |
| return perf_probe_api(perf_probe_comm_exec); |
| } |
| |
| bool perf_can_record_switch_events(void) |
| { |
| return perf_probe_api(perf_probe_context_switch); |
| } |
| |
| bool perf_can_record_cpu_wide(void) |
| { |
| struct perf_event_attr attr = { |
| .type = PERF_TYPE_SOFTWARE, |
| .config = PERF_COUNT_SW_CPU_CLOCK, |
| .exclude_kernel = 1, |
| }; |
| struct perf_cpu_map *cpus; |
| int cpu, fd; |
| |
| cpus = perf_cpu_map__new(NULL); |
| if (!cpus) |
| return false; |
| cpu = cpus->map[0]; |
| perf_cpu_map__put(cpus); |
| |
| fd = sys_perf_event_open(&attr, -1, cpu, -1, 0); |
| if (fd < 0) |
| return false; |
| close(fd); |
| |
| return true; |
| } |
| |
| /* |
| * Architectures are expected to know if AUX area sampling is supported by the |
| * hardware. Here we check for kernel support. |
| */ |
| bool perf_can_aux_sample(void) |
| { |
| struct perf_event_attr attr = { |
| .size = sizeof(struct perf_event_attr), |
| .exclude_kernel = 1, |
| /* |
| * Non-zero value causes the kernel to calculate the effective |
| * attribute size up to that byte. |
| */ |
| .aux_sample_size = 1, |
| }; |
| int fd; |
| |
| fd = sys_perf_event_open(&attr, -1, 0, -1, 0); |
| /* |
| * If the kernel attribute is big enough to contain aux_sample_size |
| * then we assume that it is supported. We are relying on the kernel to |
| * validate the attribute size before anything else that could be wrong. |
| */ |
| if (fd < 0 && errno == E2BIG) |
| return false; |
| if (fd >= 0) |
| close(fd); |
| |
| return true; |
| } |