xref: /linux/tools/perf/util/cputopo.c (revision 1b0975ee3bdd3eb19a47371c26fd7ef8f7f6b599)
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(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 
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(NULL);
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 
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(&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 
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(&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