| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #include "plugin_proc.h" |
| 4 | |
| 5 | #define PLUGIN_PROC_MODULE_STAT_NAME "/proc/stat" |
| 6 | |
| 7 | #define _COMMON_PLUGIN_NAME PLUGIN_PROC_NAME |
| 8 | #define _COMMON_PLUGIN_MODULE_NAME PLUGIN_PROC_MODULE_STAT_NAME |
| 9 | #include "../common-contexts/common-contexts.h" |
| 10 | |
| 11 | struct per_core_single_number_file { |
| 12 | unsigned char found:1; |
| 13 | const char *filename; |
| 14 | int fd; |
| 15 | collected_number value; |
| 16 | RRDDIM *rd; |
| 17 | }; |
| 18 | |
| 19 | struct last_ticks { |
| 20 | collected_number frequency; |
| 21 | collected_number ticks; |
| 22 | }; |
| 23 | |
| 24 | // This is an extension of struct per_core_single_number_file at CPU_FREQ_INDEX. |
| 25 | // Either scaling_cur_freq or time_in_state file is used at one time. |
| 26 | struct per_core_time_in_state_file { |
| 27 | const char *filename; |
| 28 | procfile *ff; |
| 29 | size_t last_ticks_len; |
| 30 | struct last_ticks *last_ticks; |
| 31 | }; |
| 32 | |
| 33 | #define CORE_THROTTLE_COUNT_INDEX 0 |
| 34 | #define PACKAGE_THROTTLE_COUNT_INDEX 1 |
| 35 | #define CPU_FREQ_INDEX 2 |
| 36 | #define PER_CORE_FILES 3 |
| 37 | |
| 38 | struct cpu_chart { |
| 39 | const char *id; |
| 40 | |
| 41 | RRDSET *st; |
| 42 | RRDDIM *rd_user; |
| 43 | RRDDIM *rd_nice; |
| 44 | RRDDIM *rd_system; |
| 45 | RRDDIM *rd_idle; |
| 46 | RRDDIM *rd_iowait; |
| 47 | RRDDIM *rd_irq; |
| 48 | RRDDIM *rd_softirq; |
| 49 | RRDDIM *rd_steal; |
| 50 | RRDDIM *rd_guest; |
| 51 | RRDDIM *rd_guest_nice; |
| 52 | |
| 53 | bool per_core_files_found; |
| 54 | struct per_core_single_number_file files[PER_CORE_FILES]; |
| 55 | |
| 56 | struct per_core_time_in_state_file time_in_state_files; |
| 57 | }; |
| 58 | |
| 59 | static int keep_per_core_fds_open = CONFIG_BOOLEAN_YES; |
| 60 | static int keep_cpuidle_fds_open = CONFIG_BOOLEAN_YES; |
| 61 | |
| 62 | static int read_per_core_files(struct cpu_chart *all_cpu_charts, size_t len, size_t index) { |
| 63 | char buf[50 + 1]; |
| 64 | size_t x, files_read = 0, files_nonzero = 0; |
| 65 | |
| 66 | for(x = 0; x < len ; x++) { |
| 67 | struct per_core_single_number_file *f = &all_cpu_charts[x].files[index]; |
| 68 | |
| 69 | f->found = 0; |
| 70 | |
| 71 | if(unlikely(!f->filename)) |
| 72 | continue; |
| 73 | |
| 74 | if(unlikely(f->fd == -1)) { |
| 75 | f->fd = open(f->filename, O_RDONLY | O_CLOEXEC); |
| 76 | if (unlikely(f->fd == -1)) { |
| 77 | collector_error("Cannot open file '%s'", f->filename); |
| 78 | continue; |
| 79 | } |
| 80 | } |
| 81 | |
| 82 | ssize_t ret = read(f->fd, buf, 50); |
| 83 | if(unlikely(ret < 0)) { |
| 84 | // cannot read that file |
| 85 | |
| 86 | collector_error("Cannot read file '%s'", f->filename); |
| 87 | close(f->fd); |
| 88 | f->fd = -1; |
| 89 | continue; |
| 90 | } |
| 91 | else { |
| 92 | // successful read |
| 93 | |
| 94 | // terminate the buffer |
| 95 | buf[ret] = '\0'; |
| 96 | |
| 97 | if(unlikely(keep_per_core_fds_open != CONFIG_BOOLEAN_YES)) { |
| 98 | close(f->fd); |
| 99 | f->fd = -1; |
| 100 | } |
| 101 | else if(lseek(f->fd, 0, SEEK_SET) == -1) { |
| 102 | collector_error("Cannot seek in file '%s'", f->filename); |
| 103 | close(f->fd); |
| 104 | f->fd = -1; |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | files_read++; |
| 109 | f->found = 1; |
| 110 | |
| 111 | f->value = str2ll(buf, NULL); |
| 112 | if(likely(f->value != 0)) |
| 113 | files_nonzero++; |
| 114 | } |
| 115 | |
| 116 | if(files_read == 0) |
| 117 | return -1; |
| 118 | |
| 119 | if(files_nonzero == 0) |
| 120 | return 0; |
| 121 | |
| 122 | return (int)files_nonzero; |
| 123 | } |
| 124 | |
| 125 | static int read_per_core_time_in_state_files(struct cpu_chart *all_cpu_charts, size_t len, size_t index) { |
| 126 | size_t x, files_read = 0, files_nonzero = 0; |
| 127 | |
| 128 | for(x = 0; x < len ; x++) { |
| 129 | struct per_core_single_number_file *f = &all_cpu_charts[x].files[index]; |
| 130 | struct per_core_time_in_state_file *tsf = &all_cpu_charts[x].time_in_state_files; |
| 131 | |
| 132 | f->found = 0; |
| 133 | |
| 134 | if(unlikely(!tsf->filename)) |
| 135 | continue; |
| 136 | |
| 137 | if(unlikely(!tsf->ff)) { |
| 138 | tsf->ff = procfile_open(tsf->filename, " \t:", PROCFILE_FLAG_DEFAULT); |
| 139 | if(unlikely(!tsf->ff)) |
| 140 | { |
| 141 | collector_error("Cannot open file '%s'", tsf->filename); |
| 142 | continue; |
| 143 | } |
| 144 | } |
| 145 | |
| 146 | tsf->ff = procfile_readall(tsf->ff); |
| 147 | if(unlikely(!tsf->ff)) { |
| 148 | collector_error("Cannot read file '%s'", tsf->filename); |
| 149 | procfile_close(tsf->ff); |
| 150 | tsf->ff = NULL; |
| 151 | continue; |
| 152 | } |
| 153 | else { |
| 154 | // successful read |
| 155 | |
| 156 | size_t lines = procfile_lines(tsf->ff), l; |
| 157 | size_t words; |
| 158 | unsigned long long total_ticks_since_last = 0, avg_freq = 0; |
| 159 | |
| 160 | // Check if there is at least one frequency in time_in_state |
| 161 | if (procfile_word(tsf->ff, 0)[0] == '\0') { |
| 162 | if(unlikely(keep_per_core_fds_open != CONFIG_BOOLEAN_YES)) { |
| 163 | procfile_close(tsf->ff); |
| 164 | tsf->ff = NULL; |
| 165 | } |
| 166 | // TODO: Is there a better way to avoid spikes than calculating the average over |
| 167 | // the whole period under schedutil governor? |
| 168 | // freez(tsf->last_ticks); |
| 169 | // tsf->last_ticks = NULL; |
| 170 | // tsf->last_ticks_len = 0; |
| 171 | continue; |
| 172 | } |
| 173 | |
| 174 | if (unlikely(tsf->last_ticks_len < lines || tsf->last_ticks == NULL)) { |
| 175 | tsf->last_ticks = reallocz(tsf->last_ticks, sizeof(struct last_ticks) * lines); |
| 176 | memset(tsf->last_ticks, 0, sizeof(struct last_ticks) * lines); |
| 177 | tsf->last_ticks_len = lines; |
| 178 | } |
| 179 | |
| 180 | f->value = 0; |
| 181 | |
| 182 | for(l = 0; l < lines - 1 ;l++) { |
| 183 | unsigned long long frequency = 0, ticks = 0, ticks_since_last = 0; |
| 184 | |
| 185 | words = procfile_linewords(tsf->ff, l); |
| 186 | if(unlikely(words < 2)) { |
| 187 | collector_error("Cannot read time_in_state line. Expected 2 params, read %zu.", words); |
| 188 | continue; |
| 189 | } |
| 190 | frequency = str2ull(procfile_lineword(tsf->ff, l, 0), NULL); |
| 191 | ticks = str2ull(procfile_lineword(tsf->ff, l, 1), NULL); |
| 192 | |
| 193 | // It is assumed that frequencies are static and sorted |
| 194 | ticks_since_last = ticks - tsf->last_ticks[l].ticks; |
| 195 | tsf->last_ticks[l].frequency = frequency; |
| 196 | tsf->last_ticks[l].ticks = ticks; |
| 197 | |
| 198 | total_ticks_since_last += ticks_since_last; |
| 199 | avg_freq += frequency * ticks_since_last; |
| 200 | |
| 201 | } |
| 202 | |
| 203 | if (likely(total_ticks_since_last)) { |
| 204 | avg_freq /= total_ticks_since_last; |
| 205 | f->value = avg_freq; |
| 206 | } |
| 207 | |
| 208 | if(unlikely(keep_per_core_fds_open != CONFIG_BOOLEAN_YES)) { |
| 209 | procfile_close(tsf->ff); |
| 210 | tsf->ff = NULL; |
| 211 | } |
| 212 | } |
| 213 | |
| 214 | files_read++; |
| 215 | |
| 216 | f->found = 1; |
| 217 | |
| 218 | if(likely(f->value != 0)) |
| 219 | files_nonzero++; |
| 220 | } |
| 221 | |
| 222 | if(unlikely(files_read == 0)) |
| 223 | return -1; |
| 224 | |
| 225 | if(unlikely(files_nonzero == 0)) |
| 226 | return 0; |
| 227 | |
| 228 | return (int)files_nonzero; |
| 229 | } |
| 230 | |
| 231 | static void chart_per_core_files(struct cpu_chart *all_cpu_charts, size_t len, size_t index, RRDSET *st, collected_number multiplier, collected_number divisor, RRD_ALGORITHM algorithm) { |
| 232 | size_t x; |
| 233 | for(x = 0; x < len ; x++) { |
| 234 | struct per_core_single_number_file *f = &all_cpu_charts[x].files[index]; |
| 235 | |
| 236 | if(unlikely(!f->found)) |
| 237 | continue; |
| 238 | |
| 239 | if(unlikely(!f->rd)) |
| 240 | f->rd = rrddim_add(st, all_cpu_charts[x].id, NULL, multiplier, divisor, algorithm); |
| 241 | |
| 242 | rrddim_set_by_pointer(st, f->rd, f->value); |
| 243 | } |
| 244 | } |
| 245 | |
| 246 | struct cpuidle_state { |
| 247 | char *name; |
| 248 | |
| 249 | char *time_filename; |
| 250 | int time_fd; |
| 251 | |
| 252 | collected_number value; |
| 253 | |
| 254 | RRDDIM *rd; |
| 255 | }; |
| 256 | |
| 257 | struct per_core_cpuidle_chart { |
| 258 | RRDSET *st; |
| 259 | |
| 260 | RRDDIM *active_time_rd; |
| 261 | collected_number active_time; |
| 262 | collected_number last_active_time; |
| 263 | |
| 264 | struct cpuidle_state *cpuidle_state; |
| 265 | size_t cpuidle_state_len; |
| 266 | int rescan_cpu_states; |
| 267 | }; |
| 268 | |
| 269 | static void* wake_cpu_thread(void* core) { |
| 270 | pthread_t thread; |
| 271 | cpu_set_t cpu_set; |
| 272 | static size_t cpu_wakeups = 0; |
| 273 | static int errors = 0; |
| 274 | |
| 275 | CPU_ZERO(&cpu_set); |
| 276 | CPU_SET(*(int*)core, &cpu_set); |
| 277 | |
| 278 | thread = pthread_self(); |
| 279 | if(unlikely(pthread_setaffinity_np(thread, sizeof(cpu_set_t), &cpu_set))) { |
| 280 | if(unlikely(errors < 8)) { |
| 281 | collector_error("Cannot set CPU affinity for core %d", *(int*)core); |
| 282 | errors++; |
| 283 | } |
| 284 | else if(unlikely(errors < 9)) { |
| 285 | collector_error("CPU affinity errors are disabled"); |
| 286 | errors++; |
| 287 | } |
| 288 | } |
| 289 | |
| 290 | // Make the CPU core do something to force it to update its idle counters |
| 291 | cpu_wakeups++; |
| 292 | |
| 293 | return 0; |
| 294 | } |
| 295 | |
| 296 | static int read_schedstat(const char *schedstat_filename, struct per_core_cpuidle_chart **cpuidle_charts_address, size_t *schedstat_cores_found) { |
| 297 | static size_t cpuidle_charts_len = 0; |
| 298 | static procfile *ff = NULL; |
| 299 | struct per_core_cpuidle_chart *cpuidle_charts = *cpuidle_charts_address; |
| 300 | size_t cores_found = 0; |
| 301 | |
| 302 | if(unlikely(!ff)) { |
| 303 | ff = procfile_open(schedstat_filename, " \t:", PROCFILE_FLAG_DEFAULT); |
| 304 | if(unlikely(!ff)) return 1; |
| 305 | } |
| 306 | |
| 307 | ff = procfile_readall(ff); |
| 308 | if(unlikely(!ff)) return 1; |
| 309 | |
| 310 | size_t lines = procfile_lines(ff), l; |
| 311 | size_t words; |
| 312 | |
| 313 | for(l = 0; l < lines ;l++) { |
| 314 | char *row_key = procfile_lineword(ff, l, 0); |
| 315 | |
| 316 | // faster strncmp(row_key, "cpu", 3) == 0 |
| 317 | if(likely(row_key[0] == 'c' && row_key[1] == 'p' && row_key[2] == 'u')) { |
| 318 | words = procfile_linewords(ff, l); |
| 319 | if(unlikely(words < 10)) { |
| 320 | collector_error("Cannot read /proc/schedstat cpu line. Expected 9 params, read %zu.", words); |
| 321 | return 1; |
| 322 | } |
| 323 | cores_found++; |
| 324 | |
| 325 | size_t core = str2ul(&row_key[3]); |
| 326 | if(unlikely(core >= cores_found)) { |
| 327 | collector_error("Core %zu found but no more than %zu cores were expected.", core, cores_found); |
| 328 | return 1; |
| 329 | } |
| 330 | |
| 331 | if(unlikely(cpuidle_charts_len < cores_found)) { |
| 332 | cpuidle_charts = reallocz(cpuidle_charts, sizeof(struct per_core_cpuidle_chart) * cores_found); |
| 333 | *cpuidle_charts_address = cpuidle_charts; |
| 334 | memset(cpuidle_charts + cpuidle_charts_len, 0, sizeof(struct per_core_cpuidle_chart) * (cores_found - cpuidle_charts_len)); |
| 335 | cpuidle_charts_len = cores_found; |
| 336 | } |
| 337 | |
| 338 | cpuidle_charts[core].active_time = str2ull(procfile_lineword(ff, l, 7), NULL) / 1000; |
| 339 | } |
| 340 | } |
| 341 | |
| 342 | *schedstat_cores_found = cores_found; |
| 343 | return 0; |
| 344 | } |
| 345 | |
| 346 | static int read_one_state(char *buf, const char *filename, int *fd) { |
| 347 | ssize_t ret = read(*fd, buf, 50); |
| 348 | |
| 349 | if(unlikely(ret <= 0)) { |
| 350 | // cannot read that file |
| 351 | collector_error("Cannot read file '%s'", filename); |
| 352 | close(*fd); |
| 353 | *fd = -1; |
| 354 | return 0; |
| 355 | } |
| 356 | else { |
| 357 | // successful read |
| 358 | |
| 359 | // terminate the buffer |
| 360 | buf[ret - 1] = '\0'; |
| 361 | |
| 362 | if(unlikely(keep_cpuidle_fds_open != CONFIG_BOOLEAN_YES)) { |
| 363 | close(*fd); |
| 364 | *fd = -1; |
| 365 | } |
| 366 | else if(lseek(*fd, 0, SEEK_SET) == -1) { |
| 367 | collector_error("Cannot seek in file '%s'", filename); |
| 368 | close(*fd); |
| 369 | *fd = -1; |
| 370 | } |
| 371 | } |
| 372 | |
| 373 | return 1; |
| 374 | } |
| 375 | |
| 376 | static int read_cpuidle_states(const char *cpuidle_name_filename, const char *cpuidle_time_filename, struct per_core_cpuidle_chart *cpuidle_charts, size_t core) { |
| 377 | char filename[FILENAME_MAX + 1]; |
| 378 | static char next_state_filename[FILENAME_MAX + 1]; |
| 379 | struct stat stbuf; |
| 380 | struct per_core_cpuidle_chart *cc = &cpuidle_charts[core]; |
| 381 | size_t state; |
| 382 | |
| 383 | if(unlikely(!cc->cpuidle_state_len || cc->rescan_cpu_states)) { |
| 384 | int state_file_found = 1; // check at least one state |
| 385 | |
| 386 | if(cc->cpuidle_state_len) { |
| 387 | for(state = 0; state < cc->cpuidle_state_len; state++) { |
| 388 | freez(cc->cpuidle_state[state].name); |
| 389 | |
| 390 | freez(cc->cpuidle_state[state].time_filename); |
| 391 | close(cc->cpuidle_state[state].time_fd); |
| 392 | cc->cpuidle_state[state].time_fd = -1; |
| 393 | } |
| 394 | |
| 395 | freez(cc->cpuidle_state); |
| 396 | cc->cpuidle_state = NULL; |
| 397 | cc->cpuidle_state_len = 0; |
| 398 | |
| 399 | cc->active_time_rd = NULL; |
| 400 | cc->st = NULL; |
| 401 | } |
| 402 | |
| 403 | while(likely(state_file_found)) { |
| 404 | snprintfz(filename, FILENAME_MAX, cpuidle_name_filename, core, cc->cpuidle_state_len); |
| 405 | if (stat(filename, &stbuf) == 0) |
| 406 | cc->cpuidle_state_len++; |
| 407 | else |
| 408 | state_file_found = 0; |
| 409 | } |
| 410 | snprintfz(next_state_filename, FILENAME_MAX, cpuidle_name_filename, core, cc->cpuidle_state_len); |
| 411 | |
| 412 | if(likely(cc->cpuidle_state_len)) |
| 413 | cc->cpuidle_state = callocz(cc->cpuidle_state_len, sizeof(struct cpuidle_state)); |
| 414 | |
| 415 | for(state = 0; state < cc->cpuidle_state_len; state++) { |
| 416 | char name_buf[50 + 1]; |
| 417 | snprintfz(filename, FILENAME_MAX, cpuidle_name_filename, core, state); |
| 418 | |
| 419 | int fd = open(filename, O_RDONLY | O_CLOEXEC, 0666); |
| 420 | if(unlikely(fd == -1)) { |
| 421 | collector_error("Cannot open file '%s'", filename); |
| 422 | cc->rescan_cpu_states = 1; |
| 423 | return 1; |
| 424 | } |
| 425 | |
| 426 | ssize_t r = read(fd, name_buf, 50); |
| 427 | if(unlikely(r < 1)) { |
| 428 | collector_error("Cannot read file '%s'", filename); |
| 429 | close(fd); |
| 430 | cc->rescan_cpu_states = 1; |
| 431 | return 1; |
| 432 | } |
| 433 | |
| 434 | name_buf[r - 1] = '\0'; // erase extra character |
| 435 | cc->cpuidle_state[state].name = strdupz(trim(name_buf)); |
| 436 | close(fd); |
| 437 | |
| 438 | snprintfz(filename, FILENAME_MAX, cpuidle_time_filename, core, state); |
| 439 | cc->cpuidle_state[state].time_filename = strdupz(filename); |
| 440 | cc->cpuidle_state[state].time_fd = -1; |
| 441 | } |
| 442 | |
| 443 | cc->rescan_cpu_states = 0; |
| 444 | } |
| 445 | |
| 446 | for(state = 0; state < cc->cpuidle_state_len; state++) { |
| 447 | |
| 448 | struct cpuidle_state *cs = &cc->cpuidle_state[state]; |
| 449 | |
| 450 | if(unlikely(cs->time_fd == -1)) { |
| 451 | cs->time_fd = open(cs->time_filename, O_RDONLY | O_CLOEXEC); |
| 452 | if (unlikely(cs->time_fd == -1)) { |
| 453 | collector_error("Cannot open file '%s'", cs->time_filename); |
| 454 | cc->rescan_cpu_states = 1; |
| 455 | return 1; |
| 456 | } |
| 457 | } |
| 458 | |
| 459 | char time_buf[50 + 1]; |
| 460 | if(likely(read_one_state(time_buf, cs->time_filename, &cs->time_fd))) { |
| 461 | cs->value = str2ll(time_buf, NULL); |
| 462 | } |
| 463 | else { |
| 464 | cc->rescan_cpu_states = 1; |
| 465 | return 1; |
| 466 | } |
| 467 | } |
| 468 | |
| 469 | // check if the number of states was increased |
| 470 | if(unlikely(stat(next_state_filename, &stbuf) == 0)) { |
| 471 | cc->rescan_cpu_states = 1; |
| 472 | return 1; |
| 473 | } |
| 474 | |
| 475 | return 0; |
| 476 | } |
| 477 | |
| 478 | static const RRDVAR_ACQUIRED *cpus_var = NULL; |
| 479 | |
| 480 | void proc_stat_plugin_cleanup(void) { |
| 481 | // Cleanup any acquired RRDVARs |
| 482 | if (cpus_var) { |
| 483 | rrdvar_host_variable_release(localhost, cpus_var); |
| 484 | cpus_var = NULL; |
| 485 | } |
| 486 | } |
| 487 | |
| 488 | int do_proc_stat(int update_every, usec_t dt) { |
| 489 | (void)dt; |
| 490 | |
| 491 | static struct cpu_chart *all_cpu_charts = NULL; |
| 492 | static size_t all_cpu_charts_size = 0; |
| 493 | static procfile *ff = NULL; |
| 494 | static int do_cpu = -1, do_cpu_cores = -1, do_interrupts = -1, do_context = -1, do_forks = -1, do_processes = -1, |
| 495 | do_core_throttle_count = -1, do_package_throttle_count = -1, do_cpu_freq = -1, do_cpuidle = -1; |
| 496 | static uint32_t hash_intr, hash_ctxt, hash_processes, hash_procs_running, hash_procs_blocked; |
| 497 | static const char *core_throttle_count_filename = NULL, *package_throttle_count_filename = NULL, *scaling_cur_freq_filename = NULL, |
| 498 | *time_in_state_filename = NULL, *schedstat_filename = NULL, *cpuidle_name_filename = NULL, *cpuidle_time_filename = NULL; |
| 499 | |
| 500 | static int accurate_freq_avail = 0, accurate_freq_is_used = 0; |
| 501 | size_t cores_found = (size_t)os_get_system_cpus(); |
| 502 | |
| 503 | if(unlikely(do_cpu == -1)) { |
| 504 | do_cpu = inicfg_get_boolean(&netdata_config, "plugin:proc:/proc/stat", "cpu utilization", CONFIG_BOOLEAN_YES); |
| 505 | do_cpu_cores = inicfg_get_boolean(&netdata_config, "plugin:proc:/proc/stat", "per cpu core utilization", CONFIG_BOOLEAN_NO); |
| 506 | do_interrupts = inicfg_get_boolean(&netdata_config, "plugin:proc:/proc/stat", "cpu interrupts", CONFIG_BOOLEAN_YES); |
| 507 | do_context = inicfg_get_boolean(&netdata_config, "plugin:proc:/proc/stat", "context switches", CONFIG_BOOLEAN_YES); |
| 508 | do_forks = inicfg_get_boolean(&netdata_config, "plugin:proc:/proc/stat", "processes started", CONFIG_BOOLEAN_YES); |
| 509 | do_processes = inicfg_get_boolean(&netdata_config, "plugin:proc:/proc/stat", "processes running", CONFIG_BOOLEAN_YES); |
| 510 | |
| 511 | // give sane defaults based on the number of processors |
| 512 | if(unlikely(os_get_system_cpus() > 128)) { |
| 513 | // the system has too many processors |
| 514 | keep_per_core_fds_open = CONFIG_BOOLEAN_NO; |
| 515 | do_core_throttle_count = CONFIG_BOOLEAN_NO; |
| 516 | do_package_throttle_count = CONFIG_BOOLEAN_NO; |
| 517 | do_cpu_freq = CONFIG_BOOLEAN_NO; |
| 518 | do_cpuidle = CONFIG_BOOLEAN_NO; |
| 519 | } |
| 520 | else { |
| 521 | // the system has a reasonable number of processors |
| 522 | keep_per_core_fds_open = CONFIG_BOOLEAN_YES; |
| 523 | do_core_throttle_count = CONFIG_BOOLEAN_AUTO; |
| 524 | do_package_throttle_count = CONFIG_BOOLEAN_NO; |
| 525 | do_cpu_freq = CONFIG_BOOLEAN_YES; |
| 526 | do_cpuidle = CONFIG_BOOLEAN_NO; |
| 527 | } |
| 528 | if(unlikely(os_get_system_cpus() > 24)) { |
| 529 | // the system has too many processors |
| 530 | keep_cpuidle_fds_open = CONFIG_BOOLEAN_NO; |
| 531 | } |
| 532 | else { |
| 533 | // the system has a reasonable number of processors |
| 534 | keep_cpuidle_fds_open = CONFIG_BOOLEAN_YES; |
| 535 | } |
| 536 | |
| 537 | keep_per_core_fds_open = inicfg_get_boolean(&netdata_config, "plugin:proc:/proc/stat", "keep per core files open", keep_per_core_fds_open); |
| 538 | keep_cpuidle_fds_open = inicfg_get_boolean(&netdata_config, "plugin:proc:/proc/stat", "keep cpuidle files open", keep_cpuidle_fds_open); |
| 539 | do_core_throttle_count = inicfg_get_boolean_ondemand(&netdata_config, "plugin:proc:/proc/stat", "core_throttle_count", do_core_throttle_count); |
| 540 | do_package_throttle_count = inicfg_get_boolean_ondemand(&netdata_config, "plugin:proc:/proc/stat", "package_throttle_count", do_package_throttle_count); |
| 541 | do_cpu_freq = inicfg_get_boolean_ondemand(&netdata_config, "plugin:proc:/proc/stat", "cpu frequency", do_cpu_freq); |
| 542 | do_cpuidle = inicfg_get_boolean_ondemand(&netdata_config, "plugin:proc:/proc/stat", "cpu idle states", do_cpuidle); |
| 543 | |
| 544 | hash_intr = simple_hash("intr"); |
| 545 | hash_ctxt = simple_hash("ctxt"); |
| 546 | hash_processes = simple_hash("processes"); |
| 547 | hash_procs_running = simple_hash("procs_running"); |
| 548 | hash_procs_blocked = simple_hash("procs_blocked"); |
| 549 | |
| 550 | char filename[FILENAME_MAX + 1]; |
| 551 | snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/sys/devices/system/cpu/%s/thermal_throttle/core_throttle_count"); |
| 552 | core_throttle_count_filename = inicfg_get(&netdata_config, "plugin:proc:/proc/stat", "core_throttle_count filename to monitor", filename); |
| 553 | |
| 554 | snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/sys/devices/system/cpu/%s/thermal_throttle/package_throttle_count"); |
| 555 | package_throttle_count_filename = inicfg_get(&netdata_config, "plugin:proc:/proc/stat", "package_throttle_count filename to monitor", filename); |
| 556 | |
| 557 | snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/sys/devices/system/cpu/%s/cpufreq/scaling_cur_freq"); |
| 558 | scaling_cur_freq_filename = inicfg_get(&netdata_config, "plugin:proc:/proc/stat", "scaling_cur_freq filename to monitor", filename); |
| 559 | |
| 560 | snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/sys/devices/system/cpu/%s/cpufreq/stats/time_in_state"); |
| 561 | time_in_state_filename = inicfg_get(&netdata_config, "plugin:proc:/proc/stat", "time_in_state filename to monitor", filename); |
| 562 | |
| 563 | snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/proc/schedstat"); |
| 564 | schedstat_filename = inicfg_get(&netdata_config, "plugin:proc:/proc/stat", "schedstat filename to monitor", filename); |
| 565 | |
| 566 | if(do_cpuidle != CONFIG_BOOLEAN_NO) { |
| 567 | struct stat stbuf; |
| 568 | |
| 569 | if (stat(schedstat_filename, &stbuf)) |
| 570 | do_cpuidle = CONFIG_BOOLEAN_NO; |
| 571 | } |
| 572 | |
| 573 | snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/sys/devices/system/cpu/cpu%zu/cpuidle/state%zu/name"); |
| 574 | cpuidle_name_filename = inicfg_get(&netdata_config, "plugin:proc:/proc/stat", "cpuidle name filename to monitor", filename); |
| 575 | |
| 576 | snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/sys/devices/system/cpu/cpu%zu/cpuidle/state%zu/time"); |
| 577 | cpuidle_time_filename = inicfg_get(&netdata_config, "plugin:proc:/proc/stat", "cpuidle time filename to monitor", filename); |
| 578 | } |
| 579 | |
| 580 | if(unlikely(!ff)) { |
| 581 | char filename[FILENAME_MAX + 1]; |
| 582 | snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/proc/stat"); |
| 583 | ff = procfile_open(inicfg_get(&netdata_config, "plugin:proc:/proc/stat", "filename to monitor", filename), " \t:", PROCFILE_FLAG_DEFAULT); |
| 584 | if(unlikely(!ff)) return 1; |
| 585 | } |
| 586 | |
| 587 | ff = procfile_readall(ff); |
| 588 | if(unlikely(!ff)) return 0; // we return 0, so that we will retry to open it next time |
| 589 | |
| 590 | size_t lines = procfile_lines(ff), l; |
| 591 | size_t words; |
| 592 | |
| 593 | unsigned long long processes = 0, running = 0 , blocked = 0; |
| 594 | |
| 595 | for(l = 0; l < lines ;l++) { |
| 596 | char *row_key = procfile_lineword(ff, l, 0); |
| 597 | uint32_t hash = simple_hash(row_key); |
| 598 | |
| 599 | // faster strncmp(row_key, "cpu", 3) == 0 |
| 600 | if(likely(row_key[0] == 'c' && row_key[1] == 'p' && row_key[2] == 'u')) { |
| 601 | words = procfile_linewords(ff, l); |
| 602 | if(unlikely(words < 9)) { |
| 603 | collector_error("Cannot read /proc/stat cpu line. Expected 9 params, read %zu.", words); |
| 604 | continue; |
| 605 | } |
| 606 | |
| 607 | size_t core = (row_key[3] == '\0') ? 0 : str2ul(&row_key[3]) + 1; |
| 608 | if (likely(core > 0)) |
| 609 | cores_found = core; |
| 610 | |
| 611 | bool do_any_core_metric = do_cpu_cores || do_core_throttle_count || do_cpu_freq || do_cpuidle; |
| 612 | |
| 613 | if (likely((core == 0 && do_cpu) || (core > 0 && do_any_core_metric))) { |
| 614 | if (unlikely(core >= all_cpu_charts_size)) { |
| 615 | size_t old_cpu_charts_size = all_cpu_charts_size; |
| 616 | all_cpu_charts_size = core + 1; |
| 617 | all_cpu_charts = reallocz(all_cpu_charts, sizeof(struct cpu_chart) * all_cpu_charts_size); |
| 618 | memset(&all_cpu_charts[old_cpu_charts_size], 0, sizeof(struct cpu_chart) * (all_cpu_charts_size - old_cpu_charts_size)); |
| 619 | } |
| 620 | |
| 621 | struct cpu_chart *cpu_chart = &all_cpu_charts[core]; |
| 622 | |
| 623 | if (unlikely(!cpu_chart->id)) |
| 624 | cpu_chart->id = strdupz(row_key); |
| 625 | |
| 626 | if (core > 0 && !cpu_chart->per_core_files_found) { |
| 627 | cpu_chart->per_core_files_found = true; |
| 628 | |
| 629 | char filename[FILENAME_MAX + 1]; |
| 630 | struct stat stbuf; |
| 631 | |
| 632 | if (do_core_throttle_count != CONFIG_BOOLEAN_NO) { |
| 633 | snprintfz(filename, FILENAME_MAX, core_throttle_count_filename, cpu_chart->id); |
| 634 | if (stat(filename, &stbuf) == 0) { |
| 635 | cpu_chart->files[CORE_THROTTLE_COUNT_INDEX].filename = strdupz(filename); |
| 636 | cpu_chart->files[CORE_THROTTLE_COUNT_INDEX].fd = -1; |
| 637 | do_core_throttle_count = CONFIG_BOOLEAN_YES; |
| 638 | } |
| 639 | } |
| 640 | |
| 641 | if (do_package_throttle_count != CONFIG_BOOLEAN_NO) { |
| 642 | snprintfz(filename, FILENAME_MAX, package_throttle_count_filename, cpu_chart->id); |
| 643 | if (stat(filename, &stbuf) == 0) { |
| 644 | cpu_chart->files[PACKAGE_THROTTLE_COUNT_INDEX].filename = strdupz(filename); |
| 645 | cpu_chart->files[PACKAGE_THROTTLE_COUNT_INDEX].fd = -1; |
| 646 | do_package_throttle_count = CONFIG_BOOLEAN_YES; |
| 647 | } |
| 648 | } |
| 649 | |
| 650 | if (do_cpu_freq != CONFIG_BOOLEAN_NO) { |
| 651 | snprintfz(filename, FILENAME_MAX, scaling_cur_freq_filename, cpu_chart->id); |
| 652 | if (stat(filename, &stbuf) == 0) { |
| 653 | cpu_chart->files[CPU_FREQ_INDEX].filename = strdupz(filename); |
| 654 | cpu_chart->files[CPU_FREQ_INDEX].fd = -1; |
| 655 | do_cpu_freq = CONFIG_BOOLEAN_YES; |
| 656 | } |
| 657 | |
| 658 | snprintfz(filename, FILENAME_MAX, time_in_state_filename, cpu_chart->id); |
| 659 | if (stat(filename, &stbuf) == 0) { |
| 660 | cpu_chart->time_in_state_files.filename = strdupz(filename); |
| 661 | cpu_chart->time_in_state_files.ff = NULL; |
| 662 | do_cpu_freq = CONFIG_BOOLEAN_YES; |
| 663 | accurate_freq_avail = 1; |
| 664 | } |
| 665 | } |
| 666 | } |
| 667 | } |
| 668 | |
| 669 | if(likely((core == 0 && do_cpu) || (core > 0 && do_cpu_cores))) { |
| 670 | unsigned long long user = 0, nice = 0, system = 0, idle = 0, iowait = 0, irq = 0, softirq = 0, steal = 0, guest = 0, guest_nice = 0; |
| 671 | |
| 672 | user = str2ull(procfile_lineword(ff, l, 1), NULL); |
| 673 | nice = str2ull(procfile_lineword(ff, l, 2), NULL); |
| 674 | system = str2ull(procfile_lineword(ff, l, 3), NULL); |
| 675 | idle = str2ull(procfile_lineword(ff, l, 4), NULL); |
| 676 | iowait = str2ull(procfile_lineword(ff, l, 5), NULL); |
| 677 | irq = str2ull(procfile_lineword(ff, l, 6), NULL); |
| 678 | softirq = str2ull(procfile_lineword(ff, l, 7), NULL); |
| 679 | steal = str2ull(procfile_lineword(ff, l, 8), NULL); |
| 680 | |
| 681 | guest = str2ull(procfile_lineword(ff, l, 9), NULL); |
| 682 | user -= guest; |
| 683 | |
| 684 | guest_nice = str2ull(procfile_lineword(ff, l, 10), NULL); |
| 685 | nice -= guest_nice; |
| 686 | |
| 687 | char *title, *type, *context, *family; |
| 688 | long priority; |
| 689 | |
| 690 | struct cpu_chart *cpu_chart = &all_cpu_charts[core]; |
| 691 | |
| 692 | char *id = row_key; |
| 693 | |
| 694 | if(unlikely(!cpu_chart->st)) { |
| 695 | if(unlikely(core == 0)) { |
| 696 | title = "Total CPU utilization"; |
| 697 | type = "system"; |
| 698 | context = "system.cpu"; |
| 699 | family = id; |
| 700 | priority = NETDATA_CHART_PRIO_SYSTEM_CPU; |
| 701 | } |
| 702 | else { |
| 703 | title = "Core utilization"; |
| 704 | type = "cpu"; |
| 705 | context = "cpu.cpu"; |
| 706 | family = "utilization"; |
| 707 | priority = NETDATA_CHART_PRIO_CPU_PER_CORE; |
| 708 | } |
| 709 | |
| 710 | cpu_chart->st = rrdset_create_localhost( |
| 711 | type |
| 712 | , id |
| 713 | , NULL |
| 714 | , family |
| 715 | , context |
| 716 | , title |
| 717 | , "percentage" |
| 718 | , PLUGIN_PROC_NAME |
| 719 | , PLUGIN_PROC_MODULE_STAT_NAME |
| 720 | , priority + core |
| 721 | , update_every |
| 722 | , RRDSET_TYPE_STACKED |
| 723 | ); |
| 724 | |
| 725 | long multiplier = 1; |
| 726 | long divisor = 1; // sysconf(_SC_CLK_TCK); |
| 727 | |
| 728 | cpu_chart->rd_guest_nice = rrddim_add(cpu_chart->st, "guest_nice", NULL, multiplier, divisor, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL); |
| 729 | cpu_chart->rd_guest = rrddim_add(cpu_chart->st, "guest", NULL, multiplier, divisor, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL); |
| 730 | cpu_chart->rd_steal = rrddim_add(cpu_chart->st, "steal", NULL, multiplier, divisor, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL); |
| 731 | cpu_chart->rd_softirq = rrddim_add(cpu_chart->st, "softirq", NULL, multiplier, divisor, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL); |
| 732 | cpu_chart->rd_irq = rrddim_add(cpu_chart->st, "irq", NULL, multiplier, divisor, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL); |
| 733 | cpu_chart->rd_user = rrddim_add(cpu_chart->st, "user", NULL, multiplier, divisor, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL); |
| 734 | cpu_chart->rd_system = rrddim_add(cpu_chart->st, "system", NULL, multiplier, divisor, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL); |
| 735 | cpu_chart->rd_nice = rrddim_add(cpu_chart->st, "nice", NULL, multiplier, divisor, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL); |
| 736 | cpu_chart->rd_iowait = rrddim_add(cpu_chart->st, "iowait", NULL, multiplier, divisor, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL); |
| 737 | cpu_chart->rd_idle = rrddim_add(cpu_chart->st, "idle", NULL, multiplier, divisor, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL); |
| 738 | rrddim_hide(cpu_chart->st, "idle"); |
| 739 | |
| 740 | if (core > 0) { |
| 741 | char cpu_core[50 + 1]; |
| 742 | snprintfz(cpu_core, 50, "cpu%zu", core - 1); |
| 743 | rrdlabels_add(cpu_chart->st->rrdlabels, "cpu", cpu_core, RRDLABEL_SRC_AUTO); |
| 744 | } |
| 745 | |
| 746 | if(unlikely(core == 0 && cpus_var == NULL)) |
| 747 | cpus_var = rrdvar_host_variable_add_and_acquire(localhost, "active_processors"); |
| 748 | } |
| 749 | |
| 750 | rrddim_set_by_pointer(cpu_chart->st, cpu_chart->rd_user, user); |
| 751 | rrddim_set_by_pointer(cpu_chart->st, cpu_chart->rd_nice, nice); |
| 752 | rrddim_set_by_pointer(cpu_chart->st, cpu_chart->rd_system, system); |
| 753 | rrddim_set_by_pointer(cpu_chart->st, cpu_chart->rd_idle, idle); |
| 754 | rrddim_set_by_pointer(cpu_chart->st, cpu_chart->rd_iowait, iowait); |
| 755 | rrddim_set_by_pointer(cpu_chart->st, cpu_chart->rd_irq, irq); |
| 756 | rrddim_set_by_pointer(cpu_chart->st, cpu_chart->rd_softirq, softirq); |
| 757 | rrddim_set_by_pointer(cpu_chart->st, cpu_chart->rd_steal, steal); |
| 758 | rrddim_set_by_pointer(cpu_chart->st, cpu_chart->rd_guest, guest); |
| 759 | rrddim_set_by_pointer(cpu_chart->st, cpu_chart->rd_guest_nice, guest_nice); |
| 760 | rrdset_done(cpu_chart->st); |
| 761 | } |
| 762 | } |
| 763 | else if(unlikely(hash == hash_intr && strcmp(row_key, "intr") == 0)) { |
| 764 | if(likely(do_interrupts)) { |
| 765 | unsigned long long value = str2ull(procfile_lineword(ff, l, 1), NULL); |
| 766 | common_interrupts(value, update_every, NULL); |
| 767 | } |
| 768 | } |
| 769 | else if(unlikely(hash == hash_ctxt && strcmp(row_key, "ctxt") == 0)) { |
| 770 | if(likely(do_context)) { |
| 771 | unsigned long long value = str2ull(procfile_lineword(ff, l, 1), NULL); |
| 772 | common_system_context_switch(value, update_every); |
| 773 | } |
| 774 | } |
| 775 | else if(unlikely(hash == hash_processes && !processes && strcmp(row_key, "processes") == 0)) { |
| 776 | processes = str2ull(procfile_lineword(ff, l, 1), NULL); |
| 777 | } |
| 778 | else if(unlikely(hash == hash_procs_running && !running && strcmp(row_key, "procs_running") == 0)) { |
| 779 | running = str2ull(procfile_lineword(ff, l, 1), NULL); |
| 780 | } |
| 781 | else if(unlikely(hash == hash_procs_blocked && !blocked && strcmp(row_key, "procs_blocked") == 0)) { |
| 782 | blocked = str2ull(procfile_lineword(ff, l, 1), NULL); |
| 783 | } |
| 784 | } |
| 785 | |
| 786 | // -------------------------------------------------------------------- |
| 787 | |
| 788 | if(likely(do_forks)) { |
| 789 | static RRDSET *st_forks = NULL; |
| 790 | static RRDDIM *rd_started = NULL; |
| 791 | |
| 792 | if(unlikely(!st_forks)) { |
| 793 | st_forks = rrdset_create_localhost( |
| 794 | "system" |
| 795 | , "forks" |
| 796 | , NULL |
| 797 | , "processes" |
| 798 | , NULL |
| 799 | , "Started Processes" |
| 800 | , "processes/s" |
| 801 | , PLUGIN_PROC_NAME |
| 802 | , PLUGIN_PROC_MODULE_STAT_NAME |
| 803 | , NETDATA_CHART_PRIO_SYSTEM_FORKS |
| 804 | , update_every |
| 805 | , RRDSET_TYPE_LINE |
| 806 | ); |
| 807 | |
| 808 | rd_started = rrddim_add(st_forks, "started", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); |
| 809 | } |
| 810 | |
| 811 | rrddim_set_by_pointer(st_forks, rd_started, processes); |
| 812 | rrdset_done(st_forks); |
| 813 | } |
| 814 | |
| 815 | // -------------------------------------------------------------------- |
| 816 | |
| 817 | if(likely(do_processes)) { |
| 818 | common_system_processes(running, blocked, update_every); |
| 819 | } |
| 820 | |
| 821 | if(likely(all_cpu_charts_size > 1)) { |
| 822 | if(likely(do_core_throttle_count != CONFIG_BOOLEAN_NO)) { |
| 823 | int r = read_per_core_files(&all_cpu_charts[1], all_cpu_charts_size - 1, CORE_THROTTLE_COUNT_INDEX); |
| 824 | if(likely(r != -1 && (do_core_throttle_count == CONFIG_BOOLEAN_YES || r > 0))) { |
| 825 | do_core_throttle_count = CONFIG_BOOLEAN_YES; |
| 826 | |
| 827 | static RRDSET *st_core_throttle_count = NULL; |
| 828 | |
| 829 | if (unlikely(!st_core_throttle_count)) { |
| 830 | st_core_throttle_count = rrdset_create_localhost( |
| 831 | "cpu" |
| 832 | , "core_throttling" |
| 833 | , NULL |
| 834 | , "throttling" |
| 835 | , "cpu.core_throttling" |
| 836 | , "Core Thermal Throttling Events" |
| 837 | , "events/s" |
| 838 | , PLUGIN_PROC_NAME |
| 839 | , PLUGIN_PROC_MODULE_STAT_NAME |
| 840 | , NETDATA_CHART_PRIO_CORE_THROTTLING |
| 841 | , update_every |
| 842 | , RRDSET_TYPE_LINE |
| 843 | ); |
| 844 | } |
| 845 | |
| 846 | chart_per_core_files(&all_cpu_charts[1], all_cpu_charts_size - 1, CORE_THROTTLE_COUNT_INDEX, st_core_throttle_count, 1, 1, RRD_ALGORITHM_INCREMENTAL); |
| 847 | rrdset_done(st_core_throttle_count); |
| 848 | } |
| 849 | } |
| 850 | |
| 851 | if(likely(do_package_throttle_count != CONFIG_BOOLEAN_NO)) { |
| 852 | int r = read_per_core_files(&all_cpu_charts[1], all_cpu_charts_size - 1, PACKAGE_THROTTLE_COUNT_INDEX); |
| 853 | if(likely(r != -1 && (do_package_throttle_count == CONFIG_BOOLEAN_YES || r > 0))) { |
| 854 | do_package_throttle_count = CONFIG_BOOLEAN_YES; |
| 855 | |
| 856 | static RRDSET *st_package_throttle_count = NULL; |
| 857 | |
| 858 | if(unlikely(!st_package_throttle_count)) { |
| 859 | st_package_throttle_count = rrdset_create_localhost( |
| 860 | "cpu" |
| 861 | , "package_throttling" |
| 862 | , NULL |
| 863 | , "throttling" |
| 864 | , "cpu.package_throttling" |
| 865 | , "Package Thermal Throttling Events" |
| 866 | , "events/s" |
| 867 | , PLUGIN_PROC_NAME |
| 868 | , PLUGIN_PROC_MODULE_STAT_NAME |
| 869 | , NETDATA_CHART_PRIO_PACKAGE_THROTTLING |
| 870 | , update_every |
| 871 | , RRDSET_TYPE_LINE |
| 872 | ); |
| 873 | } |
| 874 | |
| 875 | chart_per_core_files(&all_cpu_charts[1], all_cpu_charts_size - 1, PACKAGE_THROTTLE_COUNT_INDEX, st_package_throttle_count, 1, 1, RRD_ALGORITHM_INCREMENTAL); |
| 876 | rrdset_done(st_package_throttle_count); |
| 877 | } |
| 878 | } |
| 879 | |
| 880 | if(likely(do_cpu_freq != CONFIG_BOOLEAN_NO)) { |
| 881 | char filename[FILENAME_MAX + 1]; |
| 882 | int r = 0; |
| 883 | |
| 884 | if (accurate_freq_avail) { |
| 885 | r = read_per_core_time_in_state_files(&all_cpu_charts[1], all_cpu_charts_size - 1, CPU_FREQ_INDEX); |
| 886 | if(r > 0 && !accurate_freq_is_used) { |
| 887 | accurate_freq_is_used = 1; |
| 888 | snprintfz(filename, FILENAME_MAX, time_in_state_filename, "cpu*"); |
| 889 | collector_info("cpufreq is using %s", filename); |
| 890 | } |
| 891 | } |
| 892 | if (r < 1) { |
| 893 | r = read_per_core_files(&all_cpu_charts[1], all_cpu_charts_size - 1, CPU_FREQ_INDEX); |
| 894 | if(accurate_freq_is_used) { |
| 895 | accurate_freq_is_used = 0; |
| 896 | snprintfz(filename, FILENAME_MAX, scaling_cur_freq_filename, "cpu*"); |
| 897 | collector_info("cpufreq fell back to %s", filename); |
| 898 | } |
| 899 | } |
| 900 | |
| 901 | if(likely(r != -1 && (do_cpu_freq == CONFIG_BOOLEAN_YES || r > 0))) { |
| 902 | do_cpu_freq = CONFIG_BOOLEAN_YES; |
| 903 | |
| 904 | static RRDSET *st_scaling_cur_freq = NULL; |
| 905 | |
| 906 | if(unlikely(!st_scaling_cur_freq)) { |
| 907 | st_scaling_cur_freq = rrdset_create_localhost( |
| 908 | "cpu" |
| 909 | , "cpufreq" |
| 910 | , NULL |
| 911 | , "cpufreq" |
| 912 | , "cpufreq.cpufreq" |
| 913 | , "Current CPU Frequency" |
| 914 | , "MHz" |
| 915 | , PLUGIN_PROC_NAME |
| 916 | , PLUGIN_PROC_MODULE_STAT_NAME |
| 917 | , NETDATA_CHART_PRIO_CPUFREQ_SCALING_CUR_FREQ |
| 918 | , update_every |
| 919 | , RRDSET_TYPE_LINE |
| 920 | ); |
| 921 | } |
| 922 | |
| 923 | chart_per_core_files(&all_cpu_charts[1], all_cpu_charts_size - 1, CPU_FREQ_INDEX, st_scaling_cur_freq, 1, 1000, RRD_ALGORITHM_ABSOLUTE); |
| 924 | rrdset_done(st_scaling_cur_freq); |
| 925 | } |
| 926 | } |
| 927 | } |
| 928 | |
| 929 | // -------------------------------------------------------------------- |
| 930 | |
| 931 | static struct per_core_cpuidle_chart *cpuidle_charts = NULL; |
| 932 | size_t schedstat_cores_found = 0; |
| 933 | |
| 934 | if(likely(do_cpuidle != CONFIG_BOOLEAN_NO && !read_schedstat(schedstat_filename, &cpuidle_charts, &schedstat_cores_found))) { |
| 935 | int cpu_states_updated = 0; |
| 936 | size_t core, state; |
| 937 | |
| 938 | |
| 939 | // proc.plugin runs on Linux systems only. Multi-platform compatibility is not needed here, |
| 940 | // so bare pthread functions are used to avoid unneeded overheads. |
| 941 | for(core = 0; core < schedstat_cores_found; core++) { |
| 942 | if(unlikely(!(cpuidle_charts[core].active_time - cpuidle_charts[core].last_active_time))) { |
| 943 | pthread_t thread; |
| 944 | cpu_set_t global_cpu_set; |
| 945 | |
| 946 | if (likely(!pthread_getaffinity_np(pthread_self(), sizeof(cpu_set_t), &global_cpu_set))) { |
| 947 | if (unlikely(!CPU_ISSET(core, &global_cpu_set))) { |
| 948 | continue; |
| 949 | } |
| 950 | } |
| 951 | else |
| 952 | collector_error("Cannot read current process affinity"); |
| 953 | |
| 954 | // These threads are very ephemeral and don't need to have a specific name |
| 955 | if(unlikely(pthread_create(&thread, NULL, wake_cpu_thread, (void *)&core))) |
| 956 | collector_error("Cannot create wake_cpu_thread"); |
| 957 | else if(unlikely(pthread_join(thread, NULL))) |
| 958 | collector_error("Cannot join wake_cpu_thread"); |
| 959 | cpu_states_updated = 1; |
| 960 | } |
| 961 | } |
| 962 | |
| 963 | if(unlikely(!cpu_states_updated || !read_schedstat(schedstat_filename, &cpuidle_charts, &schedstat_cores_found))) { |
| 964 | for(core = 0; core < schedstat_cores_found; core++) { |
| 965 | cpuidle_charts[core].last_active_time = cpuidle_charts[core].active_time; |
| 966 | |
| 967 | int r = read_cpuidle_states(cpuidle_name_filename, cpuidle_time_filename, cpuidle_charts, core); |
| 968 | if(likely(r != -1 && (do_cpuidle == CONFIG_BOOLEAN_YES || r > 0))) { |
| 969 | do_cpuidle = CONFIG_BOOLEAN_YES; |
| 970 | |
| 971 | char cpuidle_chart_id[RRD_ID_LENGTH_MAX + 1]; |
| 972 | snprintfz(cpuidle_chart_id, RRD_ID_LENGTH_MAX, "cpu%zu_cpuidle", core); |
| 973 | |
| 974 | if(unlikely(!cpuidle_charts[core].st)) { |
| 975 | cpuidle_charts[core].st = rrdset_create_localhost( |
| 976 | "cpu" |
| 977 | , cpuidle_chart_id |
| 978 | , NULL |
| 979 | , "cpuidle" |
| 980 | , "cpuidle.cpu_cstate_residency_time" |
| 981 | , "C-state residency time" |
| 982 | , "percentage" |
| 983 | , PLUGIN_PROC_NAME |
| 984 | , PLUGIN_PROC_MODULE_STAT_NAME |
| 985 | , NETDATA_CHART_PRIO_CPUIDLE + core |
| 986 | , update_every |
| 987 | , RRDSET_TYPE_STACKED |
| 988 | ); |
| 989 | |
| 990 | char corebuf[50+1]; |
| 991 | snprintfz(corebuf, sizeof(corebuf) - 1, "cpu%zu", core); |
| 992 | rrdlabels_add(cpuidle_charts[core].st->rrdlabels, "cpu", corebuf, RRDLABEL_SRC_AUTO); |
| 993 | |
| 994 | char cpuidle_dim_id[RRD_ID_LENGTH_MAX + 1]; |
| 995 | cpuidle_charts[core].active_time_rd = rrddim_add(cpuidle_charts[core].st, "active", "C0 (active)", 1, 1, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL); |
| 996 | for(state = 0; state < cpuidle_charts[core].cpuidle_state_len; state++) { |
| 997 | strncpyz(cpuidle_dim_id, cpuidle_charts[core].cpuidle_state[state].name, RRD_ID_LENGTH_MAX); |
| 998 | for(int i = 0; cpuidle_dim_id[i]; i++) |
| 999 | cpuidle_dim_id[i] = tolower(cpuidle_dim_id[i]); |
| 1000 | cpuidle_charts[core].cpuidle_state[state].rd = rrddim_add(cpuidle_charts[core].st, cpuidle_dim_id, |
| 1001 | cpuidle_charts[core].cpuidle_state[state].name, |
| 1002 | 1, 1, RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL); |
| 1003 | } |
| 1004 | } |
| 1005 | |
| 1006 | rrddim_set_by_pointer(cpuidle_charts[core].st, cpuidle_charts[core].active_time_rd, cpuidle_charts[core].active_time); |
| 1007 | for(state = 0; state < cpuidle_charts[core].cpuidle_state_len; state++) { |
| 1008 | rrddim_set_by_pointer(cpuidle_charts[core].st, cpuidle_charts[core].cpuidle_state[state].rd, cpuidle_charts[core].cpuidle_state[state].value); |
| 1009 | } |
| 1010 | rrdset_done(cpuidle_charts[core].st); |
| 1011 | } |
| 1012 | } |
| 1013 | } |
| 1014 | } |
| 1015 | |
| 1016 | if(cpus_var) |
| 1017 | rrdvar_host_variable_set(localhost, cpus_var, cores_found); |
| 1018 | |
| 1019 | return 0; |
| 1020 | } |