160 lines
4.0 KiB
Go
160 lines
4.0 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("- Average CPU usage across all cores < %.1f%%\n", cpuThreshold)
|
|
log.Printf("- Average GPU usage across all GPUs < %.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 across all cores
|
|
if cpuPercent, err := cpu.Percent(0, true); err == nil && len(cpuPercent) > 0 {
|
|
// Calculate average CPU usage across all cores
|
|
var totalCPU float64
|
|
for _, percent := range cpuPercent {
|
|
totalCPU += percent
|
|
}
|
|
usage.cpuUsage = totalCPU / float64(len(cpuPercent))
|
|
}
|
|
|
|
// Get GPU usage across all GPUs
|
|
count, ret := nvml.DeviceGetCount()
|
|
if ret == nvml.SUCCESS && count > 0 {
|
|
var totalGPU float64
|
|
var activeGPUs int
|
|
for i := 0; i < count; i++ {
|
|
device, ret := nvml.DeviceGetHandleByIndex(i)
|
|
if ret == nvml.SUCCESS {
|
|
utilization, ret := device.GetUtilizationRates()
|
|
if ret == nvml.SUCCESS {
|
|
totalGPU += float64(utilization.Gpu)
|
|
activeGPUs++
|
|
}
|
|
}
|
|
}
|
|
if activeGPUs > 0 {
|
|
usage.gpuUsage = totalGPU / float64(activeGPUs)
|
|
}
|
|
}
|
|
|
|
// 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()
|
|
} |