github.com/TeaOSLab/EdgeNode@v1.3.8/internal/stats/bandwidth_stat_manager.go (about) 1 // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 3 package stats 4 5 import ( 6 "encoding/json" 7 "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" 8 "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" 9 teaconst "github.com/TeaOSLab/EdgeNode/internal/const" 10 "github.com/TeaOSLab/EdgeNode/internal/events" 11 "github.com/TeaOSLab/EdgeNode/internal/goman" 12 "github.com/TeaOSLab/EdgeNode/internal/remotelogs" 13 "github.com/TeaOSLab/EdgeNode/internal/rpc" 14 "github.com/TeaOSLab/EdgeNode/internal/utils/fasttime" 15 "github.com/iwind/TeaGo/Tea" 16 "github.com/iwind/TeaGo/logs" 17 "github.com/iwind/TeaGo/types" 18 timeutil "github.com/iwind/TeaGo/utils/time" 19 "os" 20 "sync" 21 "time" 22 ) 23 24 var SharedBandwidthStatManager = NewBandwidthStatManager() 25 26 const bandwidthTimestampDelim = 2 // N秒平均,更为精确 27 28 func init() { 29 if !teaconst.IsMain { 30 return 31 } 32 33 events.On(events.EventLoaded, func() { 34 goman.New(func() { 35 SharedBandwidthStatManager.Start() 36 }) 37 }) 38 39 events.OnClose(func() { 40 SharedBandwidthStatManager.Cancel() 41 42 err := SharedBandwidthStatManager.Save() 43 if err != nil { 44 remotelogs.Error("STAT", "save bandwidth stats failed: "+err.Error()) 45 } 46 }) 47 } 48 49 type BandwidthStat struct { 50 Day string `json:"day"` 51 TimeAt string `json:"timeAt"` 52 UserId int64 `json:"userId"` 53 ServerId int64 `json:"serverId"` 54 55 CurrentBytes int64 `json:"currentBytes"` 56 CurrentTimestamp int64 `json:"currentTimestamp"` 57 MaxBytes int64 `json:"maxBytes"` 58 TotalBytes int64 `json:"totalBytes"` 59 60 CachedBytes int64 `json:"cachedBytes"` 61 AttackBytes int64 `json:"attackBytes"` 62 CountRequests int64 `json:"countRequests"` 63 CountCachedRequests int64 `json:"countCachedRequests"` 64 CountAttackRequests int64 `json:"countAttackRequests"` 65 CountWebsocketConnections int64 `json:"countWebsocketConnections"` 66 UserPlanId int64 `json:"userPlanId"` 67 } 68 69 // BandwidthStatManager 服务带宽统计 70 type BandwidthStatManager struct { 71 m map[string]*BandwidthStat // serverId@day@time => *BandwidthStat 72 73 pbStats []*pb.ServerBandwidthStat 74 75 lastTime string // 上一次执行的时间 76 77 ticker *time.Ticker 78 locker sync.Mutex 79 80 cacheFile string 81 } 82 83 func NewBandwidthStatManager() *BandwidthStatManager { 84 return &BandwidthStatManager{ 85 m: map[string]*BandwidthStat{}, 86 ticker: time.NewTicker(1 * time.Minute), // 时间小于1分钟是为了更快速地上传结果 87 cacheFile: Tea.Root + "/data/stat_bandwidth.cache", 88 } 89 } 90 91 func (this *BandwidthStatManager) Start() { 92 // 初始化DAU统计 93 { 94 err := SharedDAUManager.Init() 95 if err != nil { 96 remotelogs.Error("DAU_MANAGER", "initialize DAU manager failed: "+err.Error()) 97 } 98 } 99 100 // 从上次数据中恢复 101 this.locker.Lock() 102 this.recover() 103 this.locker.Unlock() 104 105 // 循环上报数据 106 for range this.ticker.C { 107 err := this.Loop() 108 if err != nil && !rpc.IsConnError(err) { 109 remotelogs.Error("BANDWIDTH_STAT_MANAGER", err.Error()) 110 } 111 } 112 } 113 114 func (this *BandwidthStatManager) Loop() error { 115 var regionId int64 116 nodeConfig, _ := nodeconfigs.SharedNodeConfig() 117 if nodeConfig != nil { 118 regionId = nodeConfig.RegionId 119 } 120 121 var now = time.Now() 122 var day = timeutil.Format("Ymd", now) 123 var currentTime = timeutil.FormatTime("Hi", now.Unix()/300*300) // 300s = 5 minutes 124 125 if this.lastTime == currentTime { 126 return nil 127 } 128 this.lastTime = currentTime 129 130 var pbStats = []*pb.ServerBandwidthStat{} 131 132 // 历史未提交记录 133 if len(this.pbStats) > 0 { 134 var expiredTime = timeutil.FormatTime("Hi", time.Now().Unix()-1200) // 只保留20分钟 135 136 for _, stat := range this.pbStats { 137 if stat.TimeAt > expiredTime { 138 pbStats = append(pbStats, stat) 139 } 140 } 141 this.pbStats = nil 142 } 143 144 var ipStatMap = SharedDAUManager.ReadStatMap() 145 146 this.locker.Lock() 147 for key, stat := range this.m { 148 if stat.Day < day || stat.TimeAt < currentTime { 149 // 防止数据出现错误 150 if stat.CachedBytes > stat.TotalBytes || stat.CountCachedRequests == stat.CountRequests { 151 stat.CachedBytes = stat.TotalBytes 152 } 153 154 if stat.AttackBytes > stat.TotalBytes { 155 stat.AttackBytes = stat.TotalBytes 156 } 157 158 var ipKey = "server_" + stat.Day + "_" + types.String(stat.ServerId) 159 160 pbStats = append(pbStats, &pb.ServerBandwidthStat{ 161 Id: 0, 162 UserId: stat.UserId, 163 ServerId: stat.ServerId, 164 Day: stat.Day, 165 TimeAt: stat.TimeAt, 166 Bytes: stat.MaxBytes / bandwidthTimestampDelim, 167 TotalBytes: stat.TotalBytes, 168 CachedBytes: stat.CachedBytes, 169 AttackBytes: stat.AttackBytes, 170 CountRequests: stat.CountRequests, 171 CountCachedRequests: stat.CountCachedRequests, 172 CountAttackRequests: stat.CountAttackRequests, 173 CountWebsocketConnections: stat.CountWebsocketConnections, 174 CountIPs: ipStatMap[ipKey], 175 UserPlanId: stat.UserPlanId, 176 NodeRegionId: regionId, 177 }) 178 delete(this.m, key) 179 } 180 } 181 this.locker.Unlock() 182 183 if len(pbStats) > 0 { 184 // 上传 185 rpcClient, err := rpc.SharedRPC() 186 if err != nil { 187 return err 188 } 189 _, err = rpcClient.ServerBandwidthStatRPC.UploadServerBandwidthStats(rpcClient.Context(), &pb.UploadServerBandwidthStatsRequest{ServerBandwidthStats: pbStats}) 190 if err != nil { 191 this.pbStats = pbStats 192 193 return err 194 } 195 } 196 197 return nil 198 } 199 200 // AddBandwidth 添加带宽数据 201 func (this *BandwidthStatManager) AddBandwidth(userId int64, userPlanId int64, serverId int64, peekBytes int64, totalBytes int64) { 202 if serverId <= 0 || (peekBytes == 0 && totalBytes == 0) { 203 return 204 } 205 206 var now = fasttime.Now() 207 var timestamp = now.Unix() / bandwidthTimestampDelim * bandwidthTimestampDelim // 将时间戳均分成N等份 208 var day = now.Ymd() 209 var timeAt = now.Round5Hi() 210 var key = types.String(serverId) + "@" + day + "@" + timeAt 211 212 // 增加TCP Header尺寸,这里默认MTU为1500,且默认为IPv4 213 const mtu = 1500 214 const tcpHeaderSize = 20 215 if peekBytes > mtu { 216 peekBytes += peekBytes * tcpHeaderSize / mtu 217 } 218 219 this.locker.Lock() 220 stat, ok := this.m[key] 221 if ok { 222 // 此刻如果发生用户ID(userId)的变化也忽略,等N分钟后有新记录后再换 223 224 if stat.CurrentTimestamp == timestamp { 225 stat.CurrentBytes += peekBytes 226 } else { 227 stat.CurrentBytes = peekBytes 228 stat.CurrentTimestamp = timestamp 229 } 230 if stat.CurrentBytes > stat.MaxBytes { 231 stat.MaxBytes = stat.CurrentBytes 232 } 233 234 stat.TotalBytes += totalBytes 235 } else { 236 this.m[key] = &BandwidthStat{ 237 Day: day, 238 TimeAt: timeAt, 239 UserId: userId, 240 UserPlanId: userPlanId, 241 ServerId: serverId, 242 CurrentBytes: peekBytes, 243 MaxBytes: peekBytes, 244 TotalBytes: totalBytes, 245 CurrentTimestamp: timestamp, 246 } 247 } 248 this.locker.Unlock() 249 } 250 251 // AddTraffic 添加请求数据 252 func (this *BandwidthStatManager) AddTraffic(serverId int64, cachedBytes int64, countRequests int64, countCachedRequests int64, countAttacks int64, attackBytes int64, countWebsocketConnections int64) { 253 var now = fasttime.Now() 254 var day = now.Ymd() 255 var timeAt = now.Round5Hi() 256 var key = types.String(serverId) + "@" + day + "@" + timeAt 257 this.locker.Lock() 258 // 只有有记录了才会添加 259 stat, ok := this.m[key] 260 if ok { 261 stat.CachedBytes += cachedBytes 262 stat.CountRequests += countRequests 263 stat.CountCachedRequests += countCachedRequests 264 stat.CountAttackRequests += countAttacks 265 stat.AttackBytes += attackBytes 266 stat.CountWebsocketConnections += countWebsocketConnections 267 } 268 this.locker.Unlock() 269 } 270 271 func (this *BandwidthStatManager) Inspect() { 272 this.locker.Lock() 273 logs.PrintAsJSON(this.m) 274 this.locker.Unlock() 275 } 276 277 func (this *BandwidthStatManager) Map() map[int64]int64 /** serverId => max bytes **/ { 278 this.locker.Lock() 279 defer this.locker.Unlock() 280 281 var m = map[int64]int64{} 282 for _, v := range this.m { 283 m[v.ServerId] = v.MaxBytes / bandwidthTimestampDelim 284 } 285 286 return m 287 } 288 289 // Save 保存到本地磁盘 290 func (this *BandwidthStatManager) Save() error { 291 this.locker.Lock() 292 defer this.locker.Unlock() 293 294 if len(this.m) == 0 { 295 return nil 296 } 297 298 data, err := json.Marshal(this.m) 299 if err != nil { 300 return err 301 } 302 303 _ = os.Remove(this.cacheFile) 304 return os.WriteFile(this.cacheFile, data, 0666) 305 } 306 307 // Cancel 取消上传 308 func (this *BandwidthStatManager) Cancel() { 309 this.ticker.Stop() 310 } 311 312 // 从本地缓存文件中恢复数据 313 func (this *BandwidthStatManager) recover() { 314 cacheData, err := os.ReadFile(this.cacheFile) 315 if err == nil { 316 var m = map[string]*BandwidthStat{} 317 err = json.Unmarshal(cacheData, &m) 318 if err == nil && len(m) > 0 { 319 var lastTime = "" 320 for _, stat := range m { 321 if stat.Day != fasttime.Now().Ymd() { 322 continue 323 } 324 if len(lastTime) == 0 || stat.TimeAt > lastTime { 325 lastTime = stat.TimeAt 326 } 327 } 328 if len(lastTime) > 0 { 329 var availableTime = timeutil.FormatTime("Hi", (time.Now().Unix()-300) /** 只保留5分钟的 **/ /300*300) // 300s = 5 minutes 330 if lastTime >= availableTime { 331 this.m = m 332 this.lastTime = lastTime 333 } 334 } 335 } 336 _ = os.Remove(this.cacheFile) 337 } 338 }