18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * mm/percpu-debug.c
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2017		Facebook Inc.
68c2ecf20Sopenharmony_ci * Copyright (C) 2017		Dennis Zhou <dennis@kernel.org>
78c2ecf20Sopenharmony_ci *
88c2ecf20Sopenharmony_ci * Prints statistics about the percpu allocator and backing chunks.
98c2ecf20Sopenharmony_ci */
108c2ecf20Sopenharmony_ci#include <linux/debugfs.h>
118c2ecf20Sopenharmony_ci#include <linux/list.h>
128c2ecf20Sopenharmony_ci#include <linux/percpu.h>
138c2ecf20Sopenharmony_ci#include <linux/seq_file.h>
148c2ecf20Sopenharmony_ci#include <linux/sort.h>
158c2ecf20Sopenharmony_ci#include <linux/vmalloc.h>
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_ci#include "percpu-internal.h"
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci#define P(X, Y) \
208c2ecf20Sopenharmony_ci	seq_printf(m, "  %-20s: %12lld\n", X, (long long int)Y)
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_cistruct percpu_stats pcpu_stats;
238c2ecf20Sopenharmony_cistruct pcpu_alloc_info pcpu_stats_ai;
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_cistatic int cmpint(const void *a, const void *b)
268c2ecf20Sopenharmony_ci{
278c2ecf20Sopenharmony_ci	return *(int *)a - *(int *)b;
288c2ecf20Sopenharmony_ci}
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_ci/*
318c2ecf20Sopenharmony_ci * Iterates over all chunks to find the max nr_alloc entries.
328c2ecf20Sopenharmony_ci */
338c2ecf20Sopenharmony_cistatic int find_max_nr_alloc(void)
348c2ecf20Sopenharmony_ci{
358c2ecf20Sopenharmony_ci	struct pcpu_chunk *chunk;
368c2ecf20Sopenharmony_ci	int slot, max_nr_alloc;
378c2ecf20Sopenharmony_ci	enum pcpu_chunk_type type;
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_ci	max_nr_alloc = 0;
408c2ecf20Sopenharmony_ci	for (type = 0; type < PCPU_NR_CHUNK_TYPES; type++)
418c2ecf20Sopenharmony_ci		for (slot = 0; slot < pcpu_nr_slots; slot++)
428c2ecf20Sopenharmony_ci			list_for_each_entry(chunk, &pcpu_chunk_list(type)[slot],
438c2ecf20Sopenharmony_ci					    list)
448c2ecf20Sopenharmony_ci				max_nr_alloc = max(max_nr_alloc,
458c2ecf20Sopenharmony_ci						   chunk->nr_alloc);
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci	return max_nr_alloc;
488c2ecf20Sopenharmony_ci}
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_ci/*
518c2ecf20Sopenharmony_ci * Prints out chunk state. Fragmentation is considered between
528c2ecf20Sopenharmony_ci * the beginning of the chunk to the last allocation.
538c2ecf20Sopenharmony_ci *
548c2ecf20Sopenharmony_ci * All statistics are in bytes unless stated otherwise.
558c2ecf20Sopenharmony_ci */
568c2ecf20Sopenharmony_cistatic void chunk_map_stats(struct seq_file *m, struct pcpu_chunk *chunk,
578c2ecf20Sopenharmony_ci			    int *buffer)
588c2ecf20Sopenharmony_ci{
598c2ecf20Sopenharmony_ci	struct pcpu_block_md *chunk_md = &chunk->chunk_md;
608c2ecf20Sopenharmony_ci	int i, last_alloc, as_len, start, end;
618c2ecf20Sopenharmony_ci	int *alloc_sizes, *p;
628c2ecf20Sopenharmony_ci	/* statistics */
638c2ecf20Sopenharmony_ci	int sum_frag = 0, max_frag = 0;
648c2ecf20Sopenharmony_ci	int cur_min_alloc = 0, cur_med_alloc = 0, cur_max_alloc = 0;
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_ci	alloc_sizes = buffer;
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_ci	/*
698c2ecf20Sopenharmony_ci	 * find_last_bit returns the start value if nothing found.
708c2ecf20Sopenharmony_ci	 * Therefore, we must determine if it is a failure of find_last_bit
718c2ecf20Sopenharmony_ci	 * and set the appropriate value.
728c2ecf20Sopenharmony_ci	 */
738c2ecf20Sopenharmony_ci	last_alloc = find_last_bit(chunk->alloc_map,
748c2ecf20Sopenharmony_ci				   pcpu_chunk_map_bits(chunk) -
758c2ecf20Sopenharmony_ci				   chunk->end_offset / PCPU_MIN_ALLOC_SIZE - 1);
768c2ecf20Sopenharmony_ci	last_alloc = test_bit(last_alloc, chunk->alloc_map) ?
778c2ecf20Sopenharmony_ci		     last_alloc + 1 : 0;
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_ci	as_len = 0;
808c2ecf20Sopenharmony_ci	start = chunk->start_offset / PCPU_MIN_ALLOC_SIZE;
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_ci	/*
838c2ecf20Sopenharmony_ci	 * If a bit is set in the allocation map, the bound_map identifies
848c2ecf20Sopenharmony_ci	 * where the allocation ends.  If the allocation is not set, the
858c2ecf20Sopenharmony_ci	 * bound_map does not identify free areas as it is only kept accurate
868c2ecf20Sopenharmony_ci	 * on allocation, not free.
878c2ecf20Sopenharmony_ci	 *
888c2ecf20Sopenharmony_ci	 * Positive values are allocations and negative values are free
898c2ecf20Sopenharmony_ci	 * fragments.
908c2ecf20Sopenharmony_ci	 */
918c2ecf20Sopenharmony_ci	while (start < last_alloc) {
928c2ecf20Sopenharmony_ci		if (test_bit(start, chunk->alloc_map)) {
938c2ecf20Sopenharmony_ci			end = find_next_bit(chunk->bound_map, last_alloc,
948c2ecf20Sopenharmony_ci					    start + 1);
958c2ecf20Sopenharmony_ci			alloc_sizes[as_len] = 1;
968c2ecf20Sopenharmony_ci		} else {
978c2ecf20Sopenharmony_ci			end = find_next_bit(chunk->alloc_map, last_alloc,
988c2ecf20Sopenharmony_ci					    start + 1);
998c2ecf20Sopenharmony_ci			alloc_sizes[as_len] = -1;
1008c2ecf20Sopenharmony_ci		}
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci		alloc_sizes[as_len++] *= (end - start) * PCPU_MIN_ALLOC_SIZE;
1038c2ecf20Sopenharmony_ci
1048c2ecf20Sopenharmony_ci		start = end;
1058c2ecf20Sopenharmony_ci	}
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci	/*
1088c2ecf20Sopenharmony_ci	 * The negative values are free fragments and thus sorting gives the
1098c2ecf20Sopenharmony_ci	 * free fragments at the beginning in largest first order.
1108c2ecf20Sopenharmony_ci	 */
1118c2ecf20Sopenharmony_ci	if (as_len > 0) {
1128c2ecf20Sopenharmony_ci		sort(alloc_sizes, as_len, sizeof(int), cmpint, NULL);
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_ci		/* iterate through the unallocated fragments */
1158c2ecf20Sopenharmony_ci		for (i = 0, p = alloc_sizes; *p < 0 && i < as_len; i++, p++) {
1168c2ecf20Sopenharmony_ci			sum_frag -= *p;
1178c2ecf20Sopenharmony_ci			max_frag = max(max_frag, -1 * (*p));
1188c2ecf20Sopenharmony_ci		}
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_ci		cur_min_alloc = alloc_sizes[i];
1218c2ecf20Sopenharmony_ci		cur_med_alloc = alloc_sizes[(i + as_len - 1) / 2];
1228c2ecf20Sopenharmony_ci		cur_max_alloc = alloc_sizes[as_len - 1];
1238c2ecf20Sopenharmony_ci	}
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_ci	P("nr_alloc", chunk->nr_alloc);
1268c2ecf20Sopenharmony_ci	P("max_alloc_size", chunk->max_alloc_size);
1278c2ecf20Sopenharmony_ci	P("empty_pop_pages", chunk->nr_empty_pop_pages);
1288c2ecf20Sopenharmony_ci	P("first_bit", chunk_md->first_free);
1298c2ecf20Sopenharmony_ci	P("free_bytes", chunk->free_bytes);
1308c2ecf20Sopenharmony_ci	P("contig_bytes", chunk_md->contig_hint * PCPU_MIN_ALLOC_SIZE);
1318c2ecf20Sopenharmony_ci	P("sum_frag", sum_frag);
1328c2ecf20Sopenharmony_ci	P("max_frag", max_frag);
1338c2ecf20Sopenharmony_ci	P("cur_min_alloc", cur_min_alloc);
1348c2ecf20Sopenharmony_ci	P("cur_med_alloc", cur_med_alloc);
1358c2ecf20Sopenharmony_ci	P("cur_max_alloc", cur_max_alloc);
1368c2ecf20Sopenharmony_ci#ifdef CONFIG_MEMCG_KMEM
1378c2ecf20Sopenharmony_ci	P("memcg_aware", pcpu_is_memcg_chunk(pcpu_chunk_type(chunk)));
1388c2ecf20Sopenharmony_ci#endif
1398c2ecf20Sopenharmony_ci	seq_putc(m, '\n');
1408c2ecf20Sopenharmony_ci}
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_cistatic int percpu_stats_show(struct seq_file *m, void *v)
1438c2ecf20Sopenharmony_ci{
1448c2ecf20Sopenharmony_ci	struct pcpu_chunk *chunk;
1458c2ecf20Sopenharmony_ci	int slot, max_nr_alloc;
1468c2ecf20Sopenharmony_ci	int *buffer;
1478c2ecf20Sopenharmony_ci	enum pcpu_chunk_type type;
1488c2ecf20Sopenharmony_ci	int nr_empty_pop_pages;
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_cialloc_buffer:
1518c2ecf20Sopenharmony_ci	spin_lock_irq(&pcpu_lock);
1528c2ecf20Sopenharmony_ci	max_nr_alloc = find_max_nr_alloc();
1538c2ecf20Sopenharmony_ci	spin_unlock_irq(&pcpu_lock);
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ci	/* there can be at most this many free and allocated fragments */
1568c2ecf20Sopenharmony_ci	buffer = vmalloc(array_size(sizeof(int), (2 * max_nr_alloc + 1)));
1578c2ecf20Sopenharmony_ci	if (!buffer)
1588c2ecf20Sopenharmony_ci		return -ENOMEM;
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_ci	spin_lock_irq(&pcpu_lock);
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ci	/* if the buffer allocated earlier is too small */
1638c2ecf20Sopenharmony_ci	if (max_nr_alloc < find_max_nr_alloc()) {
1648c2ecf20Sopenharmony_ci		spin_unlock_irq(&pcpu_lock);
1658c2ecf20Sopenharmony_ci		vfree(buffer);
1668c2ecf20Sopenharmony_ci		goto alloc_buffer;
1678c2ecf20Sopenharmony_ci	}
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci	nr_empty_pop_pages = 0;
1708c2ecf20Sopenharmony_ci	for (type = 0; type < PCPU_NR_CHUNK_TYPES; type++)
1718c2ecf20Sopenharmony_ci		nr_empty_pop_pages += pcpu_nr_empty_pop_pages[type];
1728c2ecf20Sopenharmony_ci
1738c2ecf20Sopenharmony_ci#define PL(X)								\
1748c2ecf20Sopenharmony_ci	seq_printf(m, "  %-20s: %12lld\n", #X, (long long int)pcpu_stats_ai.X)
1758c2ecf20Sopenharmony_ci
1768c2ecf20Sopenharmony_ci	seq_printf(m,
1778c2ecf20Sopenharmony_ci			"Percpu Memory Statistics\n"
1788c2ecf20Sopenharmony_ci			"Allocation Info:\n"
1798c2ecf20Sopenharmony_ci			"----------------------------------------\n");
1808c2ecf20Sopenharmony_ci	PL(unit_size);
1818c2ecf20Sopenharmony_ci	PL(static_size);
1828c2ecf20Sopenharmony_ci	PL(reserved_size);
1838c2ecf20Sopenharmony_ci	PL(dyn_size);
1848c2ecf20Sopenharmony_ci	PL(atom_size);
1858c2ecf20Sopenharmony_ci	PL(alloc_size);
1868c2ecf20Sopenharmony_ci	seq_putc(m, '\n');
1878c2ecf20Sopenharmony_ci
1888c2ecf20Sopenharmony_ci#undef PL
1898c2ecf20Sopenharmony_ci
1908c2ecf20Sopenharmony_ci#define PU(X) \
1918c2ecf20Sopenharmony_ci	seq_printf(m, "  %-20s: %12llu\n", #X, (unsigned long long)pcpu_stats.X)
1928c2ecf20Sopenharmony_ci
1938c2ecf20Sopenharmony_ci	seq_printf(m,
1948c2ecf20Sopenharmony_ci			"Global Stats:\n"
1958c2ecf20Sopenharmony_ci			"----------------------------------------\n");
1968c2ecf20Sopenharmony_ci	PU(nr_alloc);
1978c2ecf20Sopenharmony_ci	PU(nr_dealloc);
1988c2ecf20Sopenharmony_ci	PU(nr_cur_alloc);
1998c2ecf20Sopenharmony_ci	PU(nr_max_alloc);
2008c2ecf20Sopenharmony_ci	PU(nr_chunks);
2018c2ecf20Sopenharmony_ci	PU(nr_max_chunks);
2028c2ecf20Sopenharmony_ci	PU(min_alloc_size);
2038c2ecf20Sopenharmony_ci	PU(max_alloc_size);
2048c2ecf20Sopenharmony_ci	P("empty_pop_pages", nr_empty_pop_pages);
2058c2ecf20Sopenharmony_ci	seq_putc(m, '\n');
2068c2ecf20Sopenharmony_ci
2078c2ecf20Sopenharmony_ci#undef PU
2088c2ecf20Sopenharmony_ci
2098c2ecf20Sopenharmony_ci	seq_printf(m,
2108c2ecf20Sopenharmony_ci			"Per Chunk Stats:\n"
2118c2ecf20Sopenharmony_ci			"----------------------------------------\n");
2128c2ecf20Sopenharmony_ci
2138c2ecf20Sopenharmony_ci	if (pcpu_reserved_chunk) {
2148c2ecf20Sopenharmony_ci		seq_puts(m, "Chunk: <- Reserved Chunk\n");
2158c2ecf20Sopenharmony_ci		chunk_map_stats(m, pcpu_reserved_chunk, buffer);
2168c2ecf20Sopenharmony_ci	}
2178c2ecf20Sopenharmony_ci
2188c2ecf20Sopenharmony_ci	for (type = 0; type < PCPU_NR_CHUNK_TYPES; type++) {
2198c2ecf20Sopenharmony_ci		for (slot = 0; slot < pcpu_nr_slots; slot++) {
2208c2ecf20Sopenharmony_ci			list_for_each_entry(chunk, &pcpu_chunk_list(type)[slot],
2218c2ecf20Sopenharmony_ci					    list) {
2228c2ecf20Sopenharmony_ci				if (chunk == pcpu_first_chunk) {
2238c2ecf20Sopenharmony_ci					seq_puts(m, "Chunk: <- First Chunk\n");
2248c2ecf20Sopenharmony_ci					chunk_map_stats(m, chunk, buffer);
2258c2ecf20Sopenharmony_ci				} else {
2268c2ecf20Sopenharmony_ci					seq_puts(m, "Chunk:\n");
2278c2ecf20Sopenharmony_ci					chunk_map_stats(m, chunk, buffer);
2288c2ecf20Sopenharmony_ci				}
2298c2ecf20Sopenharmony_ci			}
2308c2ecf20Sopenharmony_ci		}
2318c2ecf20Sopenharmony_ci	}
2328c2ecf20Sopenharmony_ci
2338c2ecf20Sopenharmony_ci	spin_unlock_irq(&pcpu_lock);
2348c2ecf20Sopenharmony_ci
2358c2ecf20Sopenharmony_ci	vfree(buffer);
2368c2ecf20Sopenharmony_ci
2378c2ecf20Sopenharmony_ci	return 0;
2388c2ecf20Sopenharmony_ci}
2398c2ecf20Sopenharmony_ciDEFINE_SHOW_ATTRIBUTE(percpu_stats);
2408c2ecf20Sopenharmony_ci
2418c2ecf20Sopenharmony_cistatic int __init init_percpu_stats_debugfs(void)
2428c2ecf20Sopenharmony_ci{
2438c2ecf20Sopenharmony_ci	debugfs_create_file("percpu_stats", 0444, NULL, NULL,
2448c2ecf20Sopenharmony_ci			&percpu_stats_fops);
2458c2ecf20Sopenharmony_ci
2468c2ecf20Sopenharmony_ci	return 0;
2478c2ecf20Sopenharmony_ci}
2488c2ecf20Sopenharmony_ci
2498c2ecf20Sopenharmony_cilate_initcall(init_percpu_stats_debugfs);
250