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