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  }