github.com/database64128/shadowsocks-go@v1.10.2-0.20240315062903-143a773533f1/stats/collector.go (about)

     1  package stats
     2  
     3  import (
     4  	"cmp"
     5  	"slices"
     6  	"sync"
     7  	"sync/atomic"
     8  )
     9  
    10  type trafficCollector struct {
    11  	downlinkPackets atomic.Uint64
    12  	downlinkBytes   atomic.Uint64
    13  	uplinkPackets   atomic.Uint64
    14  	uplinkBytes     atomic.Uint64
    15  	tcpSessions     atomic.Uint64
    16  	udpSessions     atomic.Uint64
    17  }
    18  
    19  func (tc *trafficCollector) collectTCPSession(downlinkBytes, uplinkBytes uint64) {
    20  	tc.downlinkBytes.Add(downlinkBytes)
    21  	tc.uplinkBytes.Add(uplinkBytes)
    22  	tc.tcpSessions.Add(1)
    23  }
    24  
    25  func (tc *trafficCollector) collectUDPSessionDownlink(downlinkPackets, downlinkBytes uint64) {
    26  	tc.downlinkPackets.Add(downlinkPackets)
    27  	tc.downlinkBytes.Add(downlinkBytes)
    28  	tc.udpSessions.Add(1)
    29  }
    30  
    31  func (tc *trafficCollector) collectUDPSessionUplink(uplinkPackets, uplinkBytes uint64) {
    32  	tc.uplinkPackets.Add(uplinkPackets)
    33  	tc.uplinkBytes.Add(uplinkBytes)
    34  }
    35  
    36  // Traffic stores the traffic statistics.
    37  type Traffic struct {
    38  	DownlinkPackets uint64 `json:"downlinkPackets"`
    39  	DownlinkBytes   uint64 `json:"downlinkBytes"`
    40  	UplinkPackets   uint64 `json:"uplinkPackets"`
    41  	UplinkBytes     uint64 `json:"uplinkBytes"`
    42  	TCPSessions     uint64 `json:"tcpSessions"`
    43  	UDPSessions     uint64 `json:"udpSessions"`
    44  }
    45  
    46  func (t *Traffic) Add(u Traffic) {
    47  	t.DownlinkPackets += u.DownlinkPackets
    48  	t.DownlinkBytes += u.DownlinkBytes
    49  	t.UplinkPackets += u.UplinkPackets
    50  	t.UplinkBytes += u.UplinkBytes
    51  	t.TCPSessions += u.TCPSessions
    52  	t.UDPSessions += u.UDPSessions
    53  }
    54  
    55  func (tc *trafficCollector) snapshot() Traffic {
    56  	return Traffic{
    57  		DownlinkPackets: tc.downlinkPackets.Load(),
    58  		DownlinkBytes:   tc.downlinkBytes.Load(),
    59  		UplinkPackets:   tc.uplinkPackets.Load(),
    60  		UplinkBytes:     tc.uplinkBytes.Load(),
    61  		TCPSessions:     tc.tcpSessions.Load(),
    62  		UDPSessions:     tc.udpSessions.Load(),
    63  	}
    64  }
    65  
    66  func (tc *trafficCollector) snapshotAndReset() Traffic {
    67  	return Traffic{
    68  		DownlinkPackets: tc.downlinkPackets.Swap(0),
    69  		DownlinkBytes:   tc.downlinkBytes.Swap(0),
    70  		UplinkPackets:   tc.uplinkPackets.Swap(0),
    71  		UplinkBytes:     tc.uplinkBytes.Swap(0),
    72  		TCPSessions:     tc.tcpSessions.Swap(0),
    73  		UDPSessions:     tc.udpSessions.Swap(0),
    74  	}
    75  }
    76  
    77  type userCollector struct {
    78  	trafficCollector
    79  }
    80  
    81  // User stores the user's traffic statistics.
    82  type User struct {
    83  	Name string `json:"username"`
    84  	Traffic
    85  }
    86  
    87  // Compare is useful for sorting users by name.
    88  func (u User) Compare(other User) int {
    89  	return cmp.Compare(u.Name, other.Name)
    90  }
    91  
    92  func (uc *userCollector) snapshot(username string) User {
    93  	return User{
    94  		Name:    username,
    95  		Traffic: uc.trafficCollector.snapshot(),
    96  	}
    97  }
    98  
    99  func (uc *userCollector) snapshotAndReset(username string) User {
   100  	return User{
   101  		Name:    username,
   102  		Traffic: uc.trafficCollector.snapshotAndReset(),
   103  	}
   104  }
   105  
   106  type serverCollector struct {
   107  	tc  trafficCollector
   108  	ucs map[string]*userCollector
   109  	mu  sync.RWMutex
   110  }
   111  
   112  // NewServerCollector returns a new collector for collecting server traffic statistics.
   113  func NewServerCollector() *serverCollector {
   114  	return &serverCollector{
   115  		ucs: make(map[string]*userCollector),
   116  	}
   117  }
   118  
   119  func (sc *serverCollector) userCollector(username string) *userCollector {
   120  	sc.mu.RLock()
   121  	uc := sc.ucs[username]
   122  	sc.mu.RUnlock()
   123  	if uc == nil {
   124  		sc.mu.Lock()
   125  		uc = sc.ucs[username]
   126  		if uc == nil {
   127  			uc = &userCollector{}
   128  			sc.ucs[username] = uc
   129  		}
   130  		sc.mu.Unlock()
   131  	}
   132  	return uc
   133  }
   134  
   135  func (sc *serverCollector) trafficCollector(username string) *trafficCollector {
   136  	if username == "" {
   137  		return &sc.tc
   138  	}
   139  	return &sc.userCollector(username).trafficCollector
   140  }
   141  
   142  // CollectTCPSession implements the Collector CollectTCPSession method.
   143  func (sc *serverCollector) CollectTCPSession(username string, downlinkBytes, uplinkBytes uint64) {
   144  	sc.trafficCollector(username).collectTCPSession(downlinkBytes, uplinkBytes)
   145  }
   146  
   147  // CollectUDPSessionDownlink implements the Collector CollectUDPSessionDownlink method.
   148  func (sc *serverCollector) CollectUDPSessionDownlink(username string, downlinkPackets, downlinkBytes uint64) {
   149  	sc.trafficCollector(username).collectUDPSessionDownlink(downlinkPackets, downlinkBytes)
   150  }
   151  
   152  // CollectUDPSessionUplink implements the Collector CollectUDPSessionUplink method.
   153  func (sc *serverCollector) CollectUDPSessionUplink(username string, uplinkPackets, uplinkBytes uint64) {
   154  	sc.trafficCollector(username).collectUDPSessionUplink(uplinkPackets, uplinkBytes)
   155  }
   156  
   157  // Server stores the server's traffic statistics.
   158  type Server struct {
   159  	Traffic
   160  	Users []User `json:"users,omitempty"`
   161  }
   162  
   163  // Snapshot implements the Collector Snapshot method.
   164  func (sc *serverCollector) Snapshot() (s Server) {
   165  	s.Traffic = sc.tc.snapshot()
   166  	sc.mu.RLock()
   167  	s.Users = make([]User, 0, len(sc.ucs))
   168  	for username, uc := range sc.ucs {
   169  		u := uc.snapshot(username)
   170  		s.Traffic.Add(u.Traffic)
   171  		s.Users = append(s.Users, u)
   172  	}
   173  	sc.mu.RUnlock()
   174  	slices.SortFunc(s.Users, User.Compare)
   175  	return
   176  }
   177  
   178  // SnapshotAndReset implements the Collector SnapshotAndReset method.
   179  func (sc *serverCollector) SnapshotAndReset() (s Server) {
   180  	s.Traffic = sc.tc.snapshotAndReset()
   181  	sc.mu.RLock()
   182  	s.Users = make([]User, 0, len(sc.ucs))
   183  	for username, uc := range sc.ucs {
   184  		u := uc.snapshotAndReset(username)
   185  		s.Traffic.Add(u.Traffic)
   186  		s.Users = append(s.Users, u)
   187  	}
   188  	sc.mu.RUnlock()
   189  	slices.SortFunc(s.Users, User.Compare)
   190  	return
   191  }
   192  
   193  // Collector collects server traffic statistics.
   194  type Collector interface {
   195  	// CollectTCPSession collects the TCP session's traffic statistics.
   196  	CollectTCPSession(username string, downlinkBytes, uplinkBytes uint64)
   197  
   198  	// CollectUDPSessionDownlink collects the UDP session's downlink traffic statistics.
   199  	CollectUDPSessionDownlink(username string, downlinkPackets, downlinkBytes uint64)
   200  
   201  	// CollectUDPSessionUplink collects the UDP session's uplink traffic statistics.
   202  	CollectUDPSessionUplink(username string, uplinkPackets, uplinkBytes uint64)
   203  
   204  	// Snapshot returns the server's traffic statistics.
   205  	Snapshot() Server
   206  
   207  	// SnapshotAndReset returns the server's traffic statistics and resets the statistics.
   208  	SnapshotAndReset() Server
   209  }
   210  
   211  // NoopCollector is a no-op collector.
   212  // Its collect methods do nothing and its snapshot method returns empty statistics.
   213  type NoopCollector struct{}
   214  
   215  // CollectTCPSession implements the Collector CollectTCPSession method.
   216  func (NoopCollector) CollectTCPSession(username string, downlinkBytes, uplinkBytes uint64) {}
   217  
   218  // CollectUDPSessionDownlink implements the Collector CollectUDPSessionDownlink method.
   219  func (NoopCollector) CollectUDPSessionDownlink(username string, downlinkPackets, downlinkBytes uint64) {
   220  }
   221  
   222  // CollectUDPSessionUplink implements the Collector CollectUDPSessionUplink method.
   223  func (NoopCollector) CollectUDPSessionUplink(username string, uplinkPackets, uplinkBytes uint64) {}
   224  
   225  // Snapshot implements the Collector Snapshot method.
   226  func (NoopCollector) Snapshot() Server {
   227  	return Server{}
   228  }
   229  
   230  // SnapshotAndReset implements the Collector SnapshotAndReset method.
   231  func (NoopCollector) SnapshotAndReset() Server {
   232  	return Server{}
   233  }
   234  
   235  // Config stores configuration for the stats collector.
   236  type Config struct {
   237  	Enabled bool `json:"enabled"`
   238  }
   239  
   240  // Collector returns a new stats collector from the config.
   241  func (c Config) Collector() Collector {
   242  	if c.Enabled {
   243  		return NewServerCollector()
   244  	}
   245  	return NoopCollector{}
   246  }