1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * bpf_kwork_top.c |
4 | * |
5 | * Copyright (c) 2022 Huawei Inc, Yang Jihong <yangjihong1@huawei.com> |
6 | */ |
7 | |
8 | #include <time.h> |
9 | #include <fcntl.h> |
10 | #include <signal.h> |
11 | #include <stdio.h> |
12 | #include <unistd.h> |
13 | |
14 | #include <linux/time64.h> |
15 | |
16 | #include "util/debug.h" |
17 | #include "util/evsel.h" |
18 | #include "util/kwork.h" |
19 | |
20 | #include <bpf/bpf.h> |
21 | #include <perf/cpumap.h> |
22 | |
23 | #include "util/bpf_skel/kwork_top.skel.h" |
24 | |
25 | /* |
26 | * This should be in sync with "util/kwork_top.bpf.c" |
27 | */ |
28 | #define MAX_COMMAND_LEN 16 |
29 | |
30 | struct time_data { |
31 | __u64 timestamp; |
32 | }; |
33 | |
34 | struct work_data { |
35 | __u64 runtime; |
36 | }; |
37 | |
38 | struct task_data { |
39 | __u32 tgid; |
40 | __u32 is_kthread; |
41 | char comm[MAX_COMMAND_LEN]; |
42 | }; |
43 | |
44 | struct work_key { |
45 | __u32 type; |
46 | __u32 pid; |
47 | __u64 task_p; |
48 | }; |
49 | |
50 | struct task_key { |
51 | __u32 pid; |
52 | __u32 cpu; |
53 | }; |
54 | |
55 | struct kwork_class_bpf { |
56 | struct kwork_class *class; |
57 | void (*load_prepare)(void); |
58 | }; |
59 | |
60 | static struct kwork_top_bpf *skel; |
61 | |
62 | void perf_kwork__top_start(void) |
63 | { |
64 | struct timespec ts; |
65 | |
66 | clock_gettime(CLOCK_MONOTONIC, &ts); |
67 | skel->bss->from_timestamp = (u64)ts.tv_sec * NSEC_PER_SEC + ts.tv_nsec; |
68 | skel->bss->enabled = 1; |
69 | pr_debug("perf kwork top start at: %lld\n" , skel->bss->from_timestamp); |
70 | } |
71 | |
72 | void perf_kwork__top_finish(void) |
73 | { |
74 | struct timespec ts; |
75 | |
76 | skel->bss->enabled = 0; |
77 | clock_gettime(CLOCK_MONOTONIC, &ts); |
78 | skel->bss->to_timestamp = (u64)ts.tv_sec * NSEC_PER_SEC + ts.tv_nsec; |
79 | pr_debug("perf kwork top finish at: %lld\n" , skel->bss->to_timestamp); |
80 | } |
81 | |
82 | static void irq_load_prepare(void) |
83 | { |
84 | bpf_program__set_autoload(skel->progs.on_irq_handler_entry, true); |
85 | bpf_program__set_autoload(skel->progs.on_irq_handler_exit, true); |
86 | } |
87 | |
88 | static struct kwork_class_bpf kwork_irq_bpf = { |
89 | .load_prepare = irq_load_prepare, |
90 | }; |
91 | |
92 | static void softirq_load_prepare(void) |
93 | { |
94 | bpf_program__set_autoload(skel->progs.on_softirq_entry, true); |
95 | bpf_program__set_autoload(skel->progs.on_softirq_exit, true); |
96 | } |
97 | |
98 | static struct kwork_class_bpf kwork_softirq_bpf = { |
99 | .load_prepare = softirq_load_prepare, |
100 | }; |
101 | |
102 | static void sched_load_prepare(void) |
103 | { |
104 | bpf_program__set_autoload(skel->progs.on_switch, true); |
105 | } |
106 | |
107 | static struct kwork_class_bpf kwork_sched_bpf = { |
108 | .load_prepare = sched_load_prepare, |
109 | }; |
110 | |
111 | static struct kwork_class_bpf * |
112 | kwork_class_bpf_supported_list[KWORK_CLASS_MAX] = { |
113 | [KWORK_CLASS_IRQ] = &kwork_irq_bpf, |
114 | [KWORK_CLASS_SOFTIRQ] = &kwork_softirq_bpf, |
115 | [KWORK_CLASS_SCHED] = &kwork_sched_bpf, |
116 | }; |
117 | |
118 | static bool valid_kwork_class_type(enum kwork_class_type type) |
119 | { |
120 | return type >= 0 && type < KWORK_CLASS_MAX; |
121 | } |
122 | |
123 | static int setup_filters(struct perf_kwork *kwork) |
124 | { |
125 | u8 val = 1; |
126 | int i, nr_cpus, fd; |
127 | struct perf_cpu_map *map; |
128 | |
129 | if (kwork->cpu_list) { |
130 | fd = bpf_map__fd(skel->maps.kwork_top_cpu_filter); |
131 | if (fd < 0) { |
132 | pr_debug("Invalid cpu filter fd\n" ); |
133 | return -1; |
134 | } |
135 | |
136 | map = perf_cpu_map__new(kwork->cpu_list); |
137 | if (!map) { |
138 | pr_debug("Invalid cpu_list\n" ); |
139 | return -1; |
140 | } |
141 | |
142 | nr_cpus = libbpf_num_possible_cpus(); |
143 | for (i = 0; i < perf_cpu_map__nr(map); i++) { |
144 | struct perf_cpu cpu = perf_cpu_map__cpu(map, i); |
145 | |
146 | if (cpu.cpu >= nr_cpus) { |
147 | perf_cpu_map__put(map); |
148 | pr_err("Requested cpu %d too large\n" , cpu.cpu); |
149 | return -1; |
150 | } |
151 | bpf_map_update_elem(fd, &cpu.cpu, &val, BPF_ANY); |
152 | } |
153 | perf_cpu_map__put(map); |
154 | |
155 | skel->bss->has_cpu_filter = 1; |
156 | } |
157 | |
158 | return 0; |
159 | } |
160 | |
161 | int perf_kwork__top_prepare_bpf(struct perf_kwork *kwork __maybe_unused) |
162 | { |
163 | struct bpf_program *prog; |
164 | struct kwork_class *class; |
165 | struct kwork_class_bpf *class_bpf; |
166 | enum kwork_class_type type; |
167 | |
168 | skel = kwork_top_bpf__open(); |
169 | if (!skel) { |
170 | pr_debug("Failed to open kwork top skeleton\n" ); |
171 | return -1; |
172 | } |
173 | |
174 | /* |
175 | * set all progs to non-autoload, |
176 | * then set corresponding progs according to config |
177 | */ |
178 | bpf_object__for_each_program(prog, skel->obj) |
179 | bpf_program__set_autoload(prog, false); |
180 | |
181 | list_for_each_entry(class, &kwork->class_list, list) { |
182 | type = class->type; |
183 | if (!valid_kwork_class_type(type: type) || |
184 | !kwork_class_bpf_supported_list[type]) { |
185 | pr_err("Unsupported bpf trace class %s\n" , class->name); |
186 | goto out; |
187 | } |
188 | |
189 | class_bpf = kwork_class_bpf_supported_list[type]; |
190 | class_bpf->class = class; |
191 | |
192 | if (class_bpf->load_prepare) |
193 | class_bpf->load_prepare(); |
194 | } |
195 | |
196 | if (kwork_top_bpf__load(skel)) { |
197 | pr_debug("Failed to load kwork top skeleton\n" ); |
198 | goto out; |
199 | } |
200 | |
201 | if (setup_filters(kwork)) |
202 | goto out; |
203 | |
204 | if (kwork_top_bpf__attach(skel)) { |
205 | pr_debug("Failed to attach kwork top skeleton\n" ); |
206 | goto out; |
207 | } |
208 | |
209 | return 0; |
210 | |
211 | out: |
212 | kwork_top_bpf__destroy(skel); |
213 | return -1; |
214 | } |
215 | |
216 | static void read_task_info(struct kwork_work *work) |
217 | { |
218 | int fd; |
219 | struct task_data data; |
220 | struct task_key key = { |
221 | .pid = work->id, |
222 | .cpu = work->cpu, |
223 | }; |
224 | |
225 | fd = bpf_map__fd(skel->maps.kwork_top_tasks); |
226 | if (fd < 0) { |
227 | pr_debug("Invalid top tasks map fd\n" ); |
228 | return; |
229 | } |
230 | |
231 | if (!bpf_map_lookup_elem(fd, &key, &data)) { |
232 | work->tgid = data.tgid; |
233 | work->is_kthread = data.is_kthread; |
234 | work->name = strdup(data.comm); |
235 | } |
236 | } |
237 | static int add_work(struct perf_kwork *kwork, struct work_key *key, |
238 | struct work_data *data, int cpu) |
239 | { |
240 | struct kwork_class_bpf *bpf_trace; |
241 | struct kwork_work *work; |
242 | struct kwork_work tmp = { |
243 | .id = key->pid, |
244 | .cpu = cpu, |
245 | .name = NULL, |
246 | }; |
247 | enum kwork_class_type type = key->type; |
248 | |
249 | if (!valid_kwork_class_type(type: type)) { |
250 | pr_debug("Invalid class type %d to add work\n" , type); |
251 | return -1; |
252 | } |
253 | |
254 | bpf_trace = kwork_class_bpf_supported_list[type]; |
255 | tmp.class = bpf_trace->class; |
256 | |
257 | work = perf_kwork_add_work(kwork, tmp.class, &tmp); |
258 | if (!work) |
259 | return -1; |
260 | |
261 | work->total_runtime = data->runtime; |
262 | read_task_info(work); |
263 | |
264 | return 0; |
265 | } |
266 | |
267 | int perf_kwork__top_read_bpf(struct perf_kwork *kwork) |
268 | { |
269 | int i, fd, nr_cpus; |
270 | struct work_data *data; |
271 | struct work_key key, prev; |
272 | |
273 | fd = bpf_map__fd(skel->maps.kwork_top_works); |
274 | if (fd < 0) { |
275 | pr_debug("Invalid top runtime fd\n" ); |
276 | return -1; |
277 | } |
278 | |
279 | nr_cpus = libbpf_num_possible_cpus(); |
280 | data = calloc(nr_cpus, sizeof(struct work_data)); |
281 | if (!data) |
282 | return -1; |
283 | |
284 | memset(&prev, 0, sizeof(prev)); |
285 | while (!bpf_map_get_next_key(fd, &prev, &key)) { |
286 | if ((bpf_map_lookup_elem(fd, &key, data)) != 0) { |
287 | pr_debug("Failed to lookup top elem\n" ); |
288 | return -1; |
289 | } |
290 | |
291 | for (i = 0; i < nr_cpus; i++) { |
292 | if (data[i].runtime == 0) |
293 | continue; |
294 | |
295 | if (add_work(kwork, key: &key, data: &data[i], cpu: i)) |
296 | return -1; |
297 | } |
298 | prev = key; |
299 | } |
300 | free(data); |
301 | |
302 | return 0; |
303 | } |
304 | |
305 | void perf_kwork__top_cleanup_bpf(void) |
306 | { |
307 | kwork_top_bpf__destroy(skel); |
308 | } |
309 | |