mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-10-26 13:06:56 +00:00
Record memory usage continuously in OOM report
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
//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
|
||||
}
|
||||
Reference in New Issue
Block a user