| // 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__exit(struct perf_evsel *evsel) | |
| { | |
| assert(evsel->fd == NULL); /* If not fds were not closed. */ | |
| assert(evsel->mmap == NULL); /* If not munmap wasn't called. */ | |
| assert(evsel->sample_id == NULL); /* If not free_id wasn't called. */ | |
| perf_cpu_map__put(evsel->cpus); | |
| perf_cpu_map__put(evsel->pmu_cpus); | |
| perf_thread_map__put(evsel->threads); | |
| } | |
| void perf_evsel__delete(struct perf_evsel *evsel) | |
| { | |
| perf_evsel__exit(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; | |
| unsigned int idx; | |
| int 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; | |
| unsigned 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; | |
| for (unsigned int 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; | |
| } |