| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #include "plugin_proc.h" |
| 4 | |
| 5 | struct node { |
| 6 | char *name; |
| 7 | |
| 8 | struct { |
| 9 | char *filename; |
| 10 | procfile *ff; |
| 11 | RRDSET *st; |
| 12 | } numastat; |
| 13 | |
| 14 | struct { |
| 15 | char *filename; |
| 16 | procfile *ff; |
| 17 | RRDSET *st_mem_usage; |
| 18 | RRDSET *st_mem_activity; |
| 19 | } meminfo; |
| 20 | |
| 21 | struct node *next; |
| 22 | }; |
| 23 | static struct node *numa_root = NULL; |
| 24 | static int numa_node_count = 0; |
| 25 | |
| 26 | static int find_all_nodes() { |
| 27 | int numa_node_count = 0; |
| 28 | char name[FILENAME_MAX + 1]; |
| 29 | snprintfz(name, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/sys/devices/system/node"); |
| 30 | const char *dirname = inicfg_get(&netdata_config, "plugin:proc:/sys/devices/system/node", "directory to monitor", name); |
| 31 | |
| 32 | DIR *dir = opendir(dirname); |
| 33 | if(!dir) { |
| 34 | nd_log( |
| 35 | NDLS_COLLECTORS, errno == ENOENT ? NDLP_INFO : NDLP_ERR, "Cannot read NUMA node directory '%s'", dirname); |
| 36 | return 0; |
| 37 | } |
| 38 | |
| 39 | struct dirent *de = NULL; |
| 40 | while((de = readdir(dir))) { |
| 41 | if(de->d_type != DT_DIR) |
| 42 | continue; |
| 43 | |
| 44 | if(strncmp(de->d_name, "node", 4) != 0) |
| 45 | continue; |
| 46 | |
| 47 | if(!isdigit(de->d_name[4])) |
| 48 | continue; |
| 49 | |
| 50 | numa_node_count++; |
| 51 | |
| 52 | struct node *m = callocz(1, sizeof(struct node)); |
| 53 | m->name = strdupz(de->d_name); |
| 54 | |
| 55 | struct stat st; |
| 56 | |
| 57 | snprintfz(name, FILENAME_MAX, "%s/%s/numastat", dirname, de->d_name); |
| 58 | if(stat(name, &st) == -1) { |
| 59 | freez(m->name); |
| 60 | freez(m); |
| 61 | continue; |
| 62 | } |
| 63 | m->numastat.filename = strdupz(name); |
| 64 | |
| 65 | snprintfz(name, FILENAME_MAX, "%s/%s/meminfo", dirname, de->d_name); |
| 66 | if(stat(name, &st) == -1) { |
| 67 | freez(m->numastat.filename); |
| 68 | freez(m->name); |
| 69 | freez(m); |
| 70 | continue; |
| 71 | } |
| 72 | m->meminfo.filename = strdupz(name); |
| 73 | |
| 74 | m->next = numa_root; |
| 75 | numa_root = m; |
| 76 | } |
| 77 | |
| 78 | closedir(dir); |
| 79 | |
| 80 | return numa_node_count; |
| 81 | } |
| 82 | |
| 83 | static void do_muma_numastat(struct node *m, int update_every) { |
| 84 | static uint32_t hash_local_node = 0, hash_numa_foreign = 0, hash_interleave_hit = 0, hash_other_node = 0, hash_numa_hit = 0, hash_numa_miss = 0; |
| 85 | static bool initialized = false; |
| 86 | |
| 87 | if(unlikely(!initialized)) { |
| 88 | initialized = true; |
| 89 | hash_local_node = simple_hash("local_node"); |
| 90 | hash_numa_foreign = simple_hash("numa_foreign"); |
| 91 | hash_interleave_hit = simple_hash("interleave_hit"); |
| 92 | hash_other_node = simple_hash("other_node"); |
| 93 | hash_numa_hit = simple_hash("numa_hit"); |
| 94 | hash_numa_miss = simple_hash("numa_miss"); |
| 95 | } |
| 96 | |
| 97 | if (m->numastat.filename) { |
| 98 | if(unlikely(!m->numastat.ff)) { |
| 99 | m->numastat.ff = procfile_open(m->numastat.filename, " ", PROCFILE_FLAG_DEFAULT); |
| 100 | |
| 101 | if(unlikely(!m->numastat.ff)) |
| 102 | return; |
| 103 | } |
| 104 | |
| 105 | m->numastat.ff = procfile_readall(m->numastat.ff); |
| 106 | if(unlikely(!m->numastat.ff || procfile_lines(m->numastat.ff) < 1 || procfile_linewords(m->numastat.ff, 0) < 1)) |
| 107 | return; |
| 108 | |
| 109 | if(unlikely(!m->numastat.st)) { |
| 110 | m->numastat.st = rrdset_create_localhost( |
| 111 | "numa_node_stat" |
| 112 | , m->name |
| 113 | , NULL |
| 114 | , "numa" |
| 115 | , "mem.numa_node_stat" |
| 116 | , "NUMA Node Memory Allocation Events" |
| 117 | , "events/s" |
| 118 | , PLUGIN_PROC_NAME |
| 119 | , "/sys/devices/system/node" |
| 120 | , NETDATA_CHART_PRIO_MEM_NUMA_NODES_NUMASTAT |
| 121 | , update_every |
| 122 | , RRDSET_TYPE_LINE |
| 123 | ); |
| 124 | |
| 125 | rrdlabels_add(m->numastat.st->rrdlabels, "numa_node", m->name, RRDLABEL_SRC_AUTO); |
| 126 | |
| 127 | rrddim_add(m->numastat.st, "numa_hit", "hit", 1, 1, RRD_ALGORITHM_INCREMENTAL); |
| 128 | rrddim_add(m->numastat.st, "numa_miss", "miss", 1, 1, RRD_ALGORITHM_INCREMENTAL); |
| 129 | rrddim_add(m->numastat.st, "local_node", "local", 1, 1, RRD_ALGORITHM_INCREMENTAL); |
| 130 | rrddim_add(m->numastat.st, "numa_foreign", "foreign", 1, 1, RRD_ALGORITHM_INCREMENTAL); |
| 131 | rrddim_add(m->numastat.st, "interleave_hit", "interleave", 1, 1, RRD_ALGORITHM_INCREMENTAL); |
| 132 | rrddim_add(m->numastat.st, "other_node", "other", 1, 1, RRD_ALGORITHM_INCREMENTAL); |
| 133 | |
| 134 | } |
| 135 | |
| 136 | size_t lines = procfile_lines(m->numastat.ff), l; |
| 137 | for(l = 0; l < lines; l++) { |
| 138 | size_t words = procfile_linewords(m->numastat.ff, l); |
| 139 | |
| 140 | if(unlikely(words < 2)) { |
| 141 | if(unlikely(words)) |
| 142 | collector_error("Cannot read %s line %zu. Expected 2 params, read %zu.", m->numastat.filename, l, words); |
| 143 | continue; |
| 144 | } |
| 145 | |
| 146 | char *name = procfile_lineword(m->numastat.ff, l, 0); |
| 147 | char *value = procfile_lineword(m->numastat.ff, l, 1); |
| 148 | |
| 149 | if (unlikely(!name || !*name || !value || !*value)) |
| 150 | continue; |
| 151 | |
| 152 | uint32_t hash = simple_hash(name); |
| 153 | |
| 154 | if ((hash == hash_numa_hit && !strcmp(name, "numa_hit")) || |
| 155 | (hash == hash_numa_miss && !strcmp(name, "numa_miss")) || |
| 156 | (hash == hash_local_node && !strcmp(name, "local_node")) || |
| 157 | (hash == hash_numa_foreign && !strcmp(name, "numa_foreign")) || |
| 158 | (hash == hash_interleave_hit && !strcmp(name, "interleave_hit")) || |
| 159 | (hash == hash_other_node && !strcmp(name, "other_node"))) { |
| 160 | rrddim_set(m->numastat.st, name, (collected_number)str2kernel_uint_t(value)); |
| 161 | } |
| 162 | } |
| 163 | |
| 164 | rrdset_done(m->numastat.st); |
| 165 | } |
| 166 | } |
| 167 | |
| 168 | static void do_numa_meminfo(struct node *m, int update_every) { |
| 169 | static uint32_t hash_MemFree = 0, hash_MemUsed = 0, hash_ActiveAnon = 0, hash_InactiveAnon = 0, hash_ActiveFile = 0, |
| 170 | hash_InactiveFile = 0; |
| 171 | static bool initialized = false; |
| 172 | |
| 173 | if (unlikely(!initialized)) { |
| 174 | initialized = true; |
| 175 | hash_MemFree = simple_hash("MemFree"); |
| 176 | hash_MemUsed = simple_hash("MemUsed"); |
| 177 | hash_ActiveAnon = simple_hash("Active(anon)"); |
| 178 | hash_InactiveAnon = simple_hash("Inactive(anon)"); |
| 179 | hash_ActiveFile = simple_hash("Active(file)"); |
| 180 | hash_InactiveFile = simple_hash("Inactive(file)"); |
| 181 | } |
| 182 | |
| 183 | if (m->meminfo.filename) { |
| 184 | if (unlikely(!m->meminfo.ff)) { |
| 185 | m->meminfo.ff = procfile_open(m->meminfo.filename, " :", PROCFILE_FLAG_DEFAULT); |
| 186 | if (unlikely(!m->meminfo.ff)) |
| 187 | return; |
| 188 | } |
| 189 | |
| 190 | m->meminfo.ff = procfile_readall(m->meminfo.ff); |
| 191 | if (unlikely(!m->meminfo.ff || procfile_lines(m->meminfo.ff) < 1 || procfile_linewords(m->meminfo.ff, 0) < 1)) |
| 192 | return; |
| 193 | |
| 194 | if (unlikely(!m->meminfo.st_mem_usage)) { |
| 195 | m->meminfo.st_mem_usage = rrdset_create_localhost( |
| 196 | "numa_node_mem_usage", |
| 197 | m->name, |
| 198 | NULL, |
| 199 | "numa", |
| 200 | "mem.numa_node_mem_usage", |
| 201 | "NUMA Node Memory Usage", |
| 202 | "bytes", |
| 203 | PLUGIN_PROC_NAME, |
| 204 | "/sys/devices/system/node", |
| 205 | NETDATA_CHART_PRIO_MEM_NUMA_NODES_MEMINFO, |
| 206 | update_every, |
| 207 | RRDSET_TYPE_STACKED); |
| 208 | |
| 209 | rrdlabels_add(m->meminfo.st_mem_usage->rrdlabels, "numa_node", m->name, RRDLABEL_SRC_AUTO); |
| 210 | |
| 211 | rrddim_add(m->meminfo.st_mem_usage, "MemFree", "free", 1, 1, RRD_ALGORITHM_ABSOLUTE); |
| 212 | rrddim_add(m->meminfo.st_mem_usage, "MemUsed", "used", 1, 1, RRD_ALGORITHM_ABSOLUTE); |
| 213 | } |
| 214 | if (unlikely(!m->meminfo.st_mem_activity)) { |
| 215 | m->meminfo.st_mem_activity = rrdset_create_localhost( |
| 216 | "numa_node_mem_activity", |
| 217 | m->name, |
| 218 | NULL, |
| 219 | "numa", |
| 220 | "mem.numa_node_mem_activity", |
| 221 | "NUMA Node Memory Activity", |
| 222 | "bytes", |
| 223 | PLUGIN_PROC_NAME, |
| 224 | "/sys/devices/system/node", |
| 225 | NETDATA_CHART_PRIO_MEM_NUMA_NODES_ACTIVITY, |
| 226 | update_every, |
| 227 | RRDSET_TYPE_STACKED); |
| 228 | |
| 229 | rrdlabels_add(m->meminfo.st_mem_activity->rrdlabels, "numa_node", m->name, RRDLABEL_SRC_AUTO); |
| 230 | |
| 231 | rrddim_add(m->meminfo.st_mem_activity, "Active(anon)", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); |
| 232 | rrddim_add(m->meminfo.st_mem_activity, "Inactive(anon)", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); |
| 233 | rrddim_add(m->meminfo.st_mem_activity, "Active(file)", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); |
| 234 | rrddim_add(m->meminfo.st_mem_activity, "Inactive(file)", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); |
| 235 | } |
| 236 | |
| 237 | size_t lines = procfile_lines(m->meminfo.ff), l; |
| 238 | for (l = 0; l < lines; l++) { |
| 239 | size_t words = procfile_linewords(m->meminfo.ff, l); |
| 240 | |
| 241 | if (unlikely(words < 4)) { |
| 242 | if (words) |
| 243 | collector_error( |
| 244 | "Cannot read %s line %zu. Expected 4 params, read %zu.", m->meminfo.filename, l, words); |
| 245 | continue; |
| 246 | } |
| 247 | |
| 248 | char *name = procfile_lineword(m->meminfo.ff, l, 2); |
| 249 | char *value = procfile_lineword(m->meminfo.ff, l, 3); |
| 250 | |
| 251 | if (unlikely(!name || !*name || !value || !*value)) |
| 252 | continue; |
| 253 | |
| 254 | uint32_t hash = simple_hash(name); |
| 255 | |
| 256 | if ((hash == hash_MemFree && !strcmp(name, "MemFree")) || |
| 257 | (hash == hash_MemUsed && !strcmp(name, "MemUsed"))) { |
| 258 | rrddim_set(m->meminfo.st_mem_usage, name, (collected_number)str2kernel_uint_t(value) * 1024); |
| 259 | } else if ( |
| 260 | (hash == hash_ActiveAnon && !strcmp(name, "Active(anon)")) || |
| 261 | (hash == hash_InactiveAnon && !strcmp(name, "Inactive(anon)")) || |
| 262 | (hash == hash_ActiveFile && !strcmp(name, "Active(file)")) || |
| 263 | (hash == hash_InactiveFile && !strcmp(name, "Inactive(file)"))) { |
| 264 | rrddim_set(m->meminfo.st_mem_activity, name, (collected_number)str2kernel_uint_t(value) * 1024); |
| 265 | } |
| 266 | } |
| 267 | rrdset_done(m->meminfo.st_mem_usage); |
| 268 | rrdset_done(m->meminfo.st_mem_activity); |
| 269 | } |
| 270 | } |
| 271 | |
| 272 | int do_proc_sys_devices_system_node(int update_every, usec_t dt) { |
| 273 | (void)dt; |
| 274 | struct node *m; |
| 275 | |
| 276 | static int do_numastat = -1; |
| 277 | |
| 278 | if(unlikely(do_numastat == -1)) { |
| 279 | do_numastat = inicfg_get_boolean_ondemand( |
| 280 | &netdata_config, "plugin:proc:/sys/devices/system/node", "enable per-node numa metrics", CONFIG_BOOLEAN_AUTO); |
| 281 | } |
| 282 | |
| 283 | if(unlikely(numa_root == NULL)) { |
| 284 | numa_node_count = find_all_nodes(); |
| 285 | if(unlikely(numa_root == NULL)) |
| 286 | return 1; |
| 287 | } |
| 288 | |
| 289 | if (do_numastat == CONFIG_BOOLEAN_YES || (do_numastat == CONFIG_BOOLEAN_AUTO && numa_node_count >= 2)) { |
| 290 | for (m = numa_root; m; m = m->next) { |
| 291 | do_muma_numastat(m, update_every); |
| 292 | do_numa_meminfo(m, update_every); |
| 293 | } |
| 294 | return 0; |
| 295 | } |
| 296 | |
| 297 | return 1; |
| 298 | } |