xref: /linux/tools/perf/util/bpf_lock_contention.c (revision b8fd76f41820951d8a6e2521c25f54afadf338bd)
1 // SPDX-License-Identifier: GPL-2.0
2 #include "util/debug.h"
3 #include "util/evlist.h"
4 #include "util/machine.h"
5 #include "util/map.h"
6 #include "util/symbol.h"
7 #include "util/target.h"
8 #include "util/thread.h"
9 #include "util/thread_map.h"
10 #include "util/lock-contention.h"
11 #include <linux/zalloc.h>
12 #include <linux/string.h>
13 #include <bpf/bpf.h>
14 
15 #include "bpf_skel/lock_contention.skel.h"
16 #include "bpf_skel/lock_data.h"
17 
18 static struct lock_contention_bpf *skel;
19 
20 int lock_contention_prepare(struct lock_contention *con)
21 {
22 	int i, fd;
23 	int ncpus = 1, ntasks = 1;
24 	struct evlist *evlist = con->evlist;
25 	struct target *target = con->target;
26 
27 	skel = lock_contention_bpf__open();
28 	if (!skel) {
29 		pr_err("Failed to open lock-contention BPF skeleton\n");
30 		return -1;
31 	}
32 
33 	bpf_map__set_value_size(skel->maps.stacks, con->max_stack * sizeof(u64));
34 	bpf_map__set_max_entries(skel->maps.lock_stat, con->map_nr_entries);
35 	bpf_map__set_max_entries(skel->maps.tstamp, con->map_nr_entries);
36 
37 	if (con->aggr_mode == LOCK_AGGR_TASK) {
38 		bpf_map__set_max_entries(skel->maps.task_data, con->map_nr_entries);
39 		bpf_map__set_max_entries(skel->maps.stacks, 1);
40 	} else {
41 		bpf_map__set_max_entries(skel->maps.task_data, 1);
42 		bpf_map__set_max_entries(skel->maps.stacks, con->map_nr_entries);
43 	}
44 
45 	if (target__has_cpu(target))
46 		ncpus = perf_cpu_map__nr(evlist->core.user_requested_cpus);
47 	if (target__has_task(target))
48 		ntasks = perf_thread_map__nr(evlist->core.threads);
49 
50 	bpf_map__set_max_entries(skel->maps.cpu_filter, ncpus);
51 	bpf_map__set_max_entries(skel->maps.task_filter, ntasks);
52 
53 	if (lock_contention_bpf__load(skel) < 0) {
54 		pr_err("Failed to load lock-contention BPF skeleton\n");
55 		return -1;
56 	}
57 
58 	if (target__has_cpu(target)) {
59 		u32 cpu;
60 		u8 val = 1;
61 
62 		skel->bss->has_cpu = 1;
63 		fd = bpf_map__fd(skel->maps.cpu_filter);
64 
65 		for (i = 0; i < ncpus; i++) {
66 			cpu = perf_cpu_map__cpu(evlist->core.user_requested_cpus, i).cpu;
67 			bpf_map_update_elem(fd, &cpu, &val, BPF_ANY);
68 		}
69 	}
70 
71 	if (target__has_task(target)) {
72 		u32 pid;
73 		u8 val = 1;
74 
75 		skel->bss->has_task = 1;
76 		fd = bpf_map__fd(skel->maps.task_filter);
77 
78 		for (i = 0; i < ntasks; i++) {
79 			pid = perf_thread_map__pid(evlist->core.threads, i);
80 			bpf_map_update_elem(fd, &pid, &val, BPF_ANY);
81 		}
82 	}
83 
84 	if (target__none(target) && evlist->workload.pid > 0) {
85 		u32 pid = evlist->workload.pid;
86 		u8 val = 1;
87 
88 		skel->bss->has_task = 1;
89 		fd = bpf_map__fd(skel->maps.task_filter);
90 		bpf_map_update_elem(fd, &pid, &val, BPF_ANY);
91 	}
92 
93 	/* these don't work well if in the rodata section */
94 	skel->bss->stack_skip = con->stack_skip;
95 	skel->bss->aggr_mode = con->aggr_mode;
96 
97 	lock_contention_bpf__attach(skel);
98 	return 0;
99 }
100 
101 int lock_contention_start(void)
102 {
103 	skel->bss->enabled = 1;
104 	return 0;
105 }
106 
107 int lock_contention_stop(void)
108 {
109 	skel->bss->enabled = 0;
110 	return 0;
111 }
112 
113 int lock_contention_read(struct lock_contention *con)
114 {
115 	int fd, stack, task_fd, err = 0;
116 	struct contention_key *prev_key, key;
117 	struct contention_data data = {};
118 	struct lock_stat *st = NULL;
119 	struct machine *machine = con->machine;
120 	u64 *stack_trace;
121 	size_t stack_size = con->max_stack * sizeof(*stack_trace);
122 
123 	fd = bpf_map__fd(skel->maps.lock_stat);
124 	stack = bpf_map__fd(skel->maps.stacks);
125 	task_fd = bpf_map__fd(skel->maps.task_data);
126 
127 	con->lost = skel->bss->lost;
128 
129 	stack_trace = zalloc(stack_size);
130 	if (stack_trace == NULL)
131 		return -1;
132 
133 	if (con->aggr_mode == LOCK_AGGR_TASK) {
134 		struct thread *idle = __machine__findnew_thread(machine,
135 								/*pid=*/0,
136 								/*tid=*/0);
137 		thread__set_comm(idle, "swapper", /*timestamp=*/0);
138 	}
139 
140 	/* make sure it loads the kernel map */
141 	map__load(maps__first(machine->kmaps));
142 
143 	prev_key = NULL;
144 	while (!bpf_map_get_next_key(fd, prev_key, &key)) {
145 		struct map *kmap;
146 		struct symbol *sym;
147 		int idx = 0;
148 		s32 stack_id;
149 
150 		/* to handle errors in the loop body */
151 		err = -1;
152 
153 		bpf_map_lookup_elem(fd, &key, &data);
154 		st = zalloc(sizeof(*st));
155 		if (st == NULL)
156 			break;
157 
158 		st->nr_contended = data.count;
159 		st->wait_time_total = data.total_time;
160 		st->wait_time_max = data.max_time;
161 		st->wait_time_min = data.min_time;
162 
163 		if (data.count)
164 			st->avg_wait_time = data.total_time / data.count;
165 
166 		st->flags = data.flags;
167 		st->addr = key.aggr_key;
168 
169 		if (con->aggr_mode == LOCK_AGGR_TASK) {
170 			struct contention_task_data task;
171 			struct thread *t;
172 			int pid = key.aggr_key;
173 
174 			/* do not update idle comm which contains CPU number */
175 			if (st->addr) {
176 				bpf_map_lookup_elem(task_fd, &pid, &task);
177 				t = __machine__findnew_thread(machine, /*pid=*/-1, pid);
178 				thread__set_comm(t, task.comm, /*timestamp=*/0);
179 			}
180 			goto next;
181 		}
182 
183 		if (con->aggr_mode == LOCK_AGGR_ADDR) {
184 			sym = machine__find_kernel_symbol(machine, st->addr, &kmap);
185 			if (sym)
186 				st->name = strdup(sym->name);
187 			goto next;
188 		}
189 
190 		stack_id = key.aggr_key;
191 		bpf_map_lookup_elem(stack, &stack_id, stack_trace);
192 
193 		/* skip lock internal functions */
194 		while (machine__is_lock_function(machine, stack_trace[idx]) &&
195 		       idx < con->max_stack - 1)
196 			idx++;
197 
198 		st->addr = stack_trace[idx];
199 		sym = machine__find_kernel_symbol(machine, st->addr, &kmap);
200 
201 		if (sym) {
202 			unsigned long offset;
203 			int ret = 0;
204 
205 			offset = kmap->map_ip(kmap, st->addr) - sym->start;
206 
207 			if (offset)
208 				ret = asprintf(&st->name, "%s+%#lx", sym->name, offset);
209 			else
210 				st->name = strdup(sym->name);
211 
212 			if (ret < 0 || st->name == NULL)
213 				break;
214 		} else if (asprintf(&st->name, "%#lx", (unsigned long)st->addr) < 0) {
215 			break;
216 		}
217 
218 		if (verbose) {
219 			st->callstack = memdup(stack_trace, stack_size);
220 			if (st->callstack == NULL)
221 				break;
222 		}
223 next:
224 		hlist_add_head(&st->hash_entry, con->result);
225 		prev_key = &key;
226 
227 		/* we're fine now, reset the values */
228 		st = NULL;
229 		err = 0;
230 	}
231 
232 	free(stack_trace);
233 	if (st) {
234 		free(st->name);
235 		free(st);
236 	}
237 
238 	return err;
239 }
240 
241 int lock_contention_finish(void)
242 {
243 	if (skel) {
244 		skel->bss->enabled = 0;
245 		lock_contention_bpf__destroy(skel);
246 	}
247 
248 	return 0;
249 }
250