| // SPDX-License-Identifier: GPL-2.0 |
| #include <errno.h> |
| #include <unistd.h> |
| #include <sys/syscall.h> |
| #include <perf/evsel.h> |
| #include <perf/cpumap.h> |
| #include <perf/threadmap.h> |
| #include <linux/hash.h> |
| #include <linux/list.h> |
| #include <internal/evsel.h> |
| #include <linux/zalloc.h> |
| #include <stdlib.h> |
| #include <internal/xyarray.h> |
| #include <internal/cpumap.h> |
| #include <internal/mmap.h> |
| #include <internal/threadmap.h> |
| #include <internal/lib.h> |
| #include <linux/string.h> |
| #include <sys/ioctl.h> |
| #include <sys/mman.h> |
| #include <asm/bug.h> |
| |
| void perf_evsel__init(struct perf_evsel *evsel, struct perf_event_attr *attr, |
| int idx) |
| { |
| INIT_LIST_HEAD(&evsel->node); |
| INIT_LIST_HEAD(&evsel->per_stream_periods); |
| evsel->attr = *attr; |
| evsel->idx = idx; |
| evsel->leader = evsel; |
| } |
| |
| struct perf_evsel *perf_evsel__new(struct perf_event_attr *attr) |
| { |
| struct perf_evsel *evsel = zalloc(sizeof(*evsel)); |
| |
| if (evsel != NULL) |
| perf_evsel__init(evsel, attr, 0); |
| |
| return evsel; |
| } |
| |
| void perf_evsel__delete(struct perf_evsel *evsel) |
| { |
| free(evsel); |
| } |
| |
| #define FD(_evsel, _cpu_map_idx, _thread) \ |
| ((int *)xyarray__entry(_evsel->fd, _cpu_map_idx, _thread)) |
| #define MMAP(_evsel, _cpu_map_idx, _thread) \ |
| (_evsel->mmap ? ((struct perf_mmap *) xyarray__entry(_evsel->mmap, _cpu_map_idx, _thread)) \ |
| : NULL) |
| |
| int perf_evsel__alloc_fd(struct perf_evsel *evsel, int ncpus, int nthreads) |
| { |
| evsel->fd = xyarray__new(ncpus, nthreads, sizeof(int)); |
| |
| if (evsel->fd) { |
| int idx, thread; |
| |
| for (idx = 0; idx < ncpus; idx++) { |
| for (thread = 0; thread < nthreads; thread++) { |
| int *fd = FD(evsel, idx, thread); |
| |
| if (fd) |
| *fd = -1; |
| } |
| } |
| } |
| |
| return evsel->fd != NULL ? 0 : -ENOMEM; |
| } |
| |
| static int perf_evsel__alloc_mmap(struct perf_evsel *evsel, int ncpus, int nthreads) |
| { |
| evsel->mmap = xyarray__new(ncpus, nthreads, sizeof(struct perf_mmap)); |
| |
| return evsel->mmap != NULL ? 0 : -ENOMEM; |
| } |
| |
| static int |
| sys_perf_event_open(struct perf_event_attr *attr, |
| pid_t pid, struct perf_cpu cpu, int group_fd, |
| unsigned long flags) |
| { |
| return syscall(__NR_perf_event_open, attr, pid, cpu.cpu, group_fd, flags); |
| } |
| |
| static int get_group_fd(struct perf_evsel *evsel, int cpu_map_idx, int thread, int *group_fd) |
| { |
| struct perf_evsel *leader = evsel->leader; |
| int *fd; |
| |
| if (evsel == leader) { |
| *group_fd = -1; |
| return 0; |
| } |
| |
| /* |
| * Leader must be already processed/open, |
| * if not it's a bug. |
| */ |
| if (!leader->fd) |
| return -ENOTCONN; |
| |
| fd = FD(leader, cpu_map_idx, thread); |
| if (fd == NULL || *fd == -1) |
| return -EBADF; |
| |
| *group_fd = *fd; |
| |
| return 0; |
| } |
| |
| int perf_evsel__open(struct perf_evsel *evsel, struct perf_cpu_map *cpus, |
| struct perf_thread_map *threads) |
| { |
| struct perf_cpu cpu; |
| int idx, thread, err = 0; |
| |
| if (cpus == NULL) { |
| static struct perf_cpu_map *empty_cpu_map; |
| |
| if (empty_cpu_map == NULL) { |
| empty_cpu_map = perf_cpu_map__new_any_cpu(); |
| if (empty_cpu_map == NULL) |
| return -ENOMEM; |
| } |
| |
| cpus = empty_cpu_map; |
| } |
| |
| if (threads == NULL) { |
| static struct perf_thread_map *empty_thread_map; |
| |
| if (empty_thread_map == NULL) { |
| empty_thread_map = perf_thread_map__new_dummy(); |
| if (empty_thread_map == NULL) |
| return -ENOMEM; |
| } |
| |
| threads = empty_thread_map; |
| } |
| |
| if (evsel->fd == NULL && |
| perf_evsel__alloc_fd(evsel, perf_cpu_map__nr(cpus), threads->nr) < 0) |
| return -ENOMEM; |
| |
| perf_cpu_map__for_each_cpu(cpu, idx, cpus) { |
| for (thread = 0; thread < threads->nr; thread++) { |
| int fd, group_fd, *evsel_fd; |
| |
| evsel_fd = FD(evsel, idx, thread); |
| if (evsel_fd == NULL) { |
| err = -EINVAL; |
| goto out; |
| } |
| |
| err = get_group_fd(evsel, idx, thread, &group_fd); |
| if (err < 0) |
| goto out; |
| |
| fd = sys_perf_event_open(&evsel->attr, |
| threads->map[thread].pid, |
| cpu, group_fd, 0); |
| |
| if (fd < 0) { |
| err = -errno; |
| goto out; |
| } |
| |
| *evsel_fd = fd; |
| } |
| } |
| out: |
| if (err) |
| perf_evsel__close(evsel); |
| |
| return err; |
| } |
| |
| static void perf_evsel__close_fd_cpu(struct perf_evsel *evsel, int cpu_map_idx) |
| { |
| int thread; |
| |
| for (thread = 0; thread < xyarray__max_y(evsel->fd); ++thread) { |
| int *fd = FD(evsel, cpu_map_idx, thread); |
| |
| if (fd && *fd >= 0) { |
| close(*fd); |
| *fd = -1; |
| } |
| } |
| } |
| |
| void perf_evsel__close_fd(struct perf_evsel *evsel) |
| { |
| for (int idx = 0; idx < xyarray__max_x(evsel->fd); idx++) |
| perf_evsel__close_fd_cpu(evsel, idx); |
| } |
| |
| void perf_evsel__free_fd(struct perf_evsel *evsel) |
| { |
| xyarray__delete(evsel->fd); |
| evsel->fd = NULL; |
| } |
| |
| void perf_evsel__close(struct perf_evsel *evsel) |
| { |
| if (evsel->fd == NULL) |
| return; |
| |
| perf_evsel__close_fd(evsel); |
| perf_evsel__free_fd(evsel); |
| } |
| |
| void perf_evsel__close_cpu(struct perf_evsel *evsel, int cpu_map_idx) |
| { |
| if (evsel->fd == NULL) |
| return; |
| |
| perf_evsel__close_fd_cpu(evsel, cpu_map_idx); |
| } |
| |
| void perf_evsel__munmap(struct perf_evsel *evsel) |
| { |
| int idx, thread; |
| |
| if (evsel->fd == NULL || evsel->mmap == NULL) |
| return; |
| |
| for (idx = 0; idx < xyarray__max_x(evsel->fd); idx++) { |
| for (thread = 0; thread < xyarray__max_y(evsel->fd); thread++) { |
| int *fd = FD(evsel, idx, thread); |
| |
| if (fd == NULL || *fd < 0) |
| continue; |
| |
| perf_mmap__munmap(MMAP(evsel, idx, thread)); |
| } |
| } |
| |
| xyarray__delete(evsel->mmap); |
| evsel->mmap = NULL; |
| } |
| |
| int perf_evsel__mmap(struct perf_evsel *evsel, int pages) |
| { |
| int ret, idx, thread; |
| struct perf_mmap_param mp = { |
| .prot = PROT_READ | PROT_WRITE, |
| .mask = (pages * page_size) - 1, |
| }; |
| |
| if (evsel->fd == NULL || evsel->mmap) |
| return -EINVAL; |
| |
| if (perf_evsel__alloc_mmap(evsel, xyarray__max_x(evsel->fd), xyarray__max_y(evsel->fd)) < 0) |
| return -ENOMEM; |
| |
| for (idx = 0; idx < xyarray__max_x(evsel->fd); idx++) { |
| for (thread = 0; thread < xyarray__max_y(evsel->fd); thread++) { |
| int *fd = FD(evsel, idx, thread); |
| struct perf_mmap *map; |
| struct perf_cpu cpu = perf_cpu_map__cpu(evsel->cpus, idx); |
| |
| if (fd == NULL || *fd < 0) |
| continue; |
| |
| map = MMAP(evsel, idx, thread); |
| perf_mmap__init(map, NULL, false, NULL); |
| |
| ret = perf_mmap__mmap(map, &mp, *fd, cpu); |
| if (ret) { |
| perf_evsel__munmap(evsel); |
| return ret; |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| void *perf_evsel__mmap_base(struct perf_evsel *evsel, int cpu_map_idx, int thread) |
| { |
| int *fd = FD(evsel, cpu_map_idx, thread); |
| |
| if (fd == NULL || *fd < 0 || MMAP(evsel, cpu_map_idx, thread) == NULL) |
| return NULL; |
| |
| return MMAP(evsel, cpu_map_idx, thread)->base; |
| } |
| |
| int perf_evsel__read_size(struct perf_evsel *evsel) |
| { |
| u64 read_format = evsel->attr.read_format; |
| int entry = sizeof(u64); /* value */ |
| int size = 0; |
| int nr = 1; |
| |
| if (read_format & PERF_FORMAT_TOTAL_TIME_ENABLED) |
| size += sizeof(u64); |
| |
| if (read_format & PERF_FORMAT_TOTAL_TIME_RUNNING) |
| size += sizeof(u64); |
| |
| if (read_format & PERF_FORMAT_ID) |
| entry += sizeof(u64); |
| |
| if (read_format & PERF_FORMAT_LOST) |
| entry += sizeof(u64); |
| |
| if (read_format & PERF_FORMAT_GROUP) { |
| nr = evsel->nr_members; |
| size += sizeof(u64); |
| } |
| |
| size += entry * nr; |
| return size; |
| } |
| |
| /* This only reads values for the leader */ |
| static int perf_evsel__read_group(struct perf_evsel *evsel, int cpu_map_idx, |
| int thread, struct perf_counts_values *count) |
| { |
| size_t size = perf_evsel__read_size(evsel); |
| int *fd = FD(evsel, cpu_map_idx, thread); |
| u64 read_format = evsel->attr.read_format; |
| u64 *data; |
| int idx = 1; |
| |
| if (fd == NULL || *fd < 0) |
| return -EINVAL; |
| |
| data = calloc(1, size); |
| if (data == NULL) |
| return -ENOMEM; |
| |
| if (readn(*fd, data, size) <= 0) { |
| free(data); |
| return -errno; |
| } |
| |
| /* |
| * This reads only the leader event intentionally since we don't have |
| * perf counts values for sibling events. |
| */ |
| if (read_format & PERF_FORMAT_TOTAL_TIME_ENABLED) |
| count->ena = data[idx++]; |
| if (read_format & PERF_FORMAT_TOTAL_TIME_RUNNING) |
| count->run = data[idx++]; |
| |
| /* value is always available */ |
| count->val = data[idx++]; |
| if (read_format & PERF_FORMAT_ID) |
| count->id = data[idx++]; |
| if (read_format & PERF_FORMAT_LOST) |
| count->lost = data[idx++]; |
| |
| free(data); |
| return 0; |
| } |
| |
| /* |
| * The perf read format is very flexible. It needs to set the proper |
| * values according to the read format. |
| */ |
| static void perf_evsel__adjust_values(struct perf_evsel *evsel, u64 *buf, |
| struct perf_counts_values *count) |
| { |
| u64 read_format = evsel->attr.read_format; |
| int n = 0; |
| |
| count->val = buf[n++]; |
| |
| if (read_format & PERF_FORMAT_TOTAL_TIME_ENABLED) |
| count->ena = buf[n++]; |
| |
| if (read_format & PERF_FORMAT_TOTAL_TIME_RUNNING) |
| count->run = buf[n++]; |
| |
| if (read_format & PERF_FORMAT_ID) |
| count->id = buf[n++]; |
| |
| if (read_format & PERF_FORMAT_LOST) |
| count->lost = buf[n++]; |
| } |
| |
| int perf_evsel__read(struct perf_evsel *evsel, int cpu_map_idx, int thread, |
| struct perf_counts_values *count) |
| { |
| size_t size = perf_evsel__read_size(evsel); |
| int *fd = FD(evsel, cpu_map_idx, thread); |
| u64 read_format = evsel->attr.read_format; |
| struct perf_counts_values buf; |
| |
| memset(count, 0, sizeof(*count)); |
| |
| if (fd == NULL || *fd < 0) |
| return -EINVAL; |
| |
| if (read_format & PERF_FORMAT_GROUP) |
| return perf_evsel__read_group(evsel, cpu_map_idx, thread, count); |
| |
| if (MMAP(evsel, cpu_map_idx, thread) && |
| !(read_format & (PERF_FORMAT_ID | PERF_FORMAT_LOST)) && |
| !perf_mmap__read_self(MMAP(evsel, cpu_map_idx, thread), count)) |
| return 0; |
| |
| if (readn(*fd, buf.values, size) <= 0) |
| return -errno; |
| |
| perf_evsel__adjust_values(evsel, buf.values, count); |
| return 0; |
| } |
| |
| static int perf_evsel__ioctl(struct perf_evsel *evsel, int ioc, void *arg, |
| int cpu_map_idx, int thread) |
| { |
| int *fd = FD(evsel, cpu_map_idx, thread); |
| |
| if (fd == NULL || *fd < 0) |
| return -1; |
| |
| return ioctl(*fd, ioc, arg); |
| } |
| |
| static int perf_evsel__run_ioctl(struct perf_evsel *evsel, |
| int ioc, void *arg, |
| int cpu_map_idx) |
| { |
| int thread; |
| |
| for (thread = 0; thread < xyarray__max_y(evsel->fd); thread++) { |
| int err = perf_evsel__ioctl(evsel, ioc, arg, cpu_map_idx, thread); |
| |
| if (err) |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| int perf_evsel__enable_cpu(struct perf_evsel *evsel, int cpu_map_idx) |
| { |
| return perf_evsel__run_ioctl(evsel, PERF_EVENT_IOC_ENABLE, NULL, cpu_map_idx); |
| } |
| |
| int perf_evsel__enable_thread(struct perf_evsel *evsel, int thread) |
| { |
| struct perf_cpu cpu __maybe_unused; |
| int idx; |
| int err; |
| |
| perf_cpu_map__for_each_cpu(cpu, idx, evsel->cpus) { |
| err = perf_evsel__ioctl(evsel, PERF_EVENT_IOC_ENABLE, NULL, idx, thread); |
| if (err) |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| int perf_evsel__enable(struct perf_evsel *evsel) |
| { |
| int i; |
| int err = 0; |
| |
| for (i = 0; i < xyarray__max_x(evsel->fd) && !err; i++) |
| err = perf_evsel__run_ioctl(evsel, PERF_EVENT_IOC_ENABLE, NULL, i); |
| return err; |
| } |
| |
| int perf_evsel__disable_cpu(struct perf_evsel *evsel, int cpu_map_idx) |
| { |
| return perf_evsel__run_ioctl(evsel, PERF_EVENT_IOC_DISABLE, NULL, cpu_map_idx); |
| } |
| |
| int perf_evsel__disable(struct perf_evsel *evsel) |
| { |
| int i; |
| int err = 0; |
| |
| for (i = 0; i < xyarray__max_x(evsel->fd) && !err; i++) |
| err = perf_evsel__run_ioctl(evsel, PERF_EVENT_IOC_DISABLE, NULL, i); |
| return err; |
| } |
| |
| int perf_evsel__apply_filter(struct perf_evsel *evsel, const char *filter) |
| { |
| int err = 0, i; |
| |
| for (i = 0; i < perf_cpu_map__nr(evsel->cpus) && !err; i++) |
| err = perf_evsel__run_ioctl(evsel, |
| PERF_EVENT_IOC_SET_FILTER, |
| (void *)filter, i); |
| return err; |
| } |
| |
| struct perf_cpu_map *perf_evsel__cpus(struct perf_evsel *evsel) |
| { |
| return evsel->cpus; |
| } |
| |
| struct perf_thread_map *perf_evsel__threads(struct perf_evsel *evsel) |
| { |
| return evsel->threads; |
| } |
| |
| struct perf_event_attr *perf_evsel__attr(struct perf_evsel *evsel) |
| { |
| return &evsel->attr; |
| } |
| |
| int perf_evsel__alloc_id(struct perf_evsel *evsel, int ncpus, int nthreads) |
| { |
| if (ncpus == 0 || nthreads == 0) |
| return 0; |
| |
| evsel->sample_id = xyarray__new(ncpus, nthreads, sizeof(struct perf_sample_id)); |
| if (evsel->sample_id == NULL) |
| return -ENOMEM; |
| |
| evsel->id = zalloc(ncpus * nthreads * sizeof(u64)); |
| if (evsel->id == NULL) { |
| xyarray__delete(evsel->sample_id); |
| evsel->sample_id = NULL; |
| return -ENOMEM; |
| } |
| |
| return 0; |
| } |
| |
| void perf_evsel__free_id(struct perf_evsel *evsel) |
| { |
| struct perf_sample_id_period *pos, *n; |
| |
| xyarray__delete(evsel->sample_id); |
| evsel->sample_id = NULL; |
| zfree(&evsel->id); |
| evsel->ids = 0; |
| |
| perf_evsel_for_each_per_thread_period_safe(evsel, n, pos) { |
| list_del_init(&pos->node); |
| free(pos); |
| } |
| } |
| |
| bool perf_evsel__attr_has_per_thread_sample_period(struct perf_evsel *evsel) |
| { |
| return (evsel->attr.sample_type & PERF_SAMPLE_READ) && |
| (evsel->attr.sample_type & PERF_SAMPLE_TID) && |
| evsel->attr.inherit; |
| } |
| |
| u64 *perf_sample_id__get_period_storage(struct perf_sample_id *sid, u32 tid, bool per_thread) |
| { |
| struct hlist_head *head; |
| struct perf_sample_id_period *res; |
| int hash; |
| |
| if (!per_thread) |
| return &sid->period; |
| |
| hash = hash_32(tid, PERF_SAMPLE_ID__HLIST_BITS); |
| head = &sid->periods[hash]; |
| |
| hlist_for_each_entry(res, head, hnode) |
| if (res->tid == tid) |
| return &res->period; |
| |
| if (sid->evsel == NULL) |
| return NULL; |
| |
| res = zalloc(sizeof(struct perf_sample_id_period)); |
| if (res == NULL) |
| return NULL; |
| |
| INIT_LIST_HEAD(&res->node); |
| res->tid = tid; |
| |
| list_add_tail(&res->node, &sid->evsel->per_stream_periods); |
| hlist_add_head(&res->hnode, &sid->periods[hash]); |
| |
| return &res->period; |
| } |
| |
| void perf_counts_values__scale(struct perf_counts_values *count, |
| bool scale, __s8 *pscaled) |
| { |
| s8 scaled = 0; |
| |
| if (scale) { |
| if (count->run == 0) { |
| scaled = -1; |
| count->val = 0; |
| } else if (count->run < count->ena) { |
| scaled = 1; |
| count->val = (u64)((double)count->val * count->ena / count->run); |
| } |
| } |
| |
| if (pscaled) |
| *pscaled = scaled; |
| } |