github.com/pinpoint-apm/pinpoint-go-agent@v1.4.1-0.20240110120318-a50c2eb18c8c/goroutine.go (about)

     1  package pinpoint
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"io"
     7  	"reflect"
     8  	"regexp"
     9  	"runtime"
    10  	"runtime/pprof"
    11  	"strconv"
    12  	"strings"
    13  	"unsafe"
    14  
    15  	"github.com/pinpoint-apm/pinpoint-go-agent/asm"
    16  	pb "github.com/pinpoint-apm/pinpoint-go-agent/protobuf"
    17  )
    18  
    19  var (
    20  	goIdOffset uintptr
    21  	stateMap   map[string]pb.PThreadState
    22  )
    23  
    24  func initGoroutine() {
    25  	goIdOffset = getOffset()
    26  	if goIdOffset > 0 {
    27  		Log("cmd").Infof("goroutine id from runtime.g (offset = %d)", goIdOffset)
    28  	} else {
    29  		Log("cmd").Infof("goroutine id from stack dump")
    30  	}
    31  
    32  	stateMap = make(map[string]pb.PThreadState, 0)
    33  	stateMap[""] = pb.PThreadState_THREAD_STATE_UNKNOWN
    34  	stateMap["???"] = pb.PThreadState_THREAD_STATE_UNKNOWN
    35  	stateMap["idle"] = pb.PThreadState_THREAD_STATE_NEW
    36  	stateMap["runnable"] = pb.PThreadState_THREAD_STATE_RUNNABLE
    37  	stateMap["running"] = pb.PThreadState_THREAD_STATE_RUNNABLE
    38  	stateMap["syscall"] = pb.PThreadState_THREAD_STATE_RUNNABLE
    39  	stateMap["copystack"] = pb.PThreadState_THREAD_STATE_RUNNABLE
    40  	stateMap["preempted"] = pb.PThreadState_THREAD_STATE_RUNNABLE
    41  	stateMap["dead"] = pb.PThreadState_THREAD_STATE_TERMINATED
    42  	stateMap["dumping heap"] = pb.PThreadState_THREAD_STATE_BLOCKED
    43  	stateMap["garbage collection"] = pb.PThreadState_THREAD_STATE_BLOCKED
    44  	stateMap["garbage collection scan"] = pb.PThreadState_THREAD_STATE_BLOCKED
    45  	stateMap["force gc (idle)"] = pb.PThreadState_THREAD_STATE_BLOCKED
    46  	stateMap["trace reader (blocked)"] = pb.PThreadState_THREAD_STATE_BLOCKED
    47  	stateMap["preempted"] = pb.PThreadState_THREAD_STATE_BLOCKED
    48  	stateMap["debug call"] = pb.PThreadState_THREAD_STATE_BLOCKED
    49  	stateMap["stopping the world"] = pb.PThreadState_THREAD_STATE_BLOCKED
    50  	// the rest of the state are considered pb.PThreadState_THREAD_STATE_WAITING
    51  	// refer https://github.com/golang/go/blob/master/src/runtime/runtime2.go: waitReasonStrings
    52  }
    53  
    54  type goroutine struct {
    55  	id     int64
    56  	header string
    57  	state  string
    58  	buf    *bytes.Buffer
    59  	span   *activeSpanInfo
    60  }
    61  
    62  func (g *goroutine) addLine(line string) {
    63  	g.buf.WriteString(line)
    64  	g.buf.WriteString("\n")
    65  }
    66  
    67  func (g *goroutine) threadState() pb.PThreadState {
    68  	if s, ok := stateMap[g.state]; ok {
    69  		return s
    70  	}
    71  	return pb.PThreadState_THREAD_STATE_WAITING
    72  }
    73  
    74  func (g *goroutine) stackTrace() []string {
    75  	return append(make([]string, 0), g.buf.String())
    76  }
    77  
    78  func newGoroutine(idStr string, state string, line string) *goroutine {
    79  	if id, err := strconv.Atoi(idStr); err == nil {
    80  		g := &goroutine{
    81  			id:     int64(id),
    82  			header: "goroutine " + idStr,
    83  			state:  strings.TrimSpace(strings.Split(state, ",")[0]),
    84  			buf:    &bytes.Buffer{},
    85  		}
    86  		g.addLine(line)
    87  		return g
    88  	} else {
    89  		Log("cmd").Errorf("convert goroutine id: %v", err)
    90  	}
    91  	return nil
    92  }
    93  
    94  type goroutineDump struct {
    95  	goroutines []*goroutine
    96  }
    97  
    98  func (gd *goroutineDump) add(g *goroutine) {
    99  	gd.goroutines = append(gd.goroutines, g)
   100  }
   101  
   102  func (gd *goroutineDump) search(s string) *goroutine {
   103  	for _, g := range gd.goroutines {
   104  		if g.header == s {
   105  			return g
   106  		}
   107  	}
   108  
   109  	return nil
   110  }
   111  
   112  func newGoroutineDump() *goroutineDump {
   113  	return &goroutineDump{
   114  		goroutines: []*goroutine{},
   115  	}
   116  }
   117  
   118  func dumpGoroutine() (dump *goroutineDump) {
   119  	var b bytes.Buffer
   120  	buf := bufio.NewWriter(&b)
   121  
   122  	defer func() {
   123  		if e := recover(); e != nil {
   124  			Log("cmd").Errorf("profile goroutine: %v", e)
   125  			dump = nil
   126  		}
   127  	}()
   128  
   129  	if p := pprof.Lookup("goroutine"); p != nil {
   130  		if err := p.WriteTo(buf, 2); err != nil {
   131  			Log("cmd").Errorf("profile goroutine: %v", err)
   132  			return nil
   133  		}
   134  	}
   135  
   136  	dump = parseProfile(&b)
   137  	return
   138  }
   139  
   140  func parseProfile(r io.Reader) *goroutineDump {
   141  	dump := newGoroutineDump()
   142  	var g *goroutine
   143  
   144  	scanner := bufio.NewScanner(r)
   145  	re := regexp.MustCompile(`^goroutine\s+(\d+)\s+\[(.*)\]:$`)
   146  
   147  	for scanner.Scan() {
   148  		line := scanner.Text()
   149  		if g == nil {
   150  			if match := re.FindAllStringSubmatch(line, 1); match != nil {
   151  				if g = newGoroutine(match[0][1], match[0][2], line); g != nil {
   152  					if v, ok := realTimeActiveSpan.Load(g.id); ok {
   153  						g.span = v.(*activeSpanInfo)
   154  						dump.add(g)
   155  					}
   156  				}
   157  			}
   158  		} else {
   159  			if line == "" {
   160  				g = nil
   161  			} else {
   162  				g.addLine(line)
   163  			}
   164  		}
   165  	}
   166  
   167  	if err := scanner.Err(); err != nil {
   168  		Log("cmd").Errorf("scan goroutine profile: %v", err)
   169  		return nil
   170  	}
   171  
   172  	return dump
   173  }
   174  
   175  func curGoroutineID() int64 {
   176  	if goIdOffset > 0 {
   177  		return goIdFromG()
   178  	} else {
   179  		return goIdFromDump()
   180  	}
   181  }
   182  
   183  var prefix = len("goroutine ")
   184  
   185  func goIdFromDump() int64 {
   186  	b := make([]byte, 64)
   187  	b = b[prefix:runtime.Stack(b, false)]
   188  	idStr := string(b[:bytes.IndexByte(b, ' ')])
   189  	Log("cmd").Debugf("idStr: '%s'", idStr)
   190  	id, _ := strconv.ParseInt(idStr, 10, 64)
   191  	return id
   192  }
   193  
   194  func getOffset() uintptr {
   195  	if typ := typeRuntimeG(); typ != nil {
   196  		if f, ok := typ.FieldByName("goid"); ok {
   197  			return f.Offset
   198  		}
   199  	}
   200  	return 0
   201  }
   202  
   203  func typeRuntimeG() reflect.Type {
   204  	sections, offsets := typelinks()
   205  	//load go types
   206  	for i, base := range sections {
   207  		for _, offset := range offsets[i] {
   208  			typeAddr := add(base, uintptr(offset), "")
   209  			typ := reflect.TypeOf(*(*interface{})(unsafe.Pointer(&typeAddr)))
   210  			if typ.Kind() == reflect.Ptr && typ.Elem().String() == "runtime.g" {
   211  				return typ.Elem()
   212  			}
   213  		}
   214  	}
   215  	return nil
   216  }
   217  
   218  //go:linkname typelinks reflect.typelinks
   219  func typelinks() (sections []unsafe.Pointer, offset [][]int32)
   220  
   221  //go:linkname add reflect.add
   222  func add(p unsafe.Pointer, x uintptr, whySafe string) unsafe.Pointer
   223  
   224  func goIdFromG() int64 {
   225  	return *(*int64)(unsafe.Pointer(uintptr(asm.Getg()) + goIdOffset))
   226  }