rewrite: single file, zero deps, Windows API, logging, CLI flags
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
*.exe
|
||||||
|
*.log
|
||||||
@@ -1,3 +1,38 @@
|
|||||||
# autoPriority
|
# autoPriority
|
||||||
|
|
||||||
A program that automatically sets a high priority for game processes
|
Automatically sets high CPU priority for memory-heavy processes.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```
|
||||||
|
# Standard build (with console window)
|
||||||
|
go build -o autopriority.exe .
|
||||||
|
|
||||||
|
# Background build (no console, minimal size)
|
||||||
|
go build -ldflags="-H windowsgui -s -w" -o autopriority.exe .
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
autopriority [flags]
|
||||||
|
```
|
||||||
|
|
||||||
|
| Flag | Default | Description |
|
||||||
|
|-------------|---------------|----------------------------------|
|
||||||
|
| `-mem` | 536870912 | Memory threshold in bytes |
|
||||||
|
| `-interval` | 1m0s | Scan interval (min 10s) |
|
||||||
|
| `-dry-run` | false | Log only, don't change priority |
|
||||||
|
| `-log` | true | Write `autopriority.log` file |
|
||||||
|
|
||||||
|
## Auto-start
|
||||||
|
|
||||||
|
Place a shortcut to `autopriority.exe` in:
|
||||||
|
|
||||||
|
```
|
||||||
|
shell:startup
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
None. Uses only Windows API (kernel32, psapi) via raw syscall.
|
||||||
|
|||||||
@@ -1,13 +1,3 @@
|
|||||||
module autoPriority
|
module autopriority
|
||||||
|
|
||||||
go 1.19
|
go 1.24
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
|
||||||
github.com/postfinance/single v0.0.2 // indirect
|
|
||||||
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
|
|
||||||
github.com/tklauser/go-sysconf v0.3.11 // indirect
|
|
||||||
github.com/tklauser/numcpus v0.6.0 // indirect
|
|
||||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
|
||||||
golang.org/x/sys v0.4.0
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
github.com/davecgh/go-spew v1.1.0/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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/postfinance/single v0.0.2 h1:YLjLxxeGDnsW93oK4CxxOFSVOOiBi1OyoK4ZTl5biJw=
|
|
||||||
github.com/postfinance/single v0.0.2/go.mod h1:OYWUsdMIZK9eQyZYpAsMHN+j6+jXJ6RFUNqIWH0oC5U=
|
|
||||||
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
|
|
||||||
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
|
|
||||||
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
|
|
||||||
github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
|
|
||||||
github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
|
|
||||||
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
|
||||||
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
|
||||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
|
|
||||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
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=
|
|
||||||
@@ -0,0 +1,262 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
k32 = syscall.NewLazyDLL("kernel32.dll")
|
||||||
|
ps = syscall.NewLazyDLL("psapi.dll")
|
||||||
|
|
||||||
|
pCreateSnap = k32.NewProc("CreateToolhelp32Snapshot")
|
||||||
|
pProcess32First = k32.NewProc("Process32FirstW")
|
||||||
|
pProcess32Next = k32.NewProc("Process32NextW")
|
||||||
|
pGetProcMemInfo = ps.NewProc("GetProcessMemoryInfo")
|
||||||
|
pSetPriority = k32.NewProc("SetPriorityClass")
|
||||||
|
pGetPriority = k32.NewProc("GetPriorityClass")
|
||||||
|
pOpenProcess = k32.NewProc("OpenProcess")
|
||||||
|
pCloseHandle = k32.NewProc("CloseHandle")
|
||||||
|
pCreateMutex = k32.NewProc("CreateMutexW")
|
||||||
|
pMessageBox = k32.NewProc("MessageBoxW")
|
||||||
|
pGetLastError = k32.NewProc("GetLastError")
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
SNAPPROCESS = 0x00000002
|
||||||
|
SET_INFO = 0x0200
|
||||||
|
QUERY_INFO = 0x0400
|
||||||
|
IDLE_PRIO = 0x00000040
|
||||||
|
NORMAL_PRIO = 0x00000020
|
||||||
|
HIGH_PRIO = 0x00000080
|
||||||
|
EXISTING = 1832
|
||||||
|
)
|
||||||
|
|
||||||
|
type pe32 struct {
|
||||||
|
Size uint32
|
||||||
|
CntUsage uint32
|
||||||
|
PID uint32
|
||||||
|
HeapID uintptr
|
||||||
|
ModuleID uint32
|
||||||
|
Threads uint32
|
||||||
|
ParentPID uint32
|
||||||
|
PrioClass int32
|
||||||
|
Flags uint32
|
||||||
|
ExeFile [260]uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
type pmc struct {
|
||||||
|
CBM uint32
|
||||||
|
PageFaults uint32
|
||||||
|
PeakWS uintptr
|
||||||
|
WorkingSet uintptr
|
||||||
|
QuotaPeakPG uintptr
|
||||||
|
QuotaPG uintptr
|
||||||
|
QuotaPeakPM uintptr
|
||||||
|
QuotaPM uintptr
|
||||||
|
PeakPF uintptr
|
||||||
|
Pagefile uintptr
|
||||||
|
Reserved [2]uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
type procInfo struct {
|
||||||
|
PID uint32
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func wide(s string) *uint16 {
|
||||||
|
return syscall.StringToUTF16Ptr(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func closeH(h syscall.Handle) {
|
||||||
|
_, _, _ = pCloseHandle.Call(uintptr(h))
|
||||||
|
}
|
||||||
|
|
||||||
|
func openProc(pid uint32, acc uint32) syscall.Handle {
|
||||||
|
r, _, _ := pOpenProcess.Call(uintptr(acc), 0, uintptr(pid))
|
||||||
|
return syscall.Handle(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setPrio(pid uint32, cls uint32) bool {
|
||||||
|
h := openProc(pid, SET_INFO)
|
||||||
|
if h == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer closeH(h)
|
||||||
|
r, _, _ := pSetPriority.Call(uintptr(h), uintptr(cls))
|
||||||
|
return r != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func curPrio(pid uint32) uint32 {
|
||||||
|
h := openProc(pid, QUERY_INFO)
|
||||||
|
if h == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
defer closeH(h)
|
||||||
|
r, _, _ := pGetPriority.Call(uintptr(h))
|
||||||
|
return uint32(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func rss(pid uint32) uint64 {
|
||||||
|
h := openProc(pid, QUERY_INFO)
|
||||||
|
if h == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
defer closeH(h)
|
||||||
|
|
||||||
|
var m pmc
|
||||||
|
r, _, _ := pGetProcMemInfo.Call(uintptr(h), uintptr(unsafe.Pointer(&m)), uintptr(unsafe.Sizeof(m)))
|
||||||
|
if r == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return uint64(m.WorkingSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
func allProcs() []procInfo {
|
||||||
|
var out []procInfo
|
||||||
|
pe := pe32{Size: uint32(unsafe.Sizeof(pe32{}))}
|
||||||
|
snap, _, _ := pCreateSnap.Call(SNAPPROCESS, 0)
|
||||||
|
if snap == 0 {
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
defer closeH(syscall.Handle(snap))
|
||||||
|
|
||||||
|
r, _, _ := pProcess32First.Call(snap, uintptr(unsafe.Pointer(&pe)))
|
||||||
|
if r != 0 {
|
||||||
|
for {
|
||||||
|
out = append(out, procInfo{
|
||||||
|
PID: pe.PID,
|
||||||
|
Name: syscall.UTF16ToString(pe.ExeFile[:]),
|
||||||
|
})
|
||||||
|
r, _, _ := pProcess32Next.Call(snap, uintptr(unsafe.Pointer(&pe)))
|
||||||
|
if r == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func prioName(c uint32) string {
|
||||||
|
switch c {
|
||||||
|
case IDLE_PRIO:
|
||||||
|
return "IDLE"
|
||||||
|
case NORMAL_PRIO:
|
||||||
|
return "NORMAL"
|
||||||
|
case HIGH_PRIO:
|
||||||
|
return "HIGH"
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("0x%X", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
mem := flag.Uint64("mem", 512*1024*1024, "memory threshold in bytes")
|
||||||
|
interval := flag.Duration("interval", time.Minute, "scan interval")
|
||||||
|
dryRun := flag.Bool("dry-run", false, "log only, do not change priorities")
|
||||||
|
doLog := flag.Bool("log", true, "write log file next to executable")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if *interval < 10*time.Second {
|
||||||
|
*interval = 10 * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
|
var logFile *os.File
|
||||||
|
if *doLog {
|
||||||
|
exePath, _ := os.Executable()
|
||||||
|
f, err := os.OpenFile(filepath.Join(filepath.Dir(exePath), "autopriority.log"),
|
||||||
|
os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
|
||||||
|
if err == nil {
|
||||||
|
logFile = f
|
||||||
|
defer logFile.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ts := func() string {
|
||||||
|
return time.Now().Format("02.01.2006 15:04:05")
|
||||||
|
}
|
||||||
|
|
||||||
|
logf := func(format string, a ...any) {
|
||||||
|
if logFile == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Fprintf(logFile, "[%s] ", ts())
|
||||||
|
fmt.Fprintf(logFile, format+"\n", a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
logf("autoPriority started (mem=%d bytes, interval=%s, dry-run=%v)", *mem, *interval, *dryRun)
|
||||||
|
|
||||||
|
// Single instance
|
||||||
|
mh, _, _ := pCreateMutex.Call(0, 0, uintptr(unsafe.Pointer(wide("Global\\autopriority_v1"))))
|
||||||
|
lastErr, _, _ := pGetLastError.Call()
|
||||||
|
if lastErr == uintptr(EXISTING) {
|
||||||
|
closeH(syscall.Handle(mh))
|
||||||
|
pMessageBox.Call(0,
|
||||||
|
uintptr(unsafe.Pointer(wide("autoPriority is already running."))),
|
||||||
|
uintptr(unsafe.Pointer(wide("autoPriority"))),
|
||||||
|
0x30,
|
||||||
|
)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lower own priority
|
||||||
|
pSetPriority.Call(uintptr(syscall.Handle(^uintptr(0))), IDLE_PRIO)
|
||||||
|
logf("own priority set to IDLE")
|
||||||
|
|
||||||
|
// promoted: PID → name (for restore logging)
|
||||||
|
promoted := make(map[uint32]string)
|
||||||
|
|
||||||
|
// Restore on exit
|
||||||
|
defer func() {
|
||||||
|
for pid, name := range promoted {
|
||||||
|
if setPrio(pid, NORMAL_PRIO) {
|
||||||
|
logf("RESTORE %s (PID %d) → NORMAL", name, pid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logf("autoPriority stopped")
|
||||||
|
}()
|
||||||
|
|
||||||
|
myPID := uint32(os.Getpid())
|
||||||
|
ticker := time.NewTicker(*interval)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
scan := func() {
|
||||||
|
for _, p := range allProcs() {
|
||||||
|
if p.PID == myPID || p.PID == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := promoted[p.PID]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if rss(p.PID) < *mem {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
orig := curPrio(p.PID)
|
||||||
|
|
||||||
|
if *dryRun {
|
||||||
|
logf("[DRY-RUN] %s (PID %d) RSS=%dMB, priority=%s — would set HIGH",
|
||||||
|
p.Name, p.PID, rss(p.PID)/1024/1024, prioName(orig))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if setPrio(p.PID, HIGH_PRIO) {
|
||||||
|
promoted[p.PID] = p.Name
|
||||||
|
logf("PROMOTE %s (PID %d) RSS=%dMB, %s → HIGH",
|
||||||
|
p.Name, p.PID, rss(p.PID)/1024/1024, prioName(orig))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scan()
|
||||||
|
for range ticker.C {
|
||||||
|
scan()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/postfinance/single"
|
|
||||||
"github.com/shirou/gopsutil/process"
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
memThreshold = 512 * 1024 * 1024
|
|
||||||
selfPriority = windows.IDLE_PRIORITY_CLASS
|
|
||||||
targetPriority = windows.HIGH_PRIORITY_CLASS
|
|
||||||
)
|
|
||||||
|
|
||||||
func setPriority(pid int32, class uint32) error {
|
|
||||||
h, err := windows.OpenProcess(windows.PROCESS_SET_INFORMATION, false, uint32(pid))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("open process %d: %w", pid, err)
|
|
||||||
}
|
|
||||||
defer windows.CloseHandle(h)
|
|
||||||
return windows.SetPriorityClass(h, class)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
lock, err := single.New("autoPriority")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("create lock: %v", err)
|
|
||||||
}
|
|
||||||
if err := lock.Lock(); err != nil {
|
|
||||||
windows.MessageBox(0,
|
|
||||||
windows.StringToUTF16Ptr("The program is already running.\nYou can close it via the Task Manager."),
|
|
||||||
windows.StringToUTF16Ptr("autoPriority"),
|
|
||||||
windows.MB_OK|windows.MB_ICONWARNING,
|
|
||||||
)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
defer lock.Unlock()
|
|
||||||
|
|
||||||
if err := setPriority(int32(os.Getpid()), selfPriority); err != nil {
|
|
||||||
log.Printf("set self priority: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
sig := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(sig, os.Interrupt)
|
|
||||||
|
|
||||||
tk := time.NewTicker(time.Minute)
|
|
||||||
defer tk.Stop()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-sig:
|
|
||||||
log.Println("exit")
|
|
||||||
return
|
|
||||||
case <-tk.C:
|
|
||||||
procs, err := process.Processes()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("list processes: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, p := range procs {
|
|
||||||
m, err := p.MemoryInfo()
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if m.RSS >= memThreshold {
|
|
||||||
if err := setPriority(p.Pid, targetPriority); err != nil {
|
|
||||||
log.Printf("PID %d: %v", p.Pid, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user