1 // SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
2 // Copyright (c) 2021 Wenbo Zhang
3 //
4 // Based on tcprtt(8) from BCC by zhenwei pi.
5 // 06-Aug-2021 Wenbo Zhang Created this.
6 #define _DEFAULT_SOURCE
7 #include <arpa/inet.h>
8 #include <argp.h>
9 #include <stdio.h>
10 #include <signal.h>
11 #include <unistd.h>
12 #include <time.h>
13 #include <bpf/libbpf.h>
14 #include <bpf/bpf.h>
15 #include "tcprtt.h"
16 #include "tcprtt.skel.h"
17 #include "trace_helpers.h"
18
19 static struct env {
20 __u16 lport;
21 __u16 rport;
22 __u32 laddr;
23 __u32 raddr;
24 bool milliseconds;
25 time_t duration;
26 time_t interval;
27 bool timestamp;
28 bool laddr_hist;
29 bool raddr_hist;
30 bool extended;
31 bool verbose;
32 } env = {
33 .interval = 99999999,
34 };
35
36 static volatile bool exiting;
37
38 const char *argp_program_version = "tcprtt 0.1";
39 const char *argp_program_bug_address =
40 "https://github.com/iovisor/bcc/tree/master/libbpf-tools";
41 const char argp_program_doc[] =
42 "Summarize TCP RTT as a histogram.\n"
43 "\n"
44 "USAGE: \n"
45 "\n"
46 "EXAMPLES:\n"
47 " tcprtt # summarize TCP RTT\n"
48 " tcprtt -i 1 -d 10 # print 1 second summaries, 10 times\n"
49 " tcprtt -m -T # summarize in millisecond, and timestamps\n"
50 " tcprtt -p # filter for local port\n"
51 " tcprtt -P # filter for remote port\n"
52 " tcprtt -a # filter for local address\n"
53 " tcprtt -A # filter for remote address\n"
54 " tcprtt -b # show sockets histogram by local address\n"
55 " tcprtt -B # show sockets histogram by remote address\n"
56 " tcprtt -e # show extension summary(average)\n";
57
58 static const struct argp_option opts[] = {
59 { "interval", 'i', "INTERVAL", 0, "summary interval, seconds" },
60 { "duration", 'd', "DURATION", 0, "total duration of trace, seconds" },
61 { "timestamp", 'T', NULL, 0, "include timestamp on output" },
62 { "millisecond", 'm', NULL, 0, "millisecond histogram" },
63 { "lport", 'p', "LPORT", 0, "filter for local port" },
64 { "rport", 'P', "RPORT", 0, "filter for remote port" },
65 { "laddr", 'a', "LADDR", 0, "filter for local address" },
66 { "raddr", 'A', "RADDR", 0, "filter for remote address" },
67 { "byladdr", 'b', NULL, 0,
68 "show sockets histogram by local address" },
69 { "byraddr", 'B', NULL, 0,
70 "show sockets histogram by remote address" },
71 { "extension", 'e', NULL, 0, "show extension summary(average)" },
72 { "verbose", 'v', NULL, 0, "Verbose debug output" },
73 { NULL, 'h', NULL, OPTION_HIDDEN, "Show the full help" },
74 {},
75 };
76
parse_arg(int key,char * arg,struct argp_state * state)77 static error_t parse_arg(int key, char *arg, struct argp_state *state)
78 {
79 struct in_addr addr;
80
81 switch (key) {
82 case 'h':
83 argp_state_help(state, stderr, ARGP_HELP_STD_HELP);
84 break;
85 case 'v':
86 env.verbose = true;
87 break;
88 case 'i':
89 errno = 0;
90 env.interval = strtol(arg, NULL, 10);
91 if (errno || env.interval <= 0) {
92 fprintf(stderr, "invalid interval: %s\n", arg);
93 argp_usage(state);
94 }
95 break;
96 case 'd':
97 errno = 0;
98 env.duration = strtol(arg, NULL, 10);
99 if (errno || env.duration <= 0) {
100 fprintf(stderr, "invalid duration: %s\n", arg);
101 argp_usage(state);
102 }
103 break;
104 case 'T':
105 env.timestamp = true;
106 break;
107 case 'm':
108 env.milliseconds = true;
109 break;
110 case 'p':
111 errno = 0;
112 env.lport = strtoul(arg, NULL, 10);
113 if (errno) {
114 fprintf(stderr, "invalid lport: %s\n", arg);
115 argp_usage(state);
116 }
117 env.lport = htons(env.lport);
118 break;
119 case 'P':
120 errno = 0;
121 env.rport = strtoul(arg, NULL, 10);
122 if (errno) {
123 fprintf(stderr, "invalid rport: %s\n", arg);
124 argp_usage(state);
125 }
126 env.rport = htons(env.rport);
127 break;
128 case 'a':
129 if (inet_aton(arg, &addr) < 0) {
130 fprintf(stderr, "invalid local address: %s\n", arg);
131 argp_usage(state);
132 }
133 env.laddr = addr.s_addr;
134 break;
135 case 'A':
136 if (inet_aton(arg, &addr) < 0) {
137 fprintf(stderr, "invalid remote address: %s\n", arg);
138 argp_usage(state);
139 }
140 env.raddr = addr.s_addr;
141 break;
142 case 'b':
143 env.laddr_hist = true;
144 break;
145 case 'B':
146 env.raddr_hist = true;
147 break;
148 case 'e':
149 env.extended = true;
150 break;
151 default:
152 return ARGP_ERR_UNKNOWN;
153 }
154 return 0;
155 }
156
libbpf_print_fn(enum libbpf_print_level level,const char * format,va_list args)157 static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
158 {
159 if (level == LIBBPF_DEBUG && !env.verbose)
160 return 0;
161 return vfprintf(stderr, format, args);
162 }
163
sig_handler(int sig)164 static void sig_handler(int sig)
165 {
166 exiting = true;
167 }
168
print_map(struct bpf_map * map)169 static int print_map(struct bpf_map *map)
170 {
171 const char *units = env.milliseconds ? "msecs" : "usecs";
172 __u64 lookup_key = -1, next_key;
173 int err, fd = bpf_map__fd(map);
174 struct hist hist;
175
176 while (!bpf_map_get_next_key(fd, &lookup_key, &next_key)) {
177 err = bpf_map_lookup_elem(fd, &next_key, &hist);
178 if (err < 0) {
179 fprintf(stderr, "failed to lookup infos: %d\n", err);
180 return -1;
181 }
182
183 struct in_addr addr = {.s_addr = next_key };
184 if (env.laddr_hist)
185 printf("Local Address = %s ", inet_ntoa(addr));
186 else if (env.raddr_hist)
187 printf("Remote Address = %s ", inet_ntoa(addr));
188 else
189 printf("All Addresses = ****** ");
190 if (env.extended)
191 printf("[AVG %llu]", hist.latency / hist.cnt);
192 printf("\n");
193 print_log2_hist(hist.slots, MAX_SLOTS, units);
194 lookup_key = next_key;
195 }
196
197 lookup_key = -1;
198 while (!bpf_map_get_next_key(fd, &lookup_key, &next_key)) {
199 err = bpf_map_delete_elem(fd, &next_key);
200 if (err < 0) {
201 fprintf(stderr, "failed to cleanup infos: %d\n", err);
202 return -1;
203 }
204 lookup_key = next_key;
205 }
206
207 return 0;
208 }
209
main(int argc,char ** argv)210 int main(int argc, char **argv)
211 {
212 static const struct argp argp = {
213 .options = opts,
214 .parser = parse_arg,
215 .doc = argp_program_doc,
216 };
217 struct tcprtt_bpf *obj;
218 __u64 time_end = 0;
219 struct tm *tm;
220 char ts[32];
221 time_t t;
222 int err;
223
224 err = argp_parse(&argp, argc, argv, 0, NULL, NULL);
225 if (err)
226 return err;
227
228 libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
229 libbpf_set_print(libbpf_print_fn);
230
231 obj = tcprtt_bpf__open();
232 if (!obj) {
233 fprintf(stderr, "failed to open BPF object\n");
234 return 1;
235 }
236
237 obj->rodata->targ_laddr_hist = env.laddr_hist;
238 obj->rodata->targ_raddr_hist = env.raddr_hist;
239 obj->rodata->targ_show_ext = env.extended;
240 obj->rodata->targ_sport = env.lport;
241 obj->rodata->targ_dport = env.rport;
242 obj->rodata->targ_saddr = env.laddr;
243 obj->rodata->targ_daddr = env.raddr;
244 obj->rodata->targ_ms = env.milliseconds;
245
246 if (fentry_can_attach("tcp_rcv_established", NULL))
247 bpf_program__set_autoload(obj->progs.tcp_rcv_kprobe, false);
248 else
249 bpf_program__set_autoload(obj->progs.tcp_rcv, false);
250
251 err = tcprtt_bpf__load(obj);
252 if (err) {
253 fprintf(stderr, "failed to load BPF object: %d\n", err);
254 goto cleanup;
255 }
256
257 err = tcprtt_bpf__attach(obj);
258 if (err) {
259 fprintf(stderr, "failed to attach BPF programs: %d\n", err);
260 goto cleanup;
261 }
262
263 signal(SIGINT, sig_handler);
264
265 printf("Tracing TCP RTT");
266 if (env.duration)
267 printf(" for %ld secs.\n", env.duration);
268 else
269 printf("... Hit Ctrl-C to end.\n");
270
271 /* setup duration */
272 if (env.duration)
273 time_end = get_ktime_ns() + env.duration * NSEC_PER_SEC;
274
275 /* main: poll */
276 while (1) {
277 sleep(env.interval);
278 printf("\n");
279
280 if (env.timestamp) {
281 time(&t);
282 tm = localtime(&t);
283 strftime(ts, sizeof(ts), "%H:%M:%S", tm);
284 printf("%-8s\n", ts);
285 }
286
287 err = print_map(obj->maps.hists);
288 if (err)
289 break;
290
291 if (env.duration && get_ktime_ns() > time_end)
292 goto cleanup;
293
294 if (exiting)
295 break;
296 }
297
298 cleanup:
299 tcprtt_bpf__destroy(obj);
300 return err != 0;
301 }
302