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  }