mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-09-21 15:36:51 +00:00
173 lines
3.7 KiB
Go
173 lines
3.7 KiB
Go
//go:build badlinkname
|
|
|
|
package runtimeinfo
|
|
|
|
import (
|
|
"bytes"
|
|
"cmp"
|
|
"reflect"
|
|
"runtime"
|
|
"slices"
|
|
"strconv"
|
|
"sync"
|
|
"sync/atomic"
|
|
"unsafe"
|
|
)
|
|
|
|
// Mirrors runtime.g of the pinned Go toolchain up to goid; the offsets of gopc and startpc are
|
|
// discovered by probeGoroutine.
|
|
|
|
type runtimeG struct {
|
|
stackLo uintptr
|
|
stackHi uintptr
|
|
_ [2]uintptr
|
|
_ [3]unsafe.Pointer
|
|
_ [6]uintptr
|
|
_ [4]uintptr
|
|
_ unsafe.Pointer
|
|
atomicstatus uint32
|
|
_ uint32
|
|
goid uint64
|
|
}
|
|
|
|
//go:linkname allgs runtime.allgs
|
|
var allgs []*runtimeG
|
|
|
|
const probeScanBytes = 512
|
|
|
|
var (
|
|
gopcOffset uintptr
|
|
startpcOffset uintptr
|
|
probeDone chan bool
|
|
)
|
|
|
|
func (gp *runtimeG) word(offset uintptr) uintptr {
|
|
return *(*uintptr)(unsafe.Add(unsafe.Pointer(gp), offset))
|
|
}
|
|
|
|
func (gp *runtimeG) gopc() uintptr {
|
|
return gp.word(gopcOffset)
|
|
}
|
|
|
|
func (gp *runtimeG) startpc() uintptr {
|
|
return gp.word(startpcOffset)
|
|
}
|
|
|
|
const (
|
|
statusDead = 6
|
|
statusScan = 0x1000
|
|
)
|
|
|
|
var statusNames = map[uint32]string{
|
|
0: "idle",
|
|
1: "runnable",
|
|
2: "running",
|
|
3: "syscall",
|
|
4: "waiting",
|
|
6: "dead",
|
|
8: "copystack",
|
|
9: "preempted",
|
|
}
|
|
|
|
var (
|
|
layoutOnce sync.Once
|
|
layoutVerified bool
|
|
)
|
|
|
|
func probeLayout() {
|
|
probeDone = make(chan bool, 1)
|
|
go probeGoroutine()
|
|
layoutVerified = <-probeDone
|
|
}
|
|
|
|
func probeGoroutine() {
|
|
id := currentGoroutineID()
|
|
var self *runtimeG
|
|
for _, gp := range allgs {
|
|
if gp.goid == id {
|
|
self = gp
|
|
break
|
|
}
|
|
}
|
|
if self == nil {
|
|
probeDone <- false
|
|
return
|
|
}
|
|
creatorName := functionName(reflect.ValueOf(probeLayout).Pointer())
|
|
selfName := functionName(reflect.ValueOf(probeGoroutine).Pointer())
|
|
goidOffset := unsafe.Offsetof(self.goid)
|
|
for offset := goidOffset + 8; offset < goidOffset+probeScanBytes; offset += 8 {
|
|
name := functionName(self.word(offset))
|
|
if gopcOffset == 0 && name == creatorName {
|
|
gopcOffset = offset
|
|
} else if startpcOffset == 0 && name == selfName {
|
|
startpcOffset = offset
|
|
}
|
|
}
|
|
probeDone <- gopcOffset != 0 && startpcOffset != 0
|
|
}
|
|
|
|
func functionName(pc uintptr) string {
|
|
function := runtime.FuncForPC(pc)
|
|
if function == nil {
|
|
return "unknown"
|
|
}
|
|
return function.Name()
|
|
}
|
|
|
|
func currentGoroutineID() uint64 {
|
|
var stackBuffer [64]byte
|
|
n := runtime.Stack(stackBuffer[:], false)
|
|
fields := bytes.Fields(stackBuffer[:n])
|
|
if len(fields) < 2 {
|
|
return 0
|
|
}
|
|
id, _ := strconv.ParseUint(string(fields[1]), 10, 64)
|
|
return id
|
|
}
|
|
|
|
func collectGoroutines() *GoroutineReport {
|
|
layoutOnce.Do(probeLayout)
|
|
if !layoutVerified {
|
|
return nil
|
|
}
|
|
report := &GoroutineReport{ByStatus: make(map[string]int)}
|
|
groups := make(map[string]*GoroutineGroup)
|
|
for _, gp := range allgs {
|
|
status := atomic.LoadUint32(&gp.atomicstatus) &^ statusScan
|
|
stackSize := uint64(gp.stackHi - gp.stackLo)
|
|
if status == statusDead {
|
|
report.Dead++
|
|
if gp.stackLo != 0 {
|
|
report.DeadStackBytes += stackSize
|
|
}
|
|
continue
|
|
}
|
|
report.Total++
|
|
report.StackBytes += stackSize
|
|
statusName, known := statusNames[status]
|
|
if !known {
|
|
statusName = strconv.Itoa(int(status))
|
|
}
|
|
report.ByStatus[statusName]++
|
|
name := functionName(gp.startpc())
|
|
group, found := groups[name]
|
|
if !found {
|
|
group = &GoroutineGroup{Function: name, CreatedBy: functionName(gp.gopc()), MinStackBytes: stackSize}
|
|
groups[name] = group
|
|
}
|
|
group.Count++
|
|
group.StackBytes += stackSize
|
|
group.MaxStackBytes = max(group.MaxStackBytes, stackSize)
|
|
group.MinStackBytes = min(group.MinStackBytes, stackSize)
|
|
}
|
|
report.ByFunction = make([]GoroutineGroup, 0, len(groups))
|
|
for _, group := range groups {
|
|
report.ByFunction = append(report.ByFunction, *group)
|
|
}
|
|
slices.SortFunc(report.ByFunction, func(a, b GoroutineGroup) int {
|
|
return cmp.Compare(b.StackBytes, a.StackBytes)
|
|
})
|
|
return report
|
|
}
|