xref: /openbmc/linux/tools/perf/util/cputopo.c (revision f71a261a)
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 static bool has_die_topology(void)
161 {
162 	char filename[MAXPATHLEN];
163 	struct utsname uts;
164 
165 	if (uname(&uts) < 0)
166 		return false;
167 
168 	if (strncmp(uts.machine, "x86_64", 6) &&
169 	    strncmp(uts.machine, "s390x", 5))
170 		return false;
171 
172 	scnprintf(filename, MAXPATHLEN, DIE_CPUS_FMT,
173 		  sysfs__mountpoint(), 0);
174 	if (access(filename, F_OK) == -1)
175 		return false;
176 
177 	return true;
178 }
179 
180 struct cpu_topology *cpu_topology__new(void)
181 {
182 	struct cpu_topology *tp = NULL;
183 	void *addr;
184 	u32 nr, i, nr_addr;
185 	size_t sz;
186 	long ncpus;
187 	int ret = -1;
188 	struct perf_cpu_map *map;
189 	bool has_die = has_die_topology();
190 
191 	ncpus = cpu__max_present_cpu().cpu;
192 
193 	/* build online CPU map */
194 	map = perf_cpu_map__new(NULL);
195 	if (map == NULL) {
196 		pr_debug("failed to get system cpumap\n");
197 		return NULL;
198 	}
199 
200 	nr = (u32)(ncpus & UINT_MAX);
201 
202 	sz = nr * sizeof(char *);
203 	if (has_die)
204 		nr_addr = 3;
205 	else
206 		nr_addr = 2;
207 	addr = calloc(1, sizeof(*tp) + nr_addr * sz);
208 	if (!addr)
209 		goto out_free;
210 
211 	tp = addr;
212 	addr += sizeof(*tp);
213 	tp->package_cpus_list = addr;
214 	addr += sz;
215 	if (has_die) {
216 		tp->die_cpus_list = addr;
217 		addr += sz;
218 	}
219 	tp->core_cpus_list = addr;
220 
221 	for (i = 0; i < nr; i++) {
222 		if (!perf_cpu_map__has(map, (struct perf_cpu){ .cpu = i }))
223 			continue;
224 
225 		ret = build_cpu_topology(tp, i);
226 		if (ret < 0)
227 			break;
228 	}
229 
230 out_free:
231 	perf_cpu_map__put(map);
232 	if (ret) {
233 		cpu_topology__delete(tp);
234 		tp = NULL;
235 	}
236 	return tp;
237 }
238 
239 static int load_numa_node(struct numa_topology_node *node, int nr)
240 {
241 	char str[MAXPATHLEN];
242 	char field[32];
243 	char *buf = NULL, *p;
244 	size_t len = 0;
245 	int ret = -1;
246 	FILE *fp;
247 	u64 mem;
248 
249 	node->node = (u32) nr;
250 
251 	scnprintf(str, MAXPATHLEN, NODE_MEMINFO_FMT,
252 		  sysfs__mountpoint(), nr);
253 	fp = fopen(str, "r");
254 	if (!fp)
255 		return -1;
256 
257 	while (getline(&buf, &len, fp) > 0) {
258 		/* skip over invalid lines */
259 		if (!strchr(buf, ':'))
260 			continue;
261 		if (sscanf(buf, "%*s %*d %31s %"PRIu64, field, &mem) != 2)
262 			goto err;
263 		if (!strcmp(field, "MemTotal:"))
264 			node->mem_total = mem;
265 		if (!strcmp(field, "MemFree:"))
266 			node->mem_free = mem;
267 		if (node->mem_total && node->mem_free)
268 			break;
269 	}
270 
271 	fclose(fp);
272 	fp = NULL;
273 
274 	scnprintf(str, MAXPATHLEN, NODE_CPULIST_FMT,
275 		  sysfs__mountpoint(), nr);
276 
277 	fp = fopen(str, "r");
278 	if (!fp)
279 		return -1;
280 
281 	if (getline(&buf, &len, fp) <= 0)
282 		goto err;
283 
284 	p = strchr(buf, '\n');
285 	if (p)
286 		*p = '\0';
287 
288 	node->cpus = buf;
289 	fclose(fp);
290 	return 0;
291 
292 err:
293 	free(buf);
294 	if (fp)
295 		fclose(fp);
296 	return ret;
297 }
298 
299 struct numa_topology *numa_topology__new(void)
300 {
301 	struct perf_cpu_map *node_map = NULL;
302 	struct numa_topology *tp = NULL;
303 	char path[MAXPATHLEN];
304 	char *buf = NULL;
305 	size_t len = 0;
306 	u32 nr, i;
307 	FILE *fp;
308 	char *c;
309 
310 	scnprintf(path, MAXPATHLEN, NODE_ONLINE_FMT,
311 		  sysfs__mountpoint());
312 
313 	fp = fopen(path, "r");
314 	if (!fp)
315 		return NULL;
316 
317 	if (getline(&buf, &len, fp) <= 0)
318 		goto out;
319 
320 	c = strchr(buf, '\n');
321 	if (c)
322 		*c = '\0';
323 
324 	node_map = perf_cpu_map__new(buf);
325 	if (!node_map)
326 		goto out;
327 
328 	nr = (u32) perf_cpu_map__nr(node_map);
329 
330 	tp = zalloc(sizeof(*tp) + sizeof(tp->nodes[0])*nr);
331 	if (!tp)
332 		goto out;
333 
334 	tp->nr = nr;
335 
336 	for (i = 0; i < nr; i++) {
337 		if (load_numa_node(&tp->nodes[i], perf_cpu_map__cpu(node_map, i).cpu)) {
338 			numa_topology__delete(tp);
339 			tp = NULL;
340 			break;
341 		}
342 	}
343 
344 out:
345 	free(buf);
346 	fclose(fp);
347 	perf_cpu_map__put(node_map);
348 	return tp;
349 }
350 
351 void numa_topology__delete(struct numa_topology *tp)
352 {
353 	u32 i;
354 
355 	for (i = 0; i < tp->nr; i++)
356 		zfree(&tp->nodes[i].cpus);
357 
358 	free(tp);
359 }
360 
361 static int load_hybrid_node(struct hybrid_topology_node *node,
362 			    struct perf_pmu *pmu)
363 {
364 	const char *sysfs;
365 	char path[PATH_MAX];
366 	char *buf = NULL, *p;
367 	FILE *fp;
368 	size_t len = 0;
369 
370 	node->pmu_name = strdup(pmu->name);
371 	if (!node->pmu_name)
372 		return -1;
373 
374 	sysfs = sysfs__mountpoint();
375 	if (!sysfs)
376 		goto err;
377 
378 	snprintf(path, PATH_MAX, CPUS_TEMPLATE_CPU, sysfs, pmu->name);
379 	fp = fopen(path, "r");
380 	if (!fp)
381 		goto err;
382 
383 	if (getline(&buf, &len, fp) <= 0) {
384 		fclose(fp);
385 		goto err;
386 	}
387 
388 	p = strchr(buf, '\n');
389 	if (p)
390 		*p = '\0';
391 
392 	fclose(fp);
393 	node->cpus = buf;
394 	return 0;
395 
396 err:
397 	zfree(&node->pmu_name);
398 	free(buf);
399 	return -1;
400 }
401 
402 struct hybrid_topology *hybrid_topology__new(void)
403 {
404 	struct perf_pmu *pmu;
405 	struct hybrid_topology *tp = NULL;
406 	u32 nr, i = 0;
407 
408 	nr = perf_pmu__hybrid_pmu_num();
409 	if (nr == 0)
410 		return NULL;
411 
412 	tp = zalloc(sizeof(*tp) + sizeof(tp->nodes[0]) * nr);
413 	if (!tp)
414 		return NULL;
415 
416 	tp->nr = nr;
417 	perf_pmu__for_each_hybrid_pmu(pmu) {
418 		if (load_hybrid_node(&tp->nodes[i], pmu)) {
419 			hybrid_topology__delete(tp);
420 			return NULL;
421 		}
422 		i++;
423 	}
424 
425 	return tp;
426 }
427 
428 void hybrid_topology__delete(struct hybrid_topology *tp)
429 {
430 	u32 i;
431 
432 	for (i = 0; i < tp->nr; i++) {
433 		zfree(&tp->nodes[i].pmu_name);
434 		zfree(&tp->nodes[i].cpus);
435 	}
436 
437 	free(tp);
438 }
439