507 lines
9.6 KiB
C
507 lines
9.6 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
#include <sys/param.h>
|
|
#include <sys/utsname.h>
|
|
#include <inttypes.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <api/fs/fs.h>
|
|
#include <linux/zalloc.h>
|
|
#include <perf/cpumap.h>
|
|
|
|
#include "cputopo.h"
|
|
#include "cpumap.h"
|
|
#include "debug.h"
|
|
#include "env.h"
|
|
#include "pmu.h"
|
|
#include "pmus.h"
|
|
|
|
#define PACKAGE_CPUS_FMT \
|
|
"%s/devices/system/cpu/cpu%d/topology/package_cpus_list"
|
|
#define PACKAGE_CPUS_FMT_OLD \
|
|
"%s/devices/system/cpu/cpu%d/topology/core_siblings_list"
|
|
#define DIE_CPUS_FMT \
|
|
"%s/devices/system/cpu/cpu%d/topology/die_cpus_list"
|
|
#define CORE_CPUS_FMT \
|
|
"%s/devices/system/cpu/cpu%d/topology/core_cpus_list"
|
|
#define CORE_CPUS_FMT_OLD \
|
|
"%s/devices/system/cpu/cpu%d/topology/thread_siblings_list"
|
|
#define NODE_ONLINE_FMT \
|
|
"%s/devices/system/node/online"
|
|
#define NODE_MEMINFO_FMT \
|
|
"%s/devices/system/node/node%d/meminfo"
|
|
#define NODE_CPULIST_FMT \
|
|
"%s/devices/system/node/node%d/cpulist"
|
|
|
|
static int build_cpu_topology(struct cpu_topology *tp, int cpu)
|
|
{
|
|
FILE *fp;
|
|
char filename[MAXPATHLEN];
|
|
char *buf = NULL, *p;
|
|
size_t len = 0;
|
|
ssize_t sret;
|
|
u32 i = 0;
|
|
int ret = -1;
|
|
|
|
scnprintf(filename, MAXPATHLEN, PACKAGE_CPUS_FMT,
|
|
sysfs__mountpoint(), cpu);
|
|
if (access(filename, F_OK) == -1) {
|
|
scnprintf(filename, MAXPATHLEN, PACKAGE_CPUS_FMT_OLD,
|
|
sysfs__mountpoint(), cpu);
|
|
}
|
|
fp = fopen(filename, "r");
|
|
if (!fp)
|
|
goto try_dies;
|
|
|
|
sret = getline(&buf, &len, fp);
|
|
fclose(fp);
|
|
if (sret <= 0)
|
|
goto try_dies;
|
|
|
|
p = strchr(buf, '\n');
|
|
if (p)
|
|
*p = '\0';
|
|
|
|
for (i = 0; i < tp->package_cpus_lists; i++) {
|
|
if (!strcmp(buf, tp->package_cpus_list[i]))
|
|
break;
|
|
}
|
|
if (i == tp->package_cpus_lists) {
|
|
tp->package_cpus_list[i] = buf;
|
|
tp->package_cpus_lists++;
|
|
buf = NULL;
|
|
len = 0;
|
|
}
|
|
ret = 0;
|
|
|
|
try_dies:
|
|
if (!tp->die_cpus_list)
|
|
goto try_threads;
|
|
|
|
scnprintf(filename, MAXPATHLEN, DIE_CPUS_FMT,
|
|
sysfs__mountpoint(), cpu);
|
|
fp = fopen(filename, "r");
|
|
if (!fp)
|
|
goto try_threads;
|
|
|
|
sret = getline(&buf, &len, fp);
|
|
fclose(fp);
|
|
if (sret <= 0)
|
|
goto try_threads;
|
|
|
|
p = strchr(buf, '\n');
|
|
if (p)
|
|
*p = '\0';
|
|
|
|
for (i = 0; i < tp->die_cpus_lists; i++) {
|
|
if (!strcmp(buf, tp->die_cpus_list[i]))
|
|
break;
|
|
}
|
|
if (i == tp->die_cpus_lists) {
|
|
tp->die_cpus_list[i] = buf;
|
|
tp->die_cpus_lists++;
|
|
buf = NULL;
|
|
len = 0;
|
|
}
|
|
ret = 0;
|
|
|
|
try_threads:
|
|
scnprintf(filename, MAXPATHLEN, CORE_CPUS_FMT,
|
|
sysfs__mountpoint(), cpu);
|
|
if (access(filename, F_OK) == -1) {
|
|
scnprintf(filename, MAXPATHLEN, CORE_CPUS_FMT_OLD,
|
|
sysfs__mountpoint(), cpu);
|
|
}
|
|
fp = fopen(filename, "r");
|
|
if (!fp)
|
|
goto done;
|
|
|
|
if (getline(&buf, &len, fp) <= 0)
|
|
goto done;
|
|
|
|
p = strchr(buf, '\n');
|
|
if (p)
|
|
*p = '\0';
|
|
|
|
for (i = 0; i < tp->core_cpus_lists; i++) {
|
|
if (!strcmp(buf, tp->core_cpus_list[i]))
|
|
break;
|
|
}
|
|
if (i == tp->core_cpus_lists) {
|
|
tp->core_cpus_list[i] = buf;
|
|
tp->core_cpus_lists++;
|
|
buf = NULL;
|
|
}
|
|
ret = 0;
|
|
done:
|
|
if (fp)
|
|
fclose(fp);
|
|
free(buf);
|
|
return ret;
|
|
}
|
|
|
|
void cpu_topology__delete(struct cpu_topology *tp)
|
|
{
|
|
u32 i;
|
|
|
|
if (!tp)
|
|
return;
|
|
|
|
for (i = 0 ; i < tp->package_cpus_lists; i++)
|
|
zfree(&tp->package_cpus_list[i]);
|
|
|
|
for (i = 0 ; i < tp->die_cpus_lists; i++)
|
|
zfree(&tp->die_cpus_list[i]);
|
|
|
|
for (i = 0 ; i < tp->core_cpus_lists; i++)
|
|
zfree(&tp->core_cpus_list[i]);
|
|
|
|
free(tp);
|
|
}
|
|
|
|
bool cpu_topology__smt_on(const struct cpu_topology *topology)
|
|
{
|
|
for (u32 i = 0; i < topology->core_cpus_lists; i++) {
|
|
const char *cpu_list = topology->core_cpus_list[i];
|
|
|
|
/*
|
|
* If there is a need to separate siblings in a core then SMT is
|
|
* enabled.
|
|
*/
|
|
if (strchr(cpu_list, ',') || strchr(cpu_list, '-'))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool cpu_topology__core_wide(const struct cpu_topology *topology,
|
|
const char *user_requested_cpu_list)
|
|
{
|
|
struct perf_cpu_map *user_requested_cpus;
|
|
|
|
/*
|
|
* If user_requested_cpu_list is empty then all CPUs are recorded and so
|
|
* core_wide is true.
|
|
*/
|
|
if (!user_requested_cpu_list)
|
|
return true;
|
|
|
|
user_requested_cpus = perf_cpu_map__new(user_requested_cpu_list);
|
|
/* Check that every user requested CPU is the complete set of SMT threads on a core. */
|
|
for (u32 i = 0; i < topology->core_cpus_lists; i++) {
|
|
const char *core_cpu_list = topology->core_cpus_list[i];
|
|
struct perf_cpu_map *core_cpus = perf_cpu_map__new(core_cpu_list);
|
|
struct perf_cpu cpu;
|
|
int idx;
|
|
bool has_first, first = true;
|
|
|
|
perf_cpu_map__for_each_cpu(cpu, idx, core_cpus) {
|
|
if (first) {
|
|
has_first = perf_cpu_map__has(user_requested_cpus, cpu);
|
|
first = false;
|
|
} else {
|
|
/*
|
|
* If the first core CPU is user requested then
|
|
* all subsequent CPUs in the core must be user
|
|
* requested too. If the first CPU isn't user
|
|
* requested then none of the others must be
|
|
* too.
|
|
*/
|
|
if (perf_cpu_map__has(user_requested_cpus, cpu) != has_first) {
|
|
perf_cpu_map__put(core_cpus);
|
|
perf_cpu_map__put(user_requested_cpus);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
perf_cpu_map__put(core_cpus);
|
|
}
|
|
perf_cpu_map__put(user_requested_cpus);
|
|
return true;
|
|
}
|
|
|
|
static bool has_die_topology(void)
|
|
{
|
|
char filename[MAXPATHLEN];
|
|
struct utsname uts;
|
|
|
|
if (uname(&uts) < 0)
|
|
return false;
|
|
|
|
if (strncmp(uts.machine, "x86_64", 6) &&
|
|
strncmp(uts.machine, "s390x", 5))
|
|
return false;
|
|
|
|
scnprintf(filename, MAXPATHLEN, DIE_CPUS_FMT,
|
|
sysfs__mountpoint(), 0);
|
|
if (access(filename, F_OK) == -1)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
const struct cpu_topology *online_topology(void)
|
|
{
|
|
static const struct cpu_topology *topology;
|
|
|
|
if (!topology) {
|
|
topology = cpu_topology__new();
|
|
if (!topology) {
|
|
pr_err("Error creating CPU topology");
|
|
abort();
|
|
}
|
|
}
|
|
return topology;
|
|
}
|
|
|
|
struct cpu_topology *cpu_topology__new(void)
|
|
{
|
|
struct cpu_topology *tp = NULL;
|
|
void *addr;
|
|
u32 nr, i, nr_addr;
|
|
size_t sz;
|
|
long ncpus;
|
|
int ret = -1;
|
|
struct perf_cpu_map *map;
|
|
bool has_die = has_die_topology();
|
|
|
|
ncpus = cpu__max_present_cpu().cpu;
|
|
|
|
/* build online CPU map */
|
|
map = perf_cpu_map__new_online_cpus();
|
|
if (map == NULL) {
|
|
pr_debug("failed to get system cpumap\n");
|
|
return NULL;
|
|
}
|
|
|
|
nr = (u32)(ncpus & UINT_MAX);
|
|
|
|
sz = nr * sizeof(char *);
|
|
if (has_die)
|
|
nr_addr = 3;
|
|
else
|
|
nr_addr = 2;
|
|
addr = calloc(1, sizeof(*tp) + nr_addr * sz);
|
|
if (!addr)
|
|
goto out_free;
|
|
|
|
tp = addr;
|
|
addr += sizeof(*tp);
|
|
tp->package_cpus_list = addr;
|
|
addr += sz;
|
|
if (has_die) {
|
|
tp->die_cpus_list = addr;
|
|
addr += sz;
|
|
}
|
|
tp->core_cpus_list = addr;
|
|
|
|
for (i = 0; i < nr; i++) {
|
|
if (!perf_cpu_map__has(map, (struct perf_cpu){ .cpu = i }))
|
|
continue;
|
|
|
|
ret = build_cpu_topology(tp, i);
|
|
if (ret < 0)
|
|
break;
|
|
}
|
|
|
|
out_free:
|
|
perf_cpu_map__put(map);
|
|
if (ret) {
|
|
cpu_topology__delete(tp);
|
|
tp = NULL;
|
|
}
|
|
return tp;
|
|
}
|
|
|
|
static int load_numa_node(struct numa_topology_node *node, int nr)
|
|
{
|
|
char str[MAXPATHLEN];
|
|
char field[32];
|
|
char *buf = NULL, *p;
|
|
size_t len = 0;
|
|
int ret = -1;
|
|
FILE *fp;
|
|
u64 mem;
|
|
|
|
node->node = (u32) nr;
|
|
|
|
scnprintf(str, MAXPATHLEN, NODE_MEMINFO_FMT,
|
|
sysfs__mountpoint(), nr);
|
|
fp = fopen(str, "r");
|
|
if (!fp)
|
|
return -1;
|
|
|
|
while (getline(&buf, &len, fp) > 0) {
|
|
/* skip over invalid lines */
|
|
if (!strchr(buf, ':'))
|
|
continue;
|
|
if (sscanf(buf, "%*s %*d %31s %"PRIu64, field, &mem) != 2)
|
|
goto err;
|
|
if (!strcmp(field, "MemTotal:"))
|
|
node->mem_total = mem;
|
|
if (!strcmp(field, "MemFree:"))
|
|
node->mem_free = mem;
|
|
if (node->mem_total && node->mem_free)
|
|
break;
|
|
}
|
|
|
|
fclose(fp);
|
|
fp = NULL;
|
|
|
|
scnprintf(str, MAXPATHLEN, NODE_CPULIST_FMT,
|
|
sysfs__mountpoint(), nr);
|
|
|
|
fp = fopen(str, "r");
|
|
if (!fp)
|
|
return -1;
|
|
|
|
if (getline(&buf, &len, fp) <= 0)
|
|
goto err;
|
|
|
|
p = strchr(buf, '\n');
|
|
if (p)
|
|
*p = '\0';
|
|
|
|
node->cpus = buf;
|
|
fclose(fp);
|
|
return 0;
|
|
|
|
err:
|
|
free(buf);
|
|
if (fp)
|
|
fclose(fp);
|
|
return ret;
|
|
}
|
|
|
|
struct numa_topology *numa_topology__new(void)
|
|
{
|
|
struct perf_cpu_map *node_map = NULL;
|
|
struct numa_topology *tp = NULL;
|
|
char path[MAXPATHLEN];
|
|
char *buf = NULL;
|
|
size_t len = 0;
|
|
u32 nr, i;
|
|
FILE *fp;
|
|
char *c;
|
|
|
|
scnprintf(path, MAXPATHLEN, NODE_ONLINE_FMT,
|
|
sysfs__mountpoint());
|
|
|
|
fp = fopen(path, "r");
|
|
if (!fp)
|
|
return NULL;
|
|
|
|
if (getline(&buf, &len, fp) <= 0)
|
|
goto out;
|
|
|
|
c = strchr(buf, '\n');
|
|
if (c)
|
|
*c = '\0';
|
|
|
|
node_map = perf_cpu_map__new(buf);
|
|
if (!node_map)
|
|
goto out;
|
|
|
|
nr = (u32) perf_cpu_map__nr(node_map);
|
|
|
|
tp = zalloc(sizeof(*tp) + sizeof(tp->nodes[0])*nr);
|
|
if (!tp)
|
|
goto out;
|
|
|
|
tp->nr = nr;
|
|
|
|
for (i = 0; i < nr; i++) {
|
|
if (load_numa_node(&tp->nodes[i], perf_cpu_map__cpu(node_map, i).cpu)) {
|
|
numa_topology__delete(tp);
|
|
tp = NULL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
out:
|
|
free(buf);
|
|
fclose(fp);
|
|
perf_cpu_map__put(node_map);
|
|
return tp;
|
|
}
|
|
|
|
void numa_topology__delete(struct numa_topology *tp)
|
|
{
|
|
u32 i;
|
|
|
|
for (i = 0; i < tp->nr; i++)
|
|
zfree(&tp->nodes[i].cpus);
|
|
|
|
free(tp);
|
|
}
|
|
|
|
static int load_hybrid_node(struct hybrid_topology_node *node,
|
|
struct perf_pmu *pmu)
|
|
{
|
|
char *buf = NULL, *p;
|
|
FILE *fp;
|
|
size_t len = 0;
|
|
|
|
node->pmu_name = strdup(pmu->name);
|
|
if (!node->pmu_name)
|
|
return -1;
|
|
|
|
fp = perf_pmu__open_file(pmu, "cpus");
|
|
if (!fp)
|
|
goto err;
|
|
|
|
if (getline(&buf, &len, fp) <= 0) {
|
|
fclose(fp);
|
|
goto err;
|
|
}
|
|
|
|
p = strchr(buf, '\n');
|
|
if (p)
|
|
*p = '\0';
|
|
|
|
fclose(fp);
|
|
node->cpus = buf;
|
|
return 0;
|
|
|
|
err:
|
|
zfree(&node->pmu_name);
|
|
free(buf);
|
|
return -1;
|
|
}
|
|
|
|
struct hybrid_topology *hybrid_topology__new(void)
|
|
{
|
|
struct perf_pmu *pmu = NULL;
|
|
struct hybrid_topology *tp = NULL;
|
|
int nr = perf_pmus__num_core_pmus(), i = 0;
|
|
|
|
if (nr <= 1)
|
|
return NULL;
|
|
|
|
tp = zalloc(sizeof(*tp) + sizeof(tp->nodes[0]) * nr);
|
|
if (!tp)
|
|
return NULL;
|
|
|
|
tp->nr = nr;
|
|
while ((pmu = perf_pmus__scan_core(pmu)) != NULL) {
|
|
if (load_hybrid_node(&tp->nodes[i], pmu)) {
|
|
hybrid_topology__delete(tp);
|
|
return NULL;
|
|
}
|
|
i++;
|
|
}
|
|
|
|
return tp;
|
|
}
|
|
|
|
void hybrid_topology__delete(struct hybrid_topology *tp)
|
|
{
|
|
u32 i;
|
|
|
|
for (i = 0; i < tp->nr; i++) {
|
|
zfree(&tp->nodes[i].pmu_name);
|
|
zfree(&tp->nodes[i].cpus);
|
|
}
|
|
|
|
free(tp);
|
|
}
|