Improve power report

This commit is contained in:
世界
2026-08-30 17:41:47 +08:00
parent 3f986d584e
commit c53c2375c4
9 changed files with 155 additions and 34 deletions
+16
View File
@@ -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
View File
@@ -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
View File
@@ -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{
+5
View File
@@ -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)
}
},
}
}
+1 -1
View File
@@ -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)
}
-1
View File
@@ -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)
+8
View File
@@ -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)
}
+29 -5
View File
@@ -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"`
+21 -2
View File
@@ -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 {