First Commmit

This commit is contained in:
CN-JS-HuiBai
2026-04-14 22:41:14 +08:00
commit 9f867b19da
1086 changed files with 147554 additions and 0 deletions

View 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,
})
}

View 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
)

View 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
)

View 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)

View 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, &regionSize, 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

View 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)
}
}

View 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()
}

View 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
}

View 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--
}