| /* SPDX-License-Identifier: GPL-2.0 */ | |
| #include "perf_api_probe.h" | |
| #include <errno.h> | |
| #include <perf/cpumap.h> | |
| #include "cloexec.h" | |
| #include "evlist.h" | |
| #include "evsel.h" | |
| #include "parse-events.h" | |
| #include "perf-sys.h" | |
| #include "pmu.h" | |
| #include "pmus.h" | |
| typedef void (*setup_probe_fn_t)(struct evsel *evsel); | |
| static int perf_do_probe_api(setup_probe_fn_t fn, struct perf_cpu 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_event(evlist, str)) | |
| goto out_delete; | |
| evsel = evlist__first(evlist); | |
| while (1) { | |
| fd = sys_perf_event_open(&evsel->core.attr, pid, cpu.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.cpu, -1, flags); | |
| if (fd < 0) { | |
| if (errno == EINVAL) | |
| err = -EINVAL; | |
| goto out_delete; | |
| } | |
| close(fd); | |
| err = 0; | |
| out_delete: | |
| evlist__put(evlist); | |
| return err; | |
| } | |
| static bool perf_probe_api(setup_probe_fn_t fn) | |
| { | |
| struct perf_pmu *pmu; | |
| struct perf_cpu_map *cpus; | |
| struct perf_cpu cpu; | |
| int ret = 0; | |
| cpus = perf_cpu_map__new_online_cpus(); | |
| if (!cpus) | |
| return false; | |
| cpu = perf_cpu_map__cpu(cpus, 0); | |
| perf_cpu_map__put(cpus); | |
| ret = perf_do_probe_api(fn, cpu, "software/cpu-clock/u"); | |
| if (!ret) | |
| return true; | |
| pmu = perf_pmus__scan_core(/*pmu=*/NULL); | |
| if (pmu) { | |
| const char *try[] = {"cycles", "instructions", NULL}; | |
| char buf[256]; | |
| int i = 0; | |
| while (ret == -EAGAIN && try[i]) { | |
| snprintf(buf, sizeof(buf), "%s/%s/u", pmu->name, try[i++]); | |
| ret = perf_do_probe_api(fn, cpu, buf); | |
| if (!ret) | |
| return true; | |
| } | |
| } | |
| 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; | |
| } | |
| static void perf_probe_text_poke(struct evsel *evsel) | |
| { | |
| evsel->core.attr.text_poke = 1; | |
| } | |
| static void perf_probe_build_id(struct evsel *evsel) | |
| { | |
| evsel->core.attr.build_id = 1; | |
| } | |
| static void perf_probe_cgroup(struct evsel *evsel) | |
| { | |
| evsel->core.attr.cgroup = 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_text_poke_events(void) | |
| { | |
| return perf_probe_api(perf_probe_text_poke); | |
| } | |
| 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; | |
| struct perf_cpu cpu; | |
| int fd; | |
| cpus = perf_cpu_map__new_online_cpus(); | |
| if (!cpus) | |
| return false; | |
| cpu = perf_cpu_map__cpu(cpus, 0); | |
| perf_cpu_map__put(cpus); | |
| fd = sys_perf_event_open(&attr, -1, cpu.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; | |
| } | |
| bool perf_can_record_build_id(void) | |
| { | |
| return perf_probe_api(perf_probe_build_id); | |
| } | |
| bool perf_can_record_cgroup(void) | |
| { | |
| return perf_probe_api(perf_probe_cgroup); | |
| } |