使用 functrace 輔助進行 Go 專案原始碼分析
本文永久連結 - https://tonybai.com/2021/06/04/go-source-analysis-with-functrace
在《像跟蹤分散式服務呼叫那樣跟蹤 Go 函式呼叫鏈》一文中,我們介紹了一種跟蹤函式呼叫鏈的思路,並給出了一種實現functrace:https://github.com/bigwhite/functraceGo 專案原始碼時也能起到關鍵的輔助作用。這裡就和大家簡單講解一下如何用 functrace 來輔助 Go 原始碼閱讀和分析。。這個小工具不僅僅是分享給大家的,我自己在工作和學習時也在使用。最近發現這個小工具在閱讀和分析某個
程式設計師的日常離不開 “原始碼閱讀和分析”,日常閱讀程式碼的姿勢無非是這麼幾種(或幾種的組合):
- 結合原始碼編輯器或 IDE 提供的強大的原始碼交叉索引和跳轉功能在一個龐大的原始碼庫中建立起程式碼間的聯絡;
- 將程式碼跑起來,在程式碼中加上一些 print 輸出,跟蹤執行流並畫出;
- 也有人喜歡用偵錯程式從一點(通常是 main)開始單步跟蹤執行流。
無論哪一種方式,最終只要時間夠長,態度到位,總是會將程式碼分析出個七七八八的。
就筆者來看,無論是哪種正規化:命令式、物件導向、函式式,最終梳理出來的原始碼脈絡都是建立在執行基本單元 (函式或方法) 上,程式碼的執行主線(併發程式會有若干條)本質上就是一條函式/方法呼叫鏈。只要把這條鏈理出來,程式碼理解起來就不難了。上述的程式碼閱讀方法實質也是參照這個邏輯的。只是對於呼叫層次較深,還伴隨有回撥的程式碼,梳理呼叫鏈難度高、效率低。
functrace 最初用於跟蹤函式呼叫鏈(得益於 Go 核心開發團隊公開的抽象語法樹 AST API),但如果在閱讀程式碼時直接用 functrace 輸出函式呼叫鏈,那將大幅提高我們原始碼閱讀分析的效率。下面我們就用一個樣例專案來試試如何用 functrace 梳理出程式碼的執行主線。
我們以 Go 高效能、輕量級、非阻塞的事件驅動網路框架 gnet 為例,來看看如何閱讀分析 gnet 的原始碼。首先我們需要安裝 functrace 工具:
$go install github.com/bigwhite/functrace/cmd/gen@latest
go: downloading github.com/bigwhite/functrace v0.0.0-20210603024853-ccab68a2604c
go: downloading golang.org/x/tools v0.0.0-20201204062850-545788942d5f
$gen -h
[gen -h]
gen [-w] xxx.go
-w write result to (source) file instead of stdout
接下來,我們下載要進行原始碼分析的 gnet 原始碼:
$git clone git@github.com:panjf2000/gnet.git
我們進入 gnet 目錄,現在我們可以使用 gen 命令為任意 go 原始檔新增 “跟蹤設施” 了,比如:
$gen -w gnet.go
[gen -w gnet.go]
add trace for gnet.go ok
$ git diff gnet.go
diff --git a/gnet.go b/gnet.go
index b4c04a5..a7afe2b 100644
--- a/gnet.go
+++ b/gnet.go
@@ -29,6 +29,7 @@ import (
"sync"
"time"
+ "github.com/bigwhite/functrace"
"github.com/panjf2000/gnet/errors"
"github.com/panjf2000/gnet/internal"
"github.com/panjf2000/gnet/internal/logging"
... ...
我們可以這樣根據自己的需要在特定的 go 原始檔上新增 “跟蹤設施”,但是多數情況下,我們也可以通過指令碼為專案內所有 go 原始檔批量新增 “跟蹤設施”,functrace 專案提供了一個簡單的指令碼batch_add_trace.sh,下面我們就來通過該指令碼將 gnet 下的 go 原始檔批量加上函式跟蹤設施:
下載 functrace 原始碼:
$git clone https://github.com/bigwhite/functrace.git
將 functrace/scripts/batch_add_trace.sh 拷貝到上面 gnet 目錄下並執行下面命令:
# bash batch_add_trace.sh
... ...
[gen -w ./server_unix.go]
add trace for ./server_unix.go ok
[gen -w ./internal/socket/sockopts_posix.go]
add trace for ./internal/socket/sockopts_posix.go ok
... ...
[gen -w ./ringbuffer/ring_buffer_test.go]
add trace for ./ringbuffer/ring_buffer_test.go ok
[gen -w ./ringbuffer/ring_buffer.go]
add trace for ./ringbuffer/ring_buffer.go ok
[gen -w ./pool/bytebuffer/bytebuffer.go]
no trace added for ./pool/bytebuffer/bytebuffer.go
[gen -w ./pool/goroutine/goroutine.go]
add trace for ./pool/goroutine/goroutine.go ok
[gen -w ./pool/ringbuffer/ringbuffer.go]
add trace for ./pool/ringbuffer/ringbuffer.go ok
[gen -w ./loop_linux.go]
add trace for ./loop_linux.go ok
[gen -w ./server_windows.go]
add trace for ./server_windows.go ok
接下來我們編寫一個基於 gnet 的程式,我們就使用 gnet 參加TechEmpower的那份程式碼:
//main.go
package main
import (
"bytes"
"flag"
"fmt"
"log"
"runtime"
"time"
"github.com/panjf2000/gnet"
)
type httpServer struct {
*gnet.EventServer
}
type httpCodec struct {
delimiter []byte
}
func (hc *httpCodec) Encode(c gnet.Conn, buf []byte) (out []byte, err error) {
return buf, nil
}
func (hc *httpCodec) Decode(c gnet.Conn) (out []byte, err error) {
buf := c.Read()
if buf == nil {
return
}
c.ResetBuffer()
// process the pipeline
var i int
pipeline:
if i = bytes.Index(buf, hc.delimiter); i != -1 {
out = append(out, "HTTP/1.1 200 OK\r\nServer: gnet\r\nContent-Type: text/plain\r\nDate: "...)
out = time.Now().AppendFormat(out, "Mon, 02 Jan 2006 15:04:05 GMT")
out = append(out, "\r\nContent-Length: 13\r\n\r\nHello, World!"...)
buf = buf[i+4:]
goto pipeline
}
// request not ready, yet
return
}
func (hs *httpServer) OnInitComplete(srv gnet.Server) (action gnet.Action) {
log.Printf("HTTP server is listening on %s (multi-cores: %t, loops: %d)\n",
srv.Addr.String(), srv.Multicore, srv.NumEventLoop)
return
}
func (hs *httpServer) React(frame []byte, c gnet.Conn) (out []byte, action gnet.Action) {
// handle the request
out = frame
return
}
func init() {
runtime.GOMAXPROCS(runtime.NumCPU() * 2)
}
func main() {
var port int
var multicore bool
// Example command: go run main.go --port 8080 --multicore=true
flag.IntVar(&port, "port", 8080, "server port")
flag.BoolVar(&multicore, "multicore", true, "multicore")
flag.Parse()
http := new(httpServer)
hc := &httpCodec{delimiter: []byte("\r\n\r\n")}
// Start serving!
log.Fatal(gnet.Serve(http, fmt.Sprintf("tcp://:%d", port), gnet.WithMulticore(multicore), gnet.WithCodec(hc)))
}
構建這份程式碼:
$go mod init gnet-demo
$go get github.com/panjf2000/gnet
go: downloading github.com/panjf2000/gnet v1.4.5
go get: added github.com/panjf2000/gnet v1.4.5
//修改go.mod,使用replace讓gnet-demo使用本地的gnet程式碼
$cat go.mod
module gnet-demo
go 1.16
replace github.com/panjf2000/gnet => /root/go/src/github.com/panjf2000/gnet
require (
github.com/panjf2000/gnet v1.4.5
)
$go get github.com/bigwhite/functrace
go get: added github.com/bigwhite/functrace v0.0.0-20210603024853-ccab68a2604c
$go build -tags trace //-tags trace務必不能省略,這個是開啟functrace的關鍵
構建後,我們來執行構建出的可執行程式:gnet-demo:
$ go build -tags trace
root@VM-0-12-ubuntu:~/test/go/gnet-demo# ./gnet-demo
g[01]: ->github.com/panjf2000/gnet/internal/socket.maxListenerBacklog
g[01]: <-github.com/panjf2000/gnet/internal/socket.maxListenerBacklog
g[01]: ->github.com/panjf2000/gnet/ringbuffer.New
g[01]: <-github.com/panjf2000/gnet/ringbuffer.New
g[01]: ->github.com/panjf2000/gnet/internal/logging.init.0
g[01]: <-github.com/panjf2000/gnet/internal/logging.init.0
g[01]: ->github.com/panjf2000/gnet.WithMulticore
g[01]: <-github.com/panjf2000/gnet.WithMulticore
g[01]: ->github.com/panjf2000/gnet.WithCodec
g[01]: <-github.com/panjf2000/gnet.WithCodec
g[01]: ->github.com/panjf2000/gnet.Serve
g[01]: ->github.com/panjf2000/gnet.loadOptions
g[01]: <-github.com/panjf2000/gnet.loadOptions
g[01]: ->github.com/panjf2000/gnet.parseProtoAddr
g[01]: <-github.com/panjf2000/gnet.parseProtoAddr
g[01]: ->github.com/panjf2000/gnet.initListener
g[01]: ->github.com/panjf2000/gnet.(*listener).normalize
g[01]: ->github.com/panjf2000/gnet/internal/socket.TCPSocket
g[01]: ->github.com/panjf2000/gnet/internal/socket.tcpSocket
g[01]: ->github.com/panjf2000/gnet/internal/socket.getTCPSockaddr
g[01]: ->github.com/panjf2000/gnet/internal/socket.determineTCPProto
g[01]: <-github.com/panjf2000/gnet/internal/socket.determineTCPProto
g[01]: <-github.com/panjf2000/gnet/internal/socket.getTCPSockaddr
g[01]: ->github.com/panjf2000/gnet/internal/socket.sysSocket
g[01]: <-github.com/panjf2000/gnet/internal/socket.sysSocket
g[01]: ->github.com/panjf2000/gnet/internal/socket.SetNoDelay
g[01]: <-github.com/panjf2000/gnet/internal/socket.SetNoDelay
g[01]: <-github.com/panjf2000/gnet/internal/socket.tcpSocket
g[01]: <-github.com/panjf2000/gnet/internal/socket.TCPSocket
g[01]: <-github.com/panjf2000/gnet.(*listener).normalize
g[01]: <-github.com/panjf2000/gnet.initListener
g[01]: ->github.com/panjf2000/gnet.serve
2021/06/03 14:53:30 HTTP server is listening on :8080 (multi-cores: true, loops: 1)
g[01]: ->github.com/panjf2000/gnet.(*server).start
g[01]: ->github.com/panjf2000/gnet.(*server).activateReactors
g[01]: ->github.com/panjf2000/gnet/internal/netpoll.OpenPoller
g[01]: ->github.com/panjf2000/gnet/internal/netpoll.(*Poller).AddRead
g[01]: <-github.com/panjf2000/gnet/internal/netpoll.(*Poller).AddRead
g[01]: ->github.com/panjf2000/gnet/internal/netpoll/queue.NewLockFreeQueue
g[01]: <-github.com/panjf2000/gnet/internal/netpoll/queue.NewLockFreeQueue
g[01]: <-github.com/panjf2000/gnet/internal/netpoll.OpenPoller
g[01]: ->github.com/panjf2000/gnet.(*roundRobinLoadBalancer).register
g[01]: <-github.com/panjf2000/gnet.(*roundRobinLoadBalancer).register
g[01]: ->github.com/panjf2000/gnet.(*server).startSubReactors
g[01]: ->github.com/panjf2000/gnet.(*roundRobinLoadBalancer).iterate
g[01]: <-github.com/panjf2000/gnet.(*roundRobinLoadBalancer).iterate
g[01]: <-github.com/panjf2000/gnet.(*server).startSubReactors
g[01]: ->github.com/panjf2000/gnet/internal/netpoll.OpenPoller
g[01]: ->github.com/panjf2000/gnet/internal/netpoll.(*Poller).AddRead
g[01]: <-github.com/panjf2000/gnet/internal/netpoll.(*Poller).AddRead
g[01]: ->github.com/panjf2000/gnet/internal/netpoll/queue.NewLockFreeQueue
g[01]: <-github.com/panjf2000/gnet/internal/netpoll/queue.NewLockFreeQueue
g[01]: <-github.com/panjf2000/gnet/internal/netpoll.OpenPoller
g[01]: ->github.com/panjf2000/gnet/internal/netpoll.(*Poller).AddRead
g[01]: <-github.com/panjf2000/gnet/internal/netpoll.(*Poller).AddRead
g[01]: <-github.com/panjf2000/gnet.(*server).activateReactors
g[01]: <-github.com/panjf2000/gnet.(*server).start
g[01]: ->github.com/panjf2000/gnet.(*server).stop
g[01]: ->github.com/panjf2000/gnet.(*server).waitForShutdown
g[07]: ->github.com/panjf2000/gnet.(*server).activateMainReactor
g[07]: ->github.com/panjf2000/gnet/internal/netpoll.(*Poller).Polling
g[07]: ->github.com/panjf2000/gnet/internal/netpoll.newEventList
g[07]: <-github.com/panjf2000/gnet/internal/netpoll.newEventList
g[06]: ->github.com/panjf2000/gnet.(*server).activateSubReactor
g[06]: ->github.com/panjf2000/gnet/internal/netpoll.(*Poller).Polling
g[06]: ->github.com/panjf2000/gnet/internal/netpoll.newEventList
g[06]: <-github.com/panjf2000/gnet/internal/netpoll.newEventList
我們看到 gnet 的執行主線被清晰的列印出來,通過輸出的函式所在包我們可以輕鬆找到對應的原始檔。g[01] 這 goroutine 顯然是 main goroutine,整個程式的初始化線索通過跟蹤 g[01] 的函式鏈便一目瞭然。
如果我們要看 gnet 是如何處理一個外部連結的,我們可以向 gnet-demo 建立一個連線,看看 gnet-demo 的輸出。
我們通過 curl 命令向 gnet-demo 發起一個 http 請求:
$curl localhost:8080
Hello, World!
gnet-demo 輸出:
g[07]: ->github.com/panjf2000/gnet.(*server).acceptNewConnection
g[07]: ->github.com/panjf2000/gnet/internal/socket.SockaddrToTCPOrUnixAddr
g[07]: ->github.com/panjf2000/gnet/internal/socket.sockaddrInet6ToIPAndZone
g[07]: ->github.com/panjf2000/gnet/internal/socket.ip6ZoneToString
g[07]: <-github.com/panjf2000/gnet/internal/socket.ip6ZoneToString
g[07]: <-github.com/panjf2000/gnet/internal/socket.sockaddrInet6ToIPAndZone
g[07]: <-github.com/panjf2000/gnet/internal/socket.SockaddrToTCPOrUnixAddr
g[07]: ->github.com/panjf2000/gnet.(*roundRobinLoadBalancer).next
g[07]: <-github.com/panjf2000/gnet.(*roundRobinLoadBalancer).next
g[07]: ->github.com/panjf2000/gnet.newTCPConn
g[07]: ->github.com/panjf2000/gnet/pool/ringbuffer.Get
g[07]: ->github.com/panjf2000/gnet/pool/ringbuffer.(*Pool).Get
g[07]: ->github.com/panjf2000/gnet/ringbuffer.New
g[07]: <-github.com/panjf2000/gnet/ringbuffer.New
g[07]: <-github.com/panjf2000/gnet/pool/ringbuffer.(*Pool).Get
g[07]: <-github.com/panjf2000/gnet/pool/ringbuffer.Get
g[07]: ->github.com/panjf2000/gnet/pool/ringbuffer.Get
g[07]: ->github.com/panjf2000/gnet/pool/ringbuffer.(*Pool).Get
g[07]: ->github.com/panjf2000/gnet/ringbuffer.New
g[07]: <-github.com/panjf2000/gnet/ringbuffer.New
g[07]: <-github.com/panjf2000/gnet/pool/ringbuffer.(*Pool).Get
g[07]: <-github.com/panjf2000/gnet/pool/ringbuffer.Get
g[07]: <-github.com/panjf2000/gnet.newTCPConn
g[07]: ->github.com/panjf2000/gnet/internal/netpoll.(*Poller).Trigger
g[07]: ->github.com/panjf2000/gnet/internal/netpoll/queue.(*lockFreeQueue).Enqueue
g[07]: ->github.com/panjf2000/gnet/internal/netpoll/queue.load
g[07]: <-github.com/panjf2000/gnet/internal/netpoll/queue.load
g[07]: ->github.com/panjf2000/gnet/internal/netpoll/queue.load
g[07]: <-github.com/panjf2000/gnet/internal/netpoll/queue.load
g[07]: ->github.com/panjf2000/gnet/internal/netpoll/queue.load
g[07]: <-github.com/panjf2000/gnet/internal/netpoll/queue.load
g[07]: ->github.com/panjf2000/gnet/internal/netpoll/queue.cas
g[07]: <-github.com/panjf2000/gnet/internal/netpoll/queue.cas
g[07]: ->github.com/panjf2000/gnet/internal/netpoll/queue.cas
g[07]: <-github.com/panjf2000/gnet/internal/netpoll/queue.cas
g[07]: <-github.com/panjf2000/gnet/internal/netpoll/queue.(*lockFreeQueue).Enqueue
g[07]: <-github.com/panjf2000/gnet/internal/netpoll.(*Poller).Trigger
g[07]: <-github.com/panjf2000/gnet.(*server).acceptNewConnection
g[07]: ->github.com/panjf2000/gnet/internal/netpoll.(*eventList).shrink
g[07]: <-github.com/panjf2000/gnet/internal/netpoll.(*eventList).shrink
g[06]: ->github.com/panjf2000/gnet/internal/netpoll/queue.(*lockFreeQueue).Dequeue
g[06]: ->github.com/panjf2000/gnet/internal/netpoll/queue.load
g[06]: <-github.com/panjf2000/gnet/internal/netpoll/queue.load
g[06]: ->github.com/panjf2000/gnet/internal/netpoll/queue.load
g[06]: <-github.com/panjf2000/gnet/internal/netpoll/queue.load
g[06]: ->github.com/panjf2000/gnet/internal/netpoll/queue.load
g[06]: <-github.com/panjf2000/gnet/internal/netpoll/queue.load
g[06]: ->github.com/panjf2000/gnet/internal/netpoll/queue.load
g[06]: <-github.com/panjf2000/gnet/internal/netpoll/queue.load
g[06]: ->github.com/panjf2000/gnet/internal/netpoll/queue.cas
g[06]: <-github.com/panjf2000/gnet/internal/netpoll/queue.cas
g[06]: <-github.com/panjf2000/gnet/internal/netpoll/queue.(*lockFreeQueue).Dequeue
g[06]: ->github.com/panjf2000/gnet/internal/netpoll.(*Poller).AddRead
g[06]: <-github.com/panjf2000/gnet/internal/netpoll.(*Poller).AddRead
g[06]: ->github.com/panjf2000/gnet.(*eventloop).loopOpen
g[06]: ->github.com/panjf2000/gnet.(*eventloop).addConn
g[06]: <-github.com/panjf2000/gnet.(*eventloop).addConn
g[06]: ->github.com/panjf2000/gnet.(*EventServer).OnOpened
g[06]: <-github.com/panjf2000/gnet.(*EventServer).OnOpened
g[06]: ->github.com/panjf2000/gnet/ringbuffer.(*RingBuffer).IsEmpty
g[06]: <-github.com/panjf2000/gnet/ringbuffer.(*RingBuffer).IsEmpty
g[06]: ->github.com/panjf2000/gnet.(*eventloop).handleAction
g[06]: <-github.com/panjf2000/gnet.(*eventloop).handleAction
g[06]: <-github.com/panjf2000/gnet.(*eventloop).loopOpen
g[06]: ->github.com/panjf2000/gnet/internal/netpoll/queue.(*lockFreeQueue).Dequeue
g[06]: ->github.com/panjf2000/gnet/internal/netpoll/queue.load
g[06]: <-github.com/panjf2000/gnet/internal/netpoll/queue.load
g[06]: ->github.com/panjf2000/gnet/internal/netpoll/queue.load
g[06]: <-github.com/panjf2000/gnet/internal/netpoll/queue.load
g[06]: ->github.com/panjf2000/gnet/internal/netpoll/queue.load
g[06]: <-github.com/panjf2000/gnet/internal/netpoll/queue.load
g[06]: ->github.com/panjf2000/gnet/internal/netpoll/queue.load
g[06]: <-github.com/panjf2000/gnet/internal/netpoll/queue.load
g[06]: <-github.com/panjf2000/gnet/internal/netpoll/queue.(*lockFreeQueue).Dequeue
g[06]: ->github.com/panjf2000/gnet/internal/netpoll/queue.(*lockFreeQueue).Empty
g[06]: <-github.com/panjf2000/gnet/internal/netpoll/queue.(*lockFreeQueue).Empty
g[06]: ->github.com/panjf2000/gnet/internal/netpoll.(*eventList).shrink
g[06]: <-github.com/panjf2000/gnet/internal/netpoll.(*eventList).shrink
g[06]: ->github.com/panjf2000/gnet.(*eventloop).loopRead
g[06]: ->github.com/panjf2000/gnet.(*conn).read
g[06]: ->github.com/panjf2000/gnet.(*conn).Read
g[06]: ->github.com/panjf2000/gnet/ringbuffer.(*RingBuffer).IsEmpty
g[06]: <-github.com/panjf2000/gnet/ringbuffer.(*RingBuffer).IsEmpty
g[06]: <-github.com/panjf2000/gnet.(*conn).Read
g[06]: ->github.com/panjf2000/gnet.(*conn).ResetBuffer
g[06]: ->github.com/panjf2000/gnet/ringbuffer.(*RingBuffer).Reset
g[06]: <-github.com/panjf2000/gnet/ringbuffer.(*RingBuffer).Reset
g[06]: <-github.com/panjf2000/gnet.(*conn).ResetBuffer
g[06]: <-github.com/panjf2000/gnet.(*conn).read
g[06]: ->github.com/panjf2000/gnet.(*EventServer).PreWrite
g[06]: <-github.com/panjf2000/gnet.(*EventServer).PreWrite
g[06]: ->github.com/panjf2000/gnet.(*conn).write
g[06]: ->github.com/panjf2000/gnet/ringbuffer.(*RingBuffer).IsEmpty
g[06]: <-github.com/panjf2000/gnet/ringbuffer.(*RingBuffer).IsEmpty
g[06]: <-github.com/panjf2000/gnet.(*conn).write
g[06]: ->github.com/panjf2000/gnet.(*conn).read
g[06]: ->github.com/panjf2000/gnet.(*conn).Read
g[06]: ->github.com/panjf2000/gnet/ringbuffer.(*RingBuffer).IsEmpty
g[06]: <-github.com/panjf2000/gnet/ringbuffer.(*RingBuffer).IsEmpty
g[06]: <-github.com/panjf2000/gnet.(*conn).Read
g[06]: ->github.com/panjf2000/gnet.(*conn).ResetBuffer
g[06]: ->github.com/panjf2000/gnet/ringbuffer.(*RingBuffer).Reset
g[06]: <-github.com/panjf2000/gnet/ringbuffer.(*RingBuffer).Reset
g[06]: <-github.com/panjf2000/gnet.(*conn).ResetBuffer
g[06]: <-github.com/panjf2000/gnet.(*conn).read
g[06]: ->github.com/panjf2000/gnet/ringbuffer.(*RingBuffer).Write
g[06]: <-github.com/panjf2000/gnet/ringbuffer.(*RingBuffer).Write
g[06]: <-github.com/panjf2000/gnet.(*eventloop).loopRead
g[06]: ->github.com/panjf2000/gnet/internal/netpoll.(*eventList).shrink
g[06]: <-github.com/panjf2000/gnet/internal/netpoll.(*eventList).shrink
g[06]: ->github.com/panjf2000/gnet.(*eventloop).loopRead
g[06]: ->github.com/panjf2000/gnet.(*eventloop).loopCloseConn
g[06]: ->github.com/panjf2000/gnet/ringbuffer.(*RingBuffer).IsEmpty
g[06]: <-github.com/panjf2000/gnet/ringbuffer.(*RingBuffer).IsEmpty
g[06]: ->github.com/panjf2000/gnet/internal/netpoll.(*Poller).Delete
g[06]: <-github.com/panjf2000/gnet/internal/netpoll.(*Poller).Delete
g[06]: ->github.com/panjf2000/gnet.(*eventloop).addConn
g[06]: <-github.com/panjf2000/gnet.(*eventloop).addConn
g[06]: ->github.com/panjf2000/gnet.(*EventServer).OnClosed
g[06]: <-github.com/panjf2000/gnet.(*EventServer).OnClosed
g[06]: ->github.com/panjf2000/gnet.(*conn).releaseTCP
g[06]: ->github.com/panjf2000/gnet/pool/ringbuffer.Put
g[06]: ->github.com/panjf2000/gnet/pool/ringbuffer.(*Pool).Put
g[06]: ->github.com/panjf2000/gnet/ringbuffer.(*RingBuffer).Len
g[06]: <-github.com/panjf2000/gnet/ringbuffer.(*RingBuffer).Len
g[06]: ->github.com/panjf2000/gnet/pool/ringbuffer.index
g[06]: <-github.com/panjf2000/gnet/pool/ringbuffer.index
g[06]: ->github.com/panjf2000/gnet/ringbuffer.(*RingBuffer).Reset
g[06]: <-github.com/panjf2000/gnet/ringbuffer.(*RingBuffer).Reset
g[06]: <-github.com/panjf2000/gnet/pool/ringbuffer.(*Pool).Put
g[06]: <-github.com/panjf2000/gnet/pool/ringbuffer.Put
g[06]: ->github.com/panjf2000/gnet/pool/ringbuffer.Put
g[06]: ->github.com/panjf2000/gnet/pool/ringbuffer.(*Pool).Put
g[06]: ->github.com/panjf2000/gnet/ringbuffer.(*RingBuffer).Len
g[06]: <-github.com/panjf2000/gnet/ringbuffer.(*RingBuffer).Len
g[06]: ->github.com/panjf2000/gnet/pool/ringbuffer.index
g[06]: <-github.com/panjf2000/gnet/pool/ringbuffer.index
g[06]: ->github.com/panjf2000/gnet/ringbuffer.(*RingBuffer).Reset
g[06]: <-github.com/panjf2000/gnet/ringbuffer.(*RingBuffer).Reset
g[06]: <-github.com/panjf2000/gnet/pool/ringbuffer.(*Pool).Put
g[06]: <-github.com/panjf2000/gnet/pool/ringbuffer.Put
g[06]: <-github.com/panjf2000/gnet.(*conn).releaseTCP
g[06]: <-github.com/panjf2000/gnet.(*eventloop).loopCloseConn
g[06]: <-github.com/panjf2000/gnet.(*eventloop).loopRead
g[06]: ->github.com/panjf2000/gnet/internal/netpoll.(*eventList).shrink
g[06]: <-github.com/panjf2000/gnet/internal/netpoll.(*eventList).shrink
通過 gnet-demo 輸出,我們可以清晰看到 gnet 接收一個連線,在這個連線上讀寫以及關閉這個連線的函式呼叫鏈,有了這個鏈條,我們再來閱讀 gnet 原始碼就輕鬆許多了,即便有回撥函式也沒有問題。
上面輸出的函式呼叫鏈的內容已經很多了。但如果你還不滿足於這些,比如我還要跟蹤到 gnet 依賴的 golang.org/x/sys 中,那可以利用相同思路,將 golang.org/x/sys 下載到本地,並通過 functrace 新增跟蹤設施,並在 gnet-demo 中用 replace 換掉 golang.org/x/sys,讓其指向本地的 sys 包程式碼。如果覺得資訊太多,可以通過 gen 命令做單個必要 go 原始檔的跟蹤資訊新增,而不必要用批量方式。進一步的跟蹤 sys 包的函式呼叫鏈的作業就留給大家了,這裡就不深入了。
程式碼閱讀完成後,我們只需在 gnet 目錄下執行如下命令便可以恢復 gnet 原來的面貌:
$git checkout .
Go 技術專欄 “改善 Go 語⾔程式設計質量的 50 個有效實踐” 正在慕課網火熱熱銷中!本專欄主要滿足廣大 gopher 關於 Go 語言進階的需求,圍繞如何寫出地道且高質量 Go 程式碼給出 50 條有效實踐建議,上線後收到一致好評!歡迎大家訂閱!
我的網課 “Kubernetes 實戰:高可用叢集搭建、配置、運維與應用” 在慕課網熱賣中,歡迎小夥伴們訂閱學習!
Gopher Daily(Gopher 每日新聞) 歸檔倉庫 - https://github.com/bigwhite/gopherdaily
我的聯絡方式:
- 微博:https://weibo.com/bigwhite20xx
- 微信公眾號:iamtonybai
- 部落格:tonybai.com
- github: https://github.com/bigwhite
- “Gopher 部落” 知識星球:https://public.zsxq.com/groups/51284458844544
- 加微信實戰群請加微信(註明:實戰群):gocnio
相關文章
- 走進開源專案 - urlcat 原始碼分析原始碼
- 使用Github Action來輔助專案管理Github專案管理
- 使用 TypeScript + React + Redux 進行專案開發(入門篇,附原始碼)TypeScriptReactRedux原始碼
- 使用Git進行小專案程式碼管理Git
- go ants原始碼分析Go原始碼
- 使用Project進行專案管理Project專案管理
- go rpc 原始碼分析GoRPC原始碼
- jetty啟動web專案原始碼分析JettyWeb原始碼
- Spring事務原始碼分析專題(一)JdbcTemplate使用及原始碼分析Spring原始碼JDBC
- Go的WaitGroup原始碼分析GoAI原始碼
- go sync.Map原始碼分析Go原始碼
- Android 開源專案PhotoView原始碼分析AndroidView原始碼
- 專案中常用的 .env 檔案原理原始碼分析原始碼
- k8s client-go原始碼分析 informer原始碼分析(6)-Indexer原始碼分析K8SclientGo原始碼ORMIndex
- k8s client-go原始碼分析 informer原始碼分析(4)-DeltaFIFO原始碼分析K8SclientGo原始碼ORM
- 以太坊原始碼分析(44)p2p-database.go原始碼分析原始碼DatabaseGo
- 以太坊原始碼分析(45)p2p-dial.go原始碼分析原始碼Go
- 以太坊原始碼分析(46)p2p-peer.go原始碼分析原始碼Go
- 以太坊原始碼分析(48)p2p-server.go原始碼分析原始碼ServerGo
- 以太坊原始碼分析(49)p2p-table.go原始碼分析原始碼Go
- 以太坊原始碼分析(50)p2p-udp.go原始碼分析原始碼UDPGo
- 藉助 webpack 對專案進行分析優化Web優化
- Egg.js 原始碼分析-專案啟動JS原始碼
- 開源專案Running Life 原始碼分析(一)原始碼
- 【原始碼】使用MATLAB進行ECG模擬原始碼Matlab
- cache2go – cachetable原始碼分析Go原始碼
- Go 語言 HTTP Server 原始碼分析GoHTTPServer原始碼
- go.uber.org/ratelimit 原始碼分析GoMIT原始碼
- Go 互斥鎖 Mutex 原始碼分析(二)GoMutex原始碼
- 使用OClint進行iOS專案的靜態程式碼掃描iOS
- k8s client-go原始碼分析 informer原始碼分析(1)-概要分析K8SclientGo原始碼ORM
- RyuBook1.0案例一:SwitchingHub專案原始碼分析原始碼
- Android 原始碼分析(一)專案構建過程Android原始碼
- k8s client-go原始碼分析 informer原始碼分析(5)-Controller&Processor原始碼分析K8SclientGo原始碼ORMController
- Go語言——sync.Map原始碼分析Go原始碼
- 使用 Go 模組建立專案(vgo)Go
- 使用rabbitmq對文字使用tf_idf演算法進行分析的專案記錄MQ演算法
- 執行流程原始碼分析原始碼