package nodes import ( "encoding/json" "log" "os" "runtime" "strconv" "strings" "time" "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeHttpDNS/internal/configs" teaconst "github.com/TeaOSLab/EdgeHttpDNS/internal/const" "github.com/TeaOSLab/EdgeHttpDNS/internal/rpc" "github.com/TeaOSLab/EdgeHttpDNS/internal/utils" ) type StatusManager struct { quitCh <-chan struct{} ticker *time.Ticker } func NewStatusManager(quitCh <-chan struct{}) *StatusManager { return &StatusManager{ quitCh: quitCh, ticker: time.NewTicker(30 * time.Second), } } func (m *StatusManager) Start() { defer m.ticker.Stop() m.update() for { select { case <-m.ticker.C: m.update() case <-m.quitCh: return } } } func (m *StatusManager) update() { status := m.collectStatus() statusJSON, err := json.Marshal(status) if err != nil { log.Println("[HTTPDNS_NODE][status]marshal status failed:", err.Error()) return } rpcClient, err := rpc.SharedRPC() if err != nil { log.Println("[HTTPDNS_NODE][status]rpc unavailable:", err.Error()) return } config, err := configs.SharedAPIConfig() if err != nil { log.Println("[HTTPDNS_NODE][status]load config failed:", err.Error()) return } nodeId, _ := strconv.ParseInt(config.NodeId, 10, 64) _, err = rpcClient.HTTPDNSNodeRPC.UpdateHTTPDNSNodeStatus(rpcClient.Context(), &pb.UpdateHTTPDNSNodeStatusRequest{ NodeId: nodeId, IsUp: true, IsInstalled: true, IsActive: true, StatusJSON: statusJSON, }) if err != nil { log.Println("[HTTPDNS_NODE][status]update status failed:", err.Error()) } } func (m *StatusManager) collectStatus() *nodeconfigs.NodeStatus { now := time.Now().Unix() status := &nodeconfigs.NodeStatus{ BuildVersion: teaconst.Version, BuildVersionCode: utils.VersionToLong(teaconst.Version), ConfigVersion: 0, OS: runtime.GOOS, Arch: runtime.GOARCH, CPULogicalCount: runtime.NumCPU(), CPUPhysicalCount: runtime.NumCPU(), IsActive: true, ConnectionCount: 0, UpdatedAt: now, Timestamp: now, } rpcClient, err := rpc.SharedRPC() if err == nil { total, failed, avgCostSeconds := rpcClient.GetAndResetMetrics() if total > 0 { status.APISuccessPercent = float64(total-failed) * 100.0 / float64(total) status.APIAvgCostSeconds = avgCostSeconds } } hostname, _ := os.Hostname() status.Hostname = hostname exePath, _ := os.Executable() status.ExePath = exePath var memStats runtime.MemStats runtime.ReadMemStats(&memStats) status.MemoryTotal = memStats.Sys if status.MemoryTotal > 0 { status.MemoryUsage = float64(memStats.Alloc) / float64(status.MemoryTotal) } load1m, load5m, load15m := readLoadAvg() status.Load1m = load1m status.Load5m = load5m status.Load15m = load15m return status } func readLoadAvg() (float64, float64, float64) { data, err := os.ReadFile("/proc/loadavg") if err != nil { return 0, 0, 0 } parts := strings.Fields(strings.TrimSpace(string(data))) if len(parts) < 3 { return 0, 0, 0 } load1m, _ := strconv.ParseFloat(parts[0], 64) load5m, _ := strconv.ParseFloat(parts[1], 64) load15m, _ := strconv.ParseFloat(parts[2], 64) return load1m, load5m, load15m }