Commit | Line | Data |
---|---|---|
8c984209 YJ |
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 | ||
d2956b3a YJ |
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 | ||
36019dff YJ |
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 | ||
8c984209 YJ |
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] = { | |
d2956b3a | 113 | [KWORK_CLASS_IRQ] = &kwork_irq_bpf, |
36019dff | 114 | [KWORK_CLASS_SOFTIRQ] = &kwork_softirq_bpf, |
8c984209 YJ |
115 | [KWORK_CLASS_SCHED] = &kwork_sched_bpf, |
116 | }; | |
117 | ||
118 | static bool valid_kwork_class_type(enum kwork_class_type type) | |
119 | { | |
3ecf87b2 | 120 | return type >= 0 && type < KWORK_CLASS_MAX; |
8c984209 YJ |
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) || | |
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)) { | |
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, &data[i], 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 | } |