Demonstrations of stackcount, the Linux eBPF/bcc version. This program traces functions and frequency counts them with their entire stack trace, summarized in-kernel for efficiency. For example, counting stack traces that led to the submit_bio() kernel function, which creates block device I/O: # ./stackcount submit_bio Tracing 1 functions for "submit_bio"... Hit Ctrl-C to end. ^C submit_bio ext4_writepages do_writepages __filemap_fdatawrite_range filemap_flush ext4_alloc_da_blocks ext4_release_file __fput ____fput task_work_run exit_to_usermode_loop syscall_return_slowpath entry_SYSCALL_64_fastpath [unknown] [unknown] tar [15069] 5 submit_bio ext4_bio_write_page mpage_submit_page mpage_map_and_submit_buffers ext4_writepages do_writepages __filemap_fdatawrite_range filemap_flush ext4_alloc_da_blocks ext4_release_file __fput ____fput task_work_run exit_to_usermode_loop syscall_return_slowpath entry_SYSCALL_64_fastpath [unknown] [unknown] tar [15069] 15 submit_bio ext4_readpages __do_page_cache_readahead ondemand_readahead page_cache_async_readahead generic_file_read_iter __vfs_read vfs_read sys_read entry_SYSCALL_64_fastpath [unknown] tar [15069] 113 Detaching... The output shows unique stack traces, in order from leaf (on-CPU) to root, followed by their occurrence count. The last stack trace in the above output shows syscall handling, sys_read(), vfs_read(), and then "readahead" functions: looks like an application issued file read has triggered read ahead. The application can be seen after the stack trace, in this case, "tar [15069]" for the "tar" command, PID 15069. The order of printed stack traces is from least to most frequent. The most frequent in this case, the ext4_rename() stack, was taken 113 times during tracing. The "[unknown]" frames are from user-level, since this simple workload is the tar command, which apparently has been compiled without frame pointers. It's a common compiler optimization, but it breaks frame pointer-based stack walkers. Similar broken stacks will be seen by other profilers and debuggers that use frame pointers. Hopefully your application preserves them so that the user-level stack trace is visible. So how does one get frame pointers, if your application doesn't have them to start with? For the current bcc (until it supports other stack walkers), you need to be running a application binaries that preserves frame pointers, eg, using gcc's -fno-omit-frame-pointer. That's about all I'll say here: this is a big topic that is not bcc/BPF specific. It can be useful to trace the path to submit_bio to explain unusual rates of disk IOPS. These could have in-kernel origins (eg, background scrub). Now adding the -d option to delimit kernel and user stacks: # ./stackcount -d submit_bio Tracing 1 functions for "submit_bio"... Hit Ctrl-C to end. ^C submit_bio submit_bh journal_submit_commit_record jbd2_journal_commit_transaction kjournald2 kthread ret_from_fork -- jbd2/xvda1-8 [405] 1 submit_bio submit_bh jbd2_journal_commit_transaction kjournald2 kthread ret_from_fork -- jbd2/xvda1-8 [405] 2 submit_bio ext4_writepages do_writepages __filemap_fdatawrite_range filemap_flush ext4_alloc_da_blocks ext4_release_file __fput ____fput task_work_run exit_to_usermode_loop syscall_return_slowpath entry_SYSCALL_64_fastpath -- [unknown] [unknown] tar [15187] 5 submit_bio ext4_bio_write_page mpage_submit_page mpage_map_and_submit_buffers ext4_writepages do_writepages __filemap_fdatawrite_range filemap_flush ext4_alloc_da_blocks ext4_release_file __fput ____fput task_work_run exit_to_usermode_loop syscall_return_slowpath entry_SYSCALL_64_fastpath -- [unknown] [unknown] tar [15187] 15 submit_bio ext4_readpages __do_page_cache_readahead ondemand_readahead page_cache_async_readahead generic_file_read_iter __vfs_read vfs_read sys_read entry_SYSCALL_64_fastpath -- [unknown] [unknown] [unknown] tar [15187] 171 Detaching... A "--" is printed between the kernel and user stacks. As a different example, here is the kernel function hrtimer_init_sleeper(): # ./stackcount.py -d hrtimer_init_sleeper Tracing 1 functions for "hrtimer_init_sleeper"... Hit Ctrl-C to end. ^C hrtimer_init_sleeper do_futex SyS_futex entry_SYSCALL_64_fastpath -- [unknown] containerd [16020] 1 hrtimer_init_sleeper do_futex SyS_futex entry_SYSCALL_64_fastpath -- __pthread_cond_timedwait Monitor::IWait(Thread*, long) Monitor::wait(bool, long, bool) CompileQueue::get() CompileBroker::compiler_thread_loop() JavaThread::thread_main_inner() JavaThread::run() java_start(Thread*) start_thread java [4996] 1 hrtimer_init_sleeper do_futex SyS_futex entry_SYSCALL_64_fastpath -- [unknown] [unknown] containerd [16020] 1 hrtimer_init_sleeper do_futex SyS_futex entry_SYSCALL_64_fastpath -- __pthread_cond_timedwait VMThread::loop() VMThread::run() java_start(Thread*) start_thread java [4996] 3 hrtimer_init_sleeper do_futex SyS_futex entry_SYSCALL_64_fastpath -- [unknown] dockerd [16008] 4 hrtimer_init_sleeper do_futex SyS_futex entry_SYSCALL_64_fastpath -- [unknown] [unknown] dockerd [16008] 4 hrtimer_init_sleeper do_futex SyS_futex entry_SYSCALL_64_fastpath -- __pthread_cond_timedwait Lio/netty/util/ThreadDeathWatcher$Watcher;::run Interpreter Interpreter call_stub JavaCalls::call_helper(JavaValue*, methodHandle*, JavaCallArguments*, Thread*) JavaCalls::call_virtual(JavaValue*, KlassHandle, Symbol*, Symbol*, JavaCallArguments*, Thread*) JavaCalls::call_virtual(JavaValue*, Handle, KlassHandle, Symbol*, Symbol*, Thread*) thread_entry(JavaThread*, Thread*) JavaThread::thread_main_inner() JavaThread::run() java_start(Thread*) start_thread java [4996] 4 hrtimer_init_sleeper do_futex SyS_futex entry_SYSCALL_64_fastpath -- __pthread_cond_timedwait clock_gettime [unknown] java [4996] 79 Detaching... I was just trying to find a more interesting example. This output includes some Java stacks where user-level has been walked correctly (even includes a JIT symbol translation). dockerd and containerd don't have frame pointers (grumble), but Java does (which is running with -XX:+PreserveFramePointer). Here's another kernel function, ip_output(): # ./stackcount.py -d ip_output Tracing 1 functions for "ip_output"... Hit Ctrl-C to end. ^C ip_output ip_queue_xmit tcp_transmit_skb tcp_write_xmit __tcp_push_pending_frames tcp_push tcp_sendmsg inet_sendmsg sock_sendmsg sock_write_iter __vfs_write vfs_write SyS_write entry_SYSCALL_64_fastpath -- __write_nocancel [unknown] sshd [15015] 5 ip_output ip_queue_xmit tcp_transmit_skb tcp_write_xmit __tcp_push_pending_frames tcp_push tcp_sendmsg inet_sendmsg sock_sendmsg sock_write_iter __vfs_write vfs_write SyS_write entry_SYSCALL_64_fastpath -- __write_nocancel [unknown] [unknown] sshd [8234] 5 ip_output ip_queue_xmit tcp_transmit_skb tcp_write_xmit __tcp_push_pending_frames tcp_push tcp_sendmsg inet_sendmsg sock_sendmsg sock_write_iter __vfs_write vfs_write SyS_write entry_SYSCALL_64_fastpath -- __write_nocancel sshd [15015] 7 Detaching... This time just sshd is triggering ip_output() calls. Watch what happens if I filter on kernel stacks only (-K) for ip_output(): # ./stackcount.py -K ip_output Tracing 1 functions for "ip_output"... Hit Ctrl-C to end. ^C ip_output ip_queue_xmit tcp_transmit_skb tcp_write_xmit __tcp_push_pending_frames tcp_push tcp_sendmsg inet_sendmsg sock_sendmsg sock_write_iter __vfs_write vfs_write SyS_write entry_SYSCALL_64_fastpath 13 Detaching... They have grouped together as a single unique stack, since the kernel part was the same. Here is just the user stacks, fetched during the kernel function ip_output(): # ./stackcount.py -U ip_output Tracing 1 functions for "ip_output"... Hit Ctrl-C to end. ^C [unknown] snmpd [1645] 1 __write_nocancel [unknown] [unknown] sshd [8234] 3 __write_nocancel sshd [15015] 4 I should really run a custom sshd with frame pointers so we can see its stack trace... User-space functions can also be traced if a library name is provided. For example, to quickly identify code locations that allocate heap memory for PID 4902 (using -p), by tracing malloc from libc ("c:malloc"): # ./stackcount -p 4902 c:malloc Tracing 1 functions for "malloc"... Hit Ctrl-C to end. ^C malloc rbtree_new main [unknown] 12 malloc _rbtree_node_new_internal _rbtree_node_insert rbtree_insert main [unknown] 1189 Detaching... Kernel stacks are absent as this didn't enter kernel code. Note that user-space uses of stackcount can be somewhat more limited because a lot of user-space libraries and binaries are compiled without frame-pointers as discussed earlier (the -fomit-frame-pointer compiler default) or are used without debuginfo. In addition to kernel and user-space functions, kernel tracepoints and USDT tracepoints are also supported. For example, to determine where threads are being created in a particular process, use the pthread_create USDT tracepoint: # ./stackcount -p $(pidof parprimes) u:pthread:pthread_create Tracing 1 functions for "u:pthread:pthread_create"... Hit Ctrl-C to end. ^C parprimes [11923] pthread_create@@GLIBC_2.2.5 main __libc_start_main [unknown] 7 You can use "readelf -n file" to see if it has USDT tracepoints. Similarly, to determine where context switching is happening in the kernel, use the sched:sched_switch kernel tracepoint: # ./stackcount t:sched:sched_switch __schedule schedule worker_thread kthread ret_from_fork kworker/0:2 [25482] 1 __schedule schedule schedule_hrtimeout_range_clock schedule_hrtimeout_range ep_poll SyS_epoll_wait entry_SYSCALL_64_fastpath epoll_wait Lsun/nio/ch/SelectorImpl;::lockAndDoSelect Lsun/nio/ch/SelectorImpl;::select Lio/netty/channel/nio/NioEventLoop;::select Lio/netty/channel/nio/NioEventLoop;::run Interpreter Interpreter call_stub JavaCalls::call_helper(JavaValue*, methodHandle*, JavaCallArguments*, Thread*) JavaCalls::call_virtual(JavaValue*, KlassHandle, Symbol*, Symbol*, JavaCallArguments*, Thread*) JavaCalls::call_virtual(JavaValue*, Handle, KlassHandle, Symbol*, Symbol*, Thread*) thread_entry(JavaThread*, Thread*) JavaThread::thread_main_inner() JavaThread::run() java_start(Thread*) start_thread java [4996] 1 ... (omitted for brevity) __schedule schedule schedule_preempt_disabled cpu_startup_entry xen_play_dead arch_cpu_idle_dead cpu_startup_entry cpu_bringup_and_idle swapper/1 [0] 289 A -i option can be used to set an output interval, and -T to include a timestamp. For example: # ./stackcount.py -Tdi 1 submit_bio Tracing 1 functions for "submit_bio"... Hit Ctrl-C to end. 06:05:13 06:05:14 submit_bio xfs_do_writepage write_cache_pages xfs_vm_writepages do_writepages __writeback_single_inode writeback_sb_inodes __writeback_inodes_wb wb_writeback wb_workfn process_one_work worker_thread kthread ret_from_fork -- kworker/u16:1 [15686] 1 submit_bio process_one_work worker_thread kthread ret_from_fork -- kworker/u16:0 [16007] 1 submit_bio xfs_buf_submit xlog_bdstrat xlog_sync xlog_state_release_iclog _xfs_log_force xfs_log_force xfs_fs_sync_fs sync_fs_one_sb iterate_supers sys_sync entry_SYSCALL_64_fastpath -- [unknown] sync [16039] 1 submit_bio submit_bh journal_submit_commit_record jbd2_journal_commit_transaction kjournald2 kthread ret_from_fork -- jbd2/xvda1-8 [405] 1 submit_bio process_one_work worker_thread kthread ret_from_fork -- kworker/0:2 [25482] 2 submit_bio ext4_writepages do_writepages __writeback_single_inode writeback_sb_inodes __writeback_inodes_wb wb_writeback wb_workfn process_one_work worker_thread kthread ret_from_fork -- kworker/u16:0 [16007] 4 submit_bio xfs_vm_writepages do_writepages __writeback_single_inode writeback_sb_inodes __writeback_inodes_wb wb_writeback wb_workfn process_one_work worker_thread kthread ret_from_fork -- kworker/u16:1 [15686] 5 submit_bio __block_write_full_page block_write_full_page blkdev_writepage __writepage write_cache_pages generic_writepages blkdev_writepages do_writepages __filemap_fdatawrite_range filemap_fdatawrite fdatawrite_one_bdev iterate_bdevs sys_sync entry_SYSCALL_64_fastpath -- [unknown] sync [16039] 7 submit_bio submit_bh jbd2_journal_commit_transaction kjournald2 kthread ret_from_fork -- jbd2/xvda1-8 [405] 8 submit_bio ext4_bio_write_page mpage_submit_page mpage_map_and_submit_buffers ext4_writepages do_writepages __writeback_single_inode writeback_sb_inodes __writeback_inodes_wb wb_writeback wb_workfn process_one_work worker_thread kthread ret_from_fork -- kworker/u16:0 [16007] 8 submit_bio __block_write_full_page block_write_full_page blkdev_writepage __writepage write_cache_pages generic_writepages blkdev_writepages do_writepages __writeback_single_inode writeback_sb_inodes __writeback_inodes_wb wb_writeback wb_workfn process_one_work worker_thread kthread ret_from_fork -- kworker/u16:0 [16007] 60 06:05:15 06:05:16 Detaching... This only included output for the 06:05:14 interval. The other internals did not span block device I/O. The -s output prints the return instruction offset for each function (aka symbol offset). Eg: # ./stackcount.py -s tcp_sendmsg Tracing 1 functions for "tcp_sendmsg"... Hit Ctrl-C to end. ^C tcp_sendmsg+0x1 sock_sendmsg+0x38 sock_write_iter+0x85 __vfs_write+0xe3 vfs_write+0xb8 SyS_write+0x55 entry_SYSCALL_64_fastpath+0x1e __write_nocancel+0x7 sshd [15015] 3 tcp_sendmsg+0x1 sock_sendmsg+0x38 sock_write_iter+0x85 __vfs_write+0xe3 vfs_write+0xb8 SyS_write+0x55 entry_SYSCALL_64_fastpath+0x1e __write_nocancel+0x7 sshd [8234] 3 Detaching... If it wasn't clear how one function called another, knowing the instruction offset can help you locate the lines of code from a disassembly dump. The -v output is verbose, and shows raw addresses: ./stackcount.py -v tcp_sendmsg Tracing 1 functions for "tcp_sendmsg"... Hit Ctrl-C to end. ^C ffffffff817b05c1 tcp_sendmsg ffffffff8173ea48 sock_sendmsg ffffffff8173eae5 sock_write_iter ffffffff81232b33 __vfs_write ffffffff812331b8 vfs_write ffffffff81234625 SyS_write ffffffff818739bb entry_SYSCALL_64_fastpath 7f120511e6e0 __write_nocancel sshd [8234] 3 ffffffff817b05c1 tcp_sendmsg ffffffff8173ea48 sock_sendmsg ffffffff8173eae5 sock_write_iter ffffffff81232b33 __vfs_write ffffffff812331b8 vfs_write ffffffff81234625 SyS_write ffffffff818739bb entry_SYSCALL_64_fastpath 7f919c5a26e0 __write_nocancel sshd [15015] 11 Detaching... A wildcard can also be used. Eg, all functions beginning with "tcp_send", kernel stacks only (-K) with offsets (-s): # ./stackcount -Ks 'tcp_send*' Tracing 14 functions for "tcp_send*"... Hit Ctrl-C to end. ^C tcp_send_delayed_ack0x1 tcp_rcv_established0x3b1 tcp_v4_do_rcv0x130 tcp_v4_rcv0x8e0 ip_local_deliver_finish0x9f ip_local_deliver0x51 ip_rcv_finish0x8a ip_rcv0x29d __netif_receive_skb_core0x637 __netif_receive_skb0x18 netif_receive_skb_internal0x23 1 tcp_send_delayed_ack0x1 tcp_rcv_established0x222 tcp_v4_do_rcv0x130 tcp_v4_rcv0x8e0 ip_local_deliver_finish0x9f ip_local_deliver0x51 ip_rcv_finish0x8a ip_rcv0x29d __netif_receive_skb_core0x637 __netif_receive_skb0x18 netif_receive_skb_internal0x23 4 tcp_send_mss0x1 inet_sendmsg0x67 sock_sendmsg0x38 sock_write_iter0x78 __vfs_write0xaa vfs_write0xa9 sys_write0x46 entry_SYSCALL_64_fastpath0x16 7 tcp_sendmsg0x1 sock_sendmsg0x38 sock_write_iter0x78 __vfs_write0xaa vfs_write0xa9 sys_write0x46 entry_SYSCALL_64_fastpath0x16 7 Detaching... Use -r to allow regular expressions. The -f option will emit folded output, which can be used as input to other tools including flame graphs. For example, with delimiters as well: # ./stackcount.py -df t:sched:sched_switch ^Csnmp-pass;[unknown];[unknown];[unknown];[unknown];[unknown];-;entry_SYSCALL_64_fastpath;SyS_select;core_sys_select;do_select;poll_schedule_timeout;schedule_hrtimeout_range;schedule_hrtimeout_range_clock;schedule;__schedule 1 kworker/7:0;-;ret_from_fork;kthread;worker_thread;schedule;__schedule 1 watchdog/0;-;ret_from_fork;kthread;smpboot_thread_fn;schedule;__schedule 1 snmp-pass;[unknown];[unknown];[unknown];[unknown];[unknown];-;entry_SYSCALL_64_fastpath;SyS_select;core_sys_select;do_select;poll_schedule_timeout;schedule_hrtimeout_range;schedule_hrtimeout_range_clock;schedule;__schedule 1 svscan;[unknown];-;entry_SYSCALL_64_fastpath;SyS_nanosleep;hrtimer_nanosleep;do_nanosleep;schedule;__schedule 1 python;[unknown];__select_nocancel;-;entry_SYSCALL_64_fastpath;SyS_select;core_sys_select;do_select;poll_schedule_timeout;schedule_hrtimeout_range;schedule_hrtimeout_range_clock;schedule;__schedule 1 kworker/2:0;-;ret_from_fork;kthread;worker_thread;schedule;__schedule 1 [...] Flame graphs visualize stack traces. For information about them and links to open source software, see http://www.brendangregg.com/flamegraphs.html . This folded output can be piped directly into flamegraph.pl (the Perl version). USAGE message: # ./stackcount -h usage: stackcount [-h] [-p PID] [-i INTERVAL] [-D DURATION] [-T] [-r] [-s] [-P] [-K] [-U] [-v] [-d] [-f] [--debug] pattern Count events and their stack traces positional arguments: pattern search expression for events optional arguments: -h, --help show this help message and exit -p PID, --pid PID trace this PID only -i INTERVAL, --interval INTERVAL summary interval, seconds -D DURATION, --duration DURATION total duration of trace, seconds -T, --timestamp include timestamp on output -r, --regexp use regular expressions. Default is "*" wildcards only. -s, --offset show address offsets -P, --perpid display stacks separately for each process -K, --kernel-stacks-only kernel stack only -U, --user-stacks-only user stack only -v, --verbose show raw addresses -d, --delimited insert delimiter between kernel/user stacks -f, --folded output folded format --debug print BPF program before starting (for debugging purposes) examples: ./stackcount submit_bio # count kernel stack traces for submit_bio ./stackcount -d ip_output # include a user/kernel stack delimiter ./stackcount -s ip_output # show symbol offsets ./stackcount -sv ip_output # show offsets and raw addresses (verbose) ./stackcount 'tcp_send*' # count stacks for funcs matching tcp_send* ./stackcount -r '^tcp_send.*' # same as above, using regular expressions ./stackcount -Ti 5 ip_output # output every 5 seconds, with timestamps ./stackcount -p 185 ip_output # count ip_output stacks for PID 185 only ./stackcount -p 185 c:malloc # count stacks for malloc in PID 185 ./stackcount t:sched:sched_fork # count stacks for sched_fork tracepoint ./stackcount -p 185 u:node:* # count stacks for all USDT probes in node ./stackcount -K t:sched:sched_switch # kernel stacks only ./stackcount -U t:sched:sched_switch # user stacks only