github.com/ks888/tgo@v0.0.0-20190130135156-80bf89407292/lib/tracer/tracer.go (about) 1 // Package tracer provides functions to start and stop tracing, as well as the options to change 2 // the tracer's behaviors. 3 package tracer 4 5 import ( 6 "fmt" 7 "io" 8 "net" 9 "net/rpc" 10 "os" 11 "os/exec" 12 "reflect" 13 "runtime" 14 "sync" 15 "syscall" 16 "time" 17 "unsafe" // For go:linkname 18 19 "github.com/ks888/tgo/service" 20 ) 21 22 const expectedVersion = 1 23 24 var ( 25 client *rpc.Client 26 serverCmd *exec.Cmd 27 tracerProgramName = "tgo" 28 traceLevel = 1 29 parseLevel = 1 30 verbose = false 31 writer io.Writer = os.Stdout 32 errorWriter io.Writer = os.Stderr 33 // Protects the server command and its rpc client 34 serverMtx sync.Mutex 35 ) 36 37 //go:linkname firstModuleData runtime.firstmoduledata 38 var firstModuleData interface{} 39 40 // SetTraceLevel sets the trace level. Functions are traced if the stack depth is within this trace level. The stack depth here is based on the point tracing is enabled. The default is 1. 41 func SetTraceLevel(option int) { 42 traceLevel = option 43 } 44 45 // SetParseLevel sets the parse level. The trace log includes the function's args. The parselevel option determines how detailed these values should be. The default is 1. 46 func SetParseLevel(option int) { 47 parseLevel = option 48 } 49 50 // SetVerboseOption sets the verbose option. It true, the debug-level messages are written as well as the normal tracing log. The default is false. 51 func SetVerboseOption(option bool) { 52 verbose = option 53 } 54 55 // SetWriter sets the writer for the tracing log. The default is os.Stdout. 56 func SetWriter(option io.Writer) { 57 writer = option 58 } 59 60 // SetErrorWriter sets the writer for the error log. The default is os.Stderrr. 61 func SetErrorWriter(option io.Writer) { 62 errorWriter = option 63 } 64 65 // Start enables tracing. 66 func Start() error { 67 serverMtx.Lock() 68 defer serverMtx.Unlock() 69 70 pcs := make([]uintptr, 1) 71 _ = runtime.Callers(2, pcs) 72 startTracePoint := pcs[0] 73 74 if serverCmd == nil { 75 err := initialize(startTracePoint) 76 if err != nil { 77 _ = terminateServer() 78 return fmt.Errorf("failed to start tracer: %v", err) 79 } 80 return nil 81 } 82 83 reply := &struct{}{} // sometimes the nil reply value causes panic even if the reply is not written. 84 return client.Call("Tracer.AddStartTracePoint", startTracePoint, reply) 85 } 86 87 func initialize(startTracePoint uintptr) error { 88 addr, err := startServer() 89 if err != nil { 90 return err 91 } 92 93 client, err = connectServer(addr) 94 if err != nil { 95 return err 96 } 97 98 if err := checkVersion(); err != nil { 99 return err 100 } 101 102 programPath, err := os.Executable() 103 if err != nil { 104 return err 105 } 106 107 attachArgs := &service.AttachArgs{ 108 Pid: os.Getpid(), 109 TraceLevel: traceLevel, 110 ParseLevel: parseLevel, 111 InitialStartTracePoint: startTracePoint, 112 GoVersion: runtime.Version(), 113 ProgramPath: programPath, 114 FirstModuleDataAddr: uintptr(unsafe.Pointer(&firstModuleData)), 115 } 116 reply := &struct{}{} 117 if err := client.Call("Tracer.Attach", attachArgs, reply); err != nil { 118 return err 119 } 120 121 stopFuncAddr := reflect.ValueOf(Stop).Pointer() 122 return client.Call("Tracer.AddEndTracePoint", stopFuncAddr, reply) 123 } 124 125 func checkVersion() error { 126 var serverVersion int 127 if err := client.Call("Tracer.Version", struct{}{}, &serverVersion); err != nil { 128 return err 129 } 130 if expectedVersion != serverVersion { 131 return fmt.Errorf("the expected API version (%d) is not same as the actual API version (%d)", expectedVersion, serverVersion) 132 } 133 return nil 134 } 135 136 // Stop stops tracing. 137 // 138 //go:noinline 139 func Stop() { 140 return 141 } 142 143 func startServer() (string, error) { 144 unusedPort, err := findUnusedPort() 145 if err != nil { 146 return "", fmt.Errorf("failed to find unused port: %v", err) 147 } 148 addr := fmt.Sprintf(":%d", unusedPort) 149 150 args := []string{"server"} 151 if verbose { 152 args = append(args, "-verbose") 153 } 154 args = append(args, addr) 155 serverCmd = exec.Command(tracerProgramName, args...) 156 serverCmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} // Otherwise, tracer may receive the signal to this process. 157 serverCmd.Stdout = writer 158 serverCmd.Stderr = errorWriter 159 if err := serverCmd.Start(); err != nil { 160 return "", fmt.Errorf("failed to start server: %v", err) 161 } 162 return addr, nil 163 } 164 165 func findUnusedPort() (int, error) { 166 listener, err := net.ListenTCP("tcp", &net.TCPAddr{}) 167 if err != nil { 168 return 0, err 169 } 170 defer listener.Close() 171 172 return listener.Addr().(*net.TCPAddr).Port, nil 173 } 174 175 func connectServer(addr string) (*rpc.Client, error) { 176 const numRetries = 5 177 interval := 100 * time.Millisecond 178 var err error 179 for i := 0; i < numRetries; i++ { 180 client, err = rpc.Dial("tcp", addr) 181 if err == nil { 182 return client, nil 183 } 184 185 time.Sleep(interval) 186 interval *= 2 187 } 188 return nil, fmt.Errorf("can't connect to the server (addr: %s): %v", addr, err) 189 } 190 191 func terminateServer() error { 192 defer func() { serverCmd = nil }() 193 194 if client != nil { 195 if err := client.Close(); err != nil { 196 return err 197 } 198 } 199 200 if serverCmd != nil && serverCmd.Process != nil { 201 if err := serverCmd.Process.Kill(); err != nil { 202 return err 203 } 204 _, err := serverCmd.Process.Wait() 205 return err 206 } 207 return nil 208 }