155716d26SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
230a5b536SDennis Zhou /*
330a5b536SDennis Zhou * mm/percpu-debug.c
430a5b536SDennis Zhou *
530a5b536SDennis Zhou * Copyright (C) 2017 Facebook Inc.
6bfacd38fSDennis Zhou * Copyright (C) 2017 Dennis Zhou <dennis@kernel.org>
730a5b536SDennis Zhou *
830a5b536SDennis Zhou * Prints statistics about the percpu allocator and backing chunks.
930a5b536SDennis Zhou */
1030a5b536SDennis Zhou #include <linux/debugfs.h>
1130a5b536SDennis Zhou #include <linux/list.h>
1230a5b536SDennis Zhou #include <linux/percpu.h>
1330a5b536SDennis Zhou #include <linux/seq_file.h>
1430a5b536SDennis Zhou #include <linux/sort.h>
1530a5b536SDennis Zhou #include <linux/vmalloc.h>
1630a5b536SDennis Zhou
1730a5b536SDennis Zhou #include "percpu-internal.h"
1830a5b536SDennis Zhou
1930a5b536SDennis Zhou #define P(X, Y) \
2002459164SDennis Zhou (Facebook) seq_printf(m, " %-20s: %12lld\n", X, (long long int)Y)
2130a5b536SDennis Zhou
2230a5b536SDennis Zhou struct percpu_stats pcpu_stats;
2330a5b536SDennis Zhou struct pcpu_alloc_info pcpu_stats_ai;
2430a5b536SDennis Zhou
cmpint(const void * a,const void * b)2530a5b536SDennis Zhou static int cmpint(const void *a, const void *b)
2630a5b536SDennis Zhou {
2730a5b536SDennis Zhou return *(int *)a - *(int *)b;
2830a5b536SDennis Zhou }
2930a5b536SDennis Zhou
3030a5b536SDennis Zhou /*
3140064aecSDennis Zhou (Facebook) * Iterates over all chunks to find the max nr_alloc entries.
3230a5b536SDennis Zhou */
find_max_nr_alloc(void)3340064aecSDennis Zhou (Facebook) static int find_max_nr_alloc(void)
3430a5b536SDennis Zhou {
3530a5b536SDennis Zhou struct pcpu_chunk *chunk;
3640064aecSDennis Zhou (Facebook) int slot, max_nr_alloc;
3730a5b536SDennis Zhou
3840064aecSDennis Zhou (Facebook) max_nr_alloc = 0;
3930a5b536SDennis Zhou for (slot = 0; slot < pcpu_nr_slots; slot++)
40faf65ddeSRoman Gushchin list_for_each_entry(chunk, &pcpu_chunk_lists[slot], list)
41faf65ddeSRoman Gushchin max_nr_alloc = max(max_nr_alloc, chunk->nr_alloc);
4230a5b536SDennis Zhou
4340064aecSDennis Zhou (Facebook) return max_nr_alloc;
4430a5b536SDennis Zhou }
4530a5b536SDennis Zhou
4630a5b536SDennis Zhou /*
4730a5b536SDennis Zhou * Prints out chunk state. Fragmentation is considered between
4830a5b536SDennis Zhou * the beginning of the chunk to the last allocation.
4940064aecSDennis Zhou (Facebook) *
5040064aecSDennis Zhou (Facebook) * All statistics are in bytes unless stated otherwise.
5130a5b536SDennis Zhou */
chunk_map_stats(struct seq_file * m,struct pcpu_chunk * chunk,int * buffer)5230a5b536SDennis Zhou static void chunk_map_stats(struct seq_file *m, struct pcpu_chunk *chunk,
53cd6a884dSDennis Zhou (Facebook) int *buffer)
5430a5b536SDennis Zhou {
5592c14cabSDennis Zhou struct pcpu_block_md *chunk_md = &chunk->chunk_md;
5640064aecSDennis Zhou (Facebook) int i, last_alloc, as_len, start, end;
5730a5b536SDennis Zhou int *alloc_sizes, *p;
5830a5b536SDennis Zhou /* statistics */
5930a5b536SDennis Zhou int sum_frag = 0, max_frag = 0;
6030a5b536SDennis Zhou int cur_min_alloc = 0, cur_med_alloc = 0, cur_max_alloc = 0;
6130a5b536SDennis Zhou
6230a5b536SDennis Zhou alloc_sizes = buffer;
6330a5b536SDennis Zhou
6430a5b536SDennis Zhou /*
6540064aecSDennis Zhou (Facebook) * find_last_bit returns the start value if nothing found.
6640064aecSDennis Zhou (Facebook) * Therefore, we must determine if it is a failure of find_last_bit
6740064aecSDennis Zhou (Facebook) * and set the appropriate value.
6830a5b536SDennis Zhou */
6940064aecSDennis Zhou (Facebook) last_alloc = find_last_bit(chunk->alloc_map,
7040064aecSDennis Zhou (Facebook) pcpu_chunk_map_bits(chunk) -
7140064aecSDennis Zhou (Facebook) chunk->end_offset / PCPU_MIN_ALLOC_SIZE - 1);
7240064aecSDennis Zhou (Facebook) last_alloc = test_bit(last_alloc, chunk->alloc_map) ?
7340064aecSDennis Zhou (Facebook) last_alloc + 1 : 0;
7440064aecSDennis Zhou (Facebook)
7540064aecSDennis Zhou (Facebook) as_len = 0;
762e08d20dSDennis Zhou start = chunk->start_offset / PCPU_MIN_ALLOC_SIZE;
7740064aecSDennis Zhou (Facebook)
7840064aecSDennis Zhou (Facebook) /*
7940064aecSDennis Zhou (Facebook) * If a bit is set in the allocation map, the bound_map identifies
8040064aecSDennis Zhou (Facebook) * where the allocation ends. If the allocation is not set, the
8140064aecSDennis Zhou (Facebook) * bound_map does not identify free areas as it is only kept accurate
8240064aecSDennis Zhou (Facebook) * on allocation, not free.
8340064aecSDennis Zhou (Facebook) *
8440064aecSDennis Zhou (Facebook) * Positive values are allocations and negative values are free
8540064aecSDennis Zhou (Facebook) * fragments.
8640064aecSDennis Zhou (Facebook) */
8740064aecSDennis Zhou (Facebook) while (start < last_alloc) {
8840064aecSDennis Zhou (Facebook) if (test_bit(start, chunk->alloc_map)) {
8940064aecSDennis Zhou (Facebook) end = find_next_bit(chunk->bound_map, last_alloc,
9040064aecSDennis Zhou (Facebook) start + 1);
9140064aecSDennis Zhou (Facebook) alloc_sizes[as_len] = 1;
9240064aecSDennis Zhou (Facebook) } else {
9340064aecSDennis Zhou (Facebook) end = find_next_bit(chunk->alloc_map, last_alloc,
9440064aecSDennis Zhou (Facebook) start + 1);
9540064aecSDennis Zhou (Facebook) alloc_sizes[as_len] = -1;
9630a5b536SDennis Zhou }
9730a5b536SDennis Zhou
9840064aecSDennis Zhou (Facebook) alloc_sizes[as_len++] *= (end - start) * PCPU_MIN_ALLOC_SIZE;
9930a5b536SDennis Zhou
10040064aecSDennis Zhou (Facebook) start = end;
10140064aecSDennis Zhou (Facebook) }
10240064aecSDennis Zhou (Facebook)
10340064aecSDennis Zhou (Facebook) /*
10440064aecSDennis Zhou (Facebook) * The negative values are free fragments and thus sorting gives the
10540064aecSDennis Zhou (Facebook) * free fragments at the beginning in largest first order.
10640064aecSDennis Zhou (Facebook) */
10740064aecSDennis Zhou (Facebook) if (as_len > 0) {
10840064aecSDennis Zhou (Facebook) sort(alloc_sizes, as_len, sizeof(int), cmpint, NULL);
10940064aecSDennis Zhou (Facebook)
11040064aecSDennis Zhou (Facebook) /* iterate through the unallocated fragments */
11130a5b536SDennis Zhou for (i = 0, p = alloc_sizes; *p < 0 && i < as_len; i++, p++) {
11230a5b536SDennis Zhou sum_frag -= *p;
11330a5b536SDennis Zhou max_frag = max(max_frag, -1 * (*p));
11430a5b536SDennis Zhou }
11530a5b536SDennis Zhou
11630a5b536SDennis Zhou cur_min_alloc = alloc_sizes[i];
11730a5b536SDennis Zhou cur_med_alloc = alloc_sizes[(i + as_len - 1) / 2];
11830a5b536SDennis Zhou cur_max_alloc = alloc_sizes[as_len - 1];
11930a5b536SDennis Zhou }
12030a5b536SDennis Zhou
12130a5b536SDennis Zhou P("nr_alloc", chunk->nr_alloc);
12230a5b536SDennis Zhou P("max_alloc_size", chunk->max_alloc_size);
1230cecf50cSDennis Zhou (Facebook) P("empty_pop_pages", chunk->nr_empty_pop_pages);
12492c14cabSDennis Zhou P("first_bit", chunk_md->first_free);
12540064aecSDennis Zhou (Facebook) P("free_bytes", chunk->free_bytes);
12692c14cabSDennis Zhou P("contig_bytes", chunk_md->contig_hint * PCPU_MIN_ALLOC_SIZE);
12730a5b536SDennis Zhou P("sum_frag", sum_frag);
12830a5b536SDennis Zhou P("max_frag", max_frag);
12930a5b536SDennis Zhou P("cur_min_alloc", cur_min_alloc);
13030a5b536SDennis Zhou P("cur_med_alloc", cur_med_alloc);
13130a5b536SDennis Zhou P("cur_max_alloc", cur_max_alloc);
13230a5b536SDennis Zhou seq_putc(m, '\n');
13330a5b536SDennis Zhou }
13430a5b536SDennis Zhou
percpu_stats_show(struct seq_file * m,void * v)13530a5b536SDennis Zhou static int percpu_stats_show(struct seq_file *m, void *v)
13630a5b536SDennis Zhou {
13730a5b536SDennis Zhou struct pcpu_chunk *chunk;
13840064aecSDennis Zhou (Facebook) int slot, max_nr_alloc;
139cd6a884dSDennis Zhou (Facebook) int *buffer;
14030a5b536SDennis Zhou
14130a5b536SDennis Zhou alloc_buffer:
14230a5b536SDennis Zhou spin_lock_irq(&pcpu_lock);
14340064aecSDennis Zhou (Facebook) max_nr_alloc = find_max_nr_alloc();
14430a5b536SDennis Zhou spin_unlock_irq(&pcpu_lock);
14530a5b536SDennis Zhou
14640064aecSDennis Zhou (Facebook) /* there can be at most this many free and allocated fragments */
147*3000f2e2SPaolo Bonzini buffer = vmalloc_array(2 * max_nr_alloc + 1, sizeof(int));
14830a5b536SDennis Zhou if (!buffer)
14930a5b536SDennis Zhou return -ENOMEM;
15030a5b536SDennis Zhou
15130a5b536SDennis Zhou spin_lock_irq(&pcpu_lock);
15230a5b536SDennis Zhou
15330a5b536SDennis Zhou /* if the buffer allocated earlier is too small */
15440064aecSDennis Zhou (Facebook) if (max_nr_alloc < find_max_nr_alloc()) {
15530a5b536SDennis Zhou spin_unlock_irq(&pcpu_lock);
15630a5b536SDennis Zhou vfree(buffer);
15730a5b536SDennis Zhou goto alloc_buffer;
15830a5b536SDennis Zhou }
15930a5b536SDennis Zhou
16030a5b536SDennis Zhou #define PL(X) \
16102459164SDennis Zhou (Facebook) seq_printf(m, " %-20s: %12lld\n", #X, (long long int)pcpu_stats_ai.X)
16230a5b536SDennis Zhou
16330a5b536SDennis Zhou seq_printf(m,
16430a5b536SDennis Zhou "Percpu Memory Statistics\n"
16530a5b536SDennis Zhou "Allocation Info:\n"
16630a5b536SDennis Zhou "----------------------------------------\n");
16730a5b536SDennis Zhou PL(unit_size);
16830a5b536SDennis Zhou PL(static_size);
16930a5b536SDennis Zhou PL(reserved_size);
17030a5b536SDennis Zhou PL(dyn_size);
17130a5b536SDennis Zhou PL(atom_size);
17230a5b536SDennis Zhou PL(alloc_size);
17330a5b536SDennis Zhou seq_putc(m, '\n');
17430a5b536SDennis Zhou
17530a5b536SDennis Zhou #undef PL
17630a5b536SDennis Zhou
17730a5b536SDennis Zhou #define PU(X) \
17802459164SDennis Zhou (Facebook) seq_printf(m, " %-20s: %12llu\n", #X, (unsigned long long)pcpu_stats.X)
17930a5b536SDennis Zhou
18030a5b536SDennis Zhou seq_printf(m,
18130a5b536SDennis Zhou "Global Stats:\n"
18230a5b536SDennis Zhou "----------------------------------------\n");
18330a5b536SDennis Zhou PU(nr_alloc);
18430a5b536SDennis Zhou PU(nr_dealloc);
18530a5b536SDennis Zhou PU(nr_cur_alloc);
18630a5b536SDennis Zhou PU(nr_max_alloc);
18730a5b536SDennis Zhou PU(nr_chunks);
18830a5b536SDennis Zhou PU(nr_max_chunks);
18930a5b536SDennis Zhou PU(min_alloc_size);
19030a5b536SDennis Zhou PU(max_alloc_size);
191faf65ddeSRoman Gushchin P("empty_pop_pages", pcpu_nr_empty_pop_pages);
19230a5b536SDennis Zhou seq_putc(m, '\n');
19330a5b536SDennis Zhou
19430a5b536SDennis Zhou #undef PU
19530a5b536SDennis Zhou
19630a5b536SDennis Zhou seq_printf(m,
19730a5b536SDennis Zhou "Per Chunk Stats:\n"
19830a5b536SDennis Zhou "----------------------------------------\n");
19930a5b536SDennis Zhou
20030a5b536SDennis Zhou if (pcpu_reserved_chunk) {
20130a5b536SDennis Zhou seq_puts(m, "Chunk: <- Reserved Chunk\n");
20230a5b536SDennis Zhou chunk_map_stats(m, pcpu_reserved_chunk, buffer);
20330a5b536SDennis Zhou }
20430a5b536SDennis Zhou
20530a5b536SDennis Zhou for (slot = 0; slot < pcpu_nr_slots; slot++) {
206faf65ddeSRoman Gushchin list_for_each_entry(chunk, &pcpu_chunk_lists[slot], list) {
207f1833241SRoman Gushchin if (chunk == pcpu_first_chunk)
20830a5b536SDennis Zhou seq_puts(m, "Chunk: <- First Chunk\n");
209f1833241SRoman Gushchin else if (slot == pcpu_to_depopulate_slot)
210f1833241SRoman Gushchin seq_puts(m, "Chunk (to_depopulate)\n");
211f1833241SRoman Gushchin else if (slot == pcpu_sidelined_slot)
212f1833241SRoman Gushchin seq_puts(m, "Chunk (sidelined):\n");
213f1833241SRoman Gushchin else
21430a5b536SDennis Zhou seq_puts(m, "Chunk:\n");
21530a5b536SDennis Zhou chunk_map_stats(m, chunk, buffer);
21630a5b536SDennis Zhou }
2173c7be18aSRoman Gushchin }
21830a5b536SDennis Zhou
21930a5b536SDennis Zhou spin_unlock_irq(&pcpu_lock);
22030a5b536SDennis Zhou
22130a5b536SDennis Zhou vfree(buffer);
22230a5b536SDennis Zhou
22330a5b536SDennis Zhou return 0;
22430a5b536SDennis Zhou }
2255ad35093SAndy Shevchenko DEFINE_SHOW_ATTRIBUTE(percpu_stats);
22630a5b536SDennis Zhou
init_percpu_stats_debugfs(void)22730a5b536SDennis Zhou static int __init init_percpu_stats_debugfs(void)
22830a5b536SDennis Zhou {
22930a5b536SDennis Zhou debugfs_create_file("percpu_stats", 0444, NULL, NULL,
23030a5b536SDennis Zhou &percpu_stats_fops);
23130a5b536SDennis Zhou
23230a5b536SDennis Zhou return 0;
23330a5b536SDennis Zhou }
23430a5b536SDennis Zhou
23530a5b536SDennis Zhou late_initcall(init_percpu_stats_debugfs);
236