package protocol import ( "bufio" "os" "runtime" "strconv" "strings" "sync" ) var cpuSampleMu sync.Mutex var previousCPUSample struct{ total, idle uint64 } // CollectTelemetry reads Linux host utilization without adding a platform-specific dependency. func CollectTelemetry() WorkerTelemetry { var result WorkerTelemetry if runtime.GOOS != "linux" { return result } if data, err := os.ReadFile("/proc/stat"); err == nil { fields := strings.Fields(strings.SplitN(string(data), "\n", 2)[0]) if len(fields) >= 5 { var total, idle uint64 for index, field := range fields[1:] { value, parseErr := strconv.ParseUint(field, 10, 64) if parseErr != nil { break } total += value if index == 3 || index == 4 { idle += value } } cpuSampleMu.Lock() if previousCPUSample.total > 0 && total > previousCPUSample.total { totalDelta := total - previousCPUSample.total idleDelta := idle - previousCPUSample.idle if idleDelta > totalDelta { idleDelta = totalDelta } if totalDelta > 0 { result.CPUPercent = float64(totalDelta-idleDelta) * 100 / float64(totalDelta) } } previousCPUSample.total, previousCPUSample.idle = total, idle cpuSampleMu.Unlock() } } if file, err := os.Open("/proc/meminfo"); err == nil { defer file.Close() var totalKB, availableKB uint64 scanner := bufio.NewScanner(file) for scanner.Scan() { fields := strings.Fields(scanner.Text()) if len(fields) < 2 { continue } value, parseErr := strconv.ParseUint(fields[1], 10, 64) if parseErr != nil { continue } switch strings.TrimSuffix(fields[0], ":") { case "MemTotal": totalKB = value case "MemAvailable": availableKB = value } } if totalKB > 0 { usedKB := totalKB - availableKB result.MemoryTotalMB = int64(totalKB / 1024) result.MemoryUsedMB = int64(usedKB / 1024) result.MemoryPercent = float64(usedKB) * 100 / float64(totalKB) } } usedMB, totalMB, diskPct := collectDiskUsage() result.DiskUsedMB = usedMB result.DiskTotalMB = totalMB result.DiskPercent = diskPct return result }