mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-09-22 16:06:54 +00:00
259 lines
7.8 KiB
Go
259 lines
7.8 KiB
Go
//go:build darwin || linux || windows
|
|
|
|
package libbox
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"path/filepath"
|
|
"sort"
|
|
"time"
|
|
|
|
"github.com/sagernet/sing-box/common/trafficcontrol"
|
|
"github.com/sagernet/sing-box/daemon"
|
|
"github.com/sagernet/sing-box/experimental/libbox/internal/oomprofile"
|
|
"github.com/sagernet/sing-box/experimental/libbox/internal/runtimeinfo"
|
|
"github.com/sagernet/sing-box/log"
|
|
"github.com/sagernet/sing-box/service/oomkiller"
|
|
"github.com/sagernet/sing/common/byteformats"
|
|
F "github.com/sagernet/sing/common/format"
|
|
)
|
|
|
|
var oomReportProfiles = []string{
|
|
"allocs",
|
|
"block",
|
|
"goroutine",
|
|
"heap",
|
|
"mutex",
|
|
"threadcreate",
|
|
}
|
|
|
|
type oomReportMetadata struct {
|
|
reportMetadata
|
|
RecordedAt string `json:"recordedAt"`
|
|
EndedAt string `json:"endedAt,omitempty"`
|
|
MemoryLimit string `json:"memoryLimit,omitempty"`
|
|
MemoryUsage string `json:"memoryUsage"`
|
|
AvailableMemory string `json:"availableMemory,omitempty"`
|
|
Snapshots int `json:"snapshots,omitempty,string"`
|
|
}
|
|
|
|
func OOMRecorderOptions(startedService *daemon.StartedService) oomkiller.RecorderOptions {
|
|
return oomkiller.RecorderOptions{
|
|
BasePath: sWorkingPath,
|
|
Logger: log.StdLogger(),
|
|
AcceptDraft: acceptOOMDraft,
|
|
MetadataCallback: func(status oomkiller.ReportStatus) any {
|
|
metadata := oomReportMetadata{
|
|
reportMetadata: baseReportMetadata(),
|
|
RecordedAt: status.RecordedAt.UTC().Format(time.RFC3339),
|
|
MemoryUsage: byteformats.FormatMemoryBytes(status.PeakMemory),
|
|
Snapshots: status.Snapshots,
|
|
}
|
|
metadata.StartedAt = status.StartedAt.UTC().Format(time.RFC3339)
|
|
if !status.EndedAt.IsZero() {
|
|
metadata.EndedAt = status.EndedAt.UTC().Format(time.RFC3339)
|
|
}
|
|
if status.MemoryLimit > 0 {
|
|
metadata.MemoryLimit = byteformats.FormatMemoryBytes(status.MemoryLimit)
|
|
}
|
|
if status.AvailableKnown {
|
|
metadata.AvailableMemory = byteformats.FormatMemoryBytes(status.MinAvailable)
|
|
}
|
|
return metadata
|
|
},
|
|
OwnerCallback: chownReport,
|
|
LogCallback: func() []byte {
|
|
return formatLogEntries(startedService.SavedLog())
|
|
},
|
|
SnapshotCallback: func(directory string, prefix string) {
|
|
for _, name := range oomReportProfiles {
|
|
writeOOMProfile(filepath.Join(directory, prefix+"."+name+".pb"), name)
|
|
}
|
|
runtimeInfoPath := filepath.Join(directory, prefix+".runtime.json")
|
|
err := runtimeinfo.WriteFile(runtimeInfoPath)
|
|
if err == nil {
|
|
chownReport(runtimeInfoPath)
|
|
}
|
|
copyConfigSnapshot(directory)
|
|
content := oomConnectionsContent(startedService)
|
|
if content != nil {
|
|
writeReportFile(directory, prefix+".connections.json", content)
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
func acceptOOMDraft(metadataContent []byte) bool {
|
|
var draftMetadata reportMetadata
|
|
err := json.Unmarshal(metadataContent, &draftMetadata)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return draftMetadata.AppVersion == sAppVersion && draftMetadata.AppMarketingVersion == sAppMarketingVersion
|
|
}
|
|
|
|
type oomConnectionsInfo struct {
|
|
UploadTotal string `json:"uploadTotal,omitempty"`
|
|
DownloadTotal string `json:"downloadTotal,omitempty"`
|
|
Connections []oomConnectionInfo `json:"connections"`
|
|
ClosedConnections []oomConnectionInfo `json:"closedConnections,omitempty"`
|
|
}
|
|
|
|
type oomConnectionInfo struct {
|
|
ID string `json:"id"`
|
|
CreatedAt string `json:"createdAt"`
|
|
ClosedAt string `json:"closedAt,omitempty"`
|
|
Inbound string `json:"inbound,omitempty"`
|
|
Network string `json:"network,omitempty"`
|
|
Source string `json:"source,omitempty"`
|
|
Destination string `json:"destination,omitempty"`
|
|
Host string `json:"host,omitempty"`
|
|
User string `json:"user,omitempty"`
|
|
Process string `json:"process,omitempty"`
|
|
Rule string `json:"rule,omitempty"`
|
|
Chain []string `json:"chain,omitempty"`
|
|
Outbound string `json:"outbound,omitempty"`
|
|
OutboundType string `json:"outboundType,omitempty"`
|
|
Upload string `json:"upload,omitempty"`
|
|
Download string `json:"download,omitempty"`
|
|
}
|
|
|
|
func oomConnectionsContent(startedService *daemon.StartedService) []byte {
|
|
instance := startedService.Instance()
|
|
if instance == nil {
|
|
return nil
|
|
}
|
|
trafficManager := instance.TrafficManager()
|
|
if trafficManager == nil {
|
|
return nil
|
|
}
|
|
connections := trafficManager.Connections()
|
|
sort.Slice(connections, func(i, j int) bool {
|
|
return connections[i].CreatedAt.Before(connections[j].CreatedAt)
|
|
})
|
|
uploadTotal, downloadTotal := trafficManager.Total()
|
|
info := oomConnectionsInfo{
|
|
UploadTotal: byteformats.FormatBytes(uint64(uploadTotal)),
|
|
DownloadTotal: byteformats.FormatBytes(uint64(downloadTotal)),
|
|
Connections: buildOOMConnections(connections),
|
|
ClosedConnections: buildOOMConnections(trafficManager.ClosedConnections()),
|
|
}
|
|
data, err := json.MarshalIndent(info, "", " ")
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return data
|
|
}
|
|
|
|
func buildOOMConnections(connections []*trafficcontrol.TrackerMetadata) []oomConnectionInfo {
|
|
result := make([]oomConnectionInfo, 0, len(connections))
|
|
for _, connection := range connections {
|
|
result = append(result, buildOOMConnection(connection))
|
|
}
|
|
return result
|
|
}
|
|
|
|
func buildOOMConnection(connection *trafficcontrol.TrackerMetadata) oomConnectionInfo {
|
|
metadata := connection.Metadata
|
|
var inbound string
|
|
if metadata.Inbound != "" {
|
|
inbound = metadata.InboundType + "/" + metadata.Inbound
|
|
} else {
|
|
inbound = metadata.InboundType
|
|
}
|
|
var process string
|
|
if processInfo := metadata.ProcessInfo; processInfo != nil {
|
|
if processInfo.ProcessPath != "" {
|
|
process = processInfo.ProcessPath
|
|
} else if len(processInfo.AndroidPackageNames) > 0 {
|
|
process = processInfo.AndroidPackageNames[0]
|
|
}
|
|
if process == "" {
|
|
if processInfo.UserId != -1 {
|
|
process = F.ToString(processInfo.UserId)
|
|
}
|
|
} else if processInfo.UserName != "" {
|
|
process = F.ToString(process, " (", processInfo.UserName, ")")
|
|
} else if processInfo.UserId != -1 {
|
|
process = F.ToString(process, " (", processInfo.UserId, ")")
|
|
}
|
|
}
|
|
var rule string
|
|
if connection.Rule != nil {
|
|
rule = F.ToString(connection.Rule, " => ", connection.Rule.Action())
|
|
} else {
|
|
rule = "final"
|
|
}
|
|
info := oomConnectionInfo{
|
|
ID: connection.ID.String(),
|
|
CreatedAt: connection.CreatedAt.UTC().Format(time.RFC3339),
|
|
Inbound: inbound,
|
|
Network: metadata.Network,
|
|
Source: metadata.Source.String(),
|
|
Destination: metadata.Destination.String(),
|
|
Host: metadata.Domain,
|
|
User: metadata.User,
|
|
Process: process,
|
|
Rule: rule,
|
|
Chain: connection.Chain,
|
|
Outbound: connection.Outbound,
|
|
OutboundType: connection.OutboundType,
|
|
Upload: byteformats.FormatBytes(uint64(connection.Upload.Load())),
|
|
Download: byteformats.FormatBytes(uint64(connection.Download.Load())),
|
|
}
|
|
if !connection.ClosedAt.IsZero() {
|
|
info.ClosedAt = connection.ClosedAt.UTC().Format(time.RFC3339)
|
|
}
|
|
return info
|
|
}
|
|
|
|
func formatLogEntries(entries []*log.Entry) []byte {
|
|
if len(entries) == 0 {
|
|
return nil
|
|
}
|
|
var buffer bytes.Buffer
|
|
for _, entry := range entries {
|
|
writeWithoutColors(&buffer, entry.Message)
|
|
buffer.WriteByte('\n')
|
|
}
|
|
return buffer.Bytes()
|
|
}
|
|
|
|
func writeWithoutColors(buffer *bytes.Buffer, message string) {
|
|
start := 0
|
|
for index := 0; index < len(message); {
|
|
if message[index] != '\x1b' || index+1 >= len(message) || message[index+1] != '[' {
|
|
index++
|
|
continue
|
|
}
|
|
end := index + 2
|
|
for end < len(message) && message[end] != 'm' {
|
|
end++
|
|
}
|
|
if end >= len(message) {
|
|
break
|
|
}
|
|
buffer.WriteString(message[start:index])
|
|
index = end + 1
|
|
start = index
|
|
}
|
|
buffer.WriteString(message[start:])
|
|
}
|
|
|
|
func writeOOMProfile(filePath string, name string) {
|
|
err := oomprofile.WriteFile(filePath, name)
|
|
if err != nil {
|
|
return
|
|
}
|
|
chownReport(filePath)
|
|
}
|
|
|
|
func PromoteOOMDraft() {
|
|
oomkiller.PromoteDraft(sWorkingPath, acceptOOMDraft)
|
|
}
|
|
|
|
func PromoteOOMDraftAt(workingPath string) {
|
|
oomkiller.PromoteDraft(workingPath, acceptOOMDraft)
|
|
}
|