| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #include "ebpf.h" |
| 4 | #include "ebpf_softirq.h" |
| 5 | #include "libbpf_api/ebpf_library.h" |
| 6 | |
| 7 | struct config softirq_config = APPCONFIG_INITIALIZER; |
| 8 | |
| 9 | #define SOFTIRQ_MAP_LATENCY 0 |
| 10 | static ebpf_local_maps_t softirq_maps[] = { |
| 11 | {.name = "tbl_softirq", |
| 12 | .internal_input = NETDATA_SOFTIRQ_MAX_IRQS, |
| 13 | .user_input = 0, |
| 14 | .type = NETDATA_EBPF_MAP_STATIC, |
| 15 | .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, |
| 16 | #ifdef LIBBPF_MAJOR_VERSION |
| 17 | .map_type = BPF_MAP_TYPE_PERCPU_ARRAY |
| 18 | #endif |
| 19 | }, |
| 20 | {.name = NULL, .internal_input = 0, .user_input = 0}}; |
| 21 | |
| 22 | #define SOFTIRQ_TP_CLASS_IRQ "irq" |
| 23 | static ebpf_tracepoint_t softirq_tracepoints[] = { |
| 24 | {.enabled = false, .class = SOFTIRQ_TP_CLASS_IRQ, .event = "softirq_entry"}, |
| 25 | {.enabled = false, .class = SOFTIRQ_TP_CLASS_IRQ, .event = "softirq_exit"}, |
| 26 | /* end */ |
| 27 | {.enabled = false, .class = NULL, .event = NULL}}; |
| 28 | |
| 29 | // these must be in the order defined by the kernel: |
| 30 | // https://elixir.bootlin.com/linux/v5.12.19/source/include/trace/events/irq.h#L13 |
| 31 | static softirq_val_t softirq_vals[] = { |
| 32 | {.name = "HI", .latency = 0}, |
| 33 | {.name = "TIMER", .latency = 0}, |
| 34 | {.name = "NET_TX", .latency = 0}, |
| 35 | {.name = "NET_RX", .latency = 0}, |
| 36 | {.name = "BLOCK", .latency = 0}, |
| 37 | {.name = "IRQ_POLL", .latency = 0}, |
| 38 | {.name = "TASKLET", .latency = 0}, |
| 39 | {.name = "SCHED", .latency = 0}, |
| 40 | {.name = "HRTIMER", .latency = 0}, |
| 41 | {.name = "RCU", .latency = 0}, |
| 42 | }; |
| 43 | |
| 44 | // tmp store for soft IRQ values we get from a per-CPU eBPF map. |
| 45 | static softirq_ebpf_val_t *softirq_ebpf_vals = NULL; |
| 46 | static bool softirq_safe_clean = false; |
| 47 | |
| 48 | /** |
| 49 | * Obsolete global |
| 50 | * |
| 51 | * Obsolete global charts created by thread. |
| 52 | * |
| 53 | * @param em a pointer to `struct ebpf_module` |
| 54 | */ |
| 55 | static void ebpf_obsolete_softirq_global(ebpf_module_t *em) |
| 56 | { |
| 57 | ebpf_write_chart_obsolete( |
| 58 | NETDATA_EBPF_SYSTEM_GROUP, |
| 59 | "softirq_latency", |
| 60 | "", |
| 61 | "Software IRQ latency", |
| 62 | EBPF_COMMON_UNITS_MILLISECONDS, |
| 63 | "softirqs", |
| 64 | NETDATA_EBPF_CHART_TYPE_STACKED, |
| 65 | "system.softirq_latency", |
| 66 | NETDATA_CHART_PRIO_SYSTEM_SOFTIRQS + 1, |
| 67 | em->update_every); |
| 68 | } |
| 69 | |
| 70 | static void softirq_cleanup(void *pptr) |
| 71 | { |
| 72 | ebpf_module_t *em = CLEANUP_FUNCTION_GET_PTR(pptr); |
| 73 | if (!em) |
| 74 | return; |
| 75 | |
| 76 | if (!softirq_safe_clean) { |
| 77 | netdata_mutex_lock(&ebpf_exit_cleanup); |
| 78 | ebpf_module_enabled_set(em, NETDATA_THREAD_EBPF_STOPPED); |
| 79 | netdata_mutex_unlock(&ebpf_exit_cleanup); |
| 80 | return; |
| 81 | } |
| 82 | |
| 83 | if (ebpf_module_enabled_get(em) == NETDATA_THREAD_EBPF_FUNCTION_RUNNING && !ebpf_plugin_stop()) { |
| 84 | netdata_mutex_lock(&lock); |
| 85 | |
| 86 | ebpf_obsolete_softirq_global(em); |
| 87 | |
| 88 | netdata_mutex_unlock(&lock); |
| 89 | fflush(stdout); |
| 90 | } |
| 91 | |
| 92 | for (int i = 0; softirq_tracepoints[i].class != NULL; i++) { |
| 93 | ebpf_disable_tracepoint(&softirq_tracepoints[i]); |
| 94 | } |
| 95 | freez(softirq_ebpf_vals); |
| 96 | softirq_ebpf_vals = NULL; |
| 97 | |
| 98 | if (!ebpf_plugin_stop() && em->functions.bpf_unload) |
| 99 | em->functions.bpf_unload(em); |
| 100 | |
| 101 | netdata_mutex_lock(&ebpf_exit_cleanup); |
| 102 | ebpf_module_enabled_set(em, NETDATA_THREAD_EBPF_STOPPED); |
| 103 | netdata_mutex_unlock(&ebpf_exit_cleanup); |
| 104 | } |
| 105 | |
| 106 | /***************************************************************** |
| 107 | * MAIN LOOP |
| 108 | *****************************************************************/ |
| 109 | |
| 110 | /** |
| 111 | * Read Latency Map |
| 112 | * |
| 113 | * Read data from kernel ring to plot for users. |
| 114 | * |
| 115 | * @param maps_per_core do I need to read all cores? |
| 116 | */ |
| 117 | static void softirq_read_latency_map(int maps_per_core) |
| 118 | { |
| 119 | int fd = softirq_maps[SOFTIRQ_MAP_LATENCY].map_fd; |
| 120 | int i; |
| 121 | int end = (maps_per_core) ? ebpf_nprocs : 1; |
| 122 | |
| 123 | for (i = 0; i < NETDATA_SOFTIRQ_MAX_IRQS; i++) { |
| 124 | int ret = bpf_map_lookup_elem(fd, &i, softirq_ebpf_vals); |
| 125 | if (unlikely(ret < 0)) { |
| 126 | continue; |
| 127 | } |
| 128 | |
| 129 | uint64_t total_latency = 0; |
| 130 | int cpu_i; |
| 131 | for (cpu_i = 0; cpu_i < end; cpu_i++) { |
| 132 | total_latency += softirq_ebpf_vals[cpu_i].latency / 1000; |
| 133 | } |
| 134 | |
| 135 | softirq_vals[i].latency = total_latency; |
| 136 | memset(softirq_ebpf_vals, 0, end * sizeof(softirq_ebpf_val_t)); |
| 137 | } |
| 138 | } |
| 139 | |
| 140 | static void softirq_create_charts(int update_every) |
| 141 | { |
| 142 | ebpf_create_chart( |
| 143 | NETDATA_EBPF_SYSTEM_GROUP, |
| 144 | "softirq_latency", |
| 145 | "Software IRQ latency", |
| 146 | EBPF_COMMON_UNITS_MILLISECONDS, |
| 147 | "softirqs", |
| 148 | "system.softirq_latency", |
| 149 | NETDATA_EBPF_CHART_TYPE_STACKED, |
| 150 | NETDATA_CHART_PRIO_SYSTEM_SOFTIRQS + 1, |
| 151 | NULL, |
| 152 | NULL, |
| 153 | 0, |
| 154 | update_every, |
| 155 | NETDATA_EBPF_MODULE_NAME_SOFTIRQ); |
| 156 | |
| 157 | fflush(stdout); |
| 158 | } |
| 159 | |
| 160 | static void softirq_create_dims() |
| 161 | { |
| 162 | uint32_t i; |
| 163 | for (i = 0; i < NETDATA_SOFTIRQ_MAX_IRQS; i++) { |
| 164 | ebpf_write_global_dimension( |
| 165 | softirq_vals[i].name, softirq_vals[i].name, ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]); |
| 166 | } |
| 167 | } |
| 168 | |
| 169 | static inline void softirq_write_dims() |
| 170 | { |
| 171 | uint32_t i; |
| 172 | for (i = 0; i < NETDATA_SOFTIRQ_MAX_IRQS; i++) { |
| 173 | write_chart_dimension(softirq_vals[i].name, softirq_vals[i].latency); |
| 174 | } |
| 175 | } |
| 176 | |
| 177 | /** |
| 178 | * Main loop for this collector. |
| 179 | */ |
| 180 | static void softirq_collector(ebpf_module_t *em) |
| 181 | { |
| 182 | softirq_ebpf_vals = callocz(ebpf_nprocs, sizeof(softirq_ebpf_val_t)); |
| 183 | |
| 184 | // create chart and static dims. |
| 185 | netdata_mutex_lock(&lock); |
| 186 | softirq_create_charts(em->update_every); |
| 187 | softirq_create_dims(); |
| 188 | ebpf_update_stats(&plugin_statistics, em); |
| 189 | ebpf_update_kernel_memory_with_vector(&plugin_statistics, em->maps, EBPF_ACTION_STAT_ADD); |
| 190 | netdata_mutex_unlock(&lock); |
| 191 | |
| 192 | // loop and read from published data until ebpf plugin is closed. |
| 193 | heartbeat_t hb; |
| 194 | heartbeat_init(&hb, USEC_PER_SEC); |
| 195 | int update_every = em->update_every; |
| 196 | int counter = update_every - 1; |
| 197 | int maps_per_core = em->maps_per_core; |
| 198 | //This will be cancelled by its parent |
| 199 | uint32_t running_time = 0; |
| 200 | uint32_t lifetime = em->lifetime; |
| 201 | while (!ebpf_plugin_stop() && running_time < lifetime) { |
| 202 | if (ebpf_plugin_stop()) |
| 203 | break; |
| 204 | |
| 205 | heartbeat_next(&hb); |
| 206 | if (ebpf_plugin_stop()) |
| 207 | break; |
| 208 | |
| 209 | if (++counter != update_every) |
| 210 | continue; |
| 211 | |
| 212 | counter = 0; |
| 213 | softirq_read_latency_map(maps_per_core); |
| 214 | netdata_mutex_lock(&lock); |
| 215 | |
| 216 | // write dims now for all hitherto discovered IRQs. |
| 217 | ebpf_write_begin_chart(NETDATA_EBPF_SYSTEM_GROUP, "softirq_latency", ""); |
| 218 | softirq_write_dims(); |
| 219 | ebpf_write_end_chart(); |
| 220 | |
| 221 | netdata_mutex_unlock(&lock); |
| 222 | |
| 223 | if (ebpf_plugin_stop()) |
| 224 | break; |
| 225 | |
| 226 | netdata_mutex_lock(&ebpf_exit_cleanup); |
| 227 | running_time += update_every; |
| 228 | em->running_time = running_time; |
| 229 | netdata_mutex_unlock(&ebpf_exit_cleanup); |
| 230 | } |
| 231 | } |
| 232 | |
| 233 | /***************************************************************** |
| 234 | * EBPF SOFTIRQ THREAD |
| 235 | *****************************************************************/ |
| 236 | |
| 237 | /** |
| 238 | * Soft IRQ latency thread. |
| 239 | * |
| 240 | * @param ptr a `ebpf_module_t *`. |
| 241 | * @return always NULL. |
| 242 | */ |
| 243 | void ebpf_softirq_thread(void *ptr) |
| 244 | { |
| 245 | ebpf_module_t *em = ptr; |
| 246 | |
| 247 | CLEANUP_FUNCTION_REGISTER(softirq_cleanup) cleanup_ptr = em; |
| 248 | |
| 249 | if (!ebpf_module_thread_has_valid_state(em)) { |
| 250 | goto endsoftirq; |
| 251 | } |
| 252 | |
| 253 | em->maps = softirq_maps; |
| 254 | |
| 255 | if (ebpf_enable_tracepoints(softirq_tracepoints) == 0) { |
| 256 | goto endsoftirq; |
| 257 | } |
| 258 | |
| 259 | #ifdef LIBBPF_MAJOR_VERSION |
| 260 | ebpf_define_map_type(em->maps, em->maps_per_core, running_on_kernel); |
| 261 | #endif |
| 262 | em->probe_links = ebpf_load_program(ebpf_plugin_dir, em, running_on_kernel, isrh, &em->objects); |
| 263 | if (!em->probe_links) { |
| 264 | goto endsoftirq; |
| 265 | } |
| 266 | ebpf_mark_program_loaded(); |
| 267 | |
| 268 | softirq_safe_clean = true; |
| 269 | softirq_collector(em); |
| 270 | |
| 271 | endsoftirq: |
| 272 | ebpf_update_disabled_plugin_stats(em); |
| 273 | } |