root/tools/perf/util/cputopo.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);
}