summaryrefslogtreecommitdiffstats
path: root/collectors/debugfs.plugin
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--collectors/debugfs.plugin/Makefile.am9
-rw-r--r--collectors/debugfs.plugin/README.md65
-rw-r--r--collectors/debugfs.plugin/debugfs_extfrag.c123
-rw-r--r--collectors/debugfs.plugin/debugfs_plugin.c246
-rw-r--r--collectors/debugfs.plugin/debugfs_plugin.h16
-rw-r--r--collectors/debugfs.plugin/debugfs_zswap.c437
-rw-r--r--collectors/debugfs.plugin/metrics.csv12
7 files changed, 908 insertions, 0 deletions
diff --git a/collectors/debugfs.plugin/Makefile.am b/collectors/debugfs.plugin/Makefile.am
new file mode 100644
index 000000000..02fe3a314
--- /dev/null
+++ b/collectors/debugfs.plugin/Makefile.am
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+AUTOMAKE_OPTIONS = subdir-objects
+MAINTAINERCLEANFILES = $(srcdir)/Makefile.in
+
+dist_noinst_DATA = \
+ README.md \
+ $(NULL)
+
diff --git a/collectors/debugfs.plugin/README.md b/collectors/debugfs.plugin/README.md
new file mode 100644
index 000000000..a2dc9c0f6
--- /dev/null
+++ b/collectors/debugfs.plugin/README.md
@@ -0,0 +1,65 @@
+# OS provided metrics (debugfs.plugin)
+
+`debugfs.plugin` gathers metrics from the `/sys/kernel/debug` folder on Linux
+systems. [Debugfs](https://docs.kernel.org/filesystems/debugfs.html) exists as an easy way for kernel developers to
+make information available to user space.
+
+This plugin
+is [external](https://github.com/netdata/netdata/tree/master/collectors#collector-architecture-and-terminology),
+the netdata daemon spawns it as a long-running independent process.
+
+In detail, it collects metrics from:
+
+- `/sys/kernel/debug/extfrag` (Memory fragmentation index for each order and zone).
+- `/sys/kernel/debug/zswap` ([Zswap](https://www.kernel.org/doc/Documentation/vm/zswap.txt) performance statistics).
+
+## Prerequisites
+
+### Permissions
+
+> No user action required.
+
+The debugfs root directory is accessible only to the root user by default. Netdata
+uses [Linux Capabilities](https://man7.org/linux/man-pages/man7/capabilities.7.html) to give the plugin access
+to debugfs. `CAP_DAC_READ_SEARCH` is added automatically during installation. This capability allows bypassing file read
+permission checks and directory read and execute permission checks. If file capabilities are not usable, then the plugin is instead installed with the SUID bit set in permissions so that it runs as root.
+
+## Metrics
+
+| Metric | Scope | Dimensions | Units | Labels |
+|-------------------------------------|:---------:|:---------------------------------------------------------------------------------------:|:------------:|:---------:|
+| mem.fragmentation_index_dma | numa node | order0, order1, order2, order3, order4, order5, order6, order7, order8, order9, order10 | index | numa_node |
+| mem.fragmentation_index_dma32 | numa node | order0, order1, order2, order3, order4, order5, order6, order7, order8, order9, order10 | index | numa_node |
+| mem.fragmentation_index_normal | numa node | order0, order1, order2, order3, order4, order5, order6, order7, order8, order9, order10 | index | numa_node |
+| system.zswap_pool_compression_ratio | | compression_ratio | ratio | |
+| system.zswap_pool_compressed_size | | compressed_size | bytes | |
+| system.zswap_pool_raw_size | | uncompressed_size | bytes | |
+| system.zswap_rejections | | compress_poor, kmemcache_fail, alloc_fail, reclaim_fail | rejections/s | |
+| system.zswap_pool_limit_hit | | limit | events/s | |
+| system.zswap_written_back_raw_bytes | | written_back | bytes/s | |
+| system.zswap_same_filled_raw_size | | same_filled | bytes | |
+| system.zswap_duplicate_entry | | entries | entries/s | |
+
+## Troubleshooting
+
+To troubleshoot issues with the collector, run the `debugfs.plugin` in the terminal. The output
+should give you clues as to why the collector isn't working.
+
+- Navigate to the `plugins.d` directory, usually at `/usr/libexec/netdata/plugins.d/`. If that's not the case on
+ your system, open `netdata.conf` and look for the `plugins` setting under `[directories]`.
+
+ ```bash
+ cd /usr/libexec/netdata/plugins.d/
+ ```
+
+- Switch to the `netdata` user.
+
+ ```bash
+ sudo -u netdata -s
+ ```
+
+- Run the `debugfs.plugin` to debug the collector:
+
+ ```bash
+ ./debugfs.plugin
+ ```
diff --git a/collectors/debugfs.plugin/debugfs_extfrag.c b/collectors/debugfs.plugin/debugfs_extfrag.c
new file mode 100644
index 000000000..75da4deca
--- /dev/null
+++ b/collectors/debugfs.plugin/debugfs_extfrag.c
@@ -0,0 +1,123 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "debugfs_plugin.h"
+
+#define NETDATA_ORDER_FRAGMENTATION 11
+
+static char *orders[NETDATA_ORDER_FRAGMENTATION] = { "order0", "order1", "order2", "order3", "order4",
+ "order5", "order6", "order7", "order8", "order9",
+ "order10"
+};
+
+static struct netdata_extrafrag {
+ char *node_zone;
+ uint32_t hash;
+
+ char *id;
+
+ collected_number orders[NETDATA_ORDER_FRAGMENTATION];
+
+ struct netdata_extrafrag *next;
+} *netdata_extrafrags_root = NULL;
+
+static struct netdata_extrafrag *find_or_create_extrafrag(const char *name)
+{
+ struct netdata_extrafrag *extrafrag;
+ uint32_t hash = simple_hash(name);
+
+ // search it, from beginning to the end
+ for (extrafrag = netdata_extrafrags_root ; extrafrag ; extrafrag = extrafrag->next) {
+ if (unlikely(hash == extrafrag->hash && !strcmp(name, extrafrag->node_zone))) {
+ return extrafrag;
+ }
+ }
+
+ extrafrag = callocz(1, sizeof(struct netdata_extrafrag));
+ extrafrag->node_zone = strdupz(name);
+ extrafrag->hash = hash;
+
+ if (netdata_extrafrags_root) {
+ struct netdata_extrafrag *last_node;
+ for (last_node = netdata_extrafrags_root; last_node->next ; last_node = last_node->next);
+
+ last_node->next = extrafrag;
+ } else
+ netdata_extrafrags_root = extrafrag;
+
+
+ return extrafrag;
+}
+
+static void extfrag_send_chart(char *chart_id, collected_number *values)
+{
+ int i;
+ fprintf(stdout, "BEGIN mem.fragmentation_index_%s\n", chart_id);
+ for (i = 0; i < NETDATA_ORDER_FRAGMENTATION; i++) {
+ fprintf(stdout, "SET %s = %lld\n", orders[i], values[i]);
+ }
+ fprintf(stdout, "END\n");
+ fflush(stdout);
+}
+
+int do_debugfs_extfrag(int update_every, const char *name) {
+ static procfile *ff = NULL;
+ static int chart_order = NETDATA_CHART_PRIO_MEM_FRAGMENTATION;
+
+ if (unlikely(!ff)) {
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename,
+ FILENAME_MAX,
+ "%s%s",
+ netdata_configured_host_prefix,
+ "/sys/kernel/debug/extfrag/extfrag_index");
+
+ ff = procfile_open(filename, " \t,", PROCFILE_FLAG_DEFAULT);
+ if (unlikely(!ff)) return 1;
+ }
+
+ ff = procfile_readall(ff);
+ if (unlikely(!ff)) return 1;
+
+ size_t l, i, j, lines = procfile_lines(ff);
+ for (l = 0; l < lines; l++) {
+ char chart_id[64];
+ char zone_lowercase[32];
+ if (unlikely(procfile_linewords(ff, l) < 15)) continue;
+ char *zone = procfile_lineword(ff, l, 3);
+ strncpyz(zone_lowercase, zone, 31);
+ debugfs2lower(zone_lowercase);
+
+ char *id = procfile_lineword(ff, l, 1);
+ snprintfz(chart_id, 63, "node_%s_%s", id, zone_lowercase);
+ debugfs2lower(chart_id);
+
+ struct netdata_extrafrag *extrafrag = find_or_create_extrafrag(chart_id);
+ collected_number *line_orders = extrafrag->orders;
+ for (i = 4, j = 0 ; i < 15; i++, j++) {
+ NETDATA_DOUBLE value = str2ndd(procfile_lineword(ff, l, i), NULL);
+ line_orders[j] = (collected_number) (value * 1000.0);
+ }
+
+ if (unlikely(!extrafrag->id)) {
+ extrafrag->id = extrafrag->node_zone;
+ fprintf(
+ stdout,
+ "CHART mem.fragmentation_index_%s '' 'Memory fragmentation index for each order' 'index' 'fragmentation' 'mem.fragmentation_index_%s' 'line' %d %d '' 'debugfs.plugin' '%s'\n",
+ extrafrag->node_zone,
+ zone_lowercase,
+ chart_order++, // FIXME: the same zones must have the same order
+ update_every,
+ name);
+ for (i = 0; i < NETDATA_ORDER_FRAGMENTATION; i++) {
+ fprintf(stdout, "DIMENSION '%s' '%s' absolute 1 1000 ''\n", orders[i], orders[i]);
+ }
+ fprintf(stdout,
+ "CLABEL 'numa_node' 'node%s' 1\n"
+ "CLABEL_COMMIT\n",
+ id);
+ }
+ extfrag_send_chart(chart_id, line_orders);
+ }
+
+ return 0;
+}
diff --git a/collectors/debugfs.plugin/debugfs_plugin.c b/collectors/debugfs.plugin/debugfs_plugin.c
new file mode 100644
index 000000000..9713be320
--- /dev/null
+++ b/collectors/debugfs.plugin/debugfs_plugin.c
@@ -0,0 +1,246 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "debugfs_plugin.h"
+#include "libnetdata/required_dummies.h"
+
+static char *user_config_dir = CONFIG_DIR;
+static char *stock_config_dir = LIBCONFIG_DIR;
+
+static int update_every = 1;
+
+static struct debugfs_module {
+ const char *name;
+
+ int enabled;
+
+ int (*func)(int update_every, const char *name);
+} debugfs_modules[] = {
+ // Memory Fragmentation
+ { .name = "/sys/kernel/debug/extfrag", .enabled = CONFIG_BOOLEAN_YES,
+ .func = do_debugfs_extfrag},
+ { .name = "/sys/kernel/debug/zswap", .enabled = CONFIG_BOOLEAN_YES,
+ .func = do_debugfs_zswap},
+
+ // The terminator
+ { .name = NULL, .enabled = CONFIG_BOOLEAN_NO, .func = NULL}
+};
+
+#ifdef HAVE_CAPABILITY
+static int debugfs_check_capabilities()
+{
+ cap_t caps = cap_get_proc();
+ if (!caps) {
+ error("Cannot get current capabilities.");
+ return 0;
+ }
+
+ int ret = 1;
+ cap_flag_value_t cfv = CAP_CLEAR;
+ if (cap_get_flag(caps, CAP_DAC_READ_SEARCH, CAP_EFFECTIVE, &cfv) == -1) {
+ error("Cannot find if CAP_DAC_READ_SEARCH is effective.");
+ ret = 0;
+ } else {
+ if (cfv != CAP_SET) {
+ error("debugfs.plugin should run with CAP_DAC_READ_SEARCH.");
+ ret = 0;
+ }
+ }
+ cap_free(caps);
+
+ return ret;
+}
+#else
+static int debugfs_check_capabilities()
+{
+ return 0;
+}
+#endif
+
+// TODO: This is a function used by 3 different collector, we should do it global (next PR)
+static int debugfs_am_i_running_as_root()
+{
+ uid_t uid = getuid(), euid = geteuid();
+
+ if (uid == 0 || euid == 0) {
+ return 1;
+ }
+
+ return 0;
+}
+
+void debugfs2lower(char *name)
+{
+ while (*name) {
+ *name = tolower(*name);
+ name++;
+ }
+}
+
+// Consiidering our goal to redce binaries, I preferred to copy function, instead to force link with unecessary libs
+const char *debugfs_rrdset_type_name(RRDSET_TYPE chart_type) {
+ switch(chart_type) {
+ case RRDSET_TYPE_LINE:
+ default:
+ return RRDSET_TYPE_LINE_NAME;
+
+ case RRDSET_TYPE_AREA:
+ return RRDSET_TYPE_AREA_NAME;
+
+ case RRDSET_TYPE_STACKED:
+ return RRDSET_TYPE_STACKED_NAME;
+ }
+}
+
+const char *debugfs_rrd_algorithm_name(RRD_ALGORITHM algorithm) {
+ switch(algorithm) {
+ case RRD_ALGORITHM_ABSOLUTE:
+ default:
+ return RRD_ALGORITHM_ABSOLUTE_NAME;
+
+ case RRD_ALGORITHM_INCREMENTAL:
+ return RRD_ALGORITHM_INCREMENTAL_NAME;
+
+ case RRD_ALGORITHM_PCENT_OVER_ROW_TOTAL:
+ return RRD_ALGORITHM_PCENT_OVER_ROW_TOTAL_NAME;
+
+ case RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL:
+ return RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL_NAME;
+ }
+}
+
+int debugfs_check_sys_permission() {
+ int ret = 0;
+
+ char filename[FILENAME_MAX + 1];
+
+ snprintfz(filename, FILENAME_MAX, "%s/sys/kernel/debug/extfrag/extfrag_index", netdata_configured_host_prefix);
+
+ procfile *ff = procfile_open(filename, NULL, PROCFILE_FLAG_NO_ERROR_ON_FILE_IO);
+ if(!ff) goto dcsp_cleanup;
+
+ ff = procfile_readall(ff);
+ if(!ff) goto dcsp_cleanup;
+
+ ret = 1;
+
+dcsp_cleanup:
+ if (!ret)
+ perror("Cannot open /sys/kernel/debug/extfrag/extfrag_index file");
+ procfile_close(ff);
+ return ret;
+}
+
+static void debugfs_parse_args(int argc, char **argv)
+{
+ int i, freq = 0;
+ for(i = 1; i < argc; i++) {
+ if(!freq) {
+ int n = (int)str2l(argv[i]);
+ if(n > 0) {
+ freq = n;
+ continue;
+ }
+ }
+
+ if(strcmp("test-permissions", argv[i]) == 0 || strcmp("-t", argv[i]) == 0) {
+ if(!debugfs_check_sys_permission()) {
+ exit(2);
+ }
+ printf("OK\n");
+ exit(0);
+ }
+ }
+
+ if(freq > 0) update_every = freq;
+}
+
+int main(int argc, char **argv)
+{
+ // debug_flags = D_PROCFILE;
+ stderror = stderr;
+
+ // set the name for logging
+ program_name = "debugfs.plugin";
+
+ // disable syslog for debugfs.plugin
+ error_log_syslog = 0;
+
+ netdata_configured_host_prefix = getenv("NETDATA_HOST_PREFIX");
+ if (verify_netdata_host_prefix() == -1)
+ exit(1);
+
+ user_config_dir = getenv("NETDATA_USER_CONFIG_DIR");
+ if (user_config_dir == NULL) {
+ user_config_dir = CONFIG_DIR;
+ }
+
+ stock_config_dir = getenv("NETDATA_STOCK_CONFIG_DIR");
+ if (stock_config_dir == NULL) {
+ // info("NETDATA_CONFIG_DIR is not passed from netdata");
+ stock_config_dir = LIBCONFIG_DIR;
+ }
+
+ // FIXME: should first check if /sys/kernel/debug is mounted
+
+ // FIXME: remove debugfs_check_sys_permission() after https://github.com/netdata/netdata/issues/15048 is fixed
+ if (!debugfs_check_capabilities() && !debugfs_am_i_running_as_root() && !debugfs_check_sys_permission()) {
+ uid_t uid = getuid(), euid = geteuid();
+#ifdef HAVE_CAPABILITY
+ error(
+ "debugfs.plugin should either run as root (now running with uid %u, euid %u) or have special capabilities. "
+ "Without these, debugfs.plugin cannot access /sys/kernel/debug. "
+ "To enable capabilities run: sudo setcap cap_dac_read_search,cap_sys_ptrace+ep %s; "
+ "To enable setuid to root run: sudo chown root:netdata %s; sudo chmod 4750 %s; ",
+ uid,
+ euid,
+ argv[0],
+ argv[0],
+ argv[0]);
+#else
+ error(
+ "debugfs.plugin should either run as root (now running with uid %u, euid %u) or have special capabilities. "
+ "Without these, debugfs.plugin cannot access /sys/kernel/debug."
+ "Your system does not support capabilities. "
+ "To enable setuid to root run: sudo chown root:netdata %s; sudo chmod 4750 %s; ",
+ uid,
+ euid,
+ argv[0],
+ argv[0]);
+#endif
+ exit(1);
+ }
+
+ // if (!debugfs_check_sys_permission()) {
+ // exit(2);
+ // }
+
+ debugfs_parse_args(argc, argv);
+
+ size_t iteration;
+ usec_t step = update_every * USEC_PER_SEC;
+ heartbeat_t hb;
+ heartbeat_init(&hb);
+
+ for (iteration = 0; iteration < 86400; iteration++) {
+ heartbeat_next(&hb, step);
+ int enabled = 0;
+
+ for (int i = 0; debugfs_modules[i].name; i++) {
+ struct debugfs_module *pm = &debugfs_modules[i];
+ if (unlikely(!pm->enabled))
+ continue;
+
+ pm->enabled = !pm->func(update_every, pm->name);
+ if (likely(pm->enabled))
+ enabled++;
+ }
+ if (!enabled) {
+ info("all modules are disabled, exiting...");
+ return 1;
+ }
+ }
+
+ fprintf(stdout, "EXIT\n");
+ fflush(stdout);
+ return 0;
+}
diff --git a/collectors/debugfs.plugin/debugfs_plugin.h b/collectors/debugfs.plugin/debugfs_plugin.h
new file mode 100644
index 000000000..c53187d6e
--- /dev/null
+++ b/collectors/debugfs.plugin/debugfs_plugin.h
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_DEBUGFS_PLUGIN_H
+#define NETDATA_DEBUGFS_PLUGIN_H 1
+
+#include "libnetdata/libnetdata.h"
+#include "collectors/all.h"
+#include "database/rrd.h"
+
+int do_debugfs_extfrag(int update_every, const char *name);
+int do_debugfs_zswap(int update_every, const char *name);
+void debugfs2lower(char *name);
+const char *debugfs_rrdset_type_name(RRDSET_TYPE chart_type);
+const char *debugfs_rrd_algorithm_name(RRD_ALGORITHM algorithm);
+
+#endif // NETDATA_DEBUGFS_PLUGIN_H
diff --git a/collectors/debugfs.plugin/debugfs_zswap.c b/collectors/debugfs.plugin/debugfs_zswap.c
new file mode 100644
index 000000000..a2991b9f1
--- /dev/null
+++ b/collectors/debugfs.plugin/debugfs_zswap.c
@@ -0,0 +1,437 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "debugfs_plugin.h"
+
+static long system_page_size = 4096;
+
+static collected_number pages_to_bytes(collected_number value)
+{
+ return value * system_page_size;
+}
+
+struct netdata_zswap_metric {
+ const char *filename;
+
+ const char *chart_id;
+ const char *title;
+ const char *units;
+ RRDSET_TYPE charttype;
+ int prio;
+ const char *dimension;
+ RRD_ALGORITHM algorithm;
+ int divisor;
+
+ int enabled;
+ int chart_created;
+
+ collected_number value;
+ collected_number (*convertv)(collected_number v);
+};
+
+static struct netdata_zswap_metric zswap_calculated_metrics[] = {
+ {.filename = "",
+ .chart_id = "pool_compression_ratio",
+ .dimension = "compression_ratio",
+ .units = "ratio",
+ .title = "Zswap compression ratio",
+ .algorithm = RRD_ALGORITHM_ABSOLUTE,
+ .charttype = RRDSET_TYPE_LINE,
+ .enabled = CONFIG_BOOLEAN_YES,
+ .chart_created = CONFIG_BOOLEAN_NO,
+ .prio = NETDATA_CHART_PRIO_SYSTEM_ZSWAP_COMPRESS_RATIO,
+ .divisor = 100,
+ .convertv = NULL,
+ .value = -1},
+};
+
+enum netdata_zswap_calculated {
+ NETDATA_ZSWAP_COMPRESSION_RATIO_CHART,
+};
+
+enum netdata_zwap_independent {
+ NETDATA_ZSWAP_POOL_TOTAL_SIZE,
+ NETDATA_ZSWAP_STORED_PAGES,
+ NETDATA_ZSWAP_POOL_LIMIT_HIT,
+ NETDATA_ZSWAP_WRITTEN_BACK_PAGES,
+ NETDATA_ZSWAP_SAME_FILLED_PAGES,
+ NETDATA_ZSWAP_DUPLICATE_ENTRY,
+
+ // Terminator
+ NETDATA_ZSWAP_SITE_END
+};
+
+static struct netdata_zswap_metric zswap_independent_metrics[] = {
+ // https://elixir.bootlin.com/linux/latest/source/mm/zswap.c
+ {.filename = "/sys/kernel/debug/zswap/pool_total_size",
+ .chart_id = "pool_compressed_size",
+ .dimension = "compressed_size",
+ .units = "bytes",
+ .title = "Zswap compressed bytes currently stored",
+ .algorithm = RRD_ALGORITHM_ABSOLUTE,
+ .charttype = RRDSET_TYPE_AREA,
+ .enabled = CONFIG_BOOLEAN_YES,
+ .chart_created = CONFIG_BOOLEAN_NO,
+ .prio = NETDATA_CHART_PRIO_SYSTEM_ZSWAP_POOL_TOT_SIZE,
+ .divisor = 1,
+ .convertv = NULL,
+ .value = -1},
+ {.filename = "/sys/kernel/debug/zswap/stored_pages",
+ .chart_id = "pool_raw_size",
+ .dimension = "uncompressed_size",
+ .units = "bytes",
+ .title = "Zswap uncompressed bytes currently stored",
+ .algorithm = RRD_ALGORITHM_ABSOLUTE,
+ .charttype = RRDSET_TYPE_AREA,
+ .enabled = CONFIG_BOOLEAN_YES,
+ .chart_created = CONFIG_BOOLEAN_NO,
+ .prio = NETDATA_CHART_PRIO_SYSTEM_ZSWAP_STORED_PAGE,
+ .divisor = 1,
+ .convertv = pages_to_bytes,
+ .value = -1},
+ {.filename = "/sys/kernel/debug/zswap/pool_limit_hit",
+ .chart_id = "pool_limit_hit",
+ .dimension = "limit",
+ .units = "events/s",
+ .title = "Zswap pool limit was reached",
+ .algorithm = RRD_ALGORITHM_INCREMENTAL,
+ .charttype = RRDSET_TYPE_LINE,
+ .enabled = CONFIG_BOOLEAN_YES,
+ .chart_created = CONFIG_BOOLEAN_NO,
+ .prio = NETDATA_CHART_PRIO_SYSTEM_ZSWAP_POOL_LIM_HIT,
+ .divisor = 1,
+ .convertv = NULL,
+ .value = -1},
+ {.filename = "/sys/kernel/debug/zswap/written_back_pages",
+ .chart_id = "written_back_raw_bytes",
+ .dimension = "written_back",
+ .units = "bytes/s",
+ .title = "Zswap uncomressed bytes written back when pool limit was reached",
+ .algorithm = RRD_ALGORITHM_INCREMENTAL,
+ .charttype = RRDSET_TYPE_AREA,
+ .enabled = CONFIG_BOOLEAN_YES,
+ .chart_created = CONFIG_BOOLEAN_NO,
+ .prio = NETDATA_CHART_PRIO_SYSTEM_ZSWAP_WRT_BACK_PAGES,
+ .divisor = 1,
+ .convertv = pages_to_bytes,
+ .value = -1},
+ {.filename = "/sys/kernel/debug/zswap/same_filled_pages",
+ .chart_id = "same_filled_raw_size",
+ .dimension = "same_filled",
+ .units = "bytes",
+ .title = "Zswap same-value filled uncompressed bytes currently stored",
+ .algorithm = RRD_ALGORITHM_ABSOLUTE,
+ .charttype = RRDSET_TYPE_AREA,
+ .enabled = CONFIG_BOOLEAN_YES,
+ .chart_created = CONFIG_BOOLEAN_NO,
+ .prio = NETDATA_CHART_PRIO_SYSTEM_ZSWAP_SAME_FILL_PAGE,
+ .divisor = 1,
+ .convertv = pages_to_bytes,
+ .value = -1},
+ {.filename = "/sys/kernel/debug/zswap/duplicate_entry",
+ .chart_id = "duplicate_entry",
+ .dimension = "duplicate",
+ .units = "entries/s",
+ .title = "Zswap duplicate store was encountered",
+ .algorithm = RRD_ALGORITHM_INCREMENTAL,
+ .charttype = RRDSET_TYPE_LINE,
+ .enabled = CONFIG_BOOLEAN_YES,
+ .chart_created = CONFIG_BOOLEAN_NO,
+ .prio = NETDATA_CHART_PRIO_SYSTEM_ZSWAP_DUPP_ENTRY,
+ .divisor = 1,
+ .convertv = NULL,
+ .value = -1},
+
+ // The terminator
+ {.filename = NULL,
+ .chart_id = NULL,
+ .dimension = NULL,
+ .units = NULL,
+ .title = NULL,
+ .algorithm = RRD_ALGORITHM_ABSOLUTE,
+ .charttype = RRDSET_TYPE_LINE,
+ .enabled = CONFIG_BOOLEAN_NO,
+ .chart_created = CONFIG_BOOLEAN_NO,
+ .prio = -1,
+ .value = -1}};
+
+enum netdata_zswap_rejected {
+ NETDATA_ZSWAP_REJECTED_CHART,
+ NETDATA_ZSWAP_REJECTED_COMPRESS_POOR,
+ NETDATA_ZSWAP_REJECTED_KMEM_FAIL,
+ NETDATA_ZSWAP_REJECTED_RALLOC_FAIL,
+ NETDATA_ZSWAP_REJECTED_RRECLAIM_FAIL,
+
+ // Terminator
+ NETDATA_ZSWAP_REJECTED_END
+};
+
+static struct netdata_zswap_metric zswap_rejected_metrics[] = {
+ {.filename = "/sys/kernel/debug/zswap/",
+ .chart_id = "rejections",
+ .dimension = NULL,
+ .units = "rejections/s",
+ .title = "Zswap rejections",
+ .algorithm = RRD_ALGORITHM_INCREMENTAL,
+ .charttype = RRDSET_TYPE_STACKED,
+ .enabled = CONFIG_BOOLEAN_YES,
+ .chart_created = CONFIG_BOOLEAN_NO,
+ .prio = NETDATA_CHART_PRIO_SYSTEM_ZSWAP_REJECTS,
+ .divisor = 1,
+ .convertv = NULL,
+ .value = -1},
+ {.filename = "/sys/kernel/debug/zswap/reject_compress_poor",
+ .chart_id = "reject_compress_poor",
+ .dimension = "compress_poor",
+ .units = NULL,
+ .title = NULL,
+ .algorithm = RRD_ALGORITHM_INCREMENTAL,
+ .charttype = RRDSET_TYPE_STACKED,
+ .enabled = CONFIG_BOOLEAN_YES,
+ .chart_created = CONFIG_BOOLEAN_NO,
+ .prio = NETDATA_CHART_PRIO_SYSTEM_ZSWAP_REJECTS,
+ .divisor = 1,
+ .convertv = NULL,
+ .value = -1},
+ {.filename = "/sys/kernel/debug/zswap/reject_kmemcache_fail",
+ .chart_id = "reject_kmemcache_fail",
+ .dimension = "kmemcache_fail",
+ .units = NULL,
+ .title = NULL,
+ .algorithm = RRD_ALGORITHM_INCREMENTAL,
+ .charttype = RRDSET_TYPE_STACKED,
+ .enabled = CONFIG_BOOLEAN_YES,
+ .chart_created = CONFIG_BOOLEAN_NO,
+ .prio = NETDATA_CHART_PRIO_SYSTEM_ZSWAP_REJECTS,
+ .divisor = 1,
+ .convertv = NULL,
+ .value = -1},
+ {.filename = "/sys/kernel/debug/zswap/reject_alloc_fail",
+ .chart_id = "reject_alloc_fail",
+ .dimension = "alloc_fail",
+ .units = NULL,
+ .title = NULL,
+ .algorithm = RRD_ALGORITHM_INCREMENTAL,
+ .charttype = RRDSET_TYPE_STACKED,
+ .enabled = CONFIG_BOOLEAN_YES,
+ .chart_created = CONFIG_BOOLEAN_NO,
+ .prio = NETDATA_CHART_PRIO_SYSTEM_ZSWAP_REJECTS,
+ .divisor = 1,
+ .convertv = NULL,
+ .value = -1},
+ {.filename = "/sys/kernel/debug/zswap/reject_reclaim_fail",
+ .chart_id = "reject_reclaim_fail",
+ .dimension = "reclaim_fail",
+ .units = NULL,
+ .title = NULL,
+ .algorithm = RRD_ALGORITHM_INCREMENTAL,
+ .charttype = RRDSET_TYPE_STACKED,
+ .enabled = CONFIG_BOOLEAN_YES,
+ .chart_created = CONFIG_BOOLEAN_NO,
+ .prio = NETDATA_CHART_PRIO_SYSTEM_ZSWAP_REJECTS,
+ .divisor = 1,
+ .convertv = NULL,
+ .value = -1},
+
+ // The terminator
+ {.filename = NULL,
+ .chart_id = NULL,
+ .dimension = NULL,
+ .units = NULL,
+ .title = NULL,
+ .algorithm = RRD_ALGORITHM_ABSOLUTE,
+ .charttype = RRDSET_TYPE_STACKED,
+ .enabled = CONFIG_BOOLEAN_NO,
+ .chart_created = CONFIG_BOOLEAN_NO,
+ .prio = -1,
+ .value = -1}};
+
+int zswap_collect_data(struct netdata_zswap_metric *metric)
+{
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, metric->filename);
+
+ if (read_single_number_file(filename, (unsigned long long *)&metric->value)) {
+ error("Cannot read file %s", filename);
+ return 1;
+ }
+
+ if (metric->convertv)
+ metric->value = metric->convertv(metric->value);
+
+ return 0;
+}
+
+static void
+zswap_send_chart(struct netdata_zswap_metric *metric, int update_every, const char *name, const char *option)
+{
+ fprintf(
+ stdout,
+ "CHART system.zswap_%s '' '%s' '%s' 'zswap' '' '%s' %d %d '%s' 'debugfs.plugin' '%s'\n",
+ metric->chart_id,
+ metric->title,
+ metric->units,
+ debugfs_rrdset_type_name(metric->charttype),
+ metric->prio,
+ update_every,
+ (!option) ? "" : option,
+ name);
+}
+
+static void zswap_send_dimension(struct netdata_zswap_metric *metric)
+{
+ int div = metric->divisor > 0 ? metric->divisor : 1;
+ fprintf(
+ stdout,
+ "DIMENSION '%s' '%s' %s 1 %d ''\n",
+ metric->dimension,
+ metric->dimension,
+ debugfs_rrd_algorithm_name(metric->algorithm),
+ div);
+}
+
+static void zswap_send_begin(struct netdata_zswap_metric *metric)
+{
+ fprintf(stdout, "BEGIN system.zswap_%s\n", metric->chart_id);
+}
+
+static void zswap_send_set(struct netdata_zswap_metric *metric)
+{
+ fprintf(stdout, "SET %s = %lld\n", metric->dimension, metric->value);
+}
+
+static void zswap_send_end_and_flush()
+{
+ fprintf(stdout, "END\n");
+ fflush(stdout);
+}
+
+static void zswap_independent_chart(struct netdata_zswap_metric *metric, int update_every, const char *name)
+{
+ if (unlikely(!metric->chart_created)) {
+ metric->chart_created = CONFIG_BOOLEAN_YES;
+
+ zswap_send_chart(metric, update_every, name, NULL);
+ zswap_send_dimension(metric);
+ }
+
+ zswap_send_begin(metric);
+ zswap_send_set(metric);
+ zswap_send_end_and_flush();
+}
+
+void zswap_reject_chart(int update_every, const char *name)
+{
+ struct netdata_zswap_metric *metric = &zswap_rejected_metrics[NETDATA_ZSWAP_REJECTED_CHART];
+
+ if (unlikely(!metric->chart_created)) {
+ metric->chart_created = CONFIG_BOOLEAN_YES;
+
+ zswap_send_chart(metric, update_every, name, NULL);
+ for (int i = NETDATA_ZSWAP_REJECTED_COMPRESS_POOR; zswap_rejected_metrics[i].filename; i++) {
+ metric = &zswap_rejected_metrics[i];
+ if (likely(metric->enabled))
+ zswap_send_dimension(metric);
+ }
+ }
+
+ metric = &zswap_rejected_metrics[NETDATA_ZSWAP_REJECTED_CHART];
+ zswap_send_begin(metric);
+ for (int i = NETDATA_ZSWAP_REJECTED_COMPRESS_POOR; zswap_rejected_metrics[i].filename; i++) {
+ metric = &zswap_rejected_metrics[i];
+ if (likely(metric->enabled))
+ zswap_send_set(metric);
+ }
+ zswap_send_end_and_flush();
+}
+
+static void zswap_obsolete_charts(int update_every, const char *name)
+{
+ struct netdata_zswap_metric *metric = NULL;
+
+ for (int i = 0; zswap_independent_metrics[i].filename; i++) {
+ metric = &zswap_independent_metrics[i];
+ if (likely(metric->chart_created))
+ zswap_send_chart(metric, update_every, name, "obsolete");
+ }
+
+ metric = &zswap_rejected_metrics[NETDATA_ZSWAP_REJECTED_CHART];
+ if (likely(metric->chart_created))
+ zswap_send_chart(metric, update_every, name, "obsolete");
+
+ metric = &zswap_calculated_metrics[NETDATA_ZSWAP_COMPRESSION_RATIO_CHART];
+ if (likely(metric->chart_created))
+ zswap_send_chart(metric, update_every, name, "obsolete");
+}
+
+#define ZSWAP_STATE_SIZE 1 // Y or N
+static int debugfs_is_zswap_enabled()
+{
+ char filename[FILENAME_MAX + 1];
+ snprintfz(filename, FILENAME_MAX, "/sys/module/zswap/parameters/enabled"); // host prefix is not needed here
+ char state[ZSWAP_STATE_SIZE + 1];
+
+ int ret = read_file(filename, state, ZSWAP_STATE_SIZE);
+
+ if (unlikely(!ret && !strcmp(state, "Y"))) {
+ return 0;
+ }
+ return 1;
+}
+
+int do_debugfs_zswap(int update_every, const char *name)
+{
+ static int check_if_enabled = 1;
+
+ if (likely(check_if_enabled && debugfs_is_zswap_enabled())) {
+ info("Zswap is disabled");
+ return 1;
+ }
+
+ check_if_enabled = 0;
+
+ system_page_size = sysconf(_SC_PAGESIZE);
+ struct netdata_zswap_metric *metric = NULL;
+ int enabled = 0;
+
+ for (int i = 0; zswap_independent_metrics[i].filename; i++) {
+ metric = &zswap_independent_metrics[i];
+ if (unlikely(!metric->enabled))
+ continue;
+ if (unlikely(!(metric->enabled = !zswap_collect_data(metric))))
+ continue;
+ zswap_independent_chart(metric, update_every, name);
+ enabled++;
+ }
+
+ struct netdata_zswap_metric *metric_size = &zswap_independent_metrics[NETDATA_ZSWAP_POOL_TOTAL_SIZE];
+ struct netdata_zswap_metric *metric_raw_size = &zswap_independent_metrics[NETDATA_ZSWAP_STORED_PAGES];
+ if (metric_size->enabled && metric_raw_size->enabled) {
+ metric = &zswap_calculated_metrics[NETDATA_ZSWAP_COMPRESSION_RATIO_CHART];
+ metric->value = 0;
+ if (metric_size->value > 0)
+ metric->value =
+ (collected_number)((NETDATA_DOUBLE)metric_raw_size->value / (NETDATA_DOUBLE)metric_size->value * 100);
+ zswap_independent_chart(metric, update_every, name);
+ }
+
+ int enabled_rejected = 0;
+ for (int i = NETDATA_ZSWAP_REJECTED_COMPRESS_POOR; zswap_rejected_metrics[i].filename; i++) {
+ metric = &zswap_rejected_metrics[i];
+ if (unlikely(!metric->enabled))
+ continue;
+ if (unlikely(!(metric->enabled = !zswap_collect_data(metric))))
+ continue;
+ enabled++;
+ enabled_rejected++;
+ }
+
+ if (likely(enabled_rejected > 0))
+ zswap_reject_chart(update_every, name);
+
+ if (unlikely(!enabled)) {
+ zswap_obsolete_charts(update_every, name);
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/collectors/debugfs.plugin/metrics.csv b/collectors/debugfs.plugin/metrics.csv
new file mode 100644
index 000000000..a21383941
--- /dev/null
+++ b/collectors/debugfs.plugin/metrics.csv
@@ -0,0 +1,12 @@
+metric,scode,dimensions,unit,description,chart_type,labels,plugin,module
+mem.fragmentation_index_dma,node,"order0, order1, order2, order3, order4, order5, order6, order7, order8, order9, order10",index,Memory fragmentation index for each order,line,numa_node,debugfs.plugin,/sys/kernel/debug/extfrag
+mem.fragmentation_index_dma32,node,"order0, order1, order2, order3, order4, order5, order6, order7, order8, order9, order10",index,Memory fragmentation index for each order,line,numa_node,debugfs.plugin,/sys/kernel/debug/extfrag
+mem.fragmentation_index_normal,node,"order0, order1, order2, order3, order4, order5, order6, order7, order8, order9, order10",index,Memory fragmentation index for each order,line,numa_node,debugfs.plugin,/sys/kernel/debug/extfrag
+system.zswap_pool_compression_ratio,,compression_ratio,ratio,Zswap compression ratio,line,,debugfs.plugin,/sys/kernel/debug/zswap
+system.zswap_pool_compressed_size,,compressed_size,bytes,Zswap compressed bytes currently stored,area,,debugfs.plugin,/sys/kernel/debug/zswap
+system.zswap_pool_raw_size,,uncompressed_size,bytes,Zswap uncompressed bytes currently stored,area,,debugfs.plugin,/sys/kernel/debug/zswap
+system.zswap_rejections,,"compress_poor, kmemcache_fail, alloc_fail, reclaim_fail",rejections/s,Zswap rejections,stacked,,debugfs.plugin,/sys/kernel/debug/zswap
+system.zswap_pool_limit_hit,,limit,events/s,Zswap pool limit was reached,line,,debugfs.plugin,/sys/kernel/debug/zswap
+system.zswap_written_back_raw_bytes,,written_back,bytes/s,Zswap uncomressed bytes written back when pool limit was reached,area,,debugfs.plugin,/sys/kernel/debug/zswap
+system.zswap_same_filled_raw_size,,same_filled,bytes,Zswap same-value filled uncompressed bytes currently stored,area,,debugfs.plugin,/sys/kernel/debug/zswap
+system.zswap_duplicate_entry,,duplicate,entries/s,Zswap duplicate store was encountered,line,,debugfs.plugin,/sys/kernel/debug/zswap