1

Rewrite: simplify to strict algorithm, remove promoted map

- Remove promoted map entirely — processes are demoted when RSS < -mem
  and promoted when RSS >= -mem, no permanent state
- Remove trySetPrio closure — use setPrio directly for simplicity
- Remove curPrioWithHandle helper — inline procGetPriority.Call
- Remove GAME ... tracked as game (blocked) repeated log — log once
  on detection via GAME DETECT, silently skip on subsequent scans
- Game mode: log GAME DETECT once when process first exceeds game-mem
- gameSaved stores original priority before IDLE demote, restores on
  GAME MODE OFF or shutdown
- Delay between priority changes: 100ms
- Delete TZ.md (was a test for other AI)
This commit is contained in:
2026-06-23 11:16:51 +03:00
parent da13a6d549
commit 0e8d7333c1
2 changed files with 163 additions and 593 deletions
+163 -232
View File
@@ -19,7 +19,6 @@ const (
CreateToolhelp32SnapshotProcess = 0x00000002
ProcessTerminate = 0x00000001
ProcessSetInformation = 0x00000200
ProcessQueryInformation = 0x00000400
ProcessQueryLimitedInformation = 0x00001000
PriorityClassIdle = 0x00000040
PriorityClassNormal = 0x00000020
@@ -44,22 +43,28 @@ type processEntry32 struct {
}
type processMemoryCounters struct {
CBM uint32
PageFaultCount uint32
PeakWorkingSetSize uintptr
WorkingSetSize uintptr
QuotaPeakPagedPoolUsage uintptr
QuotaPagedPoolUsage uintptr
CBM uint32
PageFaultCount uint32
PeakWorkingSetSize uintptr
WorkingSetSize uintptr
QuotaPeakPagedPoolUsage uintptr
QuotaPagedPoolUsage uintptr
QuotaPeakNonPagedPoolUsage uintptr
QuotaNonPagedPoolUsage uintptr
PeakPagefileUsage uintptr
PagefileUsage uintptr
PrivateUsage uintptr
QuotaNonPagedPoolUsage uintptr
PeakPagefileUsage uintptr
PagefileUsage uintptr
PrivateUsage uintptr
}
type processInfo struct {
type procInfo struct {
PID uint32
Name string
RSS uint64
}
type savedPrio struct {
name string
prio uint32
}
var (
@@ -102,14 +107,6 @@ func setPrio(pid uint32, cls uint32) error {
return nil
}
func curPrioWithHandle(h syscall.Handle) (uint32, error) {
r, _, e := procGetPriority.Call(uintptr(h))
if r == 0 {
return 0, fmt.Errorf("GetPriorityClass failed: %w", e)
}
return uint32(r), nil
}
func utf16Trim(p []uint16) string {
for i, v := range p {
if v == 0 {
@@ -162,37 +159,6 @@ func formatMemSize(b uint64) string {
return fmt.Sprintf("%dKB", b/1024)
}
func allProcs() ([]processInfo, 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 []processInfo
for {
name := utf16Trim(pe.ExeFile[:])
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 {
if e == syscall.Errno(ErrorNoMoreFiles) {
break
}
return nil, fmt.Errorf("Process32Next failed: %w", e)
}
}
return out, nil
}
func isAboveNormal(c uint32) bool {
return c == PriorityClassAboveNormal ||
c == PriorityClassHigh ||
@@ -218,7 +184,36 @@ func prioName(c uint32) string {
}
}
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()
@@ -240,7 +235,7 @@ func killOtherInstances() {
func main() {
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.")
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()
@@ -271,8 +266,7 @@ func main() {
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)
f, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0644)
if err == nil {
logFile = f
defer logFile.Close()
@@ -292,9 +286,11 @@ func main() {
}
if gameMem > 0 {
logf("autoPriority started (mem=%s, game-mem=%s, interval=%s, dry-run=%v)", formatMemSize(mem), formatMemSize(gameMem), *interval, *dryRun)
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)
logf("autoPriority started (mem=%s, interval=%s, dry-run=%v)",
formatMemSize(mem), *interval, *dryRun)
}
if err := setPrio(uint32(os.Getpid()), PriorityClassIdle); err != nil {
@@ -303,53 +299,27 @@ func main() {
logf("own priority set to IDLE")
}
promoted := make(map[uint32]string)
blocked := make(map[uint32]string)
gameProcs := make(map[uint32]string)
gameSaved := make(map[uint32]savedPrio)
gameMode := false
type savedPrio struct {
name string
prio uint32
}
gameSaved := make(map[uint32]savedPrio)
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 gameMode {
restoreProcs, _ := allProcs()
rm := make(map[uint32]string, len(restoreProcs))
for _, p := range restoreProcs {
rm[p.PID] = p.Name
}
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 {
if cur, ok := restoreMap[pid]; !ok || 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)
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, 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")
@@ -362,25 +332,6 @@ func main() {
stop := make(chan os.Signal, 1)
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() {
procs, err := allProcs()
if err != nil {
@@ -393,31 +344,25 @@ func main() {
pidMap[p.PID] = p.Name
}
for pid, name := range promoted {
if cur, ok := pidMap[pid]; !ok || cur != name {
delete(promoted, pid)
}
}
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)
}
}
type procRSS struct {
info processInfo
rss uint64
for pid, sv := range gameSaved {
if cur, ok := pidMap[pid]; !ok || cur != sv.name {
delete(gameSaved, pid)
}
}
var procList []procRSS
for _, p := range procs {
var list []procInfo
for i := range procs {
p := &procs[i]
if p.PID == myPID || p.PID == 0 {
continue
}
@@ -434,67 +379,56 @@ func main() {
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 {
closeH(h)
if _, ok := blocked[p.PID]; !ok {
logf("GetProcessMemoryInfo(%d) error: %v", p.PID, e)
}
continue
}
closeH(h)
rssBytes := uint64(m.WorkingSetSize)
procList = append(procList, procRSS{p, rssBytes})
p.RSS = uint64(m.WorkingSetSize)
list = append(list, *p)
if gameMem > 0 && rssBytes >= gameMem {
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 _, pr := range procList {
p := pr.info
rssBytes := pr.rss
isBlocked := false
if _, ok := blocked[p.PID]; ok {
isBlocked = true
}
for i := range list {
p := &list[i]
_, isBlocked := blocked[p.PID]
_, isGame := gameProcs[p.PID]
if _, ok := gameProcs[p.PID]; ok {
if hasGame {
if isBlocked {
logf("GAME %s (PID %d) RSS=%s — tracked as game (blocked)", p.Name, p.PID, formatMemSize(rssBytes))
continue
}
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 {
closeH(h)
logf("[DRY-RUN] %s (PID %d) RSS=%s — would set HIGH (game)",
p.Name, p.PID, formatMemSize(rssBytes))
} else {
if trySetPrio(h, p.PID, p.Name, PriorityClassHigh) {
logf("GAME %s (PID %d) RSS=%s -> HIGH",
p.Name, p.PID, formatMemSize(rssBytes))
} else {
logf("GAME %s (PID %d) RSS=%s — tracked as game (priority change failed)",
p.Name, p.PID, formatMemSize(rssBytes))
}
}
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
}
@@ -503,33 +437,55 @@ func main() {
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
}
origPrio, prioErr := curPrioWithHandle(h)
if prioErr != nil {
closeH(h)
cur, _, _ := procGetPriority.Call(uintptr(h))
closeH(h)
if cur == 0 || uint32(cur) == PriorityClassIdle {
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)
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 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)
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
}
@@ -537,54 +493,29 @@ func main() {
if isBlocked {
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) {
delete(promoted, p.PID)
logf("DEMOTE %s (PID %d) RSS=%s, %s -> NORMAL",
p.Name, p.PID, formatMemSize(rssBytes), prioName(origPrio))
}
}
} else {
closeH(h)
}
cur, _, _ := procGetPriority.Call(uintptr(h))
closeH(h)
if cur != 0 && uint32(cur) == PriorityClassHigh {
continue
}
origPrio, prioErr := curPrioWithHandle(h)
if prioErr != nil {
closeH(h)
continue
origStr := ""
if cur != 0 {
origStr = prioName(uint32(cur))
}
if origPrio == PriorityClassHigh {
closeH(h)
continue
}
origStr := prioName(origPrio)
if *dryRun {
closeH(h)
logf("[DRY-RUN] %s (PID %d) RSS=%s, priority=%s — would set HIGH",
p.Name, p.PID, formatMemSize(rssBytes), origStr)
logf("[DRY-RUN] %s (PID %d) RSS=%s, priority=%s — would set HIGH", p.Name, p.PID, formatMemSize(p.RSS), origStr)
continue
}
if trySetPrio(h, p.PID, p.Name, PriorityClassHigh) {
promoted[p.PID] = p.Name
logf("PROMOTE %s (PID %d) RSS=%s, %s -> HIGH",
p.Name, p.PID, formatMemSize(rssBytes), origStr)
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)
}
}
@@ -598,8 +529,7 @@ func main() {
gameMode = false
logf("GAME MODE OFF")
for pid, sv := range gameSaved {
cur, ok := pidMap[pid]
if !ok || cur != sv.name {
if cur, ok := pidMap[pid]; !ok || cur != sv.name {
delete(gameSaved, pid)
continue
}
@@ -610,6 +540,7 @@ func main() {
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)