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 }