blob: d369e1338191bca7e27219516a99c96e11918420 [file] [log] [blame]
Alexey Ivanovcc01a9c2019-01-16 09:50:46 -08001#!/usr/bin/python
Brendan Gregg60393ea2016-10-04 15:18:11 -07002# @lint-avoid-python-3-compatibility-imports
3#
4# tcptop Summarize TCP send/recv throughput by host.
5# For Linux, uses BCC, eBPF. Embedded C.
6#
Hariharan Ananthakrishnan04893e32021-08-12 05:55:21 -07007# USAGE: tcptop [-h] [-C] [-S] [-p PID] [interval [count]] [-4 | -6]
Brendan Gregg60393ea2016-10-04 15:18:11 -07008#
9# This uses dynamic tracing of kernel functions, and will need to be updated
10# to match kernel changes.
11#
12# WARNING: This traces all send/receives at the TCP level, and while it
13# summarizes data in-kernel to reduce overhead, there may still be some
14# overhead at high TCP send/receive rates (eg, ~13% of one CPU at 100k TCP
15# events/sec. This is not the same as packet rate: funccount can be used to
16# count the kprobes below to find out the TCP rate). Test in a lab environment
17# first. If your send/receive rate is low (eg, <1k/sec) then the overhead is
18# expected to be negligible.
19#
20# ToDo: Fit output to screen size (top X only) in default (not -C) mode.
21#
22# Copyright 2016 Netflix, Inc.
23# Licensed under the Apache License, Version 2.0 (the "License")
24#
25# 02-Sep-2016 Brendan Gregg Created this.
26
27from __future__ import print_function
28from bcc import BPF
Alban Crequy32ab8582020-03-22 16:06:44 +010029from bcc.containers import filter_by_containers
Brendan Gregg60393ea2016-10-04 15:18:11 -070030import argparse
31from socket import inet_ntop, AF_INET, AF_INET6
32from struct import pack
33from time import sleep, strftime
34from subprocess import call
Andreas Gerstmayrc64f4872018-07-06 14:59:07 +020035from collections import namedtuple, defaultdict
Brendan Gregg60393ea2016-10-04 15:18:11 -070036
37# arguments
Benjamin Poirier8e86b9e2017-07-27 16:07:06 -070038def range_check(string):
39 value = int(string)
40 if value < 1:
41 msg = "value must be stricly positive, got %d" % (value,)
42 raise argparse.ArgumentTypeError(msg)
43 return value
44
Brendan Gregg60393ea2016-10-04 15:18:11 -070045examples = """examples:
46 ./tcptop # trace TCP send/recv by host
47 ./tcptop -C # don't clear the screen
48 ./tcptop -p 181 # only trace PID 181
Alban Crequy32ab8582020-03-22 16:06:44 +010049 ./tcptop --cgroupmap mappath # only trace cgroups in this BPF map
50 ./tcptop --mntnsmap mappath # only trace mount namespaces in the map
Hariharan Ananthakrishnan04893e32021-08-12 05:55:21 -070051 ./tcptop -4 # trace IPv4 family only
52 ./tcptop -6 # trace IPv6 family only
Brendan Gregg60393ea2016-10-04 15:18:11 -070053"""
54parser = argparse.ArgumentParser(
55 description="Summarize TCP send/recv throughput by host",
56 formatter_class=argparse.RawDescriptionHelpFormatter,
57 epilog=examples)
58parser.add_argument("-C", "--noclear", action="store_true",
59 help="don't clear the screen")
60parser.add_argument("-S", "--nosummary", action="store_true",
61 help="skip system summary line")
62parser.add_argument("-p", "--pid",
63 help="trace this PID only")
Benjamin Poirier8e86b9e2017-07-27 16:07:06 -070064parser.add_argument("interval", nargs="?", default=1, type=range_check,
Brendan Gregg60393ea2016-10-04 15:18:11 -070065 help="output interval, in seconds (default 1)")
Benjamin Poirier8e86b9e2017-07-27 16:07:06 -070066parser.add_argument("count", nargs="?", default=-1, type=range_check,
Brendan Gregg60393ea2016-10-04 15:18:11 -070067 help="number of outputs")
Alban Crequy7d626562020-03-08 16:41:34 +010068parser.add_argument("--cgroupmap",
69 help="trace cgroups in this BPF map only")
Alban Crequy32ab8582020-03-22 16:06:44 +010070parser.add_argument("--mntnsmap",
71 help="trace mount namespaces in this BPF map only")
Hariharan Ananthakrishnan04893e32021-08-12 05:55:21 -070072group = parser.add_mutually_exclusive_group()
73group.add_argument("-4", "--ipv4", action="store_true",
74 help="trace IPv4 family only")
75group.add_argument("-6", "--ipv6", action="store_true",
76 help="trace IPv6 family only")
Nathan Scottcf0792f2018-02-02 16:56:50 +110077parser.add_argument("--ebpf", action="store_true",
78 help=argparse.SUPPRESS)
Brendan Gregg60393ea2016-10-04 15:18:11 -070079args = parser.parse_args()
Brendan Gregg60393ea2016-10-04 15:18:11 -070080debug = 0
81
82# linux stats
83loadavg = "/proc/loadavg"
84
85# define BPF program
86bpf_text = """
87#include <uapi/linux/ptrace.h>
88#include <net/sock.h>
89#include <bcc/proto.h>
90
91struct ipv4_key_t {
92 u32 pid;
eiffel-flaa6437e2021-12-18 19:21:13 +010093 char name[TASK_COMM_LEN];
Brendan Gregg60393ea2016-10-04 15:18:11 -070094 u32 saddr;
95 u32 daddr;
96 u16 lport;
97 u16 dport;
98};
99BPF_HASH(ipv4_send_bytes, struct ipv4_key_t);
100BPF_HASH(ipv4_recv_bytes, struct ipv4_key_t);
101
102struct ipv6_key_t {
Marko Myllynenbfbf17e2018-09-11 21:49:58 +0300103 unsigned __int128 saddr;
104 unsigned __int128 daddr;
Jerome Marchand6b8a8962020-03-19 14:40:15 +0100105 u32 pid;
eiffel-flaa6437e2021-12-18 19:21:13 +0100106 char name[TASK_COMM_LEN];
Brendan Gregg60393ea2016-10-04 15:18:11 -0700107 u16 lport;
108 u16 dport;
Jerome Marchand6b8a8962020-03-19 14:40:15 +0100109 u64 __pad__;
Brendan Gregg60393ea2016-10-04 15:18:11 -0700110};
111BPF_HASH(ipv6_send_bytes, struct ipv6_key_t);
112BPF_HASH(ipv6_recv_bytes, struct ipv6_key_t);
113
114int kprobe__tcp_sendmsg(struct pt_regs *ctx, struct sock *sk,
115 struct msghdr *msg, size_t size)
116{
Alban Crequy32ab8582020-03-22 16:06:44 +0100117 if (container_should_be_filtered()) {
Alban Crequy7d626562020-03-08 16:41:34 +0100118 return 0;
119 }
Alban Crequy32ab8582020-03-22 16:06:44 +0100120
121 u32 pid = bpf_get_current_pid_tgid() >> 32;
122 FILTER_PID
123
Brendan Gregg60393ea2016-10-04 15:18:11 -0700124 u16 dport = 0, family = sk->__sk_common.skc_family;
Brendan Gregg60393ea2016-10-04 15:18:11 -0700125
Hariharan Ananthakrishnan04893e32021-08-12 05:55:21 -0700126 FILTER_FAMILY
127
Brendan Gregg60393ea2016-10-04 15:18:11 -0700128 if (family == AF_INET) {
129 struct ipv4_key_t ipv4_key = {.pid = pid};
eiffel-flaa6437e2021-12-18 19:21:13 +0100130 bpf_get_current_comm(&ipv4_key.name, sizeof(ipv4_key.name));
Brendan Gregg60393ea2016-10-04 15:18:11 -0700131 ipv4_key.saddr = sk->__sk_common.skc_rcv_saddr;
132 ipv4_key.daddr = sk->__sk_common.skc_daddr;
133 ipv4_key.lport = sk->__sk_common.skc_num;
134 dport = sk->__sk_common.skc_dport;
135 ipv4_key.dport = ntohs(dport);
Javier Honduvilla Coto64bf9652018-08-01 06:50:19 +0200136 ipv4_send_bytes.increment(ipv4_key, size);
Brendan Gregg60393ea2016-10-04 15:18:11 -0700137
138 } else if (family == AF_INET6) {
139 struct ipv6_key_t ipv6_key = {.pid = pid};
eiffel-flaa6437e2021-12-18 19:21:13 +0100140 bpf_get_current_comm(&ipv6_key.name, sizeof(ipv6_key.name));
Sumanth Korikkar7f6066d2020-05-20 10:49:56 -0500141 bpf_probe_read_kernel(&ipv6_key.saddr, sizeof(ipv6_key.saddr),
Yonghong Songa4834a62020-02-06 09:44:26 -0800142 &sk->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr32);
Sumanth Korikkar7f6066d2020-05-20 10:49:56 -0500143 bpf_probe_read_kernel(&ipv6_key.daddr, sizeof(ipv6_key.daddr),
Yonghong Songa4834a62020-02-06 09:44:26 -0800144 &sk->__sk_common.skc_v6_daddr.in6_u.u6_addr32);
Brendan Gregg60393ea2016-10-04 15:18:11 -0700145 ipv6_key.lport = sk->__sk_common.skc_num;
146 dport = sk->__sk_common.skc_dport;
147 ipv6_key.dport = ntohs(dport);
Javier Honduvilla Coto64bf9652018-08-01 06:50:19 +0200148 ipv6_send_bytes.increment(ipv6_key, size);
Brendan Gregg60393ea2016-10-04 15:18:11 -0700149 }
150 // else drop
151
152 return 0;
153}
154
155/*
156 * tcp_recvmsg() would be obvious to trace, but is less suitable because:
157 * - we'd need to trace both entry and return, to have both sock and size
158 * - misses tcp_read_sock() traffic
159 * we'd much prefer tracepoints once they are available.
160 */
161int kprobe__tcp_cleanup_rbuf(struct pt_regs *ctx, struct sock *sk, int copied)
162{
Alban Crequy32ab8582020-03-22 16:06:44 +0100163 if (container_should_be_filtered()) {
Alban Crequy7d626562020-03-08 16:41:34 +0100164 return 0;
165 }
Alban Crequy32ab8582020-03-22 16:06:44 +0100166
167 u32 pid = bpf_get_current_pid_tgid() >> 32;
168 FILTER_PID
169
Brendan Gregg60393ea2016-10-04 15:18:11 -0700170 u16 dport = 0, family = sk->__sk_common.skc_family;
171 u64 *val, zero = 0;
172
Benjamin Poirier81ad0542017-07-28 13:25:14 -0700173 if (copied <= 0)
Paul Chaignon6d9b1b22017-10-07 11:06:41 +0200174 return 0;
Benjamin Poirier81ad0542017-07-28 13:25:14 -0700175
Hariharan Ananthakrishnan04893e32021-08-12 05:55:21 -0700176 FILTER_FAMILY
177
Brendan Gregg60393ea2016-10-04 15:18:11 -0700178 if (family == AF_INET) {
179 struct ipv4_key_t ipv4_key = {.pid = pid};
eiffel-flaa6437e2021-12-18 19:21:13 +0100180 bpf_get_current_comm(&ipv4_key.name, sizeof(ipv4_key.name));
Brendan Gregg60393ea2016-10-04 15:18:11 -0700181 ipv4_key.saddr = sk->__sk_common.skc_rcv_saddr;
182 ipv4_key.daddr = sk->__sk_common.skc_daddr;
183 ipv4_key.lport = sk->__sk_common.skc_num;
184 dport = sk->__sk_common.skc_dport;
185 ipv4_key.dport = ntohs(dport);
Javier Honduvilla Coto64bf9652018-08-01 06:50:19 +0200186 ipv4_recv_bytes.increment(ipv4_key, copied);
187
Brendan Gregg60393ea2016-10-04 15:18:11 -0700188 } else if (family == AF_INET6) {
189 struct ipv6_key_t ipv6_key = {.pid = pid};
eiffel-flaa6437e2021-12-18 19:21:13 +0100190 bpf_get_current_comm(&ipv6_key.name, sizeof(ipv6_key.name));
Sumanth Korikkar7f6066d2020-05-20 10:49:56 -0500191 bpf_probe_read_kernel(&ipv6_key.saddr, sizeof(ipv6_key.saddr),
Yonghong Songa4834a62020-02-06 09:44:26 -0800192 &sk->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr32);
Sumanth Korikkar7f6066d2020-05-20 10:49:56 -0500193 bpf_probe_read_kernel(&ipv6_key.daddr, sizeof(ipv6_key.daddr),
Yonghong Songa4834a62020-02-06 09:44:26 -0800194 &sk->__sk_common.skc_v6_daddr.in6_u.u6_addr32);
Brendan Gregg60393ea2016-10-04 15:18:11 -0700195 ipv6_key.lport = sk->__sk_common.skc_num;
196 dport = sk->__sk_common.skc_dport;
197 ipv6_key.dport = ntohs(dport);
Javier Honduvilla Coto64bf9652018-08-01 06:50:19 +0200198 ipv6_recv_bytes.increment(ipv6_key, copied);
Brendan Gregg60393ea2016-10-04 15:18:11 -0700199 }
200 // else drop
201
202 return 0;
203}
204"""
205
206# code substitutions
207if args.pid:
Alban Crequy32ab8582020-03-22 16:06:44 +0100208 bpf_text = bpf_text.replace('FILTER_PID',
Brendan Gregg60393ea2016-10-04 15:18:11 -0700209 'if (pid != %s) { return 0; }' % args.pid)
210else:
Alban Crequy32ab8582020-03-22 16:06:44 +0100211 bpf_text = bpf_text.replace('FILTER_PID', '')
Hariharan Ananthakrishnan04893e32021-08-12 05:55:21 -0700212if args.ipv4:
213 bpf_text = bpf_text.replace('FILTER_FAMILY',
214 'if (family != AF_INET) { return 0; }')
215elif args.ipv6:
216 bpf_text = bpf_text.replace('FILTER_FAMILY',
217 'if (family != AF_INET6) { return 0; }')
218bpf_text = bpf_text.replace('FILTER_FAMILY', '')
Alban Crequy32ab8582020-03-22 16:06:44 +0100219bpf_text = filter_by_containers(args) + bpf_text
Nathan Scottcf0792f2018-02-02 16:56:50 +1100220if debug or args.ebpf:
Brendan Gregg60393ea2016-10-04 15:18:11 -0700221 print(bpf_text)
Nathan Scottcf0792f2018-02-02 16:56:50 +1100222 if args.ebpf:
223 exit()
Brendan Gregg60393ea2016-10-04 15:18:11 -0700224
eiffel-flaa6437e2021-12-18 19:21:13 +0100225TCPSessionKey = namedtuple('TCPSession', ['pid', 'name', 'laddr', 'lport', 'daddr', 'dport'])
Brendan Gregg60393ea2016-10-04 15:18:11 -0700226
Andreas Gerstmayrc64f4872018-07-06 14:59:07 +0200227def get_ipv4_session_key(k):
228 return TCPSessionKey(pid=k.pid,
eiffel-flaa6437e2021-12-18 19:21:13 +0100229 name=k.name,
Andreas Gerstmayrc64f4872018-07-06 14:59:07 +0200230 laddr=inet_ntop(AF_INET, pack("I", k.saddr)),
231 lport=k.lport,
232 daddr=inet_ntop(AF_INET, pack("I", k.daddr)),
233 dport=k.dport)
234
235def get_ipv6_session_key(k):
236 return TCPSessionKey(pid=k.pid,
eiffel-flaa6437e2021-12-18 19:21:13 +0100237 name=k.name,
Marko Myllynenbfbf17e2018-09-11 21:49:58 +0300238 laddr=inet_ntop(AF_INET6, k.saddr),
Andreas Gerstmayrc64f4872018-07-06 14:59:07 +0200239 lport=k.lport,
Marko Myllynenbfbf17e2018-09-11 21:49:58 +0300240 daddr=inet_ntop(AF_INET6, k.daddr),
Andreas Gerstmayrc64f4872018-07-06 14:59:07 +0200241 dport=k.dport)
242
Brendan Gregg60393ea2016-10-04 15:18:11 -0700243# initialize BPF
244b = BPF(text=bpf_text)
245
246ipv4_send_bytes = b["ipv4_send_bytes"]
247ipv4_recv_bytes = b["ipv4_recv_bytes"]
248ipv6_send_bytes = b["ipv6_send_bytes"]
249ipv6_recv_bytes = b["ipv6_recv_bytes"]
250
251print('Tracing... Output every %s secs. Hit Ctrl-C to end' % args.interval)
252
253# output
Benjamin Poirier8e86b9e2017-07-27 16:07:06 -0700254i = 0
255exiting = False
256while i != args.count and not exiting:
Brendan Gregg60393ea2016-10-04 15:18:11 -0700257 try:
Benjamin Poirier8e86b9e2017-07-27 16:07:06 -0700258 sleep(args.interval)
Brendan Gregg60393ea2016-10-04 15:18:11 -0700259 except KeyboardInterrupt:
Benjamin Poirier8e86b9e2017-07-27 16:07:06 -0700260 exiting = True
Brendan Gregg60393ea2016-10-04 15:18:11 -0700261
262 # header
263 if args.noclear:
264 print()
265 else:
266 call("clear")
267 if not args.nosummary:
268 with open(loadavg) as stats:
269 print("%-8s loadavg: %s" % (strftime("%H:%M:%S"), stats.read()))
270
Andreas Gerstmayrc64f4872018-07-06 14:59:07 +0200271 # IPv4: build dict of all seen keys
272 ipv4_throughput = defaultdict(lambda: [0, 0])
Brendan Gregg60393ea2016-10-04 15:18:11 -0700273 for k, v in ipv4_send_bytes.items():
Andreas Gerstmayrc64f4872018-07-06 14:59:07 +0200274 key = get_ipv4_session_key(k)
275 ipv4_throughput[key][0] = v.value
276 ipv4_send_bytes.clear()
Brendan Gregg60393ea2016-10-04 15:18:11 -0700277
Andreas Gerstmayrc64f4872018-07-06 14:59:07 +0200278 for k, v in ipv4_recv_bytes.items():
279 key = get_ipv4_session_key(k)
280 ipv4_throughput[key][1] = v.value
281 ipv4_recv_bytes.clear()
282
283 if ipv4_throughput:
xingfeng251003e49482022-03-17 13:07:16 +0800284 print("%-7s %-12s %-21s %-21s %6s %6s" % ("PID", "COMM",
Brendan Gregg60393ea2016-10-04 15:18:11 -0700285 "LADDR", "RADDR", "RX_KB", "TX_KB"))
286
287 # output
Andreas Gerstmayrc64f4872018-07-06 14:59:07 +0200288 for k, (send_bytes, recv_bytes) in sorted(ipv4_throughput.items(),
289 key=lambda kv: sum(kv[1]),
290 reverse=True):
xingfeng251003e49482022-03-17 13:07:16 +0800291 print("%-7d %-12.12s %-21s %-21s %6d %6d" % (k.pid,
eiffel-flaa6437e2021-12-18 19:21:13 +0100292 k.name,
Andreas Gerstmayrc64f4872018-07-06 14:59:07 +0200293 k.laddr + ":" + str(k.lport),
294 k.daddr + ":" + str(k.dport),
295 int(recv_bytes / 1024), int(send_bytes / 1024)))
Brendan Gregg60393ea2016-10-04 15:18:11 -0700296
297 # IPv6: build dict of all seen keys
Andreas Gerstmayrc64f4872018-07-06 14:59:07 +0200298 ipv6_throughput = defaultdict(lambda: [0, 0])
Brendan Gregg60393ea2016-10-04 15:18:11 -0700299 for k, v in ipv6_send_bytes.items():
Andreas Gerstmayrc64f4872018-07-06 14:59:07 +0200300 key = get_ipv6_session_key(k)
301 ipv6_throughput[key][0] = v.value
302 ipv6_send_bytes.clear()
Brendan Gregg60393ea2016-10-04 15:18:11 -0700303
Andreas Gerstmayrc64f4872018-07-06 14:59:07 +0200304 for k, v in ipv6_recv_bytes.items():
305 key = get_ipv6_session_key(k)
306 ipv6_throughput[key][1] = v.value
307 ipv6_recv_bytes.clear()
308
309 if ipv6_throughput:
Brendan Gregg60393ea2016-10-04 15:18:11 -0700310 # more than 80 chars, sadly.
xingfeng251003e49482022-03-17 13:07:16 +0800311 print("\n%-7s %-12s %-32s %-32s %6s %6s" % ("PID", "COMM",
Brendan Gregg60393ea2016-10-04 15:18:11 -0700312 "LADDR6", "RADDR6", "RX_KB", "TX_KB"))
313
314 # output
Andreas Gerstmayrc64f4872018-07-06 14:59:07 +0200315 for k, (send_bytes, recv_bytes) in sorted(ipv6_throughput.items(),
316 key=lambda kv: sum(kv[1]),
317 reverse=True):
xingfeng251003e49482022-03-17 13:07:16 +0800318 print("%-7d %-12.12s %-32s %-32s %6d %6d" % (k.pid,
eiffel-flaa6437e2021-12-18 19:21:13 +0100319 k.name,
Andreas Gerstmayrc64f4872018-07-06 14:59:07 +0200320 k.laddr + ":" + str(k.lport),
321 k.daddr + ":" + str(k.dport),
322 int(recv_bytes / 1024), int(send_bytes / 1024)))
Brendan Gregg60393ea2016-10-04 15:18:11 -0700323
Benjamin Poirier8e86b9e2017-07-27 16:07:06 -0700324 i += 1