выпилены дестабилизирующие и невостребованные опции, добавлено отображение в уведомлениях VmRSS и VmSwap получившего сигнал процесса
This commit is contained in:
parent
696eae2dc7
commit
455b16aedd
17
README.md
17
README.md
@ -2,7 +2,7 @@
|
||||
The No Hang Daemon
|
||||
==================
|
||||
|
||||
`Nohang` - аналог [earlyoom](https://github.com/rfjakob/earlyoom) с поддержкой `zram` и `SIGTERM`. При дефиците доступной памяти `nohang` корректно завершает наиболее прожорливые процессы сигналом `SIGTERM` или `SIGKILL`, тем самым препятствуя зависанию, а также избыточному убийству процессов ядерным `OOM killer`'ом.
|
||||
`Nohang` - аналог [earlyoom](https://github.com/rfjakob/earlyoom) с поддержкой `zram` и `SIGTERM`. При дефиците доступной памяти `nohang` корректно завершает наиболее прожорливые процессы сигналом `SIGTERM` или `SIGKILL`, тем самым препятствуя [зависанию](https://en.wikipedia.org/wiki/Hang_(computing)), а также избыточному убийству процессов ядерным `OOM killer`'ом.
|
||||
|
||||
### Зачем это нужно?
|
||||
|
||||
@ -11,6 +11,12 @@ The No Hang Daemon
|
||||
"А можете рассказать, как сделать OOM Killer более агрессивным? Например, в ситуации, когда приложение открыло/создало множество мелких файлов и держит их в памяти, при внезапной нехватке памяти ядро пытается высвободить эти файловые страницы, что вешает систему намертво со 100%-м дисковым I/O на несколько (десятков) минут. А ведь зачастую гораздо проще просто грохнуть само приложение с дальнешим его перезапуском."
|
||||
https://habr.com/company/flant/blog/348324/#comment_10659202
|
||||
|
||||
"Это хорошо, если приходит OOM-killer. Плохо, когда есть огромный iowait, а киллера так никто и не видел, спит. Надежда на то, что без свопа протекающий фаерфокс будет пристрелен, очень быстро растворились в хрусте винта."
|
||||
https://habr.com/company/flant/blog/348324/#comment_10660004
|
||||
|
||||
"А можете рассказать, как сделать OOM Killer более агрессивным? Например, в ситуации, когда приложение открыло/создало множество мелких файлов и держит их в памяти, при внезапной нехватке памяти ядро пытается высвободить эти файловые страницы, что вешает систему намертво со 100%-м дисковым I/O на несколько (десятков) минут. А ведь зачастую гораздо проще просто грохнуть само приложение с дальнешим его перезапуском."
|
||||
https://habr.com/company/flant/blog/348324/#comment_10659202
|
||||
|
||||
"Говно какое-то этот оом-киллер, нихрена не работает.
|
||||
Но чтобы это нормально работало, я думаю, нужен какой-то демон, который постоянно мониторит потребление памяти и прибивает тот процесс, который резко начинает набирать обороты. В общем сам этот демон будет проц грузить, хотя можно ограничить процессы, которые он будет проверять только пользовательскими процессами, добавить black-white list ну и настраиваемый интервал проверки.
|
||||
Короче если кто-то напишет будет круто."
|
||||
@ -40,17 +46,17 @@ https://2ch.hk/s/res/2310304.html#2311483, https://archive.li/idixk
|
||||
- поддержка работы со `zram`, возможность реакции на `mem_used_total`
|
||||
- удобный конфиг с возможностью тонкой настройки
|
||||
- аргументы командной строки -h/--help и -c/--config
|
||||
- возможность раздельного задания уровней `MemAvailable`, `SwapFree`, `mem_used_total` для отпраки `SIGTERM` и `SIGKILL`, возможность задания в процентах (%), кибибайтах (K), мебибайтах (M), гибибайтах (G)
|
||||
- возможность раздельного задания уровней `MemAvailable`, `SwapFree`, `mem_used_total` для отпраки `SIGTERM` и `SIGKILL`, возможность задания в процентах и мебибайтах.
|
||||
- возможность снижения `oom_score_adj` процессов, чьи `oom_score_adj` завышены (актуально для `chromium`)
|
||||
- лучший алгоритм выбора периодов между проверками доступной памяти: при больших объемах доступной памяти нет смысла проверять ее состояние часто, поэтому период проверки уменьшается по мере уменьшения размера доступной памяти
|
||||
- интенсивность мониторинга можно гибко настраивать (параметры конфига `rate_mem`, `rate_swap`, `rate_zram`)
|
||||
- по умолчанию память заблокирована с помощью `mlockall()` для предотвращения своппинга процесса
|
||||
- по умолчанию высокий приоритет процесса `nice -20`, может регулироваться через конфиг
|
||||
- возможность блокировки памяти с помощью `mlockall()` для предотвращения своппинга процесса
|
||||
- по умолчанию высокий приоритет процесса `nice -15`, может регулироваться через конфиг
|
||||
- предотвращение самоубийства с помощью `self_oom_score_adj = -1000`
|
||||
- возможность задания `oom_score_min` для предотвращения убийства невиновных
|
||||
- verbosity: опциональность печати параметров конфига при старте программы, опциональность печати результатов проверки памяти и времени между проверками памяти
|
||||
- возможность предотвращения избыточного убийства процессов с помощью задания миниального `oom_score` для убиваемых процессов и установка минимальной задержки просле отправки сигналов (параметры конфига `min_delay_after_sigkill` и `min_delay_after_sigterm`)
|
||||
- возможность показа десктопных уведомлений
|
||||
- возможность показа десктопных уведомлений c помощью notify-send, с показом сигнала (SIGTERM или SIGKILL), который отправлен процессу, а также Pid, oom_score, VmRSS, VmSwap, которыми обладал процесс перед получением сигнала.
|
||||
- наличие `man` страницы
|
||||
- наличие установщика для пользователей `systemd`
|
||||
- протестировано на `Debian 9 x86_64`, `Debian 8 i386`, `Fedora 28 x86_64`
|
||||
@ -97,6 +103,7 @@ sudo ./uninstall.sh
|
||||
|
||||
- Скорость разработки на Python значительно выше. Больше фич за приемлемое время.
|
||||
- Практически единственный минус Python - большее потребление памяти процессом.
|
||||
- На самом деле я просто не знаю C и немножко изучал Python, поэтому пишу на последнем.
|
||||
|
||||
### Подсказка
|
||||
|
||||
|
328
nohang
328
nohang
@ -2,9 +2,9 @@
|
||||
|
||||
# Nohang - The No Hang Daemon for Linux
|
||||
|
||||
################################################################################
|
||||
##########################################################################
|
||||
|
||||
### импорты
|
||||
# импорты
|
||||
|
||||
import os
|
||||
from ctypes import CDLL
|
||||
@ -12,13 +12,13 @@ from operator import itemgetter
|
||||
from time import sleep
|
||||
from argparse import ArgumentParser
|
||||
|
||||
################################################################################
|
||||
##########################################################################
|
||||
|
||||
### задание констант
|
||||
# задание констант
|
||||
|
||||
version = 'unknown'
|
||||
|
||||
sig_dict = {9:'SIGKILL', 15:'SIGTERM'}
|
||||
sig_dict = {9: 'SIGKILL', 15: 'SIGTERM'}
|
||||
|
||||
# директория, в которой запущен скрипт
|
||||
cd = os.getcwd()
|
||||
@ -27,11 +27,10 @@ cd = os.getcwd()
|
||||
default_configs = (
|
||||
cd + '/nohang.conf',
|
||||
'/etc/nohang/nohang.conf'
|
||||
)
|
||||
)
|
||||
|
||||
# универсальное сообщение при инвалидном конфиге
|
||||
conf_err_mess = "\nSet up the path to the valid config file w' \
|
||||
'ith -c/--config CONFIG option!\nExit"
|
||||
conf_err_mess = '\nSet up the path to the valid config file with -c/--config CONFIG option!\nExit'
|
||||
|
||||
# означает, что при задани zram disksize = 10000M доступная память
|
||||
# уменьшится на 42M
|
||||
@ -43,9 +42,9 @@ conf_err_mess = "\nSet up the path to the valid config file w' \
|
||||
# но это утверждение противоречит опытным данным
|
||||
zram_disksize_factor = 0.0042
|
||||
|
||||
################################################################################
|
||||
##########################################################################
|
||||
|
||||
### задание функций
|
||||
# задание функций
|
||||
|
||||
|
||||
def string_to_float_convert_test(string):
|
||||
@ -62,7 +61,7 @@ def string_to_int_convert_test(string):
|
||||
return None
|
||||
|
||||
|
||||
def func_decrease_oom_score_adj(oom_score_adj_before, oom_score_adj_after):
|
||||
def func_decrease_oom_score_adj(oom_score_adj_max):
|
||||
# цикл для наполнения oom_list
|
||||
for i in os.listdir('/proc'):
|
||||
|
||||
@ -72,11 +71,9 @@ def func_decrease_oom_score_adj(oom_score_adj_before, oom_score_adj_after):
|
||||
|
||||
try:
|
||||
oom_score_adj = int(rline1('/proc/' + i + '/oom_score_adj'))
|
||||
if oom_score_adj > oom_score_adj_before:
|
||||
write(
|
||||
'/proc/' + i + '/oom_score_adj',
|
||||
oom_score_adj_after + '\n'
|
||||
)
|
||||
if oom_score_adj > oom_score_adj_max:
|
||||
write('/proc/' + i + '/oom_score_adj',
|
||||
str(oom_score_adj_max) + '\n')
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
except ProcessLookupError:
|
||||
@ -107,6 +104,7 @@ def percent(num):
|
||||
def just_percent_mem(num):
|
||||
return str(round(num * 100, 1)).rjust(4, ' ')
|
||||
|
||||
|
||||
def just_percent_swap(num):
|
||||
return str(round(num * 100, 1)).rjust(5, ' ')
|
||||
|
||||
@ -133,7 +131,7 @@ def zram_stat(zram_id):
|
||||
mem_used_total = mm_stat_list[2]
|
||||
except FileNotFoundError:
|
||||
mem_used_total = rline1('/sys/block/' + zram_id + '/mem_used_total')
|
||||
return disksize, mem_used_total # BYTES, str
|
||||
return disksize, mem_used_total # BYTES, str
|
||||
|
||||
|
||||
# имя через пид
|
||||
@ -151,9 +149,7 @@ def pid_to_name(pid):
|
||||
def find_victim_and_send_signal(signal):
|
||||
|
||||
if decrease_oom_score_adj and root:
|
||||
func_decrease_oom_score_adj(
|
||||
oom_score_adj_before, oom_score_adj_after
|
||||
)
|
||||
func_decrease_oom_score_adj(oom_score_adj_max)
|
||||
|
||||
oom_list = []
|
||||
for i in os.listdir('/proc'):
|
||||
@ -173,14 +169,24 @@ def find_victim_and_send_signal(signal):
|
||||
|
||||
# посылаем сигнал
|
||||
if oom_score >= oom_score_min:
|
||||
|
||||
pid = pid_tuple_list[0]
|
||||
|
||||
name = pid_to_name(pid)
|
||||
|
||||
print(
|
||||
' Try to send the {} signal to {}, Pid {}, oom_score {}'.format(
|
||||
sig_dict[signal], name, pid, oom_score
|
||||
)
|
||||
)
|
||||
with open('/proc/' + pid + '/status') as f:
|
||||
for n, line in enumerate(f):
|
||||
|
||||
if n is vm_rss_index:
|
||||
vm_rss = kib_to_mib(int(line.split(':')[1].split(' ')[-2]))
|
||||
continue
|
||||
|
||||
if n is vm_swap_index:
|
||||
vm_swap = kib_to_mib(int(line.split(':')[1].split(' ')[-2]))
|
||||
break
|
||||
|
||||
print(' Try to send the {} signal to {},\n Pid {}, oom_score {}, VmRSS {} MiB, VmSwap {} MiB'.format(
|
||||
sig_dict[signal], name, pid, oom_score, vm_rss, vm_swap))
|
||||
|
||||
try:
|
||||
os.kill(int(pid), signal)
|
||||
@ -188,28 +194,26 @@ def find_victim_and_send_signal(signal):
|
||||
|
||||
if desktop_notifications:
|
||||
|
||||
info = '"The {} signal has been sent to <b>{}</b>,\n<i>Pid:</i> <b>{}</b>, <i>oom_score:</i> <b>{}</b>" &'.format(sig_dict[signal], name, pid, oom_score)
|
||||
info = '"The {} signal has been sent to <b>{}</b>, <i>Pid:</i> <b>{}</b>, <i>oom_score:</i> <b>{}</b>, <i>VmRSS</i> <b>{} MiB</b>, <i>VmSwap</i> <b>{} MiB</b>" &'.format(
|
||||
sig_dict[signal], name, pid, oom_score, vm_rss, vm_swap)
|
||||
|
||||
if root:
|
||||
# получаем множество залогиненных юзеров из вывода команды users
|
||||
# получаем множество залогиненных юзеров из вывода команды
|
||||
# users
|
||||
users_set = set(str(
|
||||
Popen('users', shell=True, stdout=PIPE).communicate()[0][0:-1]
|
||||
)[2:-1].split(' '))
|
||||
Popen('users', shell=True,
|
||||
stdout=PIPE).communicate()[0][0:-1]
|
||||
)[2:-1].split(' '))
|
||||
# отправляем уведомление всем залогиненным юзерам
|
||||
for i in users_set:
|
||||
root_notify_command = 'DISPLAY=:0.0 sudo -u {} {} -i dialog-warnig "Nohang tried to prevent OOM" -t {} -u critical '.format(
|
||||
i, notify_command, notify_time
|
||||
)
|
||||
root_notify_command = 'DISPLAY=:0.0 sudo -u {} notify-send "Nohang tried to prevent OOM" '.format(
|
||||
i)
|
||||
os.system(root_notify_command + info)
|
||||
else:
|
||||
user_notify_command =(
|
||||
'{} -i dialog-warnig "Nohang tried to prevent OOM" -t {} -u critical '.format(
|
||||
notify_command, notify_time
|
||||
)
|
||||
)
|
||||
user_notify_command = (
|
||||
'notify-send "Nohang tried to prevent OOM" ')
|
||||
os.system(user_notify_command + info)
|
||||
|
||||
|
||||
except ProcessLookupError:
|
||||
print(' No such process')
|
||||
except PermissionError:
|
||||
@ -231,12 +235,10 @@ def find_victim_and_send_signal(signal):
|
||||
sleep(min_delay_after_sigterm)
|
||||
|
||||
|
||||
################################################################################
|
||||
##########################################################################
|
||||
|
||||
### поиск позиций
|
||||
# поиск позиций и mem_total
|
||||
|
||||
|
||||
# ищем позиции
|
||||
with open('/proc/meminfo') as file:
|
||||
mem_list = file.readlines()
|
||||
|
||||
@ -253,24 +255,31 @@ swap_free_index = swap_total_index + 1
|
||||
|
||||
mem_total = int(mem_list[0].split(':')[1].split(' ')[-2])
|
||||
|
||||
with open('/proc/self/status') as file:
|
||||
status_list = file.readlines()
|
||||
|
||||
# еще найти позиции VmRSS & VmSwap
|
||||
# список имен из /proc/*/status для дальнейшего поиска позиций VmRSS and VmSwap
|
||||
status_names = []
|
||||
for s in status_list:
|
||||
status_names.append(s.split(':')[0])
|
||||
|
||||
vm_rss_index = status_names.index('VmRSS')
|
||||
vm_swap_index = status_names.index('VmSwap')
|
||||
|
||||
################################################################################
|
||||
##########################################################################
|
||||
|
||||
### получение пути к конфигу
|
||||
# получение пути к конфигу
|
||||
|
||||
# парсинг аргументов командной строки
|
||||
parser = ArgumentParser()
|
||||
parser.add_argument(
|
||||
'-c',
|
||||
'--config',
|
||||
'-c',
|
||||
'--config',
|
||||
help="""path to the config file, default values:
|
||||
./nohang.conf, /etc/nohang/nohang.conf""",
|
||||
default=None,
|
||||
./nohang.conf, /etc/nohang/nohang.conf""",
|
||||
default=None,
|
||||
type=str
|
||||
)
|
||||
)
|
||||
|
||||
arg_config = parser.parse_args().config
|
||||
|
||||
@ -298,19 +307,19 @@ else:
|
||||
print('The path to the config file to be used:')
|
||||
print(config)
|
||||
|
||||
################################################################################
|
||||
##########################################################################
|
||||
|
||||
### парсинг конфига с получением словаря параметров
|
||||
# парсинг конфига с получением словаря параметров
|
||||
|
||||
try:
|
||||
with open(config) as f:
|
||||
config_dict = dict()
|
||||
for line in f:
|
||||
for line in f:
|
||||
a = line.startswith('#')
|
||||
b = line.startswith('\n')
|
||||
c = line.startswith('\t')
|
||||
d = line.startswith(' ')
|
||||
if not a and not b and not c and not d:
|
||||
if not a and not b and not c and not d:
|
||||
a = line.split('=')
|
||||
config_dict[a[0].strip()] = a[1].strip()
|
||||
except PermissionError:
|
||||
@ -327,12 +336,11 @@ except IndexError:
|
||||
exit()
|
||||
|
||||
|
||||
################################################################################
|
||||
|
||||
### извлечение параметров из словаря
|
||||
### проверка наличия всех необходимых параметров
|
||||
### валидация всех параметров
|
||||
##########################################################################
|
||||
|
||||
# извлечение параметров из словаря
|
||||
# проверка наличия всех необходимых параметров
|
||||
# валидация всех параметров
|
||||
|
||||
|
||||
if 'print_config' in config_dict:
|
||||
@ -343,18 +351,16 @@ if 'print_config' in config_dict:
|
||||
print_config = False
|
||||
else:
|
||||
print(
|
||||
'Invalid print_config value {} (should be True or Fa' \
|
||||
'lse)\nExit'.format(
|
||||
'Invalid print_config value {} (should be True or False)\nExit'.format(
|
||||
print_config
|
||||
)
|
||||
)
|
||||
)
|
||||
exit()
|
||||
else:
|
||||
print('Print_config not in config\nExit')
|
||||
exit()
|
||||
|
||||
|
||||
|
||||
if 'print_mem_check_results' in config_dict:
|
||||
print_mem_check_results = config_dict['print_mem_check_results']
|
||||
if print_mem_check_results == 'True':
|
||||
@ -363,18 +369,16 @@ if 'print_mem_check_results' in config_dict:
|
||||
print_mem_check_results = False
|
||||
else:
|
||||
print(
|
||||
'Invalid print_mem_check_results value {} (should be True o' \
|
||||
'r False)\nExit'.format(
|
||||
'Invalid print_mem_check_results value {} (should be True or False)\nExit'.format(
|
||||
print_mem_check_results
|
||||
)
|
||||
)
|
||||
)
|
||||
exit()
|
||||
else:
|
||||
print('print_mem_check_results not in config\nExit')
|
||||
exit()
|
||||
|
||||
|
||||
|
||||
if 'print_sleep_periods' in config_dict:
|
||||
print_sleep_periods = config_dict['print_sleep_periods']
|
||||
if print_sleep_periods == 'True':
|
||||
@ -383,11 +387,10 @@ if 'print_sleep_periods' in config_dict:
|
||||
print_sleep_periods = False
|
||||
else:
|
||||
print(
|
||||
'Invalid print_sleep_periods value {} (should be Tru' \
|
||||
'e or False)\nExit'.format(
|
||||
'Invalid print_sleep_periods value {} (should be True or False)\nExit'.format(
|
||||
print_sleep_periods
|
||||
)
|
||||
)
|
||||
)
|
||||
exit()
|
||||
else:
|
||||
print('print_sleep_periods not in config\nExit')
|
||||
@ -404,8 +407,8 @@ if 'mlockall' in config_dict:
|
||||
print(
|
||||
'Invalid mlockall value {} (should be True or False)\nExit'.format(
|
||||
mlockall
|
||||
)
|
||||
)
|
||||
)
|
||||
exit()
|
||||
else:
|
||||
print('mlockall not in config\nExit')
|
||||
@ -427,7 +430,6 @@ else:
|
||||
exit()
|
||||
|
||||
|
||||
|
||||
if 'self_oom_score_adj' in config_dict:
|
||||
self_oom_score_adj = config_dict['self_oom_score_adj']
|
||||
self_oom_score_adj = string_to_int_convert_test(self_oom_score_adj)
|
||||
@ -485,7 +487,6 @@ else:
|
||||
exit()
|
||||
|
||||
|
||||
|
||||
if 'mem_min_sigterm' in config_dict:
|
||||
mem_min_sigterm = config_dict['mem_min_sigterm']
|
||||
else:
|
||||
@ -528,10 +529,9 @@ else:
|
||||
exit()
|
||||
|
||||
|
||||
|
||||
if 'min_delay_after_sigterm' in config_dict:
|
||||
min_delay_after_sigterm = string_to_float_convert_test(config_dict['m' \
|
||||
'in_delay_after_sigterm'])
|
||||
min_delay_after_sigterm = string_to_float_convert_test(
|
||||
config_dict['m' 'in_delay_after_sigterm'])
|
||||
if min_delay_after_sigterm is None:
|
||||
print('Invalid min_delay_after_sigterm value, not float\nExit')
|
||||
exit()
|
||||
@ -545,8 +545,8 @@ else:
|
||||
|
||||
|
||||
if 'min_delay_after_sigkill' in config_dict:
|
||||
min_delay_after_sigkill = string_to_float_convert_test(config_dict['mi' \
|
||||
'n_delay_after_sigkill'])
|
||||
min_delay_after_sigkill = string_to_float_convert_test(
|
||||
config_dict['mi' 'n_delay_after_sigkill'])
|
||||
if min_delay_after_sigkill is None:
|
||||
print('Invalid min_delay_after_sigkill value, not float\nExit')
|
||||
exit()
|
||||
@ -581,49 +581,29 @@ if 'decrease_oom_score_adj' in config_dict:
|
||||
elif decrease_oom_score_adj == 'False':
|
||||
decrease_oom_score_adj = False
|
||||
else:
|
||||
print(
|
||||
'invalid decrease_oom_score_adj value {} (should be Tru' \
|
||||
'e or False)\nExit'.format(
|
||||
decrease_oom_score_adj
|
||||
)
|
||||
)
|
||||
print('invalid decrease_oom_score_adj value {} (should be True or False)\nExit'.format(
|
||||
decrease_oom_score_adj))
|
||||
exit()
|
||||
else:
|
||||
print('decrease_oom_score_adj not in config\nExit')
|
||||
exit()
|
||||
|
||||
|
||||
if 'oom_score_adj_before' in config_dict:
|
||||
oom_score_adj_before = config_dict['oom_score_adj_before']
|
||||
if string_to_int_convert_test(oom_score_adj_before) is None:
|
||||
print('Invalid oom_score_adj_before value, not integer\nExit')
|
||||
if 'oom_score_adj_max' in config_dict:
|
||||
oom_score_adj_max = config_dict['oom_score_adj_max']
|
||||
|
||||
oom_score_adj_max = string_to_int_convert_test(oom_score_adj_max)
|
||||
if oom_score_adj_max is None:
|
||||
print('Invalid oom_score_adj_max value, not integer\nExit')
|
||||
exit()
|
||||
else:
|
||||
oom_score_adj_before = int(oom_score_adj_before)
|
||||
if oom_score_adj_before < 0 or oom_score_adj_before > 1000:
|
||||
print('Недопустимое значение oom_score_adj_before\nExit')
|
||||
exit()
|
||||
else:
|
||||
print('oom_score_adj_before not in config\nExit')
|
||||
exit()
|
||||
|
||||
|
||||
if 'oom_score_adj_after' in config_dict:
|
||||
oom_score_adj_after = config_dict['oom_score_adj_after']
|
||||
if string_to_int_convert_test(oom_score_adj_after) is None:
|
||||
print('Invalid oom_score_adj_after value, not integer\nExit')
|
||||
if oom_score_adj_max < 0 or oom_score_adj_max > 1000:
|
||||
print('Недопустимое значение oom_score_adj_max\nExit')
|
||||
exit()
|
||||
else:
|
||||
oom_score_adj_after = int(oom_score_adj_after)
|
||||
if oom_score_adj_after < 0 or oom_score_adj_after > 1000:
|
||||
print('Недопустимое значение oom_score_adj_after\nExit')
|
||||
exit()
|
||||
else:
|
||||
print('oom_score_adj_after not in config\nExit')
|
||||
print('oom_score_adj_max not in config\nExit')
|
||||
exit()
|
||||
|
||||
|
||||
|
||||
if 'desktop_notifications' in config_dict:
|
||||
desktop_notifications = config_dict['desktop_notifications']
|
||||
if desktop_notifications == 'True':
|
||||
@ -632,43 +612,17 @@ if 'desktop_notifications' in config_dict:
|
||||
elif desktop_notifications == 'False':
|
||||
desktop_notifications = False
|
||||
else:
|
||||
print(
|
||||
'Invalid desktop_notifications value {} (should be True or False)\nExit'.format(
|
||||
desktop_notifications
|
||||
)
|
||||
)
|
||||
print('Invalid desktop_notifications value {} (should be True or False)\nExit'.format(
|
||||
desktop_notifications))
|
||||
exit()
|
||||
else:
|
||||
print('desktop_notifications not in config\nExit')
|
||||
exit()
|
||||
|
||||
|
||||
if 'notify_command' in config_dict:
|
||||
notify_command = config_dict['notify_command'].strip()
|
||||
else:
|
||||
print('notify_command not in config\nExit')
|
||||
exit()
|
||||
##########################################################################
|
||||
|
||||
|
||||
if 'notify_time' in config_dict:
|
||||
notify_time = config_dict['notify_time']
|
||||
notify_time = string_to_float_convert_test(notify_time)
|
||||
if notify_time is None:
|
||||
print('Invalid notify_time value, not float\nExit')
|
||||
exit()
|
||||
if notify_time <= 0:
|
||||
print('Недопустимое значение notify_time\nExit')
|
||||
exit()
|
||||
else:
|
||||
notify_time = int(notify_time * 1000)
|
||||
else:
|
||||
print('notify_time not in config\nExit')
|
||||
exit()
|
||||
|
||||
|
||||
################################################################################
|
||||
|
||||
### получение уровней в кибибайтах
|
||||
# получение уровней в кибибайтах
|
||||
|
||||
|
||||
def sig_level_to_kb(string):
|
||||
@ -681,9 +635,7 @@ def sig_level_to_kb(string):
|
||||
elif string.endswith('G'):
|
||||
return float(string[:-1].strip()) * 1048576
|
||||
else:
|
||||
print(
|
||||
'Конфиг инвалид, где-то неверно указаны единицы измерения\nExit'
|
||||
)
|
||||
print('Конфиг инвалид, где-то неверно указаны единицы измерения\nExit')
|
||||
exit()
|
||||
|
||||
|
||||
@ -715,14 +667,14 @@ def sig_level_to_kb_swap(string):
|
||||
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:
|
||||
if isinstance(swap_min_sigterm_swap, 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:
|
||||
if isinstance(swap_min_sigkill_swap, tuple):
|
||||
swap_kill_is_percent = True
|
||||
swap_min_sigkill_percent = swap_min_sigkill_swap[0]
|
||||
else:
|
||||
@ -730,10 +682,9 @@ else:
|
||||
swap_min_sigkill_kb = swap_min_sigkill_swap
|
||||
|
||||
|
||||
##########################################################################
|
||||
|
||||
################################################################################
|
||||
|
||||
### самозащита и печать конфига
|
||||
# самозащита и печать конфига
|
||||
|
||||
|
||||
# повышаем приоритет
|
||||
@ -775,7 +726,6 @@ else:
|
||||
decrease_res = 'Impossible'
|
||||
|
||||
|
||||
|
||||
if print_config:
|
||||
|
||||
print('\nI. VERBOSITY')
|
||||
@ -787,10 +737,10 @@ if print_config:
|
||||
print('mlockall: {} ({})'.format(mlockall, mla_res))
|
||||
print('self_nice: {} ({})'.format(
|
||||
self_nice, self_nice_result
|
||||
))
|
||||
))
|
||||
print('self_oom_score_adj: {} ({})'.format(
|
||||
self_oom_score_adj, self_oom_score_adj_result
|
||||
))
|
||||
))
|
||||
|
||||
print('\nIII. ИНТЕНСИВНОСТЬ МОНИТОРИНГА')
|
||||
print('rate_mem: {}'.format(rate_mem))
|
||||
@ -813,16 +763,12 @@ if print_config:
|
||||
# False (OK) - OK не нужен когда фолс
|
||||
print('decrease_oom_score_adj: {} ({})'.format(
|
||||
decrease_oom_score_adj, decrease_res
|
||||
))
|
||||
))
|
||||
if decrease_oom_score_adj:
|
||||
print('oom_score_adj_before: {}'.format(oom_score_adj_before))
|
||||
print('oom_score_adj_after: {}'.format(oom_score_adj_after))
|
||||
print('oom_score_adj_max: {}'.format(oom_score_adj_max))
|
||||
|
||||
print('\nVI. DESKTOP NOTIFICATIONS')
|
||||
print('desktop_notifications: {}'.format(desktop_notifications))
|
||||
if desktop_notifications:
|
||||
print('notify_command: {}'.format(notify_command))
|
||||
print('notify_time: {}'.format(round(notify_time / 1000, 1)))
|
||||
|
||||
|
||||
# для рассчета ширины столбцов при печати mem и zram
|
||||
@ -831,9 +777,9 @@ mem_len = len(str(round(mem_total / 1024.0)))
|
||||
|
||||
print('\nStart monitoring...')
|
||||
|
||||
################################################################################
|
||||
##########################################################################
|
||||
|
||||
### цикл проверки уровней доступной памяти
|
||||
# цикл проверки уровней доступной памяти
|
||||
|
||||
while True:
|
||||
|
||||
@ -851,7 +797,6 @@ while True:
|
||||
swap_free = int(line.split(':')[1].split(' ')[-2])
|
||||
break
|
||||
|
||||
|
||||
# если swap_min_sigkill задан в процентах
|
||||
if swap_kill_is_percent:
|
||||
swap_min_sigkill_kb = swap_total * swap_min_sigkill_percent / 100.0
|
||||
@ -859,7 +804,6 @@ while True:
|
||||
if swap_term_is_percent:
|
||||
swap_min_sigterm_kb = swap_total * swap_min_sigterm_percent / 100.0
|
||||
|
||||
|
||||
# находим MemUsedZram
|
||||
disksize_sum = 0
|
||||
mem_used_total_sum = 0
|
||||
@ -870,13 +814,11 @@ while True:
|
||||
mem_used_total_sum += int(stat[1])
|
||||
mem_used_zram = (
|
||||
mem_used_total_sum + disksize_sum * zram_disksize_factor
|
||||
) / 1024.0
|
||||
|
||||
) / 1024.0
|
||||
|
||||
# для рассчета ширины столбца свопа
|
||||
swap_len = len(str(round(swap_total / 1024.0)))
|
||||
|
||||
|
||||
# печать размеров доступной памяти
|
||||
if swap_total == 0 and mem_used_zram == 0:
|
||||
if print_mem_check_results:
|
||||
@ -884,8 +826,8 @@ while True:
|
||||
'MemAvail: {} M, {} %'.format(
|
||||
human(mem_available, mem_len),
|
||||
just_percent_mem(mem_available / mem_total)
|
||||
)
|
||||
)
|
||||
)
|
||||
elif swap_total > 0 and mem_used_zram == 0:
|
||||
if print_mem_check_results:
|
||||
print(
|
||||
@ -894,22 +836,21 @@ while True:
|
||||
just_percent_mem(mem_available / mem_total),
|
||||
human(swap_free, swap_len),
|
||||
just_percent_swap(swap_free / (swap_total + 0.0001))
|
||||
)
|
||||
)
|
||||
)
|
||||
else:
|
||||
if print_mem_check_results:
|
||||
print(
|
||||
'MemAvail: {} M, {} % | SwapFree: {} M, {} % | MemUsed' \
|
||||
'Zram: {} M, {} %'.format(
|
||||
'MemAvail: {} M, {} % | SwapFree: {} M, {} % | MemUsed'
|
||||
'Zram: {} M, {} %'.format(
|
||||
human(mem_available, mem_len),
|
||||
just_percent_mem(mem_available / mem_total),
|
||||
just_percent_mem(mem_available / mem_total),
|
||||
human(swap_free, swap_len),
|
||||
just_percent_swap(swap_free / (swap_total + 0.0001)),
|
||||
just_percent_swap(swap_free / (swap_total + 0.0001)),
|
||||
human(mem_used_zram, mem_len),
|
||||
just_percent_mem(mem_used_zram / mem_total)
|
||||
)
|
||||
)
|
||||
|
||||
)
|
||||
|
||||
# если swap_min_sigkill задан в абсолютной величине и Swap_total = 0
|
||||
if swap_total > swap_min_sigkill_kb:
|
||||
@ -922,12 +863,11 @@ while True:
|
||||
else:
|
||||
swap_sigterm_pc = '-'
|
||||
|
||||
|
||||
# MEM SWAP KILL
|
||||
if mem_available <= mem_min_sigkill_kb and swap_free <= swap_min_sigkill_kb:
|
||||
print(
|
||||
'+ MemAvail ({} M, {} %) < mem_min_sigkill ({} M, {} %)\n S' \
|
||||
'wapFree ({} M, {} %) < swap_min_sigkill ({} M, {} %)'.format(
|
||||
'+ MemAvail ({} M, {} %) < mem_min_sigkill ({} M, {} %)\n S'
|
||||
'wapFree ({} M, {} %) < swap_min_sigkill ({} M, {} %)'.format(
|
||||
kib_to_mib(mem_available),
|
||||
percent(mem_available / mem_total),
|
||||
kib_to_mib(mem_min_sigkill_kb),
|
||||
@ -936,31 +876,29 @@ while True:
|
||||
percent(swap_free / (swap_total + 0.0001)),
|
||||
kib_to_mib(swap_min_sigkill_kb),
|
||||
swap_sigkill_pc
|
||||
)
|
||||
)
|
||||
)
|
||||
find_victim_and_send_signal(9)
|
||||
continue
|
||||
|
||||
|
||||
# ZRAM KILL
|
||||
if mem_used_zram >= zram_max_sigkill_kb:
|
||||
print(
|
||||
'+ MemUsedZram ({} M, {} %) > zram_max_sigkill ({} M, {} %)'.format(
|
||||
kib_to_mib(mem_used_zram),
|
||||
percent(mem_used_zram / mem_total),
|
||||
percent(
|
||||
mem_used_zram /
|
||||
mem_total),
|
||||
kib_to_mib(zram_max_sigkill_kb),
|
||||
percent(zram_max_sigkill_kb / mem_total)
|
||||
)
|
||||
)
|
||||
percent(
|
||||
zram_max_sigkill_kb /
|
||||
mem_total)))
|
||||
find_victim_and_send_signal(9)
|
||||
continue
|
||||
|
||||
|
||||
# MEM SWAP TERM
|
||||
if mem_available <= mem_min_sigterm_kb and swap_free <= swap_min_sigterm_kb:
|
||||
print(
|
||||
'+ MemAvail ({} M, {} %) < mem_min_sigterm ({} M, {} %)\n SwapFree' \
|
||||
' ({} M, {} %) < swap_min_sigterm ({} M, {} %)'.format(
|
||||
print('+ MemAvail ({} M, {} %) < mem_min_sigterm ({} M, {} %)\n SwapFree ({} M, {} %) < swap_min_sigterm ({} M, {} %)'.format(
|
||||
kib_to_mib(mem_available),
|
||||
percent(mem_available / mem_total),
|
||||
kib_to_mib(mem_min_sigterm_kb),
|
||||
@ -968,31 +906,28 @@ while True:
|
||||
kib_to_mib(swap_free),
|
||||
percent(swap_free / (swap_total + 0.0001)),
|
||||
kib_to_mib(swap_min_sigterm_kb),
|
||||
swap_sigterm_pc
|
||||
)
|
||||
)
|
||||
swap_sigterm_pc))
|
||||
find_victim_and_send_signal(15)
|
||||
|
||||
|
||||
# ZRAM TERM
|
||||
if mem_used_zram >= zram_max_sigterm_kb:
|
||||
print(
|
||||
'+ MemUsedZram ({} M, {} %) > zram_max_sigterm ({} M, {} %)'.format(
|
||||
print('+ MemUsedZram ({} M, {} %) > zram_max_sigterm ({} M, {} %)'.format(
|
||||
kib_to_mib(mem_used_zram),
|
||||
percent(mem_used_zram / mem_total),
|
||||
percent(
|
||||
mem_used_zram /
|
||||
mem_total),
|
||||
kib_to_mib(zram_max_sigterm_kb),
|
||||
percent(zram_max_sigterm_kb / mem_total)
|
||||
)
|
||||
)
|
||||
percent(
|
||||
zram_max_sigterm_kb /
|
||||
mem_total)))
|
||||
find_victim_and_send_signal(15)
|
||||
|
||||
|
||||
# задание периода в зависимости от рейтов и уровней доступной памяти
|
||||
t_mem = mem_available / 1000000.0 / rate_mem
|
||||
|
||||
t_swap = swap_free / 10000000.0 / rate_swap
|
||||
|
||||
t_zram = (mem_total * 0.8 - mem_used_zram) / 1000000.0 / rate_zram
|
||||
t_zram = (mem_total * 0.8 - mem_used_zram) / 1000000.0 / rate_zram
|
||||
if t_zram < 0.01:
|
||||
t_zram = 0.01
|
||||
|
||||
@ -1010,4 +945,3 @@ while True:
|
||||
sleep(t)
|
||||
except KeyboardInterrupt:
|
||||
exit()
|
||||
|
||||
|
52
nohang.conf
52
nohang.conf
@ -32,15 +32,18 @@ print_sleep_periods = False
|
||||
True - заблокировать процесс в памяти для запрета его своппинга.
|
||||
False - не блокировать. Значения чувствительны к регистру!
|
||||
|
||||
mlockall = True
|
||||
В Fedora 28 значение True вызывает увеличение потребления
|
||||
памяти процессом на 200 MiB, в Debian 8 и 9 такой проблемы нет.
|
||||
|
||||
mlockall = False
|
||||
|
||||
Установка отрицательных значений self_nice и self_oom_score_adj
|
||||
требует наличия root прав.
|
||||
|
||||
Повысить приоритет процесса, установив niceness -20
|
||||
Установка отрицательного self_nice повышает приоритет процесса.
|
||||
Допустимые значения - целые числа из диапазона [-20; 19]
|
||||
|
||||
self_nice = -20
|
||||
self_nice = -15
|
||||
|
||||
Задать oom_score_adj для процесса.
|
||||
Установка значения -1000 запретит самоубийство.
|
||||
@ -67,8 +70,8 @@ self_oom_score_adj = -1000
|
||||
достаточно хорошо, успешно справляясь с резкими скачками потребления
|
||||
памяти.
|
||||
|
||||
rate_mem = 6
|
||||
rate_swap = 0.5
|
||||
rate_mem = 4
|
||||
rate_swap = 1
|
||||
rate_zram = 1
|
||||
|
||||
#####################################################################
|
||||
@ -81,12 +84,7 @@ rate_zram = 1
|
||||
Сигнал отправляется если MemAvailable и SwapFree одновременно
|
||||
опустятся ниже соответствующих значений.
|
||||
|
||||
Значения могут быть выражены в процентах (%),
|
||||
кибибайтах (K), мебибайтах (M) или гибибайтах (G).
|
||||
Например:
|
||||
swap_min_sigterm = 8 %
|
||||
mem_min_sigterm = 0.5 G
|
||||
swap_min_sigkill = 200 M
|
||||
Значения могут быть выражены в процентах (%) и мебибайтах (M)
|
||||
|
||||
mem_min_sigterm = 8 %
|
||||
mem_min_sigkill = 4 %
|
||||
@ -100,7 +98,7 @@ swap_min_sigkill = 4 %
|
||||
система виснет или запускается OOM killer.
|
||||
По мере увеличения доли zram в памяти может падать
|
||||
отзывчивость системы.
|
||||
Может также задаваться в %, K, M, G
|
||||
Может также задаваться в % и M.
|
||||
|
||||
zram_max_sigterm = 55 %
|
||||
zram_max_sigkill = 60 %
|
||||
@ -128,41 +126,35 @@ min_delay_after_sigkill = 3
|
||||
Процессы браузера chromium обычно имеют oom_score_adj
|
||||
200 или 300. Это приводит к тому, что процессы хрома умирают
|
||||
первыми вместо действительно тяжелых процессов.
|
||||
|
||||
Если параметр decrease_oom_score_adj установлен
|
||||
в значение True, то у процессов, имеющих oom_score_adj выше
|
||||
oom_score_adj_before значение oom_score_adj будет опущено
|
||||
до oom_score_adj_after перед поиском жертвы.
|
||||
oom_score_adj_max значение oom_score_adj будет опущено
|
||||
до oom_score_adj_max перед поиском жертвы.
|
||||
False - не изменять oom_score_adj процессов перед поиском
|
||||
жертвы. Значения чувствительны к регистру!
|
||||
|
||||
Требует root прав.
|
||||
|
||||
decrease_oom_score_adj = False
|
||||
decrease_oom_score_adj = True
|
||||
|
||||
Допустимые значения - целые числа из диапазона [0; 1000]
|
||||
|
||||
oom_score_adj_before = 10
|
||||
oom_score_adj_after = 5
|
||||
oom_score_adj_max = 20
|
||||
|
||||
#####################################################################
|
||||
|
||||
VI. DESKTOP NOTIFICATIONS
|
||||
|
||||
Эта возможность требует наличия notify-send в системе.
|
||||
|
||||
В Debian/Ubuntu это обеспечивается установкой пакета
|
||||
libnotify-bin. Также требуется наличие notification-daemon.
|
||||
libnotify-bin. В Fedora и Arch Linux - пакет libnotify.
|
||||
Также требуется наличие notification-daemon.
|
||||
При запуске nohang от рута уведомления рассылаются всем
|
||||
залогиненным пользователям.
|
||||
See also wiki.archlinux.org/index.php/Desktop_notifications
|
||||
|
||||
Допустимые значения: True и False
|
||||
|
||||
desktop_notifications = False
|
||||
|
||||
Может использоваться другая команда, если ее синтаксис совместим
|
||||
с синтаксисом notify-send
|
||||
|
||||
notify_command = notify-send
|
||||
|
||||
Время показа уведомлений в секундах.
|
||||
Должно быть положительным числом.
|
||||
|
||||
notify_time = 15
|
||||
desktop_notifications = True
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user