Go project source code analysis with the help of functrace

The daily life of a programmer cannot be separated from “source code reading and analysis”, and the daily reading of code can only be done in these ways (or a combination of them).

  • Combining the powerful source code cross-indexing and jumping features provided by source code editors or IDEs to establish links between codes in a large source code base.
  • running the code up, adding some print output to the code, tracing the execution flow and drawing it out.
  • Some people also like to use a debugger to trace the execution flow in a single step starting from a point (usually main).

No matter which way, eventually, as long as enough time and attitude in place, will always analyze the code out of a rough.

From my point of view, no matter which paradigm: imperative, object-oriented, functional, the final comb out the source code vein is built on the execution of the basic unit (function or method), the main line of code execution (concurrent programs will have several) is essentially a function / method call chain. As long as this chain is sorted out, the code will not be difficult to understand. The code reading method described above essentially refers to this same logic. However, for code with a deeper call hierarchy and callbacks, sorting out the call chain is difficult and inefficient.

functrace was originally used to trace function call chains (thanks to the Abstract Syntax Tree AST API made public by the Go core development team), but if we use functrace to output function call chains directly when reading code, it will dramatically improve our source code reading and analysis efficiency. Let’s use a sample project to try out how to use functrace to sort out the main lines of code execution.

Let’s take gnet, Go’s high-performance, lightweight, non-blocking event-driven networking framework, as an example to see how to read and analyze gnet’s source code. First we need to install the functrace tool.

1
2
3
4
5
6
7
8
$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

Next, we download the gnet source code for the source code analysis to be performed.

1
$git clone git@github.com:panjf2000/gnet.git

We go to the gnet directory and now we can add a “tracking facility” to any go source file using the gen command, e.g.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
$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"
... ...

We can add “trace facilities” to specific go source files as we see fit, but in most cases, we can also add “trace facilities” to all go source files in a project in bulk via a script. The functrace project provides a simple script batch_add_trace.sh, which we will use to bulk add “trace facilities” to all the go source files under gnet files under gnet with the function trace facility.

Download the functrace source code.

1
$git clone https://github.com/bigwhite/functrace.git

Copy functrace/scripts/batch_add_trace.sh to the gnet directory above and execute the following command.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# 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

Next we write a gnet-based program, and we’ll use that code from gnet’s participation in TechEmpower.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
//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)))
}

Build this code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
$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的关键

After the build, let’s execute the built executable: gnet-demo.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
$ 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

We see that the main execution line of gnet is clearly printed out, and we can easily find the corresponding source file by the package where the output functions are located. g[01], the goroutine, is obviously the main goroutine, and the initialization trail of the whole program can be seen at a glance by tracing the function chain of g[01].

If we want to see how gnet handles an external link, we can create a connection to gnet-demo and see the output of gnet-demo.

We make an http request to gnet-demo via the curl command.

1
2
$curl localhost:8080
Hello, World!

gnet-demo output.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
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

Through the gnet-demo output, we can clearly see the chain of function calls for gnet to receive a connection, read and write on this connection and close this connection. With this chain, it is much easier for us to read the gnet source code again, even if there are callback functions.

The above output of the function call chain is already a lot of content. But if you are not satisfied with that, for example, I have to trace to golang.org/x/sys which gnet depends on, then you can use the same idea to download golang.org/x/sys locally and add the trace facility via functrace and replace golang.org/ x/sys so that it points to the local sys package code. If you think there is too much information, you can do individual necessary go source file trace information addition via the gen command, instead of unnecessarily using a batch approach. Further homework on tracing the function call chain of the sys package is left to you, so we won’t dive into it here.

Once the code reading is complete, we can restore gnet to its original form by simply executing the following command in the gnet directory.

1
$git checkout .