package client import ( "os/exec" "runtime" "strconv" "strings" "crypto-miner-agent/deploy" ) // ResourcePressure is a mining-specific runtime snapshot sent every heartbeat. // It lets the C2 flag nodes that are thermally or disk-starved BEFORE hashrate // drops — key for the "is this box safe to mine on?" question. type ResourcePressure struct { // CPU frequency (throttle detection) CPUFreqMHz *int `json:"cpu_freq_mhz,omitempty"` CPUMaxMHz *int `json:"cpu_max_mhz,omitempty"` CPUThrottle *bool `json:"cpu_throttle,omitempty"` // true when cur < 80% of max // CPU thermal CPUTempC *int `json:"cpu_temp_c,omitempty"` // Disk (drive hosting the miner binary) DiskFreeGB *float64 `json:"disk_free_gb,omitempty"` DiskTotalGB *float64 `json:"disk_total_gb,omitempty"` DiskFreePct *int `json:"disk_free_pct,omitempty"` // GPU (NVIDIA via nvidia-smi — optional) GPUTempC *int `json:"gpu_temp_c,omitempty"` GPUUsagePct *int `json:"gpu_usage_pct,omitempty"` } // DiskPressure returns true when less than 10 % of disk is free. func (r *ResourcePressure) DiskPressure() bool { return r != nil && r.DiskFreePct != nil && *r.DiskFreePct < 10 } // ThermalPressure returns true when CPU > 85 °C or GPU > 82 °C. func (r *ResourcePressure) ThermalPressure() bool { if r == nil { return false } if r.CPUTempC != nil && *r.CPUTempC > 85 { return true } if r.GPUTempC != nil && *r.GPUTempC > 82 { return true } return false } // Throttled returns true when the CPU is running below 80 % of its rated speed. func (r *ResourcePressure) Throttled() bool { return r != nil && r.CPUThrottle != nil && *r.CPUThrottle } // probeGPU queries nvidia-smi for temperature and utilisation. // Returns (nil, nil) when no NVIDIA GPU is present or nvidia-smi is absent. func probeGPU() (tempC, usagePct *int) { var out []byte var err error if runtime.GOOS == "windows" { out, err = deploy.HiddenOutput( "nvidia-smi", "--query-gpu=temperature.gpu,utilization.gpu", "--format=csv,noheader,nounits", ) } else { out, err = exec.Command( "nvidia-smi", "--query-gpu=temperature.gpu,utilization.gpu", "--format=csv,noheader,nounits", ).Output() } if err != nil { return nil, nil } line := strings.TrimSpace(string(out)) // First GPU only; multi-GPU rigs can extend later. parts := strings.SplitN(line, ",", 2) if len(parts) >= 1 { if n, e := strconv.Atoi(strings.TrimSpace(parts[0])); e == nil { tempC = &n } } if len(parts) >= 2 { clean := strings.TrimSuffix(strings.TrimSpace(parts[1]), " %") if n, e := strconv.Atoi(strings.TrimSpace(clean)); e == nil { usagePct = &n } } return }