diff options
author | thiagoftsm <thiagoftsm@gmail.com> | 2023-05-15 16:06:26 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-05-15 19:06:26 +0300 |
commit | 588096c6b682ec467d85656abab65d77f74e5755 (patch) | |
tree | 64d88be9c0ec23c5d79370cc1bed826e19a2b51a /collectors | |
parent | bdc40c318a761f5e05e3544b72c4aa3384484e44 (diff) |
Debugfs collector (#15017)
Co-authored-by: Fotis Voutsas <fotis@netdata.cloud>
Co-authored-by: Austin S. Hemmelgarn <ahferroin7@gmail.com>
Co-authored-by: ilyam8 <ilya@netdata.cloud>
Diffstat (limited to 'collectors')
-rw-r--r-- | collectors/Makefile.am | 1 | ||||
-rw-r--r-- | collectors/all.h | 10 | ||||
-rw-r--r-- | collectors/apps.plugin/apps_groups.conf | 1 | ||||
-rw-r--r-- | collectors/debugfs.plugin/Makefile.am | 9 | ||||
-rw-r--r-- | collectors/debugfs.plugin/README.md | 65 | ||||
-rw-r--r-- | collectors/debugfs.plugin/debugfs_extfrag.c | 123 | ||||
-rw-r--r-- | collectors/debugfs.plugin/debugfs_plugin.c | 244 | ||||
-rw-r--r-- | collectors/debugfs.plugin/debugfs_plugin.h | 16 | ||||
-rw-r--r-- | collectors/debugfs.plugin/debugfs_zswap.c | 432 | ||||
-rw-r--r-- | collectors/debugfs.plugin/metrics.csv | 12 | ||||
-rw-r--r-- | collectors/ebpf.plugin/ebpf_swap.c | 2 |
11 files changed, 914 insertions, 1 deletions
diff --git a/collectors/Makefile.am b/collectors/Makefile.am index 24e4c3f095..2aec3dd3e8 100644 --- a/collectors/Makefile.am +++ b/collectors/Makefile.am @@ -8,6 +8,7 @@ SUBDIRS = \ cgroups.plugin \ charts.d.plugin \ cups.plugin \ + debugfs.plugin \ diskspace.plugin \ timex.plugin \ ioping.plugin \ diff --git a/collectors/all.h b/collectors/all.h index a0ce5d7fc4..653729bbcb 100644 --- a/collectors/all.h +++ b/collectors/all.h @@ -24,8 +24,17 @@ #define NETDATA_CHART_PRIO_SYSTEM_PGPGIO 151 #define NETDATA_CHART_PRIO_SYSTEM_RAM 200 #define NETDATA_CHART_PRIO_SYSTEM_SWAP 201 +#define NETDATA_CHART_PRIO_SYSTEM_SWAP_CALLS 202 #define NETDATA_CHART_PRIO_SYSTEM_SWAPIO 250 #define NETDATA_CHART_PRIO_SYSTEM_ZSWAPIO 300 +#define NETDATA_CHART_PRIO_SYSTEM_ZSWAP_COMPRESS_RATIO 301 +#define NETDATA_CHART_PRIO_SYSTEM_ZSWAP_POOL_TOT_SIZE 302 +#define NETDATA_CHART_PRIO_SYSTEM_ZSWAP_STORED_PAGE 303 +#define NETDATA_CHART_PRIO_SYSTEM_ZSWAP_REJECTS 304 +#define NETDATA_CHART_PRIO_SYSTEM_ZSWAP_POOL_LIM_HIT 305 +#define NETDATA_CHART_PRIO_SYSTEM_ZSWAP_WRT_BACK_PAGES 306 +#define NETDATA_CHART_PRIO_SYSTEM_ZSWAP_SAME_FILL_PAGE 307 +#define NETDATA_CHART_PRIO_SYSTEM_ZSWAP_DUPP_ENTRY 308 #define NETDATA_CHART_PRIO_SYSTEM_NET 500 #define NETDATA_CHART_PRIO_SYSTEM_IPV4 500 // freebsd only #define NETDATA_CHART_PRIO_SYSTEM_IP 501 @@ -103,6 +112,7 @@ #define NETDATA_CHART_PRIO_MEM_ZRAM_SAVINGS 1601 #define NETDATA_CHART_PRIO_MEM_ZRAM_RATIO 1602 #define NETDATA_CHART_PRIO_MEM_ZRAM_EFFICIENCY 1603 +#define NETDATA_CHART_PRIO_MEM_FRAGMENTATION 1700 // Disks diff --git a/collectors/apps.plugin/apps_groups.conf b/collectors/apps.plugin/apps_groups.conf index f35454fde6..659bd0f031 100644 --- a/collectors/apps.plugin/apps_groups.conf +++ b/collectors/apps.plugin/apps_groups.conf @@ -89,6 +89,7 @@ ioping: ioping go.d.plugin: *go.d.plugin* slabinfo.plugin: slabinfo.plugin ebpf.plugin: *ebpf.plugin* +debugfs.plugin: *debugfs.plugin* # agent-service-discovery agent_sd: agent_sd diff --git a/collectors/debugfs.plugin/Makefile.am b/collectors/debugfs.plugin/Makefile.am new file mode 100644 index 0000000000..02fe3a314f --- /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 0000000000..a2dc9c0f6d --- /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 0000000000..75da4deca4 --- /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 0000000000..f75765acf9 --- /dev/null +++ b/collectors/debugfs.plugin/debugfs_plugin.c @@ -0,0 +1,244 @@ +// 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; + } + } + + return 0; +} diff --git a/collectors/debugfs.plugin/debugfs_plugin.h b/collectors/debugfs.plugin/debugfs_plugin.h new file mode 100644 index 0000000000..c53187d6ea --- /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 0000000000..1580511ca7 --- /dev/null +++ b/collectors/debugfs.plugin/debugfs_zswap.c @@ -0,0 +1,432 @@ +// 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) +{ + int fd; + int ret = 0; + char buffer[512]; + + char filename[FILENAME_MAX + 1]; + snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, metric->filename); + // we are not using profile_open/procfile_read, because they will generate error during runtime. + fd = open(filename, O_RDONLY, 0444); + if (fd < 0) { + error("Cannot open file %s", filename); + return -1; + } + + ssize_t r = read(fd, buffer, 511); + // We expect at list 1 character + if (r < 2) { + error("Cannot parse file %s", filename); + ret = -1; + goto zswap_collect_end; + } + + // We discard breakline + buffer[r - 1] = '\0'; + metric->value = str2ll(buffer, NULL); + + if (metric->convertv) + metric->value = metric->convertv(metric->value); + +zswap_collect_end: + close(fd); + return ret; +} + +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- |