@cryptotaxi247 / netdata-1 / commits / bc3d8c990

eBPF Hard IRQ latency (#11410)

Uman Shahzad committed Aug 18, 2021 at 18:46 UTC bc3d8c99066fd80b8f99f10cba8b87ed7404c025
15 files changed +722 -9
CMakeLists.txt
+2
@@ -494,6 +494,8 @@ set(EBPF_PROCESS_PLUGIN_FILES
494 collectors/ebpf.plugin/ebpf_disk.h
495 collectors/ebpf.plugin/ebpf_fd.c
496 collectors/ebpf.plugin/ebpf_fd.h
497 + collectors/ebpf.plugin/ebpf_hardirq.c
498 + collectors/ebpf.plugin/ebpf_hardirq.h
499 collectors/ebpf.plugin/ebpf_mount.c
500 collectors/ebpf.plugin/ebpf_mount.h
501 collectors/ebpf.plugin/ebpf_filesystem.c
Makefile.am
+2
@@ -300,6 +300,8 @@ EBPF_PLUGIN_FILES = \
300 collectors/ebpf.plugin/ebpf_fd.h \
301 collectors/ebpf.plugin/ebpf_filesystem.c \
302 collectors/ebpf.plugin/ebpf_filesystem.h \
303 + collectors/ebpf.plugin/ebpf_hardirq.c \
304 + collectors/ebpf.plugin/ebpf_hardirq.h \
305 collectors/ebpf.plugin/ebpf_mount.c \
306 collectors/ebpf.plugin/ebpf_mount.h \
307 collectors/ebpf.plugin/ebpf_process.c \
collectors/all.h
+3
@@ -214,6 +214,9 @@
214
215 #define NETDATA_CHART_PRIO_ZFS_POOL_STATE 2820
216
217 +// HARDIRQS
218 +
219 +#define NETDATA_CHART_PRIO_HARDIRQ_LATENCY 2900
220
221 // SOFTIRQs
222
collectors/ebpf.plugin/Makefile.am
+1
@@ -37,6 +37,7 @@ dist_ebpfconfig_DATA = \
37 ebpf.d/disk.conf \
38 ebpf.d/fd.conf \
39 ebpf.d/filesystem.conf \
40 + ebpf.d/hardirq.conf \
41 ebpf.d/mount.conf \
42 ebpf.d/network.conf \
43 ebpf.d/process.conf \
collectors/ebpf.plugin/README.md
+3
@@ -218,6 +218,8 @@ The eBPF collector enables and runs the following eBPF programs by default:
218 - `vfs`: This eBPF program creates charts that show information about VFS (Virtual File System) functions.
219 - `process`: This eBPF program creates charts that show information about process life.
220 When in `return` mode, it also creates charts showing errors when these operations are executed.
221 +- `hardirq`: This eBPF program creates charts that show information about
222 + time spent servicing individual hardware interrupt requests (hard IRQs).
223
224 You can also enable the following eBPF programs:
225 - `cachestat`: Netdata's eBPF data collector creates charts about the memory page cache. When the integration with
@@ -249,6 +251,7 @@ The following configuration files are available:
251 - `disk.conf`: Configuration for the `disk` thread.
252 - `fd.conf`: Configuration for the `file descriptor` thread.
253 - `filesystem.conf`: Configuration for the `filesystem` thread.
254 +- `hardirq.conf`: Configuration for the `hardirq` thread.
255 - `process.conf`: Configuration for the `process` thread.
256 - `network.conf`: Configuration for the `network viewer` thread. This config file overwrites the global options and
257 also lets you specify which network the eBPF collector monitors.
collectors/ebpf.plugin/ebpf.c
+111 -4
@@ -130,6 +130,11 @@ ebpf_module_t ebpf_modules[] = {
130 .optional = 0, .apps_routine = ebpf_fd_create_apps_charts, .maps = NULL,
131 .pid_map_size = ND_EBPF_DEFAULT_PID_SIZE, .names = NULL, .cfg = &fd_config,
132 .config_file = NETDATA_FD_CONFIG_FILE},
133 + { .thread_name = "hardirq", .config_name = "hardirq", .enabled = 0, .start_routine = ebpf_hardirq_thread,
134 + .update_time = 1, .global_charts = 1, .apps_charts = CONFIG_BOOLEAN_NO, .mode = MODE_ENTRY,
135 + .optional = 0, .apps_routine = NULL, .maps = NULL,
136 + .pid_map_size = ND_EBPF_DEFAULT_PID_SIZE, .names = NULL, .cfg = &hardirq_config,
137 + .config_file = NETDATA_HARDIRQ_CONFIG_FILE},
138 { .thread_name = NULL, .enabled = 0, .start_routine = NULL, .update_time = 1,
139 .global_charts = 0, .apps_charts = CONFIG_BOOLEAN_NO, .mode = MODE_ENTRY,
140 .optional = 0, .apps_routine = NULL, .maps = NULL, .pid_map_size = 0, .names = NULL,
@@ -296,8 +301,7 @@ inline void write_end_chart()
301 */
302 void write_chart_dimension(char *dim, long long value)
303 {
299 - int ret = printf("SET %s = %lld\n", dim, value);
300 - UNUSED(ret);
304 + printf("SET %s = %lld\n", dim, value);
305 }
306
307 /**
@@ -507,7 +511,9 @@ void ebpf_create_chart(char *type,
511 {
512 ebpf_write_chart_cmd(type, id, title, units, family, charttype, context, order, module);
513
510 - ncd(move, end);
514 + if (ncd) {
515 + ncd(move, end);
516 + }
517 }
518
519 /**
@@ -713,6 +719,8 @@ void ebpf_print_help()
719 "\n"
720 " --filesystem or -i Enable chart related to filesystem run time.\n"
721 "\n"
722 + " --hardirq or -q Enable chart related to hard IRQ latency.\n"
723 + "\n"
724 " --mount or -m Enable charts related to mount monitoring.\n"
725 "\n"
726 " --net or -n Enable network viewer charts.\n"
@@ -731,6 +739,87 @@ void ebpf_print_help()
739 (year >= 116) ? year + 1900 : 2020);
740 }
741
742 +/*****************************************************************
743 + *
744 + * TRACEPOINT MANAGEMENT FUNCTIONS
745 + *
746 + *****************************************************************/
747 +
748 +/**
749 + * Enable a tracepoint.
750 + *
751 + * @return 0 on success, -1 on error.
752 + */
753 +int ebpf_enable_tracepoint(ebpf_tracepoint_t *tp)
754 +{
755 + int test = ebpf_is_tracepoint_enabled(tp->class, tp->event);
756 +
757 + // err?
758 + if (test == -1) {
759 + return -1;
760 + }
761 + // disabled?
762 + else if (test == 0) {
763 + // enable it then.
764 + if (ebpf_enable_tracing_values(tp->class, tp->event)) {
765 + return -1;
766 + }
767 + }
768 +
769 + // enabled now or already was.
770 + tp->enabled = true;
771 +
772 + return 0;
773 +}
774 +
775 +/**
776 + * Disable a tracepoint if it's enabled.
777 + *
778 + * @return 0 on success, -1 on error.
779 + */
780 +int ebpf_disable_tracepoint(ebpf_tracepoint_t *tp)
781 +{
782 + int test = ebpf_is_tracepoint_enabled(tp->class, tp->event);
783 +
784 + // err?
785 + if (test == -1) {
786 + return -1;
787 + }
788 + // enabled?
789 + else if (test == 1) {
790 + // disable it then.
791 + if (ebpf_disable_tracing_values(tp->class, tp->event)) {
792 + return -1;
793 + }
794 + }
795 +
796 + // disable now or already was.
797 + tp->enabled = false;
798 +
799 + return 0;
800 +}
801 +
802 +/**
803 + * Enable multiple tracepoints on a list of tracepoints which end when the
804 + * class is NULL.
805 + *
806 + * @return the number of successful enables.
807 + */
808 +uint32_t ebpf_enable_tracepoints(ebpf_tracepoint_t *tps)
809 +{
810 + uint32_t cnt = 0;
811 + for (int i = 0; tps[i].class != NULL; i++) {
812 + if (ebpf_enable_tracepoint(&tps[i]) == -1) {
813 + infoerr("failed to enable tracepoint %s:%s",
814 + tps[i].class, tps[i].event);
815 + }
816 + else {
817 + cnt += 1;
818 + }
819 + }
820 + return cnt;
821 +}
822 +
823 /*****************************************************************
824 *
825 * AUXILIAR FUNCTIONS USED DURING INITIALIZATION
@@ -1058,6 +1147,13 @@ static void read_collector_values(int *disable_apps)
1147 started++;
1148 }
1149
1150 + enabled = appconfig_get_boolean(&collector_config, EBPF_PROGRAMS_SECTION, "hardirq",
1151 + CONFIG_BOOLEAN_YES);
1152 + if (enabled) {
1153 + ebpf_enable_chart(EBPF_MODULE_HARDIRQ_IDX, *disable_apps);
1154 + started++;
1155 + }
1156 +
1157 if (!started){
1158 ebpf_enable_all_charts(*disable_apps);
1159 // Read network viewer section
@@ -1145,6 +1241,7 @@ static void parse_args(int argc, char **argv)
1241 {"disk", no_argument, 0, 'k' },
1242 {"filesystem", no_argument, 0, 'i' },
1243 {"filedescriptor", no_argument, 0, 'e' },
1244 + {"hardirq", no_argument, 0, 'q' },
1245 {"mount", no_argument, 0, 'm' },
1246 {"net", no_argument, 0, 'n' },
1247 {"process", no_argument, 0, 'p' },
@@ -1166,7 +1263,7 @@ static void parse_args(int argc, char **argv)
1263 }
1264
1265 while (1) {
1169 - int c = getopt_long(argc, argv, "hvgacdnprsw", long_options, &option_index);
1266 + int c = getopt_long(argc, argv, "hvgacdkieqmnprswf", long_options, &option_index);
1267 if (c == -1)
1268 break;
1269
@@ -1218,6 +1315,14 @@ static void parse_args(int argc, char **argv)
1315 ebpf_enable_chart(EBPF_MODULE_FILESYSTEM_IDX, disable_apps);
1316 #ifdef NETDATA_INTERNAL_CHECKS
1317 info("EBPF enabling \"filesystem\" chart, because it was started with the option \"--filesystem\" or \"-i\".");
1318 +#endif
1319 + break;
1320 + }
1321 + case 'q': {
1322 + enabled = 1;
1323 + ebpf_enable_chart(EBPF_MODULE_HARDIRQ_IDX, disable_apps);
1324 +#ifdef NETDATA_INTERNAL_CHECKS
1325 + info("EBPF enabling \"hardirq\" chart, because it was started with the option \"--hardirq\" or \"-q\".");
1326 #endif
1327 break;
1328 }
@@ -1573,6 +1678,8 @@ int main(int argc, char **argv)
1678 NULL, NULL, ebpf_modules[EBPF_MODULE_MOUNT_IDX].start_routine},
1679 {"EBPF FD" , NULL, NULL, 1,
1680 NULL, NULL, ebpf_modules[EBPF_MODULE_FD_IDX].start_routine},
1681 + {"EBPF HARDIRQ" , NULL, NULL, 1,
1682 + NULL, NULL, ebpf_modules[EBPF_MODULE_HARDIRQ_IDX].start_routine},
1683 {NULL , NULL, NULL, 0,
1684 NULL, NULL, NULL}
1685 };
collectors/ebpf.plugin/ebpf.d.conf
+2
@@ -31,6 +31,7 @@
31 # `fd` : This eBPF program creates charts that show information about file manipulation.
32 # `mount` : Monitor calls for syscalls mount and umount
33 # `filesystem`: Monitor calls for functions used to manipulate specific filesystems
34 +# `hardirq` : Monitor latency of serving hardware interrupt requests (hard IRQs).
35 # `process` : This eBPF program creates charts that show information about process life.
36 # `socket` : This eBPF program creates charts with information about `TCP` and `UDP` functions, including the
37 # bandwidth consumed by each.
@@ -44,6 +45,7 @@
45 disk = no
46 fd = yes
47 filesystem = no
48 + hardirq = yes
49 mount = yes
50 process = yes
51 socket = yes
collectors/ebpf.plugin/ebpf.d/hardirq.conf new
+8
@@ -0,0 +1,8 @@
1 +# The `ebpf load mode` option accepts the following values :
2 +# `entry` : The eBPF collector only monitors calls for the functions, and does not show charts related to errors.
3 +# `return : In the `return` mode, the eBPF collector monitors the same kernel functions as `entry`, but also creates
4 +# new charts for the return of these functions, such as errors.
5 +#
6 +[global]
7 + ebpf load mode = entry
8 + update every = 1
collectors/ebpf.plugin/ebpf.h
+12 -1
@@ -84,9 +84,16 @@ enum ebpf_module_indexes {
84 EBPF_MODULE_FILESYSTEM_IDX,
85 EBPF_MODULE_DISK_IDX,
86 EBPF_MODULE_MOUNT_IDX,
87 - EBPF_MODULE_FD_IDX
87 + EBPF_MODULE_FD_IDX,
88 + EBPF_MODULE_HARDIRQ_IDX
89 };
90
91 +typedef struct ebpf_tracepoint {
92 + bool enabled;
93 + char *class;
94 + char *event;
95 +} ebpf_tracepoint_t;
96 +
97 // Copied from musl header
98 #ifndef offsetof
99 #if __GNUC__ > 3
@@ -202,6 +209,10 @@ extern void write_end_chart();
209
210 extern void ebpf_cleanup_publish_syscall(netdata_publish_syscall_t *nps);
211
212 +extern int ebpf_enable_tracepoint(ebpf_tracepoint_t *tp);
213 +extern int ebpf_disable_tracepoint(ebpf_tracepoint_t *tp);
214 +extern uint32_t ebpf_enable_tracepoints(ebpf_tracepoint_t *tps);
215 +
216 #define EBPF_PROGRAMS_SECTION "ebpf programs"
217
218 #define EBPF_COMMON_DIMENSION_PERCENTAGE "%"
collectors/ebpf.plugin/ebpf_apps.h
+1
@@ -28,6 +28,7 @@
28 #include "ebpf_sync.h"
29 #include "ebpf_swap.h"
30 #include "ebpf_vfs.h"
31 +#include "ebpf_hardirq.h"
32
33 #define MAX_COMPARE_NAME 100
34 #define MAX_NAME 100
collectors/ebpf.plugin/ebpf_hardirq.c new
+495
@@ -0,0 +1,495 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "ebpf.h"
4 +#include "ebpf_hardirq.h"
5 +
6 +struct config hardirq_config = { .first_section = NULL,
7 + .last_section = NULL,
8 + .mutex = NETDATA_MUTEX_INITIALIZER,
9 + .index = { .avl_tree = { .root = NULL, .compar = appconfig_section_compare },
10 + .rwlock = AVL_LOCK_INITIALIZER } };
11 +
12 +#define HARDIRQ_MAP_LATENCY 0
13 +#define HARDIRQ_MAP_LATENCY_STATIC 1
14 +static ebpf_local_maps_t hardirq_maps[] = {
15 + {
16 + .name = "tbl_hardirq",
17 + .internal_input = NETDATA_HARDIRQ_MAX_IRQS,
18 + .user_input = 0,
19 + .type = NETDATA_EBPF_MAP_STATIC,
20 + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED
21 + },
22 + {
23 + .name = "tbl_hardirq_static",
24 + .internal_input = HARDIRQ_EBPF_STATIC_END,
25 + .user_input = 0,
26 + .type = NETDATA_EBPF_MAP_STATIC,
27 + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED
28 + },
29 + /* end */
30 + {
31 + .name = NULL,
32 + .internal_input = 0,
33 + .user_input = 0,
34 + .type = NETDATA_EBPF_MAP_CONTROLLER,
35 + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED
36 + }
37 +};
38 +
39 +static ebpf_data_t hardirq_data;
40 +
41 +#define HARDIRQ_TP_CLASS_IRQ "irq"
42 +#define HARDIRQ_TP_CLASS_IRQ_VECTORS "irq_vectors"
43 +static ebpf_tracepoint_t hardirq_tracepoints[] = {
44 + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ, .event = "irq_handler_entry"},
45 + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ, .event = "irq_handler_exit"},
46 + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ_VECTORS, .event = "thermal_apic_entry"},
47 + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ_VECTORS, .event = "thermal_apic_exit"},
48 + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ_VECTORS, .event = "threshold_apic_entry"},
49 + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ_VECTORS, .event = "threshold_apic_exit"},
50 + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ_VECTORS, .event = "error_apic_entry"},
51 + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ_VECTORS, .event = "error_apic_exit"},
52 + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ_VECTORS, .event = "deferred_error_apic_entry"},
53 + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ_VECTORS, .event = "deferred_error_apic_exit"},
54 + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ_VECTORS, .event = "spurious_apic_entry"},
55 + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ_VECTORS, .event = "spurious_apic_exit"},
56 + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ_VECTORS, .event = "call_function_entry"},
57 + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ_VECTORS, .event = "call_function_exit"},
58 + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ_VECTORS, .event = "call_function_single_entry"},
59 + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ_VECTORS, .event = "call_function_single_exit"},
60 + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ_VECTORS, .event = "reschedule_entry"},
61 + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ_VECTORS, .event = "reschedule_exit"},
62 + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ_VECTORS, .event = "local_timer_entry"},
63 + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ_VECTORS, .event = "local_timer_exit"},
64 + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ_VECTORS, .event = "irq_work_entry"},
65 + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ_VECTORS, .event = "irq_work_exit"},
66 + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ_VECTORS, .event = "x86_platform_ipi_entry"},
67 + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ_VECTORS, .event = "x86_platform_ipi_exit"},
68 + /* end */
69 + {.enabled = false, .class = NULL, .event = NULL}
70 +};
71 +
72 +static hardirq_static_val_t hardirq_static_vals[] = {
73 + {
74 + .idx = HARDIRQ_EBPF_STATIC_APIC_THERMAL,
75 + .name = "apic_thermal",
76 + .latency = 0
77 + },
78 + {
79 + .idx = HARDIRQ_EBPF_STATIC_APIC_THRESHOLD,
80 + .name = "apic_threshold",
81 + .latency = 0
82 + },
83 + {
84 + .idx = HARDIRQ_EBPF_STATIC_APIC_ERROR,
85 + .name = "apic_error",
86 + .latency = 0
87 + },
88 + {
89 + .idx = HARDIRQ_EBPF_STATIC_APIC_DEFERRED_ERROR,
90 + .name = "apic_deferred_error",
91 + .latency = 0
92 + },
93 + {
94 + .idx = HARDIRQ_EBPF_STATIC_APIC_SPURIOUS,
95 + .name = "apic_spurious",
96 + .latency = 0
97 + },
98 + {
99 + .idx = HARDIRQ_EBPF_STATIC_FUNC_CALL,
100 + .name = "func_call",
101 + .latency = 0
102 + },
103 + {
104 + .idx = HARDIRQ_EBPF_STATIC_FUNC_CALL_SINGLE,
105 + .name = "func_call_single",
106 + .latency = 0
107 + },
108 + {
109 + .idx = HARDIRQ_EBPF_STATIC_RESCHEDULE,
110 + .name = "reschedule",
111 + .latency = 0
112 + },
113 + {
114 + .idx = HARDIRQ_EBPF_STATIC_LOCAL_TIMER,
115 + .name = "local_timer",
116 + .latency = 0
117 + },
118 + {
119 + .idx = HARDIRQ_EBPF_STATIC_IRQ_WORK,
120 + .name = "irq_work",
121 + .latency = 0
122 + },
123 + {
124 + .idx = HARDIRQ_EBPF_STATIC_X86_PLATFORM_IPI,
125 + .name = "x86_platform_ipi",
126 + .latency = 0
127 + },
128 +};
129 +
130 +static struct bpf_link **probe_links = NULL;
131 +static struct bpf_object *objects = NULL;
132 +
133 +static int read_thread_closed = 1;
134 +
135 +// store for "published" data from the reader thread, which the collector
136 +// thread will write to netdata agent.
137 +static avl_tree_lock hardirq_pub;
138 +
139 +// tmp store for dynamic hard IRQ values we get from a per-CPU eBPF map.
140 +static hardirq_ebpf_val_t *hardirq_ebpf_vals = NULL;
141 +
142 +// tmp store for static hard IRQ values we get from a per-CPU eBPF map.
143 +static hardirq_ebpf_static_val_t *hardirq_ebpf_static_vals = NULL;
144 +
145 +static struct netdata_static_thread hardirq_threads = {"HARDIRQ KERNEL",
146 + NULL, NULL, 1, NULL,
147 + NULL, NULL };
148 +
149 +/**
150 + * Clean up the main thread.
151 + *
152 + * @param ptr thread data.
153 + */
154 +static void hardirq_cleanup(void *ptr)
155 +{
156 + for (int i = 0; hardirq_tracepoints[i].class != NULL; i++) {
157 + ebpf_disable_tracepoint(&hardirq_tracepoints[i]);
158 + }
159 +
160 + ebpf_module_t *em = (ebpf_module_t *)ptr;
161 + if (!em->enabled) {
162 + return;
163 + }
164 +
165 + heartbeat_t hb;
166 + heartbeat_init(&hb);
167 + uint32_t tick = 1 * USEC_PER_MS;
168 + while (!read_thread_closed) {
169 + usec_t dt = heartbeat_next(&hb, tick);
170 + UNUSED(dt);
171 + }
172 +
173 + freez(hardirq_ebpf_vals);
174 + freez(hardirq_ebpf_static_vals);
175 + freez(hardirq_threads.thread);
176 +
177 + if (probe_links) {
178 + struct bpf_program *prog;
179 + size_t i = 0 ;
180 + bpf_object__for_each_program(prog, objects) {
181 + bpf_link__destroy(probe_links[i]);
182 + i++;
183 + }
184 + bpf_object__close(objects);
185 + }
186 +}
187 +
188 +/*****************************************************************
189 + * MAIN LOOP
190 + *****************************************************************/
191 +
192 +/**
193 + * Compare hard IRQ values.
194 + *
195 + * @param a `hardirq_val_t *`.
196 + * @param b `hardirq_val_t *`.
197 + *
198 + * @return 0 if a==b, 1 if a>b, -1 if a<b.
199 +*/
200 +static int hardirq_val_cmp(void *a, void *b)
201 +{
202 + hardirq_val_t *ptr1 = a;
203 + hardirq_val_t *ptr2 = b;
204 +
205 + if (ptr1->irq > ptr2->irq) {
206 + return 1;
207 + }
208 + else if (ptr1->irq < ptr2->irq) {
209 + return -1;
210 + }
211 + else {
212 + return 0;
213 + }
214 +}
215 +
216 +static void hardirq_read_latency_map(int mapfd)
217 +{
218 + hardirq_ebpf_key_t key = {};
219 + hardirq_ebpf_key_t next_key = {};
220 + hardirq_val_t search_v = {};
221 + hardirq_val_t *v = NULL;
222 +
223 + while (bpf_map_get_next_key(mapfd, &key, &next_key) == 0) {
224 + // get val for this key.
225 + int test = bpf_map_lookup_elem(mapfd, &key, hardirq_ebpf_vals);
226 + if (unlikely(test < 0)) {
227 + key = next_key;
228 + continue;
229 + }
230 +
231 + // is this IRQ saved yet?
232 + //
233 + // if not, make a new one, mark it as unsaved for now, and continue; we
234 + // will insert it at the end after all of its values are correctly set,
235 + // so that we can safely publish it to the collector within a single,
236 + // short locked operation.
237 + //
238 + // otherwise simply continue; we will only update the latency, which
239 + // can be republished safely without a lock.
240 + //
241 + // NOTE: lock isn't strictly necessary for this initial search, as only
242 + // this thread does writing, but the AVL is using a read-write lock so
243 + // there is no congestion.
244 + bool v_is_new = false;
245 + search_v.irq = key.irq;
246 + v = (hardirq_val_t *)avl_search_lock(&hardirq_pub, (avl_t *)&search_v);
247 + if (unlikely(v == NULL)) {
248 + // latency/name can only be added reliably at a later time.
249 + // when they're added, only then will we AVL insert.
250 + v = callocz(1, sizeof(hardirq_val_t));
251 + v->irq = key.irq;
252 + v->dim_exists = false;
253 +
254 + v_is_new = true;
255 + }
256 +
257 + // note two things:
258 + // 1. we must add up latency value for this IRQ across all CPUs.
259 + // 2. the name is unfortunately *not* available on all CPU maps - only
260 + // a single map contains the name, so we must find it. we only need
261 + // to copy it though if the IRQ is new for us.
262 + bool name_saved = false;
263 + uint64_t total_latency = 0;
264 + int i;
265 + int end = (running_on_kernel < NETDATA_KERNEL_V4_15) ? 1 : ebpf_nprocs;
266 + for (i = 0; i < end; i++) {
267 + total_latency += hardirq_ebpf_vals[i].latency/1000;
268 +
269 + // copy name for new IRQs.
270 + if (v_is_new && !name_saved && hardirq_ebpf_vals[i].name[0] != '\0') {
271 + strncpyz(
272 + v->name,
273 + hardirq_ebpf_vals[i].name,
274 + NETDATA_HARDIRQ_NAME_LEN
275 + );
276 + name_saved = true;
277 + }
278 + }
279 +
280 + // can now safely publish latency for existing IRQs.
281 + v->latency = total_latency;
282 +
283 + // can now safely publish new IRQ.
284 + if (v_is_new) {
285 + avl_t *check = avl_insert_lock(&hardirq_pub, (avl_t *)v);
286 + if (check != (avl_t *)v) {
287 + error("Internal error, cannot insert the AVL tree.");
288 + }
289 + }
290 +
291 + key = next_key;
292 + }
293 +}
294 +
295 +static void hardirq_read_latency_static_map(int mapfd)
296 +{
297 + uint32_t i;
298 + for (i = 0; i < HARDIRQ_EBPF_STATIC_END; i++) {
299 + uint32_t map_i = hardirq_static_vals[i].idx;
300 + int test = bpf_map_lookup_elem(mapfd, &map_i, hardirq_ebpf_static_vals);
301 + if (unlikely(test < 0)) {
302 + continue;
303 + }
304 +
305 + uint64_t total_latency = 0;
306 + int cpu_i;
307 + int end = (running_on_kernel < NETDATA_KERNEL_V4_15) ? 1 : ebpf_nprocs;
308 + for (cpu_i = 0; cpu_i < end; cpu_i++) {
309 + total_latency += hardirq_ebpf_static_vals[cpu_i].latency/1000;
310 + }
311 +
312 + hardirq_static_vals[i].latency = total_latency;
313 + }
314 +}
315 +
316 +/**
317 + * Read eBPF maps for hard IRQ.
318 + */
319 +static void *hardirq_reader(void *ptr)
320 +{
321 + read_thread_closed = 0;
322 +
323 + heartbeat_t hb;
324 + heartbeat_init(&hb);
325 +
326 + ebpf_module_t *em = (ebpf_module_t *)ptr;
327 +
328 + usec_t step = NETDATA_HARDIRQ_SLEEP_MS * em->update_time;
329 + while (!close_ebpf_plugin) {
330 + usec_t dt = heartbeat_next(&hb, step);
331 + UNUSED(dt);
332 +
333 + hardirq_read_latency_map(hardirq_maps[HARDIRQ_MAP_LATENCY].map_fd);
334 + hardirq_read_latency_static_map(hardirq_maps[HARDIRQ_MAP_LATENCY_STATIC].map_fd);
335 + }
336 +
337 + read_thread_closed = 1;
338 + return NULL;
339 +}
340 +
341 +static void hardirq_create_charts()
342 +{
343 + ebpf_create_chart(
344 + NETDATA_EBPF_SYSTEM_GROUP,
345 + "hardirq_latency",
346 + "Hardware IRQ latency",
347 + "milliseconds",
348 + "interrupts",
349 + NULL,
350 + NETDATA_EBPF_CHART_TYPE_STACKED,
351 + NETDATA_CHART_PRIO_HARDIRQ_LATENCY,
352 + NULL, NULL, 0,
353 + NETDATA_EBPF_MODULE_NAME_HARDIRQ
354 + );
355 +
356 + fflush(stdout);
357 +}
358 +
359 +static void hardirq_create_static_dims()
360 +{
361 + uint32_t i;
362 + for (i = 0; i < HARDIRQ_EBPF_STATIC_END; i++) {
363 + ebpf_write_global_dimension(
364 + hardirq_static_vals[i].name, hardirq_static_vals[i].name,
365 + ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]
366 + );
367 + }
368 +}
369 +
370 +// callback for avl tree traversal on `hardirq_pub`.
371 +static int hardirq_write_dims(void *entry, void *data)
372 +{
373 + UNUSED(data);
374 +
375 + hardirq_val_t *v = entry;
376 +
377 + // IRQs get dynamically added in, so add the dimension if we haven't yet.
378 + if (!v->dim_exists) {
379 + ebpf_write_global_dimension(
380 + v->name, v->name,
381 + ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]
382 + );
383 + v->dim_exists = true;
384 + }
385 +
386 + write_chart_dimension(v->name, v->latency);
387 +
388 + return 1;
389 +}
390 +
391 +static inline void hardirq_write_static_dims()
392 +{
393 + uint32_t i;
394 + for (i = 0; i < HARDIRQ_EBPF_STATIC_END; i++) {
395 + write_chart_dimension(
396 + hardirq_static_vals[i].name,
397 + hardirq_static_vals[i].latency
398 + );
399 + }
400 +}
401 +
402 +/**
403 +* Main loop for this collector.
404 +*/
405 +static void hardirq_collector(ebpf_module_t *em)
406 +{
407 + hardirq_ebpf_vals = callocz(
408 + (running_on_kernel < NETDATA_KERNEL_V4_15) ? 1 : ebpf_nprocs,
409 + sizeof(hardirq_ebpf_val_t)
410 + );
411 + hardirq_ebpf_static_vals = callocz(
412 + (running_on_kernel < NETDATA_KERNEL_V4_15) ? 1 : ebpf_nprocs,
413 + sizeof(hardirq_ebpf_static_val_t)
414 + );
415 +
416 + avl_init_lock(&hardirq_pub, hardirq_val_cmp);
417 +
418 + // create reader thread.
419 + hardirq_threads.thread = mallocz(sizeof(netdata_thread_t));
420 + hardirq_threads.start_routine = hardirq_reader;
421 + netdata_thread_create(
422 + hardirq_threads.thread,
423 + hardirq_threads.name,
424 + NETDATA_THREAD_OPTION_JOINABLE,
425 + hardirq_reader,
426 + em
427 + );
428 +
429 + // create chart and static dims.
430 + pthread_mutex_lock(&lock);
431 + hardirq_create_charts();
432 + hardirq_create_static_dims();
433 + pthread_mutex_unlock(&lock);
434 +
435 + // loop and read from published data until ebpf plugin is closed.
436 + while (!close_ebpf_plugin) {
437 + pthread_mutex_lock(&collect_data_mutex);
438 + pthread_cond_wait(&collect_data_cond_var, &collect_data_mutex);
439 + pthread_mutex_lock(&lock);
440 +
441 + // write dims now for all hitherto discovered IRQs.
442 + write_begin_chart(NETDATA_EBPF_SYSTEM_GROUP, "hardirq_latency");
443 + avl_traverse_lock(&hardirq_pub, hardirq_write_dims, NULL);
444 + hardirq_write_static_dims();
445 + write_end_chart();
446 +
447 + pthread_mutex_unlock(&lock);
448 + pthread_mutex_unlock(&collect_data_mutex);
449 + }
450 +}
451 +
452 +/*****************************************************************
453 + * EBPF HARDIRQ THREAD
454 + *****************************************************************/
455 +
456 +/**
457 + * Hard IRQ latency thread.
458 + *
459 + * @param ptr a `ebpf_module_t *`.
460 + * @return always NULL.
461 + */
462 +void *ebpf_hardirq_thread(void *ptr)
463 +{
464 + netdata_thread_cleanup_push(hardirq_cleanup, ptr);
465 +
466 + ebpf_module_t *em = (ebpf_module_t *)ptr;
467 + em->maps = hardirq_maps;
468 +
469 + fill_ebpf_data(&hardirq_data);
470 +
471 + if (!em->enabled) {
472 + goto endhardirq;
473 + }
474 +
475 + if (ebpf_update_kernel(&hardirq_data)) {
476 + goto endhardirq;
477 + }
478 +
479 + if (ebpf_enable_tracepoints(hardirq_tracepoints) == 0) {
480 + em->enabled = CONFIG_BOOLEAN_NO;
481 + goto endhardirq;
482 + }
483 +
484 + probe_links = ebpf_load_program(ebpf_plugin_dir, em, kernel_string, &objects, hardirq_data.map_fd);
485 + if (!probe_links) {
486 + goto endhardirq;
487 + }
488 +
489 + hardirq_collector(em);
490 +
491 +endhardirq:
492 + netdata_thread_cleanup_pop(1);
493 +
494 + return NULL;
495 +}
collectors/ebpf.plugin/ebpf_hardirq.h new
+74
@@ -0,0 +1,74 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#ifndef NETDATA_EBPF_HARDIRQ_H
4 +#define NETDATA_EBPF_HARDIRQ_H 1
5 +
6 +/*****************************************************************
7 + * copied from kernel-collectors repo, with modifications needed
8 + * for inclusion here.
9 + *****************************************************************/
10 +
11 +#define NETDATA_HARDIRQ_NAME_LEN 32
12 +#define NETDATA_HARDIRQ_MAX_IRQS 1024L
13 +
14 +typedef struct hardirq_ebpf_key {
15 + int irq;
16 +} hardirq_ebpf_key_t;
17 +
18 +typedef struct hardirq_ebpf_val {
19 + uint64_t latency;
20 + uint64_t ts;
21 + char name[NETDATA_HARDIRQ_NAME_LEN];
22 +} hardirq_ebpf_val_t;
23 +
24 +enum hardirq_ebpf_static {
25 + HARDIRQ_EBPF_STATIC_APIC_THERMAL,
26 + HARDIRQ_EBPF_STATIC_APIC_THRESHOLD,
27 + HARDIRQ_EBPF_STATIC_APIC_ERROR,
28 + HARDIRQ_EBPF_STATIC_APIC_DEFERRED_ERROR,
29 + HARDIRQ_EBPF_STATIC_APIC_SPURIOUS,
30 + HARDIRQ_EBPF_STATIC_FUNC_CALL,
31 + HARDIRQ_EBPF_STATIC_FUNC_CALL_SINGLE,
32 + HARDIRQ_EBPF_STATIC_RESCHEDULE,
33 + HARDIRQ_EBPF_STATIC_LOCAL_TIMER,
34 + HARDIRQ_EBPF_STATIC_IRQ_WORK,
35 + HARDIRQ_EBPF_STATIC_X86_PLATFORM_IPI,
36 +
37 + HARDIRQ_EBPF_STATIC_END
38 +};
39 +
40 +typedef struct hardirq_ebpf_static_val {
41 + uint64_t latency;
42 + uint64_t ts;
43 +} hardirq_ebpf_static_val_t;
44 +
45 +/*****************************************************************
46 + * below this is eBPF plugin-specific code.
47 + *****************************************************************/
48 +
49 +#define NETDATA_EBPF_MODULE_NAME_HARDIRQ "hardirq"
50 +#define NETDATA_HARDIRQ_SLEEP_MS 650000ULL
51 +#define NETDATA_HARDIRQ_CONFIG_FILE "hardirq.conf"
52 +
53 +typedef struct hardirq_val {
54 + // must be at top for simplified AVL tree usage.
55 + // if it's not at the top, we need to use `containerof` for almost all ops.
56 + avl_t avl;
57 +
58 + int irq;
59 + bool dim_exists; // keep this after `int irq` for alignment byte savings.
60 + uint64_t latency;
61 + char name[NETDATA_HARDIRQ_NAME_LEN];
62 +} hardirq_val_t;
63 +
64 +typedef struct hardirq_static_val {
65 + enum hardirq_ebpf_static idx;
66 + char *name;
67 + uint64_t latency;
68 +} hardirq_static_val_t;
69 +
70 +extern struct config hardirq_config;
71 +extern void *ebpf_hardirq_thread(void *ptr);
72 +extern void ebpf_hardirq_create_apps_charts(struct ebpf_module *em, void *ptr);
73 +
74 +#endif /* NETDATA_EBPF_HARDIRQ_H */
packaging/ebpf.checksums
+3 -3
@@ -1,3 +1,3 @@
1 -c04f3832933315669b009d629fe9cbef33d8202aa9b57d45e3777e2385ae0c5b netdata-kernel-collector-glibc-v0.7.6.1.tar.xz
2 -25e9ea852b5ec593be0d6329f1c62e0b6aa872ebc835462d3514a2c9fb9294ef netdata-kernel-collector-musl-v0.7.6.1.tar.xz
3 -6704049d192bb47d2f26fee1ce9add0e93cfde62b0672c56cf4b5ea6bd6bf5e9 netdata-kernel-collector-static-v0.7.6.1.tar.xz
1 +3a2a6ae05c38d798b3230ba8018f1794530abf41d5826bc9e0f10bf688d63e3f netdata-kernel-collector-glibc-v0.7.7.tar.xz
2 +29331d14a104db423cc5849b67be0796c5320f08bbd1fe5b76000bc0a60eaab1 netdata-kernel-collector-musl-v0.7.7.tar.xz
3 +60b09083653ae1e0f143c3c6ed0420bbcf7278f1997795bf8f72cc7e154db344 netdata-kernel-collector-static-v0.7.7.tar.xz
packaging/ebpf.version
+1 -1
@@ -1 +1 @@
1 -v0.7.6.1
1 +v0.7.7
web/gui/dashboard_info.js
+4
@@ -942,6 +942,10 @@ netdataDashboard.context = {
942 info: 'CPU interrupts in detail. At the <a href="#menu_cpu">CPUs</a> section, interrupts are analyzed per CPU core.'
943 },
944
945 + 'system.hardirq_latency': {
946 + info: 'Total time spent servicing hardware interrupts.'
947 + },
948 +
949 'system.softirqs': {
950 info: 'CPU softirqs in detail. At the <a href="#menu_cpu">CPUs</a> section, softirqs are analyzed per CPU core.'
951 },