diff --git a/app/stats/config.go b/app/stats/config.go new file mode 100644 index 000000000..4e3490034 --- /dev/null +++ b/app/stats/config.go @@ -0,0 +1,13 @@ +package stats + +import ( + "context" + + "v2ray.com/core/common" +) + +func init() { + common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { + return NewManager(ctx, config.(*Config)) + })) +} diff --git a/app/stats/config.pb.go b/app/stats/config.pb.go new file mode 100644 index 000000000..a4b3d244a --- /dev/null +++ b/app/stats/config.pb.go @@ -0,0 +1,42 @@ +package stats + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type Config struct { +} + +func (m *Config) Reset() { *m = Config{} } +func (m *Config) String() string { return proto.CompactTextString(m) } +func (*Config) ProtoMessage() {} +func (*Config) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } + +func init() { + proto.RegisterType((*Config)(nil), "v2ray.core.app.stats.Config") +} + +func init() { proto.RegisterFile("v2ray.com/core/app/stats/config.proto", fileDescriptor0) } + +var fileDescriptor0 = []byte{ + // 123 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x2d, 0x33, 0x2a, 0x4a, + 0xac, 0xd4, 0x4b, 0xce, 0xcf, 0xd5, 0x4f, 0xce, 0x2f, 0x4a, 0xd5, 0x4f, 0x2c, 0x28, 0xd0, 0x2f, + 0x2e, 0x49, 0x2c, 0x29, 0xd6, 0x4f, 0xce, 0xcf, 0x4b, 0xcb, 0x4c, 0xd7, 0x2b, 0x28, 0xca, 0x2f, + 0xc9, 0x17, 0x12, 0x81, 0x29, 0x2b, 0x4a, 0xd5, 0x4b, 0x2c, 0x28, 0xd0, 0x03, 0x2b, 0x51, 0xe2, + 0xe0, 0x62, 0x73, 0x06, 0xab, 0x72, 0xb2, 0xe2, 0x92, 0x48, 0xce, 0xcf, 0xd5, 0xc3, 0xa6, 0x2a, + 0x80, 0x31, 0x8a, 0x15, 0xcc, 0x58, 0xc5, 0x24, 0x12, 0x66, 0x14, 0x94, 0x58, 0xa9, 0xe7, 0x0c, + 0x92, 0x77, 0x2c, 0x28, 0xd0, 0x0b, 0x06, 0x09, 0x27, 0xb1, 0x81, 0xad, 0x30, 0x06, 0x04, 0x00, + 0x00, 0xff, 0xff, 0x88, 0x24, 0xc6, 0x41, 0x8b, 0x00, 0x00, 0x00, +} diff --git a/app/stats/config.proto b/app/stats/config.proto new file mode 100644 index 000000000..9407fd391 --- /dev/null +++ b/app/stats/config.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; + +package v2ray.core.app.stats; +option csharp_namespace = "V2Ray.Core.App.Stats"; +option go_package = "stats"; +option java_package = "com.v2ray.core.app.stats"; +option java_multiple_files = true; + +message Config { + +} diff --git a/app/stats/errors.generated.go b/app/stats/errors.generated.go new file mode 100644 index 000000000..1bfbe328a --- /dev/null +++ b/app/stats/errors.generated.go @@ -0,0 +1,5 @@ +package stats + +import "v2ray.com/core/common/errors" + +func newError(values ...interface{}) *errors.Error { return errors.New(values...).Path("App", "Stats") } diff --git a/app/stats/stats.go b/app/stats/stats.go new file mode 100644 index 000000000..9177e2a20 --- /dev/null +++ b/app/stats/stats.go @@ -0,0 +1,68 @@ +package stats + +//go:generate go run $GOPATH/src/v2ray.com/core/common/errors/errorgen/main.go -pkg stats -path App,Stats + +import ( + "context" + "sync" + "sync/atomic" + + "v2ray.com/core" +) + +type Counter struct { + value int64 +} + +func (c *Counter) Value() int64 { + return atomic.LoadInt64(&c.value) +} + +func (c *Counter) Exchange(newValue int64) int64 { + return atomic.SwapInt64(&c.value, newValue) +} + +func (c *Counter) Add(delta int64) int64 { + return atomic.AddInt64(&c.value, delta) +} + +type Manager struct { + access sync.RWMutex + counters map[string]*Counter +} + +func NewManager(ctx context.Context, config *Config) (*Manager, error) { + return &Manager{ + counters: make(map[string]*Counter), + }, nil +} + +func (m *Manager) RegisterCounter(name string) (core.StatCounter, error) { + m.access.Lock() + defer m.access.Unlock() + + if _, found := m.counters[name]; found { + return nil, newError("Counter ", name, " already registered.") + } + c := new(Counter) + m.counters[name] = c + return c, nil +} + +func (m *Manager) GetCounter(name string) core.StatCounter { + m.access.RLock() + defer m.access.RUnlock() + + if c, found := m.counters[name]; found { + return c + } + return nil +} + +func (m *Manager) Start() error { + return nil +} + +func (m *Manager) Close() error { + return nil +} diff --git a/app/stats/stats_test.go b/app/stats/stats_test.go new file mode 100644 index 000000000..9a3a1e5cd --- /dev/null +++ b/app/stats/stats_test.go @@ -0,0 +1,15 @@ +package stats_test + +import ( + "testing" + + "v2ray.com/core" + . "v2ray.com/core/app/stats" + . "v2ray.com/ext/assert" +) + +func TestInternface(t *testing.T) { + assert := With(t) + + assert((*Manager)(nil), Implements, (*core.StatManager)(nil)) +} diff --git a/config.pb.go b/config.pb.go index f63e1f5ab..beee21273 100644 --- a/config.pb.go +++ b/config.pb.go @@ -23,7 +23,7 @@ type Config struct { Inbound []*InboundHandlerConfig `protobuf:"bytes,1,rep,name=inbound" json:"inbound,omitempty"` // Outbound handler configurations. Must have at least one item. The first item is used as default for routing. Outbound []*OutboundHandlerConfig `protobuf:"bytes,2,rep,name=outbound" json:"outbound,omitempty"` - // App configuration. Must be one in the app directory. + // App is for configurations of all features in V2Ray. A feature must implement the Feature interface, and its config type must be registered through common.RegisterConfig. App []*v2ray_core_common_serial.TypedMessage `protobuf:"bytes,4,rep,name=app" json:"app,omitempty"` // Transport settings. Transport *v2ray_core_transport.Config `protobuf:"bytes,5,opt,name=transport" json:"transport,omitempty"` @@ -72,10 +72,11 @@ func (m *Config) GetExtension() []*v2ray_core_common_serial.TypedMessage { return nil } +// InboundHandlerConfig is the configuration for inbound handler. type InboundHandlerConfig struct { - // Tag of the inbound handler. + // Tag of the inbound handler. The tag must be unique among all inbound handlers Tag string `protobuf:"bytes,1,opt,name=tag" json:"tag,omitempty"` - // Settings for how this inbound proxy is handled. Must be ReceiverConfig above. + // Settings for how this inbound proxy is handled. ReceiverSettings *v2ray_core_common_serial.TypedMessage `protobuf:"bytes,2,opt,name=receiver_settings,json=receiverSettings" json:"receiver_settings,omitempty"` // Settings for inbound proxy. Must be one of the inbound proxies. ProxySettings *v2ray_core_common_serial.TypedMessage `protobuf:"bytes,3,opt,name=proxy_settings,json=proxySettings" json:"proxy_settings,omitempty"` @@ -107,10 +108,11 @@ func (m *InboundHandlerConfig) GetProxySettings() *v2ray_core_common_serial.Type return nil } +// OutboundHandlerConfig is the configuration for outbound handler. type OutboundHandlerConfig struct { // Tag of this outbound handler. Tag string `protobuf:"bytes,1,opt,name=tag" json:"tag,omitempty"` - // Settings for how to dial connection for this outbound handler. Must be SenderConfig above. + // Settings for how to dial connection for this outbound handler. SenderSettings *v2ray_core_common_serial.TypedMessage `protobuf:"bytes,2,opt,name=sender_settings,json=senderSettings" json:"sender_settings,omitempty"` // Settings for this outbound proxy. Must be one of the outbound proxies. ProxySettings *v2ray_core_common_serial.TypedMessage `protobuf:"bytes,3,opt,name=proxy_settings,json=proxySettings" json:"proxy_settings,omitempty"` diff --git a/stats.go b/stats.go new file mode 100644 index 000000000..42eb5e74c --- /dev/null +++ b/stats.go @@ -0,0 +1,71 @@ +package core + +import ( + "sync" +) + +type StatCounter interface { + Value() int64 + Exchange(int64) int64 + Add(int64) int64 +} + +type StatManager interface { + Feature + + RegisterCounter(string) (StatCounter, error) + GetCounter(string) StatCounter +} + +type syncStatManager struct { + sync.RWMutex + StatManager +} + +func (s *syncStatManager) Start() error { + s.RLock() + defer s.RUnlock() + + if s.StatManager == nil { + return newError("StatManager not set.") + } + + return s.StatManager.Start() +} + +func (s *syncStatManager) Close() error { + s.RLock() + defer s.RUnlock() + + if s.StatManager == nil { + return newError("StatManager not set.") + } + return s.StatManager.Close() +} + +func (s *syncStatManager) RegisterCounter(name string) (StatCounter, error) { + s.RLock() + defer s.RUnlock() + + if s.StatManager == nil { + return nil, newError("StatManager not set.") + } + return s.StatManager.RegisterCounter(name) +} + +func (s *syncStatManager) GetCounter(name string) StatCounter { + s.RLock() + defer s.RUnlock() + + if s.StatManager == nil { + return nil + } + return s.StatManager.GetCounter(name) +} + +func (s *syncStatManager) Set(m StatManager) { + s.Lock() + defer s.Unlock() + + s.StatManager = m +} diff --git a/v2ray.go b/v2ray.go index d68842169..8bd4dc718 100755 --- a/v2ray.go +++ b/v2ray.go @@ -28,6 +28,7 @@ type Instance struct { router syncRouter ihm syncInboundHandlerManager ohm syncOutboundHandlerManager + stats syncStatManager access sync.Mutex features []Feature @@ -148,6 +149,8 @@ func (s *Instance) RegisterFeature(feature interface{}, instance Feature) error s.ihm.Set(instance.(InboundHandlerManager)) case OutboundHandlerManager, *OutboundHandlerManager: s.ohm.Set(instance.(OutboundHandlerManager)) + case StatManager, *StatManager: + s.stats.Set(instance.(StatManager)) default: s.access.Lock() s.features = append(s.features, instance) @@ -162,7 +165,7 @@ func (s *Instance) RegisterFeature(feature interface{}, instance Feature) error } func (s *Instance) allFeatures() []Feature { - return append([]Feature{s.DNSClient(), s.PolicyManager(), s.Dispatcher(), s.Router(), s.InboundHandlerManager(), s.OutboundHandlerManager()}, s.features...) + return append([]Feature{s.DNSClient(), s.PolicyManager(), s.Dispatcher(), s.Router(), s.InboundHandlerManager(), s.OutboundHandlerManager(), s.Stats()}, s.features...) } // GetFeature returns a feature that was registered in this Instance. Nil if not found. @@ -207,3 +210,7 @@ func (s *Instance) InboundHandlerManager() InboundHandlerManager { func (s *Instance) OutboundHandlerManager() OutboundHandlerManager { return &(s.ohm) } + +func (s *Instance) Stats() StatManager { + return &(s.stats) +}