First Commmit
This commit is contained in:
390
experimental/libbox/internal/oomprofile/builder.go
Normal file
390
experimental/libbox/internal/oomprofile/builder.go
Normal file
@@ -0,0 +1,390 @@
|
||||
//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,
|
||||
})
|
||||
}
|
||||
24
experimental/libbox/internal/oomprofile/defs_darwin_amd64.go
Normal file
24
experimental/libbox/internal/oomprofile/defs_darwin_amd64.go
Normal file
@@ -0,0 +1,24 @@
|
||||
//go:build darwin && amd64
|
||||
|
||||
package oomprofile
|
||||
|
||||
type machVMRegionBasicInfoData struct {
|
||||
Protection int32
|
||||
MaxProtection int32
|
||||
Inheritance uint32
|
||||
Shared uint32
|
||||
Reserved uint32
|
||||
Offset [8]byte
|
||||
Behavior int32
|
||||
UserWiredCount uint16
|
||||
PadCgo1 [2]byte
|
||||
}
|
||||
|
||||
const (
|
||||
_VM_PROT_READ = 0x1
|
||||
_VM_PROT_EXECUTE = 0x4
|
||||
|
||||
_MACH_SEND_INVALID_DEST = 0x10000003
|
||||
|
||||
_MAXPATHLEN = 0x400
|
||||
)
|
||||
24
experimental/libbox/internal/oomprofile/defs_darwin_arm64.go
Normal file
24
experimental/libbox/internal/oomprofile/defs_darwin_arm64.go
Normal file
@@ -0,0 +1,24 @@
|
||||
//go:build darwin && arm64
|
||||
|
||||
package oomprofile
|
||||
|
||||
type machVMRegionBasicInfoData struct {
|
||||
Protection int32
|
||||
MaxProtection int32
|
||||
Inheritance uint32
|
||||
Shared int32
|
||||
Reserved int32
|
||||
Offset [8]byte
|
||||
Behavior int32
|
||||
UserWiredCount uint16
|
||||
PadCgo1 [2]byte
|
||||
}
|
||||
|
||||
const (
|
||||
_VM_PROT_READ = 0x1
|
||||
_VM_PROT_EXECUTE = 0x4
|
||||
|
||||
_MACH_SEND_INVALID_DEST = 0x10000003
|
||||
|
||||
_MAXPATHLEN = 0x400
|
||||
)
|
||||
46
experimental/libbox/internal/oomprofile/linkname.go
Normal file
46
experimental/libbox/internal/oomprofile/linkname.go
Normal file
@@ -0,0 +1,46 @@
|
||||
//go:build darwin || linux || windows
|
||||
|
||||
package oomprofile
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
_ "runtime/pprof"
|
||||
"unsafe"
|
||||
_ "unsafe"
|
||||
)
|
||||
|
||||
//go:linkname runtimeMemProfileInternal runtime.pprof_memProfileInternal
|
||||
func runtimeMemProfileInternal(p []memProfileRecord, inuseZero bool) (n int, ok bool)
|
||||
|
||||
//go:linkname runtimeBlockProfileInternal runtime.pprof_blockProfileInternal
|
||||
func runtimeBlockProfileInternal(p []blockProfileRecord) (n int, ok bool)
|
||||
|
||||
//go:linkname runtimeMutexProfileInternal runtime.pprof_mutexProfileInternal
|
||||
func runtimeMutexProfileInternal(p []blockProfileRecord) (n int, ok bool)
|
||||
|
||||
//go:linkname runtimeThreadCreateInternal runtime.pprof_threadCreateInternal
|
||||
func runtimeThreadCreateInternal(p []stackRecord) (n int, ok bool)
|
||||
|
||||
//go:linkname runtimeGoroutineProfileWithLabels runtime.pprof_goroutineProfileWithLabels
|
||||
func runtimeGoroutineProfileWithLabels(p []stackRecord, labels []unsafe.Pointer) (n int, ok bool)
|
||||
|
||||
//go:linkname runtimeCyclesPerSecond runtime/pprof.runtime_cyclesPerSecond
|
||||
func runtimeCyclesPerSecond() int64
|
||||
|
||||
//go:linkname runtimeMakeProfStack runtime.pprof_makeProfStack
|
||||
func runtimeMakeProfStack() []uintptr
|
||||
|
||||
//go:linkname runtimeFrameStartLine runtime/pprof.runtime_FrameStartLine
|
||||
func runtimeFrameStartLine(f *runtime.Frame) int
|
||||
|
||||
//go:linkname runtimeFrameSymbolName runtime/pprof.runtime_FrameSymbolName
|
||||
func runtimeFrameSymbolName(f *runtime.Frame) string
|
||||
|
||||
//go:linkname runtimeExpandFinalInlineFrame runtime/pprof.runtime_expandFinalInlineFrame
|
||||
func runtimeExpandFinalInlineFrame(stk []uintptr) []uintptr
|
||||
|
||||
//go:linkname stdParseProcSelfMaps runtime/pprof.parseProcSelfMaps
|
||||
func stdParseProcSelfMaps(data []byte, addMapping func(lo uint64, hi uint64, offset uint64, file string, buildID string))
|
||||
|
||||
//go:linkname stdELFBuildID runtime/pprof.elfBuildID
|
||||
func stdELFBuildID(file string) (string, error)
|
||||
56
experimental/libbox/internal/oomprofile/mapping_darwin.go
Normal file
56
experimental/libbox/internal/oomprofile/mapping_darwin.go
Normal file
@@ -0,0 +1,56 @@
|
||||
//go:build darwin
|
||||
|
||||
package oomprofile
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"os"
|
||||
"unsafe"
|
||||
_ "unsafe"
|
||||
)
|
||||
|
||||
func isExecutable(protection int32) bool {
|
||||
return (protection&_VM_PROT_EXECUTE) != 0 && (protection&_VM_PROT_READ) != 0
|
||||
}
|
||||
|
||||
func (b *profileBuilder) readMapping() {
|
||||
if !machVMInfo(b.addMapping) {
|
||||
b.addMappingEntry(0, 0, 0, "", "", true)
|
||||
}
|
||||
}
|
||||
|
||||
func machVMInfo(addMapping func(lo uint64, hi uint64, off uint64, file string, buildID string)) bool {
|
||||
added := false
|
||||
addr := uint64(0x1)
|
||||
for {
|
||||
var regionSize uint64
|
||||
var info machVMRegionBasicInfoData
|
||||
kr := machVMRegion(&addr, ®ionSize, unsafe.Pointer(&info))
|
||||
if kr != 0 {
|
||||
if kr == _MACH_SEND_INVALID_DEST {
|
||||
return true
|
||||
}
|
||||
return added
|
||||
}
|
||||
if isExecutable(info.Protection) {
|
||||
addMapping(addr, addr+regionSize, binary.LittleEndian.Uint64(info.Offset[:]), regionFilename(addr), "")
|
||||
added = true
|
||||
}
|
||||
addr += regionSize
|
||||
}
|
||||
}
|
||||
|
||||
func regionFilename(address uint64) string {
|
||||
buf := make([]byte, _MAXPATHLEN)
|
||||
n := procRegionFilename(os.Getpid(), address, unsafe.SliceData(buf), int64(cap(buf)))
|
||||
if n == 0 {
|
||||
return ""
|
||||
}
|
||||
return string(buf[:n])
|
||||
}
|
||||
|
||||
//go:linkname machVMRegion runtime/pprof.mach_vm_region
|
||||
func machVMRegion(address *uint64, regionSize *uint64, info unsafe.Pointer) int32
|
||||
|
||||
//go:linkname procRegionFilename runtime/pprof.proc_regionfilename
|
||||
func procRegionFilename(pid int, address uint64, buf *byte, buflen int64) int32
|
||||
13
experimental/libbox/internal/oomprofile/mapping_linux.go
Normal file
13
experimental/libbox/internal/oomprofile/mapping_linux.go
Normal file
@@ -0,0 +1,13 @@
|
||||
//go:build linux
|
||||
|
||||
package oomprofile
|
||||
|
||||
import "os"
|
||||
|
||||
func (b *profileBuilder) readMapping() {
|
||||
data, _ := os.ReadFile("/proc/self/maps")
|
||||
stdParseProcSelfMaps(data, b.addMapping)
|
||||
if len(b.mem) == 0 {
|
||||
b.addMappingEntry(0, 0, 0, "", "", true)
|
||||
}
|
||||
}
|
||||
58
experimental/libbox/internal/oomprofile/mapping_windows.go
Normal file
58
experimental/libbox/internal/oomprofile/mapping_windows.go
Normal file
@@ -0,0 +1,58 @@
|
||||
//go:build windows
|
||||
|
||||
package oomprofile
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func (b *profileBuilder) readMapping() {
|
||||
snapshot, err := createModuleSnapshot()
|
||||
if err != nil {
|
||||
b.addMappingEntry(0, 0, 0, "", "", true)
|
||||
return
|
||||
}
|
||||
defer windows.CloseHandle(snapshot)
|
||||
|
||||
var module windows.ModuleEntry32
|
||||
module.Size = uint32(windows.SizeofModuleEntry32)
|
||||
err = windows.Module32First(snapshot, &module)
|
||||
if err != nil {
|
||||
b.addMappingEntry(0, 0, 0, "", "", true)
|
||||
return
|
||||
}
|
||||
for err == nil {
|
||||
exe := windows.UTF16ToString(module.ExePath[:])
|
||||
b.addMappingEntry(
|
||||
uint64(module.ModBaseAddr),
|
||||
uint64(module.ModBaseAddr)+uint64(module.ModBaseSize),
|
||||
0,
|
||||
exe,
|
||||
peBuildID(exe),
|
||||
false,
|
||||
)
|
||||
err = windows.Module32Next(snapshot, &module)
|
||||
}
|
||||
}
|
||||
|
||||
func createModuleSnapshot() (windows.Handle, error) {
|
||||
for {
|
||||
snapshot, err := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE|windows.TH32CS_SNAPMODULE32, uint32(windows.GetCurrentProcessId()))
|
||||
var errno windows.Errno
|
||||
if err != nil && errors.As(err, &errno) && errno == windows.ERROR_BAD_LENGTH {
|
||||
continue
|
||||
}
|
||||
return snapshot, err
|
||||
}
|
||||
}
|
||||
|
||||
func peBuildID(file string) string {
|
||||
info, err := os.Stat(file)
|
||||
if err != nil {
|
||||
return file
|
||||
}
|
||||
return file + info.ModTime().String()
|
||||
}
|
||||
383
experimental/libbox/internal/oomprofile/oomprofile.go
Normal file
383
experimental/libbox/internal/oomprofile/oomprofile.go
Normal file
@@ -0,0 +1,383 @@
|
||||
//go:build darwin || linux || windows
|
||||
|
||||
package oomprofile
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type stackRecord struct {
|
||||
Stack []uintptr
|
||||
}
|
||||
|
||||
type memProfileRecord struct {
|
||||
AllocBytes, FreeBytes int64
|
||||
AllocObjects, FreeObjects int64
|
||||
Stack []uintptr
|
||||
}
|
||||
|
||||
func (r *memProfileRecord) InUseBytes() int64 {
|
||||
return r.AllocBytes - r.FreeBytes
|
||||
}
|
||||
|
||||
func (r *memProfileRecord) InUseObjects() int64 {
|
||||
return r.AllocObjects - r.FreeObjects
|
||||
}
|
||||
|
||||
type blockProfileRecord struct {
|
||||
Count int64
|
||||
Cycles int64
|
||||
Stack []uintptr
|
||||
}
|
||||
|
||||
type label struct {
|
||||
key string
|
||||
value string
|
||||
}
|
||||
|
||||
type labelSet struct {
|
||||
list []label
|
||||
}
|
||||
|
||||
type labelMap struct {
|
||||
labelSet
|
||||
}
|
||||
|
||||
func WriteFile(destPath string, name string) (string, error) {
|
||||
writer, ok := profileWriters[name]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("unsupported profile %q", name)
|
||||
}
|
||||
|
||||
filePath := filepath.Join(destPath, name+".pb")
|
||||
file, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if err := writer(file); err != nil {
|
||||
_ = os.Remove(filePath)
|
||||
return "", err
|
||||
}
|
||||
if err := file.Close(); err != nil {
|
||||
_ = os.Remove(filePath)
|
||||
return "", err
|
||||
}
|
||||
return filePath, nil
|
||||
}
|
||||
|
||||
var profileWriters = map[string]func(io.Writer) error{
|
||||
"allocs": writeAlloc,
|
||||
"block": writeBlock,
|
||||
"goroutine": writeGoroutine,
|
||||
"heap": writeHeap,
|
||||
"mutex": writeMutex,
|
||||
"threadcreate": writeThreadCreate,
|
||||
}
|
||||
|
||||
func writeHeap(w io.Writer) error {
|
||||
return writeHeapInternal(w, "")
|
||||
}
|
||||
|
||||
func writeAlloc(w io.Writer) error {
|
||||
return writeHeapInternal(w, "alloc_space")
|
||||
}
|
||||
|
||||
func writeHeapInternal(w io.Writer, defaultSampleType string) error {
|
||||
var profile []memProfileRecord
|
||||
n, _ := runtimeMemProfileInternal(nil, true)
|
||||
var ok bool
|
||||
for {
|
||||
profile = make([]memProfileRecord, n+50)
|
||||
n, ok = runtimeMemProfileInternal(profile, true)
|
||||
if ok {
|
||||
profile = profile[:n]
|
||||
break
|
||||
}
|
||||
}
|
||||
return writeHeapProto(w, profile, int64(runtime.MemProfileRate), defaultSampleType)
|
||||
}
|
||||
|
||||
func writeGoroutine(w io.Writer) error {
|
||||
return writeRuntimeProfile(w, "goroutine", runtimeGoroutineProfileWithLabels)
|
||||
}
|
||||
|
||||
func writeThreadCreate(w io.Writer) error {
|
||||
return writeRuntimeProfile(w, "threadcreate", func(p []stackRecord, _ []unsafe.Pointer) (int, bool) {
|
||||
return runtimeThreadCreateInternal(p)
|
||||
})
|
||||
}
|
||||
|
||||
func writeRuntimeProfile(w io.Writer, name string, fetch func([]stackRecord, []unsafe.Pointer) (int, bool)) error {
|
||||
var profile []stackRecord
|
||||
var labels []unsafe.Pointer
|
||||
|
||||
n, _ := fetch(nil, nil)
|
||||
var ok bool
|
||||
for {
|
||||
profile = make([]stackRecord, n+10)
|
||||
labels = make([]unsafe.Pointer, n+10)
|
||||
n, ok = fetch(profile, labels)
|
||||
if ok {
|
||||
profile = profile[:n]
|
||||
labels = labels[:n]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return writeCountProfile(w, name, &runtimeProfile{profile, labels})
|
||||
}
|
||||
|
||||
func writeBlock(w io.Writer) error {
|
||||
return writeCycleProfile(w, "contentions", "delay", runtimeBlockProfileInternal)
|
||||
}
|
||||
|
||||
func writeMutex(w io.Writer) error {
|
||||
return writeCycleProfile(w, "contentions", "delay", runtimeMutexProfileInternal)
|
||||
}
|
||||
|
||||
func writeCycleProfile(w io.Writer, countName string, cycleName string, fetch func([]blockProfileRecord) (int, bool)) error {
|
||||
var profile []blockProfileRecord
|
||||
n, _ := fetch(nil)
|
||||
var ok bool
|
||||
for {
|
||||
profile = make([]blockProfileRecord, n+50)
|
||||
n, ok = fetch(profile)
|
||||
if ok {
|
||||
profile = profile[:n]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(profile, func(i, j int) bool {
|
||||
return profile[i].Cycles > profile[j].Cycles
|
||||
})
|
||||
|
||||
builder := newProfileBuilder(w)
|
||||
builder.pbValueType(tagProfile_PeriodType, countName, "count")
|
||||
builder.pb.int64Opt(tagProfile_Period, 1)
|
||||
builder.pbValueType(tagProfile_SampleType, countName, "count")
|
||||
builder.pbValueType(tagProfile_SampleType, cycleName, "nanoseconds")
|
||||
|
||||
cpuGHz := float64(runtimeCyclesPerSecond()) / 1e9
|
||||
values := []int64{0, 0}
|
||||
var locs []uint64
|
||||
expandedStack := runtimeMakeProfStack()
|
||||
for _, record := range profile {
|
||||
values[0] = record.Count
|
||||
if cpuGHz > 0 {
|
||||
values[1] = int64(float64(record.Cycles) / cpuGHz)
|
||||
} else {
|
||||
values[1] = 0
|
||||
}
|
||||
n := expandInlinedFrames(expandedStack, record.Stack)
|
||||
locs = builder.appendLocsForStack(locs[:0], expandedStack[:n])
|
||||
builder.pbSample(values, locs, nil)
|
||||
}
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
type countProfile interface {
|
||||
Len() int
|
||||
Stack(i int) []uintptr
|
||||
Label(i int) *labelMap
|
||||
}
|
||||
|
||||
type runtimeProfile struct {
|
||||
stk []stackRecord
|
||||
labels []unsafe.Pointer
|
||||
}
|
||||
|
||||
func (p *runtimeProfile) Len() int {
|
||||
return len(p.stk)
|
||||
}
|
||||
|
||||
func (p *runtimeProfile) Stack(i int) []uintptr {
|
||||
return p.stk[i].Stack
|
||||
}
|
||||
|
||||
func (p *runtimeProfile) Label(i int) *labelMap {
|
||||
return (*labelMap)(p.labels[i])
|
||||
}
|
||||
|
||||
func writeCountProfile(w io.Writer, name string, profile countProfile) error {
|
||||
var buf strings.Builder
|
||||
key := func(stk []uintptr, labels *labelMap) string {
|
||||
buf.Reset()
|
||||
buf.WriteByte('@')
|
||||
for _, pc := range stk {
|
||||
fmt.Fprintf(&buf, " %#x", pc)
|
||||
}
|
||||
if labels != nil {
|
||||
buf.WriteString("\n# labels:")
|
||||
for _, label := range labels.list {
|
||||
fmt.Fprintf(&buf, " %q:%q", label.key, label.value)
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
counts := make(map[string]int)
|
||||
index := make(map[string]int)
|
||||
var keys []string
|
||||
for i := 0; i < profile.Len(); i++ {
|
||||
k := key(profile.Stack(i), profile.Label(i))
|
||||
if counts[k] == 0 {
|
||||
index[k] = i
|
||||
keys = append(keys, k)
|
||||
}
|
||||
counts[k]++
|
||||
}
|
||||
|
||||
sort.Sort(&keysByCount{keys: keys, count: counts})
|
||||
|
||||
builder := newProfileBuilder(w)
|
||||
builder.pbValueType(tagProfile_PeriodType, name, "count")
|
||||
builder.pb.int64Opt(tagProfile_Period, 1)
|
||||
builder.pbValueType(tagProfile_SampleType, name, "count")
|
||||
|
||||
values := []int64{0}
|
||||
var locs []uint64
|
||||
for _, k := range keys {
|
||||
values[0] = int64(counts[k])
|
||||
idx := index[k]
|
||||
locs = builder.appendLocsForStack(locs[:0], profile.Stack(idx))
|
||||
|
||||
var labels func()
|
||||
if profile.Label(idx) != nil {
|
||||
labels = func() {
|
||||
for _, label := range profile.Label(idx).list {
|
||||
builder.pbLabel(tagSample_Label, label.key, label.value, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
builder.pbSample(values, locs, labels)
|
||||
}
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
type keysByCount struct {
|
||||
keys []string
|
||||
count map[string]int
|
||||
}
|
||||
|
||||
func (x *keysByCount) Len() int {
|
||||
return len(x.keys)
|
||||
}
|
||||
|
||||
func (x *keysByCount) Swap(i int, j int) {
|
||||
x.keys[i], x.keys[j] = x.keys[j], x.keys[i]
|
||||
}
|
||||
|
||||
func (x *keysByCount) Less(i int, j int) bool {
|
||||
ki, kj := x.keys[i], x.keys[j]
|
||||
ci, cj := x.count[ki], x.count[kj]
|
||||
if ci != cj {
|
||||
return ci > cj
|
||||
}
|
||||
return ki < kj
|
||||
}
|
||||
|
||||
func expandInlinedFrames(dst []uintptr, pcs []uintptr) int {
|
||||
frames := runtime.CallersFrames(pcs)
|
||||
var n int
|
||||
for n < len(dst) {
|
||||
frame, more := frames.Next()
|
||||
dst[n] = frame.PC + 1
|
||||
n++
|
||||
if !more {
|
||||
break
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func writeHeapProto(w io.Writer, profile []memProfileRecord, rate int64, defaultSampleType string) error {
|
||||
builder := newProfileBuilder(w)
|
||||
builder.pbValueType(tagProfile_PeriodType, "space", "bytes")
|
||||
builder.pb.int64Opt(tagProfile_Period, rate)
|
||||
builder.pbValueType(tagProfile_SampleType, "alloc_objects", "count")
|
||||
builder.pbValueType(tagProfile_SampleType, "alloc_space", "bytes")
|
||||
builder.pbValueType(tagProfile_SampleType, "inuse_objects", "count")
|
||||
builder.pbValueType(tagProfile_SampleType, "inuse_space", "bytes")
|
||||
if defaultSampleType != "" {
|
||||
builder.pb.int64Opt(tagProfile_DefaultSampleType, builder.stringIndex(defaultSampleType))
|
||||
}
|
||||
|
||||
values := []int64{0, 0, 0, 0}
|
||||
var locs []uint64
|
||||
for _, record := range profile {
|
||||
hideRuntime := true
|
||||
for tries := 0; tries < 2; tries++ {
|
||||
stk := record.Stack
|
||||
if hideRuntime {
|
||||
for i, addr := range stk {
|
||||
if f := runtime.FuncForPC(addr); f != nil && (strings.HasPrefix(f.Name(), "runtime.") || strings.HasPrefix(f.Name(), "internal/runtime/")) {
|
||||
continue
|
||||
}
|
||||
stk = stk[i:]
|
||||
break
|
||||
}
|
||||
}
|
||||
locs = builder.appendLocsForStack(locs[:0], stk)
|
||||
if len(locs) > 0 {
|
||||
break
|
||||
}
|
||||
hideRuntime = false
|
||||
}
|
||||
|
||||
values[0], values[1] = scaleHeapSample(record.AllocObjects, record.AllocBytes, rate)
|
||||
values[2], values[3] = scaleHeapSample(record.InUseObjects(), record.InUseBytes(), rate)
|
||||
|
||||
var blockSize int64
|
||||
if record.AllocObjects > 0 {
|
||||
blockSize = record.AllocBytes / record.AllocObjects
|
||||
}
|
||||
builder.pbSample(values, locs, func() {
|
||||
if blockSize != 0 {
|
||||
builder.pbLabel(tagSample_Label, "bytes", "", blockSize)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
func scaleHeapSample(count int64, size int64, rate int64) (int64, int64) {
|
||||
if count == 0 || size == 0 {
|
||||
return 0, 0
|
||||
}
|
||||
if rate <= 1 {
|
||||
return count, size
|
||||
}
|
||||
|
||||
avgSize := float64(size) / float64(count)
|
||||
scale := 1 / (1 - math.Exp(-avgSize/float64(rate)))
|
||||
return int64(float64(count) * scale), int64(float64(size) * scale)
|
||||
}
|
||||
|
||||
type profileBuilder struct {
|
||||
start time.Time
|
||||
w io.Writer
|
||||
err error
|
||||
|
||||
pb protobuf
|
||||
strings []string
|
||||
stringMap map[string]int
|
||||
locs map[uintptr]locInfo
|
||||
funcs map[string]int
|
||||
mem []memMap
|
||||
deck pcDeck
|
||||
}
|
||||
120
experimental/libbox/internal/oomprofile/protobuf.go
Normal file
120
experimental/libbox/internal/oomprofile/protobuf.go
Normal file
@@ -0,0 +1,120 @@
|
||||
//go:build darwin || linux || windows
|
||||
|
||||
package oomprofile
|
||||
|
||||
type protobuf struct {
|
||||
data []byte
|
||||
tmp [16]byte
|
||||
nest int
|
||||
}
|
||||
|
||||
func (b *protobuf) varint(x uint64) {
|
||||
for x >= 128 {
|
||||
b.data = append(b.data, byte(x)|0x80)
|
||||
x >>= 7
|
||||
}
|
||||
b.data = append(b.data, byte(x))
|
||||
}
|
||||
|
||||
func (b *protobuf) length(tag int, length int) {
|
||||
b.varint(uint64(tag)<<3 | 2)
|
||||
b.varint(uint64(length))
|
||||
}
|
||||
|
||||
func (b *protobuf) uint64(tag int, x uint64) {
|
||||
b.varint(uint64(tag) << 3)
|
||||
b.varint(x)
|
||||
}
|
||||
|
||||
func (b *protobuf) uint64s(tag int, x []uint64) {
|
||||
if len(x) > 2 {
|
||||
n1 := len(b.data)
|
||||
for _, u := range x {
|
||||
b.varint(u)
|
||||
}
|
||||
n2 := len(b.data)
|
||||
b.length(tag, n2-n1)
|
||||
n3 := len(b.data)
|
||||
copy(b.tmp[:], b.data[n2:n3])
|
||||
copy(b.data[n1+(n3-n2):], b.data[n1:n2])
|
||||
copy(b.data[n1:], b.tmp[:n3-n2])
|
||||
return
|
||||
}
|
||||
for _, u := range x {
|
||||
b.uint64(tag, u)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *protobuf) uint64Opt(tag int, x uint64) {
|
||||
if x == 0 {
|
||||
return
|
||||
}
|
||||
b.uint64(tag, x)
|
||||
}
|
||||
|
||||
func (b *protobuf) int64(tag int, x int64) {
|
||||
b.uint64(tag, uint64(x))
|
||||
}
|
||||
|
||||
func (b *protobuf) int64Opt(tag int, x int64) {
|
||||
if x == 0 {
|
||||
return
|
||||
}
|
||||
b.int64(tag, x)
|
||||
}
|
||||
|
||||
func (b *protobuf) int64s(tag int, x []int64) {
|
||||
if len(x) > 2 {
|
||||
n1 := len(b.data)
|
||||
for _, u := range x {
|
||||
b.varint(uint64(u))
|
||||
}
|
||||
n2 := len(b.data)
|
||||
b.length(tag, n2-n1)
|
||||
n3 := len(b.data)
|
||||
copy(b.tmp[:], b.data[n2:n3])
|
||||
copy(b.data[n1+(n3-n2):], b.data[n1:n2])
|
||||
copy(b.data[n1:], b.tmp[:n3-n2])
|
||||
return
|
||||
}
|
||||
for _, u := range x {
|
||||
b.int64(tag, u)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *protobuf) bool(tag int, x bool) {
|
||||
if x {
|
||||
b.uint64(tag, 1)
|
||||
} else {
|
||||
b.uint64(tag, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *protobuf) string(tag int, x string) {
|
||||
b.length(tag, len(x))
|
||||
b.data = append(b.data, x...)
|
||||
}
|
||||
|
||||
func (b *protobuf) strings(tag int, x []string) {
|
||||
for _, s := range x {
|
||||
b.string(tag, s)
|
||||
}
|
||||
}
|
||||
|
||||
type msgOffset int
|
||||
|
||||
func (b *protobuf) startMessage() msgOffset {
|
||||
b.nest++
|
||||
return msgOffset(len(b.data))
|
||||
}
|
||||
|
||||
func (b *protobuf) endMessage(tag int, start msgOffset) {
|
||||
n1 := int(start)
|
||||
n2 := len(b.data)
|
||||
b.length(tag, n2-n1)
|
||||
n3 := len(b.data)
|
||||
copy(b.tmp[:], b.data[n2:n3])
|
||||
copy(b.data[n1+(n3-n2):], b.data[n1:n2])
|
||||
copy(b.data[n1:], b.tmp[:n3-n2])
|
||||
b.nest--
|
||||
}
|
||||
Reference in New Issue
Block a user