mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-09-15 21:00:27 +00:00
Improve power report
This commit is contained in:
@@ -54,6 +54,11 @@ type InboundContext struct {
|
||||
User string
|
||||
Outbound string
|
||||
|
||||
// power report
|
||||
|
||||
RouteRule string
|
||||
RouteOutbound string
|
||||
|
||||
// sniffer
|
||||
|
||||
Protocol string
|
||||
@@ -173,6 +178,17 @@ func DNSResponseAddresses(response *dns.Msg) []netip.Addr {
|
||||
|
||||
type inboundContextKey struct{}
|
||||
|
||||
type dnsTransportTagKey struct{}
|
||||
|
||||
func ContextWithDNSTransportTag(ctx context.Context, transportTag string) context.Context {
|
||||
return context.WithValue(ctx, (*dnsTransportTagKey)(nil), transportTag)
|
||||
}
|
||||
|
||||
func DNSTransportTagFromContext(ctx context.Context) (string, bool) {
|
||||
transportTag, loaded := ctx.Value((*dnsTransportTagKey)(nil)).(string)
|
||||
return transportTag, loaded
|
||||
}
|
||||
|
||||
func WithContext(ctx context.Context, inboundContext *InboundContext) context.Context {
|
||||
return context.WithValue(ctx, (*inboundContextKey)(nil), inboundContext)
|
||||
}
|
||||
|
||||
+72
-11
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
@@ -42,6 +43,8 @@ type DefaultDialer struct {
|
||||
connectionManager adapter.ConnectionManager
|
||||
networkManager adapter.NetworkManager
|
||||
powerManager *powerreport.Manager
|
||||
outboundManager adapter.OutboundManager
|
||||
dnsTransportManager adapter.DNSTransportManager
|
||||
networkStrategy *C.NetworkStrategy
|
||||
defaultNetworkStrategy bool
|
||||
networkType []C.InterfaceType
|
||||
@@ -235,6 +238,8 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
||||
connectionManager: connectionManager,
|
||||
networkManager: networkManager,
|
||||
powerManager: service.FromContext[*powerreport.Manager](ctx),
|
||||
outboundManager: service.FromContext[adapter.OutboundManager](ctx),
|
||||
dnsTransportManager: service.FromContext[adapter.DNSTransportManager](ctx),
|
||||
networkStrategy: networkStrategy,
|
||||
defaultNetworkStrategy: defaultNetworkStrategy,
|
||||
networkType: networkType,
|
||||
@@ -422,7 +427,7 @@ func (d *DefaultDialer) trackConn(ctx context.Context, destination M.Socksaddr,
|
||||
recorder := d.powerManager.Recorder()
|
||||
if recorder != nil {
|
||||
recorder.CountConnectionOpened()
|
||||
attribution := dialAttribution(ctx, destination)
|
||||
attribution := d.dialAttribution(ctx, destination)
|
||||
conn = bufio.NewCounterConn(conn, []N.CountFunc{func(n int64) {
|
||||
recorder.Touch(powerreport.DirectionInbound, int(n), attribution)
|
||||
}}, []N.CountFunc{func(n int64) {
|
||||
@@ -444,7 +449,7 @@ func (d *DefaultDialer) trackPacketConn(ctx context.Context, destination M.Socks
|
||||
recorder := d.powerManager.Recorder()
|
||||
if recorder != nil {
|
||||
recorder.CountConnectionOpened()
|
||||
attribution := dialAttribution(ctx, destination)
|
||||
attribution := d.dialAttribution(ctx, destination)
|
||||
conn = bufio.NewNetPacketConn(bufio.NewCounterPacketConn(bufio.NewPacketConn(conn), []N.CountFunc{func(n int64) {
|
||||
recorder.Touch(powerreport.DirectionInbound, int(n), attribution)
|
||||
}}, []N.CountFunc{func(n int64) {
|
||||
@@ -455,24 +460,80 @@ func (d *DefaultDialer) trackPacketConn(ctx context.Context, destination M.Socks
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func dialAttribution(ctx context.Context, destination M.Socksaddr) *powerreport.Attribution {
|
||||
func (d *DefaultDialer) dialAttribution(ctx context.Context, destination M.Socksaddr) *powerreport.Attribution {
|
||||
attribution := &powerreport.Attribution{}
|
||||
dnsTransportTag, hasDNSTransport := adapter.DNSTransportTagFromContext(ctx)
|
||||
if hasDNSTransport {
|
||||
attribution.DNS = dnsTransportTag
|
||||
if d.dnsTransportManager != nil {
|
||||
transport, loaded := d.dnsTransportManager.Transport(dnsTransportTag)
|
||||
if loaded {
|
||||
attribution.DNSType = transport.Type()
|
||||
}
|
||||
}
|
||||
}
|
||||
metadata := adapter.ContextFrom(ctx)
|
||||
if metadata == nil {
|
||||
return &powerreport.Attribution{Destination: destination.String()}
|
||||
attribution.Destination = destination.String()
|
||||
return attribution
|
||||
}
|
||||
attribution := &powerreport.Attribution{
|
||||
Domain: metadata.Domain,
|
||||
Outbound: metadata.Outbound,
|
||||
attribution.Inbound = metadata.Inbound
|
||||
attribution.InboundType = metadata.InboundType
|
||||
attribution.Network = metadata.Network
|
||||
if metadata.Source.IsValid() {
|
||||
attribution.Source = metadata.Source.String()
|
||||
}
|
||||
if metadata.Inbound != "" {
|
||||
attribution.Inbound = metadata.InboundType + "/" + metadata.Inbound
|
||||
} else {
|
||||
attribution.Inbound = metadata.InboundType
|
||||
attribution.Domain = metadata.Domain
|
||||
attribution.Protocol = metadata.Protocol
|
||||
attribution.User = metadata.User
|
||||
if metadata.ProcessInfo != nil {
|
||||
attribution.Process = &powerreport.ProcessAttribution{
|
||||
ProcessID: metadata.ProcessInfo.ProcessID,
|
||||
UserID: metadata.ProcessInfo.UserId,
|
||||
UserName: metadata.ProcessInfo.UserName,
|
||||
ProcessPath: metadata.ProcessInfo.ProcessPath,
|
||||
PackageNames: metadata.ProcessInfo.AndroidPackageNames,
|
||||
}
|
||||
}
|
||||
attribution.Rule = metadata.RouteRule
|
||||
attribution.Outbound = metadata.Outbound
|
||||
if d.outboundManager != nil {
|
||||
if metadata.Outbound != "" {
|
||||
outbound, loaded := d.outboundManager.Outbound(metadata.Outbound)
|
||||
if loaded {
|
||||
attribution.OutboundType = outbound.Type()
|
||||
}
|
||||
}
|
||||
if metadata.RouteOutbound != "" {
|
||||
attribution.Chain = d.outboundChain(metadata.RouteOutbound)
|
||||
}
|
||||
}
|
||||
if metadata.Destination.IsValid() {
|
||||
attribution.Destination = metadata.Destination.String()
|
||||
if metadata.Destination != destination {
|
||||
attribution.Server = destination.String()
|
||||
}
|
||||
} else {
|
||||
attribution.Destination = destination.String()
|
||||
}
|
||||
return attribution
|
||||
}
|
||||
|
||||
func (d *DefaultDialer) outboundChain(head string) []string {
|
||||
var chain []string
|
||||
next := head
|
||||
for {
|
||||
detour, loaded := d.outboundManager.Outbound(next)
|
||||
if !loaded {
|
||||
break
|
||||
}
|
||||
chain = append(chain, next)
|
||||
outboundGroup, isGroup := detour.(adapter.OutboundGroup)
|
||||
if !isGroup {
|
||||
break
|
||||
}
|
||||
next = outboundGroup.Now()
|
||||
}
|
||||
slices.Reverse(chain)
|
||||
return chain
|
||||
}
|
||||
|
||||
+3
-14
@@ -321,12 +321,12 @@ func (c *Client) beginExchange(ctx context.Context, transport adapter.DNSTranspo
|
||||
}
|
||||
}
|
||||
|
||||
contextTransport, transportTagLoaded := transportTagFromContext(ctx)
|
||||
contextTransport, transportTagLoaded := adapter.DNSTransportTagFromContext(ctx)
|
||||
if transportTagLoaded && transport.Tag() == contextTransport {
|
||||
operation.release()
|
||||
return nil, nil, exchangeDone, E.New("DNS query loopback in transport[", contextTransport, "]")
|
||||
}
|
||||
operation.ctx = contextWithTransportTag(ctx, transport.Tag())
|
||||
operation.ctx = adapter.ContextWithDNSTransportTag(ctx, transport.Tag())
|
||||
if !disableCache && responseChecker != nil && c.rdrc != nil {
|
||||
rejected := c.rdrc.LoadRDRC(transport.Tag(), question.Name, question.Qtype)
|
||||
if rejected {
|
||||
@@ -644,7 +644,7 @@ func (c *Client) backgroundRefreshDNS(transport adapter.DNSTransport, key dnsCac
|
||||
}
|
||||
go func() {
|
||||
defer c.backgroundRefresh.Delete(key)
|
||||
ctx := contextWithTransportTag(c.ctx, transport.Tag())
|
||||
ctx := adapter.ContextWithDNSTransportTag(c.ctx, transport.Tag())
|
||||
response, err := c.exchangeToTransport(ctx, transport, message, options.Timeout)
|
||||
if err != nil {
|
||||
if c.logger != nil {
|
||||
@@ -750,17 +750,6 @@ func MessageToAddresses(response *dns.Msg) []netip.Addr {
|
||||
return adapter.DNSResponseAddresses(response)
|
||||
}
|
||||
|
||||
type transportKey struct{}
|
||||
|
||||
func contextWithTransportTag(ctx context.Context, transportTag string) context.Context {
|
||||
return context.WithValue(ctx, transportKey{}, transportTag)
|
||||
}
|
||||
|
||||
func transportTagFromContext(ctx context.Context) (string, bool) {
|
||||
value, loaded := ctx.Value(transportKey{}).(string)
|
||||
return value, loaded
|
||||
}
|
||||
|
||||
func FixedResponseStatus(message *dns.Msg, rcode int) *dns.Msg {
|
||||
return &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
|
||||
@@ -27,6 +27,11 @@ func PowerReportOptions(startedService *daemon.StartedService) powerreport.Optio
|
||||
LogCallback: func() []byte {
|
||||
return formatLogEntries(startedService.SavedLog())
|
||||
},
|
||||
ProfileCallback: func(path string) {
|
||||
for _, name := range oomReportProfiles {
|
||||
writeOOMProfile(path, name)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ func ReloadSetupOptions(options *SetupOptions) {
|
||||
sOOMMemoryLimit = oomkiller.DefaultAppleNetworkExtensionMemoryLimit
|
||||
}
|
||||
if sOOMMemoryLimit > 0 {
|
||||
debug.SetMemoryLimit(sOOMMemoryLimit * 3 / 4)
|
||||
debug.SetMemoryLimit(sOOMMemoryLimit * 4 / 5)
|
||||
} else {
|
||||
debug.SetMemoryLimit(math.MaxInt64)
|
||||
}
|
||||
|
||||
@@ -90,7 +90,6 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
|
||||
func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
ctx, metadata := adapter.ExtendContext(ctx)
|
||||
metadata.Outbound = h.Tag()
|
||||
metadata.Destination = destination
|
||||
switch N.NetworkName(network) {
|
||||
case N.NetworkTCP:
|
||||
return h.client.DialContext(ctx)
|
||||
|
||||
@@ -163,6 +163,10 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad
|
||||
for _, buffer := range buffers {
|
||||
conn = bufio.NewCachedConn(conn, buffer)
|
||||
}
|
||||
if selectedRule != nil {
|
||||
metadata.RouteRule = selectedRule.String()
|
||||
}
|
||||
metadata.RouteOutbound = selectedOutbound.Tag()
|
||||
for _, tracker := range r.trackers {
|
||||
conn = tracker.RoutedConnection(ctx, conn, metadata, selectedRule, selectedOutbound)
|
||||
}
|
||||
@@ -291,6 +295,10 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m
|
||||
conn = bufio.NewCachedPacketConn(conn, buffer.Buffer, buffer.Destination)
|
||||
N.PutPacketBuffer(buffer)
|
||||
}
|
||||
if selectedRule != nil {
|
||||
metadata.RouteRule = selectedRule.String()
|
||||
}
|
||||
metadata.RouteOutbound = selectedOutbound.Tag()
|
||||
for _, tracker := range r.trackers {
|
||||
conn = tracker.RoutedPacketConnection(ctx, conn, metadata, selectedRule, selectedOutbound)
|
||||
}
|
||||
|
||||
@@ -15,11 +15,31 @@ func (d Direction) String() string {
|
||||
}
|
||||
|
||||
type Attribution struct {
|
||||
Inbound string `json:"inbound,omitempty"`
|
||||
Domain string `json:"domain,omitempty"`
|
||||
Destination string `json:"destination,omitempty"`
|
||||
Outbound string `json:"outbound,omitempty"`
|
||||
Endpoint string `json:"endpoint,omitempty"`
|
||||
Inbound string `json:"inbound,omitempty"`
|
||||
InboundType string `json:"inboundType,omitempty"`
|
||||
Network string `json:"network,omitempty"`
|
||||
Source string `json:"source,omitempty"`
|
||||
Destination string `json:"destination,omitempty"`
|
||||
Domain string `json:"domain,omitempty"`
|
||||
Protocol string `json:"protocol,omitempty"`
|
||||
User string `json:"user,omitempty"`
|
||||
Process *ProcessAttribution `json:"process,omitempty"`
|
||||
Rule string `json:"rule,omitempty"`
|
||||
Chain []string `json:"chain,omitempty"`
|
||||
Outbound string `json:"outbound,omitempty"`
|
||||
OutboundType string `json:"outboundType,omitempty"`
|
||||
Server string `json:"server,omitempty"`
|
||||
DNS string `json:"dns,omitempty"`
|
||||
DNSType string `json:"dnsType,omitempty"`
|
||||
Endpoint string `json:"endpoint,omitempty"`
|
||||
}
|
||||
|
||||
type ProcessAttribution struct {
|
||||
ProcessID uint32 `json:"processId,omitempty"`
|
||||
UserID int32 `json:"userId,omitempty"`
|
||||
UserName string `json:"userName,omitempty"`
|
||||
ProcessPath string `json:"processPath,omitempty"`
|
||||
PackageNames []string `json:"packageNames,omitempty"`
|
||||
}
|
||||
|
||||
type timelineRow struct {
|
||||
@@ -37,6 +57,10 @@ type timelineRow struct {
|
||||
DiskBytesWritten uint64 `json:"diskWriteBytes,omitempty"`
|
||||
SleptMS int64 `json:"sleptMS,omitempty"`
|
||||
Goroutines uint64 `json:"goroutines,omitempty"`
|
||||
GCCycles uint64 `json:"gcCycles,omitempty"`
|
||||
GoMemoryBytes uint64 `json:"goMemoryBytes,omitempty"`
|
||||
GoHeapLiveBytes uint64 `json:"goHeapLiveBytes,omitempty"`
|
||||
MemoryBytes uint64 `json:"memoryBytes,omitempty"`
|
||||
DNSQueries uint64 `json:"dnsQueries,omitempty"`
|
||||
ConnectionsOpened uint64 `json:"connectionsOpened,omitempty"`
|
||||
InterfacePackets map[string]uint64 `json:"interfacePackets,omitempty"`
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
"github.com/sagernet/sing/common/memory"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -41,6 +42,7 @@ type Options struct {
|
||||
Metadata any
|
||||
OwnerCallback func(path string)
|
||||
LogCallback func() []byte
|
||||
ProfileCallback func(path string)
|
||||
GateInterval time.Duration
|
||||
SampleInterval time.Duration
|
||||
FlushInterval time.Duration
|
||||
@@ -53,6 +55,7 @@ type Recorder struct {
|
||||
metadata any
|
||||
ownerCallback func(path string)
|
||||
logCallback func() []byte
|
||||
profileCallback func(path string)
|
||||
gateNano int64
|
||||
sampleNano int64
|
||||
flushInterval time.Duration
|
||||
@@ -96,6 +99,7 @@ type previousSample struct {
|
||||
at time.Time
|
||||
usage systemUsage
|
||||
gcSeconds float64
|
||||
gcCycles uint64
|
||||
absoluteTime int64
|
||||
continuousTime int64
|
||||
interfaces map[string]interfaceCounters
|
||||
@@ -130,6 +134,7 @@ func NewRecorder(options Options) *Recorder {
|
||||
metadata: options.Metadata,
|
||||
ownerCallback: options.OwnerCallback,
|
||||
logCallback: options.LogCallback,
|
||||
profileCallback: options.ProfileCallback,
|
||||
gateNano: int64(gateInterval),
|
||||
sampleNano: int64(sampleInterval),
|
||||
flushInterval: flushInterval,
|
||||
@@ -139,6 +144,9 @@ func NewRecorder(options Options) *Recorder {
|
||||
metricsSamples: []metrics.Sample{
|
||||
{Name: "/cpu/classes/gc/total:cpu-seconds"},
|
||||
{Name: "/sched/goroutines:goroutines"},
|
||||
{Name: "/gc/cycles/total:gc-cycles"},
|
||||
{Name: "/memory/classes/total:bytes"},
|
||||
{Name: "/gc/heap/live:bytes"},
|
||||
},
|
||||
done: make(chan struct{}),
|
||||
workerDone: make(chan struct{}),
|
||||
@@ -190,7 +198,7 @@ func (r *Recorder) Close() error {
|
||||
r.sampleLocked(now)
|
||||
r.flushLocked(now)
|
||||
r.access.Unlock()
|
||||
r.writeGoroutineProfile()
|
||||
r.writeProfiles()
|
||||
r.writeLog()
|
||||
finalizeDraft(r.draftPath)
|
||||
return nil
|
||||
@@ -340,6 +348,7 @@ func (r *Recorder) resetPreviousLocked(now time.Time) {
|
||||
at: now,
|
||||
usage: readSystemUsage(),
|
||||
gcSeconds: r.metricsSamples[0].Value.Float64(),
|
||||
gcCycles: r.metricsSamples[2].Value.Uint64(),
|
||||
interfaces: readInterfaceCounters(),
|
||||
dnsQueries: r.dnsQueries.Load(),
|
||||
connectionsOpened: r.connectionsOpened.Load(),
|
||||
@@ -356,10 +365,16 @@ func (r *Recorder) sampleLocked(now time.Time) {
|
||||
To: now.UTC().Format(time.RFC3339),
|
||||
CPUGCMS: int64((current.gcSeconds - previous.gcSeconds) * 1000),
|
||||
Goroutines: r.metricsSamples[1].Value.Uint64(),
|
||||
GCCycles: current.gcCycles - previous.gcCycles,
|
||||
GoMemoryBytes: r.metricsSamples[3].Value.Uint64(),
|
||||
GoHeapLiveBytes: r.metricsSamples[4].Value.Uint64(),
|
||||
DNSQueries: current.dnsQueries - previous.dnsQueries,
|
||||
ConnectionsOpened: current.connectionsOpened - previous.connectionsOpened,
|
||||
NetworkType: r.networkType,
|
||||
}
|
||||
if memory.TotalAvailable() {
|
||||
row.MemoryBytes = memory.Total()
|
||||
}
|
||||
if current.usage.valid && previous.usage.valid {
|
||||
row.CPUUserMS = (current.usage.userTime - previous.usage.userTime) / int64(time.Millisecond)
|
||||
row.CPUSystemMS = (current.usage.systemTime - previous.usage.systemTime) / int64(time.Millisecond)
|
||||
@@ -460,7 +475,11 @@ func appendRecords[T any](r *Recorder, path string, records []T) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Recorder) writeGoroutineProfile() {
|
||||
func (r *Recorder) writeProfiles() {
|
||||
if r.profileCallback != nil {
|
||||
r.profileCallback(r.draftPath)
|
||||
return
|
||||
}
|
||||
profilePath := filepath.Join(r.draftPath, goroutineProfileFileName)
|
||||
file, err := os.OpenFile(profilePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o666)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user