| // SPDX-License-Identifier: GPL-2.0-only |
| #include "cgroup.h" |
| #include "counts.h" |
| #include "cputopo.h" |
| #include "evsel.h" |
| #include "pmu.h" |
| #include "print-events.h" |
| #include "smt.h" |
| #include "time-utils.h" |
| #include "tool_pmu.h" |
| #include "tsc.h" |
| #include <api/fs/fs.h> |
| #include <api/io.h> |
| #include <internal/threadmap.h> |
| #include <perf/threadmap.h> |
| #include <fcntl.h> |
| #include <strings.h> |
| |
| static const char *const tool_pmu__event_names[TOOL_PMU__EVENT_MAX] = { |
| NULL, |
| "duration_time", |
| "user_time", |
| "system_time", |
| "has_pmem", |
| "num_cores", |
| "num_cpus", |
| "num_cpus_online", |
| "num_dies", |
| "num_packages", |
| "slots", |
| "smt_on", |
| "system_tsc_freq", |
| }; |
| |
| bool tool_pmu__skip_event(const char *name __maybe_unused) |
| { |
| #if !defined(__aarch64__) |
| /* The slots event should only appear on arm64. */ |
| if (strcasecmp(name, "slots") == 0) |
| return true; |
| #endif |
| #if !defined(__i386__) && !defined(__x86_64__) |
| /* The system_tsc_freq event should only appear on x86. */ |
| if (strcasecmp(name, "system_tsc_freq") == 0) |
| return true; |
| #endif |
| return false; |
| } |
| |
| int tool_pmu__num_skip_events(void) |
| { |
| int num = 0; |
| |
| #if !defined(__aarch64__) |
| num++; |
| #endif |
| #if !defined(__i386__) && !defined(__x86_64__) |
| num++; |
| #endif |
| return num; |
| } |
| |
| const char *tool_pmu__event_to_str(enum tool_pmu_event ev) |
| { |
| if (ev > TOOL_PMU__EVENT_NONE && ev < TOOL_PMU__EVENT_MAX) |
| return tool_pmu__event_names[ev]; |
| |
| return NULL; |
| } |
| |
| enum tool_pmu_event tool_pmu__str_to_event(const char *str) |
| { |
| int i; |
| |
| if (tool_pmu__skip_event(str)) |
| return TOOL_PMU__EVENT_NONE; |
| |
| tool_pmu__for_each_event(i) { |
| if (!strcasecmp(str, tool_pmu__event_names[i])) |
| return i; |
| } |
| return TOOL_PMU__EVENT_NONE; |
| } |
| |
| bool perf_pmu__is_tool(const struct perf_pmu *pmu) |
| { |
| return pmu && pmu->type == PERF_PMU_TYPE_TOOL; |
| } |
| |
| bool evsel__is_tool(const struct evsel *evsel) |
| { |
| return perf_pmu__is_tool(evsel->pmu); |
| } |
| |
| enum tool_pmu_event evsel__tool_event(const struct evsel *evsel) |
| { |
| if (!evsel__is_tool(evsel)) |
| return TOOL_PMU__EVENT_NONE; |
| |
| return (enum tool_pmu_event)evsel->core.attr.config; |
| } |
| |
| const char *evsel__tool_pmu_event_name(const struct evsel *evsel) |
| { |
| return tool_pmu__event_to_str(evsel->core.attr.config); |
| } |
| |
| static bool read_until_char(struct io *io, char e) |
| { |
| int c; |
| |
| do { |
| c = io__get_char(io); |
| if (c == -1) |
| return false; |
| } while (c != e); |
| return true; |
| } |
| |
| static int read_stat_field(int fd, struct perf_cpu cpu, int field, __u64 *val) |
| { |
| char buf[256]; |
| struct io io; |
| int i; |
| |
| io__init(&io, fd, buf, sizeof(buf)); |
| |
| /* Skip lines to relevant CPU. */ |
| for (i = -1; i < cpu.cpu; i++) { |
| if (!read_until_char(&io, '\n')) |
| return -EINVAL; |
| } |
| /* Skip to "cpu". */ |
| if (io__get_char(&io) != 'c') return -EINVAL; |
| if (io__get_char(&io) != 'p') return -EINVAL; |
| if (io__get_char(&io) != 'u') return -EINVAL; |
| |
| /* Skip N of cpuN. */ |
| if (!read_until_char(&io, ' ')) |
| return -EINVAL; |
| |
| i = 1; |
| while (true) { |
| if (io__get_dec(&io, val) != ' ') |
| break; |
| if (field == i) |
| return 0; |
| i++; |
| } |
| return -EINVAL; |
| } |
| |
| static int read_pid_stat_field(int fd, int field, __u64 *val) |
| { |
| char buf[256]; |
| struct io io; |
| int c, i; |
| |
| io__init(&io, fd, buf, sizeof(buf)); |
| if (io__get_dec(&io, val) != ' ') |
| return -EINVAL; |
| if (field == 1) |
| return 0; |
| |
| /* Skip comm. */ |
| if (io__get_char(&io) != '(' || !read_until_char(&io, ')')) |
| return -EINVAL; |
| if (field == 2) |
| return -EINVAL; /* String can't be returned. */ |
| |
| /* Skip state */ |
| if (io__get_char(&io) != ' ' || io__get_char(&io) == -1) |
| return -EINVAL; |
| if (field == 3) |
| return -EINVAL; /* String can't be returned. */ |
| |
| /* Loop over numeric fields*/ |
| if (io__get_char(&io) != ' ') |
| return -EINVAL; |
| |
| i = 4; |
| while (true) { |
| c = io__get_dec(&io, val); |
| if (c == -1) |
| return -EINVAL; |
| if (c == -2) { |
| /* Assume a -ve was read */ |
| c = io__get_dec(&io, val); |
| *val *= -1; |
| } |
| if (c != ' ') |
| return -EINVAL; |
| if (field == i) |
| return 0; |
| i++; |
| } |
| return -EINVAL; |
| } |
| |
| int evsel__tool_pmu_prepare_open(struct evsel *evsel, |
| struct perf_cpu_map *cpus, |
| int nthreads) |
| { |
| if ((evsel__tool_event(evsel) == TOOL_PMU__EVENT_SYSTEM_TIME || |
| evsel__tool_event(evsel) == TOOL_PMU__EVENT_USER_TIME) && |
| !evsel->start_times) { |
| evsel->start_times = xyarray__new(perf_cpu_map__nr(cpus), |
| nthreads, |
| sizeof(__u64)); |
| if (!evsel->start_times) |
| return -ENOMEM; |
| } |
| return 0; |
| } |
| |
| #define FD(e, x, y) (*(int *)xyarray__entry(e->core.fd, x, y)) |
| |
| int evsel__tool_pmu_open(struct evsel *evsel, |
| struct perf_thread_map *threads, |
| int start_cpu_map_idx, int end_cpu_map_idx) |
| { |
| enum tool_pmu_event ev = evsel__tool_event(evsel); |
| int pid = -1, idx = 0, thread = 0, nthreads, err = 0, old_errno; |
| |
| if (ev == TOOL_PMU__EVENT_NUM_CPUS) |
| return 0; |
| |
| if (ev == TOOL_PMU__EVENT_DURATION_TIME) { |
| if (evsel->core.attr.sample_period) /* no sampling */ |
| return -EINVAL; |
| evsel->start_time = rdclock(); |
| return 0; |
| } |
| |
| if (evsel->cgrp) |
| pid = evsel->cgrp->fd; |
| |
| nthreads = perf_thread_map__nr(threads); |
| for (idx = start_cpu_map_idx; idx < end_cpu_map_idx; idx++) { |
| for (thread = 0; thread < nthreads; thread++) { |
| if (thread >= nthreads) |
| break; |
| |
| if (!evsel->cgrp && !evsel->core.system_wide) |
| pid = perf_thread_map__pid(threads, thread); |
| |
| if (ev == TOOL_PMU__EVENT_USER_TIME || ev == TOOL_PMU__EVENT_SYSTEM_TIME) { |
| bool system = ev == TOOL_PMU__EVENT_SYSTEM_TIME; |
| __u64 *start_time = NULL; |
| int fd; |
| |
| if (evsel->core.attr.sample_period) { |
| /* no sampling */ |
| err = -EINVAL; |
| goto out_close; |
| } |
| if (pid > -1) { |
| char buf[64]; |
| |
| snprintf(buf, sizeof(buf), "/proc/%d/stat", pid); |
| fd = open(buf, O_RDONLY); |
| evsel->pid_stat = true; |
| } else { |
| fd = open("/proc/stat", O_RDONLY); |
| } |
| FD(evsel, idx, thread) = fd; |
| if (fd < 0) { |
| err = -errno; |
| goto out_close; |
| } |
| start_time = xyarray__entry(evsel->start_times, idx, thread); |
| if (pid > -1) { |
| err = read_pid_stat_field(fd, system ? 15 : 14, |
| start_time); |
| } else { |
| struct perf_cpu cpu; |
| |
| cpu = perf_cpu_map__cpu(evsel->core.cpus, idx); |
| err = read_stat_field(fd, cpu, system ? 3 : 1, |
| start_time); |
| } |
| if (err) |
| goto out_close; |
| } |
| |
| } |
| } |
| return 0; |
| out_close: |
| if (err) |
| threads->err_thread = thread; |
| |
| old_errno = errno; |
| do { |
| while (--thread >= 0) { |
| if (FD(evsel, idx, thread) >= 0) |
| close(FD(evsel, idx, thread)); |
| FD(evsel, idx, thread) = -1; |
| } |
| thread = nthreads; |
| } while (--idx >= 0); |
| errno = old_errno; |
| return err; |
| } |
| |
| #if !defined(__i386__) && !defined(__x86_64__) |
| u64 arch_get_tsc_freq(void) |
| { |
| return 0; |
| } |
| #endif |
| |
| #if !defined(__aarch64__) |
| u64 tool_pmu__cpu_slots_per_cycle(void) |
| { |
| return 0; |
| } |
| #endif |
| |
| static bool has_pmem(void) |
| { |
| static bool has_pmem, cached; |
| const char *sysfs = sysfs__mountpoint(); |
| char path[PATH_MAX]; |
| |
| if (!cached) { |
| snprintf(path, sizeof(path), "%s/firmware/acpi/tables/NFIT", sysfs); |
| has_pmem = access(path, F_OK) == 0; |
| cached = true; |
| } |
| return has_pmem; |
| } |
| |
| bool tool_pmu__read_event(enum tool_pmu_event ev, u64 *result) |
| { |
| const struct cpu_topology *topology; |
| |
| switch (ev) { |
| case TOOL_PMU__EVENT_HAS_PMEM: |
| *result = has_pmem() ? 1 : 0; |
| return true; |
| |
| case TOOL_PMU__EVENT_NUM_CORES: |
| topology = online_topology(); |
| *result = topology->core_cpus_lists; |
| return true; |
| |
| case TOOL_PMU__EVENT_NUM_CPUS: |
| *result = cpu__max_present_cpu().cpu; |
| return true; |
| |
| case TOOL_PMU__EVENT_NUM_CPUS_ONLINE: { |
| struct perf_cpu_map *online = cpu_map__online(); |
| |
| if (online) { |
| *result = perf_cpu_map__nr(online); |
| return true; |
| } |
| return false; |
| } |
| case TOOL_PMU__EVENT_NUM_DIES: |
| topology = online_topology(); |
| *result = topology->die_cpus_lists; |
| return true; |
| |
| case TOOL_PMU__EVENT_NUM_PACKAGES: |
| topology = online_topology(); |
| *result = topology->package_cpus_lists; |
| return true; |
| |
| case TOOL_PMU__EVENT_SLOTS: |
| *result = tool_pmu__cpu_slots_per_cycle(); |
| return *result ? true : false; |
| |
| case TOOL_PMU__EVENT_SMT_ON: |
| *result = smt_on() ? 1 : 0; |
| return true; |
| |
| case TOOL_PMU__EVENT_SYSTEM_TSC_FREQ: |
| *result = arch_get_tsc_freq(); |
| return true; |
| |
| case TOOL_PMU__EVENT_NONE: |
| case TOOL_PMU__EVENT_DURATION_TIME: |
| case TOOL_PMU__EVENT_USER_TIME: |
| case TOOL_PMU__EVENT_SYSTEM_TIME: |
| case TOOL_PMU__EVENT_MAX: |
| default: |
| return false; |
| } |
| } |
| |
| int evsel__tool_pmu_read(struct evsel *evsel, int cpu_map_idx, int thread) |
| { |
| __u64 *start_time, cur_time, delta_start; |
| u64 val; |
| int fd, err = 0; |
| struct perf_counts_values *count, *old_count = NULL; |
| bool adjust = false; |
| enum tool_pmu_event ev = evsel__tool_event(evsel); |
| |
| count = perf_counts(evsel->counts, cpu_map_idx, thread); |
| |
| switch (ev) { |
| case TOOL_PMU__EVENT_HAS_PMEM: |
| case TOOL_PMU__EVENT_NUM_CORES: |
| case TOOL_PMU__EVENT_NUM_CPUS: |
| case TOOL_PMU__EVENT_NUM_CPUS_ONLINE: |
| case TOOL_PMU__EVENT_NUM_DIES: |
| case TOOL_PMU__EVENT_NUM_PACKAGES: |
| case TOOL_PMU__EVENT_SLOTS: |
| case TOOL_PMU__EVENT_SMT_ON: |
| case TOOL_PMU__EVENT_SYSTEM_TSC_FREQ: |
| if (evsel->prev_raw_counts) |
| old_count = perf_counts(evsel->prev_raw_counts, cpu_map_idx, thread); |
| val = 0; |
| if (cpu_map_idx == 0 && thread == 0) { |
| if (!tool_pmu__read_event(ev, &val)) { |
| count->lost++; |
| val = 0; |
| } |
| } |
| if (old_count) { |
| count->val = old_count->val + val; |
| count->run = old_count->run + 1; |
| count->ena = old_count->ena + 1; |
| } else { |
| count->val = val; |
| count->run++; |
| count->ena++; |
| } |
| return 0; |
| case TOOL_PMU__EVENT_DURATION_TIME: |
| /* |
| * Pretend duration_time is only on the first CPU and thread, or |
| * else aggregation will scale duration_time by the number of |
| * CPUs/threads. |
| */ |
| start_time = &evsel->start_time; |
| if (cpu_map_idx == 0 && thread == 0) |
| cur_time = rdclock(); |
| else |
| cur_time = *start_time; |
| break; |
| case TOOL_PMU__EVENT_USER_TIME: |
| case TOOL_PMU__EVENT_SYSTEM_TIME: { |
| bool system = evsel__tool_event(evsel) == TOOL_PMU__EVENT_SYSTEM_TIME; |
| |
| start_time = xyarray__entry(evsel->start_times, cpu_map_idx, thread); |
| fd = FD(evsel, cpu_map_idx, thread); |
| lseek(fd, SEEK_SET, 0); |
| if (evsel->pid_stat) { |
| /* The event exists solely on 1 CPU. */ |
| if (cpu_map_idx == 0) |
| err = read_pid_stat_field(fd, system ? 15 : 14, &cur_time); |
| else |
| cur_time = 0; |
| } else { |
| /* The event is for all threads. */ |
| if (thread == 0) { |
| struct perf_cpu cpu = perf_cpu_map__cpu(evsel->core.cpus, |
| cpu_map_idx); |
| |
| err = read_stat_field(fd, cpu, system ? 3 : 1, &cur_time); |
| } else { |
| cur_time = 0; |
| } |
| } |
| adjust = true; |
| break; |
| } |
| case TOOL_PMU__EVENT_NONE: |
| case TOOL_PMU__EVENT_MAX: |
| default: |
| err = -EINVAL; |
| } |
| if (err) |
| return err; |
| |
| delta_start = cur_time - *start_time; |
| if (adjust) { |
| __u64 ticks_per_sec = sysconf(_SC_CLK_TCK); |
| |
| delta_start *= 1000000000 / ticks_per_sec; |
| } |
| count->val = delta_start; |
| count->ena = count->run = delta_start; |
| count->lost = 0; |
| return 0; |
| } |
| |
| struct perf_pmu *perf_pmus__tool_pmu(void) |
| { |
| static struct perf_pmu tool = { |
| .name = "tool", |
| .type = PERF_PMU_TYPE_TOOL, |
| .aliases = LIST_HEAD_INIT(tool.aliases), |
| .caps = LIST_HEAD_INIT(tool.caps), |
| .format = LIST_HEAD_INIT(tool.format), |
| }; |
| if (!tool.events_table) |
| tool.events_table = find_core_events_table("common", "common"); |
| |
| return &tool; |
| } |