diff --git a/.gitignore b/.gitignore index 5e6607e..39c1910 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,11 @@ *.exe *.log +*.exe~ +*.swp +*.swo +*~ +.env +.idea/ +.vscode/ +.DS_Store +Thumbs.db diff --git a/README.md b/README.md index dce587f..0c1a013 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,13 @@ # autoPriority -Automatically sets high CPU priority for memory-heavy processes. +Automatically sets high CPU priority for processes exceeding a memory threshold. + +**Windows only.** ## Build +Requires Go 1.26+. + ``` # Standard build (with console window) go build -o autopriority.exe . @@ -20,18 +24,25 @@ autopriority [flags] | Flag | Default | Description | |-------------|---------------|----------------------------------| -| `-mem` | 536870912 | Memory threshold in bytes | -| `-interval` | 1m0s | Scan interval (min 10s) | +| `-mem` | 536870912 | Memory threshold in bytes (512 MB) | +| `-interval` | 1 minute | Scan interval (min 10s) | | `-dry-run` | false | Log only, don't change priority | -| `-log` | true | Write `autopriority.log` to `%TEMP%` | + +Examples: + +``` +# Run with 1 GB memory threshold, scanning every 30 seconds +autopriority -mem=1073741824 -interval=30s + +# Dry run — log decisions without changing anything +autopriority -dry-run +``` + +Log is always written to `%TEMP%\autopriority.log`. ## Auto-start -Place a shortcut to `autopriority.exe` in: - -``` -shell:startup -``` +Open **Win+R**, type `shell:startup`, and press Enter. Then place a shortcut to `autopriority.exe` in the folder that opens. ## Dependencies diff --git a/go.mod b/go.mod index e938cb5..a9bd465 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module autopriority -go 1.25.0 +go 1.26 diff --git a/go.sum b/go.sum deleted file mode 100644 index e69de29..0000000 diff --git a/main.go b/main.go index 3e133b1..5b71695 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,7 @@ import ( "flag" "fmt" "os" + "os/signal" "path/filepath" "syscall" "time" @@ -20,7 +21,7 @@ const ( PriorityClassNormal = 0x00000020 PriorityClassHigh = 0x00000080 MessageBoxIconWarning = 0x00000030 - ErrorAlreadyExists = 1832 + ErrorAlreadyExists = 183 ) type processEntry32 struct { @@ -78,7 +79,7 @@ func closeH(h syscall.Handle) { func openProc(pid uint32, acc uint32) (syscall.Handle, error) { r, _, e := procOpenProcess.Call(uintptr(acc), 0, uintptr(pid)) if r == 0 { - return 0, e + return 0, fmt.Errorf("OpenProcess(%d) failed: %w", pid, e) } return syscall.Handle(r), nil } @@ -91,7 +92,7 @@ func setPrio(pid uint32, cls uint32) error { defer closeH(h) r, _, e := procSetPriority.Call(uintptr(h), uintptr(cls)) if r == 0 { - return e + return fmt.Errorf("SetPriorityClass(%d) failed: %w", pid, e) } return nil } @@ -104,7 +105,7 @@ func curPrio(pid uint32) (uint32, error) { defer closeH(h) r, _, e := procGetPriority.Call(uintptr(h)) if r == 0 { - return 0, e + return 0, fmt.Errorf("GetPriorityClass(%d) failed: %w", pid, e) } return uint32(r), nil } @@ -117,9 +118,10 @@ func rss(pid uint32) (uint64, error) { defer closeH(h) var m processMemoryCounters + m.CBM = uint32(unsafe.Sizeof(m)) r, _, e := procGetMemInfo.Call(uintptr(h), uintptr(unsafe.Pointer(&m)), uintptr(unsafe.Sizeof(m))) if r == 0 { - return 0, e + return 0, fmt.Errorf("GetProcessMemoryInfo(%d) failed: %w", pid, e) } return uint64(m.WorkingSetSize), nil } @@ -136,14 +138,14 @@ func utf16Trim(p []uint16) string { func allProcs() ([]processInfo, error) { snap, _, e := procCreateSnap.Call(CreateToolhelp32SnapshotProcess, 0) if snap == 0 { - return nil, e + return nil, fmt.Errorf("CreateToolhelp32Snapshot failed: %w", e) } defer closeH(syscall.Handle(snap)) pe := processEntry32{Size: uint32(unsafe.Sizeof(processEntry32{}))} r, _, e := procProcess32First.Call(snap, uintptr(unsafe.Pointer(&pe))) if r == 0 { - return nil, e + return nil, fmt.Errorf("Process32First failed: %w", e) } var out []processInfo @@ -152,9 +154,14 @@ func allProcs() ([]processInfo, error) { if name != "" { out = append(out, processInfo{PID: pe.PID, Name: name}) } + pe.Size = uint32(unsafe.Sizeof(processEntry32{})) r, _, e = procProcess32Next.Call(snap, uintptr(unsafe.Pointer(&pe))) if r == 0 { - break + le, _, _ := procGetLastError.Call() + if le == 18 { + break + } + return nil, fmt.Errorf("Process32Next failed: %w", e) } } return out, nil @@ -174,19 +181,22 @@ func prioName(c uint32) string { } func isAlive(pid uint32) bool { - h, _ := openProc(pid, ProcessQueryInformation) - if h == 0 { + h, err := openProc(pid, ProcessQueryInformation) + if err != nil || h == 0 { return false } closeH(h) return true } -func ensureSingleInstance() { - h, _, _ := procCreateMutex.Call(0, 0, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("Global\\autopriority_v1")))) +func ensureSingleInstance() syscall.Handle { + h, _, err := procCreateMutex.Call(0, 0, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("Global\\autopriority_v1")))) + if h == 0 { + fmt.Fprintf(os.Stderr, "autoPriority: failed to create mutex: %v\n", err) + os.Exit(1) + } le, _, _ := procGetLastError.Call() if uint32(le) == ErrorAlreadyExists { - closeH(syscall.Handle(h)) procMessageBox.Call(0, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("autoPriority is already running."))), uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("autoPriority"))), @@ -194,6 +204,7 @@ func ensureSingleInstance() { ) os.Exit(1) } + return syscall.Handle(h) } func main() { @@ -213,6 +224,7 @@ func main() { os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) if err == nil { logFile = f + defer logFile.Close() } } @@ -226,11 +238,13 @@ func main() { } fmt.Fprintf(logFile, "[%s] ", ts()) fmt.Fprintf(logFile, format+"\n", a...) + logFile.Sync() } logf("autoPriority started (mem=%d bytes, interval=%s, dry-run=%v)", *mem, *interval, *dryRun) - ensureSingleInstance() + mutex := ensureSingleInstance() + defer closeH(mutex) if err := setPrio(uint32(os.Getpid()), PriorityClassIdle); err != nil { logf("warning: could not set own priority to IDLE: %v", err) @@ -240,9 +254,6 @@ func main() { promoted := make(map[uint32]string) - if logFile != nil { - defer logFile.Close() - } defer func() { for pid, name := range promoted { if err := setPrio(pid, PriorityClassNormal); err == nil { @@ -256,6 +267,9 @@ func main() { ticker := time.NewTicker(*interval) defer ticker.Stop() + stop := make(chan os.Signal, 1) + signal.Notify(stop, os.Interrupt, syscall.SIGTERM) + scan := func() { procs, err := allProcs() if err != nil { @@ -311,7 +325,18 @@ func main() { } scan() - for range ticker.C { - scan() + select { + case <-stop: + logf("received shutdown signal") + case <-ticker.C: + for { + scan() + select { + case <-stop: + logf("received shutdown signal") + return + case <-ticker.C: + } + } } }