Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf37dd6974 | ||
|
|
0e8d7333c1 | ||
|
|
da13a6d549 | ||
|
|
ea914101bb | ||
|
|
565b751195 | ||
|
|
b5885791fa | ||
|
|
d4da33ca5e | ||
|
|
33dfd4b4c5 | ||
|
|
b999b7ec4f | ||
|
|
d0cafe1332 | ||
|
|
c3cb8ae847 | ||
|
|
c8648da296 |
+11
@@ -0,0 +1,11 @@
|
|||||||
|
*.exe
|
||||||
|
*.log
|
||||||
|
*.exe~
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
.env
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
@@ -1,3 +1,103 @@
|
|||||||
# autoPriority
|
# autoPriority
|
||||||
|
|
||||||
A program that automatically sets a high priority for game processes
|
Monitors process memory usage on Windows and automatically adjusts CPU priority: promotes memory-heavy processes to HIGH and demotes everything else to NORMAL. Optionally enters **Game Mode** when a process exceeds a higher threshold, boosting it to HIGH and setting all other processes to IDLE.
|
||||||
|
|
||||||
|
**Windows only.**
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
|
||||||
|
### Normal mode
|
||||||
|
|
||||||
|
Every scan interval the program iterates over all running processes:
|
||||||
|
|
||||||
|
| RSS vs `-mem` | Current priority | Action |
|
||||||
|
|---|---|---|
|
||||||
|
| ≥ threshold | anything except HIGH | → HIGH (logged as `PROMOTE`) |
|
||||||
|
| ≥ threshold | already HIGH | skip |
|
||||||
|
| < threshold | ABOVE_NORMAL, HIGH, or REALTIME | → NORMAL (logged as `DEMOTE`) |
|
||||||
|
| < threshold | NORMAL, BELOW_NORMAL, or IDLE | skip |
|
||||||
|
|
||||||
|
### Game mode (`-game-mem`)
|
||||||
|
|
||||||
|
When any process reaches or exceeds the `-game-mem` threshold:
|
||||||
|
|
||||||
|
1. That process → **HIGH** (logged as `GAME`)
|
||||||
|
2. All other processes → **IDLE** (original priorities saved)
|
||||||
|
3. Log: `GAME MODE ON`
|
||||||
|
|
||||||
|
When all such processes close:
|
||||||
|
|
||||||
|
1. All processes that were demoted during game mode are **restored to their original priority** (logged as `RESTORE`)
|
||||||
|
2. `GAME MODE OFF` logged
|
||||||
|
3. Normal mode resumes — priorities recalculated by `-mem` rules
|
||||||
|
|
||||||
|
If `SetPriorityClass` or `OpenProcess` fails for a process (e.g., anti-cheat protection, system processes), the process is added to an in-memory exclusion list and never touched again (logged as `BLOCK`). Blocked processes are still measured for RSS each scan — if a blocked process exceeds `-game-mem`, it triggers game mode without attempting to change its priority. When the process exits, it is automatically removed from the list.
|
||||||
|
|
||||||
|
On shutdown, all processes demoted during game mode are restored to their saved original priority.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
Requires Go 1.26+.
|
||||||
|
|
||||||
|
```
|
||||||
|
# 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` | 512M | Memory threshold (e.g. 512M, 1G, 2048M) |
|
||||||
|
| `-game-mem` | 2G | Game threshold (e.g. 2G, 4G). Must be > `-mem`. 0 = disabled |
|
||||||
|
| `-interval` | 1 minute | Scan interval (min 10s) |
|
||||||
|
| `-dry-run` | false | Log only, don't change priority |
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
```
|
||||||
|
# Run with 1 GB threshold, scanning every 30 seconds
|
||||||
|
autopriority -mem=1G -interval=30s
|
||||||
|
|
||||||
|
# Run with defaults (512M threshold, 2G game threshold)
|
||||||
|
autopriority
|
||||||
|
|
||||||
|
# Game mode: normal threshold 512M, game threshold 4G
|
||||||
|
autopriority -mem=512M -game-mem=4G
|
||||||
|
|
||||||
|
# Dry run — log decisions without changing anything
|
||||||
|
autopriority -dry-run
|
||||||
|
```
|
||||||
|
|
||||||
|
## Log
|
||||||
|
|
||||||
|
Log is always written to `%TEMP%\autopriority.log`. A new log file is created on each run (previous log is deleted).
|
||||||
|
|
||||||
|
Log entries:
|
||||||
|
|
||||||
|
| Prefix | Meaning |
|
||||||
|
|---|---|
|
||||||
|
| `PROMOTE` | Priority raised to HIGH (normal mode) |
|
||||||
|
| `DEMOTE` | Priority lowered to NORMAL (normal mode) |
|
||||||
|
| `GAME DETECT` | Process first detected as exceeding game-mem threshold |
|
||||||
|
| `GAME` | Process set to HIGH or IDLE (game mode) |
|
||||||
|
| `GAME MODE ON` | Game mode activated |
|
||||||
|
| `GAME MODE OFF` | Game mode deactivated — processes restored |
|
||||||
|
| `BLOCK` | OpenProcess or SetPriorityClass failed; process added to exclusion list |
|
||||||
|
| `RESTORE` | Process restored to original/saved priority (game mode exit or shutdown) |
|
||||||
|
| `[DRY-RUN]` | Would change priority (dry-run mode) |
|
||||||
|
|
||||||
|
## Auto-start
|
||||||
|
|
||||||
|
Open **Win+R**, type `shell:startup`, and press Enter. Then place a shortcut to `autopriority.exe` in the folder that opens.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
None. Uses only Windows API (kernel32, psapi) via raw syscall.
|
||||||
|
|||||||
@@ -1,13 +1,3 @@
|
|||||||
module autoPriority
|
module autopriority
|
||||||
|
|
||||||
go 1.19
|
go 1.26
|
||||||
|
|
||||||
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 // indirect
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -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,562 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
CreateToolhelp32SnapshotProcess = 0x00000002
|
||||||
|
ProcessTerminate = 0x00000001
|
||||||
|
ProcessSetInformation = 0x00000200
|
||||||
|
ProcessQueryLimitedInformation = 0x00001000
|
||||||
|
PriorityClassIdle = 0x00000040
|
||||||
|
PriorityClassNormal = 0x00000020
|
||||||
|
PriorityClassHigh = 0x00000080
|
||||||
|
PriorityClassAboveNormal = 0x00008000
|
||||||
|
PriorityClassRealtime = 0x00000100
|
||||||
|
PriorityClassBelowNormal = 0x00004000
|
||||||
|
ErrorNoMoreFiles = 18
|
||||||
|
)
|
||||||
|
|
||||||
|
type processEntry32 struct {
|
||||||
|
Size uint32
|
||||||
|
CntUsage uint32
|
||||||
|
PID uint32
|
||||||
|
DefaultHeapID uintptr
|
||||||
|
ModuleID uint32
|
||||||
|
CntThreads uint32
|
||||||
|
ParentPID uint32
|
||||||
|
PrioClass int32
|
||||||
|
Flags uint32
|
||||||
|
ExeFile [260]uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
type processMemoryCounters struct {
|
||||||
|
CBM uint32
|
||||||
|
PageFaultCount uint32
|
||||||
|
PeakWorkingSetSize uintptr
|
||||||
|
WorkingSetSize uintptr
|
||||||
|
QuotaPeakPagedPoolUsage uintptr
|
||||||
|
QuotaPagedPoolUsage uintptr
|
||||||
|
QuotaPeakNonPagedPoolUsage uintptr
|
||||||
|
QuotaNonPagedPoolUsage uintptr
|
||||||
|
PeakPagefileUsage uintptr
|
||||||
|
PagefileUsage uintptr
|
||||||
|
PrivateUsage uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
type procInfo struct {
|
||||||
|
PID uint32
|
||||||
|
Name string
|
||||||
|
RSS uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
type savedPrio struct {
|
||||||
|
name string
|
||||||
|
prio uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
k32 = syscall.NewLazyDLL("kernel32.dll")
|
||||||
|
ps = syscall.NewLazyDLL("psapi.dll")
|
||||||
|
|
||||||
|
procCreateSnap = k32.NewProc("CreateToolhelp32Snapshot")
|
||||||
|
procProcess32First = k32.NewProc("Process32FirstW")
|
||||||
|
procProcess32Next = k32.NewProc("Process32NextW")
|
||||||
|
procGetMemInfo = ps.NewProc("GetProcessMemoryInfo")
|
||||||
|
procSetPriority = k32.NewProc("SetPriorityClass")
|
||||||
|
procGetPriority = k32.NewProc("GetPriorityClass")
|
||||||
|
procOpenProcess = k32.NewProc("OpenProcess")
|
||||||
|
procCloseHandle = k32.NewProc("CloseHandle")
|
||||||
|
procTerminate = k32.NewProc("TerminateProcess")
|
||||||
|
)
|
||||||
|
|
||||||
|
func closeH(h syscall.Handle) {
|
||||||
|
_, _, _ = procCloseHandle.Call(uintptr(h))
|
||||||
|
}
|
||||||
|
|
||||||
|
func openProc(pid uint32, acc uint32) (syscall.Handle, error) {
|
||||||
|
r, _, e := procOpenProcess.Call(uintptr(acc), 0, uintptr(pid))
|
||||||
|
if r == 0 {
|
||||||
|
return 0, fmt.Errorf("OpenProcess(%d) failed: %w", pid, e)
|
||||||
|
}
|
||||||
|
return syscall.Handle(r), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setPrio(pid uint32, cls uint32) error {
|
||||||
|
h, err := openProc(pid, ProcessSetInformation)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer closeH(h)
|
||||||
|
r, _, e := procSetPriority.Call(uintptr(h), uintptr(cls))
|
||||||
|
if r == 0 {
|
||||||
|
return fmt.Errorf("SetPriorityClass(%d) failed: %w", pid, e)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func utf16Trim(p []uint16) string {
|
||||||
|
for i, v := range p {
|
||||||
|
if v == 0 {
|
||||||
|
return syscall.UTF16ToString(p[:i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return syscall.UTF16ToString(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseMemSize(s string) (uint64, error) {
|
||||||
|
s = strings.TrimSpace(s)
|
||||||
|
if s == "" {
|
||||||
|
return 0, fmt.Errorf("empty value")
|
||||||
|
}
|
||||||
|
var suffix byte
|
||||||
|
switch s[len(s)-1] {
|
||||||
|
case 'k', 'K':
|
||||||
|
suffix = 'K'
|
||||||
|
s = s[:len(s)-1]
|
||||||
|
case 'm', 'M':
|
||||||
|
suffix = 'M'
|
||||||
|
s = s[:len(s)-1]
|
||||||
|
case 'g', 'G':
|
||||||
|
suffix = 'G'
|
||||||
|
s = s[:len(s)-1]
|
||||||
|
}
|
||||||
|
n, err := strconv.ParseUint(s, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
switch suffix {
|
||||||
|
case 'K':
|
||||||
|
return n * 1024, nil
|
||||||
|
case 'M':
|
||||||
|
return n * 1024 * 1024, nil
|
||||||
|
case 'G':
|
||||||
|
return n * 1024 * 1024 * 1024, nil
|
||||||
|
default:
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatMemSize(b uint64) string {
|
||||||
|
if b >= 1024*1024*1024 {
|
||||||
|
return fmt.Sprintf("%.1fGB", float64(b)/(1024*1024*1024))
|
||||||
|
}
|
||||||
|
if b >= 1024*1024 {
|
||||||
|
return fmt.Sprintf("%dMB", b/1024/1024)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%dKB", b/1024)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isAboveNormal(c uint32) bool {
|
||||||
|
return c == PriorityClassAboveNormal ||
|
||||||
|
c == PriorityClassHigh ||
|
||||||
|
c == PriorityClassRealtime
|
||||||
|
}
|
||||||
|
|
||||||
|
func prioName(c uint32) string {
|
||||||
|
switch c {
|
||||||
|
case PriorityClassIdle:
|
||||||
|
return "IDLE"
|
||||||
|
case PriorityClassBelowNormal:
|
||||||
|
return "BELOW_NORMAL"
|
||||||
|
case PriorityClassNormal:
|
||||||
|
return "NORMAL"
|
||||||
|
case PriorityClassAboveNormal:
|
||||||
|
return "ABOVE_NORMAL"
|
||||||
|
case PriorityClassHigh:
|
||||||
|
return "HIGH"
|
||||||
|
case PriorityClassRealtime:
|
||||||
|
return "REALTIME"
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("0x%X", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func allProcs() ([]procInfo, error) {
|
||||||
|
snap, _, e := procCreateSnap.Call(CreateToolhelp32SnapshotProcess, 0)
|
||||||
|
if snap == 0 {
|
||||||
|
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, fmt.Errorf("Process32First failed: %w", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
var out []procInfo
|
||||||
|
for {
|
||||||
|
name := utf16Trim(pe.ExeFile[:])
|
||||||
|
if name != "" {
|
||||||
|
out = append(out, procInfo{PID: pe.PID, Name: name})
|
||||||
|
}
|
||||||
|
pe.Size = uint32(unsafe.Sizeof(processEntry32{}))
|
||||||
|
r, _, e = procProcess32Next.Call(snap, uintptr(unsafe.Pointer(&pe)))
|
||||||
|
if r == 0 {
|
||||||
|
if e == syscall.Errno(ErrorNoMoreFiles) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("Process32Next failed: %w", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func killOtherInstances() {
|
||||||
|
myPID := os.Getpid()
|
||||||
|
procs, err := allProcs()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
myName := filepath.Base(os.Args[0])
|
||||||
|
for _, p := range procs {
|
||||||
|
if p.PID != uint32(myPID) && filepath.Base(p.Name) == myName {
|
||||||
|
h, err := openProc(p.PID, ProcessTerminate)
|
||||||
|
if err == nil {
|
||||||
|
procTerminate.Call(uintptr(h), 0)
|
||||||
|
closeH(h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
memStr := flag.String("mem", "512M", "memory threshold (e.g. 512M, 1G, 2048M)")
|
||||||
|
gameMemStr := flag.String("game-mem", "2G", "game memory threshold (e.g. 2G, 4G). Must be greater than -mem. 0 = disabled.")
|
||||||
|
interval := flag.Duration("interval", time.Minute, "scan interval")
|
||||||
|
dryRun := flag.Bool("dry-run", false, "log only, do not change priorities")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if *interval < 10*time.Second {
|
||||||
|
*interval = 10 * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
|
mem, err := parseMemSize(*memStr)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "autoPriority: invalid -mem value: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
gameMem, err := parseMemSize(*gameMemStr)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "autoPriority: invalid -game-mem value: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if gameMem > 0 && gameMem <= mem {
|
||||||
|
fmt.Fprintf(os.Stderr, "autoPriority: -game-mem must be greater than -mem\n")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
killOtherInstances()
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
|
var logFile *os.File
|
||||||
|
logPath := filepath.Join(os.TempDir(), "autopriority.log")
|
||||||
|
os.Remove(logPath)
|
||||||
|
f, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 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...)
|
||||||
|
logFile.Sync()
|
||||||
|
}
|
||||||
|
|
||||||
|
if gameMem > 0 {
|
||||||
|
logf("autoPriority started (mem=%s, game-mem=%s, interval=%s, dry-run=%v)",
|
||||||
|
formatMemSize(mem), formatMemSize(gameMem), *interval, *dryRun)
|
||||||
|
} else {
|
||||||
|
logf("autoPriority started (mem=%s, interval=%s, dry-run=%v)",
|
||||||
|
formatMemSize(mem), *interval, *dryRun)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := setPrio(uint32(os.Getpid()), PriorityClassIdle); err != nil {
|
||||||
|
logf("warning: could not set own priority to IDLE: %v", err)
|
||||||
|
} else {
|
||||||
|
logf("own priority set to IDLE")
|
||||||
|
}
|
||||||
|
|
||||||
|
blocked := make(map[uint32]string)
|
||||||
|
gameProcs := make(map[uint32]string)
|
||||||
|
gameSaved := make(map[uint32]savedPrio)
|
||||||
|
gameMode := false
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if gameMode {
|
||||||
|
restoreProcs, _ := allProcs()
|
||||||
|
rm := make(map[uint32]string, len(restoreProcs))
|
||||||
|
for _, p := range restoreProcs {
|
||||||
|
rm[p.PID] = p.Name
|
||||||
|
}
|
||||||
|
for pid, sv := range gameSaved {
|
||||||
|
if cur, ok := rm[pid]; !ok || cur != sv.name {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := setPrio(pid, sv.prio); err != nil {
|
||||||
|
logf("RESTORE %s (PID %d) -> %s error: %v", sv.name, pid, prioName(sv.prio), err)
|
||||||
|
} else {
|
||||||
|
logf("RESTORE %s (PID %d) -> %s", sv.name, pid, prioName(sv.prio))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logf("autoPriority stopped")
|
||||||
|
}()
|
||||||
|
|
||||||
|
myPID := uint32(os.Getpid())
|
||||||
|
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 {
|
||||||
|
logf("allProcs error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pidMap := make(map[uint32]string, len(procs))
|
||||||
|
for _, p := range procs {
|
||||||
|
pidMap[p.PID] = p.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
for pid := range blocked {
|
||||||
|
if _, ok := pidMap[pid]; !ok {
|
||||||
|
delete(blocked, pid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for pid, name := range gameProcs {
|
||||||
|
if cur, ok := pidMap[pid]; !ok || cur != name {
|
||||||
|
delete(gameProcs, pid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for pid, sv := range gameSaved {
|
||||||
|
if cur, ok := pidMap[pid]; !ok || cur != sv.name {
|
||||||
|
delete(gameSaved, pid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var list []procInfo
|
||||||
|
for i := range procs {
|
||||||
|
p := &procs[i]
|
||||||
|
if p.PID == myPID || p.PID == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
h, err := openProc(p.PID, ProcessQueryLimitedInformation)
|
||||||
|
if err != nil {
|
||||||
|
if _, ok := blocked[p.PID]; !ok {
|
||||||
|
blocked[p.PID] = p.Name
|
||||||
|
logf("BLOCK %s (PID %d): %v (added to exclusion list)", p.Name, p.PID, err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var m processMemoryCounters
|
||||||
|
m.CBM = uint32(unsafe.Sizeof(m))
|
||||||
|
r, _, e := procGetMemInfo.Call(uintptr(h), uintptr(unsafe.Pointer(&m)), uintptr(unsafe.Sizeof(m)))
|
||||||
|
closeH(h)
|
||||||
|
if r == 0 {
|
||||||
|
if _, ok := blocked[p.PID]; !ok {
|
||||||
|
logf("GetProcessMemoryInfo(%d) error: %v", p.PID, e)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
p.RSS = uint64(m.WorkingSetSize)
|
||||||
|
list = append(list, *p)
|
||||||
|
|
||||||
|
if gameMem > 0 && p.RSS >= gameMem {
|
||||||
|
if _, ok := gameProcs[p.PID]; !ok {
|
||||||
|
gameProcs[p.PID] = p.Name
|
||||||
|
logf("GAME DETECT %s (PID %d) RSS=%s", p.Name, p.PID, formatMemSize(p.RSS))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hasGame := gameMem > 0 && len(gameProcs) > 0
|
||||||
|
|
||||||
|
for i := range list {
|
||||||
|
p := &list[i]
|
||||||
|
_, isBlocked := blocked[p.PID]
|
||||||
|
_, isGame := gameProcs[p.PID]
|
||||||
|
|
||||||
|
if hasGame && isGame {
|
||||||
|
if isBlocked {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if *dryRun {
|
||||||
|
logf("[DRY-RUN] %s (PID %d) RSS=%s — would set HIGH (game)", p.Name, p.PID, formatMemSize(p.RSS))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
h, err := openProc(p.PID, ProcessQueryLimitedInformation)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cur, _, _ := procGetPriority.Call(uintptr(h))
|
||||||
|
closeH(h)
|
||||||
|
if cur != 0 && uint32(cur) == PriorityClassHigh {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := setPrio(p.PID, PriorityClassHigh); err != nil {
|
||||||
|
blocked[p.PID] = p.Name
|
||||||
|
logf("BLOCK %s (PID %d): %v (added to exclusion list)", p.Name, p.PID, err)
|
||||||
|
logf("GAME %s (PID %d) RSS=%s — tracked as game (priority change failed)", p.Name, p.PID, formatMemSize(p.RSS))
|
||||||
|
} else {
|
||||||
|
logf("GAME %s (PID %d) RSS=%s -> HIGH", p.Name, p.PID, formatMemSize(p.RSS))
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasGame {
|
||||||
|
if isBlocked {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if *dryRun {
|
||||||
|
logf("[DRY-RUN] %s (PID %d) RSS=%s — would set IDLE (game)", p.Name, p.PID, formatMemSize(p.RSS))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
h, err := openProc(p.PID, ProcessQueryLimitedInformation)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cur, _, _ := procGetPriority.Call(uintptr(h))
|
||||||
|
closeH(h)
|
||||||
|
if cur == 0 || uint32(cur) == PriorityClassIdle {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := setPrio(p.PID, PriorityClassIdle); err != nil {
|
||||||
|
blocked[p.PID] = p.Name
|
||||||
|
logf("BLOCK %s (PID %d): %v (added to exclusion list)", p.Name, p.PID, err)
|
||||||
|
} else {
|
||||||
|
if _, saved := gameSaved[p.PID]; !saved {
|
||||||
|
gameSaved[p.PID] = savedPrio{name: p.Name, prio: uint32(cur)}
|
||||||
|
}
|
||||||
|
logf("GAME %s (PID %d) RSS=%s, %s -> IDLE", p.Name, p.PID, formatMemSize(p.RSS), prioName(uint32(cur)))
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.RSS < mem {
|
||||||
|
if isBlocked {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
h, err := openProc(p.PID, ProcessQueryLimitedInformation)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cur, _, _ := procGetPriority.Call(uintptr(h))
|
||||||
|
closeH(h)
|
||||||
|
if cur == 0 || !isAboveNormal(uint32(cur)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if *dryRun {
|
||||||
|
logf("[DRY-RUN] %s (PID %d) RSS=%s, priority=%s — would set NORMAL", p.Name, p.PID, formatMemSize(p.RSS), prioName(uint32(cur)))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := setPrio(p.PID, PriorityClassNormal); err != nil {
|
||||||
|
blocked[p.PID] = p.Name
|
||||||
|
logf("BLOCK %s (PID %d): %v (added to exclusion list)", p.Name, p.PID, err)
|
||||||
|
} else {
|
||||||
|
logf("DEMOTE %s (PID %d) RSS=%s, %s -> NORMAL", p.Name, p.PID, formatMemSize(p.RSS), prioName(uint32(cur)))
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if isBlocked {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
h, err := openProc(p.PID, ProcessQueryLimitedInformation)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cur, _, _ := procGetPriority.Call(uintptr(h))
|
||||||
|
closeH(h)
|
||||||
|
if cur != 0 && uint32(cur) == PriorityClassHigh {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
origStr := ""
|
||||||
|
if cur != 0 {
|
||||||
|
origStr = prioName(uint32(cur))
|
||||||
|
}
|
||||||
|
if *dryRun {
|
||||||
|
logf("[DRY-RUN] %s (PID %d) RSS=%s, priority=%s — would set HIGH", p.Name, p.PID, formatMemSize(p.RSS), origStr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := setPrio(p.PID, PriorityClassHigh); err != nil {
|
||||||
|
blocked[p.PID] = p.Name
|
||||||
|
logf("BLOCK %s (PID %d): %v (added to exclusion list)", p.Name, p.PID, err)
|
||||||
|
} else {
|
||||||
|
logf("PROMOTE %s (PID %d) RSS=%s, %s -> HIGH", p.Name, p.PID, formatMemSize(p.RSS), origStr)
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if gameMem > 0 {
|
||||||
|
newHasGame := len(gameProcs) > 0
|
||||||
|
if newHasGame && !gameMode {
|
||||||
|
gameMode = true
|
||||||
|
logf("GAME MODE ON")
|
||||||
|
}
|
||||||
|
if !newHasGame && gameMode {
|
||||||
|
gameMode = false
|
||||||
|
logf("GAME MODE OFF")
|
||||||
|
for pid, sv := range gameSaved {
|
||||||
|
if cur, ok := pidMap[pid]; !ok || cur != sv.name {
|
||||||
|
delete(gameSaved, pid)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if *dryRun {
|
||||||
|
logf("[DRY-RUN] RESTORE %s (PID %d) -> %s", sv.name, pid, prioName(sv.prio))
|
||||||
|
} else {
|
||||||
|
if err := setPrio(pid, sv.prio); err != nil {
|
||||||
|
logf("RESTORE %s (PID %d) -> %s error: %v", sv.name, pid, prioName(sv.prio), err)
|
||||||
|
} else {
|
||||||
|
logf("RESTORE %s (PID %d) -> %s", sv.name, pid, prioName(sv.prio))
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete(gameSaved, pid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scan()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-stop:
|
||||||
|
logf("received shutdown signal")
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
scan()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
/*
|
|
||||||
#include <windows.h>
|
|
||||||
BOOL SetPriority(DWORD pid, int priority) {
|
|
||||||
HANDLE h = OpenProcess(PROCESS_ALL_ACCESS, 0, pid);
|
|
||||||
if (!h) return FALSE;
|
|
||||||
BOOL success = SetPriorityClass(h, priority);
|
|
||||||
CloseHandle(h);
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
import (
|
|
||||||
"github.com/postfinance/single"
|
|
||||||
"github.com/shirou/gopsutil/process"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
func MessageBox(lpText string, lpCaption string, uType int) int {
|
|
||||||
var convertedCaption, _ = syscall.UTF16PtrFromString(lpCaption)
|
|
||||||
var convertedTitle, _ = syscall.UTF16PtrFromString(lpText)
|
|
||||||
returnCode, _, _ := syscall.NewLazyDLL("user32.dll").NewProc("MessageBoxW").Call(
|
|
||||||
uintptr(0),
|
|
||||||
uintptr(unsafe.Pointer(convertedCaption)),
|
|
||||||
uintptr(unsafe.Pointer(convertedTitle)),
|
|
||||||
uintptr(uType),
|
|
||||||
)
|
|
||||||
return int(returnCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
var lockFile, _ = single.New("autoPriority")
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if err := lockFile.Lock(); err != nil {
|
|
||||||
MessageBox("The program is already running", "Only one instance of the program can be run\nYou can close it via the Task Manager", 48)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
C.SetPriority(C.DWORD(os.Getpid()), 0x00000040)
|
|
||||||
for range time.Tick(time.Minute) {
|
|
||||||
processes, _ := process.Processes()
|
|
||||||
for _, proc := range processes {
|
|
||||||
memoryInfo, err := proc.MemoryInfo()
|
|
||||||
if err == nil && memoryInfo.RSS >= (0.5*1024*1024*1024) {
|
|
||||||
C.SetPriority(C.DWORD(proc.Pid), 0x00000080)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
runtime.GC()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user