mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-14 12:48:28 +10:00
391 lines
9.3 KiB
Go
391 lines
9.3 KiB
Go
//go:build darwin || linux || windows
|
|
|
|
package oomprofile
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"runtime"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
tagProfile_SampleType = 1
|
|
tagProfile_Sample = 2
|
|
tagProfile_Mapping = 3
|
|
tagProfile_Location = 4
|
|
tagProfile_Function = 5
|
|
tagProfile_StringTable = 6
|
|
tagProfile_TimeNanos = 9
|
|
tagProfile_PeriodType = 11
|
|
tagProfile_Period = 12
|
|
tagProfile_DefaultSampleType = 14
|
|
|
|
tagValueType_Type = 1
|
|
tagValueType_Unit = 2
|
|
|
|
tagSample_Location = 1
|
|
tagSample_Value = 2
|
|
tagSample_Label = 3
|
|
|
|
tagLabel_Key = 1
|
|
tagLabel_Str = 2
|
|
tagLabel_Num = 3
|
|
|
|
tagMapping_ID = 1
|
|
tagMapping_Start = 2
|
|
tagMapping_Limit = 3
|
|
tagMapping_Offset = 4
|
|
tagMapping_Filename = 5
|
|
tagMapping_BuildID = 6
|
|
tagMapping_HasFunctions = 7
|
|
tagMapping_HasFilenames = 8
|
|
tagMapping_HasLineNumbers = 9
|
|
tagMapping_HasInlineFrames = 10
|
|
|
|
tagLocation_ID = 1
|
|
tagLocation_MappingID = 2
|
|
tagLocation_Address = 3
|
|
tagLocation_Line = 4
|
|
|
|
tagLine_FunctionID = 1
|
|
tagLine_Line = 2
|
|
|
|
tagFunction_ID = 1
|
|
tagFunction_Name = 2
|
|
tagFunction_SystemName = 3
|
|
tagFunction_Filename = 4
|
|
tagFunction_StartLine = 5
|
|
)
|
|
|
|
type memMap struct {
|
|
start uintptr
|
|
end uintptr
|
|
offset uint64
|
|
file string
|
|
buildID string
|
|
funcs symbolizeFlag
|
|
fake bool
|
|
}
|
|
|
|
type symbolizeFlag uint8
|
|
|
|
const (
|
|
lookupTried symbolizeFlag = 1 << iota
|
|
lookupFailed
|
|
)
|
|
|
|
func newProfileBuilder(w io.Writer) *profileBuilder {
|
|
builder := &profileBuilder{
|
|
start: time.Now(),
|
|
w: w,
|
|
strings: []string{""},
|
|
stringMap: map[string]int{"": 0},
|
|
locs: map[uintptr]locInfo{},
|
|
funcs: map[string]int{},
|
|
}
|
|
builder.readMapping()
|
|
return builder
|
|
}
|
|
|
|
func (b *profileBuilder) stringIndex(s string) int64 {
|
|
id, ok := b.stringMap[s]
|
|
if !ok {
|
|
id = len(b.strings)
|
|
b.strings = append(b.strings, s)
|
|
b.stringMap[s] = id
|
|
}
|
|
return int64(id)
|
|
}
|
|
|
|
func (b *profileBuilder) flush() {
|
|
const dataFlush = 4096
|
|
if b.err != nil || b.pb.nest != 0 || len(b.pb.data) <= dataFlush {
|
|
return
|
|
}
|
|
|
|
_, b.err = b.w.Write(b.pb.data)
|
|
b.pb.data = b.pb.data[:0]
|
|
}
|
|
|
|
func (b *profileBuilder) pbValueType(tag int, typ string, unit string) {
|
|
start := b.pb.startMessage()
|
|
b.pb.int64(tagValueType_Type, b.stringIndex(typ))
|
|
b.pb.int64(tagValueType_Unit, b.stringIndex(unit))
|
|
b.pb.endMessage(tag, start)
|
|
}
|
|
|
|
func (b *profileBuilder) pbSample(values []int64, locs []uint64, labels func()) {
|
|
start := b.pb.startMessage()
|
|
b.pb.int64s(tagSample_Value, values)
|
|
b.pb.uint64s(tagSample_Location, locs)
|
|
if labels != nil {
|
|
labels()
|
|
}
|
|
b.pb.endMessage(tagProfile_Sample, start)
|
|
b.flush()
|
|
}
|
|
|
|
func (b *profileBuilder) pbLabel(tag int, key string, str string, num int64) {
|
|
start := b.pb.startMessage()
|
|
b.pb.int64Opt(tagLabel_Key, b.stringIndex(key))
|
|
b.pb.int64Opt(tagLabel_Str, b.stringIndex(str))
|
|
b.pb.int64Opt(tagLabel_Num, num)
|
|
b.pb.endMessage(tag, start)
|
|
}
|
|
|
|
func (b *profileBuilder) pbLine(tag int, funcID uint64, line int64) {
|
|
start := b.pb.startMessage()
|
|
b.pb.uint64Opt(tagLine_FunctionID, funcID)
|
|
b.pb.int64Opt(tagLine_Line, line)
|
|
b.pb.endMessage(tag, start)
|
|
}
|
|
|
|
func (b *profileBuilder) pbMapping(tag int, id uint64, base uint64, limit uint64, offset uint64, file string, buildID string, hasFuncs bool) {
|
|
start := b.pb.startMessage()
|
|
b.pb.uint64Opt(tagMapping_ID, id)
|
|
b.pb.uint64Opt(tagMapping_Start, base)
|
|
b.pb.uint64Opt(tagMapping_Limit, limit)
|
|
b.pb.uint64Opt(tagMapping_Offset, offset)
|
|
b.pb.int64Opt(tagMapping_Filename, b.stringIndex(file))
|
|
b.pb.int64Opt(tagMapping_BuildID, b.stringIndex(buildID))
|
|
if hasFuncs {
|
|
b.pb.bool(tagMapping_HasFunctions, true)
|
|
}
|
|
b.pb.endMessage(tag, start)
|
|
}
|
|
|
|
func (b *profileBuilder) build() error {
|
|
if b.err != nil {
|
|
return b.err
|
|
}
|
|
|
|
b.pb.int64Opt(tagProfile_TimeNanos, b.start.UnixNano())
|
|
for i, mapping := range b.mem {
|
|
hasFunctions := mapping.funcs == lookupTried
|
|
b.pbMapping(tagProfile_Mapping, uint64(i+1), uint64(mapping.start), uint64(mapping.end), mapping.offset, mapping.file, mapping.buildID, hasFunctions)
|
|
}
|
|
b.pb.strings(tagProfile_StringTable, b.strings)
|
|
if b.err != nil {
|
|
return b.err
|
|
}
|
|
_, err := b.w.Write(b.pb.data)
|
|
return err
|
|
}
|
|
|
|
func allFrames(addr uintptr) ([]runtime.Frame, symbolizeFlag) {
|
|
frames := runtime.CallersFrames([]uintptr{addr})
|
|
frame, more := frames.Next()
|
|
if frame.Function == "runtime.goexit" {
|
|
return nil, 0
|
|
}
|
|
|
|
result := lookupTried
|
|
if frame.PC == 0 || frame.Function == "" || frame.File == "" || frame.Line == 0 {
|
|
result |= lookupFailed
|
|
}
|
|
if frame.PC == 0 {
|
|
frame.PC = addr - 1
|
|
}
|
|
|
|
ret := []runtime.Frame{frame}
|
|
for frame.Function != "runtime.goexit" && more {
|
|
frame, more = frames.Next()
|
|
ret = append(ret, frame)
|
|
}
|
|
return ret, result
|
|
}
|
|
|
|
type locInfo struct {
|
|
id uint64
|
|
|
|
pcs []uintptr
|
|
|
|
firstPCFrames []runtime.Frame
|
|
firstPCSymbolizeResult symbolizeFlag
|
|
}
|
|
|
|
func (b *profileBuilder) appendLocsForStack(locs []uint64, stk []uintptr) []uint64 {
|
|
b.deck.reset()
|
|
origStk := stk
|
|
stk = runtimeExpandFinalInlineFrame(stk)
|
|
|
|
for len(stk) > 0 {
|
|
addr := stk[0]
|
|
if loc, ok := b.locs[addr]; ok {
|
|
if len(b.deck.pcs) > 0 {
|
|
if b.deck.tryAdd(addr, loc.firstPCFrames, loc.firstPCSymbolizeResult) {
|
|
stk = stk[1:]
|
|
continue
|
|
}
|
|
}
|
|
if id := b.emitLocation(); id > 0 {
|
|
locs = append(locs, id)
|
|
}
|
|
locs = append(locs, loc.id)
|
|
if len(loc.pcs) > len(stk) {
|
|
panic(fmt.Sprintf("stack too short to match cached location; stk = %#x, loc.pcs = %#x, original stk = %#x", stk, loc.pcs, origStk))
|
|
}
|
|
stk = stk[len(loc.pcs):]
|
|
continue
|
|
}
|
|
|
|
frames, symbolizeResult := allFrames(addr)
|
|
if len(frames) == 0 {
|
|
if id := b.emitLocation(); id > 0 {
|
|
locs = append(locs, id)
|
|
}
|
|
stk = stk[1:]
|
|
continue
|
|
}
|
|
|
|
if b.deck.tryAdd(addr, frames, symbolizeResult) {
|
|
stk = stk[1:]
|
|
continue
|
|
}
|
|
if id := b.emitLocation(); id > 0 {
|
|
locs = append(locs, id)
|
|
}
|
|
|
|
if loc, ok := b.locs[addr]; ok {
|
|
locs = append(locs, loc.id)
|
|
stk = stk[len(loc.pcs):]
|
|
} else {
|
|
b.deck.tryAdd(addr, frames, symbolizeResult)
|
|
stk = stk[1:]
|
|
}
|
|
}
|
|
if id := b.emitLocation(); id > 0 {
|
|
locs = append(locs, id)
|
|
}
|
|
return locs
|
|
}
|
|
|
|
type pcDeck struct {
|
|
pcs []uintptr
|
|
frames []runtime.Frame
|
|
symbolizeResult symbolizeFlag
|
|
|
|
firstPCFrames int
|
|
firstPCSymbolizeResult symbolizeFlag
|
|
}
|
|
|
|
func (d *pcDeck) reset() {
|
|
d.pcs = d.pcs[:0]
|
|
d.frames = d.frames[:0]
|
|
d.symbolizeResult = 0
|
|
d.firstPCFrames = 0
|
|
d.firstPCSymbolizeResult = 0
|
|
}
|
|
|
|
func (d *pcDeck) tryAdd(pc uintptr, frames []runtime.Frame, symbolizeResult symbolizeFlag) bool {
|
|
if existing := len(d.frames); existing > 0 {
|
|
newFrame := frames[0]
|
|
last := d.frames[existing-1]
|
|
if last.Func != nil {
|
|
return false
|
|
}
|
|
if last.Entry == 0 || newFrame.Entry == 0 {
|
|
return false
|
|
}
|
|
if last.Entry != newFrame.Entry {
|
|
return false
|
|
}
|
|
if runtimeFrameSymbolName(&last) == runtimeFrameSymbolName(&newFrame) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
d.pcs = append(d.pcs, pc)
|
|
d.frames = append(d.frames, frames...)
|
|
d.symbolizeResult |= symbolizeResult
|
|
if len(d.pcs) == 1 {
|
|
d.firstPCFrames = len(d.frames)
|
|
d.firstPCSymbolizeResult = symbolizeResult
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (b *profileBuilder) emitLocation() uint64 {
|
|
if len(b.deck.pcs) == 0 {
|
|
return 0
|
|
}
|
|
defer b.deck.reset()
|
|
|
|
addr := b.deck.pcs[0]
|
|
firstFrame := b.deck.frames[0]
|
|
|
|
type newFunc struct {
|
|
id uint64
|
|
name string
|
|
file string
|
|
startLine int64
|
|
}
|
|
|
|
newFuncs := make([]newFunc, 0, 8)
|
|
id := uint64(len(b.locs)) + 1
|
|
b.locs[addr] = locInfo{
|
|
id: id,
|
|
pcs: append([]uintptr{}, b.deck.pcs...),
|
|
firstPCFrames: append([]runtime.Frame{}, b.deck.frames[:b.deck.firstPCFrames]...),
|
|
firstPCSymbolizeResult: b.deck.firstPCSymbolizeResult,
|
|
}
|
|
|
|
start := b.pb.startMessage()
|
|
b.pb.uint64Opt(tagLocation_ID, id)
|
|
b.pb.uint64Opt(tagLocation_Address, uint64(firstFrame.PC))
|
|
for _, frame := range b.deck.frames {
|
|
funcName := runtimeFrameSymbolName(&frame)
|
|
funcID := uint64(b.funcs[funcName])
|
|
if funcID == 0 {
|
|
funcID = uint64(len(b.funcs)) + 1
|
|
b.funcs[funcName] = int(funcID)
|
|
newFuncs = append(newFuncs, newFunc{
|
|
id: funcID,
|
|
name: funcName,
|
|
file: frame.File,
|
|
startLine: int64(runtimeFrameStartLine(&frame)),
|
|
})
|
|
}
|
|
b.pbLine(tagLocation_Line, funcID, int64(frame.Line))
|
|
}
|
|
for i := range b.mem {
|
|
if (b.mem[i].start <= addr && addr < b.mem[i].end) || b.mem[i].fake {
|
|
b.pb.uint64Opt(tagLocation_MappingID, uint64(i+1))
|
|
mapping := b.mem[i]
|
|
mapping.funcs |= b.deck.symbolizeResult
|
|
b.mem[i] = mapping
|
|
break
|
|
}
|
|
}
|
|
b.pb.endMessage(tagProfile_Location, start)
|
|
|
|
for _, fn := range newFuncs {
|
|
start := b.pb.startMessage()
|
|
b.pb.uint64Opt(tagFunction_ID, fn.id)
|
|
b.pb.int64Opt(tagFunction_Name, b.stringIndex(fn.name))
|
|
b.pb.int64Opt(tagFunction_SystemName, b.stringIndex(fn.name))
|
|
b.pb.int64Opt(tagFunction_Filename, b.stringIndex(fn.file))
|
|
b.pb.int64Opt(tagFunction_StartLine, fn.startLine)
|
|
b.pb.endMessage(tagProfile_Function, start)
|
|
}
|
|
|
|
b.flush()
|
|
return id
|
|
}
|
|
|
|
func (b *profileBuilder) addMapping(lo uint64, hi uint64, offset uint64, file string, buildID string) {
|
|
b.addMappingEntry(lo, hi, offset, file, buildID, false)
|
|
}
|
|
|
|
func (b *profileBuilder) addMappingEntry(lo uint64, hi uint64, offset uint64, file string, buildID string, fake bool) {
|
|
b.mem = append(b.mem, memMap{
|
|
start: uintptr(lo),
|
|
end: uintptr(hi),
|
|
offset: offset,
|
|
file: file,
|
|
buildID: buildID,
|
|
fake: fake,
|
|
})
|
|
}
|