xref: /openbmc/linux/mm/percpu-stats.c (revision 7aacf86b)
1 /*
2  * mm/percpu-debug.c
3  *
4  * Copyright (C) 2017		Facebook Inc.
5  * Copyright (C) 2017		Dennis Zhou <dennisz@fb.com>
6  *
7  * This file is released under the GPLv2.
8  *
9  * Prints statistics about the percpu allocator and backing chunks.
10  */
11 #include <linux/debugfs.h>
12 #include <linux/list.h>
13 #include <linux/percpu.h>
14 #include <linux/seq_file.h>
15 #include <linux/sort.h>
16 #include <linux/vmalloc.h>
17 
18 #include "percpu-internal.h"
19 
20 #define P(X, Y) \
21 	seq_printf(m, "  %-24s: %8lld\n", X, (long long int)Y)
22 
23 struct percpu_stats pcpu_stats;
24 struct pcpu_alloc_info pcpu_stats_ai;
25 
26 static int cmpint(const void *a, const void *b)
27 {
28 	return *(int *)a - *(int *)b;
29 }
30 
31 /*
32  * Iterates over all chunks to find the max # of map entries used.
33  */
34 static int find_max_map_used(void)
35 {
36 	struct pcpu_chunk *chunk;
37 	int slot, max_map_used;
38 
39 	max_map_used = 0;
40 	for (slot = 0; slot < pcpu_nr_slots; slot++)
41 		list_for_each_entry(chunk, &pcpu_slot[slot], list)
42 			max_map_used = max(max_map_used, chunk->map_used);
43 
44 	return max_map_used;
45 }
46 
47 /*
48  * Prints out chunk state. Fragmentation is considered between
49  * the beginning of the chunk to the last allocation.
50  */
51 static void chunk_map_stats(struct seq_file *m, struct pcpu_chunk *chunk,
52 			    void *buffer)
53 {
54 	int i, s_index, last_alloc, alloc_sign, as_len;
55 	int *alloc_sizes, *p;
56 	/* statistics */
57 	int sum_frag = 0, max_frag = 0;
58 	int cur_min_alloc = 0, cur_med_alloc = 0, cur_max_alloc = 0;
59 
60 	alloc_sizes = buffer;
61 	s_index = chunk->has_reserved ? 1 : 0;
62 
63 	/* find last allocation */
64 	last_alloc = -1;
65 	for (i = chunk->map_used - 1; i >= s_index; i--) {
66 		if (chunk->map[i] & 1) {
67 			last_alloc = i;
68 			break;
69 		}
70 	}
71 
72 	/* if the chunk is not empty - ignoring reserve */
73 	if (last_alloc >= s_index) {
74 		as_len = last_alloc + 1 - s_index;
75 
76 		/*
77 		 * Iterate through chunk map computing size info.
78 		 * The first bit is overloaded to be a used flag.
79 		 * negative = free space, positive = allocated
80 		 */
81 		for (i = 0, p = chunk->map + s_index; i < as_len; i++, p++) {
82 			alloc_sign = (*p & 1) ? 1 : -1;
83 			alloc_sizes[i] = alloc_sign *
84 				((p[1] & ~1) - (p[0] & ~1));
85 		}
86 
87 		sort(alloc_sizes, as_len, sizeof(chunk->map[0]), cmpint, NULL);
88 
89 		/* Iterate through the unallocated fragements. */
90 		for (i = 0, p = alloc_sizes; *p < 0 && i < as_len; i++, p++) {
91 			sum_frag -= *p;
92 			max_frag = max(max_frag, -1 * (*p));
93 		}
94 
95 		cur_min_alloc = alloc_sizes[i];
96 		cur_med_alloc = alloc_sizes[(i + as_len - 1) / 2];
97 		cur_max_alloc = alloc_sizes[as_len - 1];
98 	}
99 
100 	P("nr_alloc", chunk->nr_alloc);
101 	P("max_alloc_size", chunk->max_alloc_size);
102 	P("free_size", chunk->free_size);
103 	P("contig_hint", chunk->contig_hint);
104 	P("sum_frag", sum_frag);
105 	P("max_frag", max_frag);
106 	P("cur_min_alloc", cur_min_alloc);
107 	P("cur_med_alloc", cur_med_alloc);
108 	P("cur_max_alloc", cur_max_alloc);
109 	seq_putc(m, '\n');
110 }
111 
112 static int percpu_stats_show(struct seq_file *m, void *v)
113 {
114 	struct pcpu_chunk *chunk;
115 	int slot, max_map_used;
116 	void *buffer;
117 
118 alloc_buffer:
119 	spin_lock_irq(&pcpu_lock);
120 	max_map_used = find_max_map_used();
121 	spin_unlock_irq(&pcpu_lock);
122 
123 	buffer = vmalloc(max_map_used * sizeof(pcpu_first_chunk->map[0]));
124 	if (!buffer)
125 		return -ENOMEM;
126 
127 	spin_lock_irq(&pcpu_lock);
128 
129 	/* if the buffer allocated earlier is too small */
130 	if (max_map_used < find_max_map_used()) {
131 		spin_unlock_irq(&pcpu_lock);
132 		vfree(buffer);
133 		goto alloc_buffer;
134 	}
135 
136 #define PL(X) \
137 	seq_printf(m, "  %-24s: %8lld\n", #X, (long long int)pcpu_stats_ai.X)
138 
139 	seq_printf(m,
140 			"Percpu Memory Statistics\n"
141 			"Allocation Info:\n"
142 			"----------------------------------------\n");
143 	PL(unit_size);
144 	PL(static_size);
145 	PL(reserved_size);
146 	PL(dyn_size);
147 	PL(atom_size);
148 	PL(alloc_size);
149 	seq_putc(m, '\n');
150 
151 #undef PL
152 
153 #define PU(X) \
154 	seq_printf(m, "  %-18s: %14llu\n", #X, (unsigned long long)pcpu_stats.X)
155 
156 	seq_printf(m,
157 			"Global Stats:\n"
158 			"----------------------------------------\n");
159 	PU(nr_alloc);
160 	PU(nr_dealloc);
161 	PU(nr_cur_alloc);
162 	PU(nr_max_alloc);
163 	PU(nr_chunks);
164 	PU(nr_max_chunks);
165 	PU(min_alloc_size);
166 	PU(max_alloc_size);
167 	seq_putc(m, '\n');
168 
169 #undef PU
170 
171 	seq_printf(m,
172 			"Per Chunk Stats:\n"
173 			"----------------------------------------\n");
174 
175 	if (pcpu_reserved_chunk) {
176 		seq_puts(m, "Chunk: <- Reserved Chunk\n");
177 		chunk_map_stats(m, pcpu_reserved_chunk, buffer);
178 	}
179 
180 	for (slot = 0; slot < pcpu_nr_slots; slot++) {
181 		list_for_each_entry(chunk, &pcpu_slot[slot], list) {
182 			if (chunk == pcpu_first_chunk) {
183 				seq_puts(m, "Chunk: <- First Chunk\n");
184 				chunk_map_stats(m, chunk, buffer);
185 
186 
187 			} else {
188 				seq_puts(m, "Chunk:\n");
189 				chunk_map_stats(m, chunk, buffer);
190 			}
191 
192 		}
193 	}
194 
195 	spin_unlock_irq(&pcpu_lock);
196 
197 	vfree(buffer);
198 
199 	return 0;
200 }
201 
202 static int percpu_stats_open(struct inode *inode, struct file *filp)
203 {
204 	return single_open(filp, percpu_stats_show, NULL);
205 }
206 
207 static const struct file_operations percpu_stats_fops = {
208 	.open		= percpu_stats_open,
209 	.read		= seq_read,
210 	.llseek		= seq_lseek,
211 	.release	= single_release,
212 };
213 
214 static int __init init_percpu_stats_debugfs(void)
215 {
216 	debugfs_create_file("percpu_stats", 0444, NULL, NULL,
217 			&percpu_stats_fops);
218 
219 	return 0;
220 }
221 
222 late_initcall(init_percpu_stats_debugfs);
223