1 // SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
2 // Copyright (c) 2019 Facebook
3 //
4 // Based on runqslower(8) from BCC by Ivan Babrou.
5 // 11-Feb-2020 Andrii Nakryiko Created this.
6 #include <argp.h>
7 #include <signal.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <time.h>
12 #include <bpf/libbpf.h>
13 #include <bpf/bpf.h>
14 #include "runqslower.h"
15 #include "runqslower.skel.h"
16 #include "trace_helpers.h"
17
18 static volatile sig_atomic_t exiting = 0;
19
20 struct env {
21 pid_t pid;
22 pid_t tid;
23 __u64 min_us;
24 bool previous;
25 bool verbose;
26 } env = {
27 .min_us = 10000,
28 };
29
30 const char *argp_program_version = "runqslower 0.1";
31 const char *argp_program_bug_address =
32 "https://github.com/iovisor/bcc/tree/master/libbpf-tools";
33 const char argp_program_doc[] =
34 "Trace high run queue latency.\n"
35 "\n"
36 "USAGE: runqslower [--help] [-p PID] [-t TID] [-P] [min_us]\n"
37 "\n"
38 "EXAMPLES:\n"
39 " runqslower # trace latency higher than 10000 us (default)\n"
40 " runqslower 1000 # trace latency higher than 1000 us\n"
41 " runqslower -p 123 # trace pid 123\n"
42 " runqslower -t 123 # trace tid 123 (use for threads only)\n"
43 " runqslower -P # also show previous task name and TID\n";
44
45 static const struct argp_option opts[] = {
46 { "pid", 'p', "PID", 0, "Process PID to trace"},
47 { "tid", 't', "TID", 0, "Thread TID to trace"},
48 { "verbose", 'v', NULL, 0, "Verbose debug output" },
49 { "previous", 'P', NULL, 0, "also show previous task name and TID" },
50 { NULL, 'h', NULL, OPTION_HIDDEN, "Show the full help" },
51 {},
52 };
53
parse_arg(int key,char * arg,struct argp_state * state)54 static error_t parse_arg(int key, char *arg, struct argp_state *state)
55 {
56 static int pos_args;
57 int pid;
58 long long min_us;
59
60 switch (key) {
61 case 'h':
62 argp_state_help(state, stderr, ARGP_HELP_STD_HELP);
63 break;
64 case 'v':
65 env.verbose = true;
66 break;
67 case 'P':
68 env.previous = true;
69 break;
70 case 'p':
71 errno = 0;
72 pid = strtol(arg, NULL, 10);
73 if (errno || pid <= 0) {
74 fprintf(stderr, "Invalid PID: %s\n", arg);
75 argp_usage(state);
76 }
77 env.pid = pid;
78 break;
79 case 't':
80 errno = 0;
81 pid = strtol(arg, NULL, 10);
82 if (errno || pid <= 0) {
83 fprintf(stderr, "Invalid TID: %s\n", arg);
84 argp_usage(state);
85 }
86 env.tid = pid;
87 break;
88 case ARGP_KEY_ARG:
89 if (pos_args++) {
90 fprintf(stderr,
91 "Unrecognized positional argument: %s\n", arg);
92 argp_usage(state);
93 }
94 errno = 0;
95 min_us = strtoll(arg, NULL, 10);
96 if (errno || min_us <= 0) {
97 fprintf(stderr, "Invalid delay (in us): %s\n", arg);
98 argp_usage(state);
99 }
100 env.min_us = min_us;
101 break;
102 default:
103 return ARGP_ERR_UNKNOWN;
104 }
105 return 0;
106 }
107
libbpf_print_fn(enum libbpf_print_level level,const char * format,va_list args)108 static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
109 {
110 if (level == LIBBPF_DEBUG && !env.verbose)
111 return 0;
112 return vfprintf(stderr, format, args);
113 }
114
sig_int(int signo)115 static void sig_int(int signo)
116 {
117 exiting = 1;
118 }
119
handle_event(void * ctx,int cpu,void * data,__u32 data_sz)120 void handle_event(void *ctx, int cpu, void *data, __u32 data_sz)
121 {
122 const struct event *e = data;
123 struct tm *tm;
124 char ts[32];
125 time_t t;
126
127 time(&t);
128 tm = localtime(&t);
129 strftime(ts, sizeof(ts), "%H:%M:%S", tm);
130 if (env.previous)
131 printf("%-8s %-16s %-6d %14llu %-16s %-6d\n", ts, e->task, e->pid, e->delta_us, e->prev_task, e->prev_pid);
132 else
133 printf("%-8s %-16s %-6d %14llu\n", ts, e->task, e->pid, e->delta_us);
134 }
135
handle_lost_events(void * ctx,int cpu,__u64 lost_cnt)136 void handle_lost_events(void *ctx, int cpu, __u64 lost_cnt)
137 {
138 printf("Lost %llu events on CPU #%d!\n", lost_cnt, cpu);
139 }
140
main(int argc,char ** argv)141 int main(int argc, char **argv)
142 {
143 static const struct argp argp = {
144 .options = opts,
145 .parser = parse_arg,
146 .doc = argp_program_doc,
147 };
148 struct perf_buffer *pb = NULL;
149 struct runqslower_bpf *obj;
150 int err;
151
152 err = argp_parse(&argp, argc, argv, 0, NULL, NULL);
153 if (err)
154 return err;
155
156 libbpf_set_print(libbpf_print_fn);
157
158 obj = runqslower_bpf__open();
159 if (!obj) {
160 fprintf(stderr, "failed to open BPF object\n");
161 return 1;
162 }
163
164 /* initialize global data (filtering options) */
165 obj->rodata->targ_tgid = env.pid;
166 obj->rodata->targ_pid = env.tid;
167 obj->rodata->min_us = env.min_us;
168
169 if (probe_tp_btf("sched_wakeup")) {
170 bpf_program__set_autoload(obj->progs.handle_sched_wakeup, false);
171 bpf_program__set_autoload(obj->progs.handle_sched_wakeup_new, false);
172 bpf_program__set_autoload(obj->progs.handle_sched_switch, false);
173 } else {
174 bpf_program__set_autoload(obj->progs.sched_wakeup, false);
175 bpf_program__set_autoload(obj->progs.sched_wakeup_new, false);
176 bpf_program__set_autoload(obj->progs.sched_switch, false);
177 }
178
179 err = runqslower_bpf__load(obj);
180 if (err) {
181 fprintf(stderr, "failed to load BPF object: %d\n", err);
182 goto cleanup;
183 }
184
185 err = runqslower_bpf__attach(obj);
186 if (err) {
187 fprintf(stderr, "failed to attach BPF programs\n");
188 goto cleanup;
189 }
190
191 printf("Tracing run queue latency higher than %llu us\n", env.min_us);
192 if (env.previous)
193 printf("%-8s %-16s %-6s %14s %-16s %-6s\n", "TIME", "COMM", "TID", "LAT(us)", "PREV COMM", "PREV TID");
194 else
195 printf("%-8s %-16s %-6s %14s\n", "TIME", "COMM", "TID", "LAT(us)");
196
197 pb = perf_buffer__new(bpf_map__fd(obj->maps.events), 64,
198 handle_event, handle_lost_events, NULL, NULL);
199 if (!pb) {
200 err = -errno;
201 fprintf(stderr, "failed to open perf buffer: %d\n", err);
202 goto cleanup;
203 }
204
205 if (signal(SIGINT, sig_int) == SIG_ERR) {
206 fprintf(stderr, "can't set signal handler: %s\n", strerror(errno));
207 err = 1;
208 goto cleanup;
209 }
210
211 while (!exiting) {
212 err = perf_buffer__poll(pb, 100);
213 if (err < 0 && err != -EINTR) {
214 fprintf(stderr, "error polling perf buffer: %s\n", strerror(-err));
215 goto cleanup;
216 }
217 /* reset err to return 0 if exiting */
218 err = 0;
219 }
220
221 cleanup:
222 perf_buffer__free(pb);
223 runqslower_bpf__destroy(obj);
224
225 return err != 0;
226 }
227