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 | |
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(buf: filename, size: MAXPATHLEN, PACKAGE_CPUS_FMT, |
46 | sysfs__mountpoint(), cpu); |
47 | if (access(filename, F_OK) == -1) { |
48 | scnprintf(buf: filename, size: 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(buf: filename, size: 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(buf: filename, size: MAXPATHLEN, CORE_CPUS_FMT, |
109 | sysfs__mountpoint(), cpu); |
110 | if (access(filename, F_OK) == -1) { |
111 | scnprintf(buf: filename, size: 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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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, cpu: 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 | |
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 | |
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(node: &tp->nodes[i], nr: 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 | |
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 | |
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 | |
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(node: &tp->nodes[i], pmu)) { |
487 | hybrid_topology__delete(tp); |
488 | return NULL; |
489 | } |
490 | i++; |
491 | } |
492 | |
493 | return tp; |
494 | } |
495 | |
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 | |