| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #include "plugin_proc.h" |
| 4 | |
| 5 | struct edac_count { |
| 6 | bool updated; |
| 7 | char *filename; |
| 8 | procfile *ff; |
| 9 | kernel_uint_t count; |
| 10 | RRDDIM *rd; |
| 11 | }; |
| 12 | |
| 13 | struct edac_dimm { |
| 14 | char *name; |
| 15 | |
| 16 | struct edac_count ce; |
| 17 | struct edac_count ue; |
| 18 | |
| 19 | RRDSET *st; |
| 20 | |
| 21 | struct edac_dimm *prev, *next; |
| 22 | }; |
| 23 | |
| 24 | struct mc { |
| 25 | char *name; |
| 26 | |
| 27 | struct edac_count ce; |
| 28 | struct edac_count ue; |
| 29 | struct edac_count ce_noinfo; |
| 30 | struct edac_count ue_noinfo; |
| 31 | |
| 32 | RRDSET *st; |
| 33 | |
| 34 | struct edac_dimm *dimms; |
| 35 | |
| 36 | struct mc *prev, *next; |
| 37 | }; |
| 38 | |
| 39 | static struct mc *mc_root = NULL; |
| 40 | static const char *mc_dirname = NULL; |
| 41 | |
| 42 | static void find_all_mc() { |
| 43 | char name[FILENAME_MAX + 1]; |
| 44 | snprintfz(name, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/sys/devices/system/edac/mc"); |
| 45 | mc_dirname = inicfg_get(&netdata_config, "plugin:proc:/sys/devices/system/edac/mc", "directory to monitor", name); |
| 46 | |
| 47 | DIR *dir = opendir(mc_dirname); |
| 48 | if(unlikely(!dir)) { |
| 49 | collector_error("Cannot read EDAC memory errors directory '%s'", mc_dirname); |
| 50 | return; |
| 51 | } |
| 52 | |
| 53 | struct dirent *de = NULL; |
| 54 | while((de = readdir(dir))) { |
| 55 | if(de->d_type == DT_DIR && de->d_name[0] == 'm' && de->d_name[1] == 'c' && isdigit(de->d_name[2])) { |
| 56 | struct mc *m = callocz(1, sizeof(struct mc)); |
| 57 | m->name = strdupz(de->d_name); |
| 58 | |
| 59 | struct stat st; |
| 60 | |
| 61 | snprintfz(name, FILENAME_MAX, "%s/%s/ce_count", mc_dirname, de->d_name); |
| 62 | if(stat(name, &st) != -1) |
| 63 | m->ce.filename = strdupz(name); |
| 64 | |
| 65 | snprintfz(name, FILENAME_MAX, "%s/%s/ue_count", mc_dirname, de->d_name); |
| 66 | if(stat(name, &st) != -1) |
| 67 | m->ue.filename = strdupz(name); |
| 68 | |
| 69 | snprintfz(name, FILENAME_MAX, "%s/%s/ce_noinfo_count", mc_dirname, de->d_name); |
| 70 | if(stat(name, &st) != -1) |
| 71 | m->ce_noinfo.filename = strdupz(name); |
| 72 | |
| 73 | snprintfz(name, FILENAME_MAX, "%s/%s/ue_noinfo_count", mc_dirname, de->d_name); |
| 74 | if(stat(name, &st) != -1) |
| 75 | m->ue_noinfo.filename = strdupz(name); |
| 76 | |
| 77 | if(!m->ce.filename && !m->ue.filename && !m->ce_noinfo.filename && !m->ue_noinfo.filename) { |
| 78 | freez(m->name); |
| 79 | freez(m); |
| 80 | } |
| 81 | else |
| 82 | DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(mc_root, m, prev, next); |
| 83 | } |
| 84 | } |
| 85 | closedir(dir); |
| 86 | |
| 87 | for(struct mc *m = mc_root; m ;m = m->next) { |
| 88 | snprintfz(name, FILENAME_MAX, "%s/%s", mc_dirname, m->name); |
| 89 | dir = opendir(name); |
| 90 | if(!dir) { |
| 91 | collector_error("Cannot read EDAC memory errors directory '%s'", name); |
| 92 | continue; |
| 93 | } |
| 94 | |
| 95 | while((de = readdir(dir))) { |
| 96 | // it can be dimmX or rankX directory |
| 97 | // https://www.kernel.org/doc/html/v5.0/admin-guide/ras.html#f5 |
| 98 | |
| 99 | if (de->d_type == DT_DIR && |
| 100 | ((strncmp(de->d_name, "rank", 4) == 0 || strncmp(de->d_name, "dimm", 4) == 0)) && |
| 101 | isdigit(de->d_name[4])) { |
| 102 | |
| 103 | struct edac_dimm *d = callocz(1, sizeof(struct edac_dimm)); |
| 104 | d->name = strdupz(de->d_name); |
| 105 | |
| 106 | struct stat st; |
| 107 | |
| 108 | snprintfz(name, FILENAME_MAX, "%s/%s/%s/dimm_ce_count", mc_dirname, m->name, de->d_name); |
| 109 | if(stat(name, &st) != -1) |
| 110 | d->ce.filename = strdupz(name); |
| 111 | |
| 112 | snprintfz(name, FILENAME_MAX, "%s/%s/%s/dimm_ue_count", mc_dirname, m->name, de->d_name); |
| 113 | if(stat(name, &st) != -1) |
| 114 | d->ue.filename = strdupz(name); |
| 115 | |
| 116 | if(!d->ce.filename && !d->ue.filename) { |
| 117 | freez(d->name); |
| 118 | freez(d); |
| 119 | } |
| 120 | else |
| 121 | DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(m->dimms, d, prev, next); |
| 122 | } |
| 123 | } |
| 124 | closedir(dir); |
| 125 | } |
| 126 | } |
| 127 | |
| 128 | static kernel_uint_t read_edac_count(struct edac_count *t) { |
| 129 | t->updated = false; |
| 130 | t->count = 0; |
| 131 | |
| 132 | if(t->filename) { |
| 133 | if(unlikely(!t->ff)) { |
| 134 | t->ff = procfile_open(t->filename, " \t", PROCFILE_FLAG_DEFAULT); |
| 135 | if(unlikely(!t->ff)) |
| 136 | return 0; |
| 137 | } |
| 138 | |
| 139 | t->ff = procfile_readall(t->ff); |
| 140 | if(unlikely(!t->ff || procfile_lines(t->ff) < 1 || procfile_linewords(t->ff, 0) < 1)) |
| 141 | return 0; |
| 142 | |
| 143 | t->count = str2ull(procfile_lineword(t->ff, 0, 0), NULL); |
| 144 | t->updated = true; |
| 145 | } |
| 146 | |
| 147 | return t->count; |
| 148 | } |
| 149 | |
| 150 | static bool read_edac_mc_file(const char *mc, const char *filename, char *out, size_t out_size) { |
| 151 | char f[FILENAME_MAX + 1]; |
| 152 | snprintfz(f, FILENAME_MAX, "%s/%s/%s", mc_dirname, mc, filename); |
| 153 | if(read_txt_file(f, out, out_size) != 0) { |
| 154 | collector_error("EDAC: cannot read file '%s'", f); |
| 155 | return false; |
| 156 | } |
| 157 | return true; |
| 158 | } |
| 159 | |
| 160 | static bool read_edac_mc_rank_file(const char *mc, const char *rank, const char *filename, char *out, size_t out_size) { |
| 161 | char f[FILENAME_MAX + 1]; |
| 162 | snprintfz(f, FILENAME_MAX, "%s/%s/%s/%s", mc_dirname, mc, rank, filename); |
| 163 | if(read_txt_file(f, out, out_size) != 0) { |
| 164 | collector_error("EDAC: cannot read file '%s'", f); |
| 165 | return false; |
| 166 | } |
| 167 | return true; |
| 168 | } |
| 169 | |
| 170 | int do_proc_sys_devices_system_edac_mc(int update_every, usec_t dt __maybe_unused) { |
| 171 | if(unlikely(!mc_root)) { |
| 172 | find_all_mc(); |
| 173 | |
| 174 | if(!mc_root) |
| 175 | // don't call this again |
| 176 | return 1; |
| 177 | } |
| 178 | |
| 179 | for(struct mc *m = mc_root; m; m = m->next) { |
| 180 | read_edac_count(&m->ce); |
| 181 | read_edac_count(&m->ce_noinfo); |
| 182 | read_edac_count(&m->ue); |
| 183 | read_edac_count(&m->ue_noinfo); |
| 184 | |
| 185 | for(struct edac_dimm *d = m->dimms; d ;d = d->next) { |
| 186 | read_edac_count(&d->ce); |
| 187 | read_edac_count(&d->ue); |
| 188 | } |
| 189 | } |
| 190 | |
| 191 | // -------------------------------------------------------------------- |
| 192 | |
| 193 | for(struct mc *m = mc_root; m ; m = m->next) { |
| 194 | if(unlikely(!m->ce.updated && !m->ue.updated && !m->ce_noinfo.updated && !m->ue_noinfo.updated)) |
| 195 | continue; |
| 196 | |
| 197 | if(unlikely(!m->st)) { |
| 198 | char id[RRD_ID_LENGTH_MAX + 1]; |
| 199 | snprintfz(id, RRD_ID_LENGTH_MAX, "edac_%s", m->name); |
| 200 | m->st = rrdset_create_localhost( |
| 201 | "mem" |
| 202 | , id |
| 203 | , NULL |
| 204 | , "edac" |
| 205 | , "mem.edac_mc_errors" |
| 206 | , "Memory Controller (MC) Error Detection And Correction (EDAC) Errors" |
| 207 | , "errors" |
| 208 | , PLUGIN_PROC_NAME |
| 209 | , "/sys/devices/system/edac/mc" |
| 210 | , NETDATA_CHART_PRIO_MEM_HW_ECC_CE |
| 211 | , update_every |
| 212 | , RRDSET_TYPE_LINE |
| 213 | ); |
| 214 | |
| 215 | rrdlabels_add(m->st->rrdlabels, "controller", m->name, RRDLABEL_SRC_AUTO); |
| 216 | |
| 217 | char buffer[1024 + 1]; |
| 218 | |
| 219 | if(read_edac_mc_file(m->name, "mc_name", buffer, 1024)) |
| 220 | rrdlabels_add(m->st->rrdlabels, "mc_name", buffer, RRDLABEL_SRC_AUTO); |
| 221 | |
| 222 | if(read_edac_mc_file(m->name, "size_mb", buffer, 1024)) |
| 223 | rrdlabels_add(m->st->rrdlabels, "size_mb", buffer, RRDLABEL_SRC_AUTO); |
| 224 | |
| 225 | if(read_edac_mc_file(m->name, "max_location", buffer, 1024)) |
| 226 | rrdlabels_add(m->st->rrdlabels, "max_location", buffer, RRDLABEL_SRC_AUTO); |
| 227 | |
| 228 | m->ce.rd = rrddim_add(m->st, "correctable", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); |
| 229 | m->ue.rd = rrddim_add(m->st, "uncorrectable", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); |
| 230 | m->ce_noinfo.rd = rrddim_add(m->st, "correctable_noinfo", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); |
| 231 | m->ue_noinfo.rd = rrddim_add(m->st, "uncorrectable_noinfo", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); |
| 232 | } |
| 233 | |
| 234 | rrddim_set_by_pointer(m->st, m->ce.rd, (collected_number)m->ce.count); |
| 235 | rrddim_set_by_pointer(m->st, m->ue.rd, (collected_number)m->ue.count); |
| 236 | rrddim_set_by_pointer(m->st, m->ce_noinfo.rd, (collected_number)m->ce_noinfo.count); |
| 237 | rrddim_set_by_pointer(m->st, m->ue_noinfo.rd, (collected_number)m->ue_noinfo.count); |
| 238 | |
| 239 | rrdset_done(m->st); |
| 240 | |
| 241 | for(struct edac_dimm *d = m->dimms; d ;d = d->next) { |
| 242 | if(unlikely(!d->ce.updated && !d->ue.updated)) |
| 243 | continue; |
| 244 | |
| 245 | if(unlikely(!d->st)) { |
| 246 | char id[RRD_ID_LENGTH_MAX + 1]; |
| 247 | snprintfz(id, RRD_ID_LENGTH_MAX, "edac_%s_%s", m->name, d->name); |
| 248 | d->st = rrdset_create_localhost( |
| 249 | "mem" |
| 250 | , id |
| 251 | , NULL |
| 252 | , "edac" |
| 253 | , "mem.edac_mc_dimm_errors" |
| 254 | , "DIMM Error Detection And Correction (EDAC) Errors" |
| 255 | , "errors" |
| 256 | , PLUGIN_PROC_NAME |
| 257 | , "/sys/devices/system/edac/mc" |
| 258 | , NETDATA_CHART_PRIO_MEM_HW_ECC_CE + 1 |
| 259 | , update_every |
| 260 | , RRDSET_TYPE_LINE |
| 261 | ); |
| 262 | |
| 263 | rrdlabels_add(d->st->rrdlabels, "controller", m->name, RRDLABEL_SRC_AUTO); |
| 264 | rrdlabels_add(d->st->rrdlabels, "dimm", d->name, RRDLABEL_SRC_AUTO); |
| 265 | |
| 266 | char buffer[1024 + 1]; |
| 267 | |
| 268 | if (read_edac_mc_rank_file(m->name, d->name, "dimm_dev_type", buffer, 1024)) |
| 269 | rrdlabels_add(d->st->rrdlabels, "dimm_dev_type", buffer, RRDLABEL_SRC_AUTO); |
| 270 | |
| 271 | if (read_edac_mc_rank_file(m->name, d->name, "dimm_edac_mode", buffer, 1024)) |
| 272 | rrdlabels_add(d->st->rrdlabels, "dimm_edac_mode", buffer, RRDLABEL_SRC_AUTO); |
| 273 | |
| 274 | if (read_edac_mc_rank_file(m->name, d->name, "dimm_label", buffer, 1024)) |
| 275 | rrdlabels_add(d->st->rrdlabels, "dimm_label", buffer, RRDLABEL_SRC_AUTO); |
| 276 | |
| 277 | if (read_edac_mc_rank_file(m->name, d->name, "dimm_location", buffer, 1024)) |
| 278 | rrdlabels_add(d->st->rrdlabels, "dimm_location", buffer, RRDLABEL_SRC_AUTO); |
| 279 | |
| 280 | if (read_edac_mc_rank_file(m->name, d->name, "dimm_mem_type", buffer, 1024)) |
| 281 | rrdlabels_add(d->st->rrdlabels, "dimm_mem_type", buffer, RRDLABEL_SRC_AUTO); |
| 282 | |
| 283 | if (read_edac_mc_rank_file(m->name, d->name, "size", buffer, 1024)) |
| 284 | rrdlabels_add(d->st->rrdlabels, "size", buffer, RRDLABEL_SRC_AUTO); |
| 285 | |
| 286 | d->ce.rd = rrddim_add(d->st, "correctable", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); |
| 287 | d->ue.rd = rrddim_add(d->st, "uncorrectable", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); |
| 288 | } |
| 289 | |
| 290 | rrddim_set_by_pointer(d->st, d->ce.rd, (collected_number)d->ce.count); |
| 291 | rrddim_set_by_pointer(d->st, d->ue.rd, (collected_number)d->ue.count); |
| 292 | |
| 293 | rrdset_done(d->st); |
| 294 | } |
| 295 | } |
| 296 | |
| 297 | return 0; |
| 298 | } |