diff options
Diffstat (limited to '')
-rw-r--r-- | collectors/slabinfo.plugin/slabinfo.c | 393 |
1 files changed, 393 insertions, 0 deletions
diff --git a/collectors/slabinfo.plugin/slabinfo.c b/collectors/slabinfo.plugin/slabinfo.c new file mode 100644 index 00000000..9b9119a6 --- /dev/null +++ b/collectors/slabinfo.plugin/slabinfo.c @@ -0,0 +1,393 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "daemon/common.h" +#include "libnetdata/required_dummies.h" + +#define PLUGIN_SLABINFO_NAME "slabinfo.plugin" +#define PLUGIN_SLABINFO_PROCFILE "/proc/slabinfo" + +#define CHART_TYPE "mem" +#define CHART_FAMILY "slab" +#define CHART_PRIO 3000 + +// #define slabdebug(...) if (debug) { fprintf(stderr, __VA_ARGS__); } +#define slabdebug(args...) if (debug) { \ + fprintf(stderr, "slabinfo.plugin DEBUG (%04d@%-10.10s:%-15.15s)::", __LINE__, __FILE__, __FUNCTION__); \ + fprintf(stderr, ##args); \ + fprintf(stderr, "\n"); } + +int running = 1; +int debug = 0; +size_t lines_discovered = 0; +int redraw_chart = 0; + +// ---------------------------------------------------------------------------- + +// Slabinfo format : +// format 2.1 Was provided by 57ed3eda977a215f054102b460ab0eb5d8d112e6 (2.6.24-rc6) as: +// seq_puts(m, "# name <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab>"); +// seq_puts(m, " : tunables <limit> <batchcount> <sharedfactor>"); +// seq_puts(m, " : slabdata <active_slabs> <num_slabs> <sharedavail>"); +// +// With max values: +// seq_printf(m, "%-17s %6lu %6lu %6u %4u %4d", +// cache_name(s), sinfo.active_objs, sinfo.num_objs, s->size, sinfo.objects_per_slab, (1 << sinfo.cache_order)); +// seq_printf(m, " : tunables %4u %4u %4u", +// sinfo.limit, sinfo.batchcount, sinfo.shared); +// seq_printf(m, " : slabdata %6lu %6lu %6lu", +// sinfo.active_slabs, sinfo.num_slabs, sinfo.shared_avail); +// +// If CONFIG_DEBUG_SLAB is set, it will also add columns from slabinfo_show_stats (for SLAB only): +// seq_printf(m, " : globalstat %7lu %6lu %5lu %4lu %4lu %4lu %4lu %4lu %4lu", +// allocs, high, grown, reaped, errors, max_freeable, node_allocs, node_frees, overflows); +// seq_printf(m, " : cpustat %6lu %6lu %6lu %6lu", +// allochit, allocmiss, freehit, freemiss); +// +// Implementation choices: +// - Iterates through a linked list of kmem_cache. +// - Name is a char* from struct kmem_cache (mm/slab.h). +// - max name size found is 24: +// grep -roP 'kmem_cache_create\(".+"'| awk '{split($0,a,"\""); print a[2],length(a[2]); }' | sort -k2 -n +// - Using uint64 everywhere, as types fits and allows to use standard helpers + +struct slabinfo { + // procfile fields + const char *name; + uint64_t active_objs; + uint64_t num_objs; + uint64_t obj_size; + uint64_t obj_per_slab; + uint64_t pages_per_slab; + uint64_t tune_limit; + uint64_t tune_batchcnt; + uint64_t tune_shared_factor; + uint64_t data_active_slabs; + uint64_t data_num_slabs; + uint64_t data_shared_avail; + + // Calculated fields + uint64_t mem_usage; + uint64_t mem_waste; + uint8_t obj_filling; + + uint32_t hash; + struct slabinfo *next; +} *slabinfo_root = NULL, *slabinfo_next = NULL, *slabinfo_last_used = NULL; + +// The code is very inspired from "proc_net_dev.c" and "perf_plugin.c" + +// Get the existing object, or create a new one +static struct slabinfo *get_slabstruct(const char *name) { + struct slabinfo *s; + + slabdebug("--> Requested slabstruct %s", name); + + uint32_t hash = simple_hash(name); + + // Search it, from the next to the end + for (s = slabinfo_next; s; s = s->next) { + if ((hash = s->hash) && !strcmp(name, s->name)) { + slabdebug("<-- Found existing slabstruct after %s", slabinfo_last_used->name); + // Prepare the next run + slabinfo_next = s->next; + slabinfo_last_used = s; + return s; + } + } + + // Search it from the beginning to the last position we used + for (s = slabinfo_root; s != slabinfo_last_used; s = s->next) { + if (hash == s->hash && !strcmp(name, s->name)) { + slabdebug("<-- Found existing slabstruct after root %s", slabinfo_root->name); + slabinfo_next = s->next; + slabinfo_last_used = s; + return s; + } + } + + // Create a new one + s = callocz(1, sizeof(struct slabinfo)); + s->name = strdupz(name); + s->hash = hash; + + // Add it to the current position + if (slabinfo_root) { + slabdebug("<-- Creating new slabstruct after %s", slabinfo_last_used->name); + s->next = slabinfo_last_used->next; + slabinfo_last_used->next = s; + slabinfo_last_used = s; + } + else { + slabdebug("<-- Creating new slabstruct as root"); + slabinfo_root = slabinfo_last_used = s; + } + + return s; +} + + +// Read a full pass of slabinfo to update the structs +struct slabinfo *read_file_slabinfo() { + + slabdebug("-> Reading procfile %s", PLUGIN_SLABINFO_PROCFILE); + + static procfile *ff = NULL; + static long slab_pagesize = 0; + + if (unlikely(!slab_pagesize)) { + slab_pagesize = sysconf(_SC_PAGESIZE); + slabdebug(" Discovered pagesize: %ld", slab_pagesize); + } + + if(unlikely(!ff)) { + ff = procfile_reopen(ff, PLUGIN_SLABINFO_PROCFILE, " ,:" , PROCFILE_FLAG_DEFAULT); + if(unlikely(!ff)) { + collector_error("<- Cannot open file '%s", PLUGIN_SLABINFO_PROCFILE); + exit(1); + } + } + + ff = procfile_readall(ff); + if(unlikely(!ff)) { + collector_error("<- Cannot read file '%s'", PLUGIN_SLABINFO_PROCFILE); + exit(0); + } + + + // Iterate on all lines to populate / update the slabinfo struct + size_t lines = procfile_lines(ff), l; + if (unlikely(lines != lines_discovered)) { + lines_discovered = lines; + redraw_chart = 1; + } + + slabdebug(" Read %lu lines from procfile", (unsigned long)lines); + for(l = 2; l < lines; l++) { + if (unlikely(procfile_linewords(ff, l) < 14)) { + slabdebug(" Line %zu has only %zu words, skipping", l, procfile_linewords(ff,l)); + continue; + } + + char *name = procfile_lineword(ff, l, 0); + struct slabinfo *s = get_slabstruct(name); + + s->active_objs = str2uint64_t(procfile_lineword(ff, l, 1), NULL); + s->num_objs = str2uint64_t(procfile_lineword(ff, l, 2), NULL); + s->obj_size = str2uint64_t(procfile_lineword(ff, l, 3), NULL); + s->obj_per_slab = str2uint64_t(procfile_lineword(ff, l, 4), NULL); + s->pages_per_slab = str2uint64_t(procfile_lineword(ff, l, 5), NULL); + + s->tune_limit = str2uint64_t(procfile_lineword(ff, l, 7), NULL); + s->tune_batchcnt = str2uint64_t(procfile_lineword(ff, l, 8), NULL); + s->tune_shared_factor = str2uint64_t(procfile_lineword(ff, l, 9), NULL); + + s->data_active_slabs = str2uint64_t(procfile_lineword(ff, l, 11), NULL); + s->data_num_slabs = str2uint64_t(procfile_lineword(ff, l, 12), NULL); + s->data_shared_avail = str2uint64_t(procfile_lineword(ff, l, 13), NULL); + + uint32_t memperslab = s->pages_per_slab * slab_pagesize; + // Internal fragmentation: loss per slab, due to objects not being a multiple of pagesize + //uint32_t lossperslab = memperslab - s->obj_per_slab * s->obj_size; + + // Total usage = slabs * pages per slab * page size + s->mem_usage = (uint64_t)(s->data_num_slabs * memperslab); + + // Wasted memory (filling): slabs allocated but not filled: sum total slab - sum total objects + s->mem_waste = s->mem_usage - (uint64_t)(s->active_objs * s->obj_size); + //if (s->data_num_slabs > 1) + // s->mem_waste += s->data_num_slabs * lossperslab; + + + // Slab filling efficiency + if (s->num_objs > 0) + s->obj_filling = 100 * s->active_objs / s->num_objs; + else + s->obj_filling = 0; + + slabdebug(" Updated slab %s: %"PRIu64" %"PRIu64" %"PRIu64" %"PRIu64" %"PRIu64" / %"PRIu64" %"PRIu64" %"PRIu64" / %"PRIu64" %"PRIu64" %"PRIu64" / %"PRIu64" %"PRIu64" %hhu", + name, s->active_objs, s->num_objs, s->obj_size, s->obj_per_slab, s->pages_per_slab, + s->tune_limit, s->tune_batchcnt, s->tune_shared_factor, + s->data_active_slabs, s->data_num_slabs, s->data_shared_avail, + s->mem_usage, s->mem_waste, s->obj_filling); + } + + return slabinfo_root; +} + + + +unsigned int do_slab_stats(int update_every) { + + static unsigned int loops = 0; + struct slabinfo *sactive = NULL, *s = NULL; + + // Main processing loop + while (running) { + + sactive = read_file_slabinfo(); + + // Init Charts + if (unlikely(redraw_chart)) { + redraw_chart = 0; + // Memory Usage + printf("CHART %s.%s '' 'Memory Usage' 'B' '%s' '' line %d %d %s\n" + , CHART_TYPE + , "slabmemory" + , CHART_FAMILY + , CHART_PRIO + , update_every + , PLUGIN_SLABINFO_NAME + ); + for (s = sactive; s; s = s->next) { + printf("DIMENSION %s '' absolute 1 1\n", s->name); + } + + // Slab active usage (filling) + printf("CHART %s.%s '' 'Object Filling' '%%' '%s' '' line %d %d %s\n" + , CHART_TYPE + , "slabfilling" + , CHART_FAMILY + , CHART_PRIO + 1 + , update_every + , PLUGIN_SLABINFO_NAME + ); + for (s = sactive; s; s = s->next) { + printf("DIMENSION %s '' absolute 1 1\n", s->name); + } + + // Memory waste + printf("CHART %s.%s '' 'Memory waste' 'B' '%s' '' line %d %d %s\n" + , CHART_TYPE + , "slabwaste" + , CHART_FAMILY + , CHART_PRIO + 2 + , update_every + , PLUGIN_SLABINFO_NAME + ); + for (s = sactive; s; s = s->next) { + printf("DIMENSION %s '' absolute 1 1\n", s->name); + } + } + + + // + // Memory usage + // + printf("BEGIN %s.%s\n" + , CHART_TYPE + , "slabmemory" + ); + for (s = sactive; s; s = s->next) { + printf("SET %s = %"PRIu64"\n" + , s->name + , s->mem_usage + ); + } + printf("END\n"); + + // + // Slab active usage + // + printf("BEGIN %s.%s\n" + , CHART_TYPE + , "slabfilling" + ); + for (s = sactive; s; s = s->next) { + printf("SET %s = %u\n" + , s->name + , s->obj_filling + ); + } + printf("END\n"); + + // + // Memory waste + // + printf("BEGIN %s.%s\n" + , CHART_TYPE + , "slabwaste" + ); + for (s = sactive; s; s = s->next) { + printf("SET %s = %"PRIu64"\n" + , s->name + , s->mem_waste + ); + } + printf("END\n"); + + + loops++; + + sleep(update_every); + } + + return loops; +} + + + + +// ---------------------------------------------------------------------------- +// main + +void usage(void) { + fprintf(stderr, "%s\n", program_name); + exit(1); +} + +int main(int argc, char **argv) { + clocks_init(); + nd_log_initialize_for_external_plugins("slabinfo.plugin"); + + program_name = argv[0]; + program_version = "0.1"; + + int update_every = 1, i, n, freq = 0; + + for (i = 1; i < argc; i++) { + // Frequency parsing + if(isdigit(*argv[i]) && !freq) { + n = (int) str2l(argv[i]); + if (n > 0) { + if (n >= UPDATE_EVERY_MAX) { + collector_error("Invalid interval value: %s", argv[i]); + exit(1); + } + freq = n; + } + } + else if (strcmp("debug", argv[i]) == 0) { + debug = 1; + continue; + } + else { + fprintf(stderr, + "netdata slabinfo.plugin %s\n" + "This program is a data collector plugin for netdata.\n" + "\n" + "Available command line options:\n" + "\n" + " COLLECTION_FREQUENCY data collection frequency in seconds\n" + " minimum: %d\n" + "\n" + " debug enable verbose output\n" + " default: disabled\n" + "\n", + program_version, + update_every + ); + exit(1); + } + } + + if(freq >= update_every) + update_every = freq; + else if(freq) + collector_error("update frequency %d seconds is too small for slabinfo. Using %d.", freq, update_every); + + + // Call the main function. Time drift to be added + do_slab_stats(update_every); + + return 0; +} |