go-ftrace is an bpf(2)-based ftrace(1)-like function graph tracer for Golang processes.
Limits: for now, only support following cases
- OS: Linux, with support for bpf(2) and uprobe
- Arch: x86-64 little endian
- Binary: go ELF executable, non-stripped, built with non-PIE mode, ELF sections .symtab, .(z)debug_info are required
examples
provide two examples to show how to use go-ftrace.
Check examples/trace_funcs
, try following tracing tests:
example: trace a specific function: "main.add":
ftrace -u main.add ./main
example: trace all functions like main.add*:
ftrace -u 'main.add*' ./main
example: trace all functions like main.add* or main.minus*:
ftrace -u 'main.add*' -u 'main.minus*' ./main
example: trace a specific function and include runtime.chan* builtins:
ftrace -u 'main.add' -u 'runtime.chan*' ./main
example: trace a specific method of specific type:
ftrace -u 'main.(*Student).String ./main
Check examples/trace_funcs_arguments
, try following tracing tests:
example: trace a specific method of specific type, and fetch its receiver argument:
ftrace -u 'main.(*Student).String' ./main \
'main.(*Student).String(s.name=(*+0(%ax)):c64, s.name.len=(+8(%ax)):s64, s.age=(+16(%ax)):s64)'
example: trace a specific method of specific type, and fetch its arguments list:
ftrace -u 'main.(*Student).BuyBook' ./main \
'main.(*Student).BuyBook(s.book=(+0(%bx)):c128, s.book.len=(%cx):s64, s.num=(%di):s64)'
ps:
Makefile
is provided, you can runmake <target>
to quickly test it.And tracing by ftrace can be done either before or after launching ./main, both approaches will work.
install into your $GOBIN or $GOPATH/bin, please add $GOBIN, $GOPATH/bin to your PATH
go install github.com/hitzhangjie/go-ftrace/cmd/ftrace@latest
bpf tool require special permission, so we need run ftrace as root, like sudo ftrace ...
,
and we must make sure ftrace is searchable by sudo, so link it to the searchpath by sudo
sudo ln -s ~/go/bin/ftrace /usr/sbin/
then we can run it with sudo:
sudo ftrace -u 'go.etcd.io/etcd/client/v3/concurrency.(*Mutex).tryAcquire' ./a.out
Also, Makefile
is provided, run make && make install
is enough.
- Wall time profiling;
- Execution flow observing;
Here's an example when tracing examples/main.go
, here's the code snippet:
func main() {
for {
doSomething()
}
}
...
func doSomething() {
add(1, 2)
minus(1, 2)
s := &Student{"zhang", 100}
fmt.Printf("student: %s\n", s)
time.Sleep(time.Second)
}
if we want to observing the details of doSomething
, we can trace like ths:
sudo ftrace -u 'main.*' -u 'fmt.Print*' ./main \
'main.(*Student).String(s.name=(*+0(%ax)):c64, s.name.len=(+8(%ax)):s64, s.age=(+16(%ax)):s64)'
ftrace will output the details:
$ sudo ftrace -u 'main.*' -u 'fmt.Print*' ./main 'main.(*Student).String(s.name=(*+0(%ax)):c64, s.name.len=(+8(%ax)):s64, s.age=(+16(%ax)):s64)'
WARN[0000] skip main.main, failed to get ret offsets: no ret offsets
found 14 uprobes, large number of uprobes (>1000) need long time for attaching and detaching, continue? [Y/n]
>>> press `y` to continue
y
add arg rule at 47cc40: {Type:1 Reg:0 Size:8 Length:1 Offsets:[0 0 0 0 0 0 0 0] Deference:[1 0 0 0 0 0 0 0]}
add arg rule at 47cc40: {Type:1 Reg:0 Size:8 Length:1 Offsets:[8 0 0 0 0 0 0 0] Deference:[0 0 0 0 0 0 0 0]}
add arg rule at 47cc40: {Type:1 Reg:0 Size:8 Length:1 Offsets:[16 0 0 0 0 0 0 0] Deference:[0 0 0 0 0 0 0 0]}
INFO[0002] start tracing
...
🔬 You can inspect all nested function calls, when and where started or finished
23 17:11:00.0890 main.doSomething() { main.main+15 /home/zhangjie/github/go-ftrace/examples/main.go:10
23 17:11:00.0890 main.add() { main.doSomething+37 /home/zhangjie/github/go-ftrace/examples/main.go:15
23 17:11:00.0890 main.add1() { main.add+149 /home/zhangjie/github/go-ftrace/examples/main.go:27
23 17:11:00.0890 main.add3() { main.add1+149 /home/zhangjie/github/go-ftrace/examples/main.go:40
23 17:11:00.0890 000.0000 } main.add3+148 /home/zhangjie/github/go-ftrace/examples/main.go:46
23 17:11:00.0890 000.0000 } main.add1+154 /home/zhangjie/github/go-ftrace/examples/main.go:33
23 17:11:00.0890 000.0001 } main.add+154 /home/zhangjie/github/go-ftrace/examples/main.go:27
23 17:11:00.0890 main.minus() { main.doSomething+52 /home/zhangjie/github/go-ftrace/examples/main.go:16
23 17:11:00.0890 000.0000 } main.minus+3 /home/zhangjie/github/go-ftrace/examples/main.go:51
🔍 Here, member fields of function receiver extracted, receiver is the 1st argument actually.
23 17:11:00.0891 main.(*Student).String(s.name=zhang<ni, s.name.len=5, s.age=100) { fmt.(*pp).handleMethods+690 /opt/go/src/fmt/print.go:673
23 17:11:00.0891 000.0000 } main.(*Student).String+138 /home/zhangjie/github/go-ftrace/examples/main.go:64
23 17:11:01.0895 001.0005 } main.doSomething+180 /home/zhangjie/github/go-ftrace/examples/main.go:22
⏱️ Here, timecost is displayed at the end of the function call
...
>>> press `Ctrl+C` to quit.
INFO[0007] start detaching
detaching 16/16
If you're interested in the implmention internals, please read: go-ftrace internals.
This repo is forked from jschwinger233/gofuncgraph, with some modifications to improve usability and fix the bugs of fetching arguments.
Thanks for the original work!
ps:if you want to know more about go-ftrace alternatives to C, C++, Rust and Python, or kernel ftrace tool, you can see: