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 }