1 // SPDX-License-Identifier: GPL-2.0
2 #include <sys/param.h>
3 #include <sys/utsname.h>
4 #include <inttypes.h>
5 #include <stdlib.h>
6 #include <string.h>
7 #include <api/fs/fs.h>
8 #include <linux/zalloc.h>
9 #include <perf/cpumap.h>
10
11 #include "cputopo.h"
12 #include "cpumap.h"
13 #include "debug.h"
14 #include "env.h"
15 #include "pmu.h"
16 #include "pmus.h"
17
18 #define PACKAGE_CPUS_FMT \
19 "%s/devices/system/cpu/cpu%d/topology/package_cpus_list"
20 #define PACKAGE_CPUS_FMT_OLD \
21 "%s/devices/system/cpu/cpu%d/topology/core_siblings_list"
22 #define DIE_CPUS_FMT \
23 "%s/devices/system/cpu/cpu%d/topology/die_cpus_list"
24 #define CORE_CPUS_FMT \
25 "%s/devices/system/cpu/cpu%d/topology/core_cpus_list"
26 #define CORE_CPUS_FMT_OLD \
27 "%s/devices/system/cpu/cpu%d/topology/thread_siblings_list"
28 #define NODE_ONLINE_FMT \
29 "%s/devices/system/node/online"
30 #define NODE_MEMINFO_FMT \
31 "%s/devices/system/node/node%d/meminfo"
32 #define NODE_CPULIST_FMT \
33 "%s/devices/system/node/node%d/cpulist"
34
build_cpu_topology(struct cpu_topology * tp,int cpu)35 static int build_cpu_topology(struct cpu_topology *tp, int cpu)
36 {
37 FILE *fp;
38 char filename[MAXPATHLEN];
39 char *buf = NULL, *p;
40 size_t len = 0;
41 ssize_t sret;
42 u32 i = 0;
43 int ret = -1;
44
45 scnprintf(filename, MAXPATHLEN, PACKAGE_CPUS_FMT,
46 sysfs__mountpoint(), cpu);
47 if (access(filename, F_OK) == -1) {
48 scnprintf(filename, MAXPATHLEN, PACKAGE_CPUS_FMT_OLD,
49 sysfs__mountpoint(), cpu);
50 }
51 fp = fopen(filename, "r");
52 if (!fp)
53 goto try_dies;
54
55 sret = getline(&buf, &len, fp);
56 fclose(fp);
57 if (sret <= 0)
58 goto try_dies;
59
60 p = strchr(buf, '\n');
61 if (p)
62 *p = '\0';
63
64 for (i = 0; i < tp->package_cpus_lists; i++) {
65 if (!strcmp(buf, tp->package_cpus_list[i]))
66 break;
67 }
68 if (i == tp->package_cpus_lists) {
69 tp->package_cpus_list[i] = buf;
70 tp->package_cpus_lists++;
71 buf = NULL;
72 len = 0;
73 }
74 ret = 0;
75
76 try_dies:
77 if (!tp->die_cpus_list)
78 goto try_threads;
79
80 scnprintf(filename, MAXPATHLEN, DIE_CPUS_FMT,
81 sysfs__mountpoint(), cpu);
82 fp = fopen(filename, "r");
83 if (!fp)
84 goto try_threads;
85
86 sret = getline(&buf, &len, fp);
87 fclose(fp);
88 if (sret <= 0)
89 goto try_threads;
90
91 p = strchr(buf, '\n');
92 if (p)
93 *p = '\0';
94
95 for (i = 0; i < tp->die_cpus_lists; i++) {
96 if (!strcmp(buf, tp->die_cpus_list[i]))
97 break;
98 }
99 if (i == tp->die_cpus_lists) {
100 tp->die_cpus_list[i] = buf;
101 tp->die_cpus_lists++;
102 buf = NULL;
103 len = 0;
104 }
105 ret = 0;
106
107 try_threads:
108 scnprintf(filename, MAXPATHLEN, CORE_CPUS_FMT,
109 sysfs__mountpoint(), cpu);
110 if (access(filename, F_OK) == -1) {
111 scnprintf(filename, MAXPATHLEN, CORE_CPUS_FMT_OLD,
112 sysfs__mountpoint(), cpu);
113 }
114 fp = fopen(filename, "r");
115 if (!fp)
116 goto done;
117
118 if (getline(&buf, &len, fp) <= 0)
119 goto done;
120
121 p = strchr(buf, '\n');
122 if (p)
123 *p = '\0';
124
125 for (i = 0; i < tp->core_cpus_lists; i++) {
126 if (!strcmp(buf, tp->core_cpus_list[i]))
127 break;
128 }
129 if (i == tp->core_cpus_lists) {
130 tp->core_cpus_list[i] = buf;
131 tp->core_cpus_lists++;
132 buf = NULL;
133 }
134 ret = 0;
135 done:
136 if (fp)
137 fclose(fp);
138 free(buf);
139 return ret;
140 }
141
cpu_topology__delete(struct cpu_topology * tp)142 void cpu_topology__delete(struct cpu_topology *tp)
143 {
144 u32 i;
145
146 if (!tp)
147 return;
148
149 for (i = 0 ; i < tp->package_cpus_lists; i++)
150 zfree(&tp->package_cpus_list[i]);
151
152 for (i = 0 ; i < tp->die_cpus_lists; i++)
153 zfree(&tp->die_cpus_list[i]);
154
155 for (i = 0 ; i < tp->core_cpus_lists; i++)
156 zfree(&tp->core_cpus_list[i]);
157
158 free(tp);
159 }
160
cpu_topology__smt_on(const struct cpu_topology * topology)161 bool cpu_topology__smt_on(const struct cpu_topology *topology)
162 {
163 for (u32 i = 0; i < topology->core_cpus_lists; i++) {
164 const char *cpu_list = topology->core_cpus_list[i];
165
166 /*
167 * If there is a need to separate siblings in a core then SMT is
168 * enabled.
169 */
170 if (strchr(cpu_list, ',') || strchr(cpu_list, '-'))
171 return true;
172 }
173 return false;
174 }
175
cpu_topology__core_wide(const struct cpu_topology * topology,const char * user_requested_cpu_list)176 bool cpu_topology__core_wide(const struct cpu_topology *topology,
177 const char *user_requested_cpu_list)
178 {
179 struct perf_cpu_map *user_requested_cpus;
180
181 /*
182 * If user_requested_cpu_list is empty then all CPUs are recorded and so
183 * core_wide is true.
184 */
185 if (!user_requested_cpu_list)
186 return true;
187
188 user_requested_cpus = perf_cpu_map__new(user_requested_cpu_list);
189 /* Check that every user requested CPU is the complete set of SMT threads on a core. */
190 for (u32 i = 0; i < topology->core_cpus_lists; i++) {
191 const char *core_cpu_list = topology->core_cpus_list[i];
192 struct perf_cpu_map *core_cpus = perf_cpu_map__new(core_cpu_list);
193 struct perf_cpu cpu;
194 int idx;
195 bool has_first, first = true;
196
197 perf_cpu_map__for_each_cpu(cpu, idx, core_cpus) {
198 if (first) {
199 has_first = perf_cpu_map__has(user_requested_cpus, cpu);
200 first = false;
201 } else {
202 /*
203 * If the first core CPU is user requested then
204 * all subsequent CPUs in the core must be user
205 * requested too. If the first CPU isn't user
206 * requested then none of the others must be
207 * too.
208 */
209 if (perf_cpu_map__has(user_requested_cpus, cpu) != has_first) {
210 perf_cpu_map__put(core_cpus);
211 perf_cpu_map__put(user_requested_cpus);
212 return false;
213 }
214 }
215 }
216 perf_cpu_map__put(core_cpus);
217 }
218 perf_cpu_map__put(user_requested_cpus);
219 return true;
220 }
221
has_die_topology(void)222 static bool has_die_topology(void)
223 {
224 char filename[MAXPATHLEN];
225 struct utsname uts;
226
227 if (uname(&uts) < 0)
228 return false;
229
230 if (strncmp(uts.machine, "x86_64", 6) &&
231 strncmp(uts.machine, "s390x", 5))
232 return false;
233
234 scnprintf(filename, MAXPATHLEN, DIE_CPUS_FMT,
235 sysfs__mountpoint(), 0);
236 if (access(filename, F_OK) == -1)
237 return false;
238
239 return true;
240 }
241
online_topology(void)242 const struct cpu_topology *online_topology(void)
243 {
244 static const struct cpu_topology *topology;
245
246 if (!topology) {
247 topology = cpu_topology__new();
248 if (!topology) {
249 pr_err("Error creating CPU topology");
250 abort();
251 }
252 }
253 return topology;
254 }
255
cpu_topology__new(void)256 struct cpu_topology *cpu_topology__new(void)
257 {
258 struct cpu_topology *tp = NULL;
259 void *addr;
260 u32 nr, i, nr_addr;
261 size_t sz;
262 long ncpus;
263 int ret = -1;
264 struct perf_cpu_map *map;
265 bool has_die = has_die_topology();
266
267 ncpus = cpu__max_present_cpu().cpu;
268
269 /* build online CPU map */
270 map = perf_cpu_map__new_online_cpus();
271 if (map == NULL) {
272 pr_debug("failed to get system cpumap\n");
273 return NULL;
274 }
275
276 nr = (u32)(ncpus & UINT_MAX);
277
278 sz = nr * sizeof(char *);
279 if (has_die)
280 nr_addr = 3;
281 else
282 nr_addr = 2;
283 addr = calloc(1, sizeof(*tp) + nr_addr * sz);
284 if (!addr)
285 goto out_free;
286
287 tp = addr;
288 addr += sizeof(*tp);
289 tp->package_cpus_list = addr;
290 addr += sz;
291 if (has_die) {
292 tp->die_cpus_list = addr;
293 addr += sz;
294 }
295 tp->core_cpus_list = addr;
296
297 for (i = 0; i < nr; i++) {
298 if (!perf_cpu_map__has(map, (struct perf_cpu){ .cpu = i }))
299 continue;
300
301 ret = build_cpu_topology(tp, i);
302 if (ret < 0)
303 break;
304 }
305
306 out_free:
307 perf_cpu_map__put(map);
308 if (ret) {
309 cpu_topology__delete(tp);
310 tp = NULL;
311 }
312 return tp;
313 }
314
load_numa_node(struct numa_topology_node * node,int nr)315 static int load_numa_node(struct numa_topology_node *node, int nr)
316 {
317 char str[MAXPATHLEN];
318 char field[32];
319 char *buf = NULL, *p;
320 size_t len = 0;
321 int ret = -1;
322 FILE *fp;
323 u64 mem;
324
325 node->node = (u32) nr;
326
327 scnprintf(str, MAXPATHLEN, NODE_MEMINFO_FMT,
328 sysfs__mountpoint(), nr);
329 fp = fopen(str, "r");
330 if (!fp)
331 return -1;
332
333 while (getline(&buf, &len, fp) > 0) {
334 /* skip over invalid lines */
335 if (!strchr(buf, ':'))
336 continue;
337 if (sscanf(buf, "%*s %*d %31s %"PRIu64, field, &mem) != 2)
338 goto err;
339 if (!strcmp(field, "MemTotal:"))
340 node->mem_total = mem;
341 if (!strcmp(field, "MemFree:"))
342 node->mem_free = mem;
343 if (node->mem_total && node->mem_free)
344 break;
345 }
346
347 fclose(fp);
348 fp = NULL;
349
350 scnprintf(str, MAXPATHLEN, NODE_CPULIST_FMT,
351 sysfs__mountpoint(), nr);
352
353 fp = fopen(str, "r");
354 if (!fp)
355 return -1;
356
357 if (getline(&buf, &len, fp) <= 0)
358 goto err;
359
360 p = strchr(buf, '\n');
361 if (p)
362 *p = '\0';
363
364 node->cpus = buf;
365 fclose(fp);
366 return 0;
367
368 err:
369 free(buf);
370 if (fp)
371 fclose(fp);
372 return ret;
373 }
374
numa_topology__new(void)375 struct numa_topology *numa_topology__new(void)
376 {
377 struct perf_cpu_map *node_map = NULL;
378 struct numa_topology *tp = NULL;
379 char path[MAXPATHLEN];
380 char *buf = NULL;
381 size_t len = 0;
382 u32 nr, i;
383 FILE *fp;
384 char *c;
385
386 scnprintf(path, MAXPATHLEN, NODE_ONLINE_FMT,
387 sysfs__mountpoint());
388
389 fp = fopen(path, "r");
390 if (!fp)
391 return NULL;
392
393 if (getline(&buf, &len, fp) <= 0)
394 goto out;
395
396 c = strchr(buf, '\n');
397 if (c)
398 *c = '\0';
399
400 node_map = perf_cpu_map__new(buf);
401 if (!node_map)
402 goto out;
403
404 nr = (u32) perf_cpu_map__nr(node_map);
405
406 tp = zalloc(sizeof(*tp) + sizeof(tp->nodes[0])*nr);
407 if (!tp)
408 goto out;
409
410 tp->nr = nr;
411
412 for (i = 0; i < nr; i++) {
413 if (load_numa_node(&tp->nodes[i], perf_cpu_map__cpu(node_map, i).cpu)) {
414 numa_topology__delete(tp);
415 tp = NULL;
416 break;
417 }
418 }
419
420 out:
421 free(buf);
422 fclose(fp);
423 perf_cpu_map__put(node_map);
424 return tp;
425 }
426
numa_topology__delete(struct numa_topology * tp)427 void numa_topology__delete(struct numa_topology *tp)
428 {
429 u32 i;
430
431 for (i = 0; i < tp->nr; i++)
432 zfree(&tp->nodes[i].cpus);
433
434 free(tp);
435 }
436
load_hybrid_node(struct hybrid_topology_node * node,struct perf_pmu * pmu)437 static int load_hybrid_node(struct hybrid_topology_node *node,
438 struct perf_pmu *pmu)
439 {
440 char *buf = NULL, *p;
441 FILE *fp;
442 size_t len = 0;
443
444 node->pmu_name = strdup(pmu->name);
445 if (!node->pmu_name)
446 return -1;
447
448 fp = perf_pmu__open_file(pmu, "cpus");
449 if (!fp)
450 goto err;
451
452 if (getline(&buf, &len, fp) <= 0) {
453 fclose(fp);
454 goto err;
455 }
456
457 p = strchr(buf, '\n');
458 if (p)
459 *p = '\0';
460
461 fclose(fp);
462 node->cpus = buf;
463 return 0;
464
465 err:
466 zfree(&node->pmu_name);
467 free(buf);
468 return -1;
469 }
470
hybrid_topology__new(void)471 struct hybrid_topology *hybrid_topology__new(void)
472 {
473 struct perf_pmu *pmu = NULL;
474 struct hybrid_topology *tp = NULL;
475 int nr = perf_pmus__num_core_pmus(), i = 0;
476
477 if (nr <= 1)
478 return NULL;
479
480 tp = zalloc(sizeof(*tp) + sizeof(tp->nodes[0]) * nr);
481 if (!tp)
482 return NULL;
483
484 tp->nr = nr;
485 while ((pmu = perf_pmus__scan_core(pmu)) != NULL) {
486 if (load_hybrid_node(&tp->nodes[i], pmu)) {
487 hybrid_topology__delete(tp);
488 return NULL;
489 }
490 i++;
491 }
492
493 return tp;
494 }
495
hybrid_topology__delete(struct hybrid_topology * tp)496 void hybrid_topology__delete(struct hybrid_topology *tp)
497 {
498 u32 i;
499
500 for (i = 0; i < tp->nr; i++) {
501 zfree(&tp->nodes[i].pmu_name);
502 zfree(&tp->nodes[i].cpus);
503 }
504
505 free(tp);
506 }
507