//go:build darwin || linux || windows package libbox import ( "os" "path/filepath" "runtime" "strings" "time" "github.com/sagernet/sing-box/experimental/libbox/internal/oomprofile" "github.com/sagernet/sing-box/service/oomkiller" "github.com/sagernet/sing/common/byteformats" "github.com/sagernet/sing/common/memory" ) func init() { sOOMReporter = &oomReporter{} } var oomReportProfiles = []string{ "allocs", "block", "goroutine", "heap", "mutex", "threadcreate", } type oomReportMetadata struct { reportMetadata RecordedAt string `json:"recordedAt"` MemoryUsage string `json:"memoryUsage"` AvailableMemory string `json:"availableMemory,omitempty"` // Heap HeapAlloc string `json:"heapAlloc,omitempty"` HeapObjects uint64 `json:"heapObjects,omitempty,string"` HeapInuse string `json:"heapInuse,omitempty"` HeapIdle string `json:"heapIdle,omitempty"` HeapReleased string `json:"heapReleased,omitempty"` HeapSys string `json:"heapSys,omitempty"` // Stack StackInuse string `json:"stackInuse,omitempty"` StackSys string `json:"stackSys,omitempty"` // Runtime metadata MSpanInuse string `json:"mSpanInuse,omitempty"` MSpanSys string `json:"mSpanSys,omitempty"` MCacheSys string `json:"mCacheSys,omitempty"` BuckHashSys string `json:"buckHashSys,omitempty"` GCSys string `json:"gcSys,omitempty"` OtherSys string `json:"otherSys,omitempty"` Sys string `json:"sys,omitempty"` // GC & runtime TotalAlloc string `json:"totalAlloc,omitempty"` NumGC uint32 `json:"numGC,omitempty,string"` NumGoroutine int `json:"numGoroutine,omitempty,string"` NextGC string `json:"nextGC,omitempty"` LastGC string `json:"lastGC,omitempty"` } type oomReporter struct{} var _ oomkiller.OOMReporter = (*oomReporter)(nil) func (r *oomReporter) WriteReport(memoryUsage uint64) error { now := time.Now().UTC() reportsDir := filepath.Join(sWorkingPath, "oom_reports") err := os.MkdirAll(reportsDir, 0o777) if err != nil { return err } chownReport(reportsDir) destPath, err := nextAvailableReportPath(reportsDir, now) if err != nil { return err } err = os.MkdirAll(destPath, 0o777) if err != nil { return err } chownReport(destPath) for _, name := range oomReportProfiles { writeOOMProfile(destPath, name) } writeReportFile(destPath, "cmdline", []byte(strings.Join(os.Args, "\000"))) var memStats runtime.MemStats runtime.ReadMemStats(&memStats) metadata := oomReportMetadata{ reportMetadata: baseReportMetadata(), RecordedAt: now.Format(time.RFC3339), MemoryUsage: byteformats.FormatMemoryBytes(memoryUsage), // Heap HeapAlloc: byteformats.FormatMemoryBytes(memStats.HeapAlloc), HeapObjects: memStats.HeapObjects, HeapInuse: byteformats.FormatMemoryBytes(memStats.HeapInuse), HeapIdle: byteformats.FormatMemoryBytes(memStats.HeapIdle), HeapReleased: byteformats.FormatMemoryBytes(memStats.HeapReleased), HeapSys: byteformats.FormatMemoryBytes(memStats.HeapSys), // Stack StackInuse: byteformats.FormatMemoryBytes(memStats.StackInuse), StackSys: byteformats.FormatMemoryBytes(memStats.StackSys), // Runtime metadata MSpanInuse: byteformats.FormatMemoryBytes(memStats.MSpanInuse), MSpanSys: byteformats.FormatMemoryBytes(memStats.MSpanSys), MCacheSys: byteformats.FormatMemoryBytes(memStats.MCacheSys), BuckHashSys: byteformats.FormatMemoryBytes(memStats.BuckHashSys), GCSys: byteformats.FormatMemoryBytes(memStats.GCSys), OtherSys: byteformats.FormatMemoryBytes(memStats.OtherSys), Sys: byteformats.FormatMemoryBytes(memStats.Sys), // GC & runtime TotalAlloc: byteformats.FormatMemoryBytes(memStats.TotalAlloc), NumGC: memStats.NumGC, NumGoroutine: runtime.NumGoroutine(), NextGC: byteformats.FormatMemoryBytes(memStats.NextGC), } if memStats.LastGC > 0 { metadata.LastGC = time.Unix(0, int64(memStats.LastGC)).UTC().Format(time.RFC3339) } availableMemory := memory.Available() if availableMemory > 0 { metadata.AvailableMemory = byteformats.FormatMemoryBytes(availableMemory) } writeReportMetadata(destPath, metadata) copyConfigSnapshot(destPath) return nil } func writeOOMProfile(destPath string, name string) { filePath, err := oomprofile.WriteFile(destPath, name) if err != nil { return } chownReport(filePath) }