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  }