package main import ( "context" "fmt" "os/exec" "sync" "time" "github.com/shirou/gopsutil/v3/disk" "github.com/shirou/gopsutil/v3/host" "github.com/shirou/gopsutil/v3/net" ) type MetricsSnapshot struct { CollectedAt time.Time Host string Platform string Uptime string Procs uint64 Hardware HardwareInfo HealthScore int // 0-100 system health score HealthScoreMsg string // Brief explanation CPU CPUStatus GPU []GPUStatus Memory MemoryStatus Disks []DiskStatus DiskIO DiskIOStatus Network []NetworkStatus Proxy ProxyStatus Batteries []BatteryStatus Thermal ThermalStatus Sensors []SensorReading Bluetooth []BluetoothDevice TopProcesses []ProcessInfo } type HardwareInfo struct { Model string // MacBook Pro 14-inch, 2021 CPUModel string // Apple M1 Pro / Intel Core i7 TotalRAM string // 16GB DiskSize string // 512GB OSVersion string // macOS Sonoma 14.5 } type DiskIOStatus struct { ReadRate float64 // MB/s WriteRate float64 // MB/s } type ProcessInfo struct { Name string CPU float64 Memory float64 } type CPUStatus struct { Usage float64 PerCore []float64 PerCoreEstimated bool Load1 float64 Load5 float64 Load15 float64 CoreCount int LogicalCPU int PCoreCount int // Performance cores (Apple Silicon) ECoreCount int // Efficiency cores (Apple Silicon) } type GPUStatus struct { Name string Usage float64 MemoryUsed float64 MemoryTotal float64 CoreCount int Note string } type MemoryStatus struct { Used uint64 Total uint64 UsedPercent float64 SwapUsed uint64 SwapTotal uint64 Pressure string // macOS memory pressure: normal/warn/critical } type DiskStatus struct { Mount string Device string Used uint64 Total uint64 UsedPercent float64 Fstype string External bool } type NetworkStatus struct { Name string RxRateMBs float64 TxRateMBs float64 IP string } type ProxyStatus struct { Enabled bool Type string // HTTP, SOCKS, System Host string } type BatteryStatus struct { Percent float64 Status string TimeLeft string Health string CycleCount int } type ThermalStatus struct { CPUTemp float64 GPUTemp float64 FanSpeed int FanCount int } type SensorReading struct { Label string Value float64 Unit string Note string } type BluetoothDevice struct { Name string Connected bool Battery string } type Collector struct { prevNet map[string]net.IOCountersStat lastNetAt time.Time lastBTAt time.Time lastBT []BluetoothDevice lastGPUAt time.Time cachedGPU []GPUStatus prevDiskIO disk.IOCountersStat lastDiskAt time.Time } func NewCollector() *Collector { return &Collector{ prevNet: make(map[string]net.IOCountersStat), } } func (c *Collector) Collect() (MetricsSnapshot, error) { now := time.Now() // Start host info collection early (it's fast but good to parallelize if possible, // but it returns a struct needed for result, so we can just run it here or in parallel) // host.Info is usually cached by gopsutil but let's just call it. hostInfo, _ := host.Info() var ( wg sync.WaitGroup errMu sync.Mutex mergeErr error cpuStats CPUStatus memStats MemoryStatus diskStats []DiskStatus diskIO DiskIOStatus netStats []NetworkStatus proxyStats ProxyStatus batteryStats []BatteryStatus thermalStats ThermalStatus sensorStats []SensorReading gpuStats []GPUStatus btStats []BluetoothDevice topProcs []ProcessInfo ) // Helper to launch concurrent collection collect := func(fn func() error) { wg.Add(1) go func() { defer wg.Done() if err := fn(); err != nil { errMu.Lock() if mergeErr == nil { mergeErr = err } else { mergeErr = fmt.Errorf("%v; %w", mergeErr, err) } errMu.Unlock() } }() } // Launch all independent collection tasks collect(func() (err error) { cpuStats, err = collectCPU(); return }) collect(func() (err error) { memStats, err = collectMemory(); return }) collect(func() (err error) { diskStats, err = collectDisks(); return }) collect(func() (err error) { diskIO = c.collectDiskIO(now); return nil }) collect(func() (err error) { netStats, err = c.collectNetwork(now); return }) collect(func() (err error) { proxyStats = collectProxy(); return nil }) collect(func() (err error) { batteryStats, _ = collectBatteries(); return nil }) collect(func() (err error) { thermalStats = collectThermal(); return nil }) collect(func() (err error) { sensorStats, _ = collectSensors(); return nil }) collect(func() (err error) { gpuStats, err = c.collectGPU(now); return }) collect(func() (err error) { btStats = c.collectBluetooth(now); return nil }) collect(func() (err error) { topProcs = collectTopProcesses(); return nil }) // Wait for all to complete wg.Wait() // Dependent tasks (must run after others) hwInfo := collectHardware(memStats.Total, diskStats) score, scoreMsg := calculateHealthScore(cpuStats, memStats, diskStats, diskIO, thermalStats) return MetricsSnapshot{ CollectedAt: now, Host: hostInfo.Hostname, Platform: fmt.Sprintf("%s %s", hostInfo.Platform, hostInfo.PlatformVersion), Uptime: formatUptime(hostInfo.Uptime), Procs: hostInfo.Procs, Hardware: hwInfo, HealthScore: score, HealthScoreMsg: scoreMsg, CPU: cpuStats, GPU: gpuStats, Memory: memStats, Disks: diskStats, DiskIO: diskIO, Network: netStats, Proxy: proxyStats, Batteries: batteryStats, Thermal: thermalStats, Sensors: sensorStats, Bluetooth: btStats, TopProcesses: topProcs, }, mergeErr } // Utility functions func runCmd(ctx context.Context, name string, args ...string) (string, error) { cmd := exec.CommandContext(ctx, name, args...) output, err := cmd.Output() if err != nil { return "", err } return string(output), nil } func commandExists(name string) bool { if name == "" { return false } defer func() { if r := recover(); r != nil { // If LookPath panics due to permissions or platform quirks, act as if the command is missing. } }() _, err := exec.LookPath(name) return err == nil } // humanBytes is defined in view.go to avoid duplication