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 }