// 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(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->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); }