From 6231542d26a00c692e9c3845aab7eee5ad43b0e7 Mon Sep 17 00:00:00 2001 From: Alexey Avramov Date: Tue, 19 Jun 2018 02:08:05 +0900 Subject: [PATCH] =?UTF-8?q?=D0=BE=D0=BF=D1=82=D0=B8=D0=BC=D0=B8=D0=B7?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D1=8F=20=D1=83=D0=B2=D0=B5=D0=B4=D0=BE=D0=BC?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=B8=20=D0=BF=D0=BE=D1=82?= =?UTF-8?q?=D1=80=D0=B5=D0=B1=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BF=D0=B0?= =?UTF-8?q?=D0=BC=D1=8F=D1=82=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nohang | 268 ++++++++++++++++++++++++++++------------------------ nohang.conf | 26 +++-- 2 files changed, 159 insertions(+), 135 deletions(-) diff --git a/nohang b/nohang index bdc1091..5a6d1f5 100755 --- a/nohang +++ b/nohang @@ -7,7 +7,6 @@ # импорты import os -from ctypes import CDLL from operator import itemgetter from time import sleep from argparse import ArgumentParser @@ -16,10 +15,6 @@ from argparse import ArgumentParser # задание констант -#notify_options = '-t "60000"' -#notify_options = '-u "critical"' -notify_options = '-i "dialog-warning"' - version = 'unknown' sig_dict = {9: 'SIGKILL', 15: 'SIGTERM'} @@ -34,7 +29,8 @@ default_configs = ( ) # универсальное сообщение при инвалидном конфиге -conf_err_mess = '\nSet up the path to the valid config file with -c/--config CONFIG option!\nExit' +conf_err_mess = '\nSet up the path to the valid config file with -c/--confi' \ + 'g option!\nExit' # означает, что при задани zram disksize = 10000M доступная память # уменьшится на 42M @@ -77,7 +73,7 @@ def func_decrease_oom_score_adj(oom_score_adj_max): oom_score_adj = int(rline1('/proc/' + i + '/oom_score_adj')) if oom_score_adj > oom_score_adj_max: write('/proc/' + i + '/oom_score_adj', - str(oom_score_adj_max) + '\n') + str(oom_score_adj_max) + '\n') except FileNotFoundError: pass except ProcessLookupError: @@ -150,11 +146,36 @@ def pid_to_name(pid): return '' +def send_notify(signal, name, pid, oom_score, vm_rss, vm_swap): + + # текст отправляемого уведомления + info = '"Nohang sent {} \nto the process {} \nP' \ + 'id: {} \noom_score: {} \nVmRSS: {} MiB \nVmSwap: {} MiB" &'.format( + sig_dict[signal], name, pid, oom_score, vm_rss, vm_swap) + + if root: + for uid in os.listdir('/run/user'): + root_notify_command = 'DISPLAY=:0 sudo -u {} notify-send {} "Pr' \ + 'eventing OOM" '.format(users_dict[uid], notify_options) + os.system(root_notify_command + info) + + else: + + # отправляем уведомление пользователю, который запустил nohang + user_notify_command = 'notify-send {} "Preventing OOM" '.format( + notify_options) + os.system(user_notify_command + info) + + +# принимает int (9 или 15) def find_victim_and_send_signal(signal): + # выставляем потолок для oom_score_adj всех процессов if decrease_oom_score_adj and root: func_decrease_oom_score_adj(oom_score_adj_max) + # получаем список процессов oom_list = [] for i in os.listdir('/proc'): if i.isdigit() is not True: @@ -167,8 +188,10 @@ def find_victim_and_send_signal(signal): oom_score = 0 oom_list.append((i, oom_score)) - # получаем список пар (pid, oom_score) + # получаем отсортированный по oom_score список пар (pid, oom_score) pid_tuple_list = sorted(oom_list, key=itemgetter(1), reverse=True)[0] + + # получаем максимальный oom_score oom_score = pid_tuple_list[1] # посылаем сигнал @@ -178,43 +201,38 @@ def find_victim_and_send_signal(signal): name = pid_to_name(pid) - with open('/proc/' + pid + '/status') as f: - for n, line in enumerate(f): + # находим VmRSS и VmSwap процесса, которому попытаемся послать сигнал + try: + 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('\t')[1][:-4])) + continue + if n is vm_swap_index: + vm_swap = kib_to_mib(int( + line.split('\t')[1][:-4])) + break + except FileNotFoundError: + vm_rss = 0 + vm_swap = 0 + except ProcessLookupError: + vm_rss = 0 + vm_swap = 0 + except IndexError: + vm_rss = 0 + vm_swap = 0 - 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)) + print(' Try to send the {} signal to {},\n Pid {}, oom_score {}, V' + 'mRSS {} MiB, VmSwap {} MiB'.format( + sig_dict[signal], name, pid, oom_score, vm_rss, vm_swap)) try: os.kill(int(pid), signal) print(' Success') if desktop_notifications: - - info = '"Nohang sent {} \nto the process \nName: {} \nPid: {} \noom_score: {} \nVmRSS: {} MiB \nVmSwap: {} MiB" &'.format(sig_dict[signal], name, pid, oom_score, vm_rss, vm_swap) - - if root: - # получаем множество залогиненных юзеров из вывода команды - # users - users_set = set(str( - Popen('users', shell=True, - stdout=PIPE).communicate()[0][0:-1] - )[2:-1].split(' ')) - # отправляем уведомление всем залогиненным юзерам - for i in users_set: - root_notify_command = 'DISPLAY=:0 sudo -u {} notify-send {} "Preventing OOM" '.format( - i, notify_options) - os.system(root_notify_command + info) - else: - user_notify_command = 'notify-send {} "Preventing OOM" '.format(notify_options) - os.system(user_notify_command + info) + send_notify(signal, name, pid, oom_score, vm_rss, vm_swap) except ProcessLookupError: print(' No such process') @@ -236,11 +254,11 @@ def find_victim_and_send_signal(signal): print(' sleep', min_delay_after_sigterm) sleep(min_delay_after_sigterm) - ########################################################################## # поиск позиций и mem_total + with open('/proc/meminfo') as file: mem_list = file.readlines() @@ -283,7 +301,9 @@ parser.add_argument( type=str ) -arg_config = parser.parse_args().config +args = parser.parse_args() + +arg_config = args.config if arg_config is None: @@ -337,7 +357,6 @@ except IndexError: print('IndexError', conf_err_mess) exit() - ########################################################################## # извлечение параметров из словаря @@ -352,11 +371,9 @@ if 'print_config' in config_dict: elif print_config == 'False': print_config = False else: - print( - 'Invalid print_config value {} (should be True or False)\nExit'.format( - print_config - ) - ) + print('Invalid print_config value {} (should be True or False)\nE' + 'xit'.format( + print_config)) exit() else: print('Print_config not in config\nExit') @@ -370,8 +387,9 @@ if 'print_mem_check_results' in config_dict: elif print_mem_check_results == 'False': print_mem_check_results = False else: - print('Invalid print_mem_check_results value {} (should be True or False)\nExit'.format( - print_mem_check_results)) + print('Invalid print_mem_check_results value {} (should be Tr' + 'ue or False)\nExit'.format( + print_mem_check_results)) exit() else: print('print_mem_check_results not in config\nExit') @@ -385,8 +403,9 @@ if 'print_sleep_periods' in config_dict: elif print_sleep_periods == 'False': print_sleep_periods = False else: - print('Invalid print_sleep_periods value {} (should be True or False)\nExit'.format( - print_sleep_periods)) + print('Invalid print_sleep_periods value {} (should be True or F' + 'alse)\nExit'.format( + print_sleep_periods)) exit() else: print('print_sleep_periods not in config\nExit') @@ -577,8 +596,9 @@ 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 True or False)\nExit'.format( - decrease_oom_score_adj)) + print('invalid decrease_oom_score_adj value {} (should be Tru' + 'e or False)\nExit'.format( + decrease_oom_score_adj)) exit() else: print('decrease_oom_score_adj not in config\nExit') @@ -604,18 +624,31 @@ if 'desktop_notifications' in config_dict: desktop_notifications = config_dict['desktop_notifications'] if desktop_notifications == 'True': desktop_notifications = True - from subprocess import Popen, PIPE + users_dict = dict() + with open('/etc/passwd') as f: + for line in f: + line_list = line.split(':') + username = line_list[0] + uid = line_list[2] + users_dict[uid] = username 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 Tru' + 'e or False)\nExit'.format( + desktop_notifications)) exit() else: print('desktop_notifications not in config\nExit') exit() +if 'notify_options' in config_dict: + notify_options = config_dict['notify_options'].strip() +else: + print('notify_options not in config\nExit') + exit() + ########################################################################## # получение уровней в кибибайтах @@ -677,7 +710,6 @@ else: swap_kill_is_percent = False swap_min_sigkill_kb = swap_min_sigkill_swap - ########################################################################## # самозащита и печать конфига @@ -705,6 +737,7 @@ except OSError: # запрет своппинга процесса if mlockall: + from ctypes import CDLL result = CDLL('libc.so.6', use_errno=True).mlockall(3) if result is 0: mla_res = 'OK' @@ -765,6 +798,8 @@ if print_config: print('\nVI. DESKTOP NOTIFICATIONS') print('desktop_notifications: {}'.format(desktop_notifications)) + if desktop_notifications: + print('notify_options: {}'.format(notify_options)) # для рассчета ширины столбцов при печати mem и zram @@ -784,13 +819,13 @@ while True: with open('/proc/meminfo') as f: for n, line in enumerate(f): if n is 2: - mem_available = int(line.split(':')[1].split(' ')[-2]) + mem_available = int(line.split(':')[1].strip(' kB\n')) continue if n is swap_total_index: - swap_total = int(line.split(':')[1].split(' ')[-2]) + swap_total = int(line.split(':')[1].strip(' kB\n')) continue if n is swap_free_index: - swap_free = int(line.split(':')[1].split(' ')[-2]) + swap_free = int(line.split(':')[1].strip(' kB\n')) break # если swap_min_sigkill задан в процентах @@ -818,35 +853,28 @@ while True: # печать размеров доступной памяти if swap_total == 0 and mem_used_zram == 0: if print_mem_check_results: - print( - 'MemAvail: {} M, {} %'.format( - human(mem_available, mem_len), - just_percent_mem(mem_available / mem_total) - ) - ) + print('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( - 'MemAvail: {} M, {} % | SwapFree: {} M, {} %'.format( - human(mem_available, mem_len), - just_percent_mem(mem_available / mem_total), - human(swap_free, swap_len), - just_percent_swap(swap_free / (swap_total + 0.0001)) - ) - ) + print('MemAvail: {} M, {} % | SwapFree: {} M, {} %'.format( + human(mem_available, mem_len), + 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( - human(mem_available, mem_len), - just_percent_mem(mem_available / mem_total), - human(swap_free, swap_len), - just_percent_swap(swap_free / (swap_total + 0.0001)), - human(mem_used_zram, mem_len), - just_percent_mem(mem_used_zram / mem_total) - ) - ) + print('MemAvail: {} M, {} % | SwapFree: {} M, {} % | MemUsed' + 'Zram: {} M, {} %'.format( + human(mem_available, mem_len), + just_percent_mem(mem_available / mem_total), + human(swap_free, swap_len), + 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: @@ -861,61 +889,51 @@ while True: # 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( - kib_to_mib(mem_available), - percent(mem_available / mem_total), - kib_to_mib(mem_min_sigkill_kb), - percent(mem_min_sigkill_kb / mem_total), - kib_to_mib(swap_free), - percent(swap_free / (swap_total + 0.0001)), - kib_to_mib(swap_min_sigkill_kb), - swap_sigkill_pc - ) - ) + print('+ 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), + percent(mem_min_sigkill_kb / mem_total), + kib_to_mib(swap_free), + 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), - kib_to_mib(zram_max_sigkill_kb), - percent( - zram_max_sigkill_kb / - mem_total))) + print('+ MemUsedZram ({} M, {} %) > zram_max_sigkill ({} M, {} %)'.format( + kib_to_mib(mem_used_zram), + percent(mem_used_zram / mem_total), + kib_to_mib(zram_max_sigkill_kb), + 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( - kib_to_mib(mem_available), - percent(mem_available / mem_total), - kib_to_mib(mem_min_sigterm_kb), - percent(mem_min_sigterm_kb / mem_total), - kib_to_mib(swap_free), - percent(swap_free / (swap_total + 0.0001)), - kib_to_mib(swap_min_sigterm_kb), - swap_sigterm_pc)) + print('+ MemAvail ({} M, {} %) < mem_min_sigterm ({} M, {} %)\n S' + 'wapFree ({} M, {} %) < swap_min_sigterm ({} M, {} %)'.format( + kib_to_mib(mem_available), + percent(mem_available / mem_total), + kib_to_mib(mem_min_sigterm_kb), + percent(mem_min_sigterm_kb / mem_total), + kib_to_mib(swap_free), + percent(swap_free / (swap_total + 0.0001)), + kib_to_mib(swap_min_sigterm_kb), + 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( - kib_to_mib(mem_used_zram), - percent( - mem_used_zram / - mem_total), - kib_to_mib(zram_max_sigterm_kb), - percent( - zram_max_sigterm_kb / - mem_total))) + print('+ MemUsedZram ({} M, {} %) > zram_max_sigt' + 'erm ({} M, {} %)'.format( + kib_to_mib(mem_used_zram), + percent(mem_used_zram / mem_total), + kib_to_mib(zram_max_sigterm_kb), + percent(zram_max_sigterm_kb / mem_total))) find_victim_and_send_signal(15) # задание периода в зависимости от рейтов и уровней доступной памяти diff --git a/nohang.conf b/nohang.conf index ad95417..88ae065 100644 --- a/nohang.conf +++ b/nohang.conf @@ -12,19 +12,19 @@ Печатать параметров конфига при запуске программы. Допустимые значения: True и False -print_config = True +print_config = False Печатать ли результаты измерения доступной памяти. Допустимые значения: True и False -print_mem_check_results = True +print_mem_check_results = False Печатать ли время сна между проверками памяти и после отправки сигналов. Можно установить в значение True для дебага. Допустимые значения: True и False (В этой ветке по дефолту True) -print_sleep_periods = True +print_sleep_periods = False ##################################################################### @@ -36,7 +36,7 @@ print_sleep_periods = True В Fedora 28 значение True вызывает увеличение потребления памяти процессом на 200 MiB, в Debian 8 и 9 такой проблемы нет. -mlockall = False +mlockall = True Установка отрицательных значений self_nice и self_oom_score_adj требует наличия root прав. @@ -71,9 +71,9 @@ self_oom_score_adj = -1000 достаточно хорошо, успешно справляясь с резкими скачками потребления памяти. -rate_mem = 3 -rate_swap = 1 -rate_zram = 0.5 +rate_mem = 6 +rate_swap = 3 +rate_zram = 1 ##################################################################### @@ -136,7 +136,7 @@ min_delay_after_sigkill = 3 Требует root прав. -decrease_oom_score_adj = True +decrease_oom_score_adj = False Допустимые значения - целые числа из диапазона [0; 1000] @@ -147,15 +147,21 @@ oom_score_adj_max = 50 VI. DESKTOP NOTIFICATIONS Эта возможность требует наличия notify-send в системе. - В Debian/Ubuntu это обеспечивается установкой пакета libnotify-bin. В Fedora и Arch Linux - пакет libnotify. Также требуется наличие notification-daemon. При запуске nohang от рута уведомления рассылаются всем залогиненным пользователям. See also wiki.archlinux.org/index.php/Desktop_notifications - Допустимые значения: True и False desktop_notifications = True + Дополнительные опции для notify-send. + См. notify-send --help и man notify-send + Примеры: + notify_options = -u "critical" + notify_options = -t "20000" -i "dialog-warning" + +notify_options = -u "normal" +