stats.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. package v2rayapi
  2. import (
  3. "context"
  4. "net"
  5. "regexp"
  6. "runtime"
  7. "strings"
  8. "sync"
  9. "sync/atomic"
  10. "time"
  11. "github.com/sagernet/sing-box/adapter"
  12. "github.com/sagernet/sing-box/option"
  13. "github.com/sagernet/sing/common/bufio"
  14. E "github.com/sagernet/sing/common/exceptions"
  15. N "github.com/sagernet/sing/common/network"
  16. )
  17. func init() {
  18. StatsService_ServiceDesc.ServiceName = "v2ray.core.app.stats.command.StatsService"
  19. }
  20. var (
  21. _ adapter.ConnectionTracker = (*StatsService)(nil)
  22. _ StatsServiceServer = (*StatsService)(nil)
  23. )
  24. type StatsService struct {
  25. createdAt time.Time
  26. inbounds map[string]bool
  27. outbounds map[string]bool
  28. users map[string]bool
  29. access sync.Mutex
  30. counters map[string]*atomic.Int64
  31. }
  32. func NewStatsService(options option.V2RayStatsServiceOptions) *StatsService {
  33. if !options.Enabled {
  34. return nil
  35. }
  36. inbounds := make(map[string]bool)
  37. outbounds := make(map[string]bool)
  38. users := make(map[string]bool)
  39. for _, inbound := range options.Inbounds {
  40. inbounds[inbound] = true
  41. }
  42. for _, outbound := range options.Outbounds {
  43. outbounds[outbound] = true
  44. }
  45. for _, user := range options.Users {
  46. users[user] = true
  47. }
  48. return &StatsService{
  49. createdAt: time.Now(),
  50. inbounds: inbounds,
  51. outbounds: outbounds,
  52. users: users,
  53. counters: make(map[string]*atomic.Int64),
  54. }
  55. }
  56. func (s *StatsService) RoutedConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, matchedRule adapter.Rule, matchOutbound adapter.Outbound) net.Conn {
  57. inbound := metadata.Inbound
  58. user := metadata.User
  59. outbound := matchOutbound.Tag()
  60. var readCounter []*atomic.Int64
  61. var writeCounter []*atomic.Int64
  62. countInbound := inbound != "" && s.inbounds[inbound]
  63. countOutbound := outbound != "" && s.outbounds[outbound]
  64. countUser := user != "" && s.users[user]
  65. if !countInbound && !countOutbound && !countUser {
  66. return conn
  67. }
  68. s.access.Lock()
  69. if countInbound {
  70. readCounter = append(readCounter, s.loadOrCreateCounter("inbound>>>"+inbound+">>>traffic>>>uplink"))
  71. writeCounter = append(writeCounter, s.loadOrCreateCounter("inbound>>>"+inbound+">>>traffic>>>downlink"))
  72. }
  73. if countOutbound {
  74. readCounter = append(readCounter, s.loadOrCreateCounter("outbound>>>"+outbound+">>>traffic>>>uplink"))
  75. writeCounter = append(writeCounter, s.loadOrCreateCounter("outbound>>>"+outbound+">>>traffic>>>downlink"))
  76. }
  77. if countUser {
  78. readCounter = append(readCounter, s.loadOrCreateCounter("user>>>"+user+">>>traffic>>>uplink"))
  79. writeCounter = append(writeCounter, s.loadOrCreateCounter("user>>>"+user+">>>traffic>>>downlink"))
  80. }
  81. s.access.Unlock()
  82. return bufio.NewInt64CounterConn(conn, readCounter, writeCounter)
  83. }
  84. func (s *StatsService) RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, matchedRule adapter.Rule, matchOutbound adapter.Outbound) N.PacketConn {
  85. inbound := metadata.Inbound
  86. user := metadata.User
  87. outbound := matchOutbound.Tag()
  88. var readCounter []*atomic.Int64
  89. var writeCounter []*atomic.Int64
  90. countInbound := inbound != "" && s.inbounds[inbound]
  91. countOutbound := outbound != "" && s.outbounds[outbound]
  92. countUser := user != "" && s.users[user]
  93. if !countInbound && !countOutbound && !countUser {
  94. return conn
  95. }
  96. s.access.Lock()
  97. if countInbound {
  98. readCounter = append(readCounter, s.loadOrCreateCounter("inbound>>>"+inbound+">>>traffic>>>uplink"))
  99. writeCounter = append(writeCounter, s.loadOrCreateCounter("inbound>>>"+inbound+">>>traffic>>>downlink"))
  100. }
  101. if countOutbound {
  102. readCounter = append(readCounter, s.loadOrCreateCounter("outbound>>>"+outbound+">>>traffic>>>uplink"))
  103. writeCounter = append(writeCounter, s.loadOrCreateCounter("outbound>>>"+outbound+">>>traffic>>>downlink"))
  104. }
  105. if countUser {
  106. readCounter = append(readCounter, s.loadOrCreateCounter("user>>>"+user+">>>traffic>>>uplink"))
  107. writeCounter = append(writeCounter, s.loadOrCreateCounter("user>>>"+user+">>>traffic>>>downlink"))
  108. }
  109. s.access.Unlock()
  110. return bufio.NewInt64CounterPacketConn(conn, readCounter, nil, writeCounter, nil)
  111. }
  112. func (s *StatsService) GetStats(ctx context.Context, request *GetStatsRequest) (*GetStatsResponse, error) {
  113. s.access.Lock()
  114. counter, loaded := s.counters[request.Name]
  115. s.access.Unlock()
  116. if !loaded {
  117. return nil, E.New(request.Name, " not found.")
  118. }
  119. var value int64
  120. if request.Reset_ {
  121. value = counter.Swap(0)
  122. } else {
  123. value = counter.Load()
  124. }
  125. return &GetStatsResponse{Stat: &Stat{Name: request.Name, Value: value}}, nil
  126. }
  127. func (s *StatsService) QueryStats(ctx context.Context, request *QueryStatsRequest) (*QueryStatsResponse, error) {
  128. var response QueryStatsResponse
  129. s.access.Lock()
  130. defer s.access.Unlock()
  131. if len(request.Patterns) == 0 {
  132. for name, counter := range s.counters {
  133. var value int64
  134. if request.Reset_ {
  135. value = counter.Swap(0)
  136. } else {
  137. value = counter.Load()
  138. }
  139. response.Stat = append(response.Stat, &Stat{Name: name, Value: value})
  140. }
  141. } else if request.Regexp {
  142. matchers := make([]*regexp.Regexp, 0, len(request.Patterns))
  143. for _, pattern := range request.Patterns {
  144. matcher, err := regexp.Compile(pattern)
  145. if err != nil {
  146. return nil, err
  147. }
  148. matchers = append(matchers, matcher)
  149. }
  150. for name, counter := range s.counters {
  151. for _, matcher := range matchers {
  152. if matcher.MatchString(name) {
  153. var value int64
  154. if request.Reset_ {
  155. value = counter.Swap(0)
  156. } else {
  157. value = counter.Load()
  158. }
  159. response.Stat = append(response.Stat, &Stat{Name: name, Value: value})
  160. }
  161. }
  162. }
  163. } else {
  164. for name, counter := range s.counters {
  165. for _, matcher := range request.Patterns {
  166. if strings.Contains(name, matcher) {
  167. var value int64
  168. if request.Reset_ {
  169. value = counter.Swap(0)
  170. } else {
  171. value = counter.Load()
  172. }
  173. response.Stat = append(response.Stat, &Stat{Name: name, Value: value})
  174. }
  175. }
  176. }
  177. }
  178. return &response, nil
  179. }
  180. func (s *StatsService) GetSysStats(ctx context.Context, request *SysStatsRequest) (*SysStatsResponse, error) {
  181. var rtm runtime.MemStats
  182. runtime.ReadMemStats(&rtm)
  183. response := &SysStatsResponse{
  184. Uptime: uint32(time.Since(s.createdAt).Seconds()),
  185. NumGoroutine: uint32(runtime.NumGoroutine()),
  186. Alloc: rtm.Alloc,
  187. TotalAlloc: rtm.TotalAlloc,
  188. Sys: rtm.Sys,
  189. Mallocs: rtm.Mallocs,
  190. Frees: rtm.Frees,
  191. LiveObjects: rtm.Mallocs - rtm.Frees,
  192. NumGC: rtm.NumGC,
  193. PauseTotalNs: rtm.PauseTotalNs,
  194. }
  195. return response, nil
  196. }
  197. func (s *StatsService) mustEmbedUnimplementedStatsServiceServer() {
  198. }
  199. //nolint:staticcheck
  200. func (s *StatsService) loadOrCreateCounter(name string) *atomic.Int64 {
  201. counter, loaded := s.counters[name]
  202. if loaded {
  203. return counter
  204. }
  205. counter = &atomic.Int64{}
  206. s.counters[name] = counter
  207. return counter
  208. }