summaryrefslogtreecommitdiffstats
path: root/collectors/slabinfo.plugin/slabinfo.c
diff options
context:
space:
mode:
Diffstat (limited to 'collectors/slabinfo.plugin/slabinfo.c')
-rw-r--r--collectors/slabinfo.plugin/slabinfo.c393
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;
+}