diff --git a/README.md b/README.md index 7b42636..95cd811 100644 --- a/README.md +++ b/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, поэтому пишу на последнем. ### Подсказка diff --git a/nohang b/nohang index ca5bad7..cec3c30 100755 --- a/nohang +++ b/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 {},\nPid: {}, oom_score: {}" &'.format(sig_dict[signal], name, pid, oom_score) + info = '"The {} signal has been sent to {}, Pid: {}, oom_score: {}, VmRSS {} MiB, VmSwap {} MiB" &'.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() - diff --git a/nohang.conf b/nohang.conf index f1aa80d..c1fe9d2 100644 --- a/nohang.conf +++ b/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