исправлен алгоритм, улучшен вывод, добавлены опции командной строки, дополнен конфиг, дополнен ман
This commit is contained in:
parent
dbcbadb4c3
commit
e2e314474c
37
README.md
37
README.md
@ -2,7 +2,7 @@
|
|||||||
The No Hang Daemon
|
The No Hang Daemon
|
||||||
==================
|
==================
|
||||||
|
|
||||||
`Nohang` - аналог [earlyoom](https://github.com/rfjakob/earlyoom) с поддержкой `zram` и `SIGTERM`. При дефиците доступной памяти `hohang` корректно завершает наиболее прожорливые процессы сигналом `SIGTERM`, тем самым препятствуя зависанию, а также избыточному убийству процессов ядерным `OOM killer`'ом.
|
`Nohang` - аналог [earlyoom](https://github.com/rfjakob/earlyoom) с поддержкой `zram` и `SIGTERM`. При дефиците доступной памяти `nohang` корректно завершает наиболее прожорливые процессы сигналом `SIGTERM`, тем самым препятствуя зависанию, а также избыточному убийству процессов ядерным `OOM killer`'ом.
|
||||||
|
|
||||||
### Зачем это нужно?
|
### Зачем это нужно?
|
||||||
|
|
||||||
@ -25,25 +25,25 @@ https://www.linux.org.ru/forum/talks/12684213?lastmod=1466676523241#comment-1268
|
|||||||
"И IRL ты никогда не знаешь, в какой момент момент твои данные перестанут умещаться в оперативку. Потому zram -- удел embedded систем, где это может быть детерминировано."
|
"И IRL ты никогда не знаешь, в какой момент момент твои данные перестанут умещаться в оперативку. Потому zram -- удел embedded систем, где это может быть детерминировано."
|
||||||
https://2ch.hk/s/res/2310304.html#2311483, https://archive.li/idixk
|
https://2ch.hk/s/res/2310304.html#2311483, https://archive.li/idixk
|
||||||
|
|
||||||
Nohang позволяет избавиться от перечисленных выше проблем, корректно завершая наиболее прожорливые процессы сигналом `SIGTERM` не дожидаясь когда система "встанет колом". `Nohang` позволяет не бояться зависаний при использовании `zram`.
|
`Nohang` позволяет избавиться от перечисленных выше проблем, корректно завершая наиболее прожорливые процессы сигналом `SIGTERM` не дожидаясь когда система "встанет колом". `Nohang` позволяет не бояться зависаний при использовании `zram`.
|
||||||
|
|
||||||
### Зачем нужен nohang, если уже есть earlyoom?
|
### Зачем нужен nohang, если уже есть earlyoom?
|
||||||
|
|
||||||
- `earlyoom` завершает (точнее убивает) процессы исключительно с помощью сигнала `SIGKILL`, в то время как `nohang` дает возможность сначала отправлять `SIGTERM`, и только если процесс не реагирует на `SIGTERM` - отправляется сигнал `SIGKILL`.
|
- `earlyoom` завершает (точнее убивает) процессы исключительно с помощью сигнала `SIGKILL`, в то время как `nohang` дает возможность сначала отправлять `SIGTERM`, и только если процесс не реагирует на `SIGTERM` - отправляется сигнал `SIGKILL`.
|
||||||
- `earlyoom` не поддерживает работу со `zram` и не реагирует на общую долю `zram` в памяти (`mem_used_total`). Это может привести к тому, что система все также встанет колом, как если бы `earlyoom` и не было (если `disksize` большой, а энтропия сжимаемых данных велика). `Nohang` позволяет избавиться от этой проблемы. По умолчанию если доля zram достигнет 60% памяти - будет отправлен сигнал `SIGTERM` процессу с наибольшим `oom_score`.
|
- `earlyoom` не поддерживает работу со `zram` и не реагирует на общую долю `zram` в памяти (`mem_used_total`). Это может привести к тому, что система все также встанет колом, как если бы `earlyoom` и не было (если `disksize` большой, а энтропия сжимаемых данных велика). `Nohang` позволяет избавиться от этой проблемы. По умолчанию если доля `zram` достигнет 60% памяти - будет отправлен сигнал `SIGTERM` процессу с наибольшим `oom_score`.
|
||||||
|
|
||||||
### Особенности
|
### Особенности
|
||||||
- задача - препятствовать зависанию системы при нехватке доступной памяти, а также корректное завершение процессов с целью увеличения объема доступной памяти
|
- задача - препятствовать зависанию системы при нехватке доступной памяти, а также корректное завершение процессов с целью увеличения объема доступной памяти
|
||||||
- демон на python3, RSS около 12 MiB
|
- демон на Python 3, VmRSS около 12 MiB
|
||||||
- требуется Linux 3.14+ и Python 3.4+
|
- требуется `Linux 3.14+` и `Python 3.4+`
|
||||||
- периодически проверяет размеры доступной памяти, при дефиците памяти отправляет `SIGKILL` или `SIGTERM` процессу с наибольшим `oom_score`
|
- периодически проверяет размеры доступной памяти, при дефиците памяти отправляет `SIGKILL` или `SIGTERM` процессу с наибольшим `oom_score`
|
||||||
- поддержка работы со `zram`, возможность реакции на `mem_used_total`
|
- поддержка работы со `zram`, возможность реакции на `mem_used_total`
|
||||||
- удобный конфиг с возможностью тонкой настройки
|
- удобный конфиг с возможностью тонкой настройки
|
||||||
- возможность раздельного задания уровней `MemAvailable`, `SwapFree`, `mem_used_total` для отпраки `SIGTERM` и `SIGKILL`, возможность задания в %, KiB, MiB, GiB
|
- возможность раздельного задания уровней `MemAvailable`, `SwapFree`, `mem_used_total` для отпраки `SIGTERM` и `SIGKILL`, возможность задания в процентах (%), кибибайтах (K), мебибайтах (M), гибибайтах (G)
|
||||||
- возможность снижения `oom_score_adj` процессов, чьи `oom_score_adj` завышены (актуально для chromium)
|
- возможность снижения `oom_score_adj` процессов, чьи `oom_score_adj` завышены (актуально для `chromium`)
|
||||||
- лучший алгоритм выбора периодов между проверками доступной памяти: при больших объемах доступной памяти нет смысла проверять ее состояние часто, поэтому период проверки уменьшается по мере уменьшения размера доступной памяти
|
- лучший алгоритм выбора периодов между проверками доступной памяти: при больших объемах доступной памяти нет смысла проверять ее состояние часто, поэтому период проверки уменьшается по мере уменьшения размера доступной памяти
|
||||||
- интенсивность мониторинга можно гибко настраивать (параметры конфига `rate_mem`, `rate_swap`, `rate_zram`)
|
- интенсивность мониторинга можно гибко настраивать (параметры конфига `rate_mem`, `rate_swap`, `rate_zram`)
|
||||||
- память заблокирована с помощью `mlockall()` для предотвращения своппинга процесса
|
- по умолчанию память заблокирована с помощью `mlockall()` для предотвращения своппинга процесса
|
||||||
- по умолчанию высокий приоритет процесса `nice -20`, может регулироваться через конфиг
|
- по умолчанию высокий приоритет процесса `nice -20`, может регулироваться через конфиг
|
||||||
- предотвращение самоубийства с помощью `self_oom_score_adj = -1000`
|
- предотвращение самоубийства с помощью `self_oom_score_adj = -1000`
|
||||||
- возможность задания `oom_score_min` для предотвращения убийства невиновных
|
- возможность задания `oom_score_min` для предотвращения убийства невиновных
|
||||||
@ -51,12 +51,20 @@ Nohang позволяет избавиться от перечисленных
|
|||||||
- наличие `man` страницы
|
- наличие `man` страницы
|
||||||
- наличие установщика для пользователей `systemd`
|
- наличие установщика для пользователей `systemd`
|
||||||
- протестировано на `Debian 9 x86_64`, `Debian 8 i386`, `Fedora 28 x86_64`
|
- протестировано на `Debian 9 x86_64`, `Debian 8 i386`, `Fedora 28 x86_64`
|
||||||
- вывод отчета об убийствах такого вида
|
- пример вывода с отчетом об успешной отпраке сигнала:
|
||||||
```
|
```
|
||||||
2018-Jun-07 04:55:16 Mem: 0 M, Swap: 454 M, Zram: 488 M
|
MemAvail: 0M 0.0%, SwapFree: 985M 8.4%, MemUsedZram: 625M 10.6%
|
||||||
mem_available < mem_term_level and swap_free < swap_term_level
|
MemAvail: 0M 0.0%, SwapFree: 962M 8.2%, MemUsedZram: 626M 10.6%
|
||||||
Try to send signal 15 to process python3, Pid 7281, oom_score 893
|
MemAvail: 9M 0.1%, SwapFree: 939M 8.0%, MemUsedZram: 626M 10.7%
|
||||||
|
MemAvailable 9M < 353M and SwapFree 939M < 940M
|
||||||
|
Try to send signal 15 to process python3, Pid 3392, oom_score 818
|
||||||
Success
|
Success
|
||||||
|
MemAvail: 29M 0.5%, SwapFree: 2866M 24.4%, MemUsedZram: 582M 9.9%
|
||||||
|
MemAvail: 77M 1.3%, SwapFree: 5037M 42.9%, MemUsedZram: 532M 9.1%
|
||||||
|
MemAvail: 168M 2.9%, SwapFree: 8956M 76.2%, MemUsedZram: 441M 7.5%
|
||||||
|
MemAvail: 5006M 85.2%, SwapFree: 10632M 90.5%, MemUsedZram: 356M 6.1%
|
||||||
|
MemAvail: 5000M 85.1%, SwapFree: 10633M 90.5%, MemUsedZram: 356M 6.1%
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Установка и удаление для пользователей systemd
|
### Установка и удаление для пользователей systemd
|
||||||
@ -78,7 +86,7 @@ sudo ./uninstall.sh
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Настройка
|
### Настройка
|
||||||
Nohang настраивается с помощью [конфига](https://github.com/hakavlad/nohang/blob/master/nohang.conf), расположенного после установки
|
`Nohang` настраивается с помощью [конфига](https://github.com/hakavlad/nohang/blob/master/nohang.conf), расположенного после установки
|
||||||
по адресу
|
по адресу
|
||||||
```
|
```
|
||||||
/etc/nohang/nohang.conf
|
/etc/nohang/nohang.conf
|
||||||
@ -86,6 +94,5 @@ Nohang настраивается с помощью [конфига](https://git
|
|||||||
К опциям прилагается описание. Отредактируйте значения параметров в соответствии с вашими предпочтениями.
|
К опциям прилагается описание. Отредактируйте значения параметров в соответствии с вашими предпочтениями.
|
||||||
|
|
||||||
### Известные баги
|
### Известные баги
|
||||||
На самом деле алгоритм кривой, и киллер может вовремя не запуститься. Дождитесь исправлений. Текущая версия программы - пререлизная, ждите выпуска стабильной 0.1 версии.
|
Известных нет, если найдете - пишите в [Issues](https://github.com/hakavlad/nohang/issues).
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
#!/bin/bash -v
|
#!/bin/bash -v
|
||||||
|
|
||||||
cp nohang /usr/local/bin/
|
cp -f nohang /usr/local/bin/
|
||||||
chmod 755 /usr/local/bin/nohang
|
chmod 755 /usr/local/bin/nohang
|
||||||
|
|
||||||
mkdir /etc/nohang
|
mkdir /etc/nohang
|
||||||
chmod 755 /etc/nohang
|
chmod 755 /etc/nohang
|
||||||
cp nohang.conf /etc/nohang/
|
cp -f nohang.conf /etc/nohang/
|
||||||
chmod 644 /etc/nohang/nohang.conf
|
chmod 644 /etc/nohang/nohang.conf
|
||||||
|
|
||||||
gzip -k nohang.1
|
gzip -k nohang.1
|
||||||
mkdir /usr/local/share/man/man1
|
mkdir /usr/local/share/man/man1
|
||||||
chmod 755 /usr/local/share/man/man1
|
chmod 755 /usr/local/share/man/man1
|
||||||
cp nohang.1.gz /usr/local/share/man/man1/
|
cp -f nohang.1.gz /usr/local/share/man/man1/
|
||||||
chmod 644 /usr/local/share/man/man1/nohang.1.gz
|
chmod 644 /usr/local/share/man/man1/nohang.1.gz
|
||||||
|
|
||||||
cp nohang.service /etc/systemd/system/
|
cp -f nohang.service /etc/systemd/system/
|
||||||
chmod 644 /etc/systemd/system/nohang.service
|
chmod 644 /etc/systemd/system/nohang.service
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
systemctl enable nohang
|
systemctl enable nohang
|
||||||
|
251
nohang
251
nohang
@ -1,29 +1,50 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
# nohang - no hang daemon
|
# Nohang - No Hang Daemon
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from ctypes import CDLL
|
from ctypes import CDLL
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
from signal import SIGKILL, SIGTERM
|
from signal import SIGKILL, SIGTERM
|
||||||
from time import gmtime, strftime, sleep, time
|
from time import gmtime, strftime, sleep, time
|
||||||
|
import argparse
|
||||||
|
|
||||||
if os.path.exists('./nohang.conf'):
|
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument(
|
||||||
|
'-c',
|
||||||
|
'--config',
|
||||||
|
help='path to config file, default values: ./nohang.conf, /etc/nohang/nohang.conf',
|
||||||
|
default=None,
|
||||||
|
type=str
|
||||||
|
)
|
||||||
|
arg_config = parser.parse_args().config
|
||||||
|
|
||||||
|
|
||||||
|
# ветвление требует переработки и лучшей обработки исключений
|
||||||
|
if arg_config != None:
|
||||||
|
if os.path.exists:
|
||||||
|
config = arg_config
|
||||||
|
else:
|
||||||
|
print('Указанный конфиг файл {} не существует!'.format(arg_config))
|
||||||
|
exit()
|
||||||
|
elif os.path.exists('./nohang.conf'):
|
||||||
config = './nohang.conf'
|
config = './nohang.conf'
|
||||||
print('config: {}'.format(config))
|
|
||||||
elif os.path.exists('/etc/nohang/nohang.conf'):
|
elif os.path.exists('/etc/nohang/nohang.conf'):
|
||||||
config = '/etc/nohang/nohang.conf'
|
config = '/etc/nohang/nohang.conf'
|
||||||
print('config: {}'.format(config))
|
|
||||||
else:
|
else:
|
||||||
print('укажите путь к конфигу опцией --config')
|
print('Пожалуйста, укажите путь к конфигу опцией -c или --config')
|
||||||
exit()
|
exit()
|
||||||
|
|
||||||
|
# найден экспериментально, требует уточнения с разными ядрами и архитектурами
|
||||||
zram_disksize_factor = 0.0042
|
zram_disksize_factor = 0.0042
|
||||||
|
|
||||||
|
|
||||||
###########################################################################################
|
###########################################################################################
|
||||||
|
|
||||||
|
|
||||||
def decrease_oom_score_adj(oom_score_adj_before, oom_score_adj_after):
|
def decrease_oom_score_adj(oom_score_adj_before, oom_score_adj_after):
|
||||||
print('decrease oom_score_adj...')
|
#print('Decrease oom_score_adj...')
|
||||||
# цикл для наполнения oom_list
|
# цикл для наполнения oom_list
|
||||||
for i in os.listdir('/proc'):
|
for i in os.listdir('/proc'):
|
||||||
|
|
||||||
@ -40,6 +61,7 @@ def decrease_oom_score_adj(oom_score_adj_before, oom_score_adj_after):
|
|||||||
except ProcessLookupError:
|
except ProcessLookupError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
# чтение первой строки файла
|
# чтение первой строки файла
|
||||||
def rline1(path):
|
def rline1(path):
|
||||||
with open(path) as f:
|
with open(path) as f:
|
||||||
@ -53,6 +75,7 @@ def write(path, string):
|
|||||||
f.write(string)
|
f.write(string)
|
||||||
|
|
||||||
|
|
||||||
|
# возвращает словарь с параметрами из конфига
|
||||||
def config_parser(config):
|
def config_parser(config):
|
||||||
if os.path.exists(config):
|
if os.path.exists(config):
|
||||||
try:
|
try:
|
||||||
@ -73,24 +96,10 @@ def config_parser(config):
|
|||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
def sig_level_to_kb(string):
|
def to_percent(num):
|
||||||
if string.endswith('%'):
|
return str(round(num * 100, 1)).rjust(5, ' ')
|
||||||
return mem_total * float(string[:-1].strip()) / 100
|
|
||||||
if string.endswith('KiB'):
|
|
||||||
return float(string[:-3].strip())
|
|
||||||
if string.endswith('MiB'):
|
|
||||||
return float(string[:-3].strip()) * 1024
|
|
||||||
if string.endswith('GiB'):
|
|
||||||
return float(string[:-3].strip()) * 1048576
|
|
||||||
|
|
||||||
|
|
||||||
# перевод дроби в проценты
|
|
||||||
def percent(num):
|
|
||||||
a = str(round(num * 100, 1)).split('.')
|
|
||||||
a0 = a[0].rjust(3, ' ')
|
|
||||||
a1 = a[1]
|
|
||||||
return '{}.{}'.format(a0, a1)
|
|
||||||
|
|
||||||
# K -> M, выравнивание по правому краю
|
# K -> M, выравнивание по правому краю
|
||||||
def human(num):
|
def human(num):
|
||||||
return str(round(num / 1024)).rjust(5, ' ')
|
return str(round(num / 1024)).rjust(5, ' ')
|
||||||
@ -148,7 +157,7 @@ def find_victim(signal):
|
|||||||
if decrease_oom_score_adj_enable and root:
|
if decrease_oom_score_adj_enable and root:
|
||||||
decrease_oom_score_adj(oom_score_adj_before, oom_score_adj_after)
|
decrease_oom_score_adj(oom_score_adj_before, oom_score_adj_after)
|
||||||
|
|
||||||
print('find victim...')
|
#print('Find victim...')
|
||||||
|
|
||||||
oom_list = []
|
oom_list = []
|
||||||
|
|
||||||
@ -184,15 +193,15 @@ def find_victim(signal):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
os.kill(int(pid), signal)
|
os.kill(int(pid), signal)
|
||||||
print('Success\n')
|
print('Success')
|
||||||
except ProcessLookupError:
|
except ProcessLookupError:
|
||||||
print('No such process\n')
|
print('No such process')
|
||||||
except PermissionError:
|
except PermissionError:
|
||||||
print('Operation not permitted\n')
|
print('Operation not permitted')
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
print('\noom_score {} < oom_score_min {}\n'.format(oom_score, oom_score_min))
|
print('oom_score {} < oom_score_min {}'.format(oom_score, oom_score_min))
|
||||||
|
|
||||||
|
|
||||||
###########################################################################################
|
###########################################################################################
|
||||||
@ -202,7 +211,6 @@ def find_victim(signal):
|
|||||||
|
|
||||||
|
|
||||||
# ищем позиции
|
# ищем позиции
|
||||||
|
|
||||||
with open('/proc/meminfo') as file:
|
with open('/proc/meminfo') as file:
|
||||||
mem_list = file.readlines()
|
mem_list = file.readlines()
|
||||||
|
|
||||||
@ -219,13 +227,11 @@ swap_free_index = swap_total_index + 1
|
|||||||
|
|
||||||
mem_total = int(mem_list[0].split(':')[1].split(' ')[-2])
|
mem_total = int(mem_list[0].split(':')[1].split(' ')[-2])
|
||||||
|
|
||||||
###############################################################################################
|
|
||||||
|
|
||||||
|
|
||||||
config_dict = config_parser(config)
|
config_dict = config_parser(config)
|
||||||
|
|
||||||
print(config_dict, '\n')
|
|
||||||
|
|
||||||
|
|
||||||
if config_dict is 1:
|
if config_dict is 1:
|
||||||
print('config {} does not exists'.format(config))
|
print('config {} does not exists'.format(config))
|
||||||
|
|
||||||
@ -277,12 +283,6 @@ else:
|
|||||||
swap_min_sigkill = config_dict['swap_min_sigkill']
|
swap_min_sigkill = config_dict['swap_min_sigkill']
|
||||||
print('swap_min_sigkill: {}'.format(swap_min_sigkill))
|
print('swap_min_sigkill: {}'.format(swap_min_sigkill))
|
||||||
|
|
||||||
if 'check_zram' in config_dict:
|
|
||||||
check_zram = config_dict['check_zram']
|
|
||||||
if check_zram == 'yes':
|
|
||||||
check_zram = True
|
|
||||||
print('check_zram: {}'.format(check_zram))
|
|
||||||
|
|
||||||
if 'zram_max_sigterm' in config_dict:
|
if 'zram_max_sigterm' in config_dict:
|
||||||
zram_max_sigterm = config_dict['zram_max_sigterm']
|
zram_max_sigterm = config_dict['zram_max_sigterm']
|
||||||
print('zram_max_sigterm: {}'.format(zram_max_sigterm))
|
print('zram_max_sigterm: {}'.format(zram_max_sigterm))
|
||||||
@ -295,7 +295,6 @@ else:
|
|||||||
min_delay_after_sigterm = float(config_dict['min_delay_after_sigterm'])
|
min_delay_after_sigterm = float(config_dict['min_delay_after_sigterm'])
|
||||||
print('min_delay_after_sigterm: {}'.format(min_delay_after_sigterm))
|
print('min_delay_after_sigterm: {}'.format(min_delay_after_sigterm))
|
||||||
|
|
||||||
|
|
||||||
if 'min_delay_after_sigkill' in config_dict:
|
if 'min_delay_after_sigkill' in config_dict:
|
||||||
min_delay_after_sigkill = float(config_dict['min_delay_after_sigkill'])
|
min_delay_after_sigkill = float(config_dict['min_delay_after_sigkill'])
|
||||||
print('min_delay_after_sigkill: {}'.format(min_delay_after_sigkill))
|
print('min_delay_after_sigkill: {}'.format(min_delay_after_sigkill))
|
||||||
@ -318,54 +317,70 @@ else:
|
|||||||
oom_score_adj_after = config_dict['oom_score_adj_after']
|
oom_score_adj_after = config_dict['oom_score_adj_after']
|
||||||
print('oom_score_adj_after: {}'.format(oom_score_adj_after))
|
print('oom_score_adj_after: {}'.format(oom_score_adj_after))
|
||||||
|
|
||||||
if 'use_lists' in config_dict:
|
|
||||||
use_lists = config_dict['use_lists']
|
|
||||||
if use_lists == 'yes':
|
|
||||||
use_lists = True
|
|
||||||
print('use_lists: {}'.format(use_lists))
|
|
||||||
|
|
||||||
if 'white_list' in config_dict:
|
|
||||||
white_list = config_dict['white_list'].split(',')
|
|
||||||
for i in range(len(white_list)):
|
|
||||||
white_list[i] = white_list[i].strip()
|
|
||||||
print('white_list: {}'.format(white_list))
|
|
||||||
|
|
||||||
if 'avoid_list' in config_dict:
|
|
||||||
avoid_list = config_dict['avoid_list'].split(',')
|
|
||||||
for i in range(len(avoid_list)):
|
|
||||||
avoid_list[i] = avoid_list[i].strip()
|
|
||||||
print('avoid_list: {}'.format(avoid_list))
|
|
||||||
|
|
||||||
if 'avoid_ratio' in config_dict:
|
|
||||||
avoid_ratio = float(config_dict['avoid_ratio'])
|
|
||||||
print('avoid_ratio: {}'.format(avoid_ratio))
|
|
||||||
|
|
||||||
if 'black_list' in config_dict:
|
|
||||||
black_list = config_dict['black_list'].split(',')
|
|
||||||
for i in range(len(black_list)):
|
|
||||||
black_list[i] = black_list[i].strip()
|
|
||||||
print('black_list: {}'.format(black_list))
|
|
||||||
|
|
||||||
if 'prefer_list' in config_dict:
|
|
||||||
prefer_list = config_dict['prefer_list'].split(',')
|
|
||||||
for i in range(len(prefer_list)):
|
|
||||||
prefer_list[i] = prefer_list[i].strip()
|
|
||||||
print('prefer_list: {}'.format(prefer_list))
|
|
||||||
|
|
||||||
if 'prefer_ratio' in config_dict:
|
|
||||||
prefer_ratio = float(config_dict['prefer_ratio'])
|
|
||||||
print('prefer_ratio: {}\n'.format(prefer_ratio))
|
|
||||||
|
|
||||||
|
def sig_level_to_kb(string):
|
||||||
|
if string.endswith('%'):
|
||||||
|
return float(string[:-1].strip()) / 100 * mem_total
|
||||||
|
if string.endswith('K'):
|
||||||
|
return float(string[:-1].strip())
|
||||||
|
if string.endswith('M'):
|
||||||
|
return float(string[:-1].strip()) * 1024
|
||||||
|
if string.endswith('G'):
|
||||||
|
return float(string[:-1].strip()) * 1048576
|
||||||
|
|
||||||
mem_min_sigterm_kb = sig_level_to_kb(mem_min_sigterm)
|
mem_min_sigterm_kb = sig_level_to_kb(mem_min_sigterm)
|
||||||
mem_min_sigkill_kb = sig_level_to_kb(mem_min_sigkill)
|
mem_min_sigkill_kb = sig_level_to_kb(mem_min_sigkill)
|
||||||
swap_min_sigterm_kb = sig_level_to_kb(swap_min_sigterm)
|
|
||||||
swap_min_sigkill_kb = sig_level_to_kb(swap_min_sigkill)
|
|
||||||
zram_max_sigterm_kb = sig_level_to_kb(zram_max_sigterm)
|
zram_max_sigterm_kb = sig_level_to_kb(zram_max_sigterm)
|
||||||
zram_max_sigkill_kb = sig_level_to_kb(zram_max_sigkill)
|
zram_max_sigkill_kb = sig_level_to_kb(zram_max_sigkill)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# возвращает число килобайт при задании в конфиге абсолютного значения,
|
||||||
|
# или кортеж с числом процентов
|
||||||
|
def sig_level_to_kb_swap(string):
|
||||||
|
|
||||||
|
if string.endswith('%'):
|
||||||
|
return float(string[:-1].strip()), True
|
||||||
|
|
||||||
|
if string.endswith('K'):
|
||||||
|
return float(string[:-1].strip())
|
||||||
|
if string.endswith('M'):
|
||||||
|
return float(string[:-1].strip()) * 1024
|
||||||
|
if string.endswith('G'):
|
||||||
|
return float(string[:-1].strip()) * 1048576
|
||||||
|
|
||||||
|
# получаем число килобайт или кортеж с процентами
|
||||||
|
swap_min_sigterm_swap = sig_level_to_kb_swap(swap_min_sigterm)
|
||||||
|
swap_min_sigkill_swap = sig_level_to_kb_swap(swap_min_sigkill)
|
||||||
|
|
||||||
|
if type(swap_min_sigterm_swap) is tuple:
|
||||||
|
swap_term_is_percent = True
|
||||||
|
swap_min_sigterm_percent = swap_min_sigterm_swap[0]
|
||||||
|
else:
|
||||||
|
swap_term_is_percent = False
|
||||||
|
swap_min_sigterm_kb = swap_min_sigterm_swap
|
||||||
|
|
||||||
|
if type(swap_min_sigkill_swap) is tuple:
|
||||||
|
swap_kill_is_percent = True
|
||||||
|
swap_min_sigkill_percent = swap_min_sigkill_swap[0]
|
||||||
|
else:
|
||||||
|
swap_kill_is_percent = False
|
||||||
|
swap_min_sigkill_kb = swap_min_sigkill_swap
|
||||||
|
|
||||||
|
|
||||||
|
# print('\nswap term is percent:', swap_term_is_percent)
|
||||||
|
# print('swap kill is percent:', swap_kill_is_percent)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
print("\ncurrent process's effective user id", os.geteuid())
|
print("\ncurrent process's effective user id", os.geteuid())
|
||||||
|
|
||||||
if os.geteuid() == 0:
|
if os.geteuid() == 0:
|
||||||
@ -408,6 +423,8 @@ except OSError:
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def kib_to_mib(num):
|
||||||
|
return round(num / 1024.0)
|
||||||
|
|
||||||
###########################################################################################
|
###########################################################################################
|
||||||
|
|
||||||
@ -430,8 +447,15 @@ while True:
|
|||||||
swap_free = int(line.split(':')[1].split(' ')[-2])
|
swap_free = int(line.split(':')[1].split(' ')[-2])
|
||||||
break
|
break
|
||||||
|
|
||||||
|
if swap_kill_is_percent:
|
||||||
|
swap_min_sigkill_kb = swap_total * swap_min_sigkill_percent / 100.0
|
||||||
|
|
||||||
# тут находим фулл зрам
|
if swap_term_is_percent:
|
||||||
|
swap_min_sigterm_kb = swap_total * swap_min_sigterm_percent / 100.0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# находим MemUsedZram
|
||||||
disksize_sum = 0
|
disksize_sum = 0
|
||||||
mem_used_total_sum = 0
|
mem_used_total_sum = 0
|
||||||
|
|
||||||
@ -444,67 +468,90 @@ while True:
|
|||||||
disksize_sum += int(stat[0])
|
disksize_sum += int(stat[0])
|
||||||
mem_used_total_sum += int(stat[1])
|
mem_used_total_sum += int(stat[1])
|
||||||
|
|
||||||
full_zram = (
|
mem_used_zram = (
|
||||||
mem_used_total_sum + disksize_sum * zram_disksize_factor
|
mem_used_total_sum + disksize_sum * zram_disksize_factor
|
||||||
) / 1024.0
|
) / 1024.0
|
||||||
|
|
||||||
|
|
||||||
# если не печатать периоды, то можно это вынести в конец
|
|
||||||
t_mem = mem_available / 1024.0 / 1024.0 / rate_mem
|
|
||||||
|
|
||||||
t_swap = swap_free / 1024.0 / 1024.0 / rate_swap
|
|
||||||
|
|
||||||
# fullzram может превысить 09, будет отриц значение
|
t_mem = mem_available / 1000000.0 / rate_mem
|
||||||
# memtotal * 0.9 - это фактически макс память для зрам
|
t_swap = swap_free / 10000000.0 / rate_swap
|
||||||
t_zram = (mem_total * 0.8 - full_zram) / 1024.0 / 1024.0 / rate_zram
|
t_zram = (mem_total * 0.8 - mem_used_zram) / 1000000.0 / rate_zram
|
||||||
if t_zram <= 0:
|
if t_zram < 0.01:
|
||||||
t_zram = 0.01
|
t_zram = 0.01
|
||||||
|
|
||||||
t1 = t_mem + t_swap
|
t_mem_swap = t_mem + t_swap
|
||||||
t2 = t_mem + t_zram
|
t_mem_zram = t_mem + t_zram
|
||||||
|
|
||||||
# используем наименьший
|
if t_mem_swap <= t_mem_zram:
|
||||||
if t1 <= t2:
|
t = t_mem_swap
|
||||||
t = t1
|
|
||||||
else:
|
else:
|
||||||
t = t2
|
t = t_mem_zram
|
||||||
|
|
||||||
|
|
||||||
|
# печать состояния памяти
|
||||||
print(
|
print(
|
||||||
'{} Mem: {} M, Swap: {} M, Zram: {} M'.format(
|
'MemAvail: {}M {}%, SwapFree: {}M {}%, MemUsedZram: {}M {}%'.format(
|
||||||
strftime("%Y-%b-%d %H:%M:%S", gmtime()),
|
|
||||||
human(mem_available),
|
human(mem_available),
|
||||||
|
to_percent(mem_available / mem_total),
|
||||||
human(swap_free),
|
human(swap_free),
|
||||||
human(full_zram)
|
to_percent(swap_free / (swap_total + 0.0001)),
|
||||||
|
human(mem_used_zram),
|
||||||
|
to_percent(mem_used_zram / mem_total)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# MEM SWAP KILL
|
||||||
if mem_available <= mem_min_sigkill_kb and swap_free <= swap_min_sigkill_kb:
|
if mem_available <= mem_min_sigkill_kb and swap_free <= swap_min_sigkill_kb:
|
||||||
print('mem_available < mem_min_sigkill and swap_free < swap_min_sigkill')
|
print(
|
||||||
|
'MemAvailable {}M < {}M and SwapFree {}M < {}M'.format(
|
||||||
|
kib_to_mib(mem_available),
|
||||||
|
kib_to_mib(mem_min_sigkill_kb),
|
||||||
|
kib_to_mib(swap_free),
|
||||||
|
kib_to_mib(swap_min_sigkill_kb)
|
||||||
|
)
|
||||||
|
)
|
||||||
find_victim(SIGKILL)
|
find_victim(SIGKILL)
|
||||||
sleep(min_delay_after_sigkill)
|
sleep(min_delay_after_sigkill)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if full_zram >= zram_max_sigkill_kb:
|
# MEM ZRAM KILL
|
||||||
print('full_zram > zram_max_sigkill')
|
if mem_used_zram >= zram_max_sigkill_kb:
|
||||||
|
print(
|
||||||
|
'MemUsedZram {}M > {}M'.format(
|
||||||
|
kib_to_mib(mem_used_zram),
|
||||||
|
kib_to_mib(zram_max_sigkill_kb)
|
||||||
|
)
|
||||||
|
)
|
||||||
find_victim(SIGKILL)
|
find_victim(SIGKILL)
|
||||||
sleep(min_delay_after_sigkill)
|
sleep(min_delay_after_sigkill)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# MEM SWAP TERM
|
||||||
if mem_available <= mem_min_sigterm_kb and swap_free <= swap_min_sigterm_kb:
|
if mem_available <= mem_min_sigterm_kb and swap_free <= swap_min_sigterm_kb:
|
||||||
print('mem_available < mem_min_sigterm and swap_free < swap_min_sigterm')
|
print(
|
||||||
|
'MemAvailable {}M < {}M and SwapFree {}M < {}M'.format(
|
||||||
|
kib_to_mib(mem_available),
|
||||||
|
kib_to_mib(mem_min_sigterm_kb),
|
||||||
|
kib_to_mib(swap_free),
|
||||||
|
kib_to_mib(swap_min_sigterm_kb)
|
||||||
|
)
|
||||||
|
)
|
||||||
find_victim(SIGTERM)
|
find_victim(SIGTERM)
|
||||||
sleep(min_delay_after_sigterm)
|
sleep(min_delay_after_sigterm)
|
||||||
|
|
||||||
if full_zram >= zram_max_sigterm_kb:
|
# MEM ZRAM TERM
|
||||||
print('zram_part > zram_max_sigterm')
|
if mem_used_zram >= zram_max_sigterm_kb:
|
||||||
|
print(
|
||||||
|
'MemUsedZram {}M > {}M'.format(
|
||||||
|
kib_to_mib(mem_used_zram),
|
||||||
|
kib_to_mib(zram_max_sigterm_kb)
|
||||||
|
)
|
||||||
|
)
|
||||||
find_victim(SIGTERM)
|
find_victim(SIGTERM)
|
||||||
sleep(min_delay_after_sigterm)
|
sleep(min_delay_after_sigterm)
|
||||||
# вариант - перенести задержку в фц поиска жертв
|
|
||||||
|
|
||||||
sleep(t)
|
sleep(t)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
16
nohang.1
16
nohang.1
@ -2,11 +2,21 @@
|
|||||||
.SH NAME
|
.SH NAME
|
||||||
nohang \- no hang daemon
|
nohang \- no hang daemon
|
||||||
|
|
||||||
|
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
.B nohang
|
.B nohang
|
||||||
.RB [ OPTION ]...
|
.RB [ OPTION ]...
|
||||||
|
|
||||||
|
|
||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
See https://github.com/hakavlad/nohang
|
Nohang - аналог earlyoom с поддержкой zram и SIGTERM. При дефиците доступной памяти nohang корректно завершает наиболее прожорливые процессы сигналом SIGTERM, тем самым препятствуя зависанию, а также избыточному убийству процессов ядерным OOM killer'ом. See https://github.com/hakavlad/nohang
|
||||||
|
|
||||||
|
.SH OPTIONS
|
||||||
|
.TP
|
||||||
|
.BI \-c " CONFIG",
|
||||||
|
.BI \-\-config " CONFIG"
|
||||||
|
path to config file, default values:
|
||||||
|
.I ./nohang.conf,
|
||||||
|
.I /etc/nohang/nohang.conf
|
||||||
|
.TP
|
||||||
|
.BI \-h,
|
||||||
|
.BI \-\-help
|
||||||
|
show help message and exit
|
||||||
|
27
nohang.conf
27
nohang.conf
@ -36,6 +36,16 @@ self_oom_score_adj = -1000
|
|||||||
Уменьшение коэффициентов способно снизить нагрузку на
|
Уменьшение коэффициентов способно снизить нагрузку на
|
||||||
прцессор и увеличить периоды между проверками памяти.
|
прцессор и увеличить периоды между проверками памяти.
|
||||||
|
|
||||||
|
Почему три коэффициента, а не один? - Потому что
|
||||||
|
скорость наполнения свопа обычно ниже скорости наполнения RAM.
|
||||||
|
Можно для свопа задать более низкую интенсивность
|
||||||
|
мониторинга без ущерба для предотвращения нехватки памяти
|
||||||
|
и тем самым снизить нагрузку на процессор.
|
||||||
|
|
||||||
|
В дефолтных настройках на данной интенсивности демон работает
|
||||||
|
очень хорошо, перехватывая резкие скачки потребления памяти.
|
||||||
|
Можете тестировать
|
||||||
|
|
||||||
rate_mem = 6
|
rate_mem = 6
|
||||||
rate_swap = 2
|
rate_swap = 2
|
||||||
rate_zram = 1
|
rate_zram = 1
|
||||||
@ -44,10 +54,16 @@ rate_zram = 1
|
|||||||
|
|
||||||
Задание уровней доступной памяти, ниже которых происходит
|
Задание уровней доступной памяти, ниже которых происходит
|
||||||
отправка сигналов SIGTERM или SIGKILL.
|
отправка сигналов SIGTERM или SIGKILL.
|
||||||
|
|
||||||
Сигнал отправляется если MemAvailable и SwapFree одновременно
|
Сигнал отправляется если MemAvailable и SwapFree одновременно
|
||||||
опустятся ниже соответствующих значений.
|
опустятся ниже соответствующих значений.
|
||||||
|
|
||||||
Значения могут быть выражены в процентах (%),
|
Значения могут быть выражены в процентах (%),
|
||||||
кибибайтах (KiB), мебибайтах (MiB) или гибибайтах (GiB).
|
кибибайтах (K), мебибайтах (M) или гибибайтах (G).
|
||||||
|
Например:
|
||||||
|
swap_min_sigterm = 8 %
|
||||||
|
mem_min_sigterm = 0.5 G
|
||||||
|
swap_min_sigkill = 200 M
|
||||||
|
|
||||||
mem_min_sigterm = 6 %
|
mem_min_sigterm = 6 %
|
||||||
mem_min_sigkill = 3 %
|
mem_min_sigkill = 3 %
|
||||||
@ -60,7 +76,7 @@ swap_min_sigkill = 4 %
|
|||||||
система виснет или запускается OOM killer.
|
система виснет или запускается OOM killer.
|
||||||
По мере увеличения доли zram в памяти может падать
|
По мере увеличения доли zram в памяти может падать
|
||||||
отзывчивость системы.
|
отзывчивость системы.
|
||||||
Может также задаваться в %, KiB, MiB, GiB
|
Может также задаваться в %, K, M, G
|
||||||
|
|
||||||
zram_max_sigterm = 60 %
|
zram_max_sigterm = 60 %
|
||||||
zram_max_sigkill = 65 %
|
zram_max_sigkill = 65 %
|
||||||
@ -100,6 +116,11 @@ decrease_oom_score_adj_enable = no
|
|||||||
oom_score_adj_before = 50
|
oom_score_adj_before = 50
|
||||||
oom_score_adj_after = 10
|
oom_score_adj_after = 10
|
||||||
|
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
#####################################################################
|
||||||
|
#####################################################################
|
||||||
|
#####################################################################
|
||||||
#####################################################################
|
#####################################################################
|
||||||
|
|
||||||
Значения по умолчанию
|
Значения по умолчанию
|
||||||
@ -124,7 +145,7 @@ oom_score_adj_after = 10
|
|||||||
min_delay_after_sigterm = 0.1
|
min_delay_after_sigterm = 0.1
|
||||||
min_delay_after_sigkill = 3
|
min_delay_after_sigkill = 3
|
||||||
|
|
||||||
decrease_oom_score_adj_enable = yes
|
decrease_oom_score_adj_enable = no
|
||||||
oom_score_adj_before = 50
|
oom_score_adj_before = 50
|
||||||
oom_score_adj_after = 10
|
oom_score_adj_after = 10
|
||||||
|
|
||||||
|
8
purge.sh
8
purge.sh
@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash -v
|
#!/bin/bash -v
|
||||||
systemctl stop nohang
|
systemctl stop nohang
|
||||||
systemctl disable nohang
|
systemctl disable nohang
|
||||||
rm /usr/local/share/man/man1/nohang.1.gz
|
rm -f /usr/local/share/man/man1/nohang.1.gz
|
||||||
rm /etc/systemd/system/nohang.service
|
rm -f /etc/systemd/system/nohang.service
|
||||||
rm -r /etc/nohang
|
rm -rf /etc/nohang
|
||||||
rm /usr/local/bin/nohang
|
rm -f /usr/local/bin/nohang
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#!/bin/bash -v
|
#!/bin/bash -v
|
||||||
systemctl stop nohang
|
systemctl stop nohang
|
||||||
systemctl disable nohang
|
systemctl disable nohang
|
||||||
rm /usr/local/share/man/man1/nohang.1.gz
|
rm -f /usr/local/share/man/man1/nohang.1.gz
|
||||||
rm /etc/systemd/system/nohang.service
|
rm -f /etc/systemd/system/nohang.service
|
||||||
rm /usr/local/bin/nohang
|
rm -f /usr/local/bin/nohang
|
||||||
|
Loading…
Reference in New Issue
Block a user