| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #include "plugin_proc.h" |
| 4 | |
| 5 | #define PLUGIN_PROC_MODULE_ZRAM_NAME "/sys/block/zram" |
| 6 | #define rrdset_obsolete_and_pointer_null(st) do { if(st) { rrdset_is_obsolete___safe_from_collector_thread(st); (st) = NULL; } } while(st) |
| 7 | |
| 8 | typedef struct mm_stat { |
| 9 | unsigned long long orig_data_size; |
| 10 | unsigned long long compr_data_size; |
| 11 | unsigned long long mem_used_total; |
| 12 | unsigned long long mem_limit; |
| 13 | unsigned long long mem_used_max; |
| 14 | unsigned long long same_pages; |
| 15 | unsigned long long pages_compacted; |
| 16 | } MM_STAT; |
| 17 | |
| 18 | typedef struct zram_device { |
| 19 | procfile *file; |
| 20 | |
| 21 | RRDSET *st_usage; |
| 22 | RRDDIM *rd_compr_data_size; |
| 23 | RRDDIM *rd_metadata_size; |
| 24 | |
| 25 | RRDSET *st_savings; |
| 26 | RRDDIM *rd_original_size; |
| 27 | RRDDIM *rd_savings_size; |
| 28 | |
| 29 | RRDSET *st_comp_ratio; |
| 30 | RRDDIM *rd_comp_ratio; |
| 31 | |
| 32 | RRDSET *st_alloc_efficiency; |
| 33 | RRDDIM *rd_alloc_efficiency; |
| 34 | } ZRAM_DEVICE; |
| 35 | |
| 36 | static int try_get_zram_major_number(procfile *file) { |
| 37 | size_t i; |
| 38 | unsigned int lines = procfile_lines(file); |
| 39 | int id = -1; |
| 40 | char *name = NULL; |
| 41 | for (i = 0; i < lines; i++) |
| 42 | { |
| 43 | if (procfile_linewords(file, i) < 2) |
| 44 | continue; |
| 45 | name = procfile_lineword(file, i, 1); |
| 46 | if (strcmp(name, "zram") == 0) |
| 47 | { |
| 48 | id = str2i(procfile_lineword(file, i, 0)); |
| 49 | if (id == 0) |
| 50 | return -1; |
| 51 | return id; |
| 52 | } |
| 53 | } |
| 54 | return -1; |
| 55 | } |
| 56 | |
| 57 | static inline void init_rrd(const char *name, ZRAM_DEVICE *d, int update_every) { |
| 58 | char chart_name[RRD_ID_LENGTH_MAX + 1]; |
| 59 | |
| 60 | snprintfz(chart_name, RRD_ID_LENGTH_MAX, "zram_usage.%s", name); |
| 61 | d->st_usage = rrdset_create_localhost( |
| 62 | "mem" |
| 63 | , chart_name |
| 64 | , chart_name |
| 65 | , "zram" |
| 66 | , "mem.zram_usage" |
| 67 | , "ZRAM Memory Usage" |
| 68 | , "MiB" |
| 69 | , PLUGIN_PROC_NAME |
| 70 | , PLUGIN_PROC_MODULE_ZRAM_NAME |
| 71 | , NETDATA_CHART_PRIO_MEM_ZRAM |
| 72 | , update_every |
| 73 | , RRDSET_TYPE_AREA); |
| 74 | d->rd_compr_data_size = rrddim_add(d->st_usage, "compressed", NULL, 1, 1024 * 1024, RRD_ALGORITHM_ABSOLUTE); |
| 75 | d->rd_metadata_size = rrddim_add(d->st_usage, "metadata", NULL, 1, 1024 * 1024, RRD_ALGORITHM_ABSOLUTE); |
| 76 | rrdlabels_add(d->st_usage->rrdlabels, "device", name, RRDLABEL_SRC_AUTO); |
| 77 | |
| 78 | snprintfz(chart_name, RRD_ID_LENGTH_MAX, "zram_savings.%s", name); |
| 79 | d->st_savings = rrdset_create_localhost( |
| 80 | "mem" |
| 81 | , chart_name |
| 82 | , chart_name |
| 83 | , "zram" |
| 84 | , "mem.zram_savings" |
| 85 | , "ZRAM Memory Savings" |
| 86 | , "MiB" |
| 87 | , PLUGIN_PROC_NAME |
| 88 | , PLUGIN_PROC_MODULE_ZRAM_NAME |
| 89 | , NETDATA_CHART_PRIO_MEM_ZRAM_SAVINGS |
| 90 | , update_every |
| 91 | , RRDSET_TYPE_AREA); |
| 92 | d->rd_savings_size = rrddim_add(d->st_savings, "savings", NULL, 1, 1024 * 1024, RRD_ALGORITHM_ABSOLUTE); |
| 93 | d->rd_original_size = rrddim_add(d->st_savings, "original", NULL, 1, 1024 * 1024, RRD_ALGORITHM_ABSOLUTE); |
| 94 | rrdlabels_add(d->st_savings->rrdlabels, "device", name, RRDLABEL_SRC_AUTO); |
| 95 | |
| 96 | snprintfz(chart_name, RRD_ID_LENGTH_MAX, "zram_ratio.%s", name); |
| 97 | d->st_comp_ratio = rrdset_create_localhost( |
| 98 | "mem" |
| 99 | , chart_name |
| 100 | , chart_name |
| 101 | , "zram" |
| 102 | , "mem.zram_ratio" |
| 103 | , "ZRAM Compression Ratio (original to compressed)" |
| 104 | , "ratio" |
| 105 | , PLUGIN_PROC_NAME |
| 106 | , PLUGIN_PROC_MODULE_ZRAM_NAME |
| 107 | , NETDATA_CHART_PRIO_MEM_ZRAM_RATIO |
| 108 | , update_every |
| 109 | , RRDSET_TYPE_LINE); |
| 110 | d->rd_comp_ratio = rrddim_add(d->st_comp_ratio, "ratio", NULL, 1, 100, RRD_ALGORITHM_ABSOLUTE); |
| 111 | rrdlabels_add(d->st_comp_ratio->rrdlabels, "device", name, RRDLABEL_SRC_AUTO); |
| 112 | |
| 113 | snprintfz(chart_name, RRD_ID_LENGTH_MAX, "zram_efficiency.%s", name); |
| 114 | d->st_alloc_efficiency = rrdset_create_localhost( |
| 115 | "mem" |
| 116 | , chart_name |
| 117 | , chart_name |
| 118 | , "zram" |
| 119 | , "mem.zram_efficiency" |
| 120 | , "ZRAM Efficiency" |
| 121 | , "percentage" |
| 122 | , PLUGIN_PROC_NAME |
| 123 | , PLUGIN_PROC_MODULE_ZRAM_NAME |
| 124 | , NETDATA_CHART_PRIO_MEM_ZRAM_EFFICIENCY |
| 125 | , update_every |
| 126 | , RRDSET_TYPE_LINE); |
| 127 | d->rd_alloc_efficiency = rrddim_add(d->st_alloc_efficiency, "percent", NULL, 1, 10000, RRD_ALGORITHM_ABSOLUTE); |
| 128 | rrdlabels_add(d->st_alloc_efficiency->rrdlabels, "device", name, RRDLABEL_SRC_AUTO); |
| 129 | } |
| 130 | |
| 131 | static int init_devices(DICTIONARY *devices, int update_every) { |
| 132 | int count = 0; |
| 133 | struct dirent *de; |
| 134 | struct stat st; |
| 135 | procfile *ff = NULL; |
| 136 | ZRAM_DEVICE device; |
| 137 | char filename[FILENAME_MAX + 1]; |
| 138 | |
| 139 | snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/sys/block"); |
| 140 | DIR *dir = opendir(filename); |
| 141 | |
| 142 | if (unlikely(!dir)) |
| 143 | return 0; |
| 144 | |
| 145 | while ((de = readdir(dir))) { |
| 146 | snprintfz(filename, FILENAME_MAX, "%s/sys/block/%s/mm_stat", netdata_configured_host_prefix, de->d_name); |
| 147 | if (unlikely(stat(filename, &st) != 0)) { |
| 148 | continue; |
| 149 | } |
| 150 | ff = procfile_open(filename, " \t:", PROCFILE_FLAG_DEFAULT); |
| 151 | if (ff == NULL) { |
| 152 | collector_error("ZRAM : Failed to open %s: %s", filename, strerror(errno)); |
| 153 | continue; |
| 154 | } |
| 155 | |
| 156 | device.file = ff; |
| 157 | init_rrd(de->d_name, &device, update_every); |
| 158 | dictionary_set(devices, de->d_name, &device, sizeof(ZRAM_DEVICE)); |
| 159 | count++; |
| 160 | } |
| 161 | |
| 162 | closedir(dir); |
| 163 | |
| 164 | return count; |
| 165 | } |
| 166 | |
| 167 | static void free_device(DICTIONARY *dict, const char *name) |
| 168 | { |
| 169 | ZRAM_DEVICE *d = (ZRAM_DEVICE*)dictionary_get(dict, name); |
| 170 | collector_info("ZRAM : Disabling monitoring of device %s", name); |
| 171 | rrdset_obsolete_and_pointer_null(d->st_usage); |
| 172 | rrdset_obsolete_and_pointer_null(d->st_savings); |
| 173 | rrdset_obsolete_and_pointer_null(d->st_alloc_efficiency); |
| 174 | rrdset_obsolete_and_pointer_null(d->st_comp_ratio); |
| 175 | dictionary_del(dict, name); |
| 176 | } |
| 177 | |
| 178 | static inline int read_mm_stat(procfile *ff, MM_STAT *stats) { |
| 179 | ff = procfile_readall(ff); |
| 180 | if (!ff) |
| 181 | return -1; |
| 182 | if (procfile_lines(ff) < 1) { |
| 183 | procfile_close(ff); |
| 184 | return -1; |
| 185 | } |
| 186 | if (procfile_linewords(ff, 0) < 7) { |
| 187 | procfile_close(ff); |
| 188 | return -1; |
| 189 | } |
| 190 | |
| 191 | stats->orig_data_size = str2ull(procfile_word(ff, 0), NULL); |
| 192 | stats->compr_data_size = str2ull(procfile_word(ff, 1), NULL); |
| 193 | stats->mem_used_total = str2ull(procfile_word(ff, 2), NULL); |
| 194 | stats->mem_limit = str2ull(procfile_word(ff, 3), NULL); |
| 195 | stats->mem_used_max = str2ull(procfile_word(ff, 4), NULL); |
| 196 | stats->same_pages = str2ull(procfile_word(ff, 5), NULL); |
| 197 | stats->pages_compacted = str2ull(procfile_word(ff, 6), NULL); |
| 198 | return 0; |
| 199 | } |
| 200 | |
| 201 | static int collect_zram_metrics(const DICTIONARY_ITEM *item, void *entry, void *data) { |
| 202 | const char *name = dictionary_acquired_item_name(item); |
| 203 | ZRAM_DEVICE *dev = entry; |
| 204 | DICTIONARY *dict = data; |
| 205 | |
| 206 | MM_STAT mm; |
| 207 | int value; |
| 208 | |
| 209 | if (unlikely(read_mm_stat(dev->file, &mm) < 0)) { |
| 210 | free_device(dict, name); |
| 211 | return -1; |
| 212 | } |
| 213 | |
| 214 | // zram_usage |
| 215 | rrddim_set_by_pointer(dev->st_usage, dev->rd_compr_data_size, mm.compr_data_size); |
| 216 | rrddim_set_by_pointer(dev->st_usage, dev->rd_metadata_size, mm.mem_used_total - mm.compr_data_size); |
| 217 | rrdset_done(dev->st_usage); |
| 218 | |
| 219 | // zram_savings |
| 220 | rrddim_set_by_pointer(dev->st_savings, dev->rd_savings_size, mm.compr_data_size - mm.orig_data_size); |
| 221 | rrddim_set_by_pointer(dev->st_savings, dev->rd_original_size, mm.orig_data_size); |
| 222 | rrdset_done(dev->st_savings); |
| 223 | |
| 224 | // zram_ratio |
| 225 | value = mm.compr_data_size == 0 ? 1 : mm.orig_data_size * 100 / mm.compr_data_size; |
| 226 | rrddim_set_by_pointer(dev->st_comp_ratio, dev->rd_comp_ratio, value); |
| 227 | rrdset_done(dev->st_comp_ratio); |
| 228 | |
| 229 | // zram_efficiency |
| 230 | value = mm.mem_used_total == 0 ? 100 : (mm.compr_data_size * 1000000 / mm.mem_used_total); |
| 231 | rrddim_set_by_pointer(dev->st_alloc_efficiency, dev->rd_alloc_efficiency, value); |
| 232 | rrdset_done(dev->st_alloc_efficiency); |
| 233 | |
| 234 | return 0; |
| 235 | } |
| 236 | |
| 237 | int do_sys_block_zram(int update_every, usec_t dt) { |
| 238 | static procfile *ff = NULL; |
| 239 | static DICTIONARY *devices = NULL; |
| 240 | static int initialized = 0; |
| 241 | static int device_count = 0; |
| 242 | int zram_id = -1; |
| 243 | |
| 244 | (void)dt; |
| 245 | |
| 246 | if (unlikely(!initialized)) |
| 247 | { |
| 248 | initialized = 1; |
| 249 | |
| 250 | char filename[FILENAME_MAX + 1]; |
| 251 | snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/proc/devices"); |
| 252 | |
| 253 | ff = procfile_open(filename, " \t:", PROCFILE_FLAG_DEFAULT); |
| 254 | if (ff == NULL) |
| 255 | { |
| 256 | collector_error("Cannot read %s", filename); |
| 257 | return 1; |
| 258 | } |
| 259 | ff = procfile_readall(ff); |
| 260 | if (!ff) |
| 261 | return 1; |
| 262 | zram_id = try_get_zram_major_number(ff); |
| 263 | if (zram_id == -1) |
| 264 | { |
| 265 | if (ff != NULL) |
| 266 | procfile_close(ff); |
| 267 | return 1; |
| 268 | } |
| 269 | procfile_close(ff); |
| 270 | |
| 271 | devices = dictionary_create_advanced(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_FIXED_SIZE, &dictionary_stats_category_collectors, sizeof(ZRAM_DEVICE)); |
| 272 | device_count = init_devices(devices, update_every); |
| 273 | } |
| 274 | |
| 275 | if (unlikely(device_count < 1)) |
| 276 | return 1; |
| 277 | |
| 278 | dictionary_walkthrough_write(devices, collect_zram_metrics, devices); |
| 279 | return 0; |
| 280 | } |