Rewrite game mode: two-pass scan, priority restoration, LimitedInformation access
- Use PROCESS_QUERY_LIMITED_INFORMATION instead of PROCESS_QUERY_INFORMATION to open processes (fixes Access Denied for KeePassXC, HD-Player, etc.) - Separate query handle from set handle: trySetPrio reopens with PROCESS_SET_INFORMATION only when needed - Two-pass scan: pass 1 collects RSS and detects game processes, pass 2 applies priorities with correct hasGame state - Game mode saves original priorities in gameSaved map, restores them on GAME MODE OFF (not just NORMAL) - Demote ALL non-game processes to IDLE in game mode (not just promoted) - 50ms delay between priority changes for smooth transitions - Replace isAlive/procName with pidMap from allProcs snapshot - Replace mutex/MessageBox single-instance with killOtherInstances - Defaults: -mem=512M, -game-mem=4G - Fix double CloseHandle and handle leak in trySetPrio - Update README with new defaults and game mode restoration behavior
This commit is contained in:
@@ -1,23 +1,39 @@
|
|||||||
# autoPriority
|
# autoPriority
|
||||||
|
|
||||||
Monitors process memory usage on Windows and automatically adjusts CPU priority: promotes memory-heavy processes to HIGH and demotes everything else to NORMAL.
|
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.**
|
**Windows only.**
|
||||||
|
|
||||||
## How it works
|
## How it works
|
||||||
|
|
||||||
|
### Normal mode
|
||||||
|
|
||||||
Every scan interval the program iterates over all running processes:
|
Every scan interval the program iterates over all running processes:
|
||||||
|
|
||||||
| RSS vs threshold | Current priority | Action |
|
| RSS vs `-mem` | Current priority | Action |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| ≥ threshold | anything except HIGH | → HIGH (logged as `PROMOTE`) |
|
| ≥ threshold | anything except HIGH | → HIGH (logged as `PROMOTE`) |
|
||||||
| ≥ threshold | already HIGH | skip |
|
| ≥ threshold | already HIGH | skip |
|
||||||
| < threshold | ABOVE_NORMAL, HIGH, or REALTIME | → NORMAL (logged as `DEMOTE`) |
|
| < threshold | ABOVE_NORMAL, HIGH, or REALTIME | → NORMAL (logged as `DEMOTE`) |
|
||||||
| < threshold | NORMAL, BELOW_NORMAL, or IDLE | skip |
|
| < threshold | NORMAL, BELOW_NORMAL, or IDLE | skip |
|
||||||
|
|
||||||
If `SetPriorityClass` fails for a process (e.g., anti-cheat protection), the process is added to an in-memory exclusion list and never touched again (logged as `BLOCK`). When the process exits, it is automatically removed from the list.
|
### Game mode (`-game-mem`)
|
||||||
|
|
||||||
On shutdown, all promoted processes are restored to NORMAL.
|
When any process reaches or exceeds the `-game-mem` threshold:
|
||||||
|
|
||||||
|
1. That process → **HIGH** (logged as `GAME`)
|
||||||
|
2. All other processes → **IDLE**
|
||||||
|
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`). When the process exits, it is automatically removed from the list.
|
||||||
|
|
||||||
|
On shutdown, all processes are restored: game-mode demoted processes to their saved original priority, promoted and game processes to NORMAL.
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
|
|
||||||
@@ -39,15 +55,22 @@ autopriority [flags]
|
|||||||
|
|
||||||
| Flag | Default | Description |
|
| Flag | Default | Description |
|
||||||
|-------------|---------------|----------------------------------|
|
|-------------|---------------|----------------------------------|
|
||||||
| `-mem` | 536870912 | Memory threshold in bytes (512 MB) |
|
| `-mem` | 512M | Memory threshold (e.g. 512M, 1G, 2048M) |
|
||||||
|
| `-game-mem` | 4G | Game threshold (e.g. 3G, 4G). Must be > `-mem`. 0 = disabled |
|
||||||
| `-interval` | 1 minute | Scan interval (min 10s) |
|
| `-interval` | 1 minute | Scan interval (min 10s) |
|
||||||
| `-dry-run` | false | Log only, don't change priority |
|
| `-dry-run` | false | Log only, don't change priority |
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
```
|
```
|
||||||
# Run with 1 GB memory threshold, scanning every 30 seconds
|
# Run with 1 GB threshold, scanning every 30 seconds
|
||||||
autopriority -mem=1073741824 -interval=30s
|
autopriority -mem=1G -interval=30s
|
||||||
|
|
||||||
|
# Run with defaults (512M threshold, 4G game threshold)
|
||||||
|
autopriority
|
||||||
|
|
||||||
|
# Game mode: normal threshold 512M, game threshold 4G
|
||||||
|
autopriority -mem=512M -game-mem=4G
|
||||||
|
|
||||||
# Dry run — log decisions without changing anything
|
# Dry run — log decisions without changing anything
|
||||||
autopriority -dry-run
|
autopriority -dry-run
|
||||||
@@ -61,10 +84,13 @@ Log entries:
|
|||||||
|
|
||||||
| Prefix | Meaning |
|
| Prefix | Meaning |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `PROMOTE` | Priority raised to HIGH |
|
| `PROMOTE` | Priority raised to HIGH (normal mode) |
|
||||||
| `DEMOTE` | Priority lowered to NORMAL |
|
| `DEMOTE` | Priority lowered to NORMAL (normal mode) |
|
||||||
| `BLOCK` | SetPriorityClass failed; process added to exclusion list |
|
| `GAME` | Process set to HIGH or IDLE (game mode) |
|
||||||
| `RESTORE` | Promoted process restored to NORMAL on shutdown |
|
| `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) |
|
||||||
| `SKIP RESTORE` | PID reused by a different process; restore skipped |
|
| `SKIP RESTORE` | PID reused by a different process; restore skipped |
|
||||||
| `[DRY-RUN]` | Would change priority (dry-run mode) |
|
| `[DRY-RUN]` | Would change priority (dry-run mode) |
|
||||||
|
|
||||||
|
|||||||
@@ -3,12 +3,13 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
@@ -16,19 +17,17 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
CreateToolhelp32SnapshotProcess = 0x00000002
|
CreateToolhelp32SnapshotProcess = 0x00000002
|
||||||
|
ProcessTerminate = 0x00000001
|
||||||
ProcessSetInformation = 0x00000200
|
ProcessSetInformation = 0x00000200
|
||||||
ProcessQueryInformation = 0x00000400
|
ProcessQueryInformation = 0x00000400
|
||||||
|
ProcessQueryLimitedInformation = 0x00001000
|
||||||
PriorityClassIdle = 0x00000040
|
PriorityClassIdle = 0x00000040
|
||||||
PriorityClassNormal = 0x00000020
|
PriorityClassNormal = 0x00000020
|
||||||
PriorityClassHigh = 0x00000080
|
PriorityClassHigh = 0x00000080
|
||||||
PriorityClassAboveNormal = 0x00008000
|
PriorityClassAboveNormal = 0x00008000
|
||||||
PriorityClassRealtime = 0x00000100
|
PriorityClassRealtime = 0x00000100
|
||||||
PriorityClassBelowNormal = 0x00004000
|
PriorityClassBelowNormal = 0x00004000
|
||||||
MessageBoxIconWarning = 0x00000030
|
|
||||||
ErrorAlreadyExists = 183
|
|
||||||
ErrorNoMoreFiles = 18
|
ErrorNoMoreFiles = 18
|
||||||
ErrorAccessDenied = 5
|
|
||||||
ErrorInvalidParameter = 87
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type processEntry32 struct {
|
type processEntry32 struct {
|
||||||
@@ -75,8 +74,7 @@ var (
|
|||||||
procGetPriority = k32.NewProc("GetPriorityClass")
|
procGetPriority = k32.NewProc("GetPriorityClass")
|
||||||
procOpenProcess = k32.NewProc("OpenProcess")
|
procOpenProcess = k32.NewProc("OpenProcess")
|
||||||
procCloseHandle = k32.NewProc("CloseHandle")
|
procCloseHandle = k32.NewProc("CloseHandle")
|
||||||
procCreateMutex = k32.NewProc("CreateMutexW")
|
procTerminate = k32.NewProc("TerminateProcess")
|
||||||
procMessageBox = k32.NewProc("MessageBoxW")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func closeH(h syscall.Handle) {
|
func closeH(h syscall.Handle) {
|
||||||
@@ -104,11 +102,6 @@ func setPrio(pid uint32, cls uint32) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ignoreOpenError(err error) bool {
|
|
||||||
return errors.Is(err, syscall.Errno(ErrorAccessDenied)) ||
|
|
||||||
errors.Is(err, syscall.Errno(ErrorInvalidParameter))
|
|
||||||
}
|
|
||||||
|
|
||||||
func curPrioWithHandle(h syscall.Handle) (uint32, error) {
|
func curPrioWithHandle(h syscall.Handle) (uint32, error) {
|
||||||
r, _, e := procGetPriority.Call(uintptr(h))
|
r, _, e := procGetPriority.Call(uintptr(h))
|
||||||
if r == 0 {
|
if r == 0 {
|
||||||
@@ -126,6 +119,49 @@ func utf16Trim(p []uint16) string {
|
|||||||
return syscall.UTF16ToString(p)
|
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 allProcs() ([]processInfo, error) {
|
func allProcs() ([]processInfo, error) {
|
||||||
snap, _, e := procCreateSnap.Call(CreateToolhelp32SnapshotProcess, 0)
|
snap, _, e := procCreateSnap.Call(CreateToolhelp32SnapshotProcess, 0)
|
||||||
if snap == 0 {
|
if snap == 0 {
|
||||||
@@ -182,67 +218,29 @@ func prioName(c uint32) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func procName(pid uint32) (string, error) {
|
|
||||||
snap, _, e := procCreateSnap.Call(CreateToolhelp32SnapshotProcess, 0)
|
|
||||||
if snap == 0 {
|
|
||||||
return "", 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 "", fmt.Errorf("Process32First failed: %w", e)
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
func killOtherInstances() {
|
||||||
if pe.PID == pid {
|
myPID := os.Getpid()
|
||||||
return utf16Trim(pe.ExeFile[:]), nil
|
procs, err := allProcs()
|
||||||
}
|
if err != nil {
|
||||||
pe.Size = uint32(unsafe.Sizeof(processEntry32{}))
|
return
|
||||||
r, _, e = procProcess32Next.Call(snap, uintptr(unsafe.Pointer(&pe)))
|
}
|
||||||
if r == 0 {
|
myName := filepath.Base(os.Args[0])
|
||||||
if e == syscall.Errno(ErrorNoMoreFiles) {
|
for _, p := range procs {
|
||||||
break
|
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)
|
||||||
}
|
}
|
||||||
return "", fmt.Errorf("Process32Next failed: %w", e)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isAlive(pid uint32, expectedName string) bool {
|
|
||||||
h, err := openProc(pid, ProcessQueryInformation)
|
|
||||||
if err != nil || h == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
closeH(h)
|
|
||||||
name, err := procName(pid)
|
|
||||||
if err != nil || name == "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return name == expectedName
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
if err == syscall.Errno(ErrorAlreadyExists) {
|
|
||||||
procMessageBox.Call(0,
|
|
||||||
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("autoPriority is already running."))),
|
|
||||||
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("autoPriority"))),
|
|
||||||
MessageBoxIconWarning,
|
|
||||||
)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
return syscall.Handle(h)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
mem := flag.Uint64("mem", 512*1024*1024, "memory threshold in bytes")
|
memStr := flag.String("mem", "512M", "memory threshold (e.g. 512M, 1G, 2048M)")
|
||||||
|
gameMemStr := flag.String("game-mem", "4G", "game memory threshold (e.g. 3G, 4G). Must be greater than -mem. 0 = disabled.")
|
||||||
interval := flag.Duration("interval", time.Minute, "scan interval")
|
interval := flag.Duration("interval", time.Minute, "scan interval")
|
||||||
dryRun := flag.Bool("dry-run", false, "log only, do not change priorities")
|
dryRun := flag.Bool("dry-run", false, "log only, do not change priorities")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
@@ -251,6 +249,25 @@ func main() {
|
|||||||
*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
|
var logFile *os.File
|
||||||
logPath := filepath.Join(os.TempDir(), "autopriority.log")
|
logPath := filepath.Join(os.TempDir(), "autopriority.log")
|
||||||
os.Remove(logPath)
|
os.Remove(logPath)
|
||||||
@@ -274,10 +291,11 @@ func main() {
|
|||||||
logFile.Sync()
|
logFile.Sync()
|
||||||
}
|
}
|
||||||
|
|
||||||
logf("autoPriority started (mem=%d bytes, interval=%s, dry-run=%v)", *mem, *interval, *dryRun)
|
if gameMem > 0 {
|
||||||
|
logf("autoPriority started (mem=%s, game-mem=%s, interval=%s, dry-run=%v)", formatMemSize(mem), formatMemSize(gameMem), *interval, *dryRun)
|
||||||
mutex := ensureSingleInstance()
|
} else {
|
||||||
defer closeH(mutex)
|
logf("autoPriority started (mem=%s, interval=%s, dry-run=%v)", formatMemSize(mem), *interval, *dryRun)
|
||||||
|
}
|
||||||
|
|
||||||
if err := setPrio(uint32(os.Getpid()), PriorityClassIdle); err != nil {
|
if err := setPrio(uint32(os.Getpid()), PriorityClassIdle); err != nil {
|
||||||
logf("warning: could not set own priority to IDLE: %v", err)
|
logf("warning: could not set own priority to IDLE: %v", err)
|
||||||
@@ -287,11 +305,44 @@ func main() {
|
|||||||
|
|
||||||
promoted := make(map[uint32]string)
|
promoted := make(map[uint32]string)
|
||||||
blocked := make(map[uint32]string)
|
blocked := make(map[uint32]string)
|
||||||
|
gameProcs := make(map[uint32]string)
|
||||||
|
gameMode := false
|
||||||
|
|
||||||
|
type savedPrio struct {
|
||||||
|
name string
|
||||||
|
prio uint32
|
||||||
|
}
|
||||||
|
gameSaved := make(map[uint32]savedPrio)
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
|
restoreProcs, _ := allProcs()
|
||||||
|
restoreMap := make(map[uint32]string, len(restoreProcs))
|
||||||
|
for _, p := range restoreProcs {
|
||||||
|
restoreMap[p.PID] = p.Name
|
||||||
|
}
|
||||||
|
for pid, sv := range gameSaved {
|
||||||
|
if cur, ok := restoreMap[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))
|
||||||
|
}
|
||||||
|
}
|
||||||
for pid, name := range promoted {
|
for pid, name := range promoted {
|
||||||
cur, err := procName(pid)
|
if cur, ok := restoreMap[pid]; !ok || cur != name {
|
||||||
if err != nil || cur != name {
|
logf("SKIP RESTORE PID %d: process name mismatch (expected %s, got %s)", pid, name, cur)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := setPrio(pid, PriorityClassNormal); err != nil {
|
||||||
|
logf("RESTORE %s (PID %d) -> NORMAL error: %v", name, pid, err)
|
||||||
|
} else {
|
||||||
|
logf("RESTORE %s (PID %d) -> NORMAL", name, pid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for pid, name := range gameProcs {
|
||||||
|
if cur, ok := restoreMap[pid]; !ok || cur != name {
|
||||||
logf("SKIP RESTORE PID %d: process name mismatch (expected %s, got %s)", pid, name, cur)
|
logf("SKIP RESTORE PID %d: process name mismatch (expected %s, got %s)", pid, name, cur)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -311,6 +362,25 @@ func main() {
|
|||||||
stop := make(chan os.Signal, 1)
|
stop := make(chan os.Signal, 1)
|
||||||
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
|
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
|
||||||
|
|
||||||
|
trySetPrio := func(qh syscall.Handle, pid uint32, name string, cls uint32) bool {
|
||||||
|
closeH(qh)
|
||||||
|
sh, err := openProc(pid, ProcessSetInformation)
|
||||||
|
if err != nil {
|
||||||
|
blocked[pid] = name
|
||||||
|
logf("BLOCK %s (PID %d): open SetInformation failed: %v (added to exclusion list)", name, pid, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer closeH(sh)
|
||||||
|
sr, _, se := procSetPriority.Call(uintptr(sh), uintptr(cls))
|
||||||
|
if sr == 0 {
|
||||||
|
blocked[pid] = name
|
||||||
|
logf("BLOCK %s (PID %d): SetPriorityClass failed: %v (added to exclusion list)", name, pid, se)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
scan := func() {
|
scan := func() {
|
||||||
procs, err := allProcs()
|
procs, err := allProcs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -318,34 +388,47 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pidMap := make(map[uint32]string, len(procs))
|
||||||
|
for _, p := range procs {
|
||||||
|
pidMap[p.PID] = p.Name
|
||||||
|
}
|
||||||
|
|
||||||
for pid, name := range promoted {
|
for pid, name := range promoted {
|
||||||
if !isAlive(pid, name) {
|
if cur, ok := pidMap[pid]; !ok || cur != name {
|
||||||
delete(promoted, pid)
|
delete(promoted, pid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for pid, name := range blocked {
|
for pid := range blocked {
|
||||||
if !isAlive(pid, name) {
|
if _, ok := pidMap[pid]; !ok {
|
||||||
delete(blocked, pid)
|
delete(blocked, pid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for pid, name := range gameProcs {
|
||||||
|
if cur, ok := pidMap[pid]; !ok || cur != name {
|
||||||
|
delete(gameProcs, pid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type procRSS struct {
|
||||||
|
info processInfo
|
||||||
|
rss uint64
|
||||||
|
}
|
||||||
|
var procList []procRSS
|
||||||
|
|
||||||
for _, p := range procs {
|
for _, p := range procs {
|
||||||
if p.PID == myPID || p.PID == 0 {
|
if p.PID == myPID || p.PID == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if _, ok := promoted[p.PID]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, ok := blocked[p.PID]; ok {
|
if _, ok := blocked[p.PID]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
h, err := openProc(p.PID, ProcessQueryInformation|ProcessSetInformation)
|
h, err := openProc(p.PID, ProcessQueryLimitedInformation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !ignoreOpenError(err) {
|
blocked[p.PID] = p.Name
|
||||||
logf("openProc(%s, PID %d) error: %v", p.Name, p.PID, err)
|
logf("BLOCK %s (PID %d): %v (added to exclusion list)", p.Name, p.PID, err)
|
||||||
}
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -357,29 +440,116 @@ func main() {
|
|||||||
logf("GetProcessMemoryInfo(%d) error: %v", p.PID, e)
|
logf("GetProcessMemoryInfo(%d) error: %v", p.PID, e)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
closeH(h)
|
||||||
rssBytes := uint64(m.WorkingSetSize)
|
rssBytes := uint64(m.WorkingSetSize)
|
||||||
if rssBytes < *mem {
|
|
||||||
origPrio, prioErr := curPrioWithHandle(h)
|
procList = append(procList, procRSS{p, rssBytes})
|
||||||
if prioErr == nil && isAboveNormal(origPrio) {
|
|
||||||
|
if gameMem > 0 && rssBytes >= gameMem {
|
||||||
|
if _, ok := gameProcs[p.PID]; !ok {
|
||||||
|
gameProcs[p.PID] = p.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hasGame := gameMem > 0 && len(gameProcs) > 0
|
||||||
|
|
||||||
|
for _, pr := range procList {
|
||||||
|
p := pr.info
|
||||||
|
rssBytes := pr.rss
|
||||||
|
|
||||||
|
if _, ok := gameProcs[p.PID]; ok {
|
||||||
|
if hasGame {
|
||||||
|
h, err := openProc(p.PID, ProcessQueryLimitedInformation)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
origPrio, prioErr := curPrioWithHandle(h)
|
||||||
|
if prioErr != nil {
|
||||||
|
closeH(h)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if origPrio == PriorityClassHigh {
|
||||||
|
closeH(h)
|
||||||
|
continue
|
||||||
|
}
|
||||||
if *dryRun {
|
if *dryRun {
|
||||||
logf("[DRY-RUN] %s (PID %d) RSS=%dMB, priority=%s — would set NORMAL",
|
closeH(h)
|
||||||
p.Name, p.PID, rssBytes/1024/1024, prioName(origPrio))
|
logf("[DRY-RUN] %s (PID %d) RSS=%s — would set HIGH (game)",
|
||||||
|
p.Name, p.PID, formatMemSize(rssBytes))
|
||||||
} else {
|
} else {
|
||||||
sr, _, se := procSetPriority.Call(uintptr(h), uintptr(PriorityClassNormal))
|
if trySetPrio(h, p.PID, p.Name, PriorityClassHigh) {
|
||||||
if sr != 0 {
|
logf("GAME %s (PID %d) RSS=%s -> HIGH",
|
||||||
logf("DEMOTE %s (PID %d) RSS=%dMB, %s -> NORMAL",
|
p.Name, p.PID, formatMemSize(rssBytes))
|
||||||
p.Name, p.PID, rssBytes/1024/1024, prioName(origPrio))
|
|
||||||
} else {
|
} else {
|
||||||
blocked[p.PID] = p.Name
|
logf("GAME %s (PID %d) RSS=%s — tracked as game (priority change failed)",
|
||||||
logf("BLOCK %s (PID %d): %v (added to exclusion list)", p.Name, p.PID, se)
|
p.Name, p.PID, formatMemSize(rssBytes))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
closeH(h)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
rssMB := rssBytes / 1024 / 1024
|
if hasGame {
|
||||||
|
h, err := openProc(p.PID, ProcessQueryLimitedInformation)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
origPrio, prioErr := curPrioWithHandle(h)
|
||||||
|
if prioErr != nil {
|
||||||
|
closeH(h)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if origPrio == PriorityClassIdle {
|
||||||
|
closeH(h)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
origStr := prioName(origPrio)
|
||||||
|
if *dryRun {
|
||||||
|
closeH(h)
|
||||||
|
logf("[DRY-RUN] %s (PID %d) RSS=%s, priority=%s — would set IDLE (game)",
|
||||||
|
p.Name, p.PID, formatMemSize(rssBytes), origStr)
|
||||||
|
} else {
|
||||||
|
if trySetPrio(h, p.PID, p.Name, PriorityClassIdle) {
|
||||||
|
if _, saved := gameSaved[p.PID]; !saved {
|
||||||
|
gameSaved[p.PID] = savedPrio{name: p.Name, prio: origPrio}
|
||||||
|
}
|
||||||
|
delete(promoted, p.PID)
|
||||||
|
logf("GAME %s (PID %d) RSS=%s, %s -> IDLE",
|
||||||
|
p.Name, p.PID, formatMemSize(rssBytes), origStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := promoted[p.PID]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
h, err := openProc(p.PID, ProcessQueryLimitedInformation)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if rssBytes < mem {
|
||||||
|
origPrio, prioErr := curPrioWithHandle(h)
|
||||||
|
if prioErr == nil && isAboveNormal(origPrio) {
|
||||||
|
if *dryRun {
|
||||||
|
closeH(h)
|
||||||
|
logf("[DRY-RUN] %s (PID %d) RSS=%s, priority=%s — would set NORMAL",
|
||||||
|
p.Name, p.PID, formatMemSize(rssBytes), prioName(origPrio))
|
||||||
|
} else {
|
||||||
|
if trySetPrio(h, p.PID, p.Name, PriorityClassNormal) {
|
||||||
|
logf("DEMOTE %s (PID %d) RSS=%s, %s -> NORMAL",
|
||||||
|
p.Name, p.PID, formatMemSize(rssBytes), prioName(origPrio))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
closeH(h)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
origPrio, prioErr := curPrioWithHandle(h)
|
origPrio, prioErr := curPrioWithHandle(h)
|
||||||
if prioErr != nil {
|
if prioErr != nil {
|
||||||
closeH(h)
|
closeH(h)
|
||||||
@@ -393,22 +563,45 @@ logf("BLOCK %s (PID %d): %v (added to exclusion list)", p.Name, p.PID, se)
|
|||||||
|
|
||||||
if *dryRun {
|
if *dryRun {
|
||||||
closeH(h)
|
closeH(h)
|
||||||
logf("[DRY-RUN] %s (PID %d) RSS=%dMB, priority=%s — would set HIGH",
|
logf("[DRY-RUN] %s (PID %d) RSS=%s, priority=%s — would set HIGH",
|
||||||
p.Name, p.PID, rssMB, origStr)
|
p.Name, p.PID, formatMemSize(rssBytes), origStr)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
sr, _, se := procSetPriority.Call(uintptr(h), uintptr(PriorityClassHigh))
|
if trySetPrio(h, p.PID, p.Name, PriorityClassHigh) {
|
||||||
closeH(h)
|
promoted[p.PID] = p.Name
|
||||||
if sr == 0 {
|
logf("PROMOTE %s (PID %d) RSS=%s, %s -> HIGH",
|
||||||
blocked[p.PID] = p.Name
|
p.Name, p.PID, formatMemSize(rssBytes), origStr)
|
||||||
logf("BLOCK %s (PID %d): %v (added to exclusion list)", p.Name, p.PID, se)
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
promoted[p.PID] = p.Name
|
if gameMem > 0 {
|
||||||
logf("PROMOTE %s (PID %d) RSS=%dMB, %s -> HIGH",
|
newHasGame := len(gameProcs) > 0
|
||||||
p.Name, p.PID, rssMB, origStr)
|
if newHasGame && !gameMode {
|
||||||
|
gameMode = true
|
||||||
|
logf("GAME MODE ON")
|
||||||
|
}
|
||||||
|
if !newHasGame && gameMode {
|
||||||
|
gameMode = false
|
||||||
|
logf("GAME MODE OFF")
|
||||||
|
for pid, sv := range gameSaved {
|
||||||
|
cur, ok := pidMap[pid]
|
||||||
|
if !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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete(gameSaved, pid)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user