Initial commit
This commit is contained in:
commit
c06d5dcdd4
25
.gitignore
vendored
Normal file
25
.gitignore
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
idlesleep
|
||||||
|
|
||||||
|
# Test binary, built with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Go workspace file
|
||||||
|
go.work
|
||||||
|
|
||||||
|
# Dependency directories (remove the comment below to include it)
|
||||||
|
# vendor/
|
||||||
|
|
||||||
|
# IDE specific files
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
*.swp
|
||||||
|
*.swo
|
53
README.md
Normal file
53
README.md
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# Idle Sleep Monitor
|
||||||
|
|
||||||
|
This program monitors system resource usage and automatically suspends the system when it detects extended periods of low resource utilization.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Linux system with systemd
|
||||||
|
- NVIDIA GPU with drivers installed
|
||||||
|
- Go 1.21 or later
|
||||||
|
- Root privileges (required for system suspension)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. Clone this repository
|
||||||
|
2. Install dependencies:
|
||||||
|
```bash
|
||||||
|
go mod download
|
||||||
|
```
|
||||||
|
3. Build the program:
|
||||||
|
```bash
|
||||||
|
go build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
The program must be run as root since it needs permissions to suspend the system:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ./idlesleep
|
||||||
|
```
|
||||||
|
|
||||||
|
The program will monitor the following metrics over a 5-minute period:
|
||||||
|
- CPU usage across all cores (threshold: < 20%)
|
||||||
|
- GPU usage (threshold: < 20%)
|
||||||
|
- Disk I/O (threshold: < 5 MB/s)
|
||||||
|
- Network I/O (threshold: < 1 MB/s)
|
||||||
|
|
||||||
|
If all metrics stay below their thresholds for the entire monitoring period, the system will be suspended.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The thresholds are defined as constants in `main.go`. You can modify them by editing the following values:
|
||||||
|
|
||||||
|
```go
|
||||||
|
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
|
||||||
|
)
|
||||||
|
```
|
19
go.mod
Normal file
19
go.mod
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
module idlesleep
|
||||||
|
|
||||||
|
go 1.21
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/NVIDIA/go-nvml v0.12.4-1
|
||||||
|
github.com/shirou/gopsutil/v3 v3.24.2
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||||
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||||
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||||
|
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||||
|
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||||
|
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||||
|
golang.org/x/sys v0.17.0 // indirect
|
||||||
|
)
|
48
go.sum
Normal file
48
go.sum
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
github.com/NVIDIA/go-nvml v0.12.4-1 h1:WKUvqshhWSNTfm47ETRhv0A0zJyr1ncCuHiXwoTrBEc=
|
||||||
|
github.com/NVIDIA/go-nvml v0.12.4-1/go.mod h1:8Llmj+1Rr+9VGGwZuRer5N/aCjxGuR5nPb/9ebBiIEQ=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||||
|
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
|
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||||
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||||
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||||
|
github.com/shirou/gopsutil/v3 v3.24.2 h1:kcR0erMbLg5/3LcInpw0X/rrPSqq4CDPyI6A6ZRC18Y=
|
||||||
|
github.com/shirou/gopsutil/v3 v3.24.2/go.mod h1:tSg/594BcA+8UdQU2XcW803GWYgdtauFFPgJCJKZlVk=
|
||||||
|
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||||
|
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||||
|
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||||
|
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||||
|
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||||
|
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||||
|
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||||
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
147
main.go
Normal file
147
main.go
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
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()
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user