mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-09-15 21:00:27 +00:00
Add power report service
This commit is contained in:
+70
-12
@@ -12,7 +12,9 @@ import (
|
||||
"github.com/sagernet/sing-box/common/listener"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-box/service/powerreport"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
@@ -39,6 +41,7 @@ type DefaultDialer struct {
|
||||
autoDetectBindFunc control.Func
|
||||
connectionManager adapter.ConnectionManager
|
||||
networkManager adapter.NetworkManager
|
||||
powerManager *powerreport.Manager
|
||||
networkStrategy *C.NetworkStrategy
|
||||
defaultNetworkStrategy bool
|
||||
networkType []C.InterfaceType
|
||||
@@ -231,6 +234,7 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
||||
autoDetectBindFunc: autoDetectBindFunc,
|
||||
connectionManager: connectionManager,
|
||||
networkManager: networkManager,
|
||||
powerManager: service.FromContext[*powerreport.Manager](ctx),
|
||||
networkStrategy: networkStrategy,
|
||||
defaultNetworkStrategy: defaultNetworkStrategy,
|
||||
networkType: networkType,
|
||||
@@ -262,7 +266,7 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address
|
||||
return nil, E.New("domain not resolved")
|
||||
}
|
||||
if d.networkStrategy == nil {
|
||||
return d.trackConn(listener.ListenNetworkNamespace[net.Conn](ctx, d.netns, func() (net.Conn, error) {
|
||||
conn, err := listener.ListenNetworkNamespace[net.Conn](ctx, d.netns, func() (net.Conn, error) {
|
||||
switch N.NetworkName(network) {
|
||||
case N.NetworkUDP:
|
||||
if !address.IsIPv6() {
|
||||
@@ -276,7 +280,8 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address
|
||||
} else {
|
||||
return DialSlowContext(&d.dialer6, ctx, network, address)
|
||||
}
|
||||
}))
|
||||
})
|
||||
return d.trackConn(ctx, address, conn, err)
|
||||
} else {
|
||||
return d.DialParallelInterface(ctx, network, address, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay)
|
||||
}
|
||||
@@ -327,12 +332,12 @@ func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network strin
|
||||
if !fastFallback && !isPrimary {
|
||||
d.networkLastFallback.Store(time.Now())
|
||||
}
|
||||
return d.trackConn(conn, nil)
|
||||
return d.trackConn(ctx, address, conn, nil)
|
||||
}
|
||||
|
||||
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
if d.networkStrategy == nil {
|
||||
return d.trackPacketConn(listener.ListenNetworkNamespace[net.PacketConn](ctx, d.netns, func() (net.PacketConn, error) {
|
||||
packetConn, err := listener.ListenNetworkNamespace[net.PacketConn](ctx, d.netns, func() (net.PacketConn, error) {
|
||||
listenConfig := d.udpListener
|
||||
if d.autoDetectBindFunc != nil {
|
||||
listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error {
|
||||
@@ -349,7 +354,8 @@ func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksadd
|
||||
} else {
|
||||
return listenConfig.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4)
|
||||
}
|
||||
}))
|
||||
})
|
||||
return d.trackPacketConn(ctx, destination, packetConn, err)
|
||||
} else {
|
||||
return d.ListenSerialInterfacePacket(ctx, destination, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay)
|
||||
}
|
||||
@@ -393,7 +399,7 @@ func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destina
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return d.trackPacketConn(packetConn, nil)
|
||||
return d.trackPacketConn(ctx, destination, packetConn, nil)
|
||||
}
|
||||
|
||||
func (d *DefaultDialer) UDPListenerControl() (control.Func, bool) {
|
||||
@@ -405,16 +411,68 @@ func (d *DefaultDialer) UDPListenerControl() (control.Func, bool) {
|
||||
return listenerControl, egressEnabled
|
||||
}
|
||||
|
||||
func (d *DefaultDialer) trackConn(conn net.Conn, err error) (net.Conn, error) {
|
||||
if d.connectionManager == nil || err != nil {
|
||||
func (d *DefaultDialer) trackConn(ctx context.Context, destination M.Socksaddr, conn net.Conn, err error) (net.Conn, error) {
|
||||
if err != nil {
|
||||
return conn, err
|
||||
}
|
||||
return d.connectionManager.TrackConn(conn), nil
|
||||
if d.connectionManager != nil {
|
||||
conn = d.connectionManager.TrackConn(conn)
|
||||
}
|
||||
if d.powerManager != nil {
|
||||
recorder := d.powerManager.Recorder()
|
||||
if recorder != nil {
|
||||
recorder.CountConnectionOpened()
|
||||
attribution := dialAttribution(ctx, destination)
|
||||
conn = bufio.NewCounterConn(conn, []N.CountFunc{func(n int64) {
|
||||
recorder.Touch(powerreport.DirectionInbound, int(n), attribution)
|
||||
}}, []N.CountFunc{func(n int64) {
|
||||
recorder.Touch(powerreport.DirectionOutbound, int(n), attribution)
|
||||
}})
|
||||
}
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (d *DefaultDialer) trackPacketConn(conn net.PacketConn, err error) (net.PacketConn, error) {
|
||||
if d.connectionManager == nil || err != nil {
|
||||
func (d *DefaultDialer) trackPacketConn(ctx context.Context, destination M.Socksaddr, conn net.PacketConn, err error) (net.PacketConn, error) {
|
||||
if err != nil {
|
||||
return conn, err
|
||||
}
|
||||
return d.connectionManager.TrackPacketConn(conn), nil
|
||||
if d.connectionManager != nil {
|
||||
conn = d.connectionManager.TrackPacketConn(conn)
|
||||
}
|
||||
if d.powerManager != nil {
|
||||
recorder := d.powerManager.Recorder()
|
||||
if recorder != nil {
|
||||
recorder.CountConnectionOpened()
|
||||
attribution := 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) {
|
||||
recorder.Touch(powerreport.DirectionOutbound, int(n), attribution)
|
||||
}}))
|
||||
}
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func dialAttribution(ctx context.Context, destination M.Socksaddr) *powerreport.Attribution {
|
||||
metadata := adapter.ContextFrom(ctx)
|
||||
if metadata == nil {
|
||||
return &powerreport.Attribution{Destination: destination.String()}
|
||||
}
|
||||
attribution := &powerreport.Attribution{
|
||||
Domain: metadata.Domain,
|
||||
Outbound: metadata.Outbound,
|
||||
}
|
||||
if metadata.Inbound != "" {
|
||||
attribution.Inbound = metadata.InboundType + "/" + metadata.Inbound
|
||||
} else {
|
||||
attribution.Inbound = metadata.InboundType
|
||||
}
|
||||
if metadata.Destination.IsValid() {
|
||||
attribution.Destination = metadata.Destination.String()
|
||||
} else {
|
||||
attribution.Destination = destination.String()
|
||||
}
|
||||
return attribution
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
R "github.com/sagernet/sing-box/route/rule"
|
||||
"github.com/sagernet/sing-box/service/powerreport"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
@@ -39,6 +40,7 @@ type Router struct {
|
||||
logger logger.ContextLogger
|
||||
transport adapter.DNSTransportManager
|
||||
outbound adapter.OutboundManager
|
||||
powerManager *powerreport.Manager
|
||||
client adapter.DNSClient
|
||||
rawRules []option.DNSRule
|
||||
rules []adapter.DNSRule
|
||||
@@ -57,6 +59,7 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.DNSOp
|
||||
logger: logFactory.NewLogger("dns"),
|
||||
transport: service.FromContext[adapter.DNSTransportManager](ctx),
|
||||
outbound: service.FromContext[adapter.OutboundManager](ctx),
|
||||
powerManager: service.FromContext[*powerreport.Manager](ctx),
|
||||
rawRules: make([]option.DNSRule, 0, len(options.Rules)),
|
||||
rules: make([]adapter.DNSRule, 0, len(options.Rules)),
|
||||
defaultDomainStrategy: C.DomainStrategy(options.Strategy),
|
||||
@@ -1053,6 +1056,12 @@ type dnsExchangeContext struct {
|
||||
}
|
||||
|
||||
func (r *Router) prepareExchange(ctx context.Context, message *mDNS.Msg) (*dnsExchangeContext, *mDNS.Msg, error) {
|
||||
if r.powerManager != nil {
|
||||
recorder := r.powerManager.Recorder()
|
||||
if recorder != nil {
|
||||
recorder.CountDNSQuery()
|
||||
}
|
||||
}
|
||||
if len(message.Question) != 1 {
|
||||
r.logger.WarnContext(ctx, "bad question size: ", len(message.Question))
|
||||
return nil, &mDNS.Msg{
|
||||
|
||||
@@ -65,6 +65,7 @@ func prepareWorkingDirectory() error {
|
||||
return err
|
||||
}
|
||||
libbox.PromoteOOMDraft()
|
||||
libbox.PromotePowerReportDraft()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -87,6 +87,7 @@ func (s *desktopService) StartService(ctx context.Context, request *StartService
|
||||
mergedOptions.OOMKillerEnabled = request.Options.OomKillerEnabled
|
||||
mergedOptions.OOMKillerDisabled = request.Options.OomKillerDisabled
|
||||
mergedOptions.OOMMemoryLimit = request.Options.OomMemoryLimit
|
||||
mergedOptions.PowerReportEnabled = request.Options.PowerReportEnabled
|
||||
}
|
||||
err = s.daemon.startServiceLocked(ctx, identity.UserID, request.ConfigContent, mergedOptions)
|
||||
if err != nil {
|
||||
|
||||
@@ -449,12 +449,13 @@ func (x *StartServiceRequest) GetOptions() *StartOptions {
|
||||
}
|
||||
|
||||
type StartOptions struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
OomKillerEnabled bool `protobuf:"varint,1,opt,name=oom_killer_enabled,json=oomKillerEnabled,proto3" json:"oom_killer_enabled,omitempty"`
|
||||
OomKillerDisabled bool `protobuf:"varint,2,opt,name=oom_killer_disabled,json=oomKillerDisabled,proto3" json:"oom_killer_disabled,omitempty"`
|
||||
OomMemoryLimit int64 `protobuf:"varint,3,opt,name=oom_memory_limit,json=oomMemoryLimit,proto3" json:"oom_memory_limit,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
OomKillerEnabled bool `protobuf:"varint,1,opt,name=oom_killer_enabled,json=oomKillerEnabled,proto3" json:"oom_killer_enabled,omitempty"`
|
||||
OomKillerDisabled bool `protobuf:"varint,2,opt,name=oom_killer_disabled,json=oomKillerDisabled,proto3" json:"oom_killer_disabled,omitempty"`
|
||||
OomMemoryLimit int64 `protobuf:"varint,3,opt,name=oom_memory_limit,json=oomMemoryLimit,proto3" json:"oom_memory_limit,omitempty"`
|
||||
PowerReportEnabled bool `protobuf:"varint,4,opt,name=power_report_enabled,json=powerReportEnabled,proto3" json:"power_report_enabled,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *StartOptions) Reset() {
|
||||
@@ -508,6 +509,13 @@ func (x *StartOptions) GetOomMemoryLimit() int64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *StartOptions) GetPowerReportEnabled() bool {
|
||||
if x != nil {
|
||||
return x.PowerReportEnabled
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type ConfigContent struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Content string `protobuf:"bytes,1,opt,name=content,proto3" json:"content,omitempty"`
|
||||
@@ -1676,11 +1684,12 @@ const file_experimental_boxdd_desktop_service_proto_rawDesc = "" +
|
||||
"\townership\x18\x02 \x01(\x0e2\x18.desktop.DaemonOwnershipR\townership\"m\n" +
|
||||
"\x13StartServiceRequest\x12%\n" +
|
||||
"\x0econfig_content\x18\x01 \x01(\tR\rconfigContent\x12/\n" +
|
||||
"\aoptions\x18\x02 \x01(\v2\x15.desktop.StartOptionsR\aoptions\"\x96\x01\n" +
|
||||
"\aoptions\x18\x02 \x01(\v2\x15.desktop.StartOptionsR\aoptions\"\xc8\x01\n" +
|
||||
"\fStartOptions\x12,\n" +
|
||||
"\x12oom_killer_enabled\x18\x01 \x01(\bR\x10oomKillerEnabled\x12.\n" +
|
||||
"\x13oom_killer_disabled\x18\x02 \x01(\bR\x11oomKillerDisabled\x12(\n" +
|
||||
"\x10oom_memory_limit\x18\x03 \x01(\x03R\x0eoomMemoryLimit\")\n" +
|
||||
"\x10oom_memory_limit\x18\x03 \x01(\x03R\x0eoomMemoryLimit\x120\n" +
|
||||
"\x14power_report_enabled\x18\x04 \x01(\bR\x12powerReportEnabled\")\n" +
|
||||
"\rConfigContent\x12\x18\n" +
|
||||
"\acontent\x18\x01 \x01(\tR\acontent\"\xb0\x02\n" +
|
||||
"\x0eProfileContent\x120\n" +
|
||||
@@ -1767,7 +1776,7 @@ const file_experimental_boxdd_desktop_service_proto_rawDesc = "" +
|
||||
"!INSTALL_UPDATE_RESULT_UNSPECIFIED\x10\x00\x12!\n" +
|
||||
"\x1dINSTALL_UPDATE_RESULT_STARTED\x10\x01\x12)\n" +
|
||||
"%INSTALL_UPDATE_RESULT_SIGNER_MISMATCH\x10\x02\x12#\n" +
|
||||
"\x1fINSTALL_UPDATE_RESULT_NOT_NEWER\x10\x032\x86\r\n" +
|
||||
"\x1fINSTALL_UPDATE_RESULT_NOT_NEWER\x10\x032\xcd\x10\n" +
|
||||
"\x0eDesktopService\x12>\n" +
|
||||
"\rGetDaemonInfo\x12\x16.google.protobuf.Empty\x1a\x13.desktop.DaemonInfo\"\x00\x12@\n" +
|
||||
"\fClaimService\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\"\x00\x12C\n" +
|
||||
@@ -1786,7 +1795,13 @@ const file_experimental_boxdd_desktop_service_proto_rawDesc = "" +
|
||||
"\x11MarkOOMReportRead\x12\x19.desktop.OOMReportRequest\x1a\x16.google.protobuf.Empty\"\x00\x12Q\n" +
|
||||
"\x0fExportOOMReport\x12\x1f.desktop.OOMReportExportRequest\x1a\x1b.desktop.CrashReportArchive\"\x00\x12F\n" +
|
||||
"\x0fDeleteOOMReport\x12\x19.desktop.OOMReportRequest\x1a\x16.google.protobuf.Empty\"\x00\x12G\n" +
|
||||
"\x13DeleteAllOOMReports\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\"\x00\x12P\n" +
|
||||
"\x13DeleteAllOOMReports\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\"\x00\x12D\n" +
|
||||
"\x10ListPowerReports\x12\x16.google.protobuf.Empty\x1a\x16.desktop.OOMReportList\"\x00\x12I\n" +
|
||||
"\x0fReadPowerReport\x12\x19.desktop.OOMReportRequest\x1a\x19.desktop.OOMReportContent\"\x00\x12J\n" +
|
||||
"\x13MarkPowerReportRead\x12\x19.desktop.OOMReportRequest\x1a\x16.google.protobuf.Empty\"\x00\x12S\n" +
|
||||
"\x11ExportPowerReport\x12\x1f.desktop.OOMReportExportRequest\x1a\x1b.desktop.CrashReportArchive\"\x00\x12H\n" +
|
||||
"\x11DeletePowerReport\x12\x19.desktop.OOMReportRequest\x1a\x16.google.protobuf.Empty\"\x00\x12I\n" +
|
||||
"\x15DeleteAllPowerReports\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\"\x00\x12P\n" +
|
||||
"\rInstallUpdate\x12\x1d.desktop.InstallUpdateRequest\x1a\x1e.desktop.InstallUpdateResponse\"\x00\x12J\n" +
|
||||
"\x13GetSecuritySettings\x12\x16.google.protobuf.Empty\x1a\x19.desktop.SecuritySettings\"\x00\x12Z\n" +
|
||||
"\x16SetInsecureModeEnabled\x12&.desktop.SetInsecureModeEnabledRequest\x1a\x16.google.protobuf.Empty\"\x00\x12@\n" +
|
||||
@@ -1881,50 +1896,62 @@ var file_experimental_boxdd_desktop_service_proto_depIdxs = []int32{
|
||||
23, // 23: desktop.DesktopService.ExportOOMReport:input_type -> desktop.OOMReportExportRequest
|
||||
22, // 24: desktop.DesktopService.DeleteOOMReport:input_type -> desktop.OOMReportRequest
|
||||
31, // 25: desktop.DesktopService.DeleteAllOOMReports:input_type -> google.protobuf.Empty
|
||||
29, // 26: desktop.DesktopService.InstallUpdate:input_type -> desktop.InstallUpdateRequest
|
||||
31, // 27: desktop.DesktopService.GetSecuritySettings:input_type -> google.protobuf.Empty
|
||||
27, // 28: desktop.DesktopService.SetInsecureModeEnabled:input_type -> desktop.SetInsecureModeEnabledRequest
|
||||
28, // 29: desktop.DesktopService.SetLocale:input_type -> desktop.SetLocaleRequest
|
||||
9, // 30: desktop.ApplicationService.CheckConfig:input_type -> desktop.ConfigContent
|
||||
9, // 31: desktop.ApplicationService.FormatConfig:input_type -> desktop.ConfigContent
|
||||
31, // 32: desktop.ApplicationService.GenerateConfigSchema:input_type -> google.protobuf.Empty
|
||||
10, // 33: desktop.ApplicationService.EncodeProfile:input_type -> desktop.ProfileContent
|
||||
11, // 34: desktop.ApplicationService.DecodeProfile:input_type -> desktop.ProfileData
|
||||
3, // 35: desktop.ApplicationService.ArchiveReport:input_type -> desktop.ArchiveReportRequest
|
||||
4, // 36: desktop.ApplicationService.StartStandaloneNetworkQualityTest:input_type -> desktop.StandaloneNetworkQualityTestRequest
|
||||
5, // 37: desktop.ApplicationService.StartStandaloneSTUNTest:input_type -> desktop.StandaloneSTUNTestRequest
|
||||
6, // 38: desktop.DesktopService.GetDaemonInfo:output_type -> desktop.DaemonInfo
|
||||
31, // 39: desktop.DesktopService.ClaimService:output_type -> google.protobuf.Empty
|
||||
31, // 40: desktop.DesktopService.TakeOverService:output_type -> google.protobuf.Empty
|
||||
31, // 41: desktop.DesktopService.StartService:output_type -> google.protobuf.Empty
|
||||
12, // 42: desktop.DesktopService.GetWorkingDirectory:output_type -> desktop.WorkingDirectoryInfo
|
||||
31, // 43: desktop.DesktopService.DestroyWorkingDirectory:output_type -> google.protobuf.Empty
|
||||
13, // 44: desktop.DesktopService.ListCrashReports:output_type -> desktop.CrashReportList
|
||||
17, // 45: desktop.DesktopService.ReadCrashReport:output_type -> desktop.CrashReportContent
|
||||
31, // 46: desktop.DesktopService.MarkCrashReportRead:output_type -> google.protobuf.Empty
|
||||
19, // 47: desktop.DesktopService.ExportCrashReport:output_type -> desktop.CrashReportArchive
|
||||
31, // 48: desktop.DesktopService.DeleteCrashReport:output_type -> google.protobuf.Empty
|
||||
31, // 49: desktop.DesktopService.DeleteAllCrashReports:output_type -> google.protobuf.Empty
|
||||
20, // 50: desktop.DesktopService.ListOOMReports:output_type -> desktop.OOMReportList
|
||||
24, // 51: desktop.DesktopService.ReadOOMReport:output_type -> desktop.OOMReportContent
|
||||
31, // 52: desktop.DesktopService.MarkOOMReportRead:output_type -> google.protobuf.Empty
|
||||
19, // 53: desktop.DesktopService.ExportOOMReport:output_type -> desktop.CrashReportArchive
|
||||
31, // 54: desktop.DesktopService.DeleteOOMReport:output_type -> google.protobuf.Empty
|
||||
31, // 55: desktop.DesktopService.DeleteAllOOMReports:output_type -> google.protobuf.Empty
|
||||
30, // 56: desktop.DesktopService.InstallUpdate:output_type -> desktop.InstallUpdateResponse
|
||||
26, // 57: desktop.DesktopService.GetSecuritySettings:output_type -> desktop.SecuritySettings
|
||||
31, // 58: desktop.DesktopService.SetInsecureModeEnabled:output_type -> google.protobuf.Empty
|
||||
31, // 59: desktop.DesktopService.SetLocale:output_type -> google.protobuf.Empty
|
||||
31, // 60: desktop.ApplicationService.CheckConfig:output_type -> google.protobuf.Empty
|
||||
9, // 61: desktop.ApplicationService.FormatConfig:output_type -> desktop.ConfigContent
|
||||
9, // 62: desktop.ApplicationService.GenerateConfigSchema:output_type -> desktop.ConfigContent
|
||||
11, // 63: desktop.ApplicationService.EncodeProfile:output_type -> desktop.ProfileData
|
||||
10, // 64: desktop.ApplicationService.DecodeProfile:output_type -> desktop.ProfileContent
|
||||
31, // 65: desktop.ApplicationService.ArchiveReport:output_type -> google.protobuf.Empty
|
||||
32, // 66: desktop.ApplicationService.StartStandaloneNetworkQualityTest:output_type -> daemon.NetworkQualityTestProgress
|
||||
33, // 67: desktop.ApplicationService.StartStandaloneSTUNTest:output_type -> daemon.STUNTestProgress
|
||||
38, // [38:68] is the sub-list for method output_type
|
||||
8, // [8:38] is the sub-list for method input_type
|
||||
31, // 26: desktop.DesktopService.ListPowerReports:input_type -> google.protobuf.Empty
|
||||
22, // 27: desktop.DesktopService.ReadPowerReport:input_type -> desktop.OOMReportRequest
|
||||
22, // 28: desktop.DesktopService.MarkPowerReportRead:input_type -> desktop.OOMReportRequest
|
||||
23, // 29: desktop.DesktopService.ExportPowerReport:input_type -> desktop.OOMReportExportRequest
|
||||
22, // 30: desktop.DesktopService.DeletePowerReport:input_type -> desktop.OOMReportRequest
|
||||
31, // 31: desktop.DesktopService.DeleteAllPowerReports:input_type -> google.protobuf.Empty
|
||||
29, // 32: desktop.DesktopService.InstallUpdate:input_type -> desktop.InstallUpdateRequest
|
||||
31, // 33: desktop.DesktopService.GetSecuritySettings:input_type -> google.protobuf.Empty
|
||||
27, // 34: desktop.DesktopService.SetInsecureModeEnabled:input_type -> desktop.SetInsecureModeEnabledRequest
|
||||
28, // 35: desktop.DesktopService.SetLocale:input_type -> desktop.SetLocaleRequest
|
||||
9, // 36: desktop.ApplicationService.CheckConfig:input_type -> desktop.ConfigContent
|
||||
9, // 37: desktop.ApplicationService.FormatConfig:input_type -> desktop.ConfigContent
|
||||
31, // 38: desktop.ApplicationService.GenerateConfigSchema:input_type -> google.protobuf.Empty
|
||||
10, // 39: desktop.ApplicationService.EncodeProfile:input_type -> desktop.ProfileContent
|
||||
11, // 40: desktop.ApplicationService.DecodeProfile:input_type -> desktop.ProfileData
|
||||
3, // 41: desktop.ApplicationService.ArchiveReport:input_type -> desktop.ArchiveReportRequest
|
||||
4, // 42: desktop.ApplicationService.StartStandaloneNetworkQualityTest:input_type -> desktop.StandaloneNetworkQualityTestRequest
|
||||
5, // 43: desktop.ApplicationService.StartStandaloneSTUNTest:input_type -> desktop.StandaloneSTUNTestRequest
|
||||
6, // 44: desktop.DesktopService.GetDaemonInfo:output_type -> desktop.DaemonInfo
|
||||
31, // 45: desktop.DesktopService.ClaimService:output_type -> google.protobuf.Empty
|
||||
31, // 46: desktop.DesktopService.TakeOverService:output_type -> google.protobuf.Empty
|
||||
31, // 47: desktop.DesktopService.StartService:output_type -> google.protobuf.Empty
|
||||
12, // 48: desktop.DesktopService.GetWorkingDirectory:output_type -> desktop.WorkingDirectoryInfo
|
||||
31, // 49: desktop.DesktopService.DestroyWorkingDirectory:output_type -> google.protobuf.Empty
|
||||
13, // 50: desktop.DesktopService.ListCrashReports:output_type -> desktop.CrashReportList
|
||||
17, // 51: desktop.DesktopService.ReadCrashReport:output_type -> desktop.CrashReportContent
|
||||
31, // 52: desktop.DesktopService.MarkCrashReportRead:output_type -> google.protobuf.Empty
|
||||
19, // 53: desktop.DesktopService.ExportCrashReport:output_type -> desktop.CrashReportArchive
|
||||
31, // 54: desktop.DesktopService.DeleteCrashReport:output_type -> google.protobuf.Empty
|
||||
31, // 55: desktop.DesktopService.DeleteAllCrashReports:output_type -> google.protobuf.Empty
|
||||
20, // 56: desktop.DesktopService.ListOOMReports:output_type -> desktop.OOMReportList
|
||||
24, // 57: desktop.DesktopService.ReadOOMReport:output_type -> desktop.OOMReportContent
|
||||
31, // 58: desktop.DesktopService.MarkOOMReportRead:output_type -> google.protobuf.Empty
|
||||
19, // 59: desktop.DesktopService.ExportOOMReport:output_type -> desktop.CrashReportArchive
|
||||
31, // 60: desktop.DesktopService.DeleteOOMReport:output_type -> google.protobuf.Empty
|
||||
31, // 61: desktop.DesktopService.DeleteAllOOMReports:output_type -> google.protobuf.Empty
|
||||
20, // 62: desktop.DesktopService.ListPowerReports:output_type -> desktop.OOMReportList
|
||||
24, // 63: desktop.DesktopService.ReadPowerReport:output_type -> desktop.OOMReportContent
|
||||
31, // 64: desktop.DesktopService.MarkPowerReportRead:output_type -> google.protobuf.Empty
|
||||
19, // 65: desktop.DesktopService.ExportPowerReport:output_type -> desktop.CrashReportArchive
|
||||
31, // 66: desktop.DesktopService.DeletePowerReport:output_type -> google.protobuf.Empty
|
||||
31, // 67: desktop.DesktopService.DeleteAllPowerReports:output_type -> google.protobuf.Empty
|
||||
30, // 68: desktop.DesktopService.InstallUpdate:output_type -> desktop.InstallUpdateResponse
|
||||
26, // 69: desktop.DesktopService.GetSecuritySettings:output_type -> desktop.SecuritySettings
|
||||
31, // 70: desktop.DesktopService.SetInsecureModeEnabled:output_type -> google.protobuf.Empty
|
||||
31, // 71: desktop.DesktopService.SetLocale:output_type -> google.protobuf.Empty
|
||||
31, // 72: desktop.ApplicationService.CheckConfig:output_type -> google.protobuf.Empty
|
||||
9, // 73: desktop.ApplicationService.FormatConfig:output_type -> desktop.ConfigContent
|
||||
9, // 74: desktop.ApplicationService.GenerateConfigSchema:output_type -> desktop.ConfigContent
|
||||
11, // 75: desktop.ApplicationService.EncodeProfile:output_type -> desktop.ProfileData
|
||||
10, // 76: desktop.ApplicationService.DecodeProfile:output_type -> desktop.ProfileContent
|
||||
31, // 77: desktop.ApplicationService.ArchiveReport:output_type -> google.protobuf.Empty
|
||||
32, // 78: desktop.ApplicationService.StartStandaloneNetworkQualityTest:output_type -> daemon.NetworkQualityTestProgress
|
||||
33, // 79: desktop.ApplicationService.StartStandaloneSTUNTest:output_type -> daemon.STUNTestProgress
|
||||
44, // [44:80] is the sub-list for method output_type
|
||||
8, // [8:44] is the sub-list for method input_type
|
||||
8, // [8:8] is the sub-list for extension type_name
|
||||
8, // [8:8] is the sub-list for extension extendee
|
||||
0, // [0:8] is the sub-list for field type_name
|
||||
|
||||
@@ -25,6 +25,12 @@ service DesktopService {
|
||||
rpc ExportOOMReport(OOMReportExportRequest) returns (CrashReportArchive) {}
|
||||
rpc DeleteOOMReport(OOMReportRequest) returns (google.protobuf.Empty) {}
|
||||
rpc DeleteAllOOMReports(google.protobuf.Empty) returns (google.protobuf.Empty) {}
|
||||
rpc ListPowerReports(google.protobuf.Empty) returns (OOMReportList) {}
|
||||
rpc ReadPowerReport(OOMReportRequest) returns (OOMReportContent) {}
|
||||
rpc MarkPowerReportRead(OOMReportRequest) returns (google.protobuf.Empty) {}
|
||||
rpc ExportPowerReport(OOMReportExportRequest) returns (CrashReportArchive) {}
|
||||
rpc DeletePowerReport(OOMReportRequest) returns (google.protobuf.Empty) {}
|
||||
rpc DeleteAllPowerReports(google.protobuf.Empty) returns (google.protobuf.Empty) {}
|
||||
rpc InstallUpdate(InstallUpdateRequest) returns (InstallUpdateResponse) {}
|
||||
rpc GetSecuritySettings(google.protobuf.Empty) returns (SecuritySettings) {}
|
||||
rpc SetInsecureModeEnabled(SetInsecureModeEnabledRequest) returns (google.protobuf.Empty) {}
|
||||
@@ -80,6 +86,7 @@ message StartOptions {
|
||||
bool oom_killer_enabled = 1;
|
||||
bool oom_killer_disabled = 2;
|
||||
int64 oom_memory_limit = 3;
|
||||
bool power_report_enabled = 4;
|
||||
}
|
||||
|
||||
message ConfigContent {
|
||||
|
||||
@@ -35,6 +35,12 @@ const (
|
||||
DesktopService_ExportOOMReport_FullMethodName = "/desktop.DesktopService/ExportOOMReport"
|
||||
DesktopService_DeleteOOMReport_FullMethodName = "/desktop.DesktopService/DeleteOOMReport"
|
||||
DesktopService_DeleteAllOOMReports_FullMethodName = "/desktop.DesktopService/DeleteAllOOMReports"
|
||||
DesktopService_ListPowerReports_FullMethodName = "/desktop.DesktopService/ListPowerReports"
|
||||
DesktopService_ReadPowerReport_FullMethodName = "/desktop.DesktopService/ReadPowerReport"
|
||||
DesktopService_MarkPowerReportRead_FullMethodName = "/desktop.DesktopService/MarkPowerReportRead"
|
||||
DesktopService_ExportPowerReport_FullMethodName = "/desktop.DesktopService/ExportPowerReport"
|
||||
DesktopService_DeletePowerReport_FullMethodName = "/desktop.DesktopService/DeletePowerReport"
|
||||
DesktopService_DeleteAllPowerReports_FullMethodName = "/desktop.DesktopService/DeleteAllPowerReports"
|
||||
DesktopService_InstallUpdate_FullMethodName = "/desktop.DesktopService/InstallUpdate"
|
||||
DesktopService_GetSecuritySettings_FullMethodName = "/desktop.DesktopService/GetSecuritySettings"
|
||||
DesktopService_SetInsecureModeEnabled_FullMethodName = "/desktop.DesktopService/SetInsecureModeEnabled"
|
||||
@@ -63,6 +69,12 @@ type DesktopServiceClient interface {
|
||||
ExportOOMReport(ctx context.Context, in *OOMReportExportRequest, opts ...grpc.CallOption) (*CrashReportArchive, error)
|
||||
DeleteOOMReport(ctx context.Context, in *OOMReportRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
DeleteAllOOMReports(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
ListPowerReports(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*OOMReportList, error)
|
||||
ReadPowerReport(ctx context.Context, in *OOMReportRequest, opts ...grpc.CallOption) (*OOMReportContent, error)
|
||||
MarkPowerReportRead(ctx context.Context, in *OOMReportRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
ExportPowerReport(ctx context.Context, in *OOMReportExportRequest, opts ...grpc.CallOption) (*CrashReportArchive, error)
|
||||
DeletePowerReport(ctx context.Context, in *OOMReportRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
DeleteAllPowerReports(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
InstallUpdate(ctx context.Context, in *InstallUpdateRequest, opts ...grpc.CallOption) (*InstallUpdateResponse, error)
|
||||
GetSecuritySettings(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*SecuritySettings, error)
|
||||
SetInsecureModeEnabled(ctx context.Context, in *SetInsecureModeEnabledRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
@@ -257,6 +269,66 @@ func (c *desktopServiceClient) DeleteAllOOMReports(ctx context.Context, in *empt
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *desktopServiceClient) ListPowerReports(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*OOMReportList, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(OOMReportList)
|
||||
err := c.cc.Invoke(ctx, DesktopService_ListPowerReports_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *desktopServiceClient) ReadPowerReport(ctx context.Context, in *OOMReportRequest, opts ...grpc.CallOption) (*OOMReportContent, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(OOMReportContent)
|
||||
err := c.cc.Invoke(ctx, DesktopService_ReadPowerReport_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *desktopServiceClient) MarkPowerReportRead(ctx context.Context, in *OOMReportRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(emptypb.Empty)
|
||||
err := c.cc.Invoke(ctx, DesktopService_MarkPowerReportRead_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *desktopServiceClient) ExportPowerReport(ctx context.Context, in *OOMReportExportRequest, opts ...grpc.CallOption) (*CrashReportArchive, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(CrashReportArchive)
|
||||
err := c.cc.Invoke(ctx, DesktopService_ExportPowerReport_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *desktopServiceClient) DeletePowerReport(ctx context.Context, in *OOMReportRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(emptypb.Empty)
|
||||
err := c.cc.Invoke(ctx, DesktopService_DeletePowerReport_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *desktopServiceClient) DeleteAllPowerReports(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(emptypb.Empty)
|
||||
err := c.cc.Invoke(ctx, DesktopService_DeleteAllPowerReports_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *desktopServiceClient) InstallUpdate(ctx context.Context, in *InstallUpdateRequest, opts ...grpc.CallOption) (*InstallUpdateResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(InstallUpdateResponse)
|
||||
@@ -319,6 +391,12 @@ type DesktopServiceServer interface {
|
||||
ExportOOMReport(context.Context, *OOMReportExportRequest) (*CrashReportArchive, error)
|
||||
DeleteOOMReport(context.Context, *OOMReportRequest) (*emptypb.Empty, error)
|
||||
DeleteAllOOMReports(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
|
||||
ListPowerReports(context.Context, *emptypb.Empty) (*OOMReportList, error)
|
||||
ReadPowerReport(context.Context, *OOMReportRequest) (*OOMReportContent, error)
|
||||
MarkPowerReportRead(context.Context, *OOMReportRequest) (*emptypb.Empty, error)
|
||||
ExportPowerReport(context.Context, *OOMReportExportRequest) (*CrashReportArchive, error)
|
||||
DeletePowerReport(context.Context, *OOMReportRequest) (*emptypb.Empty, error)
|
||||
DeleteAllPowerReports(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
|
||||
InstallUpdate(context.Context, *InstallUpdateRequest) (*InstallUpdateResponse, error)
|
||||
GetSecuritySettings(context.Context, *emptypb.Empty) (*SecuritySettings, error)
|
||||
SetInsecureModeEnabled(context.Context, *SetInsecureModeEnabledRequest) (*emptypb.Empty, error)
|
||||
@@ -405,6 +483,30 @@ func (UnimplementedDesktopServiceServer) DeleteAllOOMReports(context.Context, *e
|
||||
return nil, status.Error(codes.Unimplemented, "method DeleteAllOOMReports not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedDesktopServiceServer) ListPowerReports(context.Context, *emptypb.Empty) (*OOMReportList, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method ListPowerReports not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedDesktopServiceServer) ReadPowerReport(context.Context, *OOMReportRequest) (*OOMReportContent, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method ReadPowerReport not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedDesktopServiceServer) MarkPowerReportRead(context.Context, *OOMReportRequest) (*emptypb.Empty, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method MarkPowerReportRead not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedDesktopServiceServer) ExportPowerReport(context.Context, *OOMReportExportRequest) (*CrashReportArchive, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method ExportPowerReport not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedDesktopServiceServer) DeletePowerReport(context.Context, *OOMReportRequest) (*emptypb.Empty, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method DeletePowerReport not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedDesktopServiceServer) DeleteAllPowerReports(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method DeleteAllPowerReports not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedDesktopServiceServer) InstallUpdate(context.Context, *InstallUpdateRequest) (*InstallUpdateResponse, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method InstallUpdate not implemented")
|
||||
}
|
||||
@@ -765,6 +867,114 @@ func _DesktopService_DeleteAllOOMReports_Handler(srv interface{}, ctx context.Co
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _DesktopService_ListPowerReports_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(emptypb.Empty)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DesktopServiceServer).ListPowerReports(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: DesktopService_ListPowerReports_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DesktopServiceServer).ListPowerReports(ctx, req.(*emptypb.Empty))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _DesktopService_ReadPowerReport_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(OOMReportRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DesktopServiceServer).ReadPowerReport(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: DesktopService_ReadPowerReport_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DesktopServiceServer).ReadPowerReport(ctx, req.(*OOMReportRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _DesktopService_MarkPowerReportRead_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(OOMReportRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DesktopServiceServer).MarkPowerReportRead(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: DesktopService_MarkPowerReportRead_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DesktopServiceServer).MarkPowerReportRead(ctx, req.(*OOMReportRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _DesktopService_ExportPowerReport_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(OOMReportExportRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DesktopServiceServer).ExportPowerReport(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: DesktopService_ExportPowerReport_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DesktopServiceServer).ExportPowerReport(ctx, req.(*OOMReportExportRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _DesktopService_DeletePowerReport_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(OOMReportRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DesktopServiceServer).DeletePowerReport(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: DesktopService_DeletePowerReport_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DesktopServiceServer).DeletePowerReport(ctx, req.(*OOMReportRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _DesktopService_DeleteAllPowerReports_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(emptypb.Empty)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DesktopServiceServer).DeleteAllPowerReports(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: DesktopService_DeleteAllPowerReports_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DesktopServiceServer).DeleteAllPowerReports(ctx, req.(*emptypb.Empty))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _DesktopService_InstallUpdate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(InstallUpdateRequest)
|
||||
if err := dec(in); err != nil {
|
||||
@@ -916,6 +1126,30 @@ var DesktopService_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "DeleteAllOOMReports",
|
||||
Handler: _DesktopService_DeleteAllOOMReports_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ListPowerReports",
|
||||
Handler: _DesktopService_ListPowerReports_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ReadPowerReport",
|
||||
Handler: _DesktopService_ReadPowerReport_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "MarkPowerReportRead",
|
||||
Handler: _DesktopService_MarkPowerReportRead_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ExportPowerReport",
|
||||
Handler: _DesktopService_ExportPowerReport_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "DeletePowerReport",
|
||||
Handler: _DesktopService_DeletePowerReport_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "DeleteAllPowerReports",
|
||||
Handler: _DesktopService_DeleteAllPowerReports_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "InstallUpdate",
|
||||
Handler: _DesktopService_InstallUpdate_Handler,
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
const (
|
||||
powerReportsDirectoryName = "power_reports"
|
||||
powerTimelineFileName = "timeline.jsonl"
|
||||
powerEventsFileName = "events.jsonl"
|
||||
|
||||
powerReportFileContentLimit = 1024 * 1024
|
||||
)
|
||||
|
||||
// File order follows the client convention:
|
||||
// sing-box-for-apple Library/Shared/PowerReportManager.swift (availableFiles).
|
||||
var powerReportLeadingFileOrder = []string{metadataFileName, configSnapshotFileName, powerTimelineFileName, powerEventsFileName, goLogFileName}
|
||||
|
||||
func (s *desktopService) ListPowerReports(ctx context.Context, empty *emptypb.Empty) (*OOMReportList, error) {
|
||||
reportsDirectory, userID, err := s.daemon.reportCaller(ctx, powerReportsDirectoryName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
entries, err := os.ReadDir(reportsDirectory)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return &OOMReportList{}, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
reports := make([]*OOMReportEntry, 0, len(entries))
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
fullPath := filepath.Join(reportsDirectory, entry.Name())
|
||||
if !reportOwnedBy(fullPath, userID) {
|
||||
continue
|
||||
}
|
||||
reports = append(reports, &OOMReportEntry{
|
||||
Name: entry.Name(),
|
||||
RecordedAt: reportTime(fullPath, "startedAt").UnixMilli(),
|
||||
IsRead: reportIsRead(fullPath),
|
||||
})
|
||||
}
|
||||
sort.Slice(reports, func(i, j int) bool {
|
||||
return reports[i].RecordedAt > reports[j].RecordedAt
|
||||
})
|
||||
return &OOMReportList{Reports: reports}, nil
|
||||
}
|
||||
|
||||
func (s *desktopService) ReadPowerReport(ctx context.Context, request *OOMReportRequest) (*OOMReportContent, error) {
|
||||
reportsDirectory, userID, err := s.daemon.reportCaller(ctx, powerReportsDirectoryName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fullPath, err := reportPathForUser(reportsDirectory, request.Name, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
files := make([]*OOMReportFile, 0, len(powerReportLeadingFileOrder))
|
||||
for _, fileName := range powerReportLeadingFileOrder {
|
||||
content, readError := readFileTail(filepath.Join(fullPath, fileName), powerReportFileContentLimit)
|
||||
if readError != nil {
|
||||
if os.IsNotExist(readError) {
|
||||
continue
|
||||
}
|
||||
return nil, readError
|
||||
}
|
||||
files = append(files, &OOMReportFile{
|
||||
Name: fileName,
|
||||
Content: content,
|
||||
})
|
||||
}
|
||||
entries, err := os.ReadDir(fullPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
profileNames := make([]string, 0, len(entries))
|
||||
for _, entry := range entries {
|
||||
name := entry.Name()
|
||||
if entry.IsDir() || strings.HasPrefix(name, ".") {
|
||||
continue
|
||||
}
|
||||
if slices.Contains(powerReportLeadingFileOrder, name) {
|
||||
continue
|
||||
}
|
||||
profileNames = append(profileNames, name)
|
||||
}
|
||||
sort.Strings(profileNames)
|
||||
for _, name := range profileNames {
|
||||
files = append(files, &OOMReportFile{
|
||||
Name: name,
|
||||
IsProfile: true,
|
||||
})
|
||||
}
|
||||
return &OOMReportContent{Files: files}, nil
|
||||
}
|
||||
|
||||
func readFileTail(path string, limit int64) ([]byte, error) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
info, err := file.Stat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if info.Size() <= limit {
|
||||
return os.ReadFile(path)
|
||||
}
|
||||
_, err = file.Seek(info.Size()-limit, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
content := make([]byte, 0, limit+4)
|
||||
content = append(content, "…\n"...)
|
||||
buffer := make([]byte, limit)
|
||||
n, err := file.Read(buffer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append(content, buffer[:n]...), nil
|
||||
}
|
||||
|
||||
func (s *desktopService) MarkPowerReportRead(ctx context.Context, request *OOMReportRequest) (*emptypb.Empty, error) {
|
||||
reportsDirectory, userID, err := s.daemon.reportCaller(ctx, powerReportsDirectoryName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fullPath, err := reportPathForUser(reportsDirectory, request.Name, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = os.WriteFile(filepath.Join(fullPath, readMarkerFileName), nil, 0o600)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *desktopService) ExportPowerReport(ctx context.Context, request *OOMReportExportRequest) (*CrashReportArchive, error) {
|
||||
reportsDirectory, userID, err := s.daemon.reportCaller(ctx, powerReportsDirectoryName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return exportReportArchive(reportsDirectory, request.Name, userID, request.WithConfiguration, request.WithLog, request.Encrypt)
|
||||
}
|
||||
|
||||
func (s *desktopService) DeletePowerReport(ctx context.Context, request *OOMReportRequest) (*emptypb.Empty, error) {
|
||||
reportsDirectory, userID, err := s.daemon.reportCaller(ctx, powerReportsDirectoryName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fullPath, err := reportPathForUser(reportsDirectory, request.Name, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = os.RemoveAll(fullPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *desktopService) DeleteAllPowerReports(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) {
|
||||
reportsDirectory, userID, err := s.daemon.reportCaller(ctx, powerReportsDirectoryName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = deleteReportsForUser(reportsDirectory, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/sagernet/sing-box/include"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/service/oomkiller"
|
||||
"github.com/sagernet/sing-box/service/powerreport"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/service"
|
||||
|
||||
@@ -30,6 +31,7 @@ type Daemon struct {
|
||||
ctx context.Context
|
||||
logger log.ContextLogger
|
||||
startedService *daemon.StartedService
|
||||
powerManager *powerreport.Manager
|
||||
server *grpc.Server
|
||||
runtimeWorkingDirectory string
|
||||
lifecycleAccess sync.Mutex
|
||||
@@ -62,6 +64,8 @@ func newDaemon() (*Daemon, error) {
|
||||
})
|
||||
reporter := libbox.NewOOMReporter(d.startedService)
|
||||
service.MustRegister[oomkiller.OOMReporter](ctx, reporter)
|
||||
d.powerManager = powerreport.NewManager()
|
||||
service.MustRegister[*powerreport.Manager](ctx, d.powerManager)
|
||||
managedService := daemon.NewManagedService(daemon.ManagedServiceOptions{
|
||||
Handler: &managedHandler{d},
|
||||
Debug: debugEnabled,
|
||||
@@ -195,6 +199,7 @@ func (d *Daemon) configureWorkingDirectoryLocked(directory string) error {
|
||||
return err
|
||||
}
|
||||
libbox.PromoteOOMDraft()
|
||||
libbox.PromotePowerReportDraft()
|
||||
d.runtimeWorkingDirectory = directory
|
||||
return nil
|
||||
}
|
||||
@@ -207,11 +212,20 @@ func (d *Daemon) startServiceLocked(ctx context.Context, ownerUserID string, con
|
||||
}
|
||||
_ = os.WriteFile(filepath.Join(directory, configSnapshotFileName), []byte(configContent), 0o600)
|
||||
libbox.ReloadSetupOptions(&libbox.SetupOptions{
|
||||
OomKillerEnabled: options.OOMKillerEnabled,
|
||||
OomKillerDisabled: options.OOMKillerDisabled,
|
||||
OomMemoryLimit: options.OOMMemoryLimit,
|
||||
OomKillerEnabled: options.OOMKillerEnabled,
|
||||
OomKillerDisabled: options.OOMKillerDisabled,
|
||||
OomMemoryLimit: options.OOMMemoryLimit,
|
||||
PowerReportEnabled: options.PowerReportEnabled,
|
||||
})
|
||||
d.startedService.SetOOMKillerOptions(options.OOMKillerEnabled, options.OOMKillerDisabled, uint64(options.OOMMemoryLimit))
|
||||
if options.PowerReportEnabled {
|
||||
err = d.powerManager.Start(libbox.PowerReportOptions(d.startedService))
|
||||
if err != nil {
|
||||
d.logger.Warn("start power report recorder: ", err)
|
||||
}
|
||||
} else {
|
||||
d.powerManager.Close()
|
||||
}
|
||||
if d.platform != nil {
|
||||
d.platform.SetSystemProxyPreference(options.systemProxyEnabled())
|
||||
err = d.platform.ResetPlatformOptions()
|
||||
@@ -243,6 +257,8 @@ func (d *Daemon) stopServiceLocked(ownerUserID string) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
d.powerManager.Close()
|
||||
libbox.PromotePowerReportDraft()
|
||||
directory := userWorkingDirectory(ownerUserID)
|
||||
crashReportError := tagUnownedReports(filepath.Join(directory, crashReportsDirectoryName), ownerUserID)
|
||||
if crashReportError != nil {
|
||||
@@ -252,6 +268,10 @@ func (d *Daemon) stopServiceLocked(ownerUserID string) error {
|
||||
if oomReportError != nil {
|
||||
return oomReportError
|
||||
}
|
||||
powerReportError := tagUnownedReports(filepath.Join(directory, powerReportsDirectoryName), ownerUserID)
|
||||
if powerReportError != nil {
|
||||
return powerReportError
|
||||
}
|
||||
options.WasRunning = false
|
||||
return saveStartOptions(ownerUserID, options)
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ type startOptions struct {
|
||||
OOMKillerEnabled bool `json:"oom_killer_enabled"`
|
||||
OOMKillerDisabled bool `json:"oom_killer_disabled"`
|
||||
OOMMemoryLimit int64 `json:"oom_memory_limit"`
|
||||
PowerReportEnabled bool `json:"power_report_enabled,omitempty"`
|
||||
SystemProxyEnabled *bool `json:"system_proxy_enabled,omitempty"`
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/sagernet/sing-box/daemon"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/service/oomkiller"
|
||||
"github.com/sagernet/sing-box/service/powerreport"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/service"
|
||||
@@ -34,6 +35,7 @@ type CommandServer struct {
|
||||
handler CommandServerHandler
|
||||
platformInterface PlatformInterface
|
||||
platformWrapper *platformInterfaceWrapper
|
||||
powerManager *powerreport.Manager
|
||||
grpcServer *grpc.Server
|
||||
listener net.Listener
|
||||
endPauseTimer *time.Timer
|
||||
@@ -51,9 +53,12 @@ type CommandServerHandler interface {
|
||||
|
||||
func NewCommandServer(handler CommandServerHandler, platformInterface PlatformInterface) (*CommandServer, error) {
|
||||
ctx := baseContext(platformInterface)
|
||||
powerManager := powerreport.NewManager()
|
||||
service.MustRegister[*powerreport.Manager](ctx, powerManager)
|
||||
platformWrapper := &platformInterfaceWrapper{
|
||||
iif: platformInterface,
|
||||
useProcFS: platformInterface.UseProcFS(),
|
||||
iif: platformInterface,
|
||||
useProcFS: platformInterface.UseProcFS(),
|
||||
powerManager: powerManager,
|
||||
}
|
||||
service.MustRegister[adapter.PlatformInterface](ctx, platformWrapper)
|
||||
server := &CommandServer{
|
||||
@@ -61,6 +66,7 @@ func NewCommandServer(handler CommandServerHandler, platformInterface PlatformIn
|
||||
handler: handler,
|
||||
platformInterface: platformInterface,
|
||||
platformWrapper: platformWrapper,
|
||||
powerManager: powerManager,
|
||||
}
|
||||
server.StartedService = daemon.NewStartedService(daemon.ServiceOptions{
|
||||
Context: ctx,
|
||||
@@ -84,6 +90,12 @@ func NewCommandServer(handler CommandServerHandler, platformInterface PlatformIn
|
||||
Debug: sDebug,
|
||||
OOMReporter: reporter,
|
||||
})
|
||||
if sPowerReportEnabled {
|
||||
err := powerManager.Start(PowerReportOptions(server.StartedService))
|
||||
if err != nil {
|
||||
log.StdLogger().Error(E.Cause(err, "start power report recorder"))
|
||||
}
|
||||
}
|
||||
return server, nil
|
||||
}
|
||||
|
||||
@@ -183,6 +195,7 @@ func (s *CommandServer) Close() {
|
||||
}
|
||||
common.Close(s.listener)
|
||||
s.StartedService.Close()
|
||||
s.powerManager.Close()
|
||||
}
|
||||
|
||||
type OverrideOptions struct {
|
||||
@@ -193,6 +206,9 @@ type OverrideOptions struct {
|
||||
|
||||
func (s *CommandServer) StartOrReloadService(configContent string, options *OverrideOptions) error {
|
||||
saveConfigSnapshot(configContent)
|
||||
if s.powerManager.Recorder() != nil {
|
||||
copyConfigSnapshot(filepath.Join(sWorkingPath, powerreport.DraftDirectoryName))
|
||||
}
|
||||
err := s.StartedService.StartOrReloadService(s.ctx, configContent, &daemon.OverrideOptions{
|
||||
AutoRedirect: options.AutoRedirect,
|
||||
IncludePackage: iteratorToArray(options.IncludePackage),
|
||||
@@ -233,6 +249,10 @@ func (s *CommandServer) NeedFindProcess() bool {
|
||||
}
|
||||
|
||||
func (s *CommandServer) Pause() {
|
||||
recorder := s.powerManager.Recorder()
|
||||
if recorder != nil {
|
||||
recorder.RecordPlatformEvent("ne-sleep")
|
||||
}
|
||||
instance := s.StartedService.Instance()
|
||||
if instance == nil || instance.PauseManager() == nil {
|
||||
return
|
||||
@@ -248,6 +268,10 @@ func (s *CommandServer) Pause() {
|
||||
}
|
||||
|
||||
func (s *CommandServer) Wake() {
|
||||
recorder := s.powerManager.Recorder()
|
||||
if recorder != nil {
|
||||
recorder.RecordPlatformEvent("ne-wake")
|
||||
}
|
||||
instance := s.StartedService.Instance()
|
||||
if instance == nil || instance.PauseManager() == nil {
|
||||
return
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"github.com/sagernet/sing-box/service/powerreport"
|
||||
tun "github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
@@ -69,6 +70,24 @@ func (m *platformDefaultInterfaceMonitor) UpdateDefaultInterface(interfaceName s
|
||||
}
|
||||
|
||||
func (m *platformDefaultInterfaceMonitor) updateDefaultInterface(interfaceName string, interfaceIndex32 int32, isExpensive bool, isConstrained bool) {
|
||||
var recorder *powerreport.Recorder
|
||||
if m.powerManager != nil {
|
||||
recorder = m.powerManager.Recorder()
|
||||
}
|
||||
if recorder != nil {
|
||||
networkType := interfaceName
|
||||
if interfaceIndex32 == -1 {
|
||||
networkType = "none"
|
||||
} else {
|
||||
if isExpensive {
|
||||
networkType += ",expensive"
|
||||
}
|
||||
if isConstrained {
|
||||
networkType += ",constrained"
|
||||
}
|
||||
}
|
||||
recorder.UpdateNetworkType(networkType)
|
||||
}
|
||||
m.isExpensive = isExpensive
|
||||
m.isConstrained = isConstrained
|
||||
err := m.networkManager.UpdateInterfaces()
|
||||
|
||||
@@ -302,16 +302,24 @@ func buildOOMConnection(connection *trafficcontrol.TrackerMetadata) oomConnectio
|
||||
return info
|
||||
}
|
||||
|
||||
func writeOOMLog(destPath string, entries []*log.Entry) {
|
||||
func formatLogEntries(entries []*log.Entry) []byte {
|
||||
if len(entries) == 0 {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
var buffer bytes.Buffer
|
||||
for _, entry := range entries {
|
||||
writeWithoutColors(&buffer, entry.Message)
|
||||
buffer.WriteByte('\n')
|
||||
}
|
||||
writeReportFile(destPath, "go.log", buffer.Bytes())
|
||||
return buffer.Bytes()
|
||||
}
|
||||
|
||||
func writeOOMLog(destPath string, entries []*log.Entry) {
|
||||
content := formatLogEntries(entries)
|
||||
if content == nil {
|
||||
return
|
||||
}
|
||||
writeReportFile(destPath, "go.log", content)
|
||||
}
|
||||
|
||||
func writeWithoutColors(buffer *bytes.Buffer, message string) {
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
//go:build darwin || linux || windows
|
||||
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/daemon"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/service/powerreport"
|
||||
)
|
||||
|
||||
type powerReportMetadata struct {
|
||||
reportMetadata
|
||||
StartedAt string `json:"startedAt"`
|
||||
}
|
||||
|
||||
func PowerReportOptions(startedService *daemon.StartedService) powerreport.Options {
|
||||
return powerreport.Options{
|
||||
BasePath: sWorkingPath,
|
||||
Logger: log.StdLogger(),
|
||||
Metadata: powerReportMetadata{
|
||||
reportMetadata: baseReportMetadata(),
|
||||
StartedAt: time.Now().UTC().Format(time.RFC3339),
|
||||
},
|
||||
OwnerCallback: chownReport,
|
||||
LogCallback: func() []byte {
|
||||
return formatLogEntries(startedService.SavedLog())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func PromotePowerReportDraft() {
|
||||
powerreport.PromoteDraft(sWorkingPath)
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/experimental/libbox/internal/procfs"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-box/service/powerreport"
|
||||
tun "github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
@@ -29,6 +30,7 @@ type platformInterfaceWrapper struct {
|
||||
iif PlatformInterface
|
||||
useProcFS bool
|
||||
networkManager adapter.NetworkManager
|
||||
powerManager *powerreport.Manager
|
||||
myTunName string
|
||||
myTunAddress []netip.Addr
|
||||
defaultInterfaceAccess sync.Mutex
|
||||
|
||||
@@ -34,6 +34,7 @@ var (
|
||||
sOOMKillerEnabled bool
|
||||
sOOMKillerDisabled bool
|
||||
sOOMMemoryLimit int64
|
||||
sPowerReportEnabled bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -54,6 +55,7 @@ type SetupOptions struct {
|
||||
OomKillerEnabled bool
|
||||
OomKillerDisabled bool
|
||||
OomMemoryLimit int64
|
||||
PowerReportEnabled bool
|
||||
}
|
||||
|
||||
func applySetupOptions(options *SetupOptions) {
|
||||
@@ -80,6 +82,7 @@ func ReloadSetupOptions(options *SetupOptions) {
|
||||
sOOMKillerEnabled = options.OomKillerEnabled
|
||||
sOOMKillerDisabled = options.OomKillerDisabled
|
||||
sOOMMemoryLimit = options.OomMemoryLimit
|
||||
sPowerReportEnabled = options.PowerReportEnabled
|
||||
if sOOMKillerEnabled {
|
||||
if sOOMMemoryLimit == 0 && C.IsIos {
|
||||
sOOMMemoryLimit = oomkiller.DefaultAppleNetworkExtensionMemoryLimit
|
||||
|
||||
@@ -44,13 +44,13 @@ require (
|
||||
github.com/sagernet/gvisor v0.0.0-20260727.0-sing-box-mod.1
|
||||
github.com/sagernet/netlink v0.0.0-20260814022025-64455d367bbf
|
||||
github.com/sagernet/nftables v0.3.0-mod.4
|
||||
github.com/sagernet/quic-go v0.61.0-sing-box-mod.4
|
||||
github.com/sagernet/sing v0.9.0-beta.3
|
||||
github.com/sagernet/quic-go v0.61.0-sing-box-mod.5
|
||||
github.com/sagernet/sing v0.9.0-beta.4
|
||||
github.com/sagernet/sing-cloudflared v0.1.3-0.20260706062323-d9787e794aa3
|
||||
github.com/sagernet/sing-mux v0.3.5
|
||||
github.com/sagernet/sing-openconnect v0.0.0-20260810065514-53aa8058f8df
|
||||
github.com/sagernet/sing-openvpn v0.0.0-20260729104525-103eb5fe5eb6
|
||||
github.com/sagernet/sing-quic v0.7.0-beta.2
|
||||
github.com/sagernet/sing-quic v0.7.0-beta.3
|
||||
github.com/sagernet/sing-shadowsocks v0.2.8
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.1
|
||||
github.com/sagernet/sing-shadowtls v0.2.1
|
||||
@@ -60,7 +60,7 @@ require (
|
||||
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1
|
||||
github.com/sagernet/smux v1.5.50-sing-box-mod.1
|
||||
github.com/sagernet/tailscale v1.102.1-sing-box-1.14-mod.3
|
||||
github.com/sagernet/wireguard-go v0.0.5-0.20260810121456-c6c8a831ef70
|
||||
github.com/sagernet/wireguard-go v0.0.5-0.20260823125007-8bd032a91a30
|
||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/stretchr/testify v1.12.0
|
||||
|
||||
@@ -316,10 +316,10 @@ github.com/sagernet/netlink v0.0.0-20260814022025-64455d367bbf h1:b/3rm+KvxzuYRc
|
||||
github.com/sagernet/netlink v0.0.0-20260814022025-64455d367bbf/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
||||
github.com/sagernet/nftables v0.3.0-mod.4 h1:vnOtcDYeSXv2e5RoRuGH0lrpttQFJ8iC4ICS2nhlDSo=
|
||||
github.com/sagernet/nftables v0.3.0-mod.4/go.mod h1:8kslHG4VvYNihcco+i6uxIX7qbT8A56T0y5q7U44ZaQ=
|
||||
github.com/sagernet/quic-go v0.61.0-sing-box-mod.4 h1:XoFf8KoBEsamWo3ePlcmS/xs8B7KDurJQfI2U7q/H1c=
|
||||
github.com/sagernet/quic-go v0.61.0-sing-box-mod.4/go.mod h1:hmLC8GJPp+BrpBgrgJnacvI4fZGIhasuy1ROJJ8GR4E=
|
||||
github.com/sagernet/sing v0.9.0-beta.3 h1:DH9B94S82daJyJHz2oeirLwa/zh03AJ3xiXLJrM95Fo=
|
||||
github.com/sagernet/sing v0.9.0-beta.3/go.mod h1:K3Owt3xPhHugvlnlPPxZJ/exXdaJfEPOTNorGk4AXjo=
|
||||
github.com/sagernet/quic-go v0.61.0-sing-box-mod.5 h1:D41e0fUHgdiYV2r4eGF3LgnBWNjYHm781dNW87IDzWc=
|
||||
github.com/sagernet/quic-go v0.61.0-sing-box-mod.5/go.mod h1:hmLC8GJPp+BrpBgrgJnacvI4fZGIhasuy1ROJJ8GR4E=
|
||||
github.com/sagernet/sing v0.9.0-beta.4 h1:DYoEzb3FeCZ9bA66hJmL0NFDXckZpa4oYF/VpbFJEkE=
|
||||
github.com/sagernet/sing v0.9.0-beta.4/go.mod h1:K3Owt3xPhHugvlnlPPxZJ/exXdaJfEPOTNorGk4AXjo=
|
||||
github.com/sagernet/sing-cloudflared v0.1.3-0.20260706062323-d9787e794aa3 h1:3y6++yIa8XlDhxPkpR4p+7RUHVY2KTP9CPIGnWmOlO8=
|
||||
github.com/sagernet/sing-cloudflared v0.1.3-0.20260706062323-d9787e794aa3/go.mod h1:XEqEDYRCAYLaoPjZ1ifVWJg5iWAJHL2gOAXe/PM28Cg=
|
||||
github.com/sagernet/sing-mux v0.3.5 h1:RHnhVEc+SFqkrK4xMygYjDwwLhzp2Bj3lztSukONfhI=
|
||||
@@ -328,8 +328,8 @@ github.com/sagernet/sing-openconnect v0.0.0-20260810065514-53aa8058f8df h1:ogyC7
|
||||
github.com/sagernet/sing-openconnect v0.0.0-20260810065514-53aa8058f8df/go.mod h1:4AKZLVcvY3r54UaK2Gbnm7aN8pOwdLz+y4EP0QFZ5Eg=
|
||||
github.com/sagernet/sing-openvpn v0.0.0-20260729104525-103eb5fe5eb6 h1:ZAvhkor0prJ8KEX9EmTEH+gJ1WlA3ffjS8GZT6KKq9c=
|
||||
github.com/sagernet/sing-openvpn v0.0.0-20260729104525-103eb5fe5eb6/go.mod h1:PWX7WygD8jpwfqfaGNySXpJYTn0SOwjBI1BKHHC2+Bw=
|
||||
github.com/sagernet/sing-quic v0.7.0-beta.2 h1:qfMz4IyGIj6WhqhrR5ZU+d4Cqppi6RM6BpGXG6phfy8=
|
||||
github.com/sagernet/sing-quic v0.7.0-beta.2/go.mod h1:Z+ZpYBLJ7WkPPrGSCYFxna4Dos2XrywEy6YH6SY5L6Q=
|
||||
github.com/sagernet/sing-quic v0.7.0-beta.3 h1:jQA8vurQ2c8uKebdSq5G1N4EnSLpq2l6rEGJ7Dv6hXA=
|
||||
github.com/sagernet/sing-quic v0.7.0-beta.3/go.mod h1:FPgVlDoSLWJlCjtT8WrFIAll4t2OutSP9nq0Iq74JLg=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI=
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo=
|
||||
@@ -348,8 +348,8 @@ github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1h
|
||||
github.com/sagernet/smux v1.5.50-sing-box-mod.1/go.mod h1:NjhsCEWedJm7eFLyhuBgIEzwfhRmytrUoiLluxs5Sk8=
|
||||
github.com/sagernet/tailscale v1.102.1-sing-box-1.14-mod.3 h1:c7jWyEt7n3WdbDxkc7EqmPQtPT+Hct2l1CtmEDztMuo=
|
||||
github.com/sagernet/tailscale v1.102.1-sing-box-1.14-mod.3/go.mod h1:WLUSOPmTcf7VN9gLCe01qUSIvD+/cKC177neENyZPkI=
|
||||
github.com/sagernet/wireguard-go v0.0.5-0.20260810121456-c6c8a831ef70 h1:WTVgbkDDGnZqxIUnBsE9QyJz2dtSaeoh9mFwxmQqfgI=
|
||||
github.com/sagernet/wireguard-go v0.0.5-0.20260810121456-c6c8a831ef70/go.mod h1:er10sELpmzLXq7S7Pbc1Zsbyapcr+/gxNAHKTo6fzVA=
|
||||
github.com/sagernet/wireguard-go v0.0.5-0.20260823125007-8bd032a91a30 h1:Z9QAr893OvR9qDHvxYTsYZhpvqwcc0LWbJ4ECg/eBgg=
|
||||
github.com/sagernet/wireguard-go v0.0.5-0.20260823125007-8bd032a91a30/go.mod h1:er10sELpmzLXq7S7Pbc1Zsbyapcr+/gxNAHKTo6fzVA=
|
||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc=
|
||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA=
|
||||
github.com/smallstep/pkcs7 v0.1.1 h1:x+rPdt2W088V9Vkjho4KtoggyktZJlMduZAtRHm68LU=
|
||||
|
||||
@@ -105,6 +105,7 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL
|
||||
},
|
||||
}))
|
||||
},
|
||||
Tag: tag,
|
||||
Name: options.Name,
|
||||
MTU: options.MTU,
|
||||
Address: options.Address,
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package powerreport
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type Manager struct {
|
||||
access sync.Mutex
|
||||
recorder atomic.Pointer[Recorder]
|
||||
}
|
||||
|
||||
func NewManager() *Manager {
|
||||
return &Manager{}
|
||||
}
|
||||
|
||||
func (m *Manager) Start(options Options) error {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
if m.recorder.Load() != nil {
|
||||
return nil
|
||||
}
|
||||
recorder := NewRecorder(options)
|
||||
err := recorder.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.recorder.Store(recorder)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) Close() error {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
recorder := m.recorder.Swap(nil)
|
||||
if recorder == nil {
|
||||
return nil
|
||||
}
|
||||
return recorder.Close()
|
||||
}
|
||||
|
||||
func (m *Manager) Recorder() *Recorder {
|
||||
return m.recorder.Load()
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package powerreport
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func PromoteDraft(basePath string) {
|
||||
promoteDirectory(filepath.Join(basePath, DraftDirectoryName), filepath.Join(basePath, ReportsDirectoryName))
|
||||
}
|
||||
|
||||
func finalizeDraft(draftPath string) {
|
||||
promoteDirectory(draftPath, filepath.Join(filepath.Dir(draftPath), ReportsDirectoryName))
|
||||
}
|
||||
|
||||
func promoteDirectory(draftPath string, reportsPath string) {
|
||||
info, err := os.Stat(draftPath)
|
||||
if err != nil || !info.IsDir() {
|
||||
return
|
||||
}
|
||||
entries, err := os.ReadDir(draftPath)
|
||||
if err != nil || len(entries) == 0 {
|
||||
os.RemoveAll(draftPath)
|
||||
return
|
||||
}
|
||||
err = os.MkdirAll(reportsPath, 0o777)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
destName := info.ModTime().UTC().Format("2006-01-02T15-04-05")
|
||||
destPath := filepath.Join(reportsPath, destName)
|
||||
for i := 1; ; i++ {
|
||||
_, err = os.Stat(destPath)
|
||||
if os.IsNotExist(err) {
|
||||
break
|
||||
}
|
||||
if i > 1000 {
|
||||
os.RemoveAll(draftPath)
|
||||
return
|
||||
}
|
||||
destPath = filepath.Join(reportsPath, destName+"-"+strconv.Itoa(i))
|
||||
}
|
||||
err = os.Rename(draftPath, destPath)
|
||||
if err != nil {
|
||||
os.RemoveAll(draftPath)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package powerreport
|
||||
|
||||
type Direction uint8
|
||||
|
||||
const (
|
||||
DirectionOutbound Direction = iota
|
||||
DirectionInbound
|
||||
)
|
||||
|
||||
func (d Direction) String() string {
|
||||
if d == DirectionInbound {
|
||||
return "in"
|
||||
}
|
||||
return "out"
|
||||
}
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
type timelineRow struct {
|
||||
From string `json:"from"`
|
||||
To string `json:"to"`
|
||||
CPUUserMS int64 `json:"cpuUserMS,omitempty"`
|
||||
CPUSystemMS int64 `json:"cpuSystemMS,omitempty"`
|
||||
CPUPerformanceMS int64 `json:"cpuPerformanceMS,omitempty"`
|
||||
CPUGCMS int64 `json:"cpuGCMS,omitempty"`
|
||||
QoSMS *qosBreakdown `json:"qosMS,omitempty"`
|
||||
PackageIdleWakeups uint64 `json:"packageIdleWakeups,omitempty"`
|
||||
InterruptWakeups uint64 `json:"interruptWakeups,omitempty"`
|
||||
EnergyNanojoules uint64 `json:"energyNJ,omitempty"`
|
||||
PerformanceEnergyNanojoules uint64 `json:"performanceEnergyNJ,omitempty"`
|
||||
DiskBytesWritten uint64 `json:"diskWriteBytes,omitempty"`
|
||||
SleptMS int64 `json:"sleptMS,omitempty"`
|
||||
Goroutines uint64 `json:"goroutines,omitempty"`
|
||||
DNSQueries uint64 `json:"dnsQueries,omitempty"`
|
||||
ConnectionsOpened uint64 `json:"connectionsOpened,omitempty"`
|
||||
InterfacePackets map[string]uint64 `json:"interfacePackets,omitempty"`
|
||||
NetworkType string `json:"network,omitempty"`
|
||||
}
|
||||
|
||||
type qosBreakdown struct {
|
||||
DefaultMS int64 `json:"default,omitempty"`
|
||||
MaintenanceMS int64 `json:"maintenance,omitempty"`
|
||||
BackgroundMS int64 `json:"background,omitempty"`
|
||||
UtilityMS int64 `json:"utility,omitempty"`
|
||||
LegacyMS int64 `json:"legacy,omitempty"`
|
||||
UserInitiatedMS int64 `json:"userInitiated,omitempty"`
|
||||
UserInteractiveMS int64 `json:"userInteractive,omitempty"`
|
||||
}
|
||||
|
||||
const (
|
||||
eventTypeBreak = "break"
|
||||
eventTypeNetwork = "network"
|
||||
)
|
||||
|
||||
type eventRecord struct {
|
||||
Type string `json:"t"`
|
||||
At string `json:"at"`
|
||||
IdleMS int64 `json:"idleMS,omitempty"`
|
||||
Direction string `json:"direction,omitempty"`
|
||||
Size int `json:"size,omitempty"`
|
||||
NetworkType string `json:"network,omitempty"`
|
||||
By *Attribution `json:"by,omitempty"`
|
||||
}
|
||||
|
||||
type systemUsage struct {
|
||||
valid bool
|
||||
userTime int64
|
||||
systemTime int64
|
||||
performanceUserTime int64
|
||||
performanceSystemTime int64
|
||||
qosDefaultTime int64
|
||||
qosMaintenanceTime int64
|
||||
qosBackgroundTime int64
|
||||
qosUtilityTime int64
|
||||
qosLegacyTime int64
|
||||
qosUserInitiatedTime int64
|
||||
qosUserInteractiveTime int64
|
||||
packageIdleWakeups uint64
|
||||
interruptWakeups uint64
|
||||
diskBytesWritten uint64
|
||||
energyNanojoules uint64
|
||||
performanceEnergyNanojoules uint64
|
||||
}
|
||||
|
||||
type interfaceCounters struct {
|
||||
inPackets uint32
|
||||
outPackets uint32
|
||||
}
|
||||
@@ -0,0 +1,472 @@
|
||||
package powerreport
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime/metrics"
|
||||
"runtime/pprof"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
)
|
||||
|
||||
const (
|
||||
DraftDirectoryName = "power_draft"
|
||||
ReportsDirectoryName = "power_reports"
|
||||
|
||||
timelineFileName = "timeline.jsonl"
|
||||
eventsFileName = "events.jsonl"
|
||||
metadataFileName = "metadata.json"
|
||||
logFileName = "go.log"
|
||||
goroutineProfileFileName = "goroutine.pb.gz"
|
||||
|
||||
defaultGateInterval = 5 * time.Second
|
||||
defaultSampleInterval = time.Minute
|
||||
defaultFlushInterval = 15 * time.Minute
|
||||
defaultFallbackInterval = 10 * time.Minute
|
||||
|
||||
activityRefreshNano = int64(time.Second)
|
||||
|
||||
rowCapacity = 4096
|
||||
eventCapacity = 8192
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
BasePath string
|
||||
Logger logger.Logger
|
||||
Metadata any
|
||||
OwnerCallback func(path string)
|
||||
LogCallback func() []byte
|
||||
GateInterval time.Duration
|
||||
SampleInterval time.Duration
|
||||
FlushInterval time.Duration
|
||||
FallbackInterval time.Duration
|
||||
}
|
||||
|
||||
type Recorder struct {
|
||||
draftPath string
|
||||
logger logger.Logger
|
||||
metadata any
|
||||
ownerCallback func(path string)
|
||||
logCallback func() []byte
|
||||
gateNano int64
|
||||
sampleNano int64
|
||||
flushInterval time.Duration
|
||||
fallbackInterval time.Duration
|
||||
baseTime time.Time
|
||||
|
||||
_ [64]byte
|
||||
lastActivity atomic.Int64
|
||||
_ [64]byte
|
||||
|
||||
lastSampleAt atomic.Int64
|
||||
pendingBreak atomic.Pointer[breakRecord]
|
||||
notify chan struct{}
|
||||
|
||||
dnsQueries atomic.Uint64
|
||||
connectionsOpened atomic.Uint64
|
||||
|
||||
access sync.Mutex
|
||||
networkType string
|
||||
rows []timelineRow
|
||||
events []eventRecord
|
||||
previous previousSample
|
||||
lastFlushAt time.Time
|
||||
started bool
|
||||
closed bool
|
||||
metricsSamples []metrics.Sample
|
||||
|
||||
done chan struct{}
|
||||
workerDone chan struct{}
|
||||
}
|
||||
|
||||
type breakRecord struct {
|
||||
at time.Time
|
||||
idleMS int64
|
||||
direction Direction
|
||||
size int
|
||||
by *Attribution
|
||||
}
|
||||
|
||||
type previousSample struct {
|
||||
at time.Time
|
||||
usage systemUsage
|
||||
gcSeconds float64
|
||||
absoluteTime int64
|
||||
continuousTime int64
|
||||
interfaces map[string]interfaceCounters
|
||||
dnsQueries uint64
|
||||
connectionsOpened uint64
|
||||
}
|
||||
|
||||
func NewRecorder(options Options) *Recorder {
|
||||
recorderLogger := options.Logger
|
||||
if recorderLogger == nil {
|
||||
recorderLogger = logger.NOP()
|
||||
}
|
||||
gateInterval := options.GateInterval
|
||||
if gateInterval == 0 {
|
||||
gateInterval = defaultGateInterval
|
||||
}
|
||||
sampleInterval := options.SampleInterval
|
||||
if sampleInterval == 0 {
|
||||
sampleInterval = defaultSampleInterval
|
||||
}
|
||||
flushInterval := options.FlushInterval
|
||||
if flushInterval == 0 {
|
||||
flushInterval = defaultFlushInterval
|
||||
}
|
||||
fallbackInterval := options.FallbackInterval
|
||||
if fallbackInterval == 0 {
|
||||
fallbackInterval = defaultFallbackInterval
|
||||
}
|
||||
return &Recorder{
|
||||
draftPath: filepath.Join(options.BasePath, DraftDirectoryName),
|
||||
logger: recorderLogger,
|
||||
metadata: options.Metadata,
|
||||
ownerCallback: options.OwnerCallback,
|
||||
logCallback: options.LogCallback,
|
||||
gateNano: int64(gateInterval),
|
||||
sampleNano: int64(sampleInterval),
|
||||
flushInterval: flushInterval,
|
||||
fallbackInterval: fallbackInterval,
|
||||
baseTime: time.Now(),
|
||||
notify: make(chan struct{}, 1),
|
||||
metricsSamples: []metrics.Sample{
|
||||
{Name: "/cpu/classes/gc/total:cpu-seconds"},
|
||||
{Name: "/sched/goroutines:goroutines"},
|
||||
},
|
||||
done: make(chan struct{}),
|
||||
workerDone: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Recorder) Start() error {
|
||||
r.access.Lock()
|
||||
defer r.access.Unlock()
|
||||
if r.started {
|
||||
return nil
|
||||
}
|
||||
PromoteDraft(filepath.Dir(r.draftPath))
|
||||
err := os.MkdirAll(r.draftPath, 0o777)
|
||||
if err != nil {
|
||||
return E.Cause(err, "create power report draft directory")
|
||||
}
|
||||
r.chown(r.draftPath)
|
||||
if r.metadata != nil {
|
||||
metadataContent, marshalErr := json.Marshal(r.metadata)
|
||||
if marshalErr == nil {
|
||||
metadataPath := filepath.Join(r.draftPath, metadataFileName)
|
||||
os.WriteFile(metadataPath, metadataContent, 0o666)
|
||||
r.chown(metadataPath)
|
||||
}
|
||||
}
|
||||
now := time.Now()
|
||||
r.resetPreviousLocked(now)
|
||||
r.lastSampleAt.Store(int64(now.Sub(r.baseTime)))
|
||||
r.lastFlushAt = now
|
||||
r.started = true
|
||||
go r.worker()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Recorder) Close() error {
|
||||
r.access.Lock()
|
||||
if !r.started || r.closed {
|
||||
r.access.Unlock()
|
||||
return nil
|
||||
}
|
||||
r.closed = true
|
||||
r.access.Unlock()
|
||||
close(r.done)
|
||||
<-r.workerDone
|
||||
now := time.Now()
|
||||
r.access.Lock()
|
||||
r.consumeBreakLocked()
|
||||
r.sampleLocked(now)
|
||||
r.flushLocked(now)
|
||||
r.access.Unlock()
|
||||
r.writeGoroutineProfile()
|
||||
r.writeLog()
|
||||
finalizeDraft(r.draftPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Recorder) writeLog() {
|
||||
if r.logCallback == nil {
|
||||
return
|
||||
}
|
||||
content := r.logCallback()
|
||||
if len(content) == 0 {
|
||||
return
|
||||
}
|
||||
logPath := filepath.Join(r.draftPath, logFileName)
|
||||
err := os.WriteFile(logPath, content, 0o666)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
r.chown(logPath)
|
||||
}
|
||||
|
||||
// Touch reports one I/O activity: one read or write call, or one batched receive or send
|
||||
// syscall on paths that batch packets. size is the size of the first packet of the activity
|
||||
// and characterizes what ended an idle period; it is not accumulated. Volume totals come from
|
||||
// the sampled interface counters instead.
|
||||
func (r *Recorder) Touch(direction Direction, size int, by *Attribution) {
|
||||
nowNano := int64(time.Since(r.baseTime))
|
||||
lastNano := r.lastActivity.Load()
|
||||
if nowNano-lastNano < activityRefreshNano {
|
||||
return
|
||||
}
|
||||
previousNano := r.lastActivity.Swap(nowNano)
|
||||
if previousNano != 0 && nowNano-previousNano >= r.gateNano {
|
||||
r.pendingBreak.Store(&breakRecord{
|
||||
at: time.Now(),
|
||||
idleMS: (nowNano - previousNano) / int64(time.Millisecond),
|
||||
direction: direction,
|
||||
size: size,
|
||||
by: by,
|
||||
})
|
||||
r.notifyWorker()
|
||||
} else if nowNano-r.lastSampleAt.Load() >= r.sampleNano {
|
||||
r.notifyWorker()
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Recorder) CountDNSQuery() {
|
||||
r.dnsQueries.Add(1)
|
||||
}
|
||||
|
||||
func (r *Recorder) CountConnectionOpened() {
|
||||
r.connectionsOpened.Add(1)
|
||||
}
|
||||
|
||||
func (r *Recorder) RecordPlatformEvent(eventType string) {
|
||||
now := time.Now()
|
||||
r.access.Lock()
|
||||
if !r.started || r.closed {
|
||||
r.access.Unlock()
|
||||
return
|
||||
}
|
||||
r.events = append(r.events, eventRecord{
|
||||
Type: eventType,
|
||||
At: now.UTC().Format(time.RFC3339),
|
||||
})
|
||||
r.access.Unlock()
|
||||
r.notifyWorker()
|
||||
}
|
||||
|
||||
func (r *Recorder) UpdateNetworkType(networkType string) {
|
||||
now := time.Now()
|
||||
r.access.Lock()
|
||||
if r.closed || r.networkType == networkType {
|
||||
r.access.Unlock()
|
||||
return
|
||||
}
|
||||
r.networkType = networkType
|
||||
r.events = append(r.events, eventRecord{
|
||||
Type: eventTypeNetwork,
|
||||
At: now.UTC().Format(time.RFC3339),
|
||||
NetworkType: networkType,
|
||||
})
|
||||
r.access.Unlock()
|
||||
r.notifyWorker()
|
||||
}
|
||||
|
||||
func (r *Recorder) notifyWorker() {
|
||||
select {
|
||||
case r.notify <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Recorder) worker() {
|
||||
defer close(r.workerDone)
|
||||
timer := time.NewTimer(r.fallbackInterval)
|
||||
defer timer.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-r.done:
|
||||
return
|
||||
case <-r.notify:
|
||||
case <-timer.C:
|
||||
timer.Reset(r.fallbackInterval)
|
||||
}
|
||||
r.process()
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Recorder) process() {
|
||||
now := time.Now()
|
||||
r.access.Lock()
|
||||
defer r.access.Unlock()
|
||||
if !r.started || r.closed {
|
||||
return
|
||||
}
|
||||
r.consumeBreakLocked()
|
||||
nowNano := int64(now.Sub(r.baseTime))
|
||||
if nowNano-r.lastSampleAt.Load() >= r.sampleNano {
|
||||
r.lastSampleAt.Store(nowNano)
|
||||
r.sampleLocked(now)
|
||||
}
|
||||
if now.Sub(r.lastFlushAt) >= r.flushInterval || len(r.rows) >= rowCapacity || len(r.events) >= eventCapacity {
|
||||
r.flushLocked(now)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Recorder) consumeBreakLocked() {
|
||||
record := r.pendingBreak.Swap(nil)
|
||||
if record == nil {
|
||||
return
|
||||
}
|
||||
r.events = append(r.events, eventRecord{
|
||||
Type: eventTypeBreak,
|
||||
At: record.at.UTC().Format(time.RFC3339),
|
||||
IdleMS: record.idleMS,
|
||||
Direction: record.direction.String(),
|
||||
Size: record.size,
|
||||
NetworkType: r.networkType,
|
||||
By: record.by,
|
||||
})
|
||||
}
|
||||
|
||||
func (r *Recorder) resetPreviousLocked(now time.Time) {
|
||||
metrics.Read(r.metricsSamples)
|
||||
r.previous = previousSample{
|
||||
at: now,
|
||||
usage: readSystemUsage(),
|
||||
gcSeconds: r.metricsSamples[0].Value.Float64(),
|
||||
interfaces: readInterfaceCounters(),
|
||||
dnsQueries: r.dnsQueries.Load(),
|
||||
connectionsOpened: r.connectionsOpened.Load(),
|
||||
}
|
||||
r.previous.absoluteTime, r.previous.continuousTime = readClocks()
|
||||
}
|
||||
|
||||
func (r *Recorder) sampleLocked(now time.Time) {
|
||||
previous := r.previous
|
||||
r.resetPreviousLocked(now)
|
||||
current := &r.previous
|
||||
row := timelineRow{
|
||||
From: previous.at.UTC().Format(time.RFC3339),
|
||||
To: now.UTC().Format(time.RFC3339),
|
||||
CPUGCMS: int64((current.gcSeconds - previous.gcSeconds) * 1000),
|
||||
Goroutines: r.metricsSamples[1].Value.Uint64(),
|
||||
DNSQueries: current.dnsQueries - previous.dnsQueries,
|
||||
ConnectionsOpened: current.connectionsOpened - previous.connectionsOpened,
|
||||
NetworkType: r.networkType,
|
||||
}
|
||||
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)
|
||||
row.CPUPerformanceMS = (current.usage.performanceUserTime - previous.usage.performanceUserTime +
|
||||
current.usage.performanceSystemTime - previous.usage.performanceSystemTime) / int64(time.Millisecond)
|
||||
row.PackageIdleWakeups = current.usage.packageIdleWakeups - previous.usage.packageIdleWakeups
|
||||
row.InterruptWakeups = current.usage.interruptWakeups - previous.usage.interruptWakeups
|
||||
row.EnergyNanojoules = current.usage.energyNanojoules - previous.usage.energyNanojoules
|
||||
row.PerformanceEnergyNanojoules = current.usage.performanceEnergyNanojoules - previous.usage.performanceEnergyNanojoules
|
||||
row.DiskBytesWritten = current.usage.diskBytesWritten - previous.usage.diskBytesWritten
|
||||
qos := qosBreakdown{
|
||||
DefaultMS: (current.usage.qosDefaultTime - previous.usage.qosDefaultTime) / int64(time.Millisecond),
|
||||
MaintenanceMS: (current.usage.qosMaintenanceTime - previous.usage.qosMaintenanceTime) / int64(time.Millisecond),
|
||||
BackgroundMS: (current.usage.qosBackgroundTime - previous.usage.qosBackgroundTime) / int64(time.Millisecond),
|
||||
UtilityMS: (current.usage.qosUtilityTime - previous.usage.qosUtilityTime) / int64(time.Millisecond),
|
||||
LegacyMS: (current.usage.qosLegacyTime - previous.usage.qosLegacyTime) / int64(time.Millisecond),
|
||||
UserInitiatedMS: (current.usage.qosUserInitiatedTime - previous.usage.qosUserInitiatedTime) / int64(time.Millisecond),
|
||||
UserInteractiveMS: (current.usage.qosUserInteractiveTime - previous.usage.qosUserInteractiveTime) / int64(time.Millisecond),
|
||||
}
|
||||
if qos != (qosBreakdown{}) {
|
||||
row.QoSMS = &qos
|
||||
}
|
||||
}
|
||||
if current.absoluteTime != 0 && previous.absoluteTime != 0 && current.absoluteTime >= previous.absoluteTime {
|
||||
sleptNano := (current.continuousTime - previous.continuousTime) - (current.absoluteTime - previous.absoluteTime)
|
||||
wallNano := now.Sub(previous.at).Nanoseconds()
|
||||
if sleptNano > wallNano {
|
||||
sleptNano = wallNano
|
||||
}
|
||||
if sleptNano > 0 {
|
||||
row.SleptMS = sleptNano / int64(time.Millisecond)
|
||||
}
|
||||
}
|
||||
if len(current.interfaces) > 0 && len(previous.interfaces) > 0 {
|
||||
interfacePackets := make(map[string]uint64)
|
||||
for name, counters := range current.interfaces {
|
||||
previousCounters, found := previous.interfaces[name]
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
delta := uint64(counters.inPackets-previousCounters.inPackets) + uint64(counters.outPackets-previousCounters.outPackets)
|
||||
if delta > 0 {
|
||||
interfacePackets[name] = delta
|
||||
}
|
||||
}
|
||||
if len(interfacePackets) > 0 {
|
||||
row.InterfacePackets = interfacePackets
|
||||
}
|
||||
}
|
||||
r.rows = append(r.rows, row)
|
||||
}
|
||||
|
||||
func (r *Recorder) chown(path string) {
|
||||
if r.ownerCallback != nil {
|
||||
r.ownerCallback(path)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Recorder) flushLocked(now time.Time) {
|
||||
err := appendRecords(r, filepath.Join(r.draftPath, timelineFileName), r.rows)
|
||||
if err == nil {
|
||||
r.rows = r.rows[:0]
|
||||
} else {
|
||||
r.logger.Error(E.Cause(err, "power report: write timeline"))
|
||||
if len(r.rows) >= rowCapacity {
|
||||
r.rows = r.rows[len(r.rows)-rowCapacity/2:]
|
||||
}
|
||||
}
|
||||
err = appendRecords(r, filepath.Join(r.draftPath, eventsFileName), r.events)
|
||||
if err == nil {
|
||||
r.events = r.events[:0]
|
||||
} else {
|
||||
r.logger.Error(E.Cause(err, "power report: write events"))
|
||||
if len(r.events) >= eventCapacity {
|
||||
r.events = r.events[len(r.events)-eventCapacity/2:]
|
||||
}
|
||||
}
|
||||
r.lastFlushAt = now
|
||||
}
|
||||
|
||||
func appendRecords[T any](r *Recorder, path string, records []T) error {
|
||||
if len(records) == 0 {
|
||||
return nil
|
||||
}
|
||||
file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
r.chown(path)
|
||||
encoder := json.NewEncoder(file)
|
||||
for _, record := range records {
|
||||
err = encoder.Encode(record)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Recorder) writeGoroutineProfile() {
|
||||
profilePath := filepath.Join(r.draftPath, goroutineProfileFileName)
|
||||
file, err := os.OpenFile(profilePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o666)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
r.chown(profilePath)
|
||||
pprof.Lookup("goroutine").WriteTo(file, 0)
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package powerreport
|
||||
|
||||
//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go syscall_windows.go
|
||||
|
||||
type processIOCounters struct {
|
||||
readOperationCount uint64
|
||||
writeOperationCount uint64
|
||||
otherOperationCount uint64
|
||||
readTransferCount uint64
|
||||
writeTransferCount uint64
|
||||
otherTransferCount uint64
|
||||
}
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getprocessiocounters
|
||||
//sys getProcessIoCounters(process windows.Handle, ioCounters *processIOCounters) (err error) = kernel32.GetProcessIoCounters
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/realtimeapiset/nf-realtimeapiset-queryunbiasedinterrupttime
|
||||
//sys queryUnbiasedInterruptTime(unbiasedTime *uint64) (err error) = kernel32.QueryUnbiasedInterruptTime
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/realtimeapiset/nf-realtimeapiset-queryinterrupttime
|
||||
//sys queryInterruptTime(interruptTime *uint64) = api-ms-win-core-realtime-l1-1-1.QueryInterruptTime
|
||||
@@ -0,0 +1,123 @@
|
||||
package powerreport
|
||||
|
||||
/*
|
||||
#include <ifaddrs.h>
|
||||
#include <mach/mach_time.h>
|
||||
#include <net/if.h>
|
||||
#include <net/if_var.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// The iOS SDK does not ship libproc.h; the symbol is exported by libSystem
|
||||
// on all darwin platforms.
|
||||
int proc_pid_rusage(int pid, int flavor, rusage_info_t *buffer);
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var (
|
||||
timebaseOnce sync.Once
|
||||
timebaseNumer int64
|
||||
timebaseDenom int64
|
||||
)
|
||||
|
||||
func machTimebase() (int64, int64) {
|
||||
timebaseOnce.Do(func() {
|
||||
var timebase C.struct_mach_timebase_info
|
||||
C.mach_timebase_info(&timebase)
|
||||
timebaseNumer = int64(timebase.numer)
|
||||
timebaseDenom = int64(timebase.denom)
|
||||
})
|
||||
return timebaseNumer, timebaseDenom
|
||||
}
|
||||
|
||||
func machToNano(value uint64) int64 {
|
||||
numer, denom := machTimebase()
|
||||
if denom == 0 {
|
||||
return int64(value)
|
||||
}
|
||||
return int64(value) * numer / denom
|
||||
}
|
||||
|
||||
// The time fields of rusage_info are in mach_absolute_time units on arm64,
|
||||
// not nanoseconds; the header does not document this.
|
||||
func readSystemUsage() systemUsage {
|
||||
var info C.struct_rusage_info_v6
|
||||
result := C.proc_pid_rusage(C.int(os.Getpid()), C.RUSAGE_INFO_V6, (*C.rusage_info_t)(unsafe.Pointer(&info)))
|
||||
if result == 0 {
|
||||
return systemUsage{
|
||||
valid: true,
|
||||
userTime: machToNano(uint64(info.ri_user_time)),
|
||||
systemTime: machToNano(uint64(info.ri_system_time)),
|
||||
performanceUserTime: machToNano(uint64(info.ri_user_ptime)),
|
||||
performanceSystemTime: machToNano(uint64(info.ri_system_ptime)),
|
||||
qosDefaultTime: machToNano(uint64(info.ri_cpu_time_qos_default)),
|
||||
qosMaintenanceTime: machToNano(uint64(info.ri_cpu_time_qos_maintenance)),
|
||||
qosBackgroundTime: machToNano(uint64(info.ri_cpu_time_qos_background)),
|
||||
qosUtilityTime: machToNano(uint64(info.ri_cpu_time_qos_utility)),
|
||||
qosLegacyTime: machToNano(uint64(info.ri_cpu_time_qos_legacy)),
|
||||
qosUserInitiatedTime: machToNano(uint64(info.ri_cpu_time_qos_user_initiated)),
|
||||
qosUserInteractiveTime: machToNano(uint64(info.ri_cpu_time_qos_user_interactive)),
|
||||
packageIdleWakeups: uint64(info.ri_pkg_idle_wkups),
|
||||
interruptWakeups: uint64(info.ri_interrupt_wkups),
|
||||
diskBytesWritten: uint64(info.ri_diskio_byteswritten),
|
||||
energyNanojoules: uint64(info.ri_energy_nj),
|
||||
performanceEnergyNanojoules: uint64(info.ri_penergy_nj),
|
||||
}
|
||||
}
|
||||
var infoV4 C.struct_rusage_info_v4
|
||||
result = C.proc_pid_rusage(C.int(os.Getpid()), C.RUSAGE_INFO_V4, (*C.rusage_info_t)(unsafe.Pointer(&infoV4)))
|
||||
if result != 0 {
|
||||
return systemUsage{}
|
||||
}
|
||||
return systemUsage{
|
||||
valid: true,
|
||||
userTime: machToNano(uint64(infoV4.ri_user_time)),
|
||||
systemTime: machToNano(uint64(infoV4.ri_system_time)),
|
||||
qosDefaultTime: machToNano(uint64(infoV4.ri_cpu_time_qos_default)),
|
||||
qosMaintenanceTime: machToNano(uint64(infoV4.ri_cpu_time_qos_maintenance)),
|
||||
qosBackgroundTime: machToNano(uint64(infoV4.ri_cpu_time_qos_background)),
|
||||
qosUtilityTime: machToNano(uint64(infoV4.ri_cpu_time_qos_utility)),
|
||||
qosLegacyTime: machToNano(uint64(infoV4.ri_cpu_time_qos_legacy)),
|
||||
qosUserInitiatedTime: machToNano(uint64(infoV4.ri_cpu_time_qos_user_initiated)),
|
||||
qosUserInteractiveTime: machToNano(uint64(infoV4.ri_cpu_time_qos_user_interactive)),
|
||||
packageIdleWakeups: uint64(infoV4.ri_pkg_idle_wkups),
|
||||
interruptWakeups: uint64(infoV4.ri_interrupt_wkups),
|
||||
diskBytesWritten: uint64(infoV4.ri_diskio_byteswritten),
|
||||
}
|
||||
}
|
||||
|
||||
func readClocks() (absoluteTime int64, continuousTime int64) {
|
||||
return machToNano(uint64(C.mach_absolute_time())), machToNano(uint64(C.mach_continuous_time()))
|
||||
}
|
||||
|
||||
func readInterfaceCounters() map[string]interfaceCounters {
|
||||
var list *C.struct_ifaddrs
|
||||
if C.getifaddrs(&list) != 0 {
|
||||
return nil
|
||||
}
|
||||
defer C.freeifaddrs(list)
|
||||
result := make(map[string]interfaceCounters)
|
||||
for entry := list; entry != nil; entry = entry.ifa_next {
|
||||
if entry.ifa_addr == nil || entry.ifa_addr.sa_family != C.AF_LINK || entry.ifa_data == nil {
|
||||
continue
|
||||
}
|
||||
name := C.GoString(entry.ifa_name)
|
||||
if !strings.HasPrefix(name, "en") && !strings.HasPrefix(name, "pdp_ip") {
|
||||
continue
|
||||
}
|
||||
data := (*C.struct_if_data)(entry.ifa_data)
|
||||
result[name] = interfaceCounters{
|
||||
inPackets: uint32(data.ifi_ipackets),
|
||||
outPackets: uint32(data.ifi_opackets),
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
//go:build darwin && !cgo
|
||||
|
||||
package powerreport
|
||||
|
||||
func readSystemUsage() systemUsage {
|
||||
panic("power report requires CGO on darwin, rebuild with CGO_ENABLED=1")
|
||||
}
|
||||
|
||||
func readClocks() (absoluteTime int64, continuousTime int64) {
|
||||
panic("power report requires CGO on darwin, rebuild with CGO_ENABLED=1")
|
||||
}
|
||||
|
||||
func readInterfaceCounters() map[string]interfaceCounters {
|
||||
panic("power report requires CGO on darwin, rebuild with CGO_ENABLED=1")
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package powerreport
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func readSystemUsage() systemUsage {
|
||||
var rusage unix.Rusage
|
||||
err := unix.Getrusage(unix.RUSAGE_SELF, &rusage)
|
||||
if err != nil {
|
||||
return systemUsage{}
|
||||
}
|
||||
return systemUsage{
|
||||
valid: true,
|
||||
userTime: rusage.Utime.Nano(),
|
||||
systemTime: rusage.Stime.Nano(),
|
||||
diskBytesWritten: readWriteBytes(),
|
||||
}
|
||||
}
|
||||
|
||||
func readWriteBytes() uint64 {
|
||||
content, err := os.ReadFile("/proc/self/io")
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
for line := range strings.SplitSeq(string(content), "\n") {
|
||||
value, found := strings.CutPrefix(line, "write_bytes: ")
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
parsed, parseErr := strconv.ParseUint(value, 10, 64)
|
||||
if parseErr != nil {
|
||||
return 0
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func readClocks() (absoluteTime int64, continuousTime int64) {
|
||||
var monotonicTime unix.Timespec
|
||||
err := unix.ClockGettime(unix.CLOCK_MONOTONIC, &monotonicTime)
|
||||
if err != nil {
|
||||
return 0, 0
|
||||
}
|
||||
var bootTime unix.Timespec
|
||||
err = unix.ClockGettime(unix.CLOCK_BOOTTIME, &bootTime)
|
||||
if err != nil {
|
||||
return 0, 0
|
||||
}
|
||||
return monotonicTime.Nano(), bootTime.Nano()
|
||||
}
|
||||
|
||||
func readInterfaceCounters() map[string]interfaceCounters {
|
||||
content, err := os.ReadFile("/proc/net/dev")
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
lines := strings.Split(string(content), "\n")
|
||||
if len(lines) <= 2 {
|
||||
return nil
|
||||
}
|
||||
result := make(map[string]interfaceCounters)
|
||||
for _, line := range lines[2:] {
|
||||
name, counters, found := strings.Cut(line, ":")
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
name = strings.TrimSpace(name)
|
||||
if name == "lo" || strings.HasPrefix(name, "tun") || strings.HasPrefix(name, "utun") || strings.HasPrefix(name, "dummy") {
|
||||
continue
|
||||
}
|
||||
fields := strings.Fields(counters)
|
||||
if len(fields) < 10 {
|
||||
continue
|
||||
}
|
||||
inPackets, inErr := strconv.ParseUint(fields[1], 10, 64)
|
||||
outPackets, outErr := strconv.ParseUint(fields[9], 10, 64)
|
||||
if inErr != nil || outErr != nil {
|
||||
continue
|
||||
}
|
||||
result[name] = interfaceCounters{
|
||||
inPackets: uint32(inPackets),
|
||||
outPackets: uint32(outPackets),
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
//go:build !darwin && !linux && !windows
|
||||
|
||||
package powerreport
|
||||
|
||||
func readSystemUsage() systemUsage {
|
||||
return systemUsage{}
|
||||
}
|
||||
|
||||
func readClocks() (absoluteTime int64, continuousTime int64) {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
func readInterfaceCounters() map[string]interfaceCounters {
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package powerreport
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func filetimeDuration(value windows.Filetime) int64 {
|
||||
return (int64(value.HighDateTime)<<32 | int64(value.LowDateTime)) * 100
|
||||
}
|
||||
|
||||
func readSystemUsage() systemUsage {
|
||||
var creationTime, exitTime, kernelTime, userTime windows.Filetime
|
||||
err := windows.GetProcessTimes(windows.CurrentProcess(), &creationTime, &exitTime, &kernelTime, &userTime)
|
||||
if err != nil {
|
||||
return systemUsage{}
|
||||
}
|
||||
usage := systemUsage{
|
||||
valid: true,
|
||||
userTime: filetimeDuration(userTime),
|
||||
systemTime: filetimeDuration(kernelTime),
|
||||
}
|
||||
var ioCounters processIOCounters
|
||||
err = getProcessIoCounters(windows.CurrentProcess(), &ioCounters)
|
||||
if err == nil {
|
||||
usage.diskBytesWritten = ioCounters.writeTransferCount
|
||||
}
|
||||
return usage
|
||||
}
|
||||
|
||||
func readClocks() (absoluteTime int64, continuousTime int64) {
|
||||
if procQueryInterruptTime.Find() != nil {
|
||||
return 0, 0
|
||||
}
|
||||
var unbiasedTime uint64
|
||||
err := queryUnbiasedInterruptTime(&unbiasedTime)
|
||||
if err != nil {
|
||||
return 0, 0
|
||||
}
|
||||
var interruptTime uint64
|
||||
queryInterruptTime(&interruptTime)
|
||||
return int64(unbiasedTime) * 100, int64(interruptTime) * 100
|
||||
}
|
||||
|
||||
func readInterfaceCounters() map[string]interfaceCounters {
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
// Code generated by 'go generate'; DO NOT EDIT.
|
||||
|
||||
package powerreport
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var _ unsafe.Pointer
|
||||
|
||||
// Do the interface allocations only once for common
|
||||
// Errno values.
|
||||
const (
|
||||
errnoERROR_IO_PENDING = 997
|
||||
)
|
||||
|
||||
var (
|
||||
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
||||
errERROR_EINVAL error = syscall.EINVAL
|
||||
)
|
||||
|
||||
// errnoErr returns common boxed Errno values, to prevent
|
||||
// allocations at runtime.
|
||||
func errnoErr(e syscall.Errno) error {
|
||||
switch e {
|
||||
case 0:
|
||||
return errERROR_EINVAL
|
||||
case errnoERROR_IO_PENDING:
|
||||
return errERROR_IO_PENDING
|
||||
}
|
||||
// TODO: add more here, after collecting data on the common
|
||||
// error values see on Windows. (perhaps when running
|
||||
// all.bat?)
|
||||
return e
|
||||
}
|
||||
|
||||
var (
|
||||
modapi_ms_win_core_realtime_l1_1_1 = windows.NewLazySystemDLL("api-ms-win-core-realtime-l1-1-1.dll")
|
||||
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
||||
|
||||
procQueryInterruptTime = modapi_ms_win_core_realtime_l1_1_1.NewProc("QueryInterruptTime")
|
||||
procGetProcessIoCounters = modkernel32.NewProc("GetProcessIoCounters")
|
||||
procQueryUnbiasedInterruptTime = modkernel32.NewProc("QueryUnbiasedInterruptTime")
|
||||
)
|
||||
|
||||
func queryInterruptTime(interruptTime *uint64) {
|
||||
syscall.SyscallN(procQueryInterruptTime.Addr(), uintptr(unsafe.Pointer(interruptTime)))
|
||||
return
|
||||
}
|
||||
|
||||
func getProcessIoCounters(process windows.Handle, ioCounters *processIOCounters) (err error) {
|
||||
r1, _, e1 := syscall.SyscallN(procGetProcessIoCounters.Addr(), uintptr(process), uintptr(unsafe.Pointer(ioCounters)))
|
||||
if r1 == 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func queryUnbiasedInterruptTime(unbiasedTime *uint64) (err error) {
|
||||
r1, _, e1 := syscall.SyscallN(procQueryUnbiasedInterruptTime.Addr(), uintptr(unsafe.Pointer(unbiasedTime)))
|
||||
if r1 == 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/common/dialer"
|
||||
"github.com/sagernet/sing-box/service/powerreport"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
@@ -152,6 +153,18 @@ func (e *Endpoint) Start(postStart bool) error {
|
||||
e.egressPool = tun.NewUDPEgressPool(egressPoolOptions)
|
||||
standardBind.SetEgressProvider(e.egressPool)
|
||||
}
|
||||
powerManager := service.FromContext[*powerreport.Manager](e.options.Context)
|
||||
if powerManager != nil {
|
||||
recorder := powerManager.Recorder()
|
||||
if recorder != nil {
|
||||
attribution := &powerreport.Attribution{Endpoint: e.options.Tag}
|
||||
standardBind.SetIOActivityFuncs(func(size int) {
|
||||
recorder.Touch(powerreport.DirectionInbound, size, attribution)
|
||||
}, func(size int) {
|
||||
recorder.Touch(powerreport.DirectionOutbound, size, attribution)
|
||||
})
|
||||
}
|
||||
}
|
||||
bind = standardBind
|
||||
} else {
|
||||
var (
|
||||
|
||||
@@ -27,6 +27,7 @@ type EndpointOptions struct {
|
||||
EgressPoolOptions tun.UDPEgressPoolOptions
|
||||
Dialer N.Dialer
|
||||
CreateDialer func(interfaceName string) N.Dialer
|
||||
Tag string
|
||||
Name string
|
||||
MTU uint32
|
||||
Address []netip.Prefix
|
||||
|
||||
Reference in New Issue
Block a user