src/collectors/debugfs.plugin/debugfs_zswap.c
// 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_MEM_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_MEM_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_MEM_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_MEM_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_MEM_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_MEM_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_MEM_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_MEM_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_MEM_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_MEM_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_MEM_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_MEM_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)) {
netdata_log_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 mem.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 mem.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_txt_file(filename, state, sizeof(state));
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())) {
netdata_log_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;
}