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