github.com/TeaOSLab/EdgeNode@v1.3.8/internal/nodes/node_status_executor.go (about)

     1  package nodes
     2  
     3  import (
     4  	"encoding/json"
     5  	"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
     6  	"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
     7  	"github.com/TeaOSLab/EdgeNode/internal/caches"
     8  	teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
     9  	"github.com/TeaOSLab/EdgeNode/internal/events"
    10  	"github.com/TeaOSLab/EdgeNode/internal/firewalls"
    11  	"github.com/TeaOSLab/EdgeNode/internal/monitor"
    12  	"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
    13  	"github.com/TeaOSLab/EdgeNode/internal/rpc"
    14  	"github.com/TeaOSLab/EdgeNode/internal/trackers"
    15  	"github.com/TeaOSLab/EdgeNode/internal/utils"
    16  	fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs"
    17  	"github.com/iwind/TeaGo/lists"
    18  	"github.com/iwind/TeaGo/maps"
    19  	"github.com/shirou/gopsutil/v3/cpu"
    20  	"github.com/shirou/gopsutil/v3/disk"
    21  	"github.com/shirou/gopsutil/v3/net"
    22  	"math"
    23  	"os"
    24  	"runtime"
    25  	"strings"
    26  	"time"
    27  )
    28  
    29  type NodeStatusExecutor struct {
    30  	isFirstTime     bool
    31  	lastUpdatedTime time.Time
    32  
    33  	cpuLogicalCount  int
    34  	cpuPhysicalCount int
    35  
    36  	// 流量统计
    37  	lastIOCounterStat   net.IOCountersStat
    38  	lastUDPInDatagrams  int64
    39  	lastUDPOutDatagrams int64
    40  
    41  	apiCallStat *rpc.CallStat
    42  
    43  	ticker *time.Ticker
    44  }
    45  
    46  func NewNodeStatusExecutor() *NodeStatusExecutor {
    47  	return &NodeStatusExecutor{
    48  		ticker:      time.NewTicker(30 * time.Second),
    49  		apiCallStat: rpc.NewCallStat(10),
    50  
    51  		lastUDPInDatagrams:  -1,
    52  		lastUDPOutDatagrams: -1,
    53  	}
    54  }
    55  
    56  func (this *NodeStatusExecutor) Listen() {
    57  	this.isFirstTime = true
    58  	this.lastUpdatedTime = time.Now()
    59  	this.update()
    60  
    61  	events.OnKey(events.EventQuit, this, func() {
    62  		remotelogs.Println("NODE_STATUS", "quit executor")
    63  		this.ticker.Stop()
    64  	})
    65  
    66  	for range this.ticker.C {
    67  		this.isFirstTime = false
    68  		this.update()
    69  	}
    70  }
    71  
    72  func (this *NodeStatusExecutor) update() {
    73  	if sharedNodeConfig == nil {
    74  		return
    75  	}
    76  
    77  	var tr = trackers.Begin("UPLOAD_NODE_STATUS")
    78  	defer tr.End()
    79  
    80  	var status = &nodeconfigs.NodeStatus{}
    81  	status.BuildVersion = teaconst.Version
    82  	status.BuildVersionCode = utils.VersionToLong(teaconst.Version)
    83  	status.OS = runtime.GOOS
    84  	status.Arch = runtime.GOARCH
    85  	status.ExePath, _ = os.Executable()
    86  	status.ConfigVersion = sharedNodeConfig.Version
    87  	status.IsActive = true
    88  	status.ConnectionCount = sharedListenerManager.TotalActiveConnections()
    89  	status.CacheTotalDiskSize = caches.SharedManager.TotalDiskSize()
    90  	status.CacheTotalMemorySize = caches.SharedManager.TotalMemorySize()
    91  	status.TrafficInBytes = teaconst.InTrafficBytes
    92  	status.TrafficOutBytes = teaconst.OutTrafficBytes
    93  
    94  	apiSuccessPercent, apiAvgCostSeconds := this.apiCallStat.Sum()
    95  	status.APISuccessPercent = apiSuccessPercent
    96  	status.APIAvgCostSeconds = apiAvgCostSeconds
    97  
    98  	var localFirewall = firewalls.Firewall()
    99  	if localFirewall != nil && !localFirewall.IsMock() {
   100  		status.LocalFirewallName = localFirewall.Name()
   101  	}
   102  
   103  	// 记录监控数据
   104  	monitor.SharedValueQueue.Add(nodeconfigs.NodeValueItemConnections, maps.Map{
   105  		"total": status.ConnectionCount,
   106  	})
   107  
   108  	hostname, _ := os.Hostname()
   109  	status.Hostname = hostname
   110  
   111  	var cpuTR = tr.Begin("cpu")
   112  	this.updateCPU(status)
   113  	cpuTR.End()
   114  
   115  	var memTR = tr.Begin("memory")
   116  	this.updateMem(status)
   117  	memTR.End()
   118  
   119  	var loadTR = tr.Begin("load")
   120  	this.updateLoad(status)
   121  	loadTR.End()
   122  
   123  	var diskTR = tr.Begin("disk")
   124  	this.updateDisk(status)
   125  	diskTR.End()
   126  
   127  	var cacheSpaceTR = tr.Begin("cache space")
   128  	this.updateCacheSpace(status)
   129  	cacheSpaceTR.End()
   130  
   131  	this.updateAllTraffic(status)
   132  
   133  	// 修改更新时间
   134  	this.lastUpdatedTime = time.Now()
   135  
   136  	status.UpdatedAt = time.Now().Unix()
   137  	status.Timestamp = status.UpdatedAt
   138  
   139  	//  发送数据
   140  	jsonData, err := json.Marshal(status)
   141  	if err != nil {
   142  		remotelogs.Error("NODE_STATUS", "serial NodeStatus fail: "+err.Error())
   143  		return
   144  	}
   145  	rpcClient, err := rpc.SharedRPC()
   146  	if err != nil {
   147  		remotelogs.Error("NODE_STATUS", "failed to open rpc: "+err.Error())
   148  		return
   149  	}
   150  
   151  	var before = time.Now()
   152  	_, err = rpcClient.NodeRPC.UpdateNodeStatus(rpcClient.Context(), &pb.UpdateNodeStatusRequest{
   153  		StatusJSON: jsonData,
   154  	})
   155  	var costSeconds = time.Since(before).Seconds()
   156  	this.apiCallStat.Add(err == nil, costSeconds)
   157  	if err != nil {
   158  		if rpc.IsConnError(err) {
   159  			remotelogs.Warn("NODE_STATUS", "rpc UpdateNodeStatus() failed: "+err.Error())
   160  		} else {
   161  			remotelogs.Error("NODE_STATUS", "rpc UpdateNodeStatus() failed: "+err.Error())
   162  		}
   163  		return
   164  	}
   165  }
   166  
   167  // 更新CPU
   168  func (this *NodeStatusExecutor) updateCPU(status *nodeconfigs.NodeStatus) {
   169  	var duration = time.Duration(0)
   170  	if this.isFirstTime {
   171  		duration = 100 * time.Millisecond
   172  	}
   173  	percents, err := cpu.Percent(duration, false)
   174  	if err != nil {
   175  		status.Error = "cpu.Percent(): " + err.Error()
   176  		return
   177  	}
   178  	if len(percents) == 0 {
   179  		return
   180  	}
   181  	status.CPUUsage = percents[0] / 100
   182  
   183  	// 记录监控数据
   184  	monitor.SharedValueQueue.Add(nodeconfigs.NodeValueItemCPU, maps.Map{
   185  		"usage": status.CPUUsage,
   186  		"cores": runtime.NumCPU(),
   187  	})
   188  
   189  	if this.cpuLogicalCount == 0 && this.cpuPhysicalCount == 0 {
   190  		status.CPULogicalCount, err = cpu.Counts(true)
   191  		if err != nil {
   192  			status.Error = "cpu.Counts(): " + err.Error()
   193  			return
   194  		}
   195  		status.CPUPhysicalCount, err = cpu.Counts(false)
   196  		if err != nil {
   197  			status.Error = "cpu.Counts(): " + err.Error()
   198  			return
   199  		}
   200  		this.cpuLogicalCount = status.CPULogicalCount
   201  		this.cpuPhysicalCount = status.CPUPhysicalCount
   202  	} else {
   203  		status.CPULogicalCount = this.cpuLogicalCount
   204  		status.CPUPhysicalCount = this.cpuPhysicalCount
   205  	}
   206  }
   207  
   208  // 更新硬盘
   209  func (this *NodeStatusExecutor) updateDisk(status *nodeconfigs.NodeStatus) {
   210  	status.DiskWritingSpeedMB = int(fsutils.DiskSpeedMB)
   211  
   212  	partitions, err := disk.Partitions(false)
   213  	if err != nil {
   214  		remotelogs.Error("NODE_STATUS", err.Error())
   215  		return
   216  	}
   217  	lists.Sort(partitions, func(i int, j int) bool {
   218  		p1 := partitions[i]
   219  		p2 := partitions[j]
   220  		return p1.Mountpoint > p2.Mountpoint
   221  	})
   222  
   223  	// 当前TeaWeb所在的fs
   224  	var rootFS = ""
   225  	var rootTotal = uint64(0)
   226  	var totalUsed = uint64(0)
   227  	if lists.ContainsString([]string{"darwin", "linux", "freebsd"}, runtime.GOOS) {
   228  		for _, p := range partitions {
   229  			if p.Mountpoint == "/" {
   230  				rootFS = p.Fstype
   231  				usage, _ := disk.Usage(p.Mountpoint)
   232  				if usage != nil {
   233  					rootTotal = usage.Total
   234  					totalUsed = usage.Used
   235  				}
   236  				break
   237  			}
   238  		}
   239  	}
   240  
   241  	var total = rootTotal
   242  	var maxUsage = float64(0)
   243  	for _, partition := range partitions {
   244  		if runtime.GOOS != "windows" && !strings.Contains(partition.Device, "/") && !strings.Contains(partition.Device, "\\") {
   245  			continue
   246  		}
   247  
   248  		// 跳过不同fs的
   249  		if len(rootFS) > 0 && rootFS != partition.Fstype {
   250  			continue
   251  		}
   252  
   253  		usage, err := disk.Usage(partition.Mountpoint)
   254  		if err != nil {
   255  			continue
   256  		}
   257  
   258  		if partition.Mountpoint != "/" && (usage.Total != rootTotal || total == 0) {
   259  			total += usage.Total
   260  			totalUsed += usage.Used
   261  			if usage.UsedPercent >= maxUsage {
   262  				maxUsage = usage.UsedPercent
   263  				status.DiskMaxUsagePartition = partition.Mountpoint
   264  			}
   265  		}
   266  	}
   267  	status.DiskTotal = total
   268  	if total > 0 {
   269  		status.DiskUsage = float64(totalUsed) / float64(total)
   270  	}
   271  	status.DiskMaxUsage = maxUsage / 100
   272  
   273  	// 记录监控数据
   274  	monitor.SharedValueQueue.Add(nodeconfigs.NodeValueItemDisk, maps.Map{
   275  		"total":    status.DiskTotal,
   276  		"usage":    status.DiskUsage,
   277  		"maxUsage": status.DiskMaxUsage,
   278  	})
   279  }
   280  
   281  // 缓存空间
   282  func (this *NodeStatusExecutor) updateCacheSpace(status *nodeconfigs.NodeStatus) {
   283  	var result = []maps.Map{}
   284  	var cachePaths = caches.SharedManager.FindAllCachePaths()
   285  	for _, path := range cachePaths {
   286  		stat, err := fsutils.StatDevice(path)
   287  		if err != nil {
   288  			return
   289  		}
   290  		result = append(result, maps.Map{
   291  			"path":  path,
   292  			"total": stat.TotalSize(),
   293  			"avail": stat.FreeSize(),
   294  			"used":  stat.UsedSize(),
   295  		})
   296  	}
   297  	monitor.SharedValueQueue.Add(nodeconfigs.NodeValueItemCacheDir, maps.Map{
   298  		"dirs": result,
   299  	})
   300  }
   301  
   302  // 流量
   303  func (this *NodeStatusExecutor) updateAllTraffic(status *nodeconfigs.NodeStatus) {
   304  	trafficCounters, err := net.IOCounters(true)
   305  	if err != nil {
   306  		remotelogs.Warn("NODE_STATUS_EXECUTOR", err.Error())
   307  		return
   308  	}
   309  
   310  	var allCounter = net.IOCountersStat{}
   311  	for _, counter := range trafficCounters {
   312  		// 跳过lo
   313  		if counter.Name == "lo" {
   314  			continue
   315  		}
   316  		allCounter.BytesRecv += counter.BytesRecv
   317  		allCounter.BytesSent += counter.BytesSent
   318  	}
   319  	if allCounter.BytesSent == 0 && allCounter.BytesRecv == 0 {
   320  		return
   321  	}
   322  
   323  	if this.lastIOCounterStat.BytesSent > 0 {
   324  		// 记录监控数据
   325  		if allCounter.BytesSent >= this.lastIOCounterStat.BytesSent && allCounter.BytesRecv >= this.lastIOCounterStat.BytesRecv {
   326  			var costSeconds = int(math.Ceil(time.Since(this.lastUpdatedTime).Seconds()))
   327  			if costSeconds > 0 {
   328  				var bytesSent = allCounter.BytesSent - this.lastIOCounterStat.BytesSent
   329  				var bytesRecv = allCounter.BytesRecv - this.lastIOCounterStat.BytesRecv
   330  
   331  				// UDP
   332  				var udpInDatagrams int64 = 0
   333  				var udpOutDatagrams int64 = 0
   334  				protoStats, protoErr := net.ProtoCounters([]string{"udp"})
   335  				if protoErr == nil {
   336  					for _, protoStat := range protoStats {
   337  						if protoStat.Protocol == "udp" {
   338  							udpInDatagrams = protoStat.Stats["InDatagrams"]
   339  							udpOutDatagrams = protoStat.Stats["OutDatagrams"]
   340  							if udpInDatagrams < 0 {
   341  								udpInDatagrams = 0
   342  							}
   343  							if udpOutDatagrams < 0 {
   344  								udpOutDatagrams = 0
   345  							}
   346  						}
   347  					}
   348  				}
   349  
   350  				var avgUDPInDatagrams int64 = 0
   351  				var avgUDPOutDatagrams int64 = 0
   352  				if this.lastUDPInDatagrams >= 0 && this.lastUDPOutDatagrams >= 0 {
   353  					avgUDPInDatagrams = (udpInDatagrams - this.lastUDPInDatagrams) / int64(costSeconds)
   354  					avgUDPOutDatagrams = (udpOutDatagrams - this.lastUDPOutDatagrams) / int64(costSeconds)
   355  					if avgUDPInDatagrams < 0 {
   356  						avgUDPInDatagrams = 0
   357  					}
   358  					if avgUDPOutDatagrams < 0 {
   359  						avgUDPOutDatagrams = 0
   360  					}
   361  				}
   362  
   363  				this.lastUDPInDatagrams = udpInDatagrams
   364  				this.lastUDPOutDatagrams = udpOutDatagrams
   365  
   366  				monitor.SharedValueQueue.Add(nodeconfigs.NodeValueItemAllTraffic, maps.Map{
   367  					"inBytes":     bytesRecv,
   368  					"outBytes":    bytesSent,
   369  					"avgInBytes":  bytesRecv / uint64(costSeconds),
   370  					"avgOutBytes": bytesSent / uint64(costSeconds),
   371  
   372  					"avgUDPInDatagrams":  avgUDPInDatagrams,
   373  					"avgUDPOutDatagrams": avgUDPOutDatagrams,
   374  				})
   375  			}
   376  		}
   377  	}
   378  	this.lastIOCounterStat = allCounter
   379  }