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:
@@ -1,361 +0,0 @@
|
|||||||
# Техническое задание: autoPriority
|
|
||||||
|
|
||||||
## Назначение
|
|
||||||
|
|
||||||
Программа для Windows, которая автоматически управляет приоритетами процессов на основе их потребления оперативной памяти. Работает в фоне, периодически сканируя все процессы и корректируя их приоритеты CPU.
|
|
||||||
|
|
||||||
## Параметры командной строки
|
|
||||||
|
|
||||||
| Параметр | По умолчанию | Описание |
|
|
||||||
|---|---|---|
|
|
||||||
| `-mem` | `512M` | Порог памяти для повышения приоритета. Процессы с RSS ≥ этого значения повышаются до HIGH |
|
|
||||||
| `-game-mem` | `4G` | Порог памяти для активации игрового режима. Процессы с RSS ≥ этого значения считаются играми. `0` — игровой режим отключён. Должен быть больше `-mem` |
|
|
||||||
| `-interval` | `1m` | Интервал сканирования. Минимум 10 секунд |
|
|
||||||
| `-dry-run` | `false` | Только логирование, без изменения приоритетов |
|
|
||||||
|
|
||||||
Формат памяти: число с суффиксом `K`/`M`/`G` (например `512M`, `4G`, `2048M`). Без суффикра — байты.
|
|
||||||
|
|
||||||
## Структуры данных
|
|
||||||
|
|
||||||
Программа поддерживает четыре основные карты (map), хранящиеся в памяти между сканами:
|
|
||||||
|
|
||||||
### `promoted: map[pid → name]`
|
|
||||||
Процессы, повышенные до HIGH в нормальном режиме. Если RSS процесса падает ниже порога `-mem`, он понижается до NORMAL и удаляется из карты. При выходе из программы все процессы из карты восстанавливаются до NORMAL.
|
|
||||||
|
|
||||||
### `blocked: map[pid → name]`
|
|
||||||
Процессы, для которых не удалось открыть или изменить приоритет (системные процессы, античит). Полностью исключаются из попыток смены приоритета, но их RSS **всё равно измеряется** каждый скан (для определения игрового режима). Удаляются из карты, когда PID больше не существует в списке процессов.
|
|
||||||
|
|
||||||
### `gameProcs: map[pid → name]`
|
|
||||||
Процессы, превысившие `-game-mem` порог. Определяют, активен ли игровой режим. Удаляются, когда процесс умирает (PID исчезает из списка или имя не совпадает).
|
|
||||||
|
|
||||||
### `gameSaved: map[pid → {name, originalPriority}]`
|
|
||||||
Оригинальные приоритеты процессов, пониженных до IDLE в игровом режиме. Используются для восстановления при выключении игрового режима. Заполняется только при успешном понижении приоритета. Каждый PID сохраняется один раз (при первом понижении), чтобы запомнить приоритет, который был **до** игрового режима.
|
|
||||||
|
|
||||||
## Запуск программы
|
|
||||||
|
|
||||||
1. Разбор флагов командной строки
|
|
||||||
2. Проверка: если `game-mem > 0` и `game-mem <= mem` — выход с ошибкой
|
|
||||||
3. Если `interval < 10s` — принудительно установить 10s
|
|
||||||
4. **Уничтожение других экземпляров:** получить список всех процессов, найти процессы с тем же именем файла, что и у текущего процесса, но с другим PID, и завершить их через `TerminateProcess`
|
|
||||||
5. Подождать 100мс (ОС освобождает файловые дескрипторы)
|
|
||||||
6. Удалить старый лог-файл, создать новый (в `%TEMP%\autopriority.log`)
|
|
||||||
7. Установить собственный приоритет в IDLE
|
|
||||||
8. Записать в лог параметры запуска
|
|
||||||
9. Инициализировать пустые карты: `promoted`, `blocked`, `gameProcs`, `gameSaved`
|
|
||||||
10. Зарегистрировать deferred-функцию восстановления (см. раздел «Завершение»)
|
|
||||||
11. Выполнить первый скан немедленно
|
|
||||||
12. Войти в цикл: ждать сигнала завершения ИЛИ тикера интервала, при тике — выполнять скан
|
|
||||||
|
|
||||||
## Алгоритм сканирования (двухпроходный)
|
|
||||||
|
|
||||||
Каждый скан состоит из двух проходов. **Первый проход** собирает данные о памяти и определяет игровые процессы. **Второй проход** применяет приоритеты, уже зная, активен ли игровой режим. Это необходимо, чтобы игровой режим срабатывал на первом же скане, а не на следующем.
|
|
||||||
|
|
||||||
### Предварительный этап: очистка карт
|
|
||||||
|
|
||||||
Перед проходами программа получает список всех процессов и строит `pidMap` (pid → name). Затем очищает четыре карты:
|
|
||||||
|
|
||||||
- **`promoted`**: если PID исчез или имя не совпадает — удалить (процесс умер или PID перешёл другому процессу)
|
|
||||||
- **`blocked`**: если PID исчез — удалить (процесс умер, PID может быть переиспользован)
|
|
||||||
- **`gameProcs`**: если PID исчез или имя не совпадает — удалить
|
|
||||||
- **`gameSaved`**: если PID исчез или имя не совпадается — удалить
|
|
||||||
|
|
||||||
Проверка имени защищает от PID reuse: если процесс умер и его PID получил другой процесс, старая запись удаляется.
|
|
||||||
|
|
||||||
### Проход 1: Сбор RSS и определение игровых процессов
|
|
||||||
|
|
||||||
Для каждого процесса (кроме себя и PID 0):
|
|
||||||
|
|
||||||
1. Открыть процесс с правом `PROCESS_QUERY_LIMITED_INFORMATION` (0x1000). Это узкое право, которое дают почти все процессы, включая защищённые. Использование `PROCESS_QUERY_INFORMATION` (0x0400) приводит к Access Denied для многих пользовательских процессов.
|
|
||||||
2. Если `OpenProcess` упал:
|
|
||||||
- Добавить PID в `blocked` (если ещё не там)
|
|
||||||
- Записать в лог `BLOCK ... (added to exclusion list)`
|
|
||||||
- **Перейти к следующему процессу** (не добавлять в `procList`)
|
|
||||||
- Примечание: blocked процессы не попадают в `procList`, но если они были добавлены в `blocked` на предыдущем скане и всё ещё живы, они НЕ измеряются повторно. Это допустимо, потому что blocked процессы не могут менять приоритет.
|
|
||||||
|
|
||||||
**Важное исключение:** если процесс УЖЕ в `blocked` (добавлен ранее), то не логировать повторно — просто пропустить.
|
|
||||||
|
|
||||||
3. Вызвать `GetProcessMemoryInfo` для получения `WorkingSetSize` (RSS)
|
|
||||||
4. Если упало — пропустить процесс
|
|
||||||
5. Закрыть handle
|
|
||||||
6. Записать `{PID, Name, RSS}` в `procList`
|
|
||||||
7. Если `game-mem > 0` и `RSS >= game-mem`:
|
|
||||||
- Добавить PID в `gameProcs` (если ещё не там)
|
|
||||||
|
|
||||||
После прохода 1 вычислить:
|
|
||||||
```
|
|
||||||
hasGame = (game-mem > 0) AND (len(gameProcs) > 0)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Проход 2: Применение приоритетов
|
|
||||||
|
|
||||||
Для каждого процесса из `procList` (в порядке обхода):
|
|
||||||
|
|
||||||
Определить `isBlocked`: процесс находится в `blocked`.
|
|
||||||
|
|
||||||
#### Вариант A: Процесс — игровой (есть в `gameProcs`)
|
|
||||||
|
|
||||||
Если `hasGame` (игровой режим активен):
|
|
||||||
- Если `isBlocked`: записать в лог `GAME ... — tracked as game (blocked)`, перейти к следующему
|
|
||||||
- Открыть с `PROCESS_QUERY_LIMITED_INFORMATION`
|
|
||||||
- Получить текущий приоритет
|
|
||||||
- Если уже HIGH — пропустить
|
|
||||||
- Если `dry-run`: записать `would set HIGH (game)`, пропустить
|
|
||||||
- Иначе: попытаться установить HIGH через `trySetPrio` (см. ниже)
|
|
||||||
- Успех → `GAME ... -> HIGH`
|
|
||||||
- Провал → `GAME ... — tracked as game (priority change failed)`
|
|
||||||
|
|
||||||
Перейти к следующему процессу.
|
|
||||||
|
|
||||||
#### Вариант B: Игровой режим активен, процесс НЕ игровой
|
|
||||||
|
|
||||||
Если `isBlocked`: пропустить (не пытаться понизить)
|
|
||||||
Открыть с `PROCESS_QUERY_LIMITED_INFORMATION`
|
|
||||||
Получить текущий приоритет
|
|
||||||
Если уже IDLE — пропустить
|
|
||||||
Если `dry-run`: записать `would set IDLE (game)`, пропустить
|
|
||||||
Иначе: попытаться установить IDLE через `trySetPrio`
|
|
||||||
- Успех → сохранить оригинальный приоритет в `gameSaved` (если ещё не сохранён), удалить из `promoted` (если там был), записать `GAME ... -> IDLE`
|
|
||||||
- Провал → процесс добавлен в `blocked`, больше не пытаться
|
|
||||||
|
|
||||||
Перейти к следующему процессу.
|
|
||||||
|
|
||||||
#### Вариант C: Нормальный режим (игровой режим не активен)
|
|
||||||
|
|
||||||
Если `isBlocked`: пропустить
|
|
||||||
|
|
||||||
Открыть с `PROCESS_QUERY_LIMITED_INFORMATION`
|
|
||||||
|
|
||||||
**Если RSS < `-mem`:**
|
|
||||||
- Получить текущий приоритет
|
|
||||||
- Если приоритет ABOVE_NORMAL / HIGH / REALTIME → понизить до NORMAL (DEMOTE), удалить из `promoted` (если там был)
|
|
||||||
- Иначе пропустить
|
|
||||||
|
|
||||||
**Если RSS >= `-mem`:**
|
|
||||||
- Получить текущий приоритет
|
|
||||||
- Если уже HIGH — пропустить (процесс уже повышен, возможно из `promoted`)
|
|
||||||
- Если `dry-run`: записать `would set HIGH`, пропустить
|
|
||||||
- Иначе: попытаться установить HIGH через `trySetPrio`
|
|
||||||
- Успех → добавить в `promoted`, записать `PROMOTE ... -> HIGH`
|
|
||||||
- Провал → процесс добавлен в `blocked`
|
|
||||||
|
|
||||||
### Финал скана: управление игровым режимом
|
|
||||||
|
|
||||||
После прохода 2, если `game-mem > 0`:
|
|
||||||
|
|
||||||
Вычислить `newHasGame = len(gameProcs) > 0`
|
|
||||||
|
|
||||||
**Если `newHasGame == true` и `gameMode == false`:**
|
|
||||||
- `gameMode = true`
|
|
||||||
- Записать `GAME MODE ON`
|
|
||||||
|
|
||||||
**Если `newHasGame == false` и `gameMode == true`:**
|
|
||||||
- `gameMode = false`
|
|
||||||
- Записать `GAME MODE OFF`
|
|
||||||
- **Восстановить приоритеты:** для каждого PID в `gameSaved`:
|
|
||||||
- Проверить, что PID всё ещё существует и имя совпадает (защита от PID reuse)
|
|
||||||
- Если `dry-run`: записать `[DRY-RUN] RESTORE ... -> originalPriority`
|
|
||||||
- Иначе: установить оригинальный приоритет через `setPrio` (отдельный `OpenProcess` с `PROCESS_SET_INFORMATION`)
|
|
||||||
- Успех → `RESTORE ... -> originalPriority`
|
|
||||||
- Провал → `RESTORE ... error`
|
|
||||||
- Удалить запись из `gameSaved`
|
|
||||||
- Очистить `gameSaved` (все записи обработаны)
|
|
||||||
|
|
||||||
## Функция trySetPrio
|
|
||||||
|
|
||||||
```
|
|
||||||
trySetPrio(queryHandle, pid, name, targetClass) → bool
|
|
||||||
```
|
|
||||||
|
|
||||||
1. Закрыть `queryHandle` (handle от `PROCESS_QUERY_LIMITED_INFORMATION`)
|
|
||||||
2. Открыть процесс заново с правом `PROCESS_SET_INFORMATION` (0x0200)
|
|
||||||
- Разделение прав: многие процессы дают `QUERY` но не дают `SET` в комбинации
|
|
||||||
3. Если `OpenProcess` упал:
|
|
||||||
- Добавить PID в `blocked`
|
|
||||||
- Записать `BLOCK ... open SetInformation failed`
|
|
||||||
- Вернуть `false`
|
|
||||||
4. Вызвать `SetPriorityClass(handle, targetClass)`
|
|
||||||
5. Закрыть handle (через defer)
|
|
||||||
6. Если `SetPriorityClass` упал:
|
|
||||||
- Добавить PID в `blocked`
|
|
||||||
- Записать `BLOCK ... SetPriorityClass failed`
|
|
||||||
- Вернуть `false`
|
|
||||||
7. Подождать 50мс (плавная смена приоритетов, предотвращает статтеры)
|
|
||||||
8. Вернуть `true`
|
|
||||||
|
|
||||||
## Функция setPrio (для восстановления)
|
|
||||||
|
|
||||||
```
|
|
||||||
setPrio(pid, targetClass) → error
|
|
||||||
```
|
|
||||||
|
|
||||||
1. Открыть с `PROCESS_SET_INFORMATION`
|
|
||||||
2. Вызвать `SetPriorityClass`
|
|
||||||
3. Закрыть handle
|
|
||||||
4. Вернуть ошибку или nil
|
|
||||||
|
|
||||||
## Завершение программы (deferred)
|
|
||||||
|
|
||||||
При получении сигнала SIGINT/SIGTERM:
|
|
||||||
|
|
||||||
1. Получить свежий список всех процессов
|
|
||||||
2. Построить `restoreMap` (pid → name)
|
|
||||||
3. **Восстановить `gameSaved`:** для каждого PID — проверить имя, установить оригинальный приоритет, записать `RESTORE ... -> originalPriority`
|
|
||||||
4. **Восстановить `promoted`:** для каждого PID — проверить имя, установить NORMAL, записать `RESTORE ... -> NORMAL`
|
|
||||||
5. **Восстановить `gameProcs`:** для каждого PID — проверить имя, установить NORMAL, записать `RESTORE ... -> NORMAL`
|
|
||||||
6. Записать `autoPriority stopped`
|
|
||||||
|
|
||||||
Порядок важен: `gameSaved` восстанавливается первым, потому что `promoted` мог быть удалён из `promoted` во время игрового режима, но его оригинальный приоритет сохранён в `gameSaved`.
|
|
||||||
|
|
||||||
## Классы приоритетов Windows
|
|
||||||
|
|
||||||
| Константа | Значение | Имя в логе |
|
|
||||||
|---|---|---|
|
|
||||||
| IDLE | 0x0040 | IDLE |
|
|
||||||
| BELOW_NORMAL | 0x4000 | BELOW_NORMAL |
|
|
||||||
| NORMAL | 0x0020 | NORMAL |
|
|
||||||
| ABOVE_NORMAL | 0x8000 | ABOVE_NORMAL |
|
|
||||||
| HIGH | 0x0080 | HIGH |
|
|
||||||
| REALTIME | 0x0100 | REALTIME |
|
|
||||||
|
|
||||||
### Логика isAboveNormal
|
|
||||||
|
|
||||||
Возвращает `true` для `ABOVE_NORMAL`, `HIGH`, `REALTIME`. НЕ возвращает `true` для `BELOW_NORMAL`, потому что числовое значение `BELOW_NORMAL` (0x4000) больше `NORMAL` (0x0020), хотя семантически ниже. Поэтому используется явная проверка, а не числовое сравнение.
|
|
||||||
|
|
||||||
## Логирование
|
|
||||||
|
|
||||||
Лог пишется в `%TEMP%\autopriority.log`. При запуске старый лог удаляется, создаётся новый. Каждая запись синхронизируется на диск (`Sync()`). Формат: `[DD.MM.YYYY HH:MM:SS] сообщение`.
|
|
||||||
|
|
||||||
### Префиксы логов
|
|
||||||
|
|
||||||
| Префикс | Значение |
|
|
||||||
|---|---|
|
|
||||||
| `PROMOTE` | Процесс повышен до HIGH (нормальный режим) |
|
|
||||||
| `DEMOTE` | Процесс понижен до NORMAL (нормальный режим) |
|
|
||||||
| `GAME` | Процесс изменён вgame режиме (→ HIGH или → IDLE) |
|
|
||||||
| `GAME MODE ON`| Игровой режим активирован |
|
|
||||||
| `GAME MODE OFF`| Игровой режим деактивирован, процессы восстановлены |
|
|
||||||
| `BLOCK` | OpenProcess или SetPriorityClass упал; процесс в исключениях |
|
|
||||||
| `RESTORE` | Процесс восстановлен в оригинальный приоритет |
|
|
||||||
| `[DRY-RUN]` | Было бы изменение приоритета (dry-run режим) |
|
|
||||||
|
|
||||||
## Windows API
|
|
||||||
|
|
||||||
Программа использует только `kernel32.dll` и `psapi.dll` через raw syscall. Без внешних зависимостей.
|
|
||||||
|
|
||||||
### Используемые функции
|
|
||||||
|
|
||||||
- `CreateToolhelp32Snapshot` — снимок списка процессов
|
|
||||||
- `Process32FirstW` / `Process32NextW` — обход снимка
|
|
||||||
- `OpenProcess` — открытие handle процесса
|
|
||||||
- `GetProcessMemoryInfo` — получение WorkingSetSize (RSS)
|
|
||||||
- `GetPriorityClass` — чтение текущего приоритета
|
|
||||||
- `SetPriorityClass` — установка приоритета
|
|
||||||
- `CloseHandle` — закрытие handle
|
|
||||||
- `TerminateProcess` — завершение других экземпляров
|
|
||||||
|
|
||||||
### Права доступа
|
|
||||||
|
|
||||||
- `PROCESS_QUERY_LIMITED_INFORMATION` (0x1000) — для чтения памяти и приоритета. Это право дают почти все процессы.
|
|
||||||
- `PROCESS_SET_INFORMATION` (0x0200) — для изменения приоритета. Запрашивается отдельно, только когда нужно менять приоритет.
|
|
||||||
- `PROCESS_TERMINATE` (0x0001) — для уничтожения других экземпляров.
|
|
||||||
|
|
||||||
Разделение прав критично: запрос `QUERY|SET` комбинированно приводит к Access Denied для многих пользовательских процессов (KeePassXC, HD-Player), хотя каждое право по отдельности они дают.
|
|
||||||
|
|
||||||
## Структура processEntry32
|
|
||||||
|
|
||||||
```
|
|
||||||
Size uint32
|
|
||||||
CntUsage uint32
|
|
||||||
PID uint32
|
|
||||||
DefaultHeapID uintptr // НЕ uint32 — intptr на x64
|
|
||||||
ModuleID uint32
|
|
||||||
CntThreads uint32
|
|
||||||
ParentPID uint32
|
|
||||||
PrioClass int32
|
|
||||||
Flags uint32
|
|
||||||
ExeFile [260]uint16 // UTF-16
|
|
||||||
```
|
|
||||||
|
|
||||||
`DefaultHeapID` должен быть `uintptr`, не `uint32` — иначе на x64 структура неправильно выровнена и `Process32Next` падает.
|
|
||||||
|
|
||||||
## Сценарии работы
|
|
||||||
|
|
||||||
### Сценарий 1: Обычная работа
|
|
||||||
|
|
||||||
1. Запуск программы
|
|
||||||
2. Chrome потребляет 600MB (≥ 512MB) → `PROMOTE Chrome.exe -> HIGH`, добавляется в `promoted`
|
|
||||||
3. Chrome сбрасывает память до 300MB (< 512MB) → `DEMOTE Chrome.exe -> NORMAL`, удаляется из `promoted`
|
|
||||||
4. Discord потребляет 700MB → `PROMOTE Discord.exe -> HIGH`
|
|
||||||
5. Discord сбрасывает до 200MB → `DEMOTE Discord.exe -> NORMAL`
|
|
||||||
|
|
||||||
### Сценарий 2: Понижение высокого приоритета
|
|
||||||
|
|
||||||
1. `wininit.exe` запущен с HIGH, потребляет 6MB (< 512MB порога)
|
|
||||||
2. Программа видит: RSS < mem, приоритет ABOVE_NORMAL → `DEMOTE wininit.exe -> NORMAL`
|
|
||||||
3. Это применяется ко всем процессам (включая ранее повышенные, если их RSS упал ниже порога)
|
|
||||||
|
|
||||||
### Сценарий 3: Игровой режим
|
|
||||||
|
|
||||||
1. Запуск RustClient, потребление растёт с 1GB до 8GB
|
|
||||||
2. На скане, где RSS ≥ 4GB: RustClient добавляется в `gameProcs`
|
|
||||||
3. `hasGame = true`
|
|
||||||
4. RustClient → `trySetPrio` HIGH → если античит блокирует → `BLOCK RustClient.exe` + `GAME RustClient.exe — tracked as game (priority change failed)`
|
|
||||||
5. RustClient остаётся в `gameProcs` (важно: блокировка SetPriorityClass не удаляет из gameProcs)
|
|
||||||
6. Все остальные процессы → IDLE (`GAME Chrome.exe -> IDLE`, etc.)
|
|
||||||
7. `GAME MODE ON`
|
|
||||||
8. При следующем скане RustClient всё ещё в `gameProcs`, остальные процессы уже IDLE — пропускаются
|
|
||||||
|
|
||||||
### Сценарий 4: Выход из игрового режима
|
|
||||||
|
|
||||||
1. RustClient закрыт
|
|
||||||
2. `gameProcs` очищен (PID исчез)
|
|
||||||
3. `newHasGame = false`, `gameMode = true` → переход
|
|
||||||
4. `gameMode = false`, `GAME MODE OFF`
|
|
||||||
5. Для каждого процесса в `gameSaved`: восстановить оригинальный приоритет
|
|
||||||
- `RESTORE Chrome.exe -> NORMAL` (был NORMAL до игрового режима)
|
|
||||||
- `RESTORE Discord.exe -> HIGH` (был HIGH, потому что был в `promoted`)
|
|
||||||
6. `gameSaved` очищен
|
|
||||||
7. Нормальный режим возобновляется: процессы с RSS ≥ mem повышаются, с RSS < mem и выше NORMAL — понижаются
|
|
||||||
|
|
||||||
### Сценарий 5: Заблокированный процесс становится игрой
|
|
||||||
|
|
||||||
1. RustClient стартует, потребляет 3.9GB
|
|
||||||
2. RSS ≥ 512MB (mem), не в `promoted` → попытка PROMOTE → античит блокирует → `BLOCK RustClient.exe` (добавлен в `blocked`)
|
|
||||||
3. RustClient дорастает до 8GB
|
|
||||||
4. На следующем скане: RustClient в `blocked`, но **проход 1 всё равно пытается его открыть**
|
|
||||||
5. `OpenProcess(QUERY_LIMITED)` для blocked процесса — если упадёт, пропустит. Если пройдёт — RSS измеряется
|
|
||||||
6. Если RustClient в `blocked` из-за `SetPriorityClass` (не `OpenProcess`), то `OpenProcess(QUERY_LIMITED)` пройдёт, RSS измерится, и он попадёт в `gameProcs`
|
|
||||||
7. `hasGame = true`, `GAME MODE ON`
|
|
||||||
8. В проходе 2: RustClient в `gameProcs`, но `isBlocked = true` → `GAME RustClient.exe — tracked as game (blocked)`, приоритет не меняется
|
|
||||||
9. Остальные процессы понижаются до IDLE
|
|
||||||
|
|
||||||
### Сценарий 6: PID reuse
|
|
||||||
|
|
||||||
1. Процесс A (PID 1234, "chrome.exe") повышен, в `promoted`
|
|
||||||
2. Процесс A умирает
|
|
||||||
3. Новый процесс B (PID 1234, "notepad.exe") запускается
|
|
||||||
4. На следующем скане: `pidMap[1234] = "notepad.exe"`, но `promoted[1234] = "chrome.exe"`
|
|
||||||
5. Имя не совпадает → `delete(promoted, 1234)`
|
|
||||||
6. Процесс B обрабатывается как новый процесс
|
|
||||||
|
|
||||||
### Сценарий 7: Системные процессы
|
|
||||||
|
|
||||||
1. `svchost.exe` (PID 932) — `OpenProcess(QUERY_LIMITED)` возвращает Access Denied
|
|
||||||
2. Добавляется в `blocked`, логируется `BLOCK`
|
|
||||||
3. На следующем скане: PID 932 всё ещё в `blocked`, `OpenProcess` снова падает, но лог НЕ пишется (процесс уже в `blocked`)
|
|
||||||
4. Когда процесс умирает: PID исчезает из `pidMap` → удаляется из `blocked`
|
|
||||||
|
|
||||||
## Сборка
|
|
||||||
|
|
||||||
```
|
|
||||||
go build -ldflags="-H windowsgui -s -w" -o autopriority.exe .
|
|
||||||
```
|
|
||||||
|
|
||||||
- `-H windowsgui` — без консольного окна
|
|
||||||
- `-s -w` — strip debug info, уменьшение размера
|
|
||||||
|
|
||||||
Требуется Go 1.26+. Только Windows (`//go:build windows`).
|
|
||||||
|
|
||||||
## Автозапуск
|
|
||||||
|
|
||||||
Поместить ярлык на `autopriority.exe` в папку `shell:startup` (Win+R → `shell:startup`).
|
|
||||||
@@ -19,7 +19,6 @@ const (
|
|||||||
CreateToolhelp32SnapshotProcess = 0x00000002
|
CreateToolhelp32SnapshotProcess = 0x00000002
|
||||||
ProcessTerminate = 0x00000001
|
ProcessTerminate = 0x00000001
|
||||||
ProcessSetInformation = 0x00000200
|
ProcessSetInformation = 0x00000200
|
||||||
ProcessQueryInformation = 0x00000400
|
|
||||||
ProcessQueryLimitedInformation = 0x00001000
|
ProcessQueryLimitedInformation = 0x00001000
|
||||||
PriorityClassIdle = 0x00000040
|
PriorityClassIdle = 0x00000040
|
||||||
PriorityClassNormal = 0x00000020
|
PriorityClassNormal = 0x00000020
|
||||||
@@ -57,9 +56,15 @@ type processMemoryCounters struct {
|
|||||||
PrivateUsage uintptr
|
PrivateUsage uintptr
|
||||||
}
|
}
|
||||||
|
|
||||||
type processInfo struct {
|
type procInfo struct {
|
||||||
PID uint32
|
PID uint32
|
||||||
Name string
|
Name string
|
||||||
|
RSS uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
type savedPrio struct {
|
||||||
|
name string
|
||||||
|
prio uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -102,14 +107,6 @@ func setPrio(pid uint32, cls uint32) error {
|
|||||||
return nil
|
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 {
|
func utf16Trim(p []uint16) string {
|
||||||
for i, v := range p {
|
for i, v := range p {
|
||||||
if v == 0 {
|
if v == 0 {
|
||||||
@@ -162,37 +159,6 @@ func formatMemSize(b uint64) string {
|
|||||||
return fmt.Sprintf("%dKB", b/1024)
|
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 {
|
func isAboveNormal(c uint32) bool {
|
||||||
return c == PriorityClassAboveNormal ||
|
return c == PriorityClassAboveNormal ||
|
||||||
c == PriorityClassHigh ||
|
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() {
|
func killOtherInstances() {
|
||||||
myPID := os.Getpid()
|
myPID := os.Getpid()
|
||||||
@@ -240,7 +235,7 @@ func killOtherInstances() {
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
memStr := flag.String("mem", "512M", "memory threshold (e.g. 512M, 1G, 2048M)")
|
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")
|
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()
|
||||||
@@ -271,8 +266,7 @@ func main() {
|
|||||||
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)
|
||||||
f, err := os.OpenFile(logPath,
|
f, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0644)
|
||||||
os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0644)
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
logFile = f
|
logFile = f
|
||||||
defer logFile.Close()
|
defer logFile.Close()
|
||||||
@@ -292,9 +286,11 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if gameMem > 0 {
|
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 {
|
} 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 {
|
if err := setPrio(uint32(os.Getpid()), PriorityClassIdle); err != nil {
|
||||||
@@ -303,25 +299,20 @@ func main() {
|
|||||||
logf("own priority set to IDLE")
|
logf("own priority set to IDLE")
|
||||||
}
|
}
|
||||||
|
|
||||||
promoted := make(map[uint32]string)
|
|
||||||
blocked := make(map[uint32]string)
|
blocked := make(map[uint32]string)
|
||||||
gameProcs := make(map[uint32]string)
|
gameProcs := make(map[uint32]string)
|
||||||
|
gameSaved := make(map[uint32]savedPrio)
|
||||||
gameMode := false
|
gameMode := false
|
||||||
|
|
||||||
type savedPrio struct {
|
|
||||||
name string
|
|
||||||
prio uint32
|
|
||||||
}
|
|
||||||
gameSaved := make(map[uint32]savedPrio)
|
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
|
if gameMode {
|
||||||
restoreProcs, _ := allProcs()
|
restoreProcs, _ := allProcs()
|
||||||
restoreMap := make(map[uint32]string, len(restoreProcs))
|
rm := make(map[uint32]string, len(restoreProcs))
|
||||||
for _, p := range restoreProcs {
|
for _, p := range restoreProcs {
|
||||||
restoreMap[p.PID] = p.Name
|
rm[p.PID] = p.Name
|
||||||
}
|
}
|
||||||
for pid, sv := range gameSaved {
|
for pid, sv := range gameSaved {
|
||||||
if cur, ok := restoreMap[pid]; !ok || cur != sv.name {
|
if cur, ok := rm[pid]; !ok || cur != sv.name {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := setPrio(pid, sv.prio); err != nil {
|
if err := setPrio(pid, sv.prio); err != nil {
|
||||||
@@ -330,27 +321,6 @@ func main() {
|
|||||||
logf("RESTORE %s (PID %d) -> %s", sv.name, pid, prioName(sv.prio))
|
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
logf("autoPriority stopped")
|
logf("autoPriority stopped")
|
||||||
}()
|
}()
|
||||||
@@ -362,25 +332,6 @@ 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 {
|
||||||
@@ -393,31 +344,25 @@ func main() {
|
|||||||
pidMap[p.PID] = p.Name
|
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 {
|
for pid := range blocked {
|
||||||
if _, ok := pidMap[pid]; !ok {
|
if _, ok := pidMap[pid]; !ok {
|
||||||
delete(blocked, pid)
|
delete(blocked, pid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for pid, name := range gameProcs {
|
for pid, name := range gameProcs {
|
||||||
if cur, ok := pidMap[pid]; !ok || cur != name {
|
if cur, ok := pidMap[pid]; !ok || cur != name {
|
||||||
delete(gameProcs, pid)
|
delete(gameProcs, pid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for pid, sv := range gameSaved {
|
||||||
type procRSS struct {
|
if cur, ok := pidMap[pid]; !ok || cur != sv.name {
|
||||||
info processInfo
|
delete(gameSaved, pid)
|
||||||
rss uint64
|
}
|
||||||
}
|
}
|
||||||
var procList []procRSS
|
|
||||||
|
|
||||||
for _, p := range procs {
|
var list []procInfo
|
||||||
|
for i := range procs {
|
||||||
|
p := &procs[i]
|
||||||
if p.PID == myPID || p.PID == 0 {
|
if p.PID == myPID || p.PID == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -434,67 +379,56 @@ func main() {
|
|||||||
var m processMemoryCounters
|
var m processMemoryCounters
|
||||||
m.CBM = uint32(unsafe.Sizeof(m))
|
m.CBM = uint32(unsafe.Sizeof(m))
|
||||||
r, _, e := procGetMemInfo.Call(uintptr(h), uintptr(unsafe.Pointer(&m)), uintptr(unsafe.Sizeof(m)))
|
r, _, e := procGetMemInfo.Call(uintptr(h), uintptr(unsafe.Pointer(&m)), uintptr(unsafe.Sizeof(m)))
|
||||||
if r == 0 {
|
|
||||||
closeH(h)
|
closeH(h)
|
||||||
|
if r == 0 {
|
||||||
if _, ok := blocked[p.PID]; !ok {
|
if _, ok := blocked[p.PID]; !ok {
|
||||||
logf("GetProcessMemoryInfo(%d) error: %v", p.PID, e)
|
logf("GetProcessMemoryInfo(%d) error: %v", p.PID, e)
|
||||||
}
|
}
|
||||||
continue
|
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 {
|
if _, ok := gameProcs[p.PID]; !ok {
|
||||||
gameProcs[p.PID] = p.Name
|
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
|
hasGame := gameMem > 0 && len(gameProcs) > 0
|
||||||
|
|
||||||
for _, pr := range procList {
|
for i := range list {
|
||||||
p := pr.info
|
p := &list[i]
|
||||||
rssBytes := pr.rss
|
_, isBlocked := blocked[p.PID]
|
||||||
isBlocked := false
|
_, isGame := gameProcs[p.PID]
|
||||||
if _, ok := blocked[p.PID]; ok {
|
|
||||||
isBlocked = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := gameProcs[p.PID]; ok {
|
if hasGame && isGame {
|
||||||
if hasGame {
|
|
||||||
if isBlocked {
|
if isBlocked {
|
||||||
logf("GAME %s (PID %d) RSS=%s — tracked as game (blocked)", p.Name, p.PID, formatMemSize(rssBytes))
|
continue
|
||||||
|
}
|
||||||
|
if *dryRun {
|
||||||
|
logf("[DRY-RUN] %s (PID %d) RSS=%s — would set HIGH (game)", p.Name, p.PID, formatMemSize(p.RSS))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
h, err := openProc(p.PID, ProcessQueryLimitedInformation)
|
h, err := openProc(p.PID, ProcessQueryLimitedInformation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
origPrio, prioErr := curPrioWithHandle(h)
|
cur, _, _ := procGetPriority.Call(uintptr(h))
|
||||||
if prioErr != nil {
|
|
||||||
closeH(h)
|
closeH(h)
|
||||||
|
if cur != 0 && uint32(cur) == PriorityClassHigh {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if origPrio == PriorityClassHigh {
|
if err := setPrio(p.PID, PriorityClassHigh); err != nil {
|
||||||
closeH(h)
|
blocked[p.PID] = p.Name
|
||||||
continue
|
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))
|
||||||
if *dryRun {
|
|
||||||
closeH(h)
|
|
||||||
logf("[DRY-RUN] %s (PID %d) RSS=%s — would set HIGH (game)",
|
|
||||||
p.Name, p.PID, formatMemSize(rssBytes))
|
|
||||||
} else {
|
} else {
|
||||||
if trySetPrio(h, p.PID, p.Name, PriorityClassHigh) {
|
logf("GAME %s (PID %d) RSS=%s -> HIGH", p.Name, p.PID, formatMemSize(p.RSS))
|
||||||
logf("GAME %s (PID %d) RSS=%s -> HIGH",
|
time.Sleep(100 * time.Millisecond)
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -503,33 +437,55 @@ func main() {
|
|||||||
if isBlocked {
|
if isBlocked {
|
||||||
continue
|
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)
|
h, err := openProc(p.PID, ProcessQueryLimitedInformation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
origPrio, prioErr := curPrioWithHandle(h)
|
cur, _, _ := procGetPriority.Call(uintptr(h))
|
||||||
if prioErr != nil {
|
|
||||||
closeH(h)
|
closeH(h)
|
||||||
|
if cur == 0 || uint32(cur) == PriorityClassIdle {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if origPrio == PriorityClassIdle {
|
if err := setPrio(p.PID, PriorityClassIdle); err != nil {
|
||||||
closeH(h)
|
blocked[p.PID] = p.Name
|
||||||
continue
|
logf("BLOCK %s (PID %d): %v (added to exclusion list)", p.Name, p.PID, err)
|
||||||
}
|
|
||||||
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 {
|
} else {
|
||||||
if trySetPrio(h, p.PID, p.Name, PriorityClassIdle) {
|
|
||||||
if _, saved := gameSaved[p.PID]; !saved {
|
if _, saved := gameSaved[p.PID]; !saved {
|
||||||
gameSaved[p.PID] = savedPrio{name: p.Name, prio: origPrio}
|
gameSaved[p.PID] = savedPrio{name: p.Name, prio: uint32(cur)}
|
||||||
}
|
}
|
||||||
delete(promoted, p.PID)
|
logf("GAME %s (PID %d) RSS=%s, %s -> IDLE", p.Name, p.PID, formatMemSize(p.RSS), prioName(uint32(cur)))
|
||||||
logf("GAME %s (PID %d) RSS=%s, %s -> IDLE",
|
time.Sleep(100 * time.Millisecond)
|
||||||
p.Name, p.PID, formatMemSize(rssBytes), origStr)
|
|
||||||
}
|
}
|
||||||
|
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
|
continue
|
||||||
}
|
}
|
||||||
@@ -537,54 +493,29 @@ func main() {
|
|||||||
if isBlocked {
|
if isBlocked {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
h, err := openProc(p.PID, ProcessQueryLimitedInformation)
|
h, err := openProc(p.PID, ProcessQueryLimitedInformation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
cur, _, _ := procGetPriority.Call(uintptr(h))
|
||||||
if rssBytes < mem {
|
closeH(h)
|
||||||
origPrio, prioErr := curPrioWithHandle(h)
|
if cur != 0 && uint32(cur) == PriorityClassHigh {
|
||||||
if prioErr == nil && isAboveNormal(origPrio) {
|
continue
|
||||||
|
}
|
||||||
|
origStr := ""
|
||||||
|
if cur != 0 {
|
||||||
|
origStr = prioName(uint32(cur))
|
||||||
|
}
|
||||||
if *dryRun {
|
if *dryRun {
|
||||||
closeH(h)
|
logf("[DRY-RUN] %s (PID %d) RSS=%s, priority=%s — would set HIGH", p.Name, p.PID, formatMemSize(p.RSS), origStr)
|
||||||
logf("[DRY-RUN] %s (PID %d) RSS=%s, priority=%s — would set NORMAL",
|
continue
|
||||||
p.Name, p.PID, formatMemSize(rssBytes), prioName(origPrio))
|
}
|
||||||
|
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 {
|
} else {
|
||||||
if trySetPrio(h, p.PID, p.Name, PriorityClassNormal) {
|
logf("PROMOTE %s (PID %d) RSS=%s, %s -> HIGH", p.Name, p.PID, formatMemSize(p.RSS), origStr)
|
||||||
delete(promoted, p.PID)
|
time.Sleep(100 * time.Millisecond)
|
||||||
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)
|
|
||||||
if prioErr != nil {
|
|
||||||
closeH(h)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -598,8 +529,7 @@ func main() {
|
|||||||
gameMode = false
|
gameMode = false
|
||||||
logf("GAME MODE OFF")
|
logf("GAME MODE OFF")
|
||||||
for pid, sv := range gameSaved {
|
for pid, sv := range gameSaved {
|
||||||
cur, ok := pidMap[pid]
|
if cur, ok := pidMap[pid]; !ok || cur != sv.name {
|
||||||
if !ok || cur != sv.name {
|
|
||||||
delete(gameSaved, pid)
|
delete(gameSaved, pid)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -610,6 +540,7 @@ func main() {
|
|||||||
logf("RESTORE %s (PID %d) -> %s error: %v", sv.name, pid, prioName(sv.prio), err)
|
logf("RESTORE %s (PID %d) -> %s error: %v", sv.name, pid, prioName(sv.prio), err)
|
||||||
} else {
|
} else {
|
||||||
logf("RESTORE %s (PID %d) -> %s", sv.name, pid, prioName(sv.prio))
|
logf("RESTORE %s (PID %d) -> %s", sv.name, pid, prioName(sv.prio))
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
delete(gameSaved, pid)
|
delete(gameSaved, pid)
|
||||||
|
|||||||
Reference in New Issue
Block a user