Rewrite: improve process handling and restoration
This commit is contained in:
@@ -1,103 +1,106 @@
|
|||||||
# 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. Optionally enters **Game Mode** when a process exceeds a higher threshold, boosting it to HIGH and setting all other processes to IDLE.
|
Отслеживает использование памяти процессами в Windows и автоматически управляет их приоритетом: ресурсоемким процессам назначает HIGH, а менее ресурсоемким снижает приоритет до NORMAL, только если он был выше. При превышении повышенного порога может включать **игровой режим**, назначая обнаруженному процессу HIGH, а всем остальным — IDLE.
|
||||||
|
|
||||||
**Windows only.**
|
**Только для Windows.**
|
||||||
|
|
||||||
## How it works
|
## Принцип работы
|
||||||
|
|
||||||
### Normal mode
|
### Обычный режим
|
||||||
|
|
||||||
Every scan interval the program iterates over all running processes:
|
Через заданный интервал программа проверяет все запущенные процессы:
|
||||||
|
|
||||||
| RSS vs `-mem` | Current priority | Action |
|
| RSS относительно `-mem` | Текущий приоритет | Действие |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| ≥ threshold | anything except HIGH | → HIGH (logged as `PROMOTE`) |
|
| ≥ порога | любой, кроме HIGH | → HIGH (запись `PROMOTE` в журнале) |
|
||||||
| ≥ threshold | already HIGH | skip |
|
| ≥ порога | уже HIGH | пропустить |
|
||||||
| < threshold | ABOVE_NORMAL, HIGH, or REALTIME | → NORMAL (logged as `DEMOTE`) |
|
| < порога | ABOVE_NORMAL, HIGH или REALTIME | → NORMAL (запись `DEMOTE` в журнале) |
|
||||||
| < threshold | NORMAL, BELOW_NORMAL, or IDLE | skip |
|
| < порога | NORMAL, BELOW_NORMAL или IDLE | пропустить |
|
||||||
|
|
||||||
### Game mode (`-game-mem`)
|
### Игровой режим (`-game-mem`)
|
||||||
|
|
||||||
When any process reaches or exceeds the `-game-mem` threshold:
|
Когда любой процесс достигает или превышает порог `-game-mem`:
|
||||||
|
|
||||||
1. That process → **HIGH** (logged as `GAME`)
|
1. Этому процессу назначается **HIGH** (запись `GAME` в журнале).
|
||||||
2. All other processes → **IDLE** (original priorities saved)
|
2. Всем остальным процессам назначается **IDLE** с сохранением исходных приоритетов.
|
||||||
3. Log: `GAME MODE ON`
|
3. В журнал добавляется запись `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`)
|
1. Всем процессам, приоритет которых был снижен в игровом режиме, **возвращается исходный приоритет** (запись `RESTORE` в журнале).
|
||||||
2. `GAME MODE OFF` logged
|
2. В журнал добавляется запись `GAME MODE OFF`.
|
||||||
3. Normal mode resumes — priorities recalculated by `-mem` rules
|
3. Возобновляется обычный режим, и приоритеты пересчитываются по правилам `-mem`.
|
||||||
|
|
||||||
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.
|
Если изменить приоритет процесса невозможно, например из-за античита или системных ограничений, процесс добавляется во внутренний список исключений и больше не изменяется (запись `BLOCK` в журнале). Его RSS продолжает измеряться, поэтому он может включить игровой режим. Процессы, память которых прочитать не удалось, пропускаются до следующей проверки.
|
||||||
|
|
||||||
On shutdown, all processes demoted during game mode are restored to their saved original priority.
|
При штатном завершении программа несколько раз пытается восстановить приоритеты всех процессов, измененных в игровом режиме. Если Windows отклоняет восстановление, в журнал записывается предупреждение.
|
||||||
|
|
||||||
## Build
|
В одном сеансе Windows может работать только один экземпляр программы. Второй экземпляр завершается, не прерывая работу первого.
|
||||||
|
|
||||||
Requires Go 1.26+.
|
## Сборка
|
||||||
|
|
||||||
|
Требуется Go 1.26 или новее.
|
||||||
|
|
||||||
```
|
```
|
||||||
# Standard build (with console window)
|
# Обычная сборка с окном консоли
|
||||||
go build -o autopriority.exe .
|
go build -o autopriority.exe .
|
||||||
|
|
||||||
# Background build (no console, minimal size)
|
# Фоновая сборка без консоли, минимальный размер
|
||||||
go build -ldflags="-H windowsgui -s -w" -o autopriority.exe .
|
go build -trimpath -buildvcs=false -gcflags="all=-l" -ldflags="-H=windowsgui -s -w -buildid=" -o autopriority.exe .
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
## Использование
|
||||||
|
|
||||||
```
|
```
|
||||||
autopriority [flags]
|
autopriority [flags]
|
||||||
```
|
```
|
||||||
|
|
||||||
| Flag | Default | Description |
|
| Флаг | Значение по умолчанию | Описание |
|
||||||
|-------------|---------------|----------------------------------|
|
|-------------|---------------|----------------------------------|
|
||||||
| `-mem` | 512M | Memory threshold (e.g. 512M, 1G, 2048M) |
|
| `-mem` | 512M | Порог памяти, например 512M, 1G или 2048M |
|
||||||
| `-game-mem` | 2G | Game threshold (e.g. 2G, 4G). Must be > `-mem`. 0 = disabled |
|
| `-game-mem` | 2G | Порог игрового режима, например 2G или 4G. Должен быть больше `-mem`. 0 отключает режим |
|
||||||
| `-interval` | 1 minute | Scan interval (min 10s) |
|
| `-interval` | 1 минута | Интервал проверки, минимум 10 секунд |
|
||||||
| `-dry-run` | false | Log only, don't change priority |
|
| `-dry-run` | false | Только записывать решения в журнал, не менять приоритеты |
|
||||||
|
|
||||||
Examples:
|
Примеры:
|
||||||
|
|
||||||
```
|
```
|
||||||
# Run with 1 GB threshold, scanning every 30 seconds
|
# Порог 1 ГБ, проверка каждые 30 секунд
|
||||||
autopriority -mem=1G -interval=30s
|
autopriority -mem=1G -interval=30s
|
||||||
|
|
||||||
# Run with defaults (512M threshold, 2G game threshold)
|
# Запуск со значениями по умолчанию: порог 512M, игровой порог 2G
|
||||||
autopriority
|
autopriority
|
||||||
|
|
||||||
# Game mode: normal threshold 512M, game threshold 4G
|
# Обычный порог 512M, игровой порог 4G
|
||||||
autopriority -mem=512M -game-mem=4G
|
autopriority -mem=512M -game-mem=4G
|
||||||
|
|
||||||
# Dry run — log decisions without changing anything
|
# Пробный запуск: записывать решения, ничего не изменяя
|
||||||
autopriority -dry-run
|
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).
|
Журнал всегда записывается в `%TEMP%\autopriority.log`. При каждом запуске создается новый файл, а предыдущий удаляется.
|
||||||
|
|
||||||
Log entries:
|
Типы записей:
|
||||||
|
|
||||||
| Prefix | Meaning |
|
| Префикс | Значение |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `PROMOTE` | Priority raised to HIGH (normal mode) |
|
| `PROMOTE` | Приоритет повышен до HIGH в обычном режиме |
|
||||||
| `DEMOTE` | Priority lowered to NORMAL (normal mode) |
|
| `DEMOTE` | Приоритет снижен до NORMAL в обычном режиме |
|
||||||
| `GAME DETECT` | Process first detected as exceeding game-mem threshold |
|
| `GAME DETECT` | Впервые обнаружен процесс, превысивший порог `-game-mem` |
|
||||||
| `GAME` | Process set to HIGH or IDLE (game mode) |
|
| `GAME` | Процессу назначен HIGH или IDLE в игровом режиме |
|
||||||
| `GAME MODE ON` | Game mode activated |
|
| `GAME MODE ON` | Игровой режим включен |
|
||||||
| `GAME MODE OFF` | Game mode deactivated — processes restored |
|
| `GAME MODE OFF` | Игровой режим выключен, приоритеты восстановлены |
|
||||||
| `BLOCK` | OpenProcess or SetPriorityClass failed; process added to exclusion list |
|
| `BLOCK` | Ошибка OpenProcess или SetPriorityClass, процесс добавлен в список исключений |
|
||||||
| `RESTORE` | Process restored to original/saved priority (game mode exit or shutdown) |
|
| `SKIP` | Не удалось прочитать сведения о процессе, попытка повторится при следующей проверке |
|
||||||
| `[DRY-RUN]` | Would change priority (dry-run mode) |
|
| `RESTORE` | Восстановлен исходный приоритет при выходе из игрового режима или завершении программы |
|
||||||
|
| `[DRY-RUN]` | Приоритет был бы изменен в пробном режиме |
|
||||||
|
|
||||||
## Auto-start
|
## Автозапуск
|
||||||
|
|
||||||
Open **Win+R**, type `shell:startup`, and press Enter. Then place a shortcut to `autopriority.exe` in the folder that opens.
|
Нажмите **Win+R**, введите `shell:startup` и нажмите Enter. Поместите ярлык `autopriority.exe` в открывшуюся папку.
|
||||||
|
|
||||||
## Dependencies
|
## Зависимости
|
||||||
|
|
||||||
None. Uses only Windows API (kernel32, psapi) via raw syscall.
|
Отсутствуют. Программа напрямую вызывает Windows API (`kernel32`, `psapi`) через syscall.
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ package main
|
|||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
@@ -17,7 +19,6 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
CreateToolhelp32SnapshotProcess = 0x00000002
|
CreateToolhelp32SnapshotProcess = 0x00000002
|
||||||
ProcessTerminate = 0x00000001
|
|
||||||
ProcessSetInformation = 0x00000200
|
ProcessSetInformation = 0x00000200
|
||||||
ProcessQueryLimitedInformation = 0x00001000
|
ProcessQueryLimitedInformation = 0x00001000
|
||||||
PriorityClassIdle = 0x00000040
|
PriorityClassIdle = 0x00000040
|
||||||
@@ -27,6 +28,7 @@ const (
|
|||||||
PriorityClassRealtime = 0x00000100
|
PriorityClassRealtime = 0x00000100
|
||||||
PriorityClassBelowNormal = 0x00004000
|
PriorityClassBelowNormal = 0x00004000
|
||||||
ErrorNoMoreFiles = 18
|
ErrorNoMoreFiles = 18
|
||||||
|
ErrorAlreadyExists = 183
|
||||||
)
|
)
|
||||||
|
|
||||||
type processEntry32 struct {
|
type processEntry32 struct {
|
||||||
@@ -56,15 +58,31 @@ type processMemoryCounters struct {
|
|||||||
PrivateUsage uintptr
|
PrivateUsage uintptr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type processKey struct {
|
||||||
|
PID uint32
|
||||||
|
Created uint64
|
||||||
|
}
|
||||||
|
|
||||||
type procInfo struct {
|
type procInfo struct {
|
||||||
PID uint32
|
PID uint32
|
||||||
Name string
|
Name string
|
||||||
RSS uint64
|
RSS uint64
|
||||||
|
Key processKey
|
||||||
|
Prio uint32
|
||||||
|
Handle syscall.Handle
|
||||||
|
CanSet bool
|
||||||
|
Keep bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type trackedProc struct {
|
||||||
|
name string
|
||||||
|
handle syscall.Handle
|
||||||
}
|
}
|
||||||
|
|
||||||
type savedPrio struct {
|
type savedPrio struct {
|
||||||
name string
|
name string
|
||||||
prio uint32
|
prio uint32
|
||||||
|
handle syscall.Handle
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -79,7 +97,9 @@ 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")
|
||||||
procTerminate = k32.NewProc("TerminateProcess")
|
procGetProcessTimes = k32.NewProc("GetProcessTimes")
|
||||||
|
procQueryImageName = k32.NewProc("QueryFullProcessImageNameW")
|
||||||
|
procCreateMutex = k32.NewProc("CreateMutexW")
|
||||||
)
|
)
|
||||||
|
|
||||||
func closeH(h syscall.Handle) {
|
func closeH(h syscall.Handle) {
|
||||||
@@ -94,12 +114,7 @@ func openProc(pid uint32, acc uint32) (syscall.Handle, error) {
|
|||||||
return syscall.Handle(r), nil
|
return syscall.Handle(r), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func setPrio(pid uint32, cls uint32) error {
|
func setPrio(h syscall.Handle, 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))
|
r, _, e := procSetPriority.Call(uintptr(h), uintptr(cls))
|
||||||
if r == 0 {
|
if r == 0 {
|
||||||
return fmt.Errorf("SetPriorityClass(%d) failed: %w", pid, e)
|
return fmt.Errorf("SetPriorityClass(%d) failed: %w", pid, e)
|
||||||
@@ -107,13 +122,44 @@ func setPrio(pid uint32, cls uint32) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func utf16Trim(p []uint16) string {
|
func processCreated(h syscall.Handle) (uint64, error) {
|
||||||
for i, v := range p {
|
var created, exited, kernel, user syscall.Filetime
|
||||||
if v == 0 {
|
r, _, e := procGetProcessTimes.Call(
|
||||||
return syscall.UTF16ToString(p[:i])
|
uintptr(h),
|
||||||
|
uintptr(unsafe.Pointer(&created)),
|
||||||
|
uintptr(unsafe.Pointer(&exited)),
|
||||||
|
uintptr(unsafe.Pointer(&kernel)),
|
||||||
|
uintptr(unsafe.Pointer(&user)),
|
||||||
|
)
|
||||||
|
if r == 0 {
|
||||||
|
return 0, fmt.Errorf("GetProcessTimes failed: %w", e)
|
||||||
}
|
}
|
||||||
|
return uint64(created.HighDateTime)<<32 | uint64(created.LowDateTime), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func processName(h syscall.Handle) (string, error) {
|
||||||
|
buf := make([]uint16, 32768)
|
||||||
|
size := uint32(len(buf))
|
||||||
|
r, _, e := procQueryImageName.Call(uintptr(h), 0, uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&size)))
|
||||||
|
if r == 0 {
|
||||||
|
return "", fmt.Errorf("QueryFullProcessImageName failed: %w", e)
|
||||||
}
|
}
|
||||||
return syscall.UTF16ToString(p)
|
return filepath.Base(syscall.UTF16ToString(buf[:size])), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func processAlive(h syscall.Handle) (bool, error) {
|
||||||
|
var created, exited, kernel, user syscall.Filetime
|
||||||
|
r, _, e := procGetProcessTimes.Call(
|
||||||
|
uintptr(h),
|
||||||
|
uintptr(unsafe.Pointer(&created)),
|
||||||
|
uintptr(unsafe.Pointer(&exited)),
|
||||||
|
uintptr(unsafe.Pointer(&kernel)),
|
||||||
|
uintptr(unsafe.Pointer(&user)),
|
||||||
|
)
|
||||||
|
if r == 0 {
|
||||||
|
return false, fmt.Errorf("GetProcessTimes failed: %w", e)
|
||||||
|
}
|
||||||
|
return exited.HighDateTime == 0 && exited.LowDateTime == 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseMemSize(s string) (uint64, error) {
|
func parseMemSize(s string) (uint64, error) {
|
||||||
@@ -121,32 +167,26 @@ func parseMemSize(s string) (uint64, error) {
|
|||||||
if s == "" {
|
if s == "" {
|
||||||
return 0, fmt.Errorf("empty value")
|
return 0, fmt.Errorf("empty value")
|
||||||
}
|
}
|
||||||
var suffix byte
|
multiplier := uint64(1)
|
||||||
switch s[len(s)-1] {
|
switch s[len(s)-1] {
|
||||||
case 'k', 'K':
|
case 'k', 'K':
|
||||||
suffix = 'K'
|
multiplier = 1024
|
||||||
s = s[:len(s)-1]
|
s = s[:len(s)-1]
|
||||||
case 'm', 'M':
|
case 'm', 'M':
|
||||||
suffix = 'M'
|
multiplier = 1024 * 1024
|
||||||
s = s[:len(s)-1]
|
s = s[:len(s)-1]
|
||||||
case 'g', 'G':
|
case 'g', 'G':
|
||||||
suffix = 'G'
|
multiplier = 1024 * 1024 * 1024
|
||||||
s = s[:len(s)-1]
|
s = s[:len(s)-1]
|
||||||
}
|
}
|
||||||
n, err := strconv.ParseUint(s, 10, 64)
|
n, err := strconv.ParseUint(s, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
switch suffix {
|
if n > math.MaxUint64/multiplier {
|
||||||
case 'K':
|
return 0, fmt.Errorf("value overflows uint64")
|
||||||
return n * 1024, nil
|
|
||||||
case 'M':
|
|
||||||
return n * 1024 * 1024, nil
|
|
||||||
case 'G':
|
|
||||||
return n * 1024 * 1024 * 1024, nil
|
|
||||||
default:
|
|
||||||
return n, nil
|
|
||||||
}
|
}
|
||||||
|
return n * multiplier, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatMemSize(b uint64) string {
|
func formatMemSize(b uint64) string {
|
||||||
@@ -165,6 +205,22 @@ func isAboveNormal(c uint32) bool {
|
|||||||
c == PriorityClassRealtime
|
c == PriorityClassRealtime
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func desiredPriority(rss, mem uint64, current uint32, gameMode, isGame bool) uint32 {
|
||||||
|
if gameMode {
|
||||||
|
if isGame {
|
||||||
|
return PriorityClassHigh
|
||||||
|
}
|
||||||
|
return PriorityClassIdle
|
||||||
|
}
|
||||||
|
if rss >= mem {
|
||||||
|
return PriorityClassHigh
|
||||||
|
}
|
||||||
|
if isAboveNormal(current) {
|
||||||
|
return PriorityClassNormal
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
func prioName(c uint32) string {
|
func prioName(c uint32) string {
|
||||||
switch c {
|
switch c {
|
||||||
case PriorityClassIdle:
|
case PriorityClassIdle:
|
||||||
@@ -186,7 +242,7 @@ func prioName(c uint32) string {
|
|||||||
|
|
||||||
func allProcs() ([]procInfo, error) {
|
func allProcs() ([]procInfo, error) {
|
||||||
snap, _, e := procCreateSnap.Call(CreateToolhelp32SnapshotProcess, 0)
|
snap, _, e := procCreateSnap.Call(CreateToolhelp32SnapshotProcess, 0)
|
||||||
if snap == 0 {
|
if snap == ^uintptr(0) {
|
||||||
return nil, fmt.Errorf("CreateToolhelp32Snapshot failed: %w", e)
|
return nil, fmt.Errorf("CreateToolhelp32Snapshot failed: %w", e)
|
||||||
}
|
}
|
||||||
defer closeH(syscall.Handle(snap))
|
defer closeH(syscall.Handle(snap))
|
||||||
@@ -199,7 +255,7 @@ func allProcs() ([]procInfo, error) {
|
|||||||
|
|
||||||
var out []procInfo
|
var out []procInfo
|
||||||
for {
|
for {
|
||||||
name := utf16Trim(pe.ExeFile[:])
|
name := syscall.UTF16ToString(pe.ExeFile[:])
|
||||||
if name != "" {
|
if name != "" {
|
||||||
out = append(out, procInfo{PID: pe.PID, Name: name})
|
out = append(out, procInfo{PID: pe.PID, Name: name})
|
||||||
}
|
}
|
||||||
@@ -215,22 +271,16 @@ func allProcs() ([]procInfo, error) {
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func killOtherInstances() {
|
func singleInstance() (syscall.Handle, bool, error) {
|
||||||
myPID := os.Getpid()
|
name, err := syscall.UTF16PtrFromString(`Local\autoPriority`)
|
||||||
procs, err := allProcs()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return 0, false, err
|
||||||
}
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
r, _, e := procCreateMutex.Call(0, 0, uintptr(unsafe.Pointer(name)))
|
||||||
|
if r == 0 {
|
||||||
|
return 0, false, fmt.Errorf("CreateMutex failed: %w", e)
|
||||||
}
|
}
|
||||||
|
return syscall.Handle(r), e == syscall.Errno(ErrorAlreadyExists), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -245,7 +295,10 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mem, err := parseMemSize(*memStr)
|
mem, err := parseMemSize(*memStr)
|
||||||
if err != nil {
|
if err != nil || mem == 0 {
|
||||||
|
if err == nil {
|
||||||
|
err = fmt.Errorf("must be greater than zero")
|
||||||
|
}
|
||||||
fmt.Fprintf(os.Stderr, "autoPriority: invalid -mem value: %v\n", err)
|
fmt.Fprintf(os.Stderr, "autoPriority: invalid -mem value: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
@@ -260,72 +313,195 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
killOtherInstances()
|
mutex, exists, err := singleInstance()
|
||||||
time.Sleep(100 * time.Millisecond)
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "autoPriority: %v\n", err)
|
||||||
var logFile *os.File
|
os.Exit(1)
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
|
if exists {
|
||||||
ts := func() string {
|
closeH(mutex)
|
||||||
return time.Now().Format("02.01.2006 15:04:05")
|
fmt.Fprintln(os.Stderr, "autoPriority: already running")
|
||||||
}
|
|
||||||
|
|
||||||
logf := func(format string, a ...any) {
|
|
||||||
if logFile == nil {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Fprintf(logFile, "[%s] ", ts())
|
defer closeH(mutex)
|
||||||
|
|
||||||
|
logPath := filepath.Join(os.TempDir(), "autopriority.log")
|
||||||
|
logFile, err := os.Create(logPath)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "autoPriority: cannot create log: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
defer logFile.Close()
|
||||||
|
|
||||||
|
logf := func(format string, a ...any) {
|
||||||
|
fmt.Fprintf(logFile, "[%s] ", time.Now().Format("02.01.2006 15:04:05"))
|
||||||
fmt.Fprintf(logFile, format+"\n", a...)
|
fmt.Fprintf(logFile, format+"\n", a...)
|
||||||
logFile.Sync()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if gameMem > 0 {
|
|
||||||
logf("autoPriority started (mem=%s, game-mem=%s, interval=%s, dry-run=%v)",
|
logf("autoPriority started (mem=%s, game-mem=%s, interval=%s, dry-run=%v)",
|
||||||
formatMemSize(mem), formatMemSize(gameMem), *interval, *dryRun)
|
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 {
|
if !*dryRun {
|
||||||
|
pid := uint32(os.Getpid())
|
||||||
|
h, err := openProc(pid, ProcessSetInformation)
|
||||||
|
if err == nil {
|
||||||
|
err = setPrio(h, pid, PriorityClassIdle)
|
||||||
|
closeH(h)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
logf("warning: could not set own priority to IDLE: %v", err)
|
logf("warning: could not set own priority to IDLE: %v", err)
|
||||||
} else {
|
} else {
|
||||||
logf("own priority set to IDLE")
|
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 {
|
blocked := make(map[processKey]string)
|
||||||
|
unreadable := make(map[uint32]string)
|
||||||
|
gameProcs := make(map[processKey]trackedProc)
|
||||||
|
gameSaved := make(map[processKey]savedPrio)
|
||||||
|
gameMode := false
|
||||||
|
myPID := uint32(os.Getpid())
|
||||||
|
|
||||||
|
readProcs := func() ([]procInfo, error) {
|
||||||
|
procs, err := allProcs()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
live := make(map[uint32]string, len(procs))
|
||||||
|
list := make([]procInfo, 0, len(procs))
|
||||||
|
for i := range procs {
|
||||||
|
p := &procs[i]
|
||||||
|
live[p.PID] = p.Name
|
||||||
|
if p.PID == myPID || p.PID == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := setPrio(pid, sv.prio); err != nil {
|
|
||||||
logf("RESTORE %s (PID %d) -> %s error: %v", sv.name, pid, prioName(sv.prio), err)
|
h, setErr := openProc(p.PID, ProcessQueryLimitedInformation|ProcessSetInformation)
|
||||||
} else {
|
p.CanSet = setErr == nil
|
||||||
logf("RESTORE %s (PID %d) -> %s", sv.name, pid, prioName(sv.prio))
|
openErr := error(nil)
|
||||||
|
if !p.CanSet {
|
||||||
|
h, openErr = openProc(p.PID, ProcessQueryLimitedInformation)
|
||||||
|
}
|
||||||
|
if openErr != nil {
|
||||||
|
if unreadable[p.PID] != p.Name {
|
||||||
|
unreadable[p.PID] = p.Name
|
||||||
|
logf("SKIP %s (PID %d): %v", p.Name, p.PID, openErr)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
name, readErr := processName(h)
|
||||||
|
if readErr == nil {
|
||||||
|
p.Name = name
|
||||||
|
}
|
||||||
|
created := uint64(0)
|
||||||
|
if readErr == nil {
|
||||||
|
created, readErr = processCreated(h)
|
||||||
|
}
|
||||||
|
var m processMemoryCounters
|
||||||
|
if readErr == nil {
|
||||||
|
m.CBM = uint32(unsafe.Sizeof(m))
|
||||||
|
r, _, e := procGetMemInfo.Call(uintptr(h), uintptr(unsafe.Pointer(&m)), uintptr(unsafe.Sizeof(m)))
|
||||||
|
if r == 0 {
|
||||||
|
readErr = fmt.Errorf("GetProcessMemoryInfo failed: %w", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var cur uintptr
|
||||||
|
if readErr == nil {
|
||||||
|
r, _, e := procGetPriority.Call(uintptr(h))
|
||||||
|
cur = r
|
||||||
|
if cur == 0 {
|
||||||
|
readErr = fmt.Errorf("GetPriorityClass failed: %w", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if readErr != nil {
|
||||||
|
closeH(h)
|
||||||
|
if unreadable[p.PID] != p.Name {
|
||||||
|
unreadable[p.PID] = p.Name
|
||||||
|
logf("SKIP %s (PID %d): %v", p.Name, p.PID, readErr)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(unreadable, p.PID)
|
||||||
|
p.Key = processKey{PID: p.PID, Created: created}
|
||||||
|
p.RSS = uint64(m.WorkingSetSize)
|
||||||
|
p.Prio = uint32(cur)
|
||||||
|
p.Handle = h
|
||||||
|
if !p.CanSet {
|
||||||
|
if _, ok := blocked[p.Key]; !ok {
|
||||||
|
blocked[p.Key] = p.Name
|
||||||
|
logf("BLOCK %s (PID %d): %v (priority changes disabled)", p.Name, p.PID, setErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
list = append(list, *p)
|
||||||
|
}
|
||||||
|
|
||||||
|
for pid, name := range unreadable {
|
||||||
|
if live[pid] != name {
|
||||||
|
delete(unreadable, pid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
restore := func(list []procInfo) {
|
||||||
|
current := make(map[processKey]*procInfo, len(list))
|
||||||
|
for i := range list {
|
||||||
|
current[list[i].Key] = &list[i]
|
||||||
|
}
|
||||||
|
for key, saved := range gameSaved {
|
||||||
|
alive, err := processAlive(saved.handle)
|
||||||
|
if err != nil {
|
||||||
|
logf("RESTORE %s (PID %d) status error: %v", saved.name, key.PID, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !alive {
|
||||||
|
closeH(saved.handle)
|
||||||
|
delete(gameSaved, key)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cur, _, e := procGetPriority.Call(uintptr(saved.handle))
|
||||||
|
if cur == 0 {
|
||||||
|
logf("RESTORE %s (PID %d) priority error: %v", saved.name, key.PID, e)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if uint32(cur) != saved.prio {
|
||||||
|
if err := setPrio(saved.handle, key.PID, saved.prio); err != nil {
|
||||||
|
logf("RESTORE %s (PID %d) -> %s error: %v", saved.name, key.PID, prioName(saved.prio), err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
logf("RESTORE %s (PID %d) -> %s", saved.name, key.PID, prioName(saved.prio))
|
||||||
|
}
|
||||||
|
if p := current[key]; p != nil {
|
||||||
|
p.Prio = saved.prio
|
||||||
|
}
|
||||||
|
closeH(saved.handle)
|
||||||
|
delete(gameSaved, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
for attempt := 0; attempt < 3 && len(gameSaved) > 0; attempt++ {
|
||||||
|
restore(nil)
|
||||||
|
if len(gameSaved) > 0 {
|
||||||
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(gameSaved) > 0 {
|
||||||
|
logf("warning: %d process priorities could not be restored", len(gameSaved))
|
||||||
|
for _, saved := range gameSaved {
|
||||||
|
closeH(saved.handle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, game := range gameProcs {
|
||||||
|
closeH(game.handle)
|
||||||
}
|
}
|
||||||
logf("autoPriority stopped")
|
logf("autoPriority stopped")
|
||||||
|
logFile.Sync()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
myPID := uint32(os.Getpid())
|
|
||||||
ticker := time.NewTicker(*interval)
|
ticker := time.NewTicker(*interval)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
@@ -333,220 +509,117 @@ func main() {
|
|||||||
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
|
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
|
||||||
|
|
||||||
scan := func() {
|
scan := func() {
|
||||||
procs, err := allProcs()
|
list, err := readProcs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logf("allProcs error: %v", err)
|
logf("process scan error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
defer func() {
|
||||||
|
for _, p := range list {
|
||||||
|
if !p.Keep {
|
||||||
|
closeH(p.Handle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
pidMap := make(map[uint32]string, len(procs))
|
current := make(map[processKey]string, len(list))
|
||||||
for _, p := range procs {
|
for _, p := range list {
|
||||||
pidMap[p.PID] = p.Name
|
current[p.Key] = p.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
for pid := range blocked {
|
for key, name := range blocked {
|
||||||
if _, ok := pidMap[pid]; !ok {
|
if current[key] != name {
|
||||||
delete(blocked, pid)
|
delete(blocked, key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for pid, name := range gameProcs {
|
for key, game := range gameProcs {
|
||||||
if cur, ok := pidMap[pid]; !ok || cur != name {
|
alive, err := processAlive(game.handle)
|
||||||
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 err != nil {
|
||||||
if _, ok := blocked[p.PID]; !ok {
|
logf("GAME %s (PID %d) status error: %v", game.name, key.PID, err)
|
||||||
blocked[p.PID] = p.Name
|
|
||||||
logf("BLOCK %s (PID %d): %v (added to exclusion list)", p.Name, p.PID, err)
|
|
||||||
}
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if !alive {
|
||||||
var m processMemoryCounters
|
closeH(game.handle)
|
||||||
m.CBM = uint32(unsafe.Sizeof(m))
|
delete(gameProcs, key)
|
||||||
r, _, e := procGetMemInfo.Call(uintptr(h), uintptr(unsafe.Pointer(&m)), uintptr(unsafe.Sizeof(m)))
|
}
|
||||||
closeH(h)
|
}
|
||||||
if r == 0 {
|
for key, saved := range gameSaved {
|
||||||
if _, ok := blocked[p.PID]; !ok {
|
alive, err := processAlive(saved.handle)
|
||||||
logf("GetProcessMemoryInfo(%d) error: %v", p.PID, e)
|
if err == nil && !alive {
|
||||||
|
closeH(saved.handle)
|
||||||
|
delete(gameSaved, key)
|
||||||
}
|
}
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
p.RSS = uint64(m.WorkingSetSize)
|
for i := range list {
|
||||||
list = append(list, *p)
|
p := &list[i]
|
||||||
|
|
||||||
if gameMem > 0 && p.RSS >= gameMem {
|
if gameMem > 0 && p.RSS >= gameMem {
|
||||||
if _, ok := gameProcs[p.PID]; !ok {
|
if _, ok := gameProcs[p.Key]; !ok {
|
||||||
gameProcs[p.PID] = p.Name
|
gameProcs[p.Key] = trackedProc{name: p.Name, handle: p.Handle}
|
||||||
|
p.Keep = true
|
||||||
logf("GAME DETECT %s (PID %d) RSS=%s", p.Name, p.PID, formatMemSize(p.RSS))
|
logf("GAME DETECT %s (PID %d) RSS=%s", p.Name, p.PID, formatMemSize(p.RSS))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hasGame := gameMem > 0 && len(gameProcs) > 0
|
hasGame := gameMem > 0 && len(gameProcs) > 0
|
||||||
|
enteringGame := hasGame && !gameMode
|
||||||
|
if enteringGame {
|
||||||
|
gameMode = true
|
||||||
|
}
|
||||||
|
if !hasGame && gameMode {
|
||||||
|
restore(list)
|
||||||
|
if len(gameSaved) == 0 {
|
||||||
|
gameMode = false
|
||||||
|
logf("GAME MODE OFF")
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hasGame {
|
||||||
|
sort.SliceStable(list, func(i, j int) bool {
|
||||||
|
_, iGame := gameProcs[list[i].Key]
|
||||||
|
_, jGame := gameProcs[list[j].Key]
|
||||||
|
return iGame && !jGame
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
for i := range list {
|
for i := range list {
|
||||||
p := &list[i]
|
p := &list[i]
|
||||||
_, isBlocked := blocked[p.PID]
|
_, isBlocked := blocked[p.Key]
|
||||||
_, isGame := gameProcs[p.PID]
|
_, isGame := gameProcs[p.Key]
|
||||||
|
target := desiredPriority(p.RSS, mem, p.Prio, hasGame, isGame)
|
||||||
|
action := "GAME"
|
||||||
|
if !hasGame && target == PriorityClassHigh {
|
||||||
|
action = "PROMOTE"
|
||||||
|
} else if !hasGame {
|
||||||
|
action = "DEMOTE"
|
||||||
|
}
|
||||||
|
|
||||||
if hasGame && isGame {
|
if target == 0 || target == p.Prio || isBlocked {
|
||||||
if isBlocked {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if *dryRun {
|
if *dryRun {
|
||||||
logf("[DRY-RUN] %s (PID %d) RSS=%s — would set HIGH (game)", p.Name, p.PID, formatMemSize(p.RSS))
|
logf("[DRY-RUN] %s %s (PID %d) RSS=%s, %s -> %s", action, p.Name, p.PID, formatMemSize(p.RSS), prioName(p.Prio), prioName(target))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
h, err := openProc(p.PID, ProcessQueryLimitedInformation)
|
if err := setPrio(p.Handle, p.PID, target); err != nil {
|
||||||
if err != nil {
|
blocked[p.Key] = p.Name
|
||||||
|
logf("BLOCK %s (PID %d): %v (priority changes disabled)", p.Name, p.PID, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
cur, _, _ := procGetPriority.Call(uintptr(h))
|
if hasGame && !isGame && target == PriorityClassIdle {
|
||||||
closeH(h)
|
if _, saved := gameSaved[p.Key]; !saved {
|
||||||
if cur != 0 && uint32(cur) == PriorityClassHigh {
|
gameSaved[p.Key] = savedPrio{name: p.Name, prio: p.Prio, handle: p.Handle}
|
||||||
continue
|
p.Keep = true
|
||||||
}
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
logf("%s %s (PID %d) RSS=%s, %s -> %s", action, p.Name, p.PID, formatMemSize(p.RSS), prioName(p.Prio), prioName(target))
|
||||||
if gameMem > 0 {
|
p.Prio = target
|
||||||
newHasGame := len(gameProcs) > 0
|
}
|
||||||
if newHasGame && !gameMode {
|
if enteringGame {
|
||||||
gameMode = true
|
|
||||||
logf("GAME MODE ON")
|
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()
|
scan()
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseMemSize(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
want uint64
|
||||||
|
bad bool
|
||||||
|
}{
|
||||||
|
{"512M", 512 * 1024 * 1024, false},
|
||||||
|
{" 1g ", 1024 * 1024 * 1024, false},
|
||||||
|
{"0", 0, false},
|
||||||
|
{"18446744073709551615", math.MaxUint64, false},
|
||||||
|
{"17179869183G", 17179869183 * 1024 * 1024 * 1024, false},
|
||||||
|
{"17179869184G", 0, true},
|
||||||
|
{"", 0, true},
|
||||||
|
{"G", 0, true},
|
||||||
|
{"1GB", 0, true},
|
||||||
|
{"1.5G", 0, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.input, func(t *testing.T) {
|
||||||
|
got, err := parseMemSize(tt.input)
|
||||||
|
if (err != nil) != tt.bad || got != tt.want {
|
||||||
|
t.Fatalf("parseMemSize(%q) = %d, %v; want %d, bad=%v", tt.input, got, err, tt.want, tt.bad)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDesiredPriority(t *testing.T) {
|
||||||
|
const mem = 512 * 1024 * 1024
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
rss uint64
|
||||||
|
current uint32
|
||||||
|
gameMode, isGame bool
|
||||||
|
want uint32
|
||||||
|
}{
|
||||||
|
{"promote heavy", mem, PriorityClassNormal, false, false, PriorityClassHigh},
|
||||||
|
{"keep heavy high", mem, PriorityClassHigh, false, false, PriorityClassHigh},
|
||||||
|
{"demote light high", mem - 1, PriorityClassHigh, false, false, PriorityClassNormal},
|
||||||
|
{"demote light realtime", mem - 1, PriorityClassRealtime, false, false, PriorityClassNormal},
|
||||||
|
{"keep light normal", mem - 1, PriorityClassNormal, false, false, 0},
|
||||||
|
{"boost game", 1, PriorityClassIdle, true, true, PriorityClassHigh},
|
||||||
|
{"idle non-game", mem * 10, PriorityClassHigh, true, false, PriorityClassIdle},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := desiredPriority(tt.rss, mem, tt.current, tt.gameMode, tt.isGame); got != tt.want {
|
||||||
|
t.Fatalf("desiredPriority() = %#x, want %#x", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user