mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-11 17:47:20 +10:00
platform: Add OOM Report & Crash Rerport
This commit is contained in:
@@ -87,12 +87,17 @@ func (s *StartedService) newInstance(profileContent string, overrideOptions *Ove
|
||||
}
|
||||
}
|
||||
}
|
||||
if s.oomKiller && C.IsIos {
|
||||
if s.oomKillerEnabled {
|
||||
if !common.Any(options.Services, func(it option.Service) bool {
|
||||
return it.Type == C.TypeOOMKiller
|
||||
}) {
|
||||
oomOptions := &option.OOMKillerServiceOptions{
|
||||
KillerDisabled: s.oomKillerDisabled,
|
||||
MemoryLimitOverride: s.oomMemoryLimit,
|
||||
}
|
||||
options.Services = append(options.Services, option.Service{
|
||||
Type: C.TypeOOMKiller,
|
||||
Options: oomOptions,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,5 +5,6 @@ type PlatformHandler interface {
|
||||
ServiceReload() error
|
||||
SystemProxyStatus() (*SystemProxyStatus, error)
|
||||
SetSystemProxyEnabled(enabled bool) error
|
||||
TriggerNativeCrash() error
|
||||
WriteDebugMessage(message string)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/protocol/group"
|
||||
"github.com/sagernet/sing-box/service/oomkiller"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/batch"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
@@ -24,6 +25,8 @@ import (
|
||||
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
@@ -35,7 +38,9 @@ type StartedService struct {
|
||||
handler PlatformHandler
|
||||
debug bool
|
||||
logMaxLines int
|
||||
oomKiller bool
|
||||
oomKillerEnabled bool
|
||||
oomKillerDisabled bool
|
||||
oomMemoryLimit uint64
|
||||
// workingDirectory string
|
||||
// tempDirectory string
|
||||
// userID int
|
||||
@@ -67,7 +72,9 @@ type ServiceOptions struct {
|
||||
Handler PlatformHandler
|
||||
Debug bool
|
||||
LogMaxLines int
|
||||
OOMKiller bool
|
||||
OOMKillerEnabled bool
|
||||
OOMKillerDisabled bool
|
||||
OOMMemoryLimit uint64
|
||||
// WorkingDirectory string
|
||||
// TempDirectory string
|
||||
// UserID int
|
||||
@@ -82,7 +89,9 @@ func NewStartedService(options ServiceOptions) *StartedService {
|
||||
handler: options.Handler,
|
||||
debug: options.Debug,
|
||||
logMaxLines: options.LogMaxLines,
|
||||
oomKiller: options.OOMKiller,
|
||||
oomKillerEnabled: options.OOMKillerEnabled,
|
||||
oomKillerDisabled: options.OOMKillerDisabled,
|
||||
oomMemoryLimit: options.OOMMemoryLimit,
|
||||
// workingDirectory: options.WorkingDirectory,
|
||||
// tempDirectory: options.TempDirectory,
|
||||
// userID: options.UserID,
|
||||
@@ -685,6 +694,41 @@ func (s *StartedService) SetSystemProxyEnabled(ctx context.Context, request *Set
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (s *StartedService) TriggerDebugCrash(ctx context.Context, request *DebugCrashRequest) (*emptypb.Empty, error) {
|
||||
if !s.debug {
|
||||
return nil, status.Error(codes.PermissionDenied, "debug crash trigger unavailable")
|
||||
}
|
||||
if request == nil {
|
||||
return nil, status.Error(codes.InvalidArgument, "missing debug crash request")
|
||||
}
|
||||
switch request.Type {
|
||||
case DebugCrashRequest_GO:
|
||||
time.AfterFunc(200*time.Millisecond, func() {
|
||||
panic("debug go crash")
|
||||
})
|
||||
case DebugCrashRequest_NATIVE:
|
||||
err := s.handler.TriggerNativeCrash()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, status.Error(codes.InvalidArgument, "unknown debug crash type")
|
||||
}
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *StartedService) TriggerOOMReport(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {
|
||||
instance := s.Instance()
|
||||
if instance == nil {
|
||||
return nil, status.Error(codes.FailedPrecondition, "service not started")
|
||||
}
|
||||
reporter := service.FromContext[oomkiller.OOMReporter](instance.ctx)
|
||||
if reporter == nil {
|
||||
return nil, status.Error(codes.Unavailable, "OOM reporter not available")
|
||||
}
|
||||
return &emptypb.Empty{}, reporter.WriteReport(memory.Total())
|
||||
}
|
||||
|
||||
func (s *StartedService) SubscribeConnections(request *SubscribeConnectionsRequest, server grpc.ServerStreamingServer[ConnectionEvents]) error {
|
||||
err := s.waitForStarted(server.Context())
|
||||
if err != nil {
|
||||
|
||||
@@ -182,6 +182,52 @@ func (ServiceStatus_Type) EnumDescriptor() ([]byte, []int) {
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{0, 0}
|
||||
}
|
||||
|
||||
type DebugCrashRequest_Type int32
|
||||
|
||||
const (
|
||||
DebugCrashRequest_GO DebugCrashRequest_Type = 0
|
||||
DebugCrashRequest_NATIVE DebugCrashRequest_Type = 1
|
||||
)
|
||||
|
||||
// Enum value maps for DebugCrashRequest_Type.
|
||||
var (
|
||||
DebugCrashRequest_Type_name = map[int32]string{
|
||||
0: "GO",
|
||||
1: "NATIVE",
|
||||
}
|
||||
DebugCrashRequest_Type_value = map[string]int32{
|
||||
"GO": 0,
|
||||
"NATIVE": 1,
|
||||
}
|
||||
)
|
||||
|
||||
func (x DebugCrashRequest_Type) Enum() *DebugCrashRequest_Type {
|
||||
p := new(DebugCrashRequest_Type)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x DebugCrashRequest_Type) String() string {
|
||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||
}
|
||||
|
||||
func (DebugCrashRequest_Type) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_daemon_started_service_proto_enumTypes[3].Descriptor()
|
||||
}
|
||||
|
||||
func (DebugCrashRequest_Type) Type() protoreflect.EnumType {
|
||||
return &file_daemon_started_service_proto_enumTypes[3]
|
||||
}
|
||||
|
||||
func (x DebugCrashRequest_Type) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use DebugCrashRequest_Type.Descriptor instead.
|
||||
func (DebugCrashRequest_Type) EnumDescriptor() ([]byte, []int) {
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{16, 0}
|
||||
}
|
||||
|
||||
type ServiceStatus struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Status ServiceStatus_Type `protobuf:"varint,1,opt,name=status,proto3,enum=daemon.ServiceStatus_Type" json:"status,omitempty"`
|
||||
@@ -1062,6 +1108,50 @@ func (x *SetSystemProxyEnabledRequest) GetEnabled() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type DebugCrashRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Type DebugCrashRequest_Type `protobuf:"varint,1,opt,name=type,proto3,enum=daemon.DebugCrashRequest_Type" json:"type,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *DebugCrashRequest) Reset() {
|
||||
*x = DebugCrashRequest{}
|
||||
mi := &file_daemon_started_service_proto_msgTypes[16]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *DebugCrashRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*DebugCrashRequest) ProtoMessage() {}
|
||||
|
||||
func (x *DebugCrashRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_started_service_proto_msgTypes[16]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use DebugCrashRequest.ProtoReflect.Descriptor instead.
|
||||
func (*DebugCrashRequest) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{16}
|
||||
}
|
||||
|
||||
func (x *DebugCrashRequest) GetType() DebugCrashRequest_Type {
|
||||
if x != nil {
|
||||
return x.Type
|
||||
}
|
||||
return DebugCrashRequest_GO
|
||||
}
|
||||
|
||||
type SubscribeConnectionsRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Interval int64 `protobuf:"varint,1,opt,name=interval,proto3" json:"interval,omitempty"`
|
||||
@@ -1071,7 +1161,7 @@ type SubscribeConnectionsRequest struct {
|
||||
|
||||
func (x *SubscribeConnectionsRequest) Reset() {
|
||||
*x = SubscribeConnectionsRequest{}
|
||||
mi := &file_daemon_started_service_proto_msgTypes[16]
|
||||
mi := &file_daemon_started_service_proto_msgTypes[17]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -1083,7 +1173,7 @@ func (x *SubscribeConnectionsRequest) String() string {
|
||||
func (*SubscribeConnectionsRequest) ProtoMessage() {}
|
||||
|
||||
func (x *SubscribeConnectionsRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_started_service_proto_msgTypes[16]
|
||||
mi := &file_daemon_started_service_proto_msgTypes[17]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -1096,7 +1186,7 @@ func (x *SubscribeConnectionsRequest) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use SubscribeConnectionsRequest.ProtoReflect.Descriptor instead.
|
||||
func (*SubscribeConnectionsRequest) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{16}
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{17}
|
||||
}
|
||||
|
||||
func (x *SubscribeConnectionsRequest) GetInterval() int64 {
|
||||
@@ -1120,7 +1210,7 @@ type ConnectionEvent struct {
|
||||
|
||||
func (x *ConnectionEvent) Reset() {
|
||||
*x = ConnectionEvent{}
|
||||
mi := &file_daemon_started_service_proto_msgTypes[17]
|
||||
mi := &file_daemon_started_service_proto_msgTypes[18]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -1132,7 +1222,7 @@ func (x *ConnectionEvent) String() string {
|
||||
func (*ConnectionEvent) ProtoMessage() {}
|
||||
|
||||
func (x *ConnectionEvent) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_started_service_proto_msgTypes[17]
|
||||
mi := &file_daemon_started_service_proto_msgTypes[18]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -1145,7 +1235,7 @@ func (x *ConnectionEvent) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use ConnectionEvent.ProtoReflect.Descriptor instead.
|
||||
func (*ConnectionEvent) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{17}
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{18}
|
||||
}
|
||||
|
||||
func (x *ConnectionEvent) GetType() ConnectionEventType {
|
||||
@@ -1200,7 +1290,7 @@ type ConnectionEvents struct {
|
||||
|
||||
func (x *ConnectionEvents) Reset() {
|
||||
*x = ConnectionEvents{}
|
||||
mi := &file_daemon_started_service_proto_msgTypes[18]
|
||||
mi := &file_daemon_started_service_proto_msgTypes[19]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -1212,7 +1302,7 @@ func (x *ConnectionEvents) String() string {
|
||||
func (*ConnectionEvents) ProtoMessage() {}
|
||||
|
||||
func (x *ConnectionEvents) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_started_service_proto_msgTypes[18]
|
||||
mi := &file_daemon_started_service_proto_msgTypes[19]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -1225,7 +1315,7 @@ func (x *ConnectionEvents) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use ConnectionEvents.ProtoReflect.Descriptor instead.
|
||||
func (*ConnectionEvents) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{18}
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{19}
|
||||
}
|
||||
|
||||
func (x *ConnectionEvents) GetEvents() []*ConnectionEvent {
|
||||
@@ -1272,7 +1362,7 @@ type Connection struct {
|
||||
|
||||
func (x *Connection) Reset() {
|
||||
*x = Connection{}
|
||||
mi := &file_daemon_started_service_proto_msgTypes[19]
|
||||
mi := &file_daemon_started_service_proto_msgTypes[20]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -1284,7 +1374,7 @@ func (x *Connection) String() string {
|
||||
func (*Connection) ProtoMessage() {}
|
||||
|
||||
func (x *Connection) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_started_service_proto_msgTypes[19]
|
||||
mi := &file_daemon_started_service_proto_msgTypes[20]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -1297,7 +1387,7 @@ func (x *Connection) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use Connection.ProtoReflect.Descriptor instead.
|
||||
func (*Connection) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{19}
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{20}
|
||||
}
|
||||
|
||||
func (x *Connection) GetId() string {
|
||||
@@ -1467,7 +1557,7 @@ type ProcessInfo struct {
|
||||
|
||||
func (x *ProcessInfo) Reset() {
|
||||
*x = ProcessInfo{}
|
||||
mi := &file_daemon_started_service_proto_msgTypes[20]
|
||||
mi := &file_daemon_started_service_proto_msgTypes[21]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -1479,7 +1569,7 @@ func (x *ProcessInfo) String() string {
|
||||
func (*ProcessInfo) ProtoMessage() {}
|
||||
|
||||
func (x *ProcessInfo) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_started_service_proto_msgTypes[20]
|
||||
mi := &file_daemon_started_service_proto_msgTypes[21]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -1492,7 +1582,7 @@ func (x *ProcessInfo) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use ProcessInfo.ProtoReflect.Descriptor instead.
|
||||
func (*ProcessInfo) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{20}
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{21}
|
||||
}
|
||||
|
||||
func (x *ProcessInfo) GetProcessId() uint32 {
|
||||
@@ -1539,7 +1629,7 @@ type CloseConnectionRequest struct {
|
||||
|
||||
func (x *CloseConnectionRequest) Reset() {
|
||||
*x = CloseConnectionRequest{}
|
||||
mi := &file_daemon_started_service_proto_msgTypes[21]
|
||||
mi := &file_daemon_started_service_proto_msgTypes[22]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -1551,7 +1641,7 @@ func (x *CloseConnectionRequest) String() string {
|
||||
func (*CloseConnectionRequest) ProtoMessage() {}
|
||||
|
||||
func (x *CloseConnectionRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_started_service_proto_msgTypes[21]
|
||||
mi := &file_daemon_started_service_proto_msgTypes[22]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -1564,7 +1654,7 @@ func (x *CloseConnectionRequest) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use CloseConnectionRequest.ProtoReflect.Descriptor instead.
|
||||
func (*CloseConnectionRequest) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{21}
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{22}
|
||||
}
|
||||
|
||||
func (x *CloseConnectionRequest) GetId() string {
|
||||
@@ -1583,7 +1673,7 @@ type DeprecatedWarnings struct {
|
||||
|
||||
func (x *DeprecatedWarnings) Reset() {
|
||||
*x = DeprecatedWarnings{}
|
||||
mi := &file_daemon_started_service_proto_msgTypes[22]
|
||||
mi := &file_daemon_started_service_proto_msgTypes[23]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -1595,7 +1685,7 @@ func (x *DeprecatedWarnings) String() string {
|
||||
func (*DeprecatedWarnings) ProtoMessage() {}
|
||||
|
||||
func (x *DeprecatedWarnings) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_started_service_proto_msgTypes[22]
|
||||
mi := &file_daemon_started_service_proto_msgTypes[23]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -1608,7 +1698,7 @@ func (x *DeprecatedWarnings) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use DeprecatedWarnings.ProtoReflect.Descriptor instead.
|
||||
func (*DeprecatedWarnings) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{22}
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{23}
|
||||
}
|
||||
|
||||
func (x *DeprecatedWarnings) GetWarnings() []*DeprecatedWarning {
|
||||
@@ -1629,7 +1719,7 @@ type DeprecatedWarning struct {
|
||||
|
||||
func (x *DeprecatedWarning) Reset() {
|
||||
*x = DeprecatedWarning{}
|
||||
mi := &file_daemon_started_service_proto_msgTypes[23]
|
||||
mi := &file_daemon_started_service_proto_msgTypes[24]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -1641,7 +1731,7 @@ func (x *DeprecatedWarning) String() string {
|
||||
func (*DeprecatedWarning) ProtoMessage() {}
|
||||
|
||||
func (x *DeprecatedWarning) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_started_service_proto_msgTypes[23]
|
||||
mi := &file_daemon_started_service_proto_msgTypes[24]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -1654,7 +1744,7 @@ func (x *DeprecatedWarning) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use DeprecatedWarning.ProtoReflect.Descriptor instead.
|
||||
func (*DeprecatedWarning) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{23}
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{24}
|
||||
}
|
||||
|
||||
func (x *DeprecatedWarning) GetMessage() string {
|
||||
@@ -1687,7 +1777,7 @@ type StartedAt struct {
|
||||
|
||||
func (x *StartedAt) Reset() {
|
||||
*x = StartedAt{}
|
||||
mi := &file_daemon_started_service_proto_msgTypes[24]
|
||||
mi := &file_daemon_started_service_proto_msgTypes[25]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -1699,7 +1789,7 @@ func (x *StartedAt) String() string {
|
||||
func (*StartedAt) ProtoMessage() {}
|
||||
|
||||
func (x *StartedAt) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_started_service_proto_msgTypes[24]
|
||||
mi := &file_daemon_started_service_proto_msgTypes[25]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -1712,7 +1802,7 @@ func (x *StartedAt) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use StartedAt.ProtoReflect.Descriptor instead.
|
||||
func (*StartedAt) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{24}
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{25}
|
||||
}
|
||||
|
||||
func (x *StartedAt) GetStartedAt() int64 {
|
||||
@@ -1732,7 +1822,7 @@ type Log_Message struct {
|
||||
|
||||
func (x *Log_Message) Reset() {
|
||||
*x = Log_Message{}
|
||||
mi := &file_daemon_started_service_proto_msgTypes[25]
|
||||
mi := &file_daemon_started_service_proto_msgTypes[26]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -1744,7 +1834,7 @@ func (x *Log_Message) String() string {
|
||||
func (*Log_Message) ProtoMessage() {}
|
||||
|
||||
func (x *Log_Message) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_started_service_proto_msgTypes[25]
|
||||
mi := &file_daemon_started_service_proto_msgTypes[26]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -1845,7 +1935,13 @@ const file_daemon_started_service_proto_rawDesc = "" +
|
||||
"\tavailable\x18\x01 \x01(\bR\tavailable\x12\x18\n" +
|
||||
"\aenabled\x18\x02 \x01(\bR\aenabled\"8\n" +
|
||||
"\x1cSetSystemProxyEnabledRequest\x12\x18\n" +
|
||||
"\aenabled\x18\x01 \x01(\bR\aenabled\"9\n" +
|
||||
"\aenabled\x18\x01 \x01(\bR\aenabled\"c\n" +
|
||||
"\x11DebugCrashRequest\x122\n" +
|
||||
"\x04type\x18\x01 \x01(\x0e2\x1e.daemon.DebugCrashRequest.TypeR\x04type\"\x1a\n" +
|
||||
"\x04Type\x12\x06\n" +
|
||||
"\x02GO\x10\x00\x12\n" +
|
||||
"\n" +
|
||||
"\x06NATIVE\x10\x01\"9\n" +
|
||||
"\x1bSubscribeConnectionsRequest\x12\x1a\n" +
|
||||
"\binterval\x18\x01 \x01(\x03R\binterval\"\xea\x01\n" +
|
||||
"\x0fConnectionEvent\x12/\n" +
|
||||
@@ -1912,7 +2008,7 @@ const file_daemon_started_service_proto_rawDesc = "" +
|
||||
"\x13ConnectionEventType\x12\x18\n" +
|
||||
"\x14CONNECTION_EVENT_NEW\x10\x00\x12\x1b\n" +
|
||||
"\x17CONNECTION_EVENT_UPDATE\x10\x01\x12\x1b\n" +
|
||||
"\x17CONNECTION_EVENT_CLOSED\x10\x022\xe5\v\n" +
|
||||
"\x17CONNECTION_EVENT_CLOSED\x10\x022\xf5\f\n" +
|
||||
"\x0eStartedService\x12=\n" +
|
||||
"\vStopService\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\x12?\n" +
|
||||
"\rReloadService\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\x12K\n" +
|
||||
@@ -1929,7 +2025,9 @@ const file_daemon_started_service_proto_rawDesc = "" +
|
||||
"\x0eSelectOutbound\x12\x1d.daemon.SelectOutboundRequest\x1a\x16.google.protobuf.Empty\"\x00\x12I\n" +
|
||||
"\x0eSetGroupExpand\x12\x1d.daemon.SetGroupExpandRequest\x1a\x16.google.protobuf.Empty\"\x00\x12K\n" +
|
||||
"\x14GetSystemProxyStatus\x12\x16.google.protobuf.Empty\x1a\x19.daemon.SystemProxyStatus\"\x00\x12W\n" +
|
||||
"\x15SetSystemProxyEnabled\x12$.daemon.SetSystemProxyEnabledRequest\x1a\x16.google.protobuf.Empty\"\x00\x12Y\n" +
|
||||
"\x15SetSystemProxyEnabled\x12$.daemon.SetSystemProxyEnabledRequest\x1a\x16.google.protobuf.Empty\"\x00\x12H\n" +
|
||||
"\x11TriggerDebugCrash\x12\x19.daemon.DebugCrashRequest\x1a\x16.google.protobuf.Empty\"\x00\x12D\n" +
|
||||
"\x10TriggerOOMReport\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\"\x00\x12Y\n" +
|
||||
"\x14SubscribeConnections\x12#.daemon.SubscribeConnectionsRequest\x1a\x18.daemon.ConnectionEvents\"\x000\x01\x12K\n" +
|
||||
"\x0fCloseConnection\x12\x1e.daemon.CloseConnectionRequest\x1a\x16.google.protobuf.Empty\"\x00\x12G\n" +
|
||||
"\x13CloseAllConnections\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\"\x00\x12M\n" +
|
||||
@@ -1949,101 +2047,108 @@ func file_daemon_started_service_proto_rawDescGZIP() []byte {
|
||||
}
|
||||
|
||||
var (
|
||||
file_daemon_started_service_proto_enumTypes = make([]protoimpl.EnumInfo, 3)
|
||||
file_daemon_started_service_proto_msgTypes = make([]protoimpl.MessageInfo, 26)
|
||||
file_daemon_started_service_proto_enumTypes = make([]protoimpl.EnumInfo, 4)
|
||||
file_daemon_started_service_proto_msgTypes = make([]protoimpl.MessageInfo, 27)
|
||||
file_daemon_started_service_proto_goTypes = []any{
|
||||
(LogLevel)(0), // 0: daemon.LogLevel
|
||||
(ConnectionEventType)(0), // 1: daemon.ConnectionEventType
|
||||
(ServiceStatus_Type)(0), // 2: daemon.ServiceStatus.Type
|
||||
(*ServiceStatus)(nil), // 3: daemon.ServiceStatus
|
||||
(*ReloadServiceRequest)(nil), // 4: daemon.ReloadServiceRequest
|
||||
(*SubscribeStatusRequest)(nil), // 5: daemon.SubscribeStatusRequest
|
||||
(*Log)(nil), // 6: daemon.Log
|
||||
(*DefaultLogLevel)(nil), // 7: daemon.DefaultLogLevel
|
||||
(*Status)(nil), // 8: daemon.Status
|
||||
(*Groups)(nil), // 9: daemon.Groups
|
||||
(*Group)(nil), // 10: daemon.Group
|
||||
(*GroupItem)(nil), // 11: daemon.GroupItem
|
||||
(*URLTestRequest)(nil), // 12: daemon.URLTestRequest
|
||||
(*SelectOutboundRequest)(nil), // 13: daemon.SelectOutboundRequest
|
||||
(*SetGroupExpandRequest)(nil), // 14: daemon.SetGroupExpandRequest
|
||||
(*ClashMode)(nil), // 15: daemon.ClashMode
|
||||
(*ClashModeStatus)(nil), // 16: daemon.ClashModeStatus
|
||||
(*SystemProxyStatus)(nil), // 17: daemon.SystemProxyStatus
|
||||
(*SetSystemProxyEnabledRequest)(nil), // 18: daemon.SetSystemProxyEnabledRequest
|
||||
(*SubscribeConnectionsRequest)(nil), // 19: daemon.SubscribeConnectionsRequest
|
||||
(*ConnectionEvent)(nil), // 20: daemon.ConnectionEvent
|
||||
(*ConnectionEvents)(nil), // 21: daemon.ConnectionEvents
|
||||
(*Connection)(nil), // 22: daemon.Connection
|
||||
(*ProcessInfo)(nil), // 23: daemon.ProcessInfo
|
||||
(*CloseConnectionRequest)(nil), // 24: daemon.CloseConnectionRequest
|
||||
(*DeprecatedWarnings)(nil), // 25: daemon.DeprecatedWarnings
|
||||
(*DeprecatedWarning)(nil), // 26: daemon.DeprecatedWarning
|
||||
(*StartedAt)(nil), // 27: daemon.StartedAt
|
||||
(*Log_Message)(nil), // 28: daemon.Log.Message
|
||||
(*emptypb.Empty)(nil), // 29: google.protobuf.Empty
|
||||
(DebugCrashRequest_Type)(0), // 3: daemon.DebugCrashRequest.Type
|
||||
(*ServiceStatus)(nil), // 4: daemon.ServiceStatus
|
||||
(*ReloadServiceRequest)(nil), // 5: daemon.ReloadServiceRequest
|
||||
(*SubscribeStatusRequest)(nil), // 6: daemon.SubscribeStatusRequest
|
||||
(*Log)(nil), // 7: daemon.Log
|
||||
(*DefaultLogLevel)(nil), // 8: daemon.DefaultLogLevel
|
||||
(*Status)(nil), // 9: daemon.Status
|
||||
(*Groups)(nil), // 10: daemon.Groups
|
||||
(*Group)(nil), // 11: daemon.Group
|
||||
(*GroupItem)(nil), // 12: daemon.GroupItem
|
||||
(*URLTestRequest)(nil), // 13: daemon.URLTestRequest
|
||||
(*SelectOutboundRequest)(nil), // 14: daemon.SelectOutboundRequest
|
||||
(*SetGroupExpandRequest)(nil), // 15: daemon.SetGroupExpandRequest
|
||||
(*ClashMode)(nil), // 16: daemon.ClashMode
|
||||
(*ClashModeStatus)(nil), // 17: daemon.ClashModeStatus
|
||||
(*SystemProxyStatus)(nil), // 18: daemon.SystemProxyStatus
|
||||
(*SetSystemProxyEnabledRequest)(nil), // 19: daemon.SetSystemProxyEnabledRequest
|
||||
(*DebugCrashRequest)(nil), // 20: daemon.DebugCrashRequest
|
||||
(*SubscribeConnectionsRequest)(nil), // 21: daemon.SubscribeConnectionsRequest
|
||||
(*ConnectionEvent)(nil), // 22: daemon.ConnectionEvent
|
||||
(*ConnectionEvents)(nil), // 23: daemon.ConnectionEvents
|
||||
(*Connection)(nil), // 24: daemon.Connection
|
||||
(*ProcessInfo)(nil), // 25: daemon.ProcessInfo
|
||||
(*CloseConnectionRequest)(nil), // 26: daemon.CloseConnectionRequest
|
||||
(*DeprecatedWarnings)(nil), // 27: daemon.DeprecatedWarnings
|
||||
(*DeprecatedWarning)(nil), // 28: daemon.DeprecatedWarning
|
||||
(*StartedAt)(nil), // 29: daemon.StartedAt
|
||||
(*Log_Message)(nil), // 30: daemon.Log.Message
|
||||
(*emptypb.Empty)(nil), // 31: google.protobuf.Empty
|
||||
}
|
||||
)
|
||||
|
||||
var file_daemon_started_service_proto_depIdxs = []int32{
|
||||
2, // 0: daemon.ServiceStatus.status:type_name -> daemon.ServiceStatus.Type
|
||||
28, // 1: daemon.Log.messages:type_name -> daemon.Log.Message
|
||||
30, // 1: daemon.Log.messages:type_name -> daemon.Log.Message
|
||||
0, // 2: daemon.DefaultLogLevel.level:type_name -> daemon.LogLevel
|
||||
10, // 3: daemon.Groups.group:type_name -> daemon.Group
|
||||
11, // 4: daemon.Group.items:type_name -> daemon.GroupItem
|
||||
1, // 5: daemon.ConnectionEvent.type:type_name -> daemon.ConnectionEventType
|
||||
22, // 6: daemon.ConnectionEvent.connection:type_name -> daemon.Connection
|
||||
20, // 7: daemon.ConnectionEvents.events:type_name -> daemon.ConnectionEvent
|
||||
23, // 8: daemon.Connection.processInfo:type_name -> daemon.ProcessInfo
|
||||
26, // 9: daemon.DeprecatedWarnings.warnings:type_name -> daemon.DeprecatedWarning
|
||||
0, // 10: daemon.Log.Message.level:type_name -> daemon.LogLevel
|
||||
29, // 11: daemon.StartedService.StopService:input_type -> google.protobuf.Empty
|
||||
29, // 12: daemon.StartedService.ReloadService:input_type -> google.protobuf.Empty
|
||||
29, // 13: daemon.StartedService.SubscribeServiceStatus:input_type -> google.protobuf.Empty
|
||||
29, // 14: daemon.StartedService.SubscribeLog:input_type -> google.protobuf.Empty
|
||||
29, // 15: daemon.StartedService.GetDefaultLogLevel:input_type -> google.protobuf.Empty
|
||||
29, // 16: daemon.StartedService.ClearLogs:input_type -> google.protobuf.Empty
|
||||
5, // 17: daemon.StartedService.SubscribeStatus:input_type -> daemon.SubscribeStatusRequest
|
||||
29, // 18: daemon.StartedService.SubscribeGroups:input_type -> google.protobuf.Empty
|
||||
29, // 19: daemon.StartedService.GetClashModeStatus:input_type -> google.protobuf.Empty
|
||||
29, // 20: daemon.StartedService.SubscribeClashMode:input_type -> google.protobuf.Empty
|
||||
15, // 21: daemon.StartedService.SetClashMode:input_type -> daemon.ClashMode
|
||||
12, // 22: daemon.StartedService.URLTest:input_type -> daemon.URLTestRequest
|
||||
13, // 23: daemon.StartedService.SelectOutbound:input_type -> daemon.SelectOutboundRequest
|
||||
14, // 24: daemon.StartedService.SetGroupExpand:input_type -> daemon.SetGroupExpandRequest
|
||||
29, // 25: daemon.StartedService.GetSystemProxyStatus:input_type -> google.protobuf.Empty
|
||||
18, // 26: daemon.StartedService.SetSystemProxyEnabled:input_type -> daemon.SetSystemProxyEnabledRequest
|
||||
19, // 27: daemon.StartedService.SubscribeConnections:input_type -> daemon.SubscribeConnectionsRequest
|
||||
24, // 28: daemon.StartedService.CloseConnection:input_type -> daemon.CloseConnectionRequest
|
||||
29, // 29: daemon.StartedService.CloseAllConnections:input_type -> google.protobuf.Empty
|
||||
29, // 30: daemon.StartedService.GetDeprecatedWarnings:input_type -> google.protobuf.Empty
|
||||
29, // 31: daemon.StartedService.GetStartedAt:input_type -> google.protobuf.Empty
|
||||
29, // 32: daemon.StartedService.StopService:output_type -> google.protobuf.Empty
|
||||
29, // 33: daemon.StartedService.ReloadService:output_type -> google.protobuf.Empty
|
||||
3, // 34: daemon.StartedService.SubscribeServiceStatus:output_type -> daemon.ServiceStatus
|
||||
6, // 35: daemon.StartedService.SubscribeLog:output_type -> daemon.Log
|
||||
7, // 36: daemon.StartedService.GetDefaultLogLevel:output_type -> daemon.DefaultLogLevel
|
||||
29, // 37: daemon.StartedService.ClearLogs:output_type -> google.protobuf.Empty
|
||||
8, // 38: daemon.StartedService.SubscribeStatus:output_type -> daemon.Status
|
||||
9, // 39: daemon.StartedService.SubscribeGroups:output_type -> daemon.Groups
|
||||
16, // 40: daemon.StartedService.GetClashModeStatus:output_type -> daemon.ClashModeStatus
|
||||
15, // 41: daemon.StartedService.SubscribeClashMode:output_type -> daemon.ClashMode
|
||||
29, // 42: daemon.StartedService.SetClashMode:output_type -> google.protobuf.Empty
|
||||
29, // 43: daemon.StartedService.URLTest:output_type -> google.protobuf.Empty
|
||||
29, // 44: daemon.StartedService.SelectOutbound:output_type -> google.protobuf.Empty
|
||||
29, // 45: daemon.StartedService.SetGroupExpand:output_type -> google.protobuf.Empty
|
||||
17, // 46: daemon.StartedService.GetSystemProxyStatus:output_type -> daemon.SystemProxyStatus
|
||||
29, // 47: daemon.StartedService.SetSystemProxyEnabled:output_type -> google.protobuf.Empty
|
||||
21, // 48: daemon.StartedService.SubscribeConnections:output_type -> daemon.ConnectionEvents
|
||||
29, // 49: daemon.StartedService.CloseConnection:output_type -> google.protobuf.Empty
|
||||
29, // 50: daemon.StartedService.CloseAllConnections:output_type -> google.protobuf.Empty
|
||||
25, // 51: daemon.StartedService.GetDeprecatedWarnings:output_type -> daemon.DeprecatedWarnings
|
||||
27, // 52: daemon.StartedService.GetStartedAt:output_type -> daemon.StartedAt
|
||||
32, // [32:53] is the sub-list for method output_type
|
||||
11, // [11:32] is the sub-list for method input_type
|
||||
11, // [11:11] is the sub-list for extension type_name
|
||||
11, // [11:11] is the sub-list for extension extendee
|
||||
0, // [0:11] is the sub-list for field type_name
|
||||
11, // 3: daemon.Groups.group:type_name -> daemon.Group
|
||||
12, // 4: daemon.Group.items:type_name -> daemon.GroupItem
|
||||
3, // 5: daemon.DebugCrashRequest.type:type_name -> daemon.DebugCrashRequest.Type
|
||||
1, // 6: daemon.ConnectionEvent.type:type_name -> daemon.ConnectionEventType
|
||||
24, // 7: daemon.ConnectionEvent.connection:type_name -> daemon.Connection
|
||||
22, // 8: daemon.ConnectionEvents.events:type_name -> daemon.ConnectionEvent
|
||||
25, // 9: daemon.Connection.processInfo:type_name -> daemon.ProcessInfo
|
||||
28, // 10: daemon.DeprecatedWarnings.warnings:type_name -> daemon.DeprecatedWarning
|
||||
0, // 11: daemon.Log.Message.level:type_name -> daemon.LogLevel
|
||||
31, // 12: daemon.StartedService.StopService:input_type -> google.protobuf.Empty
|
||||
31, // 13: daemon.StartedService.ReloadService:input_type -> google.protobuf.Empty
|
||||
31, // 14: daemon.StartedService.SubscribeServiceStatus:input_type -> google.protobuf.Empty
|
||||
31, // 15: daemon.StartedService.SubscribeLog:input_type -> google.protobuf.Empty
|
||||
31, // 16: daemon.StartedService.GetDefaultLogLevel:input_type -> google.protobuf.Empty
|
||||
31, // 17: daemon.StartedService.ClearLogs:input_type -> google.protobuf.Empty
|
||||
6, // 18: daemon.StartedService.SubscribeStatus:input_type -> daemon.SubscribeStatusRequest
|
||||
31, // 19: daemon.StartedService.SubscribeGroups:input_type -> google.protobuf.Empty
|
||||
31, // 20: daemon.StartedService.GetClashModeStatus:input_type -> google.protobuf.Empty
|
||||
31, // 21: daemon.StartedService.SubscribeClashMode:input_type -> google.protobuf.Empty
|
||||
16, // 22: daemon.StartedService.SetClashMode:input_type -> daemon.ClashMode
|
||||
13, // 23: daemon.StartedService.URLTest:input_type -> daemon.URLTestRequest
|
||||
14, // 24: daemon.StartedService.SelectOutbound:input_type -> daemon.SelectOutboundRequest
|
||||
15, // 25: daemon.StartedService.SetGroupExpand:input_type -> daemon.SetGroupExpandRequest
|
||||
31, // 26: daemon.StartedService.GetSystemProxyStatus:input_type -> google.protobuf.Empty
|
||||
19, // 27: daemon.StartedService.SetSystemProxyEnabled:input_type -> daemon.SetSystemProxyEnabledRequest
|
||||
20, // 28: daemon.StartedService.TriggerDebugCrash:input_type -> daemon.DebugCrashRequest
|
||||
31, // 29: daemon.StartedService.TriggerOOMReport:input_type -> google.protobuf.Empty
|
||||
21, // 30: daemon.StartedService.SubscribeConnections:input_type -> daemon.SubscribeConnectionsRequest
|
||||
26, // 31: daemon.StartedService.CloseConnection:input_type -> daemon.CloseConnectionRequest
|
||||
31, // 32: daemon.StartedService.CloseAllConnections:input_type -> google.protobuf.Empty
|
||||
31, // 33: daemon.StartedService.GetDeprecatedWarnings:input_type -> google.protobuf.Empty
|
||||
31, // 34: daemon.StartedService.GetStartedAt:input_type -> google.protobuf.Empty
|
||||
31, // 35: daemon.StartedService.StopService:output_type -> google.protobuf.Empty
|
||||
31, // 36: daemon.StartedService.ReloadService:output_type -> google.protobuf.Empty
|
||||
4, // 37: daemon.StartedService.SubscribeServiceStatus:output_type -> daemon.ServiceStatus
|
||||
7, // 38: daemon.StartedService.SubscribeLog:output_type -> daemon.Log
|
||||
8, // 39: daemon.StartedService.GetDefaultLogLevel:output_type -> daemon.DefaultLogLevel
|
||||
31, // 40: daemon.StartedService.ClearLogs:output_type -> google.protobuf.Empty
|
||||
9, // 41: daemon.StartedService.SubscribeStatus:output_type -> daemon.Status
|
||||
10, // 42: daemon.StartedService.SubscribeGroups:output_type -> daemon.Groups
|
||||
17, // 43: daemon.StartedService.GetClashModeStatus:output_type -> daemon.ClashModeStatus
|
||||
16, // 44: daemon.StartedService.SubscribeClashMode:output_type -> daemon.ClashMode
|
||||
31, // 45: daemon.StartedService.SetClashMode:output_type -> google.protobuf.Empty
|
||||
31, // 46: daemon.StartedService.URLTest:output_type -> google.protobuf.Empty
|
||||
31, // 47: daemon.StartedService.SelectOutbound:output_type -> google.protobuf.Empty
|
||||
31, // 48: daemon.StartedService.SetGroupExpand:output_type -> google.protobuf.Empty
|
||||
18, // 49: daemon.StartedService.GetSystemProxyStatus:output_type -> daemon.SystemProxyStatus
|
||||
31, // 50: daemon.StartedService.SetSystemProxyEnabled:output_type -> google.protobuf.Empty
|
||||
31, // 51: daemon.StartedService.TriggerDebugCrash:output_type -> google.protobuf.Empty
|
||||
31, // 52: daemon.StartedService.TriggerOOMReport:output_type -> google.protobuf.Empty
|
||||
23, // 53: daemon.StartedService.SubscribeConnections:output_type -> daemon.ConnectionEvents
|
||||
31, // 54: daemon.StartedService.CloseConnection:output_type -> google.protobuf.Empty
|
||||
31, // 55: daemon.StartedService.CloseAllConnections:output_type -> google.protobuf.Empty
|
||||
27, // 56: daemon.StartedService.GetDeprecatedWarnings:output_type -> daemon.DeprecatedWarnings
|
||||
29, // 57: daemon.StartedService.GetStartedAt:output_type -> daemon.StartedAt
|
||||
35, // [35:58] is the sub-list for method output_type
|
||||
12, // [12:35] is the sub-list for method input_type
|
||||
12, // [12:12] is the sub-list for extension type_name
|
||||
12, // [12:12] is the sub-list for extension extendee
|
||||
0, // [0:12] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_daemon_started_service_proto_init() }
|
||||
@@ -2056,8 +2161,8 @@ func file_daemon_started_service_proto_init() {
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_daemon_started_service_proto_rawDesc), len(file_daemon_started_service_proto_rawDesc)),
|
||||
NumEnums: 3,
|
||||
NumMessages: 26,
|
||||
NumEnums: 4,
|
||||
NumMessages: 27,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
|
||||
@@ -26,6 +26,8 @@ service StartedService {
|
||||
|
||||
rpc GetSystemProxyStatus(google.protobuf.Empty) returns(SystemProxyStatus) {}
|
||||
rpc SetSystemProxyEnabled(SetSystemProxyEnabledRequest) returns(google.protobuf.Empty) {}
|
||||
rpc TriggerDebugCrash(DebugCrashRequest) returns(google.protobuf.Empty) {}
|
||||
rpc TriggerOOMReport(google.protobuf.Empty) returns(google.protobuf.Empty) {}
|
||||
|
||||
rpc SubscribeConnections(SubscribeConnectionsRequest) returns(stream ConnectionEvents) {}
|
||||
rpc CloseConnection(CloseConnectionRequest) returns(google.protobuf.Empty) {}
|
||||
@@ -141,6 +143,15 @@ message SetSystemProxyEnabledRequest {
|
||||
bool enabled = 1;
|
||||
}
|
||||
|
||||
message DebugCrashRequest {
|
||||
enum Type {
|
||||
GO = 0;
|
||||
NATIVE = 1;
|
||||
}
|
||||
|
||||
Type type = 1;
|
||||
}
|
||||
|
||||
message SubscribeConnectionsRequest {
|
||||
int64 interval = 1;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ const (
|
||||
StartedService_SetGroupExpand_FullMethodName = "/daemon.StartedService/SetGroupExpand"
|
||||
StartedService_GetSystemProxyStatus_FullMethodName = "/daemon.StartedService/GetSystemProxyStatus"
|
||||
StartedService_SetSystemProxyEnabled_FullMethodName = "/daemon.StartedService/SetSystemProxyEnabled"
|
||||
StartedService_TriggerDebugCrash_FullMethodName = "/daemon.StartedService/TriggerDebugCrash"
|
||||
StartedService_TriggerOOMReport_FullMethodName = "/daemon.StartedService/TriggerOOMReport"
|
||||
StartedService_SubscribeConnections_FullMethodName = "/daemon.StartedService/SubscribeConnections"
|
||||
StartedService_CloseConnection_FullMethodName = "/daemon.StartedService/CloseConnection"
|
||||
StartedService_CloseAllConnections_FullMethodName = "/daemon.StartedService/CloseAllConnections"
|
||||
@@ -58,6 +60,8 @@ type StartedServiceClient interface {
|
||||
SetGroupExpand(ctx context.Context, in *SetGroupExpandRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
GetSystemProxyStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*SystemProxyStatus, error)
|
||||
SetSystemProxyEnabled(ctx context.Context, in *SetSystemProxyEnabledRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
TriggerDebugCrash(ctx context.Context, in *DebugCrashRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
TriggerOOMReport(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
SubscribeConnections(ctx context.Context, in *SubscribeConnectionsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ConnectionEvents], error)
|
||||
CloseConnection(ctx context.Context, in *CloseConnectionRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
CloseAllConnections(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
@@ -278,6 +282,26 @@ func (c *startedServiceClient) SetSystemProxyEnabled(ctx context.Context, in *Se
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *startedServiceClient) TriggerDebugCrash(ctx context.Context, in *DebugCrashRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(emptypb.Empty)
|
||||
err := c.cc.Invoke(ctx, StartedService_TriggerDebugCrash_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *startedServiceClient) TriggerOOMReport(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, StartedService_TriggerOOMReport_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *startedServiceClient) SubscribeConnections(ctx context.Context, in *SubscribeConnectionsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ConnectionEvents], error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[5], StartedService_SubscribeConnections_FullMethodName, cOpts...)
|
||||
@@ -357,6 +381,8 @@ type StartedServiceServer interface {
|
||||
SetGroupExpand(context.Context, *SetGroupExpandRequest) (*emptypb.Empty, error)
|
||||
GetSystemProxyStatus(context.Context, *emptypb.Empty) (*SystemProxyStatus, error)
|
||||
SetSystemProxyEnabled(context.Context, *SetSystemProxyEnabledRequest) (*emptypb.Empty, error)
|
||||
TriggerDebugCrash(context.Context, *DebugCrashRequest) (*emptypb.Empty, error)
|
||||
TriggerOOMReport(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
|
||||
SubscribeConnections(*SubscribeConnectionsRequest, grpc.ServerStreamingServer[ConnectionEvents]) error
|
||||
CloseConnection(context.Context, *CloseConnectionRequest) (*emptypb.Empty, error)
|
||||
CloseAllConnections(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
|
||||
@@ -436,6 +462,14 @@ func (UnimplementedStartedServiceServer) SetSystemProxyEnabled(context.Context,
|
||||
return nil, status.Error(codes.Unimplemented, "method SetSystemProxyEnabled not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedStartedServiceServer) TriggerDebugCrash(context.Context, *DebugCrashRequest) (*emptypb.Empty, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method TriggerDebugCrash not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedStartedServiceServer) TriggerOOMReport(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method TriggerOOMReport not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedStartedServiceServer) SubscribeConnections(*SubscribeConnectionsRequest, grpc.ServerStreamingServer[ConnectionEvents]) error {
|
||||
return status.Error(codes.Unimplemented, "method SubscribeConnections not implemented")
|
||||
}
|
||||
@@ -729,6 +763,42 @@ func _StartedService_SetSystemProxyEnabled_Handler(srv interface{}, ctx context.
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _StartedService_TriggerDebugCrash_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(DebugCrashRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(StartedServiceServer).TriggerDebugCrash(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: StartedService_TriggerDebugCrash_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(StartedServiceServer).TriggerDebugCrash(ctx, req.(*DebugCrashRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _StartedService_TriggerOOMReport_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.(StartedServiceServer).TriggerOOMReport(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: StartedService_TriggerOOMReport_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(StartedServiceServer).TriggerOOMReport(ctx, req.(*emptypb.Empty))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _StartedService_SubscribeConnections_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
m := new(SubscribeConnectionsRequest)
|
||||
if err := stream.RecvMsg(m); err != nil {
|
||||
@@ -863,6 +933,14 @@ var StartedService_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "SetSystemProxyEnabled",
|
||||
Handler: _StartedService_SetSystemProxyEnabled_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "TriggerDebugCrash",
|
||||
Handler: _StartedService_TriggerDebugCrash_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "TriggerOOMReport",
|
||||
Handler: _StartedService_TriggerOOMReport_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "CloseConnection",
|
||||
Handler: _StartedService_CloseConnection_Handler,
|
||||
|
||||
@@ -540,6 +540,31 @@ func (c *CommandClient) SetSystemProxyEnabled(isEnabled bool) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *CommandClient) TriggerGoCrash() error {
|
||||
_, err := callWithResult(c, func(client daemon.StartedServiceClient) (*emptypb.Empty, error) {
|
||||
return client.TriggerDebugCrash(context.Background(), &daemon.DebugCrashRequest{
|
||||
Type: daemon.DebugCrashRequest_GO,
|
||||
})
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *CommandClient) TriggerNativeCrash() error {
|
||||
_, err := callWithResult(c, func(client daemon.StartedServiceClient) (*emptypb.Empty, error) {
|
||||
return client.TriggerDebugCrash(context.Background(), &daemon.DebugCrashRequest{
|
||||
Type: daemon.DebugCrashRequest_NATIVE,
|
||||
})
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *CommandClient) TriggerOOMReport() error {
|
||||
_, err := callWithResult(c, func(client daemon.StartedServiceClient) (*emptypb.Empty, error) {
|
||||
return client.TriggerOOMReport(context.Background(), &emptypb.Empty{})
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *CommandClient) GetDeprecatedNotes() (DeprecatedNoteIterator, error) {
|
||||
return callWithResult(c, func(client daemon.StartedServiceClient) (DeprecatedNoteIterator, error) {
|
||||
warnings, err := client.GetDeprecatedWarnings(context.Background(), &emptypb.Empty{})
|
||||
|
||||
@@ -39,6 +39,7 @@ type CommandServerHandler interface {
|
||||
ServiceReload() error
|
||||
GetSystemProxyStatus() (*SystemProxyStatus, error)
|
||||
SetSystemProxyEnabled(enabled bool) error
|
||||
TriggerNativeCrash() error
|
||||
WriteDebugMessage(message string)
|
||||
}
|
||||
|
||||
@@ -60,7 +61,9 @@ func NewCommandServer(handler CommandServerHandler, platformInterface PlatformIn
|
||||
Handler: (*platformHandler)(server),
|
||||
Debug: sDebug,
|
||||
LogMaxLines: sLogMaxLines,
|
||||
OOMKiller: memoryLimitEnabled,
|
||||
OOMKillerEnabled: sOOMKillerEnabled,
|
||||
OOMKillerDisabled: sOOMKillerDisabled,
|
||||
OOMMemoryLimit: uint64(sOOMMemoryLimit),
|
||||
// WorkingDirectory: sWorkingPath,
|
||||
// TempDirectory: sTempPath,
|
||||
// UserID: sUserID,
|
||||
@@ -170,11 +173,16 @@ type OverrideOptions struct {
|
||||
}
|
||||
|
||||
func (s *CommandServer) StartOrReloadService(configContent string, options *OverrideOptions) error {
|
||||
return s.StartedService.StartOrReloadService(configContent, &daemon.OverrideOptions{
|
||||
saveConfigSnapshot(configContent)
|
||||
err := s.StartedService.StartOrReloadService(configContent, &daemon.OverrideOptions{
|
||||
AutoRedirect: options.AutoRedirect,
|
||||
IncludePackage: iteratorToArray(options.IncludePackage),
|
||||
ExcludePackage: iteratorToArray(options.ExcludePackage),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *CommandServer) CloseService() error {
|
||||
@@ -271,6 +279,10 @@ func (h *platformHandler) SetSystemProxyEnabled(enabled bool) error {
|
||||
return (*CommandServer)(h).handler.SetSystemProxyEnabled(enabled)
|
||||
}
|
||||
|
||||
func (h *platformHandler) TriggerNativeCrash() error {
|
||||
return (*CommandServer)(h).handler.TriggerNativeCrash()
|
||||
}
|
||||
|
||||
func (h *platformHandler) WriteDebugMessage(message string) {
|
||||
(*CommandServer)(h).handler.WriteDebugMessage(message)
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/sagernet/sing-box/include"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-box/service/oomkiller"
|
||||
tun "github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
@@ -22,6 +23,8 @@ import (
|
||||
"github.com/sagernet/sing/service/filemanager"
|
||||
)
|
||||
|
||||
var sOOMReporter oomkiller.OOMReporter
|
||||
|
||||
func baseContext(platformInterface PlatformInterface) context.Context {
|
||||
dnsRegistry := include.DNSTransportRegistry()
|
||||
if platformInterface != nil {
|
||||
@@ -33,6 +36,9 @@ func baseContext(platformInterface PlatformInterface) context.Context {
|
||||
}
|
||||
ctx := context.Background()
|
||||
ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID)
|
||||
if sOOMReporter != nil {
|
||||
ctx = service.ContextWith[oomkiller.OOMReporter](ctx, sOOMReporter)
|
||||
}
|
||||
return box.Context(ctx, include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), dnsRegistry, include.ServiceRegistry(), include.CertificateProviderRegistry())
|
||||
}
|
||||
|
||||
|
||||
9
experimental/libbox/debug.go
Normal file
9
experimental/libbox/debug.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package libbox
|
||||
|
||||
import "time"
|
||||
|
||||
func TriggerGoPanic() {
|
||||
time.AfterFunc(200*time.Millisecond, func() {
|
||||
panic("debug go crash")
|
||||
})
|
||||
}
|
||||
390
experimental/libbox/internal/oomprofile/builder.go
Normal file
390
experimental/libbox/internal/oomprofile/builder.go
Normal file
@@ -0,0 +1,390 @@
|
||||
//go:build darwin || linux || windows
|
||||
|
||||
package oomprofile
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
tagProfile_SampleType = 1
|
||||
tagProfile_Sample = 2
|
||||
tagProfile_Mapping = 3
|
||||
tagProfile_Location = 4
|
||||
tagProfile_Function = 5
|
||||
tagProfile_StringTable = 6
|
||||
tagProfile_TimeNanos = 9
|
||||
tagProfile_PeriodType = 11
|
||||
tagProfile_Period = 12
|
||||
tagProfile_DefaultSampleType = 14
|
||||
|
||||
tagValueType_Type = 1
|
||||
tagValueType_Unit = 2
|
||||
|
||||
tagSample_Location = 1
|
||||
tagSample_Value = 2
|
||||
tagSample_Label = 3
|
||||
|
||||
tagLabel_Key = 1
|
||||
tagLabel_Str = 2
|
||||
tagLabel_Num = 3
|
||||
|
||||
tagMapping_ID = 1
|
||||
tagMapping_Start = 2
|
||||
tagMapping_Limit = 3
|
||||
tagMapping_Offset = 4
|
||||
tagMapping_Filename = 5
|
||||
tagMapping_BuildID = 6
|
||||
tagMapping_HasFunctions = 7
|
||||
tagMapping_HasFilenames = 8
|
||||
tagMapping_HasLineNumbers = 9
|
||||
tagMapping_HasInlineFrames = 10
|
||||
|
||||
tagLocation_ID = 1
|
||||
tagLocation_MappingID = 2
|
||||
tagLocation_Address = 3
|
||||
tagLocation_Line = 4
|
||||
|
||||
tagLine_FunctionID = 1
|
||||
tagLine_Line = 2
|
||||
|
||||
tagFunction_ID = 1
|
||||
tagFunction_Name = 2
|
||||
tagFunction_SystemName = 3
|
||||
tagFunction_Filename = 4
|
||||
tagFunction_StartLine = 5
|
||||
)
|
||||
|
||||
type memMap struct {
|
||||
start uintptr
|
||||
end uintptr
|
||||
offset uint64
|
||||
file string
|
||||
buildID string
|
||||
funcs symbolizeFlag
|
||||
fake bool
|
||||
}
|
||||
|
||||
type symbolizeFlag uint8
|
||||
|
||||
const (
|
||||
lookupTried symbolizeFlag = 1 << iota
|
||||
lookupFailed
|
||||
)
|
||||
|
||||
func newProfileBuilder(w io.Writer) *profileBuilder {
|
||||
builder := &profileBuilder{
|
||||
start: time.Now(),
|
||||
w: w,
|
||||
strings: []string{""},
|
||||
stringMap: map[string]int{"": 0},
|
||||
locs: map[uintptr]locInfo{},
|
||||
funcs: map[string]int{},
|
||||
}
|
||||
builder.readMapping()
|
||||
return builder
|
||||
}
|
||||
|
||||
func (b *profileBuilder) stringIndex(s string) int64 {
|
||||
id, ok := b.stringMap[s]
|
||||
if !ok {
|
||||
id = len(b.strings)
|
||||
b.strings = append(b.strings, s)
|
||||
b.stringMap[s] = id
|
||||
}
|
||||
return int64(id)
|
||||
}
|
||||
|
||||
func (b *profileBuilder) flush() {
|
||||
const dataFlush = 4096
|
||||
if b.err != nil || b.pb.nest != 0 || len(b.pb.data) <= dataFlush {
|
||||
return
|
||||
}
|
||||
|
||||
_, b.err = b.w.Write(b.pb.data)
|
||||
b.pb.data = b.pb.data[:0]
|
||||
}
|
||||
|
||||
func (b *profileBuilder) pbValueType(tag int, typ string, unit string) {
|
||||
start := b.pb.startMessage()
|
||||
b.pb.int64(tagValueType_Type, b.stringIndex(typ))
|
||||
b.pb.int64(tagValueType_Unit, b.stringIndex(unit))
|
||||
b.pb.endMessage(tag, start)
|
||||
}
|
||||
|
||||
func (b *profileBuilder) pbSample(values []int64, locs []uint64, labels func()) {
|
||||
start := b.pb.startMessage()
|
||||
b.pb.int64s(tagSample_Value, values)
|
||||
b.pb.uint64s(tagSample_Location, locs)
|
||||
if labels != nil {
|
||||
labels()
|
||||
}
|
||||
b.pb.endMessage(tagProfile_Sample, start)
|
||||
b.flush()
|
||||
}
|
||||
|
||||
func (b *profileBuilder) pbLabel(tag int, key string, str string, num int64) {
|
||||
start := b.pb.startMessage()
|
||||
b.pb.int64Opt(tagLabel_Key, b.stringIndex(key))
|
||||
b.pb.int64Opt(tagLabel_Str, b.stringIndex(str))
|
||||
b.pb.int64Opt(tagLabel_Num, num)
|
||||
b.pb.endMessage(tag, start)
|
||||
}
|
||||
|
||||
func (b *profileBuilder) pbLine(tag int, funcID uint64, line int64) {
|
||||
start := b.pb.startMessage()
|
||||
b.pb.uint64Opt(tagLine_FunctionID, funcID)
|
||||
b.pb.int64Opt(tagLine_Line, line)
|
||||
b.pb.endMessage(tag, start)
|
||||
}
|
||||
|
||||
func (b *profileBuilder) pbMapping(tag int, id uint64, base uint64, limit uint64, offset uint64, file string, buildID string, hasFuncs bool) {
|
||||
start := b.pb.startMessage()
|
||||
b.pb.uint64Opt(tagMapping_ID, id)
|
||||
b.pb.uint64Opt(tagMapping_Start, base)
|
||||
b.pb.uint64Opt(tagMapping_Limit, limit)
|
||||
b.pb.uint64Opt(tagMapping_Offset, offset)
|
||||
b.pb.int64Opt(tagMapping_Filename, b.stringIndex(file))
|
||||
b.pb.int64Opt(tagMapping_BuildID, b.stringIndex(buildID))
|
||||
if hasFuncs {
|
||||
b.pb.bool(tagMapping_HasFunctions, true)
|
||||
}
|
||||
b.pb.endMessage(tag, start)
|
||||
}
|
||||
|
||||
func (b *profileBuilder) build() error {
|
||||
if b.err != nil {
|
||||
return b.err
|
||||
}
|
||||
|
||||
b.pb.int64Opt(tagProfile_TimeNanos, b.start.UnixNano())
|
||||
for i, mapping := range b.mem {
|
||||
hasFunctions := mapping.funcs == lookupTried
|
||||
b.pbMapping(tagProfile_Mapping, uint64(i+1), uint64(mapping.start), uint64(mapping.end), mapping.offset, mapping.file, mapping.buildID, hasFunctions)
|
||||
}
|
||||
b.pb.strings(tagProfile_StringTable, b.strings)
|
||||
if b.err != nil {
|
||||
return b.err
|
||||
}
|
||||
_, err := b.w.Write(b.pb.data)
|
||||
return err
|
||||
}
|
||||
|
||||
func allFrames(addr uintptr) ([]runtime.Frame, symbolizeFlag) {
|
||||
frames := runtime.CallersFrames([]uintptr{addr})
|
||||
frame, more := frames.Next()
|
||||
if frame.Function == "runtime.goexit" {
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
result := lookupTried
|
||||
if frame.PC == 0 || frame.Function == "" || frame.File == "" || frame.Line == 0 {
|
||||
result |= lookupFailed
|
||||
}
|
||||
if frame.PC == 0 {
|
||||
frame.PC = addr - 1
|
||||
}
|
||||
|
||||
ret := []runtime.Frame{frame}
|
||||
for frame.Function != "runtime.goexit" && more {
|
||||
frame, more = frames.Next()
|
||||
ret = append(ret, frame)
|
||||
}
|
||||
return ret, result
|
||||
}
|
||||
|
||||
type locInfo struct {
|
||||
id uint64
|
||||
|
||||
pcs []uintptr
|
||||
|
||||
firstPCFrames []runtime.Frame
|
||||
firstPCSymbolizeResult symbolizeFlag
|
||||
}
|
||||
|
||||
func (b *profileBuilder) appendLocsForStack(locs []uint64, stk []uintptr) []uint64 {
|
||||
b.deck.reset()
|
||||
origStk := stk
|
||||
stk = runtimeExpandFinalInlineFrame(stk)
|
||||
|
||||
for len(stk) > 0 {
|
||||
addr := stk[0]
|
||||
if loc, ok := b.locs[addr]; ok {
|
||||
if len(b.deck.pcs) > 0 {
|
||||
if b.deck.tryAdd(addr, loc.firstPCFrames, loc.firstPCSymbolizeResult) {
|
||||
stk = stk[1:]
|
||||
continue
|
||||
}
|
||||
}
|
||||
if id := b.emitLocation(); id > 0 {
|
||||
locs = append(locs, id)
|
||||
}
|
||||
locs = append(locs, loc.id)
|
||||
if len(loc.pcs) > len(stk) {
|
||||
panic(fmt.Sprintf("stack too short to match cached location; stk = %#x, loc.pcs = %#x, original stk = %#x", stk, loc.pcs, origStk))
|
||||
}
|
||||
stk = stk[len(loc.pcs):]
|
||||
continue
|
||||
}
|
||||
|
||||
frames, symbolizeResult := allFrames(addr)
|
||||
if len(frames) == 0 {
|
||||
if id := b.emitLocation(); id > 0 {
|
||||
locs = append(locs, id)
|
||||
}
|
||||
stk = stk[1:]
|
||||
continue
|
||||
}
|
||||
|
||||
if b.deck.tryAdd(addr, frames, symbolizeResult) {
|
||||
stk = stk[1:]
|
||||
continue
|
||||
}
|
||||
if id := b.emitLocation(); id > 0 {
|
||||
locs = append(locs, id)
|
||||
}
|
||||
|
||||
if loc, ok := b.locs[addr]; ok {
|
||||
locs = append(locs, loc.id)
|
||||
stk = stk[len(loc.pcs):]
|
||||
} else {
|
||||
b.deck.tryAdd(addr, frames, symbolizeResult)
|
||||
stk = stk[1:]
|
||||
}
|
||||
}
|
||||
if id := b.emitLocation(); id > 0 {
|
||||
locs = append(locs, id)
|
||||
}
|
||||
return locs
|
||||
}
|
||||
|
||||
type pcDeck struct {
|
||||
pcs []uintptr
|
||||
frames []runtime.Frame
|
||||
symbolizeResult symbolizeFlag
|
||||
|
||||
firstPCFrames int
|
||||
firstPCSymbolizeResult symbolizeFlag
|
||||
}
|
||||
|
||||
func (d *pcDeck) reset() {
|
||||
d.pcs = d.pcs[:0]
|
||||
d.frames = d.frames[:0]
|
||||
d.symbolizeResult = 0
|
||||
d.firstPCFrames = 0
|
||||
d.firstPCSymbolizeResult = 0
|
||||
}
|
||||
|
||||
func (d *pcDeck) tryAdd(pc uintptr, frames []runtime.Frame, symbolizeResult symbolizeFlag) bool {
|
||||
if existing := len(d.frames); existing > 0 {
|
||||
newFrame := frames[0]
|
||||
last := d.frames[existing-1]
|
||||
if last.Func != nil {
|
||||
return false
|
||||
}
|
||||
if last.Entry == 0 || newFrame.Entry == 0 {
|
||||
return false
|
||||
}
|
||||
if last.Entry != newFrame.Entry {
|
||||
return false
|
||||
}
|
||||
if runtimeFrameSymbolName(&last) == runtimeFrameSymbolName(&newFrame) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
d.pcs = append(d.pcs, pc)
|
||||
d.frames = append(d.frames, frames...)
|
||||
d.symbolizeResult |= symbolizeResult
|
||||
if len(d.pcs) == 1 {
|
||||
d.firstPCFrames = len(d.frames)
|
||||
d.firstPCSymbolizeResult = symbolizeResult
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (b *profileBuilder) emitLocation() uint64 {
|
||||
if len(b.deck.pcs) == 0 {
|
||||
return 0
|
||||
}
|
||||
defer b.deck.reset()
|
||||
|
||||
addr := b.deck.pcs[0]
|
||||
firstFrame := b.deck.frames[0]
|
||||
|
||||
type newFunc struct {
|
||||
id uint64
|
||||
name string
|
||||
file string
|
||||
startLine int64
|
||||
}
|
||||
|
||||
newFuncs := make([]newFunc, 0, 8)
|
||||
id := uint64(len(b.locs)) + 1
|
||||
b.locs[addr] = locInfo{
|
||||
id: id,
|
||||
pcs: append([]uintptr{}, b.deck.pcs...),
|
||||
firstPCFrames: append([]runtime.Frame{}, b.deck.frames[:b.deck.firstPCFrames]...),
|
||||
firstPCSymbolizeResult: b.deck.firstPCSymbolizeResult,
|
||||
}
|
||||
|
||||
start := b.pb.startMessage()
|
||||
b.pb.uint64Opt(tagLocation_ID, id)
|
||||
b.pb.uint64Opt(tagLocation_Address, uint64(firstFrame.PC))
|
||||
for _, frame := range b.deck.frames {
|
||||
funcName := runtimeFrameSymbolName(&frame)
|
||||
funcID := uint64(b.funcs[funcName])
|
||||
if funcID == 0 {
|
||||
funcID = uint64(len(b.funcs)) + 1
|
||||
b.funcs[funcName] = int(funcID)
|
||||
newFuncs = append(newFuncs, newFunc{
|
||||
id: funcID,
|
||||
name: funcName,
|
||||
file: frame.File,
|
||||
startLine: int64(runtimeFrameStartLine(&frame)),
|
||||
})
|
||||
}
|
||||
b.pbLine(tagLocation_Line, funcID, int64(frame.Line))
|
||||
}
|
||||
for i := range b.mem {
|
||||
if (b.mem[i].start <= addr && addr < b.mem[i].end) || b.mem[i].fake {
|
||||
b.pb.uint64Opt(tagLocation_MappingID, uint64(i+1))
|
||||
mapping := b.mem[i]
|
||||
mapping.funcs |= b.deck.symbolizeResult
|
||||
b.mem[i] = mapping
|
||||
break
|
||||
}
|
||||
}
|
||||
b.pb.endMessage(tagProfile_Location, start)
|
||||
|
||||
for _, fn := range newFuncs {
|
||||
start := b.pb.startMessage()
|
||||
b.pb.uint64Opt(tagFunction_ID, fn.id)
|
||||
b.pb.int64Opt(tagFunction_Name, b.stringIndex(fn.name))
|
||||
b.pb.int64Opt(tagFunction_SystemName, b.stringIndex(fn.name))
|
||||
b.pb.int64Opt(tagFunction_Filename, b.stringIndex(fn.file))
|
||||
b.pb.int64Opt(tagFunction_StartLine, fn.startLine)
|
||||
b.pb.endMessage(tagProfile_Function, start)
|
||||
}
|
||||
|
||||
b.flush()
|
||||
return id
|
||||
}
|
||||
|
||||
func (b *profileBuilder) addMapping(lo uint64, hi uint64, offset uint64, file string, buildID string) {
|
||||
b.addMappingEntry(lo, hi, offset, file, buildID, false)
|
||||
}
|
||||
|
||||
func (b *profileBuilder) addMappingEntry(lo uint64, hi uint64, offset uint64, file string, buildID string, fake bool) {
|
||||
b.mem = append(b.mem, memMap{
|
||||
start: uintptr(lo),
|
||||
end: uintptr(hi),
|
||||
offset: offset,
|
||||
file: file,
|
||||
buildID: buildID,
|
||||
fake: fake,
|
||||
})
|
||||
}
|
||||
24
experimental/libbox/internal/oomprofile/defs_darwin_amd64.go
Normal file
24
experimental/libbox/internal/oomprofile/defs_darwin_amd64.go
Normal file
@@ -0,0 +1,24 @@
|
||||
//go:build darwin && amd64
|
||||
|
||||
package oomprofile
|
||||
|
||||
type machVMRegionBasicInfoData struct {
|
||||
Protection int32
|
||||
MaxProtection int32
|
||||
Inheritance uint32
|
||||
Shared uint32
|
||||
Reserved uint32
|
||||
Offset [8]byte
|
||||
Behavior int32
|
||||
UserWiredCount uint16
|
||||
PadCgo1 [2]byte
|
||||
}
|
||||
|
||||
const (
|
||||
_VM_PROT_READ = 0x1
|
||||
_VM_PROT_EXECUTE = 0x4
|
||||
|
||||
_MACH_SEND_INVALID_DEST = 0x10000003
|
||||
|
||||
_MAXPATHLEN = 0x400
|
||||
)
|
||||
24
experimental/libbox/internal/oomprofile/defs_darwin_arm64.go
Normal file
24
experimental/libbox/internal/oomprofile/defs_darwin_arm64.go
Normal file
@@ -0,0 +1,24 @@
|
||||
//go:build darwin && arm64
|
||||
|
||||
package oomprofile
|
||||
|
||||
type machVMRegionBasicInfoData struct {
|
||||
Protection int32
|
||||
MaxProtection int32
|
||||
Inheritance uint32
|
||||
Shared int32
|
||||
Reserved int32
|
||||
Offset [8]byte
|
||||
Behavior int32
|
||||
UserWiredCount uint16
|
||||
PadCgo1 [2]byte
|
||||
}
|
||||
|
||||
const (
|
||||
_VM_PROT_READ = 0x1
|
||||
_VM_PROT_EXECUTE = 0x4
|
||||
|
||||
_MACH_SEND_INVALID_DEST = 0x10000003
|
||||
|
||||
_MAXPATHLEN = 0x400
|
||||
)
|
||||
47
experimental/libbox/internal/oomprofile/linkname.go
Normal file
47
experimental/libbox/internal/oomprofile/linkname.go
Normal file
@@ -0,0 +1,47 @@
|
||||
//go:build darwin || linux || windows
|
||||
|
||||
package oomprofile
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
_ "runtime/pprof"
|
||||
"unsafe"
|
||||
|
||||
_ "unsafe"
|
||||
)
|
||||
|
||||
//go:linkname runtimeMemProfileInternal runtime.pprof_memProfileInternal
|
||||
func runtimeMemProfileInternal(p []memProfileRecord, inuseZero bool) (n int, ok bool)
|
||||
|
||||
//go:linkname runtimeBlockProfileInternal runtime.pprof_blockProfileInternal
|
||||
func runtimeBlockProfileInternal(p []blockProfileRecord) (n int, ok bool)
|
||||
|
||||
//go:linkname runtimeMutexProfileInternal runtime.pprof_mutexProfileInternal
|
||||
func runtimeMutexProfileInternal(p []blockProfileRecord) (n int, ok bool)
|
||||
|
||||
//go:linkname runtimeThreadCreateInternal runtime.pprof_threadCreateInternal
|
||||
func runtimeThreadCreateInternal(p []stackRecord) (n int, ok bool)
|
||||
|
||||
//go:linkname runtimeGoroutineProfileWithLabels runtime.pprof_goroutineProfileWithLabels
|
||||
func runtimeGoroutineProfileWithLabels(p []stackRecord, labels []unsafe.Pointer) (n int, ok bool)
|
||||
|
||||
//go:linkname runtimeCyclesPerSecond runtime/pprof.runtime_cyclesPerSecond
|
||||
func runtimeCyclesPerSecond() int64
|
||||
|
||||
//go:linkname runtimeMakeProfStack runtime.pprof_makeProfStack
|
||||
func runtimeMakeProfStack() []uintptr
|
||||
|
||||
//go:linkname runtimeFrameStartLine runtime/pprof.runtime_FrameStartLine
|
||||
func runtimeFrameStartLine(f *runtime.Frame) int
|
||||
|
||||
//go:linkname runtimeFrameSymbolName runtime/pprof.runtime_FrameSymbolName
|
||||
func runtimeFrameSymbolName(f *runtime.Frame) string
|
||||
|
||||
//go:linkname runtimeExpandFinalInlineFrame runtime/pprof.runtime_expandFinalInlineFrame
|
||||
func runtimeExpandFinalInlineFrame(stk []uintptr) []uintptr
|
||||
|
||||
//go:linkname stdParseProcSelfMaps runtime/pprof.parseProcSelfMaps
|
||||
func stdParseProcSelfMaps(data []byte, addMapping func(lo uint64, hi uint64, offset uint64, file string, buildID string))
|
||||
|
||||
//go:linkname stdELFBuildID runtime/pprof.elfBuildID
|
||||
func stdELFBuildID(file string) (string, error)
|
||||
57
experimental/libbox/internal/oomprofile/mapping_darwin.go
Normal file
57
experimental/libbox/internal/oomprofile/mapping_darwin.go
Normal file
@@ -0,0 +1,57 @@
|
||||
//go:build darwin
|
||||
|
||||
package oomprofile
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"os"
|
||||
"unsafe"
|
||||
|
||||
_ "unsafe"
|
||||
)
|
||||
|
||||
func isExecutable(protection int32) bool {
|
||||
return (protection&_VM_PROT_EXECUTE) != 0 && (protection&_VM_PROT_READ) != 0
|
||||
}
|
||||
|
||||
func (b *profileBuilder) readMapping() {
|
||||
if !machVMInfo(b.addMapping) {
|
||||
b.addMappingEntry(0, 0, 0, "", "", true)
|
||||
}
|
||||
}
|
||||
|
||||
func machVMInfo(addMapping func(lo uint64, hi uint64, off uint64, file string, buildID string)) bool {
|
||||
added := false
|
||||
addr := uint64(0x1)
|
||||
for {
|
||||
var regionSize uint64
|
||||
var info machVMRegionBasicInfoData
|
||||
kr := machVMRegion(&addr, ®ionSize, unsafe.Pointer(&info))
|
||||
if kr != 0 {
|
||||
if kr == _MACH_SEND_INVALID_DEST {
|
||||
return true
|
||||
}
|
||||
return added
|
||||
}
|
||||
if isExecutable(info.Protection) {
|
||||
addMapping(addr, addr+regionSize, binary.LittleEndian.Uint64(info.Offset[:]), regionFilename(addr), "")
|
||||
added = true
|
||||
}
|
||||
addr += regionSize
|
||||
}
|
||||
}
|
||||
|
||||
func regionFilename(address uint64) string {
|
||||
buf := make([]byte, _MAXPATHLEN)
|
||||
n := procRegionFilename(os.Getpid(), address, unsafe.SliceData(buf), int64(cap(buf)))
|
||||
if n == 0 {
|
||||
return ""
|
||||
}
|
||||
return string(buf[:n])
|
||||
}
|
||||
|
||||
//go:linkname machVMRegion runtime/pprof.mach_vm_region
|
||||
func machVMRegion(address *uint64, regionSize *uint64, info unsafe.Pointer) int32
|
||||
|
||||
//go:linkname procRegionFilename runtime/pprof.proc_regionfilename
|
||||
func procRegionFilename(pid int, address uint64, buf *byte, buflen int64) int32
|
||||
13
experimental/libbox/internal/oomprofile/mapping_linux.go
Normal file
13
experimental/libbox/internal/oomprofile/mapping_linux.go
Normal file
@@ -0,0 +1,13 @@
|
||||
//go:build linux
|
||||
|
||||
package oomprofile
|
||||
|
||||
import "os"
|
||||
|
||||
func (b *profileBuilder) readMapping() {
|
||||
data, _ := os.ReadFile("/proc/self/maps")
|
||||
stdParseProcSelfMaps(data, b.addMapping)
|
||||
if len(b.mem) == 0 {
|
||||
b.addMappingEntry(0, 0, 0, "", "", true)
|
||||
}
|
||||
}
|
||||
58
experimental/libbox/internal/oomprofile/mapping_windows.go
Normal file
58
experimental/libbox/internal/oomprofile/mapping_windows.go
Normal file
@@ -0,0 +1,58 @@
|
||||
//go:build windows
|
||||
|
||||
package oomprofile
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func (b *profileBuilder) readMapping() {
|
||||
snapshot, err := createModuleSnapshot()
|
||||
if err != nil {
|
||||
b.addMappingEntry(0, 0, 0, "", "", true)
|
||||
return
|
||||
}
|
||||
defer windows.CloseHandle(snapshot)
|
||||
|
||||
var module windows.ModuleEntry32
|
||||
module.Size = uint32(windows.SizeofModuleEntry32)
|
||||
err = windows.Module32First(snapshot, &module)
|
||||
if err != nil {
|
||||
b.addMappingEntry(0, 0, 0, "", "", true)
|
||||
return
|
||||
}
|
||||
for err == nil {
|
||||
exe := windows.UTF16ToString(module.ExePath[:])
|
||||
b.addMappingEntry(
|
||||
uint64(module.ModBaseAddr),
|
||||
uint64(module.ModBaseAddr)+uint64(module.ModBaseSize),
|
||||
0,
|
||||
exe,
|
||||
peBuildID(exe),
|
||||
false,
|
||||
)
|
||||
err = windows.Module32Next(snapshot, &module)
|
||||
}
|
||||
}
|
||||
|
||||
func createModuleSnapshot() (windows.Handle, error) {
|
||||
for {
|
||||
snapshot, err := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE|windows.TH32CS_SNAPMODULE32, uint32(windows.GetCurrentProcessId()))
|
||||
var errno windows.Errno
|
||||
if err != nil && errors.As(err, &errno) && errno == windows.ERROR_BAD_LENGTH {
|
||||
continue
|
||||
}
|
||||
return snapshot, err
|
||||
}
|
||||
}
|
||||
|
||||
func peBuildID(file string) string {
|
||||
info, err := os.Stat(file)
|
||||
if err != nil {
|
||||
return file
|
||||
}
|
||||
return file + info.ModTime().String()
|
||||
}
|
||||
380
experimental/libbox/internal/oomprofile/oomprofile.go
Normal file
380
experimental/libbox/internal/oomprofile/oomprofile.go
Normal file
@@ -0,0 +1,380 @@
|
||||
//go:build darwin || linux || windows
|
||||
|
||||
package oomprofile
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type stackRecord struct {
|
||||
Stack []uintptr
|
||||
}
|
||||
|
||||
type memProfileRecord struct {
|
||||
AllocBytes, FreeBytes int64
|
||||
AllocObjects, FreeObjects int64
|
||||
Stack []uintptr
|
||||
}
|
||||
|
||||
func (r *memProfileRecord) InUseBytes() int64 {
|
||||
return r.AllocBytes - r.FreeBytes
|
||||
}
|
||||
|
||||
func (r *memProfileRecord) InUseObjects() int64 {
|
||||
return r.AllocObjects - r.FreeObjects
|
||||
}
|
||||
|
||||
type blockProfileRecord struct {
|
||||
Count int64
|
||||
Cycles int64
|
||||
Stack []uintptr
|
||||
}
|
||||
|
||||
type label struct {
|
||||
key string
|
||||
value string
|
||||
}
|
||||
|
||||
type labelSet struct {
|
||||
list []label
|
||||
}
|
||||
|
||||
type labelMap struct {
|
||||
labelSet
|
||||
}
|
||||
|
||||
func WriteFile(destPath string, name string) (string, error) {
|
||||
writer, ok := profileWriters[name]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("unsupported profile %q", name)
|
||||
}
|
||||
|
||||
filePath := filepath.Join(destPath, name+".pb")
|
||||
file, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if err := writer(file); err != nil {
|
||||
_ = os.Remove(filePath)
|
||||
return "", err
|
||||
}
|
||||
if err := file.Close(); err != nil {
|
||||
_ = os.Remove(filePath)
|
||||
return "", err
|
||||
}
|
||||
return filePath, nil
|
||||
}
|
||||
|
||||
var profileWriters = map[string]func(io.Writer) error{
|
||||
"allocs": writeAlloc,
|
||||
"block": writeBlock,
|
||||
"goroutine": writeGoroutine,
|
||||
"heap": writeHeap,
|
||||
"mutex": writeMutex,
|
||||
"threadcreate": writeThreadCreate,
|
||||
}
|
||||
|
||||
func writeHeap(w io.Writer) error {
|
||||
return writeHeapInternal(w, "")
|
||||
}
|
||||
|
||||
func writeAlloc(w io.Writer) error {
|
||||
return writeHeapInternal(w, "alloc_space")
|
||||
}
|
||||
|
||||
func writeHeapInternal(w io.Writer, defaultSampleType string) error {
|
||||
var profile []memProfileRecord
|
||||
n, ok := runtimeMemProfileInternal(nil, true)
|
||||
for {
|
||||
profile = make([]memProfileRecord, n+50)
|
||||
n, ok = runtimeMemProfileInternal(profile, true)
|
||||
if ok {
|
||||
profile = profile[:n]
|
||||
break
|
||||
}
|
||||
}
|
||||
return writeHeapProto(w, profile, int64(runtime.MemProfileRate), defaultSampleType)
|
||||
}
|
||||
|
||||
func writeGoroutine(w io.Writer) error {
|
||||
return writeRuntimeProfile(w, "goroutine", runtimeGoroutineProfileWithLabels)
|
||||
}
|
||||
|
||||
func writeThreadCreate(w io.Writer) error {
|
||||
return writeRuntimeProfile(w, "threadcreate", func(p []stackRecord, _ []unsafe.Pointer) (int, bool) {
|
||||
return runtimeThreadCreateInternal(p)
|
||||
})
|
||||
}
|
||||
|
||||
func writeRuntimeProfile(w io.Writer, name string, fetch func([]stackRecord, []unsafe.Pointer) (int, bool)) error {
|
||||
var profile []stackRecord
|
||||
var labels []unsafe.Pointer
|
||||
|
||||
n, ok := fetch(nil, nil)
|
||||
for {
|
||||
profile = make([]stackRecord, n+10)
|
||||
labels = make([]unsafe.Pointer, n+10)
|
||||
n, ok = fetch(profile, labels)
|
||||
if ok {
|
||||
profile = profile[:n]
|
||||
labels = labels[:n]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return writeCountProfile(w, name, &runtimeProfile{profile, labels})
|
||||
}
|
||||
|
||||
func writeBlock(w io.Writer) error {
|
||||
return writeCycleProfile(w, "contentions", "delay", runtimeBlockProfileInternal)
|
||||
}
|
||||
|
||||
func writeMutex(w io.Writer) error {
|
||||
return writeCycleProfile(w, "contentions", "delay", runtimeMutexProfileInternal)
|
||||
}
|
||||
|
||||
func writeCycleProfile(w io.Writer, countName string, cycleName string, fetch func([]blockProfileRecord) (int, bool)) error {
|
||||
var profile []blockProfileRecord
|
||||
n, ok := fetch(nil)
|
||||
for {
|
||||
profile = make([]blockProfileRecord, n+50)
|
||||
n, ok = fetch(profile)
|
||||
if ok {
|
||||
profile = profile[:n]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(profile, func(i, j int) bool {
|
||||
return profile[i].Cycles > profile[j].Cycles
|
||||
})
|
||||
|
||||
builder := newProfileBuilder(w)
|
||||
builder.pbValueType(tagProfile_PeriodType, countName, "count")
|
||||
builder.pb.int64Opt(tagProfile_Period, 1)
|
||||
builder.pbValueType(tagProfile_SampleType, countName, "count")
|
||||
builder.pbValueType(tagProfile_SampleType, cycleName, "nanoseconds")
|
||||
|
||||
cpuGHz := float64(runtimeCyclesPerSecond()) / 1e9
|
||||
values := []int64{0, 0}
|
||||
var locs []uint64
|
||||
expandedStack := runtimeMakeProfStack()
|
||||
for _, record := range profile {
|
||||
values[0] = record.Count
|
||||
if cpuGHz > 0 {
|
||||
values[1] = int64(float64(record.Cycles) / cpuGHz)
|
||||
} else {
|
||||
values[1] = 0
|
||||
}
|
||||
n := expandInlinedFrames(expandedStack, record.Stack)
|
||||
locs = builder.appendLocsForStack(locs[:0], expandedStack[:n])
|
||||
builder.pbSample(values, locs, nil)
|
||||
}
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
type countProfile interface {
|
||||
Len() int
|
||||
Stack(i int) []uintptr
|
||||
Label(i int) *labelMap
|
||||
}
|
||||
|
||||
type runtimeProfile struct {
|
||||
stk []stackRecord
|
||||
labels []unsafe.Pointer
|
||||
}
|
||||
|
||||
func (p *runtimeProfile) Len() int {
|
||||
return len(p.stk)
|
||||
}
|
||||
|
||||
func (p *runtimeProfile) Stack(i int) []uintptr {
|
||||
return p.stk[i].Stack
|
||||
}
|
||||
|
||||
func (p *runtimeProfile) Label(i int) *labelMap {
|
||||
return (*labelMap)(p.labels[i])
|
||||
}
|
||||
|
||||
func writeCountProfile(w io.Writer, name string, profile countProfile) error {
|
||||
var buf strings.Builder
|
||||
key := func(stk []uintptr, labels *labelMap) string {
|
||||
buf.Reset()
|
||||
buf.WriteByte('@')
|
||||
for _, pc := range stk {
|
||||
fmt.Fprintf(&buf, " %#x", pc)
|
||||
}
|
||||
if labels != nil {
|
||||
buf.WriteString("\n# labels:")
|
||||
for _, label := range labels.list {
|
||||
fmt.Fprintf(&buf, " %q:%q", label.key, label.value)
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
counts := make(map[string]int)
|
||||
index := make(map[string]int)
|
||||
var keys []string
|
||||
for i := 0; i < profile.Len(); i++ {
|
||||
k := key(profile.Stack(i), profile.Label(i))
|
||||
if counts[k] == 0 {
|
||||
index[k] = i
|
||||
keys = append(keys, k)
|
||||
}
|
||||
counts[k]++
|
||||
}
|
||||
|
||||
sort.Sort(&keysByCount{keys: keys, count: counts})
|
||||
|
||||
builder := newProfileBuilder(w)
|
||||
builder.pbValueType(tagProfile_PeriodType, name, "count")
|
||||
builder.pb.int64Opt(tagProfile_Period, 1)
|
||||
builder.pbValueType(tagProfile_SampleType, name, "count")
|
||||
|
||||
values := []int64{0}
|
||||
var locs []uint64
|
||||
for _, k := range keys {
|
||||
values[0] = int64(counts[k])
|
||||
idx := index[k]
|
||||
locs = builder.appendLocsForStack(locs[:0], profile.Stack(idx))
|
||||
|
||||
var labels func()
|
||||
if profile.Label(idx) != nil {
|
||||
labels = func() {
|
||||
for _, label := range profile.Label(idx).list {
|
||||
builder.pbLabel(tagSample_Label, label.key, label.value, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
builder.pbSample(values, locs, labels)
|
||||
}
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
type keysByCount struct {
|
||||
keys []string
|
||||
count map[string]int
|
||||
}
|
||||
|
||||
func (x *keysByCount) Len() int {
|
||||
return len(x.keys)
|
||||
}
|
||||
|
||||
func (x *keysByCount) Swap(i int, j int) {
|
||||
x.keys[i], x.keys[j] = x.keys[j], x.keys[i]
|
||||
}
|
||||
|
||||
func (x *keysByCount) Less(i int, j int) bool {
|
||||
ki, kj := x.keys[i], x.keys[j]
|
||||
ci, cj := x.count[ki], x.count[kj]
|
||||
if ci != cj {
|
||||
return ci > cj
|
||||
}
|
||||
return ki < kj
|
||||
}
|
||||
|
||||
func expandInlinedFrames(dst []uintptr, pcs []uintptr) int {
|
||||
frames := runtime.CallersFrames(pcs)
|
||||
var n int
|
||||
for n < len(dst) {
|
||||
frame, more := frames.Next()
|
||||
dst[n] = frame.PC + 1
|
||||
n++
|
||||
if !more {
|
||||
break
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func writeHeapProto(w io.Writer, profile []memProfileRecord, rate int64, defaultSampleType string) error {
|
||||
builder := newProfileBuilder(w)
|
||||
builder.pbValueType(tagProfile_PeriodType, "space", "bytes")
|
||||
builder.pb.int64Opt(tagProfile_Period, rate)
|
||||
builder.pbValueType(tagProfile_SampleType, "alloc_objects", "count")
|
||||
builder.pbValueType(tagProfile_SampleType, "alloc_space", "bytes")
|
||||
builder.pbValueType(tagProfile_SampleType, "inuse_objects", "count")
|
||||
builder.pbValueType(tagProfile_SampleType, "inuse_space", "bytes")
|
||||
if defaultSampleType != "" {
|
||||
builder.pb.int64Opt(tagProfile_DefaultSampleType, builder.stringIndex(defaultSampleType))
|
||||
}
|
||||
|
||||
values := []int64{0, 0, 0, 0}
|
||||
var locs []uint64
|
||||
for _, record := range profile {
|
||||
hideRuntime := true
|
||||
for tries := 0; tries < 2; tries++ {
|
||||
stk := record.Stack
|
||||
if hideRuntime {
|
||||
for i, addr := range stk {
|
||||
if f := runtime.FuncForPC(addr); f != nil && (strings.HasPrefix(f.Name(), "runtime.") || strings.HasPrefix(f.Name(), "internal/runtime/")) {
|
||||
continue
|
||||
}
|
||||
stk = stk[i:]
|
||||
break
|
||||
}
|
||||
}
|
||||
locs = builder.appendLocsForStack(locs[:0], stk)
|
||||
if len(locs) > 0 {
|
||||
break
|
||||
}
|
||||
hideRuntime = false
|
||||
}
|
||||
|
||||
values[0], values[1] = scaleHeapSample(record.AllocObjects, record.AllocBytes, rate)
|
||||
values[2], values[3] = scaleHeapSample(record.InUseObjects(), record.InUseBytes(), rate)
|
||||
|
||||
var blockSize int64
|
||||
if record.AllocObjects > 0 {
|
||||
blockSize = record.AllocBytes / record.AllocObjects
|
||||
}
|
||||
builder.pbSample(values, locs, func() {
|
||||
if blockSize != 0 {
|
||||
builder.pbLabel(tagSample_Label, "bytes", "", blockSize)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
func scaleHeapSample(count int64, size int64, rate int64) (int64, int64) {
|
||||
if count == 0 || size == 0 {
|
||||
return 0, 0
|
||||
}
|
||||
if rate <= 1 {
|
||||
return count, size
|
||||
}
|
||||
|
||||
avgSize := float64(size) / float64(count)
|
||||
scale := 1 / (1 - math.Exp(-avgSize/float64(rate)))
|
||||
return int64(float64(count) * scale), int64(float64(size) * scale)
|
||||
}
|
||||
|
||||
type profileBuilder struct {
|
||||
start time.Time
|
||||
w io.Writer
|
||||
err error
|
||||
|
||||
pb protobuf
|
||||
strings []string
|
||||
stringMap map[string]int
|
||||
locs map[uintptr]locInfo
|
||||
funcs map[string]int
|
||||
mem []memMap
|
||||
deck pcDeck
|
||||
}
|
||||
120
experimental/libbox/internal/oomprofile/protobuf.go
Normal file
120
experimental/libbox/internal/oomprofile/protobuf.go
Normal file
@@ -0,0 +1,120 @@
|
||||
//go:build darwin || linux || windows
|
||||
|
||||
package oomprofile
|
||||
|
||||
type protobuf struct {
|
||||
data []byte
|
||||
tmp [16]byte
|
||||
nest int
|
||||
}
|
||||
|
||||
func (b *protobuf) varint(x uint64) {
|
||||
for x >= 128 {
|
||||
b.data = append(b.data, byte(x)|0x80)
|
||||
x >>= 7
|
||||
}
|
||||
b.data = append(b.data, byte(x))
|
||||
}
|
||||
|
||||
func (b *protobuf) length(tag int, length int) {
|
||||
b.varint(uint64(tag)<<3 | 2)
|
||||
b.varint(uint64(length))
|
||||
}
|
||||
|
||||
func (b *protobuf) uint64(tag int, x uint64) {
|
||||
b.varint(uint64(tag)<<3 | 0)
|
||||
b.varint(x)
|
||||
}
|
||||
|
||||
func (b *protobuf) uint64s(tag int, x []uint64) {
|
||||
if len(x) > 2 {
|
||||
n1 := len(b.data)
|
||||
for _, u := range x {
|
||||
b.varint(u)
|
||||
}
|
||||
n2 := len(b.data)
|
||||
b.length(tag, n2-n1)
|
||||
n3 := len(b.data)
|
||||
copy(b.tmp[:], b.data[n2:n3])
|
||||
copy(b.data[n1+(n3-n2):], b.data[n1:n2])
|
||||
copy(b.data[n1:], b.tmp[:n3-n2])
|
||||
return
|
||||
}
|
||||
for _, u := range x {
|
||||
b.uint64(tag, u)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *protobuf) uint64Opt(tag int, x uint64) {
|
||||
if x == 0 {
|
||||
return
|
||||
}
|
||||
b.uint64(tag, x)
|
||||
}
|
||||
|
||||
func (b *protobuf) int64(tag int, x int64) {
|
||||
b.uint64(tag, uint64(x))
|
||||
}
|
||||
|
||||
func (b *protobuf) int64Opt(tag int, x int64) {
|
||||
if x == 0 {
|
||||
return
|
||||
}
|
||||
b.int64(tag, x)
|
||||
}
|
||||
|
||||
func (b *protobuf) int64s(tag int, x []int64) {
|
||||
if len(x) > 2 {
|
||||
n1 := len(b.data)
|
||||
for _, u := range x {
|
||||
b.varint(uint64(u))
|
||||
}
|
||||
n2 := len(b.data)
|
||||
b.length(tag, n2-n1)
|
||||
n3 := len(b.data)
|
||||
copy(b.tmp[:], b.data[n2:n3])
|
||||
copy(b.data[n1+(n3-n2):], b.data[n1:n2])
|
||||
copy(b.data[n1:], b.tmp[:n3-n2])
|
||||
return
|
||||
}
|
||||
for _, u := range x {
|
||||
b.int64(tag, u)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *protobuf) bool(tag int, x bool) {
|
||||
if x {
|
||||
b.uint64(tag, 1)
|
||||
} else {
|
||||
b.uint64(tag, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *protobuf) string(tag int, x string) {
|
||||
b.length(tag, len(x))
|
||||
b.data = append(b.data, x...)
|
||||
}
|
||||
|
||||
func (b *protobuf) strings(tag int, x []string) {
|
||||
for _, s := range x {
|
||||
b.string(tag, s)
|
||||
}
|
||||
}
|
||||
|
||||
type msgOffset int
|
||||
|
||||
func (b *protobuf) startMessage() msgOffset {
|
||||
b.nest++
|
||||
return msgOffset(len(b.data))
|
||||
}
|
||||
|
||||
func (b *protobuf) endMessage(tag int, start msgOffset) {
|
||||
n1 := int(start)
|
||||
n2 := len(b.data)
|
||||
b.length(tag, n2-n1)
|
||||
n3 := len(b.data)
|
||||
copy(b.tmp[:], b.data[n2:n3])
|
||||
copy(b.data[n1+(n3-n2):], b.data[n1:n2])
|
||||
copy(b.data[n1:], b.tmp[:n3-n2])
|
||||
b.nest--
|
||||
}
|
||||
@@ -1,24 +1,76 @@
|
||||
//go:build darwin || linux
|
||||
//go:build darwin || linux || windows
|
||||
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
)
|
||||
|
||||
var crashOutputFile *os.File
|
||||
type crashReportMetadata struct {
|
||||
reportMetadata
|
||||
CrashedAt string `json:"crashedAt,omitempty"`
|
||||
SignalName string `json:"signalName,omitempty"`
|
||||
SignalCode string `json:"signalCode,omitempty"`
|
||||
ExceptionName string `json:"exceptionName,omitempty"`
|
||||
ExceptionReason string `json:"exceptionReason,omitempty"`
|
||||
}
|
||||
|
||||
func RedirectStderr(path string) error {
|
||||
if stats, err := os.Stat(path); err == nil && stats.Size() > 0 {
|
||||
_ = os.Rename(path, path+".old")
|
||||
func archiveCrashReport(path string, crashReportsDir string) {
|
||||
content, err := os.ReadFile(path)
|
||||
if err != nil || len(content) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
info, _ := os.Stat(path)
|
||||
crashTime := time.Now().UTC()
|
||||
if info != nil {
|
||||
crashTime = info.ModTime().UTC()
|
||||
}
|
||||
|
||||
initReportDir(crashReportsDir)
|
||||
destPath, err := nextAvailableReportPath(crashReportsDir, crashTime)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
initReportDir(destPath)
|
||||
|
||||
writeReportFile(destPath, "go.log", content)
|
||||
metadata := crashReportMetadata{
|
||||
reportMetadata: baseReportMetadata(),
|
||||
CrashedAt: crashTime.Format(time.RFC3339),
|
||||
}
|
||||
writeReportMetadata(destPath, metadata)
|
||||
os.Remove(path)
|
||||
copyConfigSnapshot(destPath)
|
||||
}
|
||||
|
||||
func configSnapshotPath() string {
|
||||
return filepath.Join(sBasePath, "configuration.json")
|
||||
}
|
||||
|
||||
func saveConfigSnapshot(configContent string) {
|
||||
snapshotPath := configSnapshotPath()
|
||||
os.WriteFile(snapshotPath, []byte(configContent), 0o666)
|
||||
chownReport(snapshotPath)
|
||||
}
|
||||
|
||||
func redirectStderr(path string) error {
|
||||
crashReportsDir := filepath.Join(sWorkingPath, "crash_reports")
|
||||
archiveCrashReport(path, crashReportsDir)
|
||||
archiveCrashReport(path+".old", crashReportsDir)
|
||||
|
||||
outputFile, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if runtime.GOOS != "android" {
|
||||
if runtime.GOOS != "android" && runtime.GOOS != "windows" {
|
||||
err = outputFile.Chown(sUserID, sGroupID)
|
||||
if err != nil {
|
||||
outputFile.Close()
|
||||
@@ -26,12 +78,88 @@ func RedirectStderr(path string) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = debug.SetCrashOutput(outputFile, debug.CrashOptions{})
|
||||
if err != nil {
|
||||
outputFile.Close()
|
||||
os.Remove(outputFile.Name())
|
||||
return err
|
||||
}
|
||||
crashOutputFile = outputFile
|
||||
_ = outputFile.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateZipArchive(sourcePath string, destinationPath string) error {
|
||||
sourceInfo, err := os.Stat(sourcePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !sourceInfo.IsDir() {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
|
||||
destinationFile, err := os.Create(destinationPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = destinationFile.Close()
|
||||
}()
|
||||
|
||||
zipWriter := zip.NewWriter(destinationFile)
|
||||
|
||||
rootName := filepath.Base(sourcePath)
|
||||
err = filepath.WalkDir(sourcePath, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
relativePath, err := filepath.Rel(sourcePath, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if relativePath == "." {
|
||||
return nil
|
||||
}
|
||||
|
||||
archivePath := filepath.ToSlash(filepath.Join(rootName, relativePath))
|
||||
if d.IsDir() {
|
||||
_, err = zipWriter.Create(archivePath + "/")
|
||||
return err
|
||||
}
|
||||
|
||||
fileInfo, err := d.Info()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
header, err := zip.FileInfoHeader(fileInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
header.Name = archivePath
|
||||
header.Method = zip.Deflate
|
||||
|
||||
writer, err := zipWriter.CreateHeader(header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sourceFile, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = io.Copy(writer, sourceFile)
|
||||
closeErr := sourceFile.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return closeErr
|
||||
})
|
||||
if err != nil {
|
||||
_ = zipWriter.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
return zipWriter.Close()
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"math"
|
||||
runtimeDebug "runtime/debug"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
)
|
||||
|
||||
var memoryLimitEnabled bool
|
||||
|
||||
func SetMemoryLimit(enabled bool) {
|
||||
memoryLimitEnabled = enabled
|
||||
const memoryLimitGo = 45 * 1024 * 1024
|
||||
if enabled {
|
||||
runtimeDebug.SetGCPercent(10)
|
||||
if C.IsIos {
|
||||
runtimeDebug.SetMemoryLimit(memoryLimitGo)
|
||||
}
|
||||
} else {
|
||||
runtimeDebug.SetGCPercent(100)
|
||||
if C.IsIos {
|
||||
runtimeDebug.SetMemoryLimit(math.MaxInt64)
|
||||
}
|
||||
}
|
||||
}
|
||||
141
experimental/libbox/oom_report.go
Normal file
141
experimental/libbox/oom_report.go
Normal file
@@ -0,0 +1,141 @@
|
||||
//go:build darwin || linux || windows
|
||||
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/experimental/libbox/internal/oomprofile"
|
||||
"github.com/sagernet/sing-box/service/oomkiller"
|
||||
"github.com/sagernet/sing/common/byteformats"
|
||||
"github.com/sagernet/sing/common/memory"
|
||||
)
|
||||
|
||||
func init() {
|
||||
sOOMReporter = &oomReporter{}
|
||||
}
|
||||
|
||||
var oomReportProfiles = []string{
|
||||
"allocs",
|
||||
"block",
|
||||
"goroutine",
|
||||
"heap",
|
||||
"mutex",
|
||||
"threadcreate",
|
||||
}
|
||||
|
||||
type oomReportMetadata struct {
|
||||
reportMetadata
|
||||
RecordedAt string `json:"recordedAt"`
|
||||
MemoryUsage string `json:"memoryUsage"`
|
||||
AvailableMemory string `json:"availableMemory,omitempty"`
|
||||
// Heap
|
||||
HeapAlloc string `json:"heapAlloc,omitempty"`
|
||||
HeapObjects uint64 `json:"heapObjects,omitempty,string"`
|
||||
HeapInuse string `json:"heapInuse,omitempty"`
|
||||
HeapIdle string `json:"heapIdle,omitempty"`
|
||||
HeapReleased string `json:"heapReleased,omitempty"`
|
||||
HeapSys string `json:"heapSys,omitempty"`
|
||||
// Stack
|
||||
StackInuse string `json:"stackInuse,omitempty"`
|
||||
StackSys string `json:"stackSys,omitempty"`
|
||||
// Runtime metadata
|
||||
MSpanInuse string `json:"mSpanInuse,omitempty"`
|
||||
MSpanSys string `json:"mSpanSys,omitempty"`
|
||||
MCacheSys string `json:"mCacheSys,omitempty"`
|
||||
BuckHashSys string `json:"buckHashSys,omitempty"`
|
||||
GCSys string `json:"gcSys,omitempty"`
|
||||
OtherSys string `json:"otherSys,omitempty"`
|
||||
Sys string `json:"sys,omitempty"`
|
||||
// GC & runtime
|
||||
TotalAlloc string `json:"totalAlloc,omitempty"`
|
||||
NumGC uint32 `json:"numGC,omitempty,string"`
|
||||
NumGoroutine int `json:"numGoroutine,omitempty,string"`
|
||||
NextGC string `json:"nextGC,omitempty"`
|
||||
LastGC string `json:"lastGC,omitempty"`
|
||||
}
|
||||
|
||||
type oomReporter struct{}
|
||||
|
||||
var _ oomkiller.OOMReporter = (*oomReporter)(nil)
|
||||
|
||||
func (r *oomReporter) WriteReport(memoryUsage uint64) error {
|
||||
now := time.Now().UTC()
|
||||
reportsDir := filepath.Join(sWorkingPath, "oom_reports")
|
||||
err := os.MkdirAll(reportsDir, 0o777)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chownReport(reportsDir)
|
||||
|
||||
destPath, err := nextAvailableReportPath(reportsDir, now)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.MkdirAll(destPath, 0o777)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chownReport(destPath)
|
||||
|
||||
for _, name := range oomReportProfiles {
|
||||
writeOOMProfile(destPath, name)
|
||||
}
|
||||
|
||||
writeReportFile(destPath, "cmdline", []byte(strings.Join(os.Args, "\000")))
|
||||
|
||||
var memStats runtime.MemStats
|
||||
runtime.ReadMemStats(&memStats)
|
||||
|
||||
metadata := oomReportMetadata{
|
||||
reportMetadata: baseReportMetadata(),
|
||||
RecordedAt: now.Format(time.RFC3339),
|
||||
MemoryUsage: byteformats.FormatMemoryBytes(memoryUsage),
|
||||
// Heap
|
||||
HeapAlloc: byteformats.FormatMemoryBytes(memStats.HeapAlloc),
|
||||
HeapObjects: memStats.HeapObjects,
|
||||
HeapInuse: byteformats.FormatMemoryBytes(memStats.HeapInuse),
|
||||
HeapIdle: byteformats.FormatMemoryBytes(memStats.HeapIdle),
|
||||
HeapReleased: byteformats.FormatMemoryBytes(memStats.HeapReleased),
|
||||
HeapSys: byteformats.FormatMemoryBytes(memStats.HeapSys),
|
||||
// Stack
|
||||
StackInuse: byteformats.FormatMemoryBytes(memStats.StackInuse),
|
||||
StackSys: byteformats.FormatMemoryBytes(memStats.StackSys),
|
||||
// Runtime metadata
|
||||
MSpanInuse: byteformats.FormatMemoryBytes(memStats.MSpanInuse),
|
||||
MSpanSys: byteformats.FormatMemoryBytes(memStats.MSpanSys),
|
||||
MCacheSys: byteformats.FormatMemoryBytes(memStats.MCacheSys),
|
||||
BuckHashSys: byteformats.FormatMemoryBytes(memStats.BuckHashSys),
|
||||
GCSys: byteformats.FormatMemoryBytes(memStats.GCSys),
|
||||
OtherSys: byteformats.FormatMemoryBytes(memStats.OtherSys),
|
||||
Sys: byteformats.FormatMemoryBytes(memStats.Sys),
|
||||
// GC & runtime
|
||||
TotalAlloc: byteformats.FormatMemoryBytes(memStats.TotalAlloc),
|
||||
NumGC: memStats.NumGC,
|
||||
NumGoroutine: runtime.NumGoroutine(),
|
||||
NextGC: byteformats.FormatMemoryBytes(memStats.NextGC),
|
||||
}
|
||||
if memStats.LastGC > 0 {
|
||||
metadata.LastGC = time.Unix(0, int64(memStats.LastGC)).UTC().Format(time.RFC3339)
|
||||
}
|
||||
availableMemory := memory.Available()
|
||||
if availableMemory > 0 {
|
||||
metadata.AvailableMemory = byteformats.FormatMemoryBytes(availableMemory)
|
||||
}
|
||||
writeReportMetadata(destPath, metadata)
|
||||
copyConfigSnapshot(destPath)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeOOMProfile(destPath string, name string) {
|
||||
filePath, err := oomprofile.WriteFile(destPath, name)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
chownReport(filePath)
|
||||
}
|
||||
97
experimental/libbox/report.go
Normal file
97
experimental/libbox/report.go
Normal file
@@ -0,0 +1,97 @@
|
||||
//go:build darwin || linux || windows
|
||||
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
type reportMetadata struct {
|
||||
Source string `json:"source,omitempty"`
|
||||
BundleIdentifier string `json:"bundleIdentifier,omitempty"`
|
||||
ProcessName string `json:"processName,omitempty"`
|
||||
ProcessPath string `json:"processPath,omitempty"`
|
||||
StartedAt string `json:"startedAt,omitempty"`
|
||||
AppVersion string `json:"appVersion,omitempty"`
|
||||
AppMarketingVersion string `json:"appMarketingVersion,omitempty"`
|
||||
CoreVersion string `json:"coreVersion,omitempty"`
|
||||
GoVersion string `json:"goVersion,omitempty"`
|
||||
}
|
||||
|
||||
func baseReportMetadata() reportMetadata {
|
||||
processPath, _ := os.Executable()
|
||||
processName := filepath.Base(processPath)
|
||||
if processName == "." {
|
||||
processName = ""
|
||||
}
|
||||
return reportMetadata{
|
||||
Source: sCrashReportSource,
|
||||
ProcessName: processName,
|
||||
ProcessPath: processPath,
|
||||
CoreVersion: C.Version,
|
||||
GoVersion: GoVersion(),
|
||||
}
|
||||
}
|
||||
|
||||
func writeReportFile(destPath string, name string, content []byte) {
|
||||
filePath := filepath.Join(destPath, name)
|
||||
os.WriteFile(filePath, content, 0o666)
|
||||
chownReport(filePath)
|
||||
}
|
||||
|
||||
func writeReportMetadata(destPath string, metadata any) {
|
||||
data, err := json.Marshal(metadata)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
writeReportFile(destPath, "metadata.json", data)
|
||||
}
|
||||
|
||||
func copyConfigSnapshot(destPath string) {
|
||||
snapshotPath := configSnapshotPath()
|
||||
content, err := os.ReadFile(snapshotPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(bytes.TrimSpace(content)) == 0 {
|
||||
return
|
||||
}
|
||||
writeReportFile(destPath, "configuration.json", content)
|
||||
}
|
||||
|
||||
func initReportDir(path string) {
|
||||
os.MkdirAll(path, 0o777)
|
||||
chownReport(path)
|
||||
}
|
||||
|
||||
func chownReport(path string) {
|
||||
if runtime.GOOS != "android" && runtime.GOOS != "windows" {
|
||||
os.Chown(path, sUserID, sGroupID)
|
||||
}
|
||||
}
|
||||
|
||||
func nextAvailableReportPath(reportsDir string, timestamp time.Time) (string, error) {
|
||||
destName := timestamp.Format("2006-01-02T15-04-05")
|
||||
destPath := filepath.Join(reportsDir, destName)
|
||||
_, err := os.Stat(destPath)
|
||||
if os.IsNotExist(err) {
|
||||
return destPath, nil
|
||||
}
|
||||
for i := 1; i <= 1000; i++ {
|
||||
suffixedPath := filepath.Join(reportsDir, destName+"-"+strconv.Itoa(i))
|
||||
_, err = os.Stat(suffixedPath)
|
||||
if os.IsNotExist(err) {
|
||||
return suffixedPath, nil
|
||||
}
|
||||
}
|
||||
return "", E.New("no available report path for ", destName)
|
||||
}
|
||||
@@ -1,13 +1,17 @@
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/experimental/locale"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/service/oomkiller"
|
||||
"github.com/sagernet/sing/common/byteformats"
|
||||
)
|
||||
|
||||
@@ -22,6 +26,10 @@ var (
|
||||
sCommandServerSecret string
|
||||
sLogMaxLines int
|
||||
sDebug bool
|
||||
sCrashReportSource string
|
||||
sOOMKillerEnabled bool
|
||||
sOOMKillerDisabled bool
|
||||
sOOMMemoryLimit int64
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -38,9 +46,13 @@ type SetupOptions struct {
|
||||
CommandServerSecret string
|
||||
LogMaxLines int
|
||||
Debug bool
|
||||
CrashReportSource string
|
||||
OomKillerEnabled bool
|
||||
OomKillerDisabled bool
|
||||
OomMemoryLimit int64
|
||||
}
|
||||
|
||||
func Setup(options *SetupOptions) error {
|
||||
func applySetupOptions(options *SetupOptions) {
|
||||
sBasePath = options.BasePath
|
||||
sWorkingPath = options.WorkingPath
|
||||
sTempPath = options.TempPath
|
||||
@@ -56,10 +68,33 @@ func Setup(options *SetupOptions) error {
|
||||
sCommandServerSecret = options.CommandServerSecret
|
||||
sLogMaxLines = options.LogMaxLines
|
||||
sDebug = options.Debug
|
||||
sCrashReportSource = options.CrashReportSource
|
||||
ReloadSetupOptions(options)
|
||||
}
|
||||
|
||||
func ReloadSetupOptions(options *SetupOptions) {
|
||||
sOOMKillerEnabled = options.OomKillerEnabled
|
||||
sOOMKillerDisabled = options.OomKillerDisabled
|
||||
sOOMMemoryLimit = options.OomMemoryLimit
|
||||
if sOOMKillerEnabled {
|
||||
if sOOMMemoryLimit == 0 && C.IsIos {
|
||||
sOOMMemoryLimit = oomkiller.DefaultAppleNetworkExtensionMemoryLimit
|
||||
}
|
||||
if sOOMMemoryLimit > 0 {
|
||||
debug.SetMemoryLimit(sOOMMemoryLimit * 3 / 4)
|
||||
} else {
|
||||
debug.SetMemoryLimit(math.MaxInt64)
|
||||
}
|
||||
} else {
|
||||
debug.SetMemoryLimit(math.MaxInt64)
|
||||
}
|
||||
}
|
||||
|
||||
func Setup(options *SetupOptions) error {
|
||||
applySetupOptions(options)
|
||||
os.MkdirAll(sWorkingPath, 0o777)
|
||||
os.MkdirAll(sTempPath, 0o777)
|
||||
return nil
|
||||
return redirectStderr(filepath.Join(sWorkingPath, "CrashReport-"+sCrashReportSource+".log"))
|
||||
}
|
||||
|
||||
func SetLocale(localeId string) {
|
||||
@@ -70,6 +105,10 @@ func Version() string {
|
||||
return C.Version
|
||||
}
|
||||
|
||||
func GoVersion() string {
|
||||
return runtime.Version() + ", " + runtime.GOOS + "/" + runtime.GOARCH
|
||||
}
|
||||
|
||||
func FormatBytes(length int64) string {
|
||||
return byteformats.FormatKBytes(uint64(length))
|
||||
}
|
||||
|
||||
2
go.mod
2
go.mod
@@ -37,7 +37,7 @@ require (
|
||||
github.com/sagernet/gomobile v0.1.12
|
||||
github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1
|
||||
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4
|
||||
github.com/sagernet/sing v0.8.4
|
||||
github.com/sagernet/sing v0.8.5-0.20260404181712-947827ec3849
|
||||
github.com/sagernet/sing-mux v0.3.4
|
||||
github.com/sagernet/sing-quic v0.6.2-0.20260330152607-bf674c163212
|
||||
github.com/sagernet/sing-shadowsocks v0.2.8
|
||||
|
||||
4
go.sum
4
go.sum
@@ -236,8 +236,8 @@ github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNen
|
||||
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
|
||||
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4 h1:6qvrUW79S+CrPwWz6cMePXohgjHoKxLo3c+MDhNwc3o=
|
||||
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4=
|
||||
github.com/sagernet/sing v0.8.4 h1:Fj+jlY3F8vhcRfz/G/P3Dwcs5wqnmyNPT7u1RVVmjFI=
|
||||
github.com/sagernet/sing v0.8.4/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/sagernet/sing v0.8.5-0.20260404181712-947827ec3849 h1:P8jaGN561IbHBxjlU8IGrFK65n1vDOrHo8FOMgHfn14=
|
||||
github.com/sagernet/sing v0.8.5-0.20260404181712-947827ec3849/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s=
|
||||
github.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk=
|
||||
github.com/sagernet/sing-quic v0.6.2-0.20260330152607-bf674c163212 h1:7mFOUqy+DyOj7qKGd1X54UMXbnbJiiMileK/tn17xYc=
|
||||
|
||||
@@ -10,5 +10,6 @@ type OOMKillerServiceOptions struct {
|
||||
SafetyMargin *byteformats.MemoryBytes `json:"safety_margin,omitempty"`
|
||||
MinInterval badoption.Duration `json:"min_interval,omitempty"`
|
||||
MaxInterval badoption.Duration `json:"max_interval,omitempty"`
|
||||
ChecksBeforeLimit int `json:"checks_before_limit,omitempty"`
|
||||
KillerDisabled bool `json:"-"`
|
||||
MemoryLimitOverride uint64 `json:"-"`
|
||||
}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
package oomkiller
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
func buildTimerConfig(options option.OOMKillerServiceOptions, memoryLimit uint64, useAvailable bool) (timerConfig, error) {
|
||||
safetyMargin := uint64(defaultSafetyMargin)
|
||||
if options.SafetyMargin != nil && options.SafetyMargin.Value() > 0 {
|
||||
safetyMargin = options.SafetyMargin.Value()
|
||||
}
|
||||
|
||||
minInterval := defaultMinInterval
|
||||
if options.MinInterval != 0 {
|
||||
minInterval = time.Duration(options.MinInterval.Build())
|
||||
if minInterval <= 0 {
|
||||
return timerConfig{}, E.New("min_interval must be greater than 0")
|
||||
}
|
||||
}
|
||||
|
||||
maxInterval := defaultMaxInterval
|
||||
if options.MaxInterval != 0 {
|
||||
maxInterval = time.Duration(options.MaxInterval.Build())
|
||||
if maxInterval <= 0 {
|
||||
return timerConfig{}, E.New("max_interval must be greater than 0")
|
||||
}
|
||||
}
|
||||
if maxInterval < minInterval {
|
||||
return timerConfig{}, E.New("max_interval must be greater than or equal to min_interval")
|
||||
}
|
||||
|
||||
checksBeforeLimit := defaultChecksBeforeLimit
|
||||
if options.ChecksBeforeLimit != 0 {
|
||||
checksBeforeLimit = options.ChecksBeforeLimit
|
||||
if checksBeforeLimit <= 0 {
|
||||
return timerConfig{}, E.New("checks_before_limit must be greater than 0")
|
||||
}
|
||||
}
|
||||
|
||||
return timerConfig{
|
||||
memoryLimit: memoryLimit,
|
||||
safetyMargin: safetyMargin,
|
||||
minInterval: minInterval,
|
||||
maxInterval: maxInterval,
|
||||
checksBeforeLimit: checksBeforeLimit,
|
||||
useAvailable: useAvailable,
|
||||
}, nil
|
||||
}
|
||||
46
service/oomkiller/policy.go
Normal file
46
service/oomkiller/policy.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package oomkiller
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common/memory"
|
||||
"github.com/sagernet/sing/service"
|
||||
)
|
||||
|
||||
const DefaultAppleNetworkExtensionMemoryLimit = 50 * 1024 * 1024
|
||||
|
||||
type policyMode uint8
|
||||
|
||||
const (
|
||||
policyModeNone policyMode = iota
|
||||
policyModeMemoryLimit
|
||||
policyModeAvailable
|
||||
policyModeNetworkExtension
|
||||
)
|
||||
|
||||
func (m policyMode) hasTimerMode() bool {
|
||||
return m != policyModeNone
|
||||
}
|
||||
|
||||
func resolvePolicyMode(ctx context.Context, options option.OOMKillerServiceOptions) (uint64, policyMode) {
|
||||
platformInterface := service.FromContext[adapter.PlatformInterface](ctx)
|
||||
if C.IsIos && platformInterface != nil && platformInterface.UnderNetworkExtension() {
|
||||
return DefaultAppleNetworkExtensionMemoryLimit, policyModeNetworkExtension
|
||||
}
|
||||
if options.MemoryLimitOverride > 0 {
|
||||
return options.MemoryLimitOverride, policyModeMemoryLimit
|
||||
}
|
||||
if options.MemoryLimit != nil {
|
||||
memoryLimit := options.MemoryLimit.Value()
|
||||
if memoryLimit > 0 {
|
||||
return memoryLimit, policyModeMemoryLimit
|
||||
}
|
||||
}
|
||||
if memory.AvailableAvailable() {
|
||||
return 0, policyModeAvailable
|
||||
}
|
||||
return 0, policyModeNone
|
||||
}
|
||||
@@ -1,192 +1,83 @@
|
||||
//go:build darwin && cgo
|
||||
|
||||
package oomkiller
|
||||
|
||||
/*
|
||||
#include <dispatch/dispatch.h>
|
||||
|
||||
static dispatch_source_t memoryPressureSource;
|
||||
|
||||
extern void goMemoryPressureCallback(unsigned long status);
|
||||
|
||||
static void startMemoryPressureMonitor() {
|
||||
memoryPressureSource = dispatch_source_create(
|
||||
DISPATCH_SOURCE_TYPE_MEMORYPRESSURE,
|
||||
0,
|
||||
DISPATCH_MEMORYPRESSURE_WARN | DISPATCH_MEMORYPRESSURE_CRITICAL,
|
||||
dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)
|
||||
);
|
||||
dispatch_source_set_event_handler(memoryPressureSource, ^{
|
||||
unsigned long status = dispatch_source_get_data(memoryPressureSource);
|
||||
goMemoryPressureCallback(status);
|
||||
});
|
||||
dispatch_activate(memoryPressureSource);
|
||||
}
|
||||
|
||||
static void stopMemoryPressureMonitor() {
|
||||
if (memoryPressureSource) {
|
||||
dispatch_source_cancel(memoryPressureSource);
|
||||
memoryPressureSource = NULL;
|
||||
}
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"context"
|
||||
runtimeDebug "runtime/debug"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
boxService "github.com/sagernet/sing-box/adapter/service"
|
||||
boxConstant "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common/memory"
|
||||
"github.com/sagernet/sing/service"
|
||||
)
|
||||
|
||||
type OOMReporter interface {
|
||||
WriteReport(memoryUsage uint64) error
|
||||
}
|
||||
|
||||
func RegisterService(registry *boxService.Registry) {
|
||||
boxService.Register[option.OOMKillerServiceOptions](registry, boxConstant.TypeOOMKiller, NewService)
|
||||
}
|
||||
|
||||
var (
|
||||
globalAccess sync.Mutex
|
||||
globalServices []*Service
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
boxService.Adapter
|
||||
ctx context.Context
|
||||
logger log.ContextLogger
|
||||
router adapter.Router
|
||||
memoryLimit uint64
|
||||
hasTimerMode bool
|
||||
useAvailable bool
|
||||
timerConfig timerConfig
|
||||
adaptiveTimer *adaptiveTimer
|
||||
lastReportTime atomic.Int64
|
||||
}
|
||||
|
||||
func NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.OOMKillerServiceOptions) (adapter.Service, error) {
|
||||
s := &Service{
|
||||
Adapter: boxService.NewAdapter(boxConstant.TypeOOMKiller, tag),
|
||||
logger: logger,
|
||||
router: service.FromContext[adapter.Router](ctx),
|
||||
}
|
||||
|
||||
if options.MemoryLimit != nil {
|
||||
s.memoryLimit = options.MemoryLimit.Value()
|
||||
if s.memoryLimit > 0 {
|
||||
s.hasTimerMode = true
|
||||
}
|
||||
}
|
||||
|
||||
config, err := buildTimerConfig(options, s.memoryLimit, s.useAvailable)
|
||||
memoryLimit, mode := resolvePolicyMode(ctx, options)
|
||||
config, err := buildTimerConfig(options, memoryLimit, mode, options.KillerDisabled)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.timerConfig = config
|
||||
|
||||
return s, nil
|
||||
return &Service{
|
||||
Adapter: boxService.NewAdapter(boxConstant.TypeOOMKiller, tag),
|
||||
ctx: ctx,
|
||||
logger: logger,
|
||||
router: service.FromContext[adapter.Router](ctx),
|
||||
timerConfig: config,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Service) Start(stage adapter.StartStage) error {
|
||||
if stage != adapter.StartStateStart {
|
||||
return nil
|
||||
}
|
||||
|
||||
if s.hasTimerMode {
|
||||
s.adaptiveTimer = newAdaptiveTimer(s.logger, s.router, s.timerConfig)
|
||||
if s.memoryLimit > 0 {
|
||||
s.logger.Info("started memory monitor with limit: ", s.memoryLimit/(1024*1024), " MiB")
|
||||
} else {
|
||||
s.logger.Info("started memory monitor with available memory detection")
|
||||
}
|
||||
} else {
|
||||
s.logger.Info("started memory pressure monitor")
|
||||
}
|
||||
|
||||
globalAccess.Lock()
|
||||
isFirst := len(globalServices) == 0
|
||||
globalServices = append(globalServices, s)
|
||||
globalAccess.Unlock()
|
||||
|
||||
if isFirst {
|
||||
C.startMemoryPressureMonitor()
|
||||
}
|
||||
return nil
|
||||
func (s *Service) createTimer() {
|
||||
s.adaptiveTimer = newAdaptiveTimer(s.logger, s.router, s.timerConfig, s.writeOOMReport)
|
||||
}
|
||||
|
||||
func (s *Service) Close() error {
|
||||
func (s *Service) startTimer() {
|
||||
s.createTimer()
|
||||
s.adaptiveTimer.start()
|
||||
}
|
||||
|
||||
func (s *Service) stopTimer() {
|
||||
if s.adaptiveTimer != nil {
|
||||
s.adaptiveTimer.stop()
|
||||
}
|
||||
globalAccess.Lock()
|
||||
for i, svc := range globalServices {
|
||||
if svc == s {
|
||||
globalServices = append(globalServices[:i], globalServices[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
isLast := len(globalServices) == 0
|
||||
globalAccess.Unlock()
|
||||
if isLast {
|
||||
C.stopMemoryPressureMonitor()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//export goMemoryPressureCallback
|
||||
func goMemoryPressureCallback(status C.ulong) {
|
||||
globalAccess.Lock()
|
||||
services := make([]*Service, len(globalServices))
|
||||
copy(services, globalServices)
|
||||
globalAccess.Unlock()
|
||||
if len(services) == 0 {
|
||||
func (s *Service) writeOOMReport(memoryUsage uint64) {
|
||||
now := time.Now().Unix()
|
||||
lastReport := s.lastReportTime.Load()
|
||||
if now-lastReport < 3600 {
|
||||
return
|
||||
}
|
||||
criticalFlag := C.ulong(C.DISPATCH_MEMORYPRESSURE_CRITICAL)
|
||||
warnFlag := C.ulong(C.DISPATCH_MEMORYPRESSURE_WARN)
|
||||
isCritical := status&criticalFlag != 0
|
||||
isWarning := status&warnFlag != 0
|
||||
var level string
|
||||
switch {
|
||||
case isCritical:
|
||||
level = "critical"
|
||||
case isWarning:
|
||||
level = "warning"
|
||||
default:
|
||||
level = "normal"
|
||||
if !s.lastReportTime.CompareAndSwap(lastReport, now) {
|
||||
return
|
||||
}
|
||||
var freeOSMemory bool
|
||||
for _, s := range services {
|
||||
usage := memory.Total()
|
||||
if s.hasTimerMode {
|
||||
if isCritical {
|
||||
s.logger.Warn("memory pressure: ", level, ", usage: ", usage/(1024*1024), " MiB")
|
||||
if s.adaptiveTimer != nil {
|
||||
s.adaptiveTimer.startNow()
|
||||
reporter := service.FromContext[OOMReporter](s.ctx)
|
||||
if reporter == nil {
|
||||
return
|
||||
}
|
||||
} else if isWarning {
|
||||
s.logger.Warn("memory pressure: ", level, ", usage: ", usage/(1024*1024), " MiB")
|
||||
err := reporter.WriteReport(memoryUsage)
|
||||
if err != nil {
|
||||
s.logger.Warn("failed to write OOM report: ", err)
|
||||
} else {
|
||||
s.logger.Debug("memory pressure: ", level, ", usage: ", usage/(1024*1024), " MiB")
|
||||
if s.adaptiveTimer != nil {
|
||||
s.adaptiveTimer.stop()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if isCritical {
|
||||
s.logger.Error("memory pressure: ", level, ", usage: ", usage/(1024*1024), " MiB, resetting network")
|
||||
s.router.ResetNetwork()
|
||||
freeOSMemory = true
|
||||
} else if isWarning {
|
||||
s.logger.Warn("memory pressure: ", level, ", usage: ", usage/(1024*1024), " MiB")
|
||||
} else {
|
||||
s.logger.Debug("memory pressure: ", level, ", usage: ", usage/(1024*1024), " MiB")
|
||||
}
|
||||
}
|
||||
}
|
||||
if freeOSMemory {
|
||||
runtimeDebug.FreeOSMemory()
|
||||
s.logger.Info("OOM report saved")
|
||||
}
|
||||
}
|
||||
|
||||
103
service/oomkiller/service_darwin.go
Normal file
103
service/oomkiller/service_darwin.go
Normal file
@@ -0,0 +1,103 @@
|
||||
//go:build darwin && cgo
|
||||
|
||||
package oomkiller
|
||||
|
||||
/*
|
||||
#include <dispatch/dispatch.h>
|
||||
|
||||
static dispatch_source_t memoryPressureSource;
|
||||
|
||||
extern void goMemoryPressureCallback(unsigned long status);
|
||||
|
||||
static void startMemoryPressureMonitor() {
|
||||
memoryPressureSource = dispatch_source_create(
|
||||
DISPATCH_SOURCE_TYPE_MEMORYPRESSURE,
|
||||
0,
|
||||
DISPATCH_MEMORYPRESSURE_CRITICAL,
|
||||
dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)
|
||||
);
|
||||
dispatch_source_set_event_handler(memoryPressureSource, ^{
|
||||
unsigned long status = dispatch_source_get_data(memoryPressureSource);
|
||||
goMemoryPressureCallback(status);
|
||||
});
|
||||
dispatch_activate(memoryPressureSource);
|
||||
}
|
||||
|
||||
static void stopMemoryPressureMonitor() {
|
||||
if (memoryPressureSource) {
|
||||
dispatch_source_cancel(memoryPressureSource);
|
||||
memoryPressureSource = NULL;
|
||||
}
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing/common/byteformats"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
var (
|
||||
globalAccess sync.Mutex
|
||||
globalServices []*Service
|
||||
)
|
||||
|
||||
func (s *Service) Start(stage adapter.StartStage) error {
|
||||
if stage != adapter.StartStateStart {
|
||||
return nil
|
||||
}
|
||||
if s.timerConfig.policyMode == policyModeNetworkExtension {
|
||||
s.createTimer()
|
||||
globalAccess.Lock()
|
||||
isFirst := len(globalServices) == 0
|
||||
globalServices = append(globalServices, s)
|
||||
globalAccess.Unlock()
|
||||
if isFirst {
|
||||
C.startMemoryPressureMonitor()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if !s.timerConfig.policyMode.hasTimerMode() {
|
||||
return E.New("memory pressure monitoring is not available on this platform without memory_limit")
|
||||
}
|
||||
s.startTimer()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) Close() error {
|
||||
s.stopTimer()
|
||||
if s.timerConfig.policyMode == policyModeNetworkExtension {
|
||||
globalAccess.Lock()
|
||||
for i, svc := range globalServices {
|
||||
if svc == s {
|
||||
globalServices = append(globalServices[:i], globalServices[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
isLast := len(globalServices) == 0
|
||||
globalAccess.Unlock()
|
||||
if isLast {
|
||||
C.stopMemoryPressureMonitor()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//export goMemoryPressureCallback
|
||||
func goMemoryPressureCallback(status C.ulong) {
|
||||
globalAccess.Lock()
|
||||
services := make([]*Service, len(globalServices))
|
||||
copy(services, globalServices)
|
||||
globalAccess.Unlock()
|
||||
if len(services) == 0 {
|
||||
return
|
||||
}
|
||||
sample := readMemorySample(policyModeNetworkExtension)
|
||||
for _, s := range services {
|
||||
s.logger.Warn("memory pressure: critical, usage: ", byteformats.FormatMemoryBytes(sample.usage))
|
||||
s.adaptiveTimer.notifyPressure()
|
||||
}
|
||||
}
|
||||
@@ -3,79 +3,22 @@
|
||||
package oomkiller
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
boxService "github.com/sagernet/sing-box/adapter/service"
|
||||
boxConstant "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/memory"
|
||||
"github.com/sagernet/sing/service"
|
||||
)
|
||||
|
||||
func RegisterService(registry *boxService.Registry) {
|
||||
boxService.Register[option.OOMKillerServiceOptions](registry, boxConstant.TypeOOMKiller, NewService)
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
boxService.Adapter
|
||||
logger log.ContextLogger
|
||||
router adapter.Router
|
||||
adaptiveTimer *adaptiveTimer
|
||||
timerConfig timerConfig
|
||||
hasTimerMode bool
|
||||
useAvailable bool
|
||||
memoryLimit uint64
|
||||
}
|
||||
|
||||
func NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.OOMKillerServiceOptions) (adapter.Service, error) {
|
||||
s := &Service{
|
||||
Adapter: boxService.NewAdapter(boxConstant.TypeOOMKiller, tag),
|
||||
logger: logger,
|
||||
router: service.FromContext[adapter.Router](ctx),
|
||||
}
|
||||
|
||||
if options.MemoryLimit != nil {
|
||||
s.memoryLimit = options.MemoryLimit.Value()
|
||||
}
|
||||
if s.memoryLimit > 0 {
|
||||
s.hasTimerMode = true
|
||||
} else if memory.AvailableSupported() {
|
||||
s.useAvailable = true
|
||||
s.hasTimerMode = true
|
||||
}
|
||||
|
||||
config, err := buildTimerConfig(options, s.memoryLimit, s.useAvailable)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.timerConfig = config
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *Service) Start(stage adapter.StartStage) error {
|
||||
if stage != adapter.StartStateStart {
|
||||
return nil
|
||||
}
|
||||
if !s.hasTimerMode {
|
||||
if !s.timerConfig.policyMode.hasTimerMode() {
|
||||
return E.New("memory pressure monitoring is not available on this platform without memory_limit")
|
||||
}
|
||||
s.adaptiveTimer = newAdaptiveTimer(s.logger, s.router, s.timerConfig)
|
||||
s.adaptiveTimer.start(0)
|
||||
if s.useAvailable {
|
||||
s.logger.Info("started memory monitor with available memory detection")
|
||||
} else {
|
||||
s.logger.Info("started memory monitor with limit: ", s.memoryLimit/(1024*1024), " MiB")
|
||||
}
|
||||
s.startTimer()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) Close() error {
|
||||
if s.adaptiveTimer != nil {
|
||||
s.adaptiveTimer.stop()
|
||||
}
|
||||
s.stopTimer()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,158 +0,0 @@
|
||||
package oomkiller
|
||||
|
||||
import (
|
||||
runtimeDebug "runtime/debug"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing/common/memory"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultChecksBeforeLimit = 4
|
||||
defaultMinInterval = 500 * time.Millisecond
|
||||
defaultMaxInterval = 10 * time.Second
|
||||
defaultSafetyMargin = 5 * 1024 * 1024
|
||||
)
|
||||
|
||||
type adaptiveTimer struct {
|
||||
logger log.ContextLogger
|
||||
router adapter.Router
|
||||
memoryLimit uint64
|
||||
safetyMargin uint64
|
||||
minInterval time.Duration
|
||||
maxInterval time.Duration
|
||||
checksBeforeLimit int
|
||||
useAvailable bool
|
||||
|
||||
access sync.Mutex
|
||||
timer *time.Timer
|
||||
previousUsage uint64
|
||||
lastInterval time.Duration
|
||||
}
|
||||
|
||||
type timerConfig struct {
|
||||
memoryLimit uint64
|
||||
safetyMargin uint64
|
||||
minInterval time.Duration
|
||||
maxInterval time.Duration
|
||||
checksBeforeLimit int
|
||||
useAvailable bool
|
||||
}
|
||||
|
||||
func newAdaptiveTimer(logger log.ContextLogger, router adapter.Router, config timerConfig) *adaptiveTimer {
|
||||
return &adaptiveTimer{
|
||||
logger: logger,
|
||||
router: router,
|
||||
memoryLimit: config.memoryLimit,
|
||||
safetyMargin: config.safetyMargin,
|
||||
minInterval: config.minInterval,
|
||||
maxInterval: config.maxInterval,
|
||||
checksBeforeLimit: config.checksBeforeLimit,
|
||||
useAvailable: config.useAvailable,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *adaptiveTimer) start(_ uint64) {
|
||||
t.access.Lock()
|
||||
defer t.access.Unlock()
|
||||
t.startLocked()
|
||||
}
|
||||
|
||||
func (t *adaptiveTimer) startNow() {
|
||||
t.access.Lock()
|
||||
t.startLocked()
|
||||
t.access.Unlock()
|
||||
t.poll()
|
||||
}
|
||||
|
||||
func (t *adaptiveTimer) startLocked() {
|
||||
if t.timer != nil {
|
||||
return
|
||||
}
|
||||
t.previousUsage = memory.Total()
|
||||
t.lastInterval = t.minInterval
|
||||
t.timer = time.AfterFunc(t.minInterval, t.poll)
|
||||
}
|
||||
|
||||
func (t *adaptiveTimer) stop() {
|
||||
t.access.Lock()
|
||||
defer t.access.Unlock()
|
||||
t.stopLocked()
|
||||
}
|
||||
|
||||
func (t *adaptiveTimer) stopLocked() {
|
||||
if t.timer != nil {
|
||||
t.timer.Stop()
|
||||
t.timer = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (t *adaptiveTimer) running() bool {
|
||||
t.access.Lock()
|
||||
defer t.access.Unlock()
|
||||
return t.timer != nil
|
||||
}
|
||||
|
||||
func (t *adaptiveTimer) poll() {
|
||||
t.access.Lock()
|
||||
defer t.access.Unlock()
|
||||
if t.timer == nil {
|
||||
return
|
||||
}
|
||||
|
||||
usage := memory.Total()
|
||||
delta := int64(usage) - int64(t.previousUsage)
|
||||
t.previousUsage = usage
|
||||
|
||||
var remaining uint64
|
||||
var triggered bool
|
||||
|
||||
if t.memoryLimit > 0 {
|
||||
if usage >= t.memoryLimit {
|
||||
remaining = 0
|
||||
triggered = true
|
||||
} else {
|
||||
remaining = t.memoryLimit - usage
|
||||
}
|
||||
} else if t.useAvailable {
|
||||
available := memory.Available()
|
||||
if available <= t.safetyMargin {
|
||||
remaining = 0
|
||||
triggered = true
|
||||
} else {
|
||||
remaining = available - t.safetyMargin
|
||||
}
|
||||
} else {
|
||||
remaining = 0
|
||||
}
|
||||
|
||||
if triggered {
|
||||
t.logger.Error("memory threshold reached, usage: ", usage/(1024*1024), " MiB, resetting network")
|
||||
t.router.ResetNetwork()
|
||||
runtimeDebug.FreeOSMemory()
|
||||
}
|
||||
|
||||
var interval time.Duration
|
||||
if triggered {
|
||||
interval = t.maxInterval
|
||||
} else if delta <= 0 {
|
||||
interval = t.maxInterval
|
||||
} else if t.checksBeforeLimit <= 0 {
|
||||
interval = t.maxInterval
|
||||
} else {
|
||||
timeToLimit := time.Duration(float64(remaining) / float64(delta) * float64(t.lastInterval))
|
||||
interval = timeToLimit / time.Duration(t.checksBeforeLimit)
|
||||
if interval < t.minInterval {
|
||||
interval = t.minInterval
|
||||
}
|
||||
if interval > t.maxInterval {
|
||||
interval = t.maxInterval
|
||||
}
|
||||
}
|
||||
|
||||
t.lastInterval = interval
|
||||
t.timer.Reset(interval)
|
||||
}
|
||||
325
service/oomkiller/timer.go
Normal file
325
service/oomkiller/timer.go
Normal file
@@ -0,0 +1,325 @@
|
||||
package oomkiller
|
||||
|
||||
import (
|
||||
runtimeDebug "runtime/debug"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common/byteformats"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/memory"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultMinInterval = 100 * time.Millisecond
|
||||
defaultArmedInterval = time.Second
|
||||
defaultMaxInterval = 10 * time.Second
|
||||
defaultSafetyMargin = 5 * 1024 * 1024
|
||||
defaultAvailableTriggerMarginMin = 32 * 1024 * 1024
|
||||
defaultAvailableTriggerMarginMax = 128 * 1024 * 1024
|
||||
)
|
||||
|
||||
type pressureState uint8
|
||||
|
||||
const (
|
||||
pressureStateNormal pressureState = iota
|
||||
pressureStateArmed
|
||||
pressureStateTriggered
|
||||
)
|
||||
|
||||
type memorySample struct {
|
||||
usage uint64
|
||||
available uint64
|
||||
availableKnown bool
|
||||
}
|
||||
|
||||
type pressureThresholds struct {
|
||||
trigger uint64
|
||||
armed uint64
|
||||
resume uint64
|
||||
}
|
||||
|
||||
type timerConfig struct {
|
||||
memoryLimit uint64
|
||||
safetyMargin uint64
|
||||
hasSafetyMargin bool
|
||||
minInterval time.Duration
|
||||
armedInterval time.Duration
|
||||
maxInterval time.Duration
|
||||
policyMode policyMode
|
||||
killerDisabled bool
|
||||
}
|
||||
|
||||
func buildTimerConfig(options option.OOMKillerServiceOptions, memoryLimit uint64, policyMode policyMode, killerDisabled bool) (timerConfig, error) {
|
||||
minInterval := defaultMinInterval
|
||||
if options.MinInterval != 0 {
|
||||
minInterval = time.Duration(options.MinInterval.Build())
|
||||
if minInterval <= 0 {
|
||||
return timerConfig{}, E.New("min_interval must be greater than 0")
|
||||
}
|
||||
}
|
||||
|
||||
maxInterval := defaultMaxInterval
|
||||
if options.MaxInterval != 0 {
|
||||
maxInterval = time.Duration(options.MaxInterval.Build())
|
||||
if maxInterval <= 0 {
|
||||
return timerConfig{}, E.New("max_interval must be greater than 0")
|
||||
}
|
||||
}
|
||||
if maxInterval < minInterval {
|
||||
return timerConfig{}, E.New("max_interval must be greater than or equal to min_interval")
|
||||
}
|
||||
|
||||
var (
|
||||
safetyMargin uint64
|
||||
hasSafetyMargin bool
|
||||
)
|
||||
if options.SafetyMargin != nil && options.SafetyMargin.Value() > 0 {
|
||||
safetyMargin = options.SafetyMargin.Value()
|
||||
hasSafetyMargin = true
|
||||
} else if memoryLimit > 0 {
|
||||
safetyMargin = defaultSafetyMargin
|
||||
hasSafetyMargin = true
|
||||
}
|
||||
|
||||
return timerConfig{
|
||||
memoryLimit: memoryLimit,
|
||||
safetyMargin: safetyMargin,
|
||||
hasSafetyMargin: hasSafetyMargin,
|
||||
minInterval: minInterval,
|
||||
armedInterval: max(min(defaultArmedInterval, maxInterval), minInterval),
|
||||
maxInterval: maxInterval,
|
||||
policyMode: policyMode,
|
||||
killerDisabled: killerDisabled,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type adaptiveTimer struct {
|
||||
timerConfig
|
||||
logger log.ContextLogger
|
||||
router adapter.Router
|
||||
onTriggered func(uint64)
|
||||
limitThresholds pressureThresholds
|
||||
|
||||
access sync.Mutex
|
||||
timer *time.Timer
|
||||
state pressureState
|
||||
forceMinInterval bool
|
||||
pendingPressureBaseline bool
|
||||
pressureBaseline memorySample
|
||||
pressureBaselineTime time.Time
|
||||
}
|
||||
|
||||
func newAdaptiveTimer(logger log.ContextLogger, router adapter.Router, config timerConfig, onTriggered func(uint64)) *adaptiveTimer {
|
||||
t := &adaptiveTimer{
|
||||
timerConfig: config,
|
||||
logger: logger,
|
||||
router: router,
|
||||
onTriggered: onTriggered,
|
||||
}
|
||||
if config.policyMode == policyModeMemoryLimit || config.policyMode == policyModeNetworkExtension {
|
||||
t.limitThresholds = computeLimitThresholds(config.memoryLimit, config.safetyMargin)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *adaptiveTimer) start() {
|
||||
t.access.Lock()
|
||||
defer t.access.Unlock()
|
||||
t.startLocked()
|
||||
}
|
||||
|
||||
func (t *adaptiveTimer) notifyPressure() {
|
||||
t.access.Lock()
|
||||
t.startLocked()
|
||||
t.forceMinInterval = true
|
||||
t.pendingPressureBaseline = true
|
||||
t.access.Unlock()
|
||||
t.poll()
|
||||
}
|
||||
|
||||
func (t *adaptiveTimer) startLocked() {
|
||||
if t.timer != nil {
|
||||
return
|
||||
}
|
||||
t.state = pressureStateNormal
|
||||
t.forceMinInterval = false
|
||||
t.timer = time.AfterFunc(t.minInterval, t.poll)
|
||||
}
|
||||
|
||||
func (t *adaptiveTimer) stop() {
|
||||
t.access.Lock()
|
||||
defer t.access.Unlock()
|
||||
if t.timer != nil {
|
||||
t.timer.Stop()
|
||||
t.timer = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (t *adaptiveTimer) poll() {
|
||||
var triggered bool
|
||||
var rateTriggered bool
|
||||
sample := readMemorySample(t.policyMode)
|
||||
|
||||
t.access.Lock()
|
||||
if t.timer == nil {
|
||||
t.access.Unlock()
|
||||
return
|
||||
}
|
||||
if t.pendingPressureBaseline {
|
||||
t.pressureBaseline = sample
|
||||
t.pressureBaselineTime = time.Now()
|
||||
t.pendingPressureBaseline = false
|
||||
}
|
||||
previousState := t.state
|
||||
t.state = t.nextState(sample)
|
||||
if t.state == pressureStateNormal {
|
||||
t.forceMinInterval = false
|
||||
t.pressureBaselineTime = time.Time{}
|
||||
}
|
||||
t.timer.Reset(t.intervalForState())
|
||||
triggered = previousState != pressureStateTriggered && t.state == pressureStateTriggered
|
||||
if !triggered && !t.pressureBaselineTime.IsZero() && t.memoryLimit > 0 &&
|
||||
sample.usage > t.pressureBaseline.usage && sample.usage < t.memoryLimit {
|
||||
elapsed := time.Since(t.pressureBaselineTime)
|
||||
if elapsed >= t.minInterval/2 {
|
||||
growth := sample.usage - t.pressureBaseline.usage
|
||||
ratePerSecond := float64(growth) / elapsed.Seconds()
|
||||
headroom := t.memoryLimit - sample.usage
|
||||
timeToLimit := time.Duration(float64(headroom)/ratePerSecond) * time.Second
|
||||
if timeToLimit < t.minInterval {
|
||||
triggered = true
|
||||
rateTriggered = true
|
||||
t.state = pressureStateTriggered
|
||||
}
|
||||
}
|
||||
}
|
||||
t.access.Unlock()
|
||||
|
||||
if !triggered {
|
||||
return
|
||||
}
|
||||
if rateTriggered {
|
||||
if t.killerDisabled {
|
||||
t.logger.Warn("memory growth rate critical (report only), usage: ", byteformats.FormatMemoryBytes(sample.usage), t.logDetails(sample))
|
||||
} else {
|
||||
t.logger.Error("memory growth rate critical, usage: ", byteformats.FormatMemoryBytes(sample.usage), t.logDetails(sample), ", resetting network")
|
||||
t.router.ResetNetwork()
|
||||
}
|
||||
} else {
|
||||
if t.killerDisabled {
|
||||
t.logger.Warn("memory threshold reached (report only), usage: ", byteformats.FormatMemoryBytes(sample.usage), t.logDetails(sample))
|
||||
} else {
|
||||
t.logger.Error("memory threshold reached, usage: ", byteformats.FormatMemoryBytes(sample.usage), t.logDetails(sample), ", resetting network")
|
||||
t.router.ResetNetwork()
|
||||
}
|
||||
}
|
||||
t.onTriggered(sample.usage)
|
||||
runtimeDebug.FreeOSMemory()
|
||||
}
|
||||
|
||||
func (t *adaptiveTimer) nextState(sample memorySample) pressureState {
|
||||
switch t.policyMode {
|
||||
case policyModeMemoryLimit, policyModeNetworkExtension:
|
||||
return nextPressureState(t.state,
|
||||
sample.usage >= t.limitThresholds.trigger,
|
||||
sample.usage >= t.limitThresholds.armed,
|
||||
sample.usage >= t.limitThresholds.resume,
|
||||
)
|
||||
case policyModeAvailable:
|
||||
if !sample.availableKnown {
|
||||
return pressureStateNormal
|
||||
}
|
||||
thresholds := t.availableThresholds(sample)
|
||||
return nextPressureState(t.state,
|
||||
sample.available <= thresholds.trigger,
|
||||
sample.available <= thresholds.armed,
|
||||
sample.available <= thresholds.resume,
|
||||
)
|
||||
default:
|
||||
return pressureStateNormal
|
||||
}
|
||||
}
|
||||
|
||||
func computeLimitThresholds(memoryLimit uint64, safetyMargin uint64) pressureThresholds {
|
||||
triggerMargin := min(safetyMargin, memoryLimit)
|
||||
armedMargin := min(triggerMargin*2, memoryLimit)
|
||||
resumeMargin := min(triggerMargin*4, memoryLimit)
|
||||
return pressureThresholds{
|
||||
trigger: memoryLimit - triggerMargin,
|
||||
armed: memoryLimit - armedMargin,
|
||||
resume: memoryLimit - resumeMargin,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *adaptiveTimer) availableThresholds(sample memorySample) pressureThresholds {
|
||||
var triggerMargin uint64
|
||||
if t.hasSafetyMargin {
|
||||
triggerMargin = t.safetyMargin
|
||||
} else if sample.usage == 0 {
|
||||
triggerMargin = defaultAvailableTriggerMarginMin
|
||||
} else {
|
||||
triggerMargin = max(defaultAvailableTriggerMarginMin, min(sample.usage/4, defaultAvailableTriggerMarginMax))
|
||||
}
|
||||
return pressureThresholds{
|
||||
trigger: triggerMargin,
|
||||
armed: triggerMargin * 2,
|
||||
resume: triggerMargin * 4,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *adaptiveTimer) intervalForState() time.Duration {
|
||||
if t.state == pressureStateNormal {
|
||||
return t.maxInterval
|
||||
}
|
||||
if t.forceMinInterval || t.state == pressureStateTriggered {
|
||||
return t.minInterval
|
||||
}
|
||||
return t.armedInterval
|
||||
}
|
||||
|
||||
func (t *adaptiveTimer) logDetails(sample memorySample) string {
|
||||
switch t.policyMode {
|
||||
case policyModeMemoryLimit, policyModeNetworkExtension:
|
||||
headroom := uint64(0)
|
||||
if sample.usage < t.memoryLimit {
|
||||
headroom = t.memoryLimit - sample.usage
|
||||
}
|
||||
return ", limit: " + byteformats.FormatMemoryBytes(t.memoryLimit) + ", headroom: " + byteformats.FormatMemoryBytes(headroom)
|
||||
case policyModeAvailable:
|
||||
if sample.availableKnown {
|
||||
return ", available: " + byteformats.FormatMemoryBytes(sample.available)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func nextPressureState(current pressureState, shouldTrigger, shouldArm, shouldStayTriggered bool) pressureState {
|
||||
if current == pressureStateTriggered {
|
||||
if shouldStayTriggered {
|
||||
return pressureStateTriggered
|
||||
}
|
||||
return pressureStateNormal
|
||||
}
|
||||
if shouldTrigger {
|
||||
return pressureStateTriggered
|
||||
}
|
||||
if shouldArm {
|
||||
return pressureStateArmed
|
||||
}
|
||||
return pressureStateNormal
|
||||
}
|
||||
|
||||
func readMemorySample(mode policyMode) memorySample {
|
||||
sample := memorySample{
|
||||
usage: memory.Total(),
|
||||
}
|
||||
if mode == policyModeAvailable {
|
||||
sample.availableKnown = true
|
||||
sample.available = memory.Available()
|
||||
}
|
||||
return sample
|
||||
}
|
||||
Reference in New Issue
Block a user