acae8b36cd
With the new CPUID.1F, a new level type of CPU topology, 'die', is introduced. The 'die' information in CPU topology should be added in perf header. To be compatible with old perf.data, the patch checks the section size before reading the die information. The new info is added at the end of the cpu_topology section, the old perf tool ignores the extra data. It never reads data crossing the section boundary. The new perf tool with the patch can be used on legacy kernel. Add a new function has_die_topology() to check if die topology information is supported by kernel. The function only check X86 and CPU 0. Assuming other CPUs have same topology. Use similar method for core and socket to support die id and sibling dies string. Signed-off-by: Kan Liang <kan.liang@linux.intel.com> Reviewed-by: Jiri Olsa <jolsa@kernel.org> Cc: Andi Kleen <ak@linux.intel.com> Cc: Peter Zijlstra <peterz@infradead.org> Link: http://lkml.kernel.org/r/1559688644-106558-2-git-send-email-kan.liang@linux.intel.com Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
344 lines
5.8 KiB
C
344 lines
5.8 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
#include <sys/param.h>
|
|
#include <sys/utsname.h>
|
|
#include <inttypes.h>
|
|
#include <api/fs/fs.h>
|
|
|
|
#include "cputopo.h"
|
|
#include "cpumap.h"
|
|
#include "util.h"
|
|
#include "env.h"
|
|
|
|
#define CORE_SIB_FMT \
|
|
"%s/devices/system/cpu/cpu%d/topology/core_siblings_list"
|
|
#define DIE_SIB_FMT \
|
|
"%s/devices/system/cpu/cpu%d/topology/die_cpus_list"
|
|
#define THRD_SIB_FMT \
|
|
"%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, CORE_SIB_FMT,
|
|
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->core_sib; i++) {
|
|
if (!strcmp(buf, tp->core_siblings[i]))
|
|
break;
|
|
}
|
|
if (i == tp->core_sib) {
|
|
tp->core_siblings[i] = buf;
|
|
tp->core_sib++;
|
|
buf = NULL;
|
|
len = 0;
|
|
}
|
|
ret = 0;
|
|
|
|
try_dies:
|
|
if (!tp->die_siblings)
|
|
goto try_threads;
|
|
|
|
scnprintf(filename, MAXPATHLEN, DIE_SIB_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_sib; i++) {
|
|
if (!strcmp(buf, tp->die_siblings[i]))
|
|
break;
|
|
}
|
|
if (i == tp->die_sib) {
|
|
tp->die_siblings[i] = buf;
|
|
tp->die_sib++;
|
|
buf = NULL;
|
|
len = 0;
|
|
}
|
|
ret = 0;
|
|
|
|
try_threads:
|
|
scnprintf(filename, MAXPATHLEN, THRD_SIB_FMT,
|
|
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->thread_sib; i++) {
|
|
if (!strcmp(buf, tp->thread_siblings[i]))
|
|
break;
|
|
}
|
|
if (i == tp->thread_sib) {
|
|
tp->thread_siblings[i] = buf;
|
|
tp->thread_sib++;
|
|
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->core_sib; i++)
|
|
zfree(&tp->core_siblings[i]);
|
|
|
|
if (tp->die_sib) {
|
|
for (i = 0 ; i < tp->die_sib; i++)
|
|
zfree(&tp->die_siblings[i]);
|
|
}
|
|
|
|
for (i = 0 ; i < tp->thread_sib; i++)
|
|
zfree(&tp->thread_siblings[i]);
|
|
|
|
free(tp);
|
|
}
|
|
|
|
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))
|
|
return false;
|
|
|
|
scnprintf(filename, MAXPATHLEN, DIE_SIB_FMT,
|
|
sysfs__mountpoint(), 0);
|
|
if (access(filename, F_OK) == -1)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
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 cpu_map *map;
|
|
bool has_die = has_die_topology();
|
|
|
|
ncpus = cpu__max_present_cpu();
|
|
|
|
/* build online CPU map */
|
|
map = cpu_map__new(NULL);
|
|
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->core_siblings = addr;
|
|
addr += sz;
|
|
if (has_die) {
|
|
tp->die_siblings = addr;
|
|
addr += sz;
|
|
}
|
|
tp->thread_siblings = addr;
|
|
|
|
for (i = 0; i < nr; i++) {
|
|
if (!cpu_map__has(map, i))
|
|
continue;
|
|
|
|
ret = build_cpu_topology(tp, i);
|
|
if (ret < 0)
|
|
break;
|
|
}
|
|
|
|
out_free:
|
|
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 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 = cpu_map__new(buf);
|
|
if (!node_map)
|
|
goto out;
|
|
|
|
nr = (u32) node_map->nr;
|
|
|
|
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], node_map->map[i])) {
|
|
numa_topology__delete(tp);
|
|
tp = NULL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
out:
|
|
free(buf);
|
|
fclose(fp);
|
|
cpu_map__put(node_map);
|
|
return tp;
|
|
}
|
|
|
|
void numa_topology__delete(struct numa_topology *tp)
|
|
{
|
|
u32 i;
|
|
|
|
for (i = 0; i < tp->nr; i++)
|
|
free(tp->nodes[i].cpus);
|
|
|
|
free(tp);
|
|
}
|