506 lines
11 KiB
C
506 lines
11 KiB
C
// 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;
|
|
}
|