blob: 1f4454cc0198f5ee33747bb7e4592b0f93788d3b [file] [log] [blame]
Brendan Gregg797c3ec2016-10-19 18:55:10 -07001#!/usr/bin/python
2# @lint-avoid-python-3-compatibility-imports
3#
4# tcplife Trace the lifespan of TCP sessions and summarize.
5# For Linux, uses BCC, BPF. Embedded C.
6#
7# USAGE: tcplife [-h] [-C] [-S] [-p PID] [interval [count]]
8#
Brendan Greggfd93dc02018-03-19 15:33:48 -07009# This uses the sock:inet_sock_set_state tracepoint if it exists (added to
10# Linux 4.16, and replacing the earlier tcp:tcp_set_state), else it uses
11# kernel dynamic tracing of tcp_set_state().
Brendan Gregg797c3ec2016-10-19 18:55:10 -070012#
13# While throughput counters are emitted, they are fetched in a low-overhead
14# manner: reading members of the tcp_info struct on TCP close. ie, we do not
15# trace send/receive.
16#
17# Copyright 2016 Netflix, Inc.
18# Licensed under the Apache License, Version 2.0 (the "License")
19#
20# IDEA: Julia Evans
21#
22# 18-Oct-2016 Brendan Gregg Created this.
Brendan Gregge023bc82017-12-29 22:46:27 -080023# 29-Dec-2017 " " Added tracepoint support.
Brendan Gregg797c3ec2016-10-19 18:55:10 -070024
25from __future__ import print_function
26from bcc import BPF
27import argparse
28from socket import inet_ntop, ntohs, AF_INET, AF_INET6
29from struct import pack
30import ctypes as ct
31from time import strftime
32
33# arguments
34examples = """examples:
35 ./tcplife # trace all TCP connect()s
36 ./tcplife -t # include time column (HH:MM:SS)
37 ./tcplife -w # wider colums (fit IPv6)
38 ./tcplife -stT # csv output, with times & timestamps
39 ./tcplife -p 181 # only trace PID 181
40 ./tcplife -L 80 # only trace local port 80
41 ./tcplife -L 80,81 # only trace local ports 80 and 81
42 ./tcplife -D 80 # only trace remote port 80
43"""
44parser = argparse.ArgumentParser(
45 description="Trace the lifespan of TCP sessions and summarize",
46 formatter_class=argparse.RawDescriptionHelpFormatter,
Nathan Scott1a197db2018-01-21 09:14:27 +110047 epilog=examples)
Brendan Gregg797c3ec2016-10-19 18:55:10 -070048parser.add_argument("-T", "--time", action="store_true",
49 help="include time column on output (HH:MM:SS)")
50parser.add_argument("-t", "--timestamp", action="store_true",
51 help="include timestamp on output (seconds)")
52parser.add_argument("-w", "--wide", action="store_true",
53 help="wide column output (fits IPv6 addresses)")
54parser.add_argument("-s", "--csv", action="store_true",
Edward Bettsfdf9b082017-10-10 21:13:28 +010055 help="comma separated values output")
Brendan Gregg797c3ec2016-10-19 18:55:10 -070056parser.add_argument("-p", "--pid",
57 help="trace this PID only")
58parser.add_argument("-L", "--localport",
59 help="comma-separated list of local ports to trace.")
60parser.add_argument("-D", "--remoteport",
61 help="comma-separated list of remote ports to trace.")
Nathan Scottf5fb9af2018-01-17 09:39:59 +110062parser.add_argument("--ebpf", action="store_true",
63 help=argparse.SUPPRESS)
Brendan Gregg797c3ec2016-10-19 18:55:10 -070064args = parser.parse_args()
65debug = 0
66
67# define BPF program
68bpf_text = """
69#include <uapi/linux/ptrace.h>
70#define KBUILD_MODNAME "foo"
71#include <linux/tcp.h>
72#include <net/sock.h>
73#include <bcc/proto.h>
74
75BPF_HASH(birth, struct sock *, u64);
76
77// separate data structs for ipv4 and ipv6
78struct ipv4_data_t {
79 // XXX: switch some to u32's when supported
80 u64 ts_us;
81 u64 pid;
82 u64 saddr;
83 u64 daddr;
84 u64 ports;
85 u64 rx_b;
86 u64 tx_b;
87 u64 span_us;
88 char task[TASK_COMM_LEN];
89};
90BPF_PERF_OUTPUT(ipv4_events);
91
92struct ipv6_data_t {
93 u64 ts_us;
94 u64 pid;
95 unsigned __int128 saddr;
96 unsigned __int128 daddr;
97 u64 ports;
98 u64 rx_b;
99 u64 tx_b;
100 u64 span_us;
101 char task[TASK_COMM_LEN];
102};
103BPF_PERF_OUTPUT(ipv6_events);
104
105struct id_t {
106 u32 pid;
107 char task[TASK_COMM_LEN];
108};
109BPF_HASH(whoami, struct sock *, struct id_t);
Brendan Gregge023bc82017-12-29 22:46:27 -0800110"""
Brendan Gregg797c3ec2016-10-19 18:55:10 -0700111
Brendan Gregge023bc82017-12-29 22:46:27 -0800112#
113# XXX: The following is temporary code for older kernels, Linux 4.14 and
Brendan Greggfd93dc02018-03-19 15:33:48 -0700114# older. It uses kprobes to instrument tcp_set_state(). On Linux 4.16 and
115# later, the sock:inet_sock_set_state tracepoint should be used instead, as
116# is done by the code that follows this. In the distant future (2021?), this
Brendan Gregge023bc82017-12-29 22:46:27 -0800117# kprobe code can be removed. This is why there is so much code
118# duplication: to make removal easier.
119#
120bpf_text_kprobe = """
Brendan Gregg797c3ec2016-10-19 18:55:10 -0700121int kprobe__tcp_set_state(struct pt_regs *ctx, struct sock *sk, int state)
122{
123 u32 pid = bpf_get_current_pid_tgid() >> 32;
124
125 // lport is either used in a filter here, or later
126 u16 lport = sk->__sk_common.skc_num;
127 FILTER_LPORT
128
129 // dport is either used in a filter here, or later
130 u16 dport = sk->__sk_common.skc_dport;
Paul Chaignon95b3d8c2018-05-30 17:02:06 +0200131 dport = ntohs(dport);
Brendan Gregg797c3ec2016-10-19 18:55:10 -0700132 FILTER_DPORT
133
134 /*
135 * This tool includes PID and comm context. It's best effort, and may
136 * be wrong in some situations. It currently works like this:
Brendan Gregg4fd7d322016-11-28 17:57:20 -0800137 * - record timestamp on any state < TCP_FIN_WAIT1
138 * - cache task context on:
139 * TCP_SYN_SENT: tracing from client
140 * TCP_LAST_ACK: client-closed from server
141 * - do output on TCP_CLOSE:
142 * fetch task context if cached, or use current task
143 */
Brendan Gregg797c3ec2016-10-19 18:55:10 -0700144
Brendan Gregg42d00a42016-11-30 16:55:45 -0800145 // capture birth time
146 if (state < TCP_FIN_WAIT1) {
147 /*
148 * Matching just ESTABLISHED may be sufficient, provided no code-path
149 * sets ESTABLISHED without a tcp_set_state() call. Until we know
150 * that for sure, match all early states to increase chances a
151 * timestamp is set.
152 * Note that this needs to be set before the PID filter later on,
153 * since the PID isn't reliable for these early stages, so we must
154 * save all timestamps and do the PID filter later when we can.
155 */
156 u64 ts = bpf_ktime_get_ns();
157 birth.update(&sk, &ts);
158 }
159
Brendan Gregg797c3ec2016-10-19 18:55:10 -0700160 // record PID & comm on SYN_SENT
Brendan Gregg4fd7d322016-11-28 17:57:20 -0800161 if (state == TCP_SYN_SENT || state == TCP_LAST_ACK) {
Brendan Gregg42d00a42016-11-30 16:55:45 -0800162 // now we can PID filter, both here and a little later on for CLOSE
Brendan Gregg797c3ec2016-10-19 18:55:10 -0700163 FILTER_PID
164 struct id_t me = {.pid = pid};
165 bpf_get_current_comm(&me.task, sizeof(me.task));
166 whoami.update(&sk, &me);
167 }
168
Brendan Gregg4fd7d322016-11-28 17:57:20 -0800169 if (state != TCP_CLOSE)
170 return 0;
171
Brendan Gregg797c3ec2016-10-19 18:55:10 -0700172 // calculate lifespan
173 u64 *tsp, delta_us;
174 tsp = birth.lookup(&sk);
175 if (tsp == 0) {
Brendan Gregg42d00a42016-11-30 16:55:45 -0800176 whoami.delete(&sk); // may not exist
177 return 0; // missed create
Brendan Gregg797c3ec2016-10-19 18:55:10 -0700178 }
179 delta_us = (bpf_ktime_get_ns() - *tsp) / 1000;
Brendan Gregg42d00a42016-11-30 16:55:45 -0800180 birth.delete(&sk);
181
182 // fetch possible cached data, and filter
183 struct id_t *mep;
184 mep = whoami.lookup(&sk);
185 if (mep != 0)
186 pid = mep->pid;
187 FILTER_PID
Brendan Gregg797c3ec2016-10-19 18:55:10 -0700188
189 // get throughput stats. see tcp_get_info().
190 u64 rx_b = 0, tx_b = 0, sport = 0;
191 struct tcp_sock *tp = (struct tcp_sock *)sk;
192 rx_b = tp->bytes_received;
193 tx_b = tp->bytes_acked;
194
195 u16 family = sk->__sk_common.skc_family;
196
197 if (family == AF_INET) {
198 struct ipv4_data_t data4 = {.span_us = delta_us,
199 .rx_b = rx_b, .tx_b = tx_b};
200 data4.ts_us = bpf_ktime_get_ns() / 1000;
201 data4.saddr = sk->__sk_common.skc_rcv_saddr;
202 data4.daddr = sk->__sk_common.skc_daddr;
203 // a workaround until data4 compiles with separate lport/dport
Brendan Gregg4fd7d322016-11-28 17:57:20 -0800204 data4.pid = pid;
Andreas Gerstmayr81de82c2018-06-11 18:56:01 +0200205 data4.ports = dport + ((0ULL + lport) << 32);
Brendan Gregg797c3ec2016-10-19 18:55:10 -0700206 if (mep == 0) {
Brendan Gregg797c3ec2016-10-19 18:55:10 -0700207 bpf_get_current_comm(&data4.task, sizeof(data4.task));
Brendan Gregg797c3ec2016-10-19 18:55:10 -0700208 } else {
Brendan Gregg797c3ec2016-10-19 18:55:10 -0700209 bpf_probe_read(&data4.task, sizeof(data4.task), (void *)mep->task);
210 }
211 ipv4_events.perf_submit(ctx, &data4, sizeof(data4));
212
213 } else /* 6 */ {
214 struct ipv6_data_t data6 = {.span_us = delta_us,
215 .rx_b = rx_b, .tx_b = tx_b};
216 data6.ts_us = bpf_ktime_get_ns() / 1000;
217 bpf_probe_read(&data6.saddr, sizeof(data6.saddr),
218 sk->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr32);
219 bpf_probe_read(&data6.daddr, sizeof(data6.daddr),
220 sk->__sk_common.skc_v6_daddr.in6_u.u6_addr32);
221 // a workaround until data6 compiles with separate lport/dport
Andreas Gerstmayr81de82c2018-06-11 18:56:01 +0200222 data6.ports = dport + ((0ULL + lport) << 32);
Brendan Gregg4fd7d322016-11-28 17:57:20 -0800223 data6.pid = pid;
Brendan Gregg797c3ec2016-10-19 18:55:10 -0700224 if (mep == 0) {
Brendan Gregg797c3ec2016-10-19 18:55:10 -0700225 bpf_get_current_comm(&data6.task, sizeof(data6.task));
226 } else {
Brendan Gregg797c3ec2016-10-19 18:55:10 -0700227 bpf_probe_read(&data6.task, sizeof(data6.task), (void *)mep->task);
228 }
229 ipv6_events.perf_submit(ctx, &data6, sizeof(data6));
230 }
231
Brendan Gregg797c3ec2016-10-19 18:55:10 -0700232 if (mep != 0)
233 whoami.delete(&sk);
234
235 return 0;
236}
237"""
238
Brendan Gregge023bc82017-12-29 22:46:27 -0800239bpf_text_tracepoint = """
Brendan Greggfd93dc02018-03-19 15:33:48 -0700240TRACEPOINT_PROBE(sock, inet_sock_set_state)
Brendan Gregge023bc82017-12-29 22:46:27 -0800241{
Brendan Greggfd93dc02018-03-19 15:33:48 -0700242 if (args->protocol != IPPROTO_TCP)
243 return 0;
244
Brendan Gregge023bc82017-12-29 22:46:27 -0800245 u32 pid = bpf_get_current_pid_tgid() >> 32;
Brendan Greggfd93dc02018-03-19 15:33:48 -0700246 // sk is mostly used as a UUID, and for two tcp stats:
Brendan Gregge023bc82017-12-29 22:46:27 -0800247 struct sock *sk = (struct sock *)args->skaddr;
248
249 // lport is either used in a filter here, or later
250 u16 lport = args->sport;
251 FILTER_LPORT
252
253 // dport is either used in a filter here, or later
254 u16 dport = args->dport;
255 FILTER_DPORT
256
257 /*
258 * This tool includes PID and comm context. It's best effort, and may
259 * be wrong in some situations. It currently works like this:
260 * - record timestamp on any state < TCP_FIN_WAIT1
261 * - cache task context on:
262 * TCP_SYN_SENT: tracing from client
263 * TCP_LAST_ACK: client-closed from server
264 * - do output on TCP_CLOSE:
265 * fetch task context if cached, or use current task
266 */
267
268 // capture birth time
269 if (args->newstate < TCP_FIN_WAIT1) {
270 /*
271 * Matching just ESTABLISHED may be sufficient, provided no code-path
272 * sets ESTABLISHED without a tcp_set_state() call. Until we know
273 * that for sure, match all early states to increase chances a
274 * timestamp is set.
275 * Note that this needs to be set before the PID filter later on,
276 * since the PID isn't reliable for these early stages, so we must
277 * save all timestamps and do the PID filter later when we can.
278 */
279 u64 ts = bpf_ktime_get_ns();
280 birth.update(&sk, &ts);
281 }
282
283 // record PID & comm on SYN_SENT
284 if (args->newstate == TCP_SYN_SENT || args->newstate == TCP_LAST_ACK) {
285 // now we can PID filter, both here and a little later on for CLOSE
286 FILTER_PID
287 struct id_t me = {.pid = pid};
288 bpf_get_current_comm(&me.task, sizeof(me.task));
289 whoami.update(&sk, &me);
290 }
291
292 if (args->newstate != TCP_CLOSE)
293 return 0;
294
295 // calculate lifespan
296 u64 *tsp, delta_us;
297 tsp = birth.lookup(&sk);
298 if (tsp == 0) {
299 whoami.delete(&sk); // may not exist
300 return 0; // missed create
301 }
302 delta_us = (bpf_ktime_get_ns() - *tsp) / 1000;
303 birth.delete(&sk);
304
305 // fetch possible cached data, and filter
306 struct id_t *mep;
307 mep = whoami.lookup(&sk);
308 if (mep != 0)
309 pid = mep->pid;
310 FILTER_PID
311
312 // get throughput stats. see tcp_get_info().
313 u64 rx_b = 0, tx_b = 0, sport = 0;
314 struct tcp_sock *tp = (struct tcp_sock *)sk;
Yonghong Songcb136c12018-05-24 04:00:19 -0700315 rx_b = tp->bytes_received;
316 tx_b = tp->bytes_acked;
Brendan Gregge023bc82017-12-29 22:46:27 -0800317
Brendan Greggfd93dc02018-03-19 15:33:48 -0700318 if (args->family == AF_INET) {
Brendan Gregge023bc82017-12-29 22:46:27 -0800319 struct ipv4_data_t data4 = {.span_us = delta_us,
320 .rx_b = rx_b, .tx_b = tx_b};
321 data4.ts_us = bpf_ktime_get_ns() / 1000;
322 bpf_probe_read(&data4.saddr, sizeof(u32), args->saddr);
323 bpf_probe_read(&data4.daddr, sizeof(u32), args->daddr);
324 // a workaround until data4 compiles with separate lport/dport
325 data4.ports = dport + ((0ULL + lport) << 32);
326 data4.pid = pid;
327
328 if (mep == 0) {
329 bpf_get_current_comm(&data4.task, sizeof(data4.task));
330 } else {
331 bpf_probe_read(&data4.task, sizeof(data4.task), (void *)mep->task);
332 }
333 ipv4_events.perf_submit(args, &data4, sizeof(data4));
334
335 } else /* 6 */ {
336 struct ipv6_data_t data6 = {.span_us = delta_us,
337 .rx_b = rx_b, .tx_b = tx_b};
338 data6.ts_us = bpf_ktime_get_ns() / 1000;
339 bpf_probe_read(&data6.saddr, sizeof(data6.saddr), args->saddr_v6);
340 bpf_probe_read(&data6.daddr, sizeof(data6.daddr), args->saddr_v6);
341 // a workaround until data6 compiles with separate lport/dport
342 data6.ports = dport + ((0ULL + lport) << 32);
343 data6.pid = pid;
344 if (mep == 0) {
345 bpf_get_current_comm(&data6.task, sizeof(data6.task));
346 } else {
347 bpf_probe_read(&data6.task, sizeof(data6.task), (void *)mep->task);
348 }
349 ipv6_events.perf_submit(args, &data6, sizeof(data6));
350 }
351
352 if (mep != 0)
353 whoami.delete(&sk);
354
355 return 0;
356}
357"""
358
Brendan Greggfd93dc02018-03-19 15:33:48 -0700359if (BPF.tracepoint_exists("sock", "inet_sock_set_state")):
Brendan Gregge023bc82017-12-29 22:46:27 -0800360 bpf_text += bpf_text_tracepoint
361else:
362 bpf_text += bpf_text_kprobe
363
Brendan Gregg797c3ec2016-10-19 18:55:10 -0700364# code substitutions
365if args.pid:
366 bpf_text = bpf_text.replace('FILTER_PID',
367 'if (pid != %s) { return 0; }' % args.pid)
368if args.remoteport:
369 dports = [int(dport) for dport in args.remoteport.split(',')]
Paul Chaignon95b3d8c2018-05-30 17:02:06 +0200370 dports_if = ' && '.join(['dport != %d' % dport for dport in dports])
Brendan Gregg797c3ec2016-10-19 18:55:10 -0700371 bpf_text = bpf_text.replace('FILTER_DPORT',
372 'if (%s) { birth.delete(&sk); return 0; }' % dports_if)
373if args.localport:
374 lports = [int(lport) for lport in args.localport.split(',')]
375 lports_if = ' && '.join(['lport != %d' % lport for lport in lports])
376 bpf_text = bpf_text.replace('FILTER_LPORT',
377 'if (%s) { birth.delete(&sk); return 0; }' % lports_if)
378bpf_text = bpf_text.replace('FILTER_PID', '')
379bpf_text = bpf_text.replace('FILTER_DPORT', '')
380bpf_text = bpf_text.replace('FILTER_LPORT', '')
381
Nathan Scottca4ba552018-01-16 11:02:58 +1100382if debug or args.ebpf:
Brendan Gregg797c3ec2016-10-19 18:55:10 -0700383 print(bpf_text)
Nathan Scottca4ba552018-01-16 11:02:58 +1100384 if args.ebpf:
385 exit()
Brendan Gregg797c3ec2016-10-19 18:55:10 -0700386
387# event data
388TASK_COMM_LEN = 16 # linux/sched.h
389
390class Data_ipv4(ct.Structure):
391 _fields_ = [
392 ("ts_us", ct.c_ulonglong),
393 ("pid", ct.c_ulonglong),
394 ("saddr", ct.c_ulonglong),
395 ("daddr", ct.c_ulonglong),
396 ("ports", ct.c_ulonglong),
397 ("rx_b", ct.c_ulonglong),
398 ("tx_b", ct.c_ulonglong),
399 ("span_us", ct.c_ulonglong),
400 ("task", ct.c_char * TASK_COMM_LEN)
401 ]
402
403class Data_ipv6(ct.Structure):
404 _fields_ = [
405 ("ts_us", ct.c_ulonglong),
406 ("pid", ct.c_ulonglong),
407 ("saddr", (ct.c_ulonglong * 2)),
408 ("daddr", (ct.c_ulonglong * 2)),
409 ("ports", ct.c_ulonglong),
410 ("rx_b", ct.c_ulonglong),
411 ("tx_b", ct.c_ulonglong),
412 ("span_us", ct.c_ulonglong),
413 ("task", ct.c_char * TASK_COMM_LEN)
414 ]
415
416#
417# Setup output formats
418#
419# Don't change the default output (next 2 lines): this fits in 80 chars. I
420# know it doesn't have NS or UIDs etc. I know. If you really, really, really
421# need to add columns, columns that solve real actual problems, I'd start by
422# adding an extended mode (-x) to included those columns.
423#
424header_string = "%-5s %-10.10s %s%-15s %-5s %-15s %-5s %5s %5s %s"
425format_string = "%-5d %-10.10s %s%-15s %-5d %-15s %-5d %5d %5d %.2f"
426if args.wide:
427 header_string = "%-5s %-16.16s %-2s %-26s %-5s %-26s %-5s %6s %6s %s"
428 format_string = "%-5d %-16.16s %-2s %-26s %-5s %-26s %-5d %6d %6d %.2f"
429if args.csv:
430 header_string = "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s"
431 format_string = "%d,%s,%s,%s,%s,%s,%d,%d,%d,%.2f"
432
433# process event
434def print_ipv4_event(cpu, data, size):
435 event = ct.cast(data, ct.POINTER(Data_ipv4)).contents
436 global start_ts
437 if args.time:
438 if args.csv:
439 print("%s," % strftime("%H:%M:%S"), end="")
440 else:
441 print("%-8s " % strftime("%H:%M:%S"), end="")
442 if args.timestamp:
443 if start_ts == 0:
444 start_ts = event.ts_us
445 delta_s = (float(event.ts_us) - start_ts) / 1000000
446 if args.csv:
447 print("%.6f," % delta_s, end="")
448 else:
449 print("%-9.6f " % delta_s, end="")
Rafael F78948e42017-03-26 14:54:25 +0200450 print(format_string % (event.pid, event.task.decode(),
Brendan Gregg797c3ec2016-10-19 18:55:10 -0700451 "4" if args.wide or args.csv else "",
452 inet_ntop(AF_INET, pack("I", event.saddr)), event.ports >> 32,
453 inet_ntop(AF_INET, pack("I", event.daddr)), event.ports & 0xffffffff,
454 event.tx_b / 1024, event.rx_b / 1024, float(event.span_us) / 1000))
455
456def print_ipv6_event(cpu, data, size):
457 event = ct.cast(data, ct.POINTER(Data_ipv6)).contents
458 global start_ts
459 if args.time:
460 if args.csv:
461 print("%s," % strftime("%H:%M:%S"), end="")
462 else:
463 print("%-8s " % strftime("%H:%M:%S"), end="")
464 if args.timestamp:
465 if start_ts == 0:
466 start_ts = event.ts_us
467 delta_s = (float(event.ts_us) - start_ts) / 1000000
468 if args.csv:
469 print("%.6f," % delta_s, end="")
470 else:
471 print("%-9.6f " % delta_s, end="")
Rafael F78948e42017-03-26 14:54:25 +0200472 print(format_string % (event.pid, event.task.decode(),
Brendan Gregg797c3ec2016-10-19 18:55:10 -0700473 "6" if args.wide or args.csv else "",
474 inet_ntop(AF_INET6, event.saddr), event.ports >> 32,
475 inet_ntop(AF_INET6, event.daddr), event.ports & 0xffffffff,
476 event.tx_b / 1024, event.rx_b / 1024, float(event.span_us) / 1000))
477
478# initialize BPF
479b = BPF(text=bpf_text)
480
481# header
482if args.time:
483 if args.csv:
484 print("%s," % ("TIME"), end="")
485 else:
486 print("%-8s " % ("TIME"), end="")
487if args.timestamp:
488 if args.csv:
489 print("%s," % ("TIME(s)"), end="")
490 else:
491 print("%-9s " % ("TIME(s)"), end="")
492print(header_string % ("PID", "COMM",
493 "IP" if args.wide or args.csv else "", "LADDR",
494 "LPORT", "RADDR", "RPORT", "TX_KB", "RX_KB", "MS"))
495
496start_ts = 0
497
498# read events
Mark Drayton5f5687e2017-02-20 18:13:03 +0000499b["ipv4_events"].open_perf_buffer(print_ipv4_event, page_cnt=64)
500b["ipv6_events"].open_perf_buffer(print_ipv6_event, page_cnt=64)
Brendan Gregg797c3ec2016-10-19 18:55:10 -0700501while 1:
Teng Qindbf00292018-02-28 21:47:50 -0800502 b.perf_buffer_poll()