First Commmit
This commit is contained in:
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
|
||||
}
|
||||
Reference in New Issue
Block a user