summaryrefslogtreecommitdiffstats
path: root/collectors/slabinfo.plugin
diff options
context:
space:
mode:
Diffstat (limited to 'collectors/slabinfo.plugin')
-rw-r--r--collectors/slabinfo.plugin/Makefile.am14
-rw-r--r--collectors/slabinfo.plugin/README.md29
-rw-r--r--collectors/slabinfo.plugin/slabinfo.c418
3 files changed, 461 insertions, 0 deletions
diff --git a/collectors/slabinfo.plugin/Makefile.am b/collectors/slabinfo.plugin/Makefile.am
new file mode 100644
index 0000000..07796ea
--- /dev/null
+++ b/collectors/slabinfo.plugin/Makefile.am
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+AUTOMAKE_OPTIONS = subdir-objects
+MAINTAINERCLEANFILES = $(srcdir)/Makefile.in
+CLEANFILES = \
+ slabinfo.plugin \
+ $(NULL)
+
+include $(top_srcdir)/build/subst.inc
+SUFFIXES = .in
+
+dist_noinst_DATA = \
+ README.md \
+ $(NULL)
diff --git a/collectors/slabinfo.plugin/README.md b/collectors/slabinfo.plugin/README.md
new file mode 100644
index 0000000..21d83c9
--- /dev/null
+++ b/collectors/slabinfo.plugin/README.md
@@ -0,0 +1,29 @@
+<!--
+title: "slabinfo.plugin"
+custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/slabinfo.plugin/README.md
+-->
+
+# slabinfo.plugin
+
+SLAB is a cache mechanism used by the Kernel to avoid fragmentation.
+
+Each internal structure (process, file descriptor, inode...) is stored within a SLAB.
+
+## configuring Netdata for slabinfo
+
+The plugin is disabled by default because it collects and displays a huge amount of metrics.
+To enable it set `slabinfo = yes` in the `plugins` section of the `netdata.conf` configuration file.
+
+There is currently no configuration needed for the plugin itself.
+
+As `/proc/slabinfo` is only readable by root, this plugin is setuid root.
+
+## For what use
+
+This slabinfo details allows to have clues on actions done on your system.
+In the following screenshot, you can clearly see a `find` done on a ext4 filesystem (the number of `ext4_inode_cache` & `dentry` are rising fast), and a few seconds later, an admin issued a `echo 3 > /proc/sys/vm/drop_cached` as their count dropped.
+
+![netdata_slabinfo](https://user-images.githubusercontent.com/9157986/64433811-7f06e500-d0bf-11e9-8e1e-087497e61033.png)
+
+
+[![analytics](https://www.google-analytics.com/collect?v=1&aip=1&t=pageview&_s=1&ds=github&dr=https%3A%2F%2Fgithub.com%2Fnetdata%2Fnetdata&dl=https%3A%2F%2Fmy-netdata.io%2Fgithub%2Fcollectors%2Fslabinfo.plugin%2FREADME&_u=MAC~&cid=5792dfd7-8dc4-476b-af31-da2fdb9f93d2&tid=UA-64295674-3)](<>)
diff --git a/collectors/slabinfo.plugin/slabinfo.c b/collectors/slabinfo.plugin/slabinfo.c
new file mode 100644
index 0000000..00e0d39
--- /dev/null
+++ b/collectors/slabinfo.plugin/slabinfo.c
@@ -0,0 +1,418 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "libnetdata/libnetdata.h"
+#include "daemon/common.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"); }
+
+
+// ----------------------------------------------------------------------------
+
+// callback required by fatal()
+void netdata_cleanup_and_exit(int ret) {
+ exit(ret);
+}
+
+void send_statistics(const char *action, const char *action_result, const char *action_data) {
+ (void) action;
+ (void) action_result;
+ (void) action_data;
+ return;
+}
+
+// callbacks required by popen()
+void signals_block(void) {};
+void signals_unblock(void) {};
+void signals_reset(void) {};
+
+// callback required by eval()
+int health_variable_lookup(const char *variable, uint32_t hash, struct rrdcalc *rc, calculated_number *result) {
+ (void)variable;
+ (void)hash;
+ (void)rc;
+ (void)result;
+ return 0;
+};
+
+// required by get_system_cpus()
+char *netdata_configured_host_prefix = "";
+
+
+int running = 1;
+int debug = 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 begining 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 postion
+ 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)) {
+ error("<- Cannot open file '%s", PLUGIN_SLABINFO_PROCFILE);
+ exit(1);
+ }
+ }
+
+ ff = procfile_readall(ff);
+ if(unlikely(!ff)) {
+ 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;
+
+ 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));
+ s->num_objs = str2uint64_t(procfile_lineword(ff, l, 2));
+ s->obj_size = str2uint64_t(procfile_lineword(ff, l, 3));
+ s->obj_per_slab = str2uint64_t(procfile_lineword(ff, l, 4));
+ s->pages_per_slab = str2uint64_t(procfile_lineword(ff, l, 5));
+
+ s->tune_limit = str2uint64_t(procfile_lineword(ff, l, 7));
+ s->tune_batchcnt = str2uint64_t(procfile_lineword(ff, l, 8));
+ s->tune_shared_factor = str2uint64_t(procfile_lineword(ff, l, 9));
+
+ s->data_active_slabs = str2uint64_t(procfile_lineword(ff, l, 11));
+ s->data_num_slabs = str2uint64_t(procfile_lineword(ff, l, 12));
+ s->data_shared_avail = str2uint64_t(procfile_lineword(ff, l, 13));
+
+ 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(loops == 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) {
+
+ program_name = argv[0];
+ program_version = "0.1";
+ error_log_syslog = 0;
+
+ 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) {
+ 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)
+ 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;
+}