xref: /linux/tools/perf/util/cpumap.c (revision 7953a3a9b8e02e98c6e6958f291d0ae22393e46a)
1 // SPDX-License-Identifier: GPL-2.0
2 #include <api/fs/fs.h>
3 #include "cpumap.h"
4 #include "debug.h"
5 #include "event.h"
6 #include <assert.h>
7 #include <dirent.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <linux/bitmap.h>
11 #include "asm/bug.h"
12 
13 #include <linux/compiler.h>
14 #include <linux/ctype.h>
15 #include <linux/zalloc.h>
16 #include <internal/cpumap.h>
17 
18 static struct perf_cpu max_cpu_num;
19 static struct perf_cpu max_present_cpu_num;
20 static int max_node_num;
21 /**
22  * The numa node X as read from /sys/devices/system/node/nodeX indexed by the
23  * CPU number.
24  */
25 static int *cpunode_map;
26 
27 bool perf_record_cpu_map_data__test_bit(int i,
28 					const struct perf_record_cpu_map_data *data)
29 {
30 	int bit_word32 = i / 32;
31 	__u32 bit_mask32 = 1U << (i & 31);
32 	int bit_word64 = i / 64;
33 	__u64 bit_mask64 = ((__u64)1) << (i & 63);
34 
35 	return (data->mask32_data.long_size == 4)
36 		? (bit_word32 < data->mask32_data.nr) &&
37 		(data->mask32_data.mask[bit_word32] & bit_mask32) != 0
38 		: (bit_word64 < data->mask64_data.nr) &&
39 		(data->mask64_data.mask[bit_word64] & bit_mask64) != 0;
40 }
41 
42 /* Read ith mask value from data into the given 64-bit sized bitmap */
43 static void perf_record_cpu_map_data__read_one_mask(const struct perf_record_cpu_map_data *data,
44 						    int i, unsigned long *bitmap,
45 						    u16 long_size)
46 {
47 #if __SIZEOF_LONG__ == 8
48 	if (long_size == 4)
49 		bitmap[0] = data->mask32_data.mask[i];
50 	else
51 		bitmap[0] = data->mask64_data.mask[i];
52 #else
53 	if (long_size == 4) {
54 		bitmap[0] = data->mask32_data.mask[i];
55 		bitmap[1] = 0;
56 	} else {
57 #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
58 		bitmap[0] = (unsigned long)(data->mask64_data.mask[i] >> 32);
59 		bitmap[1] = (unsigned long)data->mask64_data.mask[i];
60 #else
61 		bitmap[0] = (unsigned long)data->mask64_data.mask[i];
62 		bitmap[1] = (unsigned long)(data->mask64_data.mask[i] >> 32);
63 #endif
64 	}
65 #endif
66 }
67 static struct perf_cpu_map *cpu_map__from_entries(const struct perf_record_cpu_map_data *data)
68 {
69 	/* Snapshot nr — data is mmap'd and could change between reads */
70 	u16 nr = READ_ONCE(data->cpus_data.nr);
71 	struct perf_cpu_map *map;
72 
73 	map = perf_cpu_map__empty_new(nr);
74 	if (!map)
75 		return NULL;
76 
77 	for (unsigned int i = 0; i < nr; i++) {
78 		u16 cpu = READ_ONCE(data->cpus_data.cpu[i]);
79 		/*
80 		 * Special treatment for -1, which is not real cpu number,
81 		 * and we need to use (int) -1 to initialize map[i],
82 		 * otherwise it would become 65535.
83 		 */
84 		if (cpu == (u16) -1) {
85 			RC_CHK_ACCESS(map)->map[i].cpu = -1;
86 		} else if (cpu < INT16_MAX) {
87 			RC_CHK_ACCESS(map)->map[i].cpu = (int16_t) cpu;
88 		} else {
89 			pr_err("Invalid cpumap entry %u\n", cpu);
90 			perf_cpu_map__put(map);
91 			return NULL;
92 		}
93 	}
94 
95 	return map;
96 }
97 
98 static struct perf_cpu_map *cpu_map__from_mask(const struct perf_record_cpu_map_data *data)
99 {
100 	DECLARE_BITMAP(local_copy, 64);
101 	int weight = 0, mask_nr;
102 	/* Snapshot before validation — data is mmap'd and could change */
103 	u16 long_size = READ_ONCE(data->mask32_data.long_size);
104 	struct perf_cpu_map *map;
105 
106 	/* long_size must be 4 or 8; other values overflow cpus_per_i below */
107 	if (long_size != 4 && long_size != 8) {
108 		pr_warning("WARNING: cpu_map mask: unsupported long_size %u\n", long_size);
109 		return NULL;
110 	}
111 
112 	mask_nr = READ_ONCE(data->mask32_data.nr);
113 
114 	for (int i = 0; i < mask_nr; i++) {
115 		perf_record_cpu_map_data__read_one_mask(data, i, local_copy, long_size);
116 		weight += bitmap_weight(local_copy, 64);
117 	}
118 
119 	map = perf_cpu_map__empty_new(weight);
120 	if (!map)
121 		return NULL;
122 
123 	for (int i = 0, j = 0; i < mask_nr; i++) {
124 		int cpus_per_i = (i * long_size * BITS_PER_BYTE);
125 		int cpu;
126 
127 		perf_record_cpu_map_data__read_one_mask(data, i, local_copy, long_size);
128 		for_each_set_bit(cpu, local_copy, 64) {
129 			/* Guard against more set bits than the first pass counted */
130 			if (j >= weight)
131 				break;
132 			if (cpu + cpus_per_i < INT16_MAX) {
133 				RC_CHK_ACCESS(map)->map[j++].cpu = cpu + cpus_per_i;
134 			} else {
135 				pr_err("Invalid cpumap entry %d\n", cpu + cpus_per_i);
136 				perf_cpu_map__put(map);
137 				return NULL;
138 			}
139 		}
140 	}
141 	return map;
142 
143 }
144 
145 static struct perf_cpu_map *cpu_map__from_range(const struct perf_record_cpu_map_data *data)
146 {
147 	/* Snapshot fields — data is mmap'd and could change between reads */
148 	u16 start_cpu = READ_ONCE(data->range_cpu_data.start_cpu);
149 	u16 end_cpu = READ_ONCE(data->range_cpu_data.end_cpu);
150 	u16 any_cpu = READ_ONCE(data->range_cpu_data.any_cpu);
151 	struct perf_cpu_map *map;
152 	unsigned int i = 0;
153 
154 	if (end_cpu < start_cpu) {
155 		pr_warning("WARNING: cpu_map range: end_cpu %u < start_cpu %u\n",
156 			   end_cpu, start_cpu);
157 		return NULL;
158 	}
159 
160 	/* any_cpu is boolean (0 or 1), not a count — clamp to avoid inflated nr */
161 	map = perf_cpu_map__empty_new(end_cpu - start_cpu + 1 + !!any_cpu);
162 	if (!map)
163 		return NULL;
164 
165 	if (any_cpu)
166 		RC_CHK_ACCESS(map)->map[i++].cpu = -1;
167 
168 	for (int cpu = start_cpu; cpu <= end_cpu;
169 	     i++, cpu++) {
170 		if (cpu < INT16_MAX) {
171 			RC_CHK_ACCESS(map)->map[i].cpu = cpu;
172 		} else {
173 			pr_err("Invalid cpumap entry %d\n", cpu);
174 			perf_cpu_map__put(map);
175 			return NULL;
176 		}
177 	}
178 
179 	return map;
180 }
181 
182 struct perf_cpu_map *cpu_map__new_data(const struct perf_record_cpu_map_data *data)
183 {
184 	switch (data->type) {
185 	case PERF_CPU_MAP__CPUS:
186 		return cpu_map__from_entries(data);
187 	case PERF_CPU_MAP__MASK:
188 		return cpu_map__from_mask(data);
189 	case PERF_CPU_MAP__RANGE_CPUS:
190 		return cpu_map__from_range(data);
191 	default:
192 		pr_err("cpu_map__new_data unknown type %d\n", data->type);
193 		return NULL;
194 	}
195 }
196 
197 size_t cpu_map__fprintf(struct perf_cpu_map *map, FILE *fp)
198 {
199 #define BUFSIZE 1024
200 	char buf[BUFSIZE];
201 
202 	cpu_map__snprint(map, buf, sizeof(buf));
203 	return fprintf(fp, "%s\n", buf);
204 #undef BUFSIZE
205 }
206 
207 struct perf_cpu_map *perf_cpu_map__empty_new(int nr)
208 {
209 	struct perf_cpu_map *cpus = perf_cpu_map__alloc(nr);
210 
211 	if (cpus != NULL) {
212 		for (int i = 0; i < nr; i++)
213 			RC_CHK_ACCESS(cpus)->map[i].cpu = -1;
214 	}
215 
216 	return cpus;
217 }
218 
219 struct cpu_aggr_map *cpu_aggr_map__empty_new(int nr)
220 {
221 	struct cpu_aggr_map *cpus = malloc(sizeof(*cpus) + sizeof(struct aggr_cpu_id) * nr);
222 
223 	if (cpus != NULL) {
224 		int i;
225 
226 		cpus->nr = nr;
227 		for (i = 0; i < nr; i++)
228 			cpus->map[i] = aggr_cpu_id__empty();
229 	}
230 
231 	return cpus;
232 }
233 
234 static int cpu__get_topology_int(int cpu, const char *name, int *value)
235 {
236 	char path[PATH_MAX];
237 
238 	snprintf(path, PATH_MAX,
239 		"devices/system/cpu/cpu%d/topology/%s", cpu, name);
240 
241 	return sysfs__read_int(path, value);
242 }
243 
244 int cpu__get_socket_id(struct perf_cpu cpu)
245 {
246 	int value, ret = cpu__get_topology_int(cpu.cpu, "physical_package_id", &value);
247 	return ret ?: value;
248 }
249 
250 struct aggr_cpu_id aggr_cpu_id__socket(struct perf_cpu cpu, void *data __maybe_unused)
251 {
252 	struct aggr_cpu_id id = aggr_cpu_id__empty();
253 
254 	id.socket = cpu__get_socket_id(cpu);
255 	return id;
256 }
257 
258 static int aggr_cpu_id__cmp(const void *a_pointer, const void *b_pointer)
259 {
260 	struct aggr_cpu_id *a = (struct aggr_cpu_id *)a_pointer;
261 	struct aggr_cpu_id *b = (struct aggr_cpu_id *)b_pointer;
262 
263 	if (a->node != b->node)
264 		return a->node - b->node;
265 	else if (a->socket != b->socket)
266 		return a->socket - b->socket;
267 	else if (a->die != b->die)
268 		return a->die - b->die;
269 	else if (a->cluster != b->cluster)
270 		return a->cluster - b->cluster;
271 	else if (a->cache_lvl != b->cache_lvl)
272 		return a->cache_lvl - b->cache_lvl;
273 	else if (a->cache != b->cache)
274 		return a->cache - b->cache;
275 	else if (a->core != b->core)
276 		return a->core - b->core;
277 	else
278 		return a->thread_idx - b->thread_idx;
279 }
280 
281 struct cpu_aggr_map *cpu_aggr_map__new(const struct perf_cpu_map *cpus,
282 				       aggr_cpu_id_get_t get_id,
283 				       void *data, bool needs_sort)
284 {
285 	unsigned int idx;
286 	struct perf_cpu cpu;
287 	struct cpu_aggr_map *c = cpu_aggr_map__empty_new(perf_cpu_map__nr(cpus));
288 
289 	if (!c)
290 		return NULL;
291 
292 	/* Reset size as it may only be partially filled */
293 	c->nr = 0;
294 
295 	perf_cpu_map__for_each_cpu(cpu, idx, cpus) {
296 		bool duplicate = false;
297 		struct aggr_cpu_id cpu_id = get_id(cpu, data);
298 
299 		for (int j = 0; j < c->nr; j++) {
300 			if (aggr_cpu_id__equal(&cpu_id, &c->map[j])) {
301 				duplicate = true;
302 				break;
303 			}
304 		}
305 		if (!duplicate) {
306 			c->map[c->nr] = cpu_id;
307 			c->nr++;
308 		}
309 	}
310 	/* Trim. */
311 	if (c->nr != (int)perf_cpu_map__nr(cpus)) {
312 		struct cpu_aggr_map *trimmed_c =
313 			realloc(c,
314 				sizeof(struct cpu_aggr_map) + sizeof(struct aggr_cpu_id) * c->nr);
315 
316 		if (trimmed_c)
317 			c = trimmed_c;
318 	}
319 
320 	/* ensure we process id in increasing order */
321 	if (needs_sort)
322 		qsort(c->map, c->nr, sizeof(struct aggr_cpu_id), aggr_cpu_id__cmp);
323 
324 	return c;
325 
326 }
327 
328 int cpu__get_die_id(struct perf_cpu cpu)
329 {
330 	int value, ret = cpu__get_topology_int(cpu.cpu, "die_id", &value);
331 
332 	return ret ?: value;
333 }
334 
335 struct aggr_cpu_id aggr_cpu_id__die(struct perf_cpu cpu, void *data)
336 {
337 	struct aggr_cpu_id id;
338 	int die;
339 
340 	die = cpu__get_die_id(cpu);
341 	/* There is no die_id on legacy system. */
342 	if (die < 0)
343 		die = 0;
344 
345 	/*
346 	 * die_id is relative to socket, so start
347 	 * with the socket ID and then add die to
348 	 * make a unique ID.
349 	 */
350 	id = aggr_cpu_id__socket(cpu, data);
351 	if (aggr_cpu_id__is_empty(&id))
352 		return id;
353 
354 	id.die = die;
355 	return id;
356 }
357 
358 int cpu__get_cluster_id(struct perf_cpu cpu)
359 {
360 	int value, ret = cpu__get_topology_int(cpu.cpu, "cluster_id", &value);
361 
362 	return ret ?: value;
363 }
364 
365 struct aggr_cpu_id aggr_cpu_id__cluster(struct perf_cpu cpu, void *data)
366 {
367 	int cluster = cpu__get_cluster_id(cpu);
368 	struct aggr_cpu_id id;
369 
370 	/* There is no cluster_id on legacy system. */
371 	if (cluster < 0)
372 		cluster = 0;
373 
374 	id = aggr_cpu_id__die(cpu, data);
375 	if (aggr_cpu_id__is_empty(&id))
376 		return id;
377 
378 	id.cluster = cluster;
379 	return id;
380 }
381 
382 int cpu__get_core_id(struct perf_cpu cpu)
383 {
384 	int value, ret = cpu__get_topology_int(cpu.cpu, "core_id", &value);
385 	return ret ?: value;
386 }
387 
388 struct aggr_cpu_id aggr_cpu_id__core(struct perf_cpu cpu, void *data)
389 {
390 	struct aggr_cpu_id id;
391 	int core = cpu__get_core_id(cpu);
392 
393 	/* aggr_cpu_id__die returns a struct with socket die, and cluster set. */
394 	id = aggr_cpu_id__cluster(cpu, data);
395 	if (aggr_cpu_id__is_empty(&id))
396 		return id;
397 
398 	/*
399 	 * core_id is relative to socket and die, we need a global id.
400 	 * So we combine the result from cpu_map__get_die with the core id
401 	 */
402 	id.core = core;
403 	return id;
404 
405 }
406 
407 struct aggr_cpu_id aggr_cpu_id__cpu(struct perf_cpu cpu, void *data)
408 {
409 	struct aggr_cpu_id id;
410 
411 	/* aggr_cpu_id__core returns a struct with socket, die and core set. */
412 	id = aggr_cpu_id__core(cpu, data);
413 	if (aggr_cpu_id__is_empty(&id))
414 		return id;
415 
416 	id.cpu = cpu;
417 	return id;
418 
419 }
420 
421 struct aggr_cpu_id aggr_cpu_id__node(struct perf_cpu cpu, void *data __maybe_unused)
422 {
423 	struct aggr_cpu_id id = aggr_cpu_id__empty();
424 
425 	id.node = cpu__get_node(cpu);
426 	return id;
427 }
428 
429 struct aggr_cpu_id aggr_cpu_id__global(struct perf_cpu cpu, void *data __maybe_unused)
430 {
431 	struct aggr_cpu_id id = aggr_cpu_id__empty();
432 
433 	/* it always aggregates to the cpu 0 */
434 	cpu.cpu = 0;
435 	id.cpu = cpu;
436 	return id;
437 }
438 
439 /* setup simple routines to easily access node numbers given a cpu number */
440 static int get_max_num(char *path, int *max)
441 {
442 	size_t num;
443 	char *buf;
444 	int err = 0;
445 
446 	if (filename__read_str(path, &buf, &num))
447 		return -1;
448 
449 	buf[num] = '\0';
450 
451 	/* empty file — nothing to parse */
452 	if (num == 0) {
453 		err = -1;
454 		goto out;
455 	}
456 
457 	/* start on the right, to find highest node num */
458 	while (--num) {
459 		if ((buf[num] == ',') || (buf[num] == '-')) {
460 			num++;
461 			break;
462 		}
463 	}
464 	if (sscanf(&buf[num], "%d", max) < 1) {
465 		err = -1;
466 		goto out;
467 	}
468 
469 	/* convert from 0-based to 1-based */
470 	(*max)++;
471 
472 out:
473 	free(buf);
474 	return err;
475 }
476 
477 /* Determine highest possible cpu in the system for sparse allocation */
478 static void set_max_cpu_num(void)
479 {
480 	const char *mnt;
481 	char path[PATH_MAX];
482 	int max, ret = -1;
483 
484 	/* set up default */
485 	max_cpu_num.cpu = 4096;
486 	max_present_cpu_num.cpu = 4096;
487 
488 	mnt = sysfs__mountpoint();
489 	if (!mnt)
490 		goto out;
491 
492 	/* get the highest possible cpu number for a sparse allocation */
493 	ret = snprintf(path, PATH_MAX, "%s/devices/system/cpu/possible", mnt);
494 	if (ret >= PATH_MAX) {
495 		pr_err("sysfs path crossed PATH_MAX(%d) size\n", PATH_MAX);
496 		goto out;
497 	}
498 
499 	ret = get_max_num(path, &max);
500 	if (ret)
501 		goto out;
502 
503 	/*
504 	 * struct perf_cpu.cpu is int16_t (libperf ABI) — clamp to avoid
505 	 * truncation to negative.  See tools/lib/perf/TODO for the ABI
506 	 * widening plan.
507 	 */
508 	if (max > INT16_MAX) {
509 		pr_warning("WARNING: max possible cpus %d exceeds int16_t, clamping to %d\n",
510 			   max, INT16_MAX);
511 		max = INT16_MAX;
512 	}
513 	max_cpu_num.cpu = max;
514 
515 	/* get the highest present cpu number for a sparse allocation */
516 	ret = snprintf(path, PATH_MAX, "%s/devices/system/cpu/present", mnt);
517 	if (ret >= PATH_MAX) {
518 		pr_err("sysfs path crossed PATH_MAX(%d) size\n", PATH_MAX);
519 		goto out;
520 	}
521 
522 	ret = get_max_num(path, &max);
523 
524 	if (!ret && max > INT16_MAX) {
525 		pr_warning("WARNING: max present cpus %d exceeds int16_t, clamping to %d\n",
526 			   max, INT16_MAX);
527 		max = INT16_MAX;
528 	}
529 	if (!ret)
530 		max_present_cpu_num.cpu = max;
531 out:
532 	if (ret)
533 		pr_err("Failed to read max cpus, using default of %d\n", max_cpu_num.cpu);
534 }
535 
536 /* Determine highest possible node in the system for sparse allocation */
537 static void set_max_node_num(void)
538 {
539 	const char *mnt;
540 	char path[PATH_MAX];
541 	int ret = -1;
542 
543 	/* set up default */
544 	max_node_num = 8;
545 
546 	mnt = sysfs__mountpoint();
547 	if (!mnt)
548 		goto out;
549 
550 	/* get the highest possible cpu number for a sparse allocation */
551 	ret = snprintf(path, PATH_MAX, "%s/devices/system/node/possible", mnt);
552 	if (ret >= PATH_MAX) {
553 		pr_err("sysfs path crossed PATH_MAX(%d) size\n", PATH_MAX);
554 		goto out;
555 	}
556 
557 	ret = get_max_num(path, &max_node_num);
558 
559 out:
560 	if (ret)
561 		pr_err("Failed to read max nodes, using default of %d\n", max_node_num);
562 }
563 
564 int cpu__max_node(void)
565 {
566 	if (unlikely(!max_node_num))
567 		set_max_node_num();
568 
569 	return max_node_num;
570 }
571 
572 struct perf_cpu cpu__max_cpu(void)
573 {
574 	if (unlikely(!max_cpu_num.cpu))
575 		set_max_cpu_num();
576 
577 	return max_cpu_num;
578 }
579 
580 struct perf_cpu cpu__max_present_cpu(void)
581 {
582 	if (unlikely(!max_present_cpu_num.cpu))
583 		set_max_cpu_num();
584 
585 	return max_present_cpu_num;
586 }
587 
588 
589 int cpu__get_node(struct perf_cpu cpu)
590 {
591 	if (unlikely(cpunode_map == NULL)) {
592 		pr_debug("cpu_map not initialized\n");
593 		return -1;
594 	}
595 
596 	/* cpunode_map allocated for max_cpu_num entries; input may be untrusted */
597 	if (cpu.cpu < 0 || cpu.cpu >= max_cpu_num.cpu)
598 		return -1;
599 
600 	return cpunode_map[cpu.cpu];
601 }
602 
603 static int init_cpunode_map(void)
604 {
605 	int i;
606 
607 	set_max_cpu_num();
608 	set_max_node_num();
609 
610 	cpunode_map = calloc(max_cpu_num.cpu, sizeof(int));
611 	if (!cpunode_map) {
612 		pr_err("%s: calloc failed\n", __func__);
613 		return -1;
614 	}
615 
616 	for (i = 0; i < max_cpu_num.cpu; i++)
617 		cpunode_map[i] = -1;
618 
619 	return 0;
620 }
621 
622 int cpu__setup_cpunode_map(void)
623 {
624 	struct dirent *dent1, *dent2;
625 	DIR *dir1, *dir2;
626 	unsigned int cpu, mem;
627 	char buf[PATH_MAX];
628 	char path[PATH_MAX];
629 	const char *mnt;
630 	int n;
631 
632 	/* initialize globals */
633 	if (init_cpunode_map())
634 		return -1;
635 
636 	mnt = sysfs__mountpoint();
637 	if (!mnt)
638 		return 0;
639 
640 	n = snprintf(path, PATH_MAX, "%s/devices/system/node", mnt);
641 	if (n >= PATH_MAX) {
642 		pr_err("sysfs path crossed PATH_MAX(%d) size\n", PATH_MAX);
643 		return -1;
644 	}
645 
646 	dir1 = opendir(path);
647 	if (!dir1)
648 		return 0;
649 
650 	/* walk tree and setup map */
651 	while ((dent1 = readdir(dir1)) != NULL) {
652 		if (dent1->d_type != DT_DIR || sscanf(dent1->d_name, "node%u", &mem) < 1)
653 			continue;
654 
655 		n = snprintf(buf, PATH_MAX, "%s/%s", path, dent1->d_name);
656 		if (n >= PATH_MAX) {
657 			pr_err("sysfs path crossed PATH_MAX(%d) size\n", PATH_MAX);
658 			continue;
659 		}
660 
661 		dir2 = opendir(buf);
662 		if (!dir2)
663 			continue;
664 		while ((dent2 = readdir(dir2)) != NULL) {
665 			if (dent2->d_type != DT_LNK || sscanf(dent2->d_name, "cpu%u", &cpu) < 1)
666 				continue;
667 			/* cpunode_map allocated for max_cpu_num entries */
668 			if (cpu < (unsigned int)max_cpu_num.cpu)
669 				cpunode_map[cpu] = mem;
670 		}
671 		closedir(dir2);
672 	}
673 	closedir(dir1);
674 	return 0;
675 }
676 
677 size_t cpu_map__snprint(struct perf_cpu_map *map, char *buf, size_t size)
678 {
679 	int i, start = -1;
680 	bool first = true;
681 	size_t ret = 0;
682 
683 #define COMMA first ? "" : ","
684 
685 	for (i = 0; i < (int)perf_cpu_map__nr(map) + 1; i++) {
686 		struct perf_cpu cpu = { .cpu = INT16_MAX };
687 		bool last = i == (int)perf_cpu_map__nr(map);
688 
689 		if (!last)
690 			cpu = perf_cpu_map__cpu(map, i);
691 
692 		if (start == -1) {
693 			start = i;
694 			if (last) {
695 				ret += scnprintf(buf + ret, size - ret,
696 						 "%s%d", COMMA,
697 						 perf_cpu_map__cpu(map, i).cpu);
698 			}
699 		} else if (((i - start) != (cpu.cpu - perf_cpu_map__cpu(map, start).cpu)) || last) {
700 			int end = i - 1;
701 
702 			if (start == end) {
703 				ret += scnprintf(buf + ret, size - ret,
704 						 "%s%d", COMMA,
705 						 perf_cpu_map__cpu(map, start).cpu);
706 			} else {
707 				ret += scnprintf(buf + ret, size - ret,
708 						 "%s%d-%d", COMMA,
709 						 perf_cpu_map__cpu(map, start).cpu, perf_cpu_map__cpu(map, end).cpu);
710 			}
711 			first = false;
712 			start = i;
713 		}
714 	}
715 
716 #undef COMMA
717 
718 	pr_debug2("cpumask list: %s\n", buf);
719 	return ret;
720 }
721 
722 static char hex_char(unsigned char val)
723 {
724 	if (val < 10)
725 		return val + '0';
726 	if (val < 16)
727 		return val - 10 + 'a';
728 	return '?';
729 }
730 
731 size_t cpu_map__snprint_mask(struct perf_cpu_map *map, char *buf, size_t size)
732 {
733 	unsigned int idx;
734 	char *ptr = buf;
735 	unsigned char *bitmap;
736 	struct perf_cpu c, last_cpu = perf_cpu_map__max(map);
737 
738 	if (buf == NULL || size == 0)
739 		return 0;
740 
741 	if (last_cpu.cpu < 0) {
742 		buf[0] = '\0';
743 		return 0;
744 	}
745 
746 	bitmap = zalloc(last_cpu.cpu / 8 + 1);
747 	if (bitmap == NULL) {
748 		buf[0] = '\0';
749 		return 0;
750 	}
751 
752 	perf_cpu_map__for_each_cpu_skip_any(c, idx, map)
753 		bitmap[c.cpu / 8] |= 1 << (c.cpu % 8);
754 
755 	for (int cpu = last_cpu.cpu / 4 * 4; cpu >= 0; cpu -= 4) {
756 		unsigned char bits = bitmap[cpu / 8];
757 
758 		if (cpu % 8)
759 			bits >>= 4;
760 		else
761 			bits &= 0xf;
762 
763 		*ptr++ = hex_char(bits);
764 		if ((cpu % 32) == 0 && cpu > 0)
765 			*ptr++ = ',';
766 	}
767 	*ptr = '\0';
768 	free(bitmap);
769 
770 	buf[size - 1] = '\0';
771 	return ptr - buf;
772 }
773 
774 struct perf_cpu_map *cpu_map__online(void) /* thread unsafe */
775 {
776 	static struct perf_cpu_map *online;
777 
778 	if (!online)
779 		online = perf_cpu_map__new_online_cpus(); /* from /sys/devices/system/cpu/online */
780 
781 	return perf_cpu_map__get(online);
782 }
783 
784 bool aggr_cpu_id__equal(const struct aggr_cpu_id *a, const struct aggr_cpu_id *b)
785 {
786 	return a->thread_idx == b->thread_idx &&
787 		a->node == b->node &&
788 		a->socket == b->socket &&
789 		a->die == b->die &&
790 		a->cluster == b->cluster &&
791 		a->cache_lvl == b->cache_lvl &&
792 		a->cache == b->cache &&
793 		a->core == b->core &&
794 		a->cpu.cpu == b->cpu.cpu;
795 }
796 
797 bool aggr_cpu_id__is_empty(const struct aggr_cpu_id *a)
798 {
799 	return a->thread_idx == -1 &&
800 		a->node == -1 &&
801 		a->socket == -1 &&
802 		a->die == -1 &&
803 		a->cluster == -1 &&
804 		a->cache_lvl == -1 &&
805 		a->cache == -1 &&
806 		a->core == -1 &&
807 		a->cpu.cpu == -1;
808 }
809 
810 struct aggr_cpu_id aggr_cpu_id__empty(void)
811 {
812 	struct aggr_cpu_id ret = {
813 		.thread_idx = -1,
814 		.node = -1,
815 		.socket = -1,
816 		.die = -1,
817 		.cluster = -1,
818 		.cache_lvl = -1,
819 		.cache = -1,
820 		.core = -1,
821 		.cpu = (struct perf_cpu){ .cpu = -1 },
822 	};
823 	return ret;
824 }
825