idlesleep/main.go

147 lines
3.6 KiB
Go

package main
import (
"log"
"os"
"os/exec"
"time"
"github.com/NVIDIA/go-nvml/pkg/nvml"
"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/disk"
"github.com/shirou/gopsutil/v3/net"
)
const (
checkInterval = 10 * time.Second
monitoringPeriod = 5 * time.Minute
cpuThreshold = 20.0 // percentage
gpuThreshold = 20.0 // percentage
diskThreshold = 5 * 1024 * 1024 // 5 MB/s
networkThreshold = 1 * 1024 * 1024 // 1 MB/s
)
type ResourceUsage struct {
timestamp time.Time
cpuUsage float64
gpuUsage float64
diskIO uint64
networkIO uint64
}
func main() {
// Check if running as root
if os.Geteuid() != 0 {
log.Fatal("This program must be run as root")
}
// Initialize NVML for GPU monitoring
ret := nvml.Init()
if ret != nvml.SUCCESS {
log.Printf("Warning: Could not initialize NVML: %v", ret)
}
defer nvml.Shutdown()
usageHistory := make([]ResourceUsage, 0)
ticker := time.NewTicker(checkInterval)
defer ticker.Stop()
log.Printf("Starting idle monitoring. System will suspend when:\n")
log.Printf("- CPU usage < %.1f%%\n", cpuThreshold)
log.Printf("- GPU usage < %.1f%%\n", gpuThreshold)
log.Printf("- Disk I/O < %.1f MB/s\n", float64(diskThreshold)/(1024*1024))
log.Printf("- Network I/O < %.1f MB/s\n", float64(networkThreshold)/(1024*1024))
log.Printf("Over the last %v\n", monitoringPeriod)
for range ticker.C {
usage := getCurrentUsage()
usageHistory = append(usageHistory, usage)
// Remove entries older than monitoring period
cutoff := time.Now().Add(-monitoringPeriod)
for i, u := range usageHistory {
if u.timestamp.After(cutoff) {
usageHistory = usageHistory[i:]
break
}
}
if len(usageHistory) > 0 && isSystemIdle(usageHistory) {
log.Println("System has been idle for the monitoring period. Suspending...")
if err := suspendSystem(); err != nil {
log.Printf("Failed to suspend system: %v", err)
}
}
}
}
func getCurrentUsage() ResourceUsage {
usage := ResourceUsage{
timestamp: time.Now(),
}
// Get CPU usage
if cpuPercent, err := cpu.Percent(0, false); err == nil && len(cpuPercent) > 0 {
usage.cpuUsage = cpuPercent[0]
}
// Get GPU usage
count, ret := nvml.DeviceGetCount()
if ret == nvml.SUCCESS && count > 0 {
device, ret := nvml.DeviceGetHandleByIndex(0)
if ret == nvml.SUCCESS {
utilization, ret := device.GetUtilizationRates()
if ret == nvml.SUCCESS {
usage.gpuUsage = float64(utilization.Gpu)
}
}
}
// Get disk I/O
if diskStats, err := disk.IOCounters(); err == nil {
var totalIO uint64
for _, stat := range diskStats {
totalIO += stat.ReadBytes + stat.WriteBytes
}
usage.diskIO = totalIO
}
// Get network I/O
if netStats, err := net.IOCounters(false); err == nil && len(netStats) > 0 {
usage.networkIO = netStats[0].BytesSent + netStats[0].BytesRecv
}
return usage
}
func isSystemIdle(history []ResourceUsage) bool {
if len(history) < 2 {
return false
}
var avgCPU, avgGPU float64
samples := len(history)
for _, usage := range history {
avgCPU += usage.cpuUsage
avgGPU += usage.gpuUsage
}
// Calculate I/O rates using first and last samples
duration := history[samples-1].timestamp.Sub(history[0].timestamp).Seconds()
diskIORate := float64(history[samples-1].diskIO-history[0].diskIO) / duration
netIORate := float64(history[samples-1].networkIO-history[0].networkIO) / duration
avgCPU /= float64(samples)
avgGPU /= float64(samples)
return avgCPU < cpuThreshold &&
avgGPU < gpuThreshold &&
diskIORate < float64(diskThreshold) &&
netIORate < float64(networkThreshold)
}
func suspendSystem() error {
cmd := exec.Command("systemctl", "suspend")
return cmd.Run()
}