master
c 399 lines 12.6 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "database/rrd.h"
4
5 #define PLUGIN_SLABINFO_NAME "slabinfo.plugin"
6 #define PLUGIN_SLABINFO_PROCFILE "/proc/slabinfo"
7
8 #define CHART_TYPE "mem"
9 #define CHART_FAMILY "slab"
10 #define CHART_PRIO 3000
11
12 // #define slabdebug(...) if (debug) { fprintf(stderr, __VA_ARGS__); }
13 #define slabdebug(args...) do { \
14 if (debug) { \
15 fprintf(stderr, "slabinfo.plugin DEBUG (%04d@%-10.10s:%-15.15s)::", __LINE__, __FILE__, __FUNCTION__); \
16 fprintf(stderr, ##args); \
17 fprintf(stderr, "\n"); \
18 } \
19 } while(0)
20
21 int running = 1;
22 int debug = 0;
23 size_t lines_discovered = 0;
24 int redraw_chart = 0;
25
26 // ----------------------------------------------------------------------------
27
28 // Slabinfo format :
29 // format 2.1 Was provided by 57ed3eda977a215f054102b460ab0eb5d8d112e6 (2.6.24-rc6) as:
30 // seq_puts(m, "# name <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab>");
31 // seq_puts(m, " : tunables <limit> <batchcount> <sharedfactor>");
32 // seq_puts(m, " : slabdata <active_slabs> <num_slabs> <sharedavail>");
33 //
34 // With max values:
35 // seq_printf(m, "%-17s %6lu %6lu %6u %4u %4d",
36 // cache_name(s), sinfo.active_objs, sinfo.num_objs, s->size, sinfo.objects_per_slab, (1 << sinfo.cache_order));
37 // seq_printf(m, " : tunables %4u %4u %4u",
38 // sinfo.limit, sinfo.batchcount, sinfo.shared);
39 // seq_printf(m, " : slabdata %6lu %6lu %6lu",
40 // sinfo.active_slabs, sinfo.num_slabs, sinfo.shared_avail);
41 //
42 // If CONFIG_DEBUG_SLAB is set, it will also add columns from slabinfo_show_stats (for SLAB only):
43 // seq_printf(m, " : globalstat %7lu %6lu %5lu %4lu %4lu %4lu %4lu %4lu %4lu",
44 // allocs, high, grown, reaped, errors, max_freeable, node_allocs, node_frees, overflows);
45 // seq_printf(m, " : cpustat %6lu %6lu %6lu %6lu",
46 // allochit, allocmiss, freehit, freemiss);
47 //
48 // Implementation choices:
49 // - Iterates through a linked list of kmem_cache.
50 // - Name is a char* from struct kmem_cache (mm/slab.h).
51 // - max name size found is 24:
52 // grep -roP 'kmem_cache_create\(".+"'| awk '{split($0,a,"\""); print a[2],length(a[2]); }' | sort -k2 -n
53 // - Using uint64 everywhere, as types fits and allows to use standard helpers
54
55 struct slabinfo {
56 // procfile fields
57 const char *name;
58 uint64_t active_objs;
59 uint64_t num_objs;
60 uint64_t obj_size;
61 uint64_t obj_per_slab;
62 uint64_t pages_per_slab;
63 uint64_t tune_limit;
64 uint64_t tune_batchcnt;
65 uint64_t tune_shared_factor;
66 uint64_t data_active_slabs;
67 uint64_t data_num_slabs;
68 uint64_t data_shared_avail;
69
70 // Calculated fields
71 uint64_t mem_usage;
72 uint64_t mem_waste;
73 uint8_t obj_filling;
74
75 uint32_t hash;
76 struct slabinfo *next;
77 } *slabinfo_root = NULL, *slabinfo_next = NULL, *slabinfo_last_used = NULL;
78
79 // The code is very inspired from "proc_net_dev.c" and "perf_plugin.c"
80
81 // Get the existing object, or create a new one
82 static struct slabinfo *get_slabstruct(const char *name) {
83 struct slabinfo *s;
84
85 slabdebug("--> Requested slabstruct %s", name);
86
87 uint32_t hash = simple_hash(name);
88
89 // Search it, from the next to the end
90 for (s = slabinfo_next; s; s = s->next) {
91 if ((hash = s->hash) && !strcmp(name, s->name)) {
92 slabdebug("<-- Found existing slabstruct after %s", slabinfo_last_used->name);
93 // Prepare the next run
94 slabinfo_next = s->next;
95 slabinfo_last_used = s;
96 return s;
97 }
98 }
99
100 // Search it from the beginning to the last position we used
101 for (s = slabinfo_root; s != slabinfo_last_used; s = s->next) {
102 if (hash == s->hash && !strcmp(name, s->name)) {
103 slabdebug("<-- Found existing slabstruct after root %s", slabinfo_root->name);
104 slabinfo_next = s->next;
105 slabinfo_last_used = s;
106 return s;
107 }
108 }
109
110 // Create a new one
111 s = callocz(1, sizeof(struct slabinfo));
112 s->name = strdupz(name);
113 s->hash = hash;
114
115 // Add it to the current position
116 if (slabinfo_root) {
117 slabdebug("<-- Creating new slabstruct after %s", slabinfo_last_used->name);
118 s->next = slabinfo_last_used->next;
119 slabinfo_last_used->next = s;
120 slabinfo_last_used = s;
121 }
122 else {
123 slabdebug("<-- Creating new slabstruct as root");
124 slabinfo_root = slabinfo_last_used = s;
125 }
126
127 return s;
128 }
129
130
131 // Read a full pass of slabinfo to update the structs
132 struct slabinfo *read_file_slabinfo() {
133
134 slabdebug("-> Reading procfile %s", PLUGIN_SLABINFO_PROCFILE);
135
136 static procfile *ff = NULL;
137 static long slab_pagesize = 0;
138
139 if (unlikely(!slab_pagesize)) {
140 slab_pagesize = sysconf(_SC_PAGESIZE);
141 slabdebug(" Discovered pagesize: %ld", slab_pagesize);
142 }
143
144 if(unlikely(!ff)) {
145 ff = procfile_reopen(ff, PLUGIN_SLABINFO_PROCFILE, " ,:" , PROCFILE_FLAG_DEFAULT);
146 if(unlikely(!ff)) {
147 collector_error("<- Cannot open file '%s", PLUGIN_SLABINFO_PROCFILE);
148 exit(1);
149 }
150 }
151
152 ff = procfile_readall(ff);
153 if(unlikely(!ff)) {
154 collector_error("<- Cannot read file '%s'", PLUGIN_SLABINFO_PROCFILE);
155 exit(0);
156 }
157
158
159 // Iterate on all lines to populate / update the slabinfo struct
160 size_t lines = procfile_lines(ff), l;
161 if (unlikely(lines != lines_discovered)) {
162 lines_discovered = lines;
163 redraw_chart = 1;
164 }
165
166 slabdebug(" Read %lu lines from procfile", (unsigned long)lines);
167 for(l = 2; l < lines; l++) {
168 if (unlikely(procfile_linewords(ff, l) < 14)) {
169 slabdebug(" Line %zu has only %zu words, skipping", l, (size_t)procfile_linewords(ff,l));
170 continue;
171 }
172
173 char *name = procfile_lineword(ff, l, 0);
174 struct slabinfo *s = get_slabstruct(name);
175
176 s->active_objs = str2uint64_t(procfile_lineword(ff, l, 1), NULL);
177 s->num_objs = str2uint64_t(procfile_lineword(ff, l, 2), NULL);
178 s->obj_size = str2uint64_t(procfile_lineword(ff, l, 3), NULL);
179 s->obj_per_slab = str2uint64_t(procfile_lineword(ff, l, 4), NULL);
180 s->pages_per_slab = str2uint64_t(procfile_lineword(ff, l, 5), NULL);
181
182 s->tune_limit = str2uint64_t(procfile_lineword(ff, l, 7), NULL);
183 s->tune_batchcnt = str2uint64_t(procfile_lineword(ff, l, 8), NULL);
184 s->tune_shared_factor = str2uint64_t(procfile_lineword(ff, l, 9), NULL);
185
186 s->data_active_slabs = str2uint64_t(procfile_lineword(ff, l, 11), NULL);
187 s->data_num_slabs = str2uint64_t(procfile_lineword(ff, l, 12), NULL);
188 s->data_shared_avail = str2uint64_t(procfile_lineword(ff, l, 13), NULL);
189
190 uint32_t memperslab = s->pages_per_slab * slab_pagesize;
191 // Internal fragmentation: loss per slab, due to objects not being a multiple of pagesize
192 //uint32_t lossperslab = memperslab - s->obj_per_slab * s->obj_size;
193
194 // Total usage = slabs * pages per slab * page size
195 s->mem_usage = (uint64_t)(s->data_num_slabs * memperslab);
196
197 // Wasted memory (filling): slabs allocated but not filled: sum total slab - sum total objects
198 s->mem_waste = s->mem_usage - (uint64_t)(s->active_objs * s->obj_size);
199 //if (s->data_num_slabs > 1)
200 // s->mem_waste += s->data_num_slabs * lossperslab;
201
202
203 // Slab filling efficiency
204 if (s->num_objs > 0)
205 s->obj_filling = 100 * s->active_objs / s->num_objs;
206 else
207 s->obj_filling = 0;
208
209 slabdebug(" Updated slab %s: %"PRIu64" %"PRIu64" %"PRIu64" %"PRIu64" %"PRIu64" / %"PRIu64" %"PRIu64" %"PRIu64" / %"PRIu64" %"PRIu64" %"PRIu64" / %"PRIu64" %"PRIu64" %hhu",
210 name, s->active_objs, s->num_objs, s->obj_size, s->obj_per_slab, s->pages_per_slab,
211 s->tune_limit, s->tune_batchcnt, s->tune_shared_factor,
212 s->data_active_slabs, s->data_num_slabs, s->data_shared_avail,
213 s->mem_usage, s->mem_waste, s->obj_filling);
214 }
215
216 return slabinfo_root;
217 }
218
219
220
221 unsigned int do_slab_stats(int update_every) {
222
223 static unsigned int loops = 0;
224 struct slabinfo *sactive = NULL, *s = NULL;
225
226 // Main processing loop
227 while (running) {
228
229 sactive = read_file_slabinfo();
230
231 // Init Charts
232 if (unlikely(redraw_chart)) {
233 redraw_chart = 0;
234 // Memory Usage
235 printf("CHART %s.%s '' 'Memory Usage' 'B' '%s' '' line %d %d %s\n"
236 , CHART_TYPE
237 , "slabmemory"
238 , CHART_FAMILY
239 , CHART_PRIO
240 , update_every
241 , PLUGIN_SLABINFO_NAME
242 );
243 for (s = sactive; s; s = s->next) {
244 printf("DIMENSION %s '' absolute 1 1\n", s->name);
245 }
246
247 // Slab active usage (filling)
248 printf("CHART %s.%s '' 'Object Filling' '%%' '%s' '' line %d %d %s\n"
249 , CHART_TYPE
250 , "slabfilling"
251 , CHART_FAMILY
252 , CHART_PRIO + 1
253 , update_every
254 , PLUGIN_SLABINFO_NAME
255 );
256 for (s = sactive; s; s = s->next) {
257 printf("DIMENSION %s '' absolute 1 1\n", s->name);
258 }
259
260 // Memory waste
261 printf("CHART %s.%s '' 'Memory waste' 'B' '%s' '' line %d %d %s\n"
262 , CHART_TYPE
263 , "slabwaste"
264 , CHART_FAMILY
265 , CHART_PRIO + 2
266 , update_every
267 , PLUGIN_SLABINFO_NAME
268 );
269 for (s = sactive; s; s = s->next) {
270 printf("DIMENSION %s '' absolute 1 1\n", s->name);
271 }
272 }
273
274
275 //
276 // Memory usage
277 //
278 printf("BEGIN %s.%s\n"
279 , CHART_TYPE
280 , "slabmemory"
281 );
282 for (s = sactive; s; s = s->next) {
283 printf("SET %s = %"PRIu64"\n"
284 , s->name
285 , s->mem_usage
286 );
287 }
288 printf("END\n");
289
290 //
291 // Slab active usage
292 //
293 printf("BEGIN %s.%s\n"
294 , CHART_TYPE
295 , "slabfilling"
296 );
297 for (s = sactive; s; s = s->next) {
298 printf("SET %s = %u\n"
299 , s->name
300 , s->obj_filling
301 );
302 }
303 printf("END\n");
304
305 //
306 // Memory waste
307 //
308 printf("BEGIN %s.%s\n"
309 , CHART_TYPE
310 , "slabwaste"
311 );
312 for (s = sactive; s; s = s->next) {
313 printf("SET %s = %"PRIu64"\n"
314 , s->name
315 , s->mem_waste
316 );
317 }
318 printf("END\n");
319
320 fprintf(stdout, "\n");
321 fflush(stdout);
322 if (ferror(stdout) && errno == EPIPE) {
323 netdata_log_error("error writing to stdout: EPIPE. Exiting...");
324 return loops;
325 }
326
327 loops++;
328
329 sleep(update_every);
330 }
331
332 return loops;
333 }
334
335
336
337
338 // ----------------------------------------------------------------------------
339 // main
340
341 void usage(void) {
342 fprintf(stderr, "%s\n", program_name);
343 exit(1);
344 }
345
346 int main(int argc, char **argv) {
347 nd_log_initialize_for_external_plugins("slabinfo.plugin");
348 netdata_threads_init_for_external_plugins(0);
349
350 program_name = argv[0];
351 int update_every = 1, i, n, freq = 0;
352
353 for (i = 1; i < argc; i++) {
354 // Frequency parsing
355 if(isdigit(*argv[i]) && !freq) {
356 n = (int) str2l(argv[i]);
357 if (n > 0) {
358 if (n >= UPDATE_EVERY_MAX) {
359 collector_error("Invalid interval value: %s", argv[i]);
360 exit(1);
361 }
362 freq = n;
363 }
364 }
365 else if (strcmp("debug", argv[i]) == 0) {
366 debug = 1;
367 continue;
368 }
369 else {
370 fprintf(stderr,
371 "netdata slabinfo.plugin %s\n"
372 "This program is a data collector plugin for netdata.\n"
373 "\n"
374 "Available command line options:\n"
375 "\n"
376 " COLLECTION_FREQUENCY data collection frequency in seconds\n"
377 " minimum: %d\n"
378 "\n"
379 " debug enable verbose output\n"
380 " default: disabled\n"
381 "\n",
382 NETDATA_VERSION,
383 update_every
384 );
385 exit(1);
386 }
387 }
388
389 if(freq >= update_every)
390 update_every = freq;
391 else if(freq)
392 collector_error("update frequency %d seconds is too small for slabinfo. Using %d.", freq, update_every);
393
394
395 // Call the main function. Time drift to be added
396 do_slab_stats(update_every);
397
398 return 0;
399 }