| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #include "plugin_proc.h" |
| 4 | |
| 5 | #define PLUGIN_PROC_MODULE_PRESSURE_NAME "/proc/pressure" |
| 6 | #define CONFIG_SECTION_PLUGIN_PROC_PRESSURE "plugin:" PLUGIN_PROC_CONFIG_NAME ":" PLUGIN_PROC_MODULE_PRESSURE_NAME |
| 7 | |
| 8 | // linux calculates this every 2 seconds, see kernel/sched/psi.c PSI_FREQ |
| 9 | #define MIN_PRESSURE_UPDATE_EVERY 2 |
| 10 | |
| 11 | static int pressure_update_every = 0; |
| 12 | |
| 13 | static struct pressure resources[PRESSURE_NUM_RESOURCES] = { |
| 14 | { |
| 15 | .some = { |
| 16 | .available = true, |
| 17 | .share_time = {.id = "cpu_some_pressure", .title = "CPU some pressure"}, |
| 18 | .total_time = {.id = "cpu_some_pressure_stall_time", .title = "CPU some pressure stall time"} |
| 19 | }, |
| 20 | .full = { |
| 21 | // Disable CPU full pressure. |
| 22 | // See https://github.com/torvalds/linux/commit/890d550d7dbac7a31ecaa78732aa22be282bb6b8 |
| 23 | .available = false, |
| 24 | .share_time = {.id = "cpu_full_pressure", .title = "CPU full pressure"}, |
| 25 | .total_time = {.id = "cpu_full_pressure_stall_time", .title = "CPU full pressure stall time"} |
| 26 | }, |
| 27 | }, |
| 28 | { |
| 29 | .some = { |
| 30 | .available = true, |
| 31 | .share_time = {.id = "memory_some_pressure", .title = "Memory some pressure"}, |
| 32 | .total_time = {.id = "memory_some_pressure_stall_time", .title = "Memory some pressure stall time"} |
| 33 | }, |
| 34 | .full = { |
| 35 | .available = true, |
| 36 | .share_time = {.id = "memory_full_pressure", .title = "Memory full pressure"}, |
| 37 | .total_time = {.id = "memory_full_pressure_stall_time", .title = "Memory full pressure stall time"} |
| 38 | }, |
| 39 | }, |
| 40 | { |
| 41 | .some = { |
| 42 | .available = true, |
| 43 | .share_time = {.id = "io_some_pressure", .title = "I/O some pressure"}, |
| 44 | .total_time = {.id = "io_some_pressure_stall_time", .title = "I/O some pressure stall time"} |
| 45 | }, |
| 46 | .full = { |
| 47 | .available = true, |
| 48 | .share_time = {.id = "io_full_pressure", .title = "I/O full pressure"}, |
| 49 | .total_time = {.id = "io_full_pressure_stall_time", .title = "I/O full pressure stall time"} |
| 50 | }, |
| 51 | }, |
| 52 | { |
| 53 | .some = { |
| 54 | // this is not available |
| 55 | .available = false, |
| 56 | .share_time = {.id = "irq_some_pressure", .title = "IRQ some pressure"}, |
| 57 | .total_time = {.id = "irq_some_pressure_stall_time", .title = "IRQ some pressure stall time"} |
| 58 | }, |
| 59 | .full = { |
| 60 | .available = true, |
| 61 | .share_time = {.id = "irq_full_pressure", .title = "IRQ full pressure"}, |
| 62 | .total_time = {.id = "irq_full_pressure_stall_time", .title = "IRQ full pressure stall time"} |
| 63 | }, |
| 64 | }, |
| 65 | }; |
| 66 | |
| 67 | static struct resource_info { |
| 68 | procfile *pf; |
| 69 | const char *name; // metric file name |
| 70 | const char *family; // webui section name |
| 71 | int section_priority; |
| 72 | } resource_info[PRESSURE_NUM_RESOURCES] = { |
| 73 | { .name = "cpu", .family = "cpu", .section_priority = NETDATA_CHART_PRIO_SYSTEM_CPU }, |
| 74 | { .name = "memory", .family = "ram", .section_priority = NETDATA_CHART_PRIO_SYSTEM_RAM }, |
| 75 | { .name = "io", .family = "disk", .section_priority = NETDATA_CHART_PRIO_SYSTEM_IO }, |
| 76 | { .name = "irq", .family = "interrupts", .section_priority = NETDATA_CHART_PRIO_SYSTEM_INTERRUPTS }, |
| 77 | }; |
| 78 | |
| 79 | void update_pressure_charts(struct pressure_charts *pcs) { |
| 80 | if (pcs->share_time.st) { |
| 81 | rrddim_set_by_pointer( |
| 82 | pcs->share_time.st, pcs->share_time.rd10, (collected_number)(pcs->share_time.value10 * 100)); |
| 83 | rrddim_set_by_pointer( |
| 84 | pcs->share_time.st, pcs->share_time.rd60, (collected_number)(pcs->share_time.value60 * 100)); |
| 85 | rrddim_set_by_pointer( |
| 86 | pcs->share_time.st, pcs->share_time.rd300, (collected_number)(pcs->share_time.value300 * 100)); |
| 87 | rrdset_done(pcs->share_time.st); |
| 88 | } |
| 89 | if (pcs->total_time.st) { |
| 90 | rrddim_set_by_pointer( |
| 91 | pcs->total_time.st, pcs->total_time.rdtotal, (collected_number)(pcs->total_time.value_total)); |
| 92 | rrdset_done(pcs->total_time.st); |
| 93 | } |
| 94 | } |
| 95 | |
| 96 | static void proc_pressure_do_resource(procfile *ff, int res_idx, size_t line, bool some) { |
| 97 | struct pressure_charts *pcs; |
| 98 | struct resource_info ri; |
| 99 | pcs = some ? &resources[res_idx].some : &resources[res_idx].full; |
| 100 | ri = resource_info[res_idx]; |
| 101 | |
| 102 | if (unlikely(!pcs->share_time.st)) { |
| 103 | pcs->share_time.st = rrdset_create_localhost( |
| 104 | "system", |
| 105 | pcs->share_time.id, |
| 106 | NULL, |
| 107 | ri.family, |
| 108 | NULL, |
| 109 | pcs->share_time.title, |
| 110 | "percentage", |
| 111 | PLUGIN_PROC_NAME, |
| 112 | PLUGIN_PROC_MODULE_PRESSURE_NAME, |
| 113 | ri.section_priority + (some ? 40 : 50), |
| 114 | pressure_update_every, |
| 115 | RRDSET_TYPE_LINE); |
| 116 | pcs->share_time.rd10 = |
| 117 | rrddim_add(pcs->share_time.st, some ? "some 10" : "full 10", NULL, 1, 100, RRD_ALGORITHM_ABSOLUTE); |
| 118 | pcs->share_time.rd60 = |
| 119 | rrddim_add(pcs->share_time.st, some ? "some 60" : "full 60", NULL, 1, 100, RRD_ALGORITHM_ABSOLUTE); |
| 120 | pcs->share_time.rd300 = |
| 121 | rrddim_add(pcs->share_time.st, some ? "some 300" : "full 300", NULL, 1, 100, RRD_ALGORITHM_ABSOLUTE); |
| 122 | } |
| 123 | |
| 124 | pcs->share_time.value10 = strtod(procfile_lineword(ff, line, 2), NULL); |
| 125 | pcs->share_time.value60 = strtod(procfile_lineword(ff, line, 4), NULL); |
| 126 | pcs->share_time.value300 = strtod(procfile_lineword(ff, line, 6), NULL); |
| 127 | |
| 128 | if (unlikely(!pcs->total_time.st)) { |
| 129 | pcs->total_time.st = rrdset_create_localhost( |
| 130 | "system", |
| 131 | pcs->total_time.id, |
| 132 | NULL, |
| 133 | ri.family, |
| 134 | NULL, |
| 135 | pcs->total_time.title, |
| 136 | "ms", |
| 137 | PLUGIN_PROC_NAME, |
| 138 | PLUGIN_PROC_MODULE_PRESSURE_NAME, |
| 139 | ri.section_priority + (some ? 45 : 55), |
| 140 | pressure_update_every, |
| 141 | RRDSET_TYPE_LINE); |
| 142 | pcs->total_time.rdtotal = rrddim_add(pcs->total_time.st, "time", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); |
| 143 | } |
| 144 | |
| 145 | pcs->total_time.value_total = str2ull(procfile_lineword(ff, line, 8), NULL) / 1000; |
| 146 | } |
| 147 | |
| 148 | static void proc_pressure_do_resource_some(procfile *ff, int res_idx, size_t line) { |
| 149 | proc_pressure_do_resource(ff, res_idx, line, true); |
| 150 | } |
| 151 | |
| 152 | static void proc_pressure_do_resource_full(procfile *ff, int res_idx, size_t line) { |
| 153 | proc_pressure_do_resource(ff, res_idx, line, false); |
| 154 | } |
| 155 | |
| 156 | int do_proc_pressure(int update_every, usec_t dt) { |
| 157 | int ok_count = 0; |
| 158 | int i; |
| 159 | |
| 160 | static usec_t next_pressure_dt = 0; |
| 161 | static const char *base_path = NULL; |
| 162 | |
| 163 | update_every = (update_every < MIN_PRESSURE_UPDATE_EVERY) ? MIN_PRESSURE_UPDATE_EVERY : update_every; |
| 164 | pressure_update_every = update_every; |
| 165 | |
| 166 | if (next_pressure_dt <= dt) { |
| 167 | next_pressure_dt = update_every * USEC_PER_SEC; |
| 168 | } else { |
| 169 | next_pressure_dt -= dt; |
| 170 | return 0; |
| 171 | } |
| 172 | |
| 173 | if (unlikely(!base_path)) |
| 174 | base_path = inicfg_get(&netdata_config, CONFIG_SECTION_PLUGIN_PROC_PRESSURE, "base path of pressure metrics", "/proc/pressure"); |
| 175 | |
| 176 | for (i = 0; i < PRESSURE_NUM_RESOURCES; i++) { |
| 177 | procfile *ff = resource_info[i].pf; |
| 178 | int do_some = resources[i].some.enabled, do_full = resources[i].full.enabled; |
| 179 | |
| 180 | if (!resources[i].some.available && !resources[i].full.available) |
| 181 | continue; |
| 182 | |
| 183 | if (unlikely(!ff)) { |
| 184 | char filename[FILENAME_MAX + 1]; |
| 185 | char config_key[CONFIG_MAX_NAME + 1]; |
| 186 | |
| 187 | snprintfz(filename |
| 188 | , FILENAME_MAX |
| 189 | , "%s%s/%s" |
| 190 | , netdata_configured_host_prefix |
| 191 | , base_path |
| 192 | , resource_info[i].name); |
| 193 | |
| 194 | do_some = resources[i].some.available ? CONFIG_BOOLEAN_YES : CONFIG_BOOLEAN_NO; |
| 195 | do_full = resources[i].full.available ? CONFIG_BOOLEAN_YES : CONFIG_BOOLEAN_NO; |
| 196 | |
| 197 | snprintfz(config_key, CONFIG_MAX_NAME, "enable %s some pressure", resource_info[i].name); |
| 198 | do_some = inicfg_get_boolean(&netdata_config, CONFIG_SECTION_PLUGIN_PROC_PRESSURE, config_key, do_some); |
| 199 | resources[i].some.enabled = do_some; |
| 200 | |
| 201 | snprintfz(config_key, CONFIG_MAX_NAME, "enable %s full pressure", resource_info[i].name); |
| 202 | do_full = inicfg_get_boolean(&netdata_config, CONFIG_SECTION_PLUGIN_PROC_PRESSURE, config_key, do_full); |
| 203 | resources[i].full.enabled = do_full; |
| 204 | |
| 205 | if (!do_full && !do_some) { |
| 206 | resources[i].some.available = false; |
| 207 | resources[i].full.available = false; |
| 208 | continue; |
| 209 | } |
| 210 | |
| 211 | ff = procfile_open(filename, " =", PROCFILE_FLAG_NO_ERROR_ON_FILE_IO); |
| 212 | if (unlikely(!ff)) { |
| 213 | // PSI IRQ was added recently (https://github.com/torvalds/linux/commit/52b1364ba0b105122d6de0e719b36db705011ac1) |
| 214 | if (strcmp(resource_info[i].name, "irq") != 0) |
| 215 | collector_error("Cannot read pressure information from %s.", filename); |
| 216 | resources[i].some.available = false; |
| 217 | resources[i].full.available = false; |
| 218 | continue; |
| 219 | } |
| 220 | } |
| 221 | |
| 222 | ff = procfile_readall(ff); |
| 223 | resource_info[i].pf = ff; |
| 224 | if (unlikely(!ff)) |
| 225 | continue; |
| 226 | |
| 227 | size_t lines = procfile_lines(ff); |
| 228 | if (unlikely(lines < 1)) { |
| 229 | collector_error("%s has no lines.", procfile_filename(ff)); |
| 230 | continue; |
| 231 | } |
| 232 | |
| 233 | for(size_t l = 0; l < lines ;l++) { |
| 234 | const char *key = procfile_lineword(ff, l, 0); |
| 235 | if(strcmp(key, "some") == 0) { |
| 236 | if(do_some) { |
| 237 | proc_pressure_do_resource_some(ff, i, l); |
| 238 | update_pressure_charts(&resources[i].some); |
| 239 | ok_count++; |
| 240 | } |
| 241 | } |
| 242 | else if(strcmp(key, "full") == 0) { |
| 243 | if(do_full) { |
| 244 | proc_pressure_do_resource_full(ff, i, l); |
| 245 | update_pressure_charts(&resources[i].full); |
| 246 | ok_count++; |
| 247 | } |
| 248 | } |
| 249 | } |
| 250 | } |
| 251 | |
| 252 | if(!ok_count) |
| 253 | return 1; |
| 254 | |
| 255 | return 0; |
| 256 | } |