оптимизация уведомлений и потребления памяти

This commit is contained in:
Alexey Avramov 2018-06-19 02:08:05 +09:00
parent 4847963277
commit 6231542d26
2 changed files with 159 additions and 135 deletions

268
nohang
View File

@ -7,7 +7,6 @@
# импорты # импорты
import os import os
from ctypes import CDLL
from operator import itemgetter from operator import itemgetter
from time import sleep from time import sleep
from argparse import ArgumentParser 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' version = 'unknown'
sig_dict = {9: 'SIGKILL', 15: 'SIGTERM'} 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 доступная память # означает, что при задани zram disksize = 10000M доступная память
# уменьшится на 42M # уменьшится на 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')) oom_score_adj = int(rline1('/proc/' + i + '/oom_score_adj'))
if oom_score_adj > oom_score_adj_max: if oom_score_adj > oom_score_adj_max:
write('/proc/' + i + '/oom_score_adj', write('/proc/' + i + '/oom_score_adj',
str(oom_score_adj_max) + '\n') str(oom_score_adj_max) + '\n')
except FileNotFoundError: except FileNotFoundError:
pass pass
except ProcessLookupError: except ProcessLookupError:
@ -150,11 +146,36 @@ def pid_to_name(pid):
return '<unknown>' return '<unknown>'
def send_notify(signal, name, pid, oom_score, vm_rss, vm_swap):
# текст отправляемого уведомления
info = '"<u>Nohang</u> sent <u>{}</u> \nto the process <b>{}</b> \n<i>P' \
'id:</i> <b>{}</b> \n<i>oom_score:</i> <b>{}</b> \n<i>VmRSS:</i> <b' \
'>{} MiB</b> \n<i>VmSwap:</i> <b>{} MiB</b>" &'.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): def find_victim_and_send_signal(signal):
# выставляем потолок для oom_score_adj всех процессов
if decrease_oom_score_adj and root: if decrease_oom_score_adj and root:
func_decrease_oom_score_adj(oom_score_adj_max) func_decrease_oom_score_adj(oom_score_adj_max)
# получаем список процессов
oom_list = [] oom_list = []
for i in os.listdir('/proc'): for i in os.listdir('/proc'):
if i.isdigit() is not True: if i.isdigit() is not True:
@ -167,8 +188,10 @@ def find_victim_and_send_signal(signal):
oom_score = 0 oom_score = 0
oom_list.append((i, oom_score)) 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] pid_tuple_list = sorted(oom_list, key=itemgetter(1), reverse=True)[0]
# получаем максимальный oom_score
oom_score = pid_tuple_list[1] oom_score = pid_tuple_list[1]
# посылаем сигнал # посылаем сигнал
@ -178,43 +201,38 @@ def find_victim_and_send_signal(signal):
name = pid_to_name(pid) name = pid_to_name(pid)
with open('/proc/' + pid + '/status') as f: # находим VmRSS и VmSwap процесса, которому попытаемся послать сигнал
for n, line in enumerate(f): 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: print(' Try to send the {} signal to {},\n Pid {}, oom_score {}, V'
vm_rss = kib_to_mib(int(line.split(':')[1].split(' ')[-2])) 'mRSS {} MiB, VmSwap {} MiB'.format(
continue sig_dict[signal], name, pid, oom_score, vm_rss, vm_swap))
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: try:
os.kill(int(pid), signal) os.kill(int(pid), signal)
print(' Success') print(' Success')
if desktop_notifications: if desktop_notifications:
send_notify(signal, name, pid, oom_score, vm_rss, vm_swap)
info = '"<u>Nohang</u> sent <u>{}</u> \nto the process \n<i>Name:</i> <b>{}</b> \n<i>Pid:</i> <b>{}</b> \n<i>oom_score:</i> <b>{}</b> \n<i>VmRSS:</i> <b>{} MiB</b> \n<i>VmSwap:</i> <b>{} MiB</b>" &'.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)
except ProcessLookupError: except ProcessLookupError:
print(' No such process') print(' No such process')
@ -236,11 +254,11 @@ def find_victim_and_send_signal(signal):
print(' sleep', min_delay_after_sigterm) print(' sleep', min_delay_after_sigterm)
sleep(min_delay_after_sigterm) sleep(min_delay_after_sigterm)
########################################################################## ##########################################################################
# поиск позиций и mem_total # поиск позиций и mem_total
with open('/proc/meminfo') as file: with open('/proc/meminfo') as file:
mem_list = file.readlines() mem_list = file.readlines()
@ -283,7 +301,9 @@ parser.add_argument(
type=str type=str
) )
arg_config = parser.parse_args().config args = parser.parse_args()
arg_config = args.config
if arg_config is None: if arg_config is None:
@ -337,7 +357,6 @@ except IndexError:
print('IndexError', conf_err_mess) print('IndexError', conf_err_mess)
exit() exit()
########################################################################## ##########################################################################
# извлечение параметров из словаря # извлечение параметров из словаря
@ -352,11 +371,9 @@ if 'print_config' in config_dict:
elif print_config == 'False': elif print_config == 'False':
print_config = False print_config = False
else: else:
print( print('Invalid print_config value {} (should be True or False)\nE'
'Invalid print_config value {} (should be True or False)\nExit'.format( 'xit'.format(
print_config print_config))
)
)
exit() exit()
else: else:
print('Print_config not in config\nExit') 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': elif print_mem_check_results == 'False':
print_mem_check_results = False print_mem_check_results = False
else: else:
print('Invalid print_mem_check_results value {} (should be True or False)\nExit'.format( print('Invalid print_mem_check_results value {} (should be Tr'
print_mem_check_results)) 'ue or False)\nExit'.format(
print_mem_check_results))
exit() exit()
else: else:
print('print_mem_check_results not in config\nExit') 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': elif print_sleep_periods == 'False':
print_sleep_periods = False print_sleep_periods = False
else: else:
print('Invalid print_sleep_periods value {} (should be True or False)\nExit'.format( print('Invalid print_sleep_periods value {} (should be True or F'
print_sleep_periods)) 'alse)\nExit'.format(
print_sleep_periods))
exit() exit()
else: else:
print('print_sleep_periods not in config\nExit') 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': elif decrease_oom_score_adj == 'False':
decrease_oom_score_adj = False decrease_oom_score_adj = False
else: else:
print('invalid decrease_oom_score_adj value {} (should be True or False)\nExit'.format( print('invalid decrease_oom_score_adj value {} (should be Tru'
decrease_oom_score_adj)) 'e or False)\nExit'.format(
decrease_oom_score_adj))
exit() exit()
else: else:
print('decrease_oom_score_adj not in config\nExit') 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'] desktop_notifications = config_dict['desktop_notifications']
if desktop_notifications == 'True': if desktop_notifications == 'True':
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': elif desktop_notifications == 'False':
desktop_notifications = False desktop_notifications = False
else: else:
print('Invalid desktop_notifications value {} (should be True or False)\nExit'.format( print('Invalid desktop_notifications value {} (should be Tru'
desktop_notifications)) 'e or False)\nExit'.format(
desktop_notifications))
exit() exit()
else: else:
print('desktop_notifications not in config\nExit') print('desktop_notifications not in config\nExit')
exit() 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_kill_is_percent = False
swap_min_sigkill_kb = swap_min_sigkill_swap swap_min_sigkill_kb = swap_min_sigkill_swap
########################################################################## ##########################################################################
# самозащита и печать конфига # самозащита и печать конфига
@ -705,6 +737,7 @@ except OSError:
# запрет своппинга процесса # запрет своппинга процесса
if mlockall: if mlockall:
from ctypes import CDLL
result = CDLL('libc.so.6', use_errno=True).mlockall(3) result = CDLL('libc.so.6', use_errno=True).mlockall(3)
if result is 0: if result is 0:
mla_res = 'OK' mla_res = 'OK'
@ -765,6 +798,8 @@ if print_config:
print('\nVI. DESKTOP NOTIFICATIONS') print('\nVI. DESKTOP NOTIFICATIONS')
print('desktop_notifications: {}'.format(desktop_notifications)) print('desktop_notifications: {}'.format(desktop_notifications))
if desktop_notifications:
print('notify_options: {}'.format(notify_options))
# для рассчета ширины столбцов при печати mem и zram # для рассчета ширины столбцов при печати mem и zram
@ -784,13 +819,13 @@ while True:
with open('/proc/meminfo') as f: with open('/proc/meminfo') as f:
for n, line in enumerate(f): for n, line in enumerate(f):
if n is 2: if n is 2:
mem_available = int(line.split(':')[1].split(' ')[-2]) mem_available = int(line.split(':')[1].strip(' kB\n'))
continue continue
if n is swap_total_index: if n is swap_total_index:
swap_total = int(line.split(':')[1].split(' ')[-2]) swap_total = int(line.split(':')[1].strip(' kB\n'))
continue continue
if n is swap_free_index: if n is swap_free_index:
swap_free = int(line.split(':')[1].split(' ')[-2]) swap_free = int(line.split(':')[1].strip(' kB\n'))
break break
# если swap_min_sigkill задан в процентах # если swap_min_sigkill задан в процентах
@ -818,35 +853,28 @@ while True:
# печать размеров доступной памяти # печать размеров доступной памяти
if swap_total == 0 and mem_used_zram == 0: if swap_total == 0 and mem_used_zram == 0:
if print_mem_check_results: if print_mem_check_results:
print( print('MemAvail: {} M, {} %'.format(
'MemAvail: {} M, {} %'.format( human(mem_available, mem_len),
human(mem_available, mem_len), just_percent_mem(mem_available / mem_total)))
just_percent_mem(mem_available / mem_total)
)
)
elif swap_total > 0 and mem_used_zram == 0: elif swap_total > 0 and mem_used_zram == 0:
if print_mem_check_results: if print_mem_check_results:
print( print('MemAvail: {} M, {} % | SwapFree: {} M, {} %'.format(
'MemAvail: {} M, {} % | SwapFree: {} M, {} %'.format( human(mem_available, mem_len),
human(mem_available, mem_len), just_percent_mem(mem_available / mem_total),
just_percent_mem(mem_available / mem_total), human(swap_free, swap_len),
human(swap_free, swap_len), just_percent_swap(swap_free / (swap_total + 0.0001))))
just_percent_swap(swap_free / (swap_total + 0.0001))
)
)
else: else:
if print_mem_check_results: if print_mem_check_results:
print( print('MemAvail: {} M, {} % | SwapFree: {} M, {} % | MemUsed'
'MemAvail: {} M, {} % | SwapFree: {} M, {} % | MemUsed' 'Zram: {} M, {} %'.format(
'Zram: {} M, {} %'.format( human(mem_available, mem_len),
human(mem_available, mem_len), just_percent_mem(mem_available / mem_total),
just_percent_mem(mem_available / mem_total), human(swap_free, swap_len),
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),
human(mem_used_zram, mem_len), just_percent_mem(mem_used_zram / mem_total)))
just_percent_mem(mem_used_zram / mem_total)
)
)
# если swap_min_sigkill задан в абсолютной величине и Swap_total = 0 # если swap_min_sigkill задан в абсолютной величине и Swap_total = 0
if swap_total > swap_min_sigkill_kb: if swap_total > swap_min_sigkill_kb:
@ -861,61 +889,51 @@ while True:
# MEM SWAP KILL # 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( print('+ MemAvail ({} M, {} %) < mem_min_sigkill ({} M, {} %)\n S'
'+ MemAvail ({} M, {} %) < mem_min_sigkill ({} M, {} %)\n S' 'wapFree ({} M, {} %) < swap_min_sigkill ({} M, {} %)'.format(
'wapFree ({} M, {} %) < swap_min_sigkill ({} M, {} %)'.format( kib_to_mib(mem_available),
kib_to_mib(mem_available), percent(mem_available / mem_total),
percent(mem_available / mem_total), kib_to_mib(mem_min_sigkill_kb),
kib_to_mib(mem_min_sigkill_kb), percent(mem_min_sigkill_kb / mem_total),
percent(mem_min_sigkill_kb / mem_total), kib_to_mib(swap_free),
kib_to_mib(swap_free), percent(swap_free / (swap_total + 0.0001)),
percent(swap_free / (swap_total + 0.0001)), kib_to_mib(swap_min_sigkill_kb),
kib_to_mib(swap_min_sigkill_kb), swap_sigkill_pc))
swap_sigkill_pc
)
)
find_victim_and_send_signal(9) find_victim_and_send_signal(9)
continue continue
# ZRAM KILL # ZRAM KILL
if mem_used_zram >= zram_max_sigkill_kb: if mem_used_zram >= zram_max_sigkill_kb:
print( print('+ MemUsedZram ({} M, {} %) > zram_max_sigkill ({} M, {} %)'.format(
'+ MemUsedZram ({} M, {} %) > zram_max_sigkill ({} M, {} %)'.format( kib_to_mib(mem_used_zram),
kib_to_mib(mem_used_zram), percent(mem_used_zram / mem_total),
percent( kib_to_mib(zram_max_sigkill_kb),
mem_used_zram / percent(zram_max_sigkill_kb / mem_total)))
mem_total),
kib_to_mib(zram_max_sigkill_kb),
percent(
zram_max_sigkill_kb /
mem_total)))
find_victim_and_send_signal(9) find_victim_and_send_signal(9)
continue continue
# MEM SWAP TERM # 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('+ MemAvail ({} M, {} %) < mem_min_sigterm ({} M, {} %)\n SwapFree ({} M, {} %) < swap_min_sigterm ({} M, {} %)'.format( print('+ MemAvail ({} M, {} %) < mem_min_sigterm ({} M, {} %)\n S'
kib_to_mib(mem_available), 'wapFree ({} M, {} %) < swap_min_sigterm ({} M, {} %)'.format(
percent(mem_available / mem_total), kib_to_mib(mem_available),
kib_to_mib(mem_min_sigterm_kb), percent(mem_available / mem_total),
percent(mem_min_sigterm_kb / mem_total), kib_to_mib(mem_min_sigterm_kb),
kib_to_mib(swap_free), percent(mem_min_sigterm_kb / mem_total),
percent(swap_free / (swap_total + 0.0001)), kib_to_mib(swap_free),
kib_to_mib(swap_min_sigterm_kb), percent(swap_free / (swap_total + 0.0001)),
swap_sigterm_pc)) kib_to_mib(swap_min_sigterm_kb),
swap_sigterm_pc))
find_victim_and_send_signal(15) find_victim_and_send_signal(15)
# ZRAM TERM # ZRAM TERM
if mem_used_zram >= zram_max_sigterm_kb: if mem_used_zram >= zram_max_sigterm_kb:
print('+ MemUsedZram ({} M, {} %) > zram_max_sigterm ({} M, {} %)'.format( print('+ MemUsedZram ({} M, {} %) > zram_max_sigt'
kib_to_mib(mem_used_zram), 'erm ({} M, {} %)'.format(
percent( kib_to_mib(mem_used_zram),
mem_used_zram / percent(mem_used_zram / mem_total),
mem_total), kib_to_mib(zram_max_sigterm_kb),
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) find_victim_and_send_signal(15)
# задание периода в зависимости от рейтов и уровней доступной памяти # задание периода в зависимости от рейтов и уровней доступной памяти

View File

@ -12,19 +12,19 @@
Печатать параметров конфига при запуске программы. Печатать параметров конфига при запуске программы.
Допустимые значения: True и False Допустимые значения: True и False
print_config = True print_config = False
Печатать ли результаты измерения доступной памяти. Печатать ли результаты измерения доступной памяти.
Допустимые значения: True и False Допустимые значения: True и False
print_mem_check_results = True print_mem_check_results = False
Печатать ли время сна между проверками памяти и после отправки Печатать ли время сна между проверками памяти и после отправки
сигналов. Можно установить в значение True для дебага. сигналов. Можно установить в значение True для дебага.
Допустимые значения: True и False Допустимые значения: True и False
(В этой ветке по дефолту True) (В этой ветке по дефолту True)
print_sleep_periods = True print_sleep_periods = False
##################################################################### #####################################################################
@ -36,7 +36,7 @@ print_sleep_periods = True
В Fedora 28 значение True вызывает увеличение потребления В Fedora 28 значение True вызывает увеличение потребления
памяти процессом на 200 MiB, в Debian 8 и 9 такой проблемы нет. памяти процессом на 200 MiB, в Debian 8 и 9 такой проблемы нет.
mlockall = False mlockall = True
Установка отрицательных значений self_nice и self_oom_score_adj Установка отрицательных значений self_nice и self_oom_score_adj
требует наличия root прав. требует наличия root прав.
@ -71,9 +71,9 @@ self_oom_score_adj = -1000
достаточно хорошо, успешно справляясь с резкими скачками потребления достаточно хорошо, успешно справляясь с резкими скачками потребления
памяти. памяти.
rate_mem = 3 rate_mem = 6
rate_swap = 1 rate_swap = 3
rate_zram = 0.5 rate_zram = 1
##################################################################### #####################################################################
@ -136,7 +136,7 @@ min_delay_after_sigkill = 3
Требует root прав. Требует root прав.
decrease_oom_score_adj = True decrease_oom_score_adj = False
Допустимые значения - целые числа из диапазона [0; 1000] Допустимые значения - целые числа из диапазона [0; 1000]
@ -147,15 +147,21 @@ oom_score_adj_max = 50
VI. DESKTOP NOTIFICATIONS VI. DESKTOP NOTIFICATIONS
Эта возможность требует наличия notify-send в системе. Эта возможность требует наличия notify-send в системе.
В Debian/Ubuntu это обеспечивается установкой пакета В Debian/Ubuntu это обеспечивается установкой пакета
libnotify-bin. В Fedora и Arch Linux - пакет libnotify. libnotify-bin. В Fedora и Arch Linux - пакет libnotify.
Также требуется наличие notification-daemon. Также требуется наличие notification-daemon.
При запуске nohang от рута уведомления рассылаются всем При запуске nohang от рута уведомления рассылаются всем
залогиненным пользователям. залогиненным пользователям.
See also wiki.archlinux.org/index.php/Desktop_notifications See also wiki.archlinux.org/index.php/Desktop_notifications
Допустимые значения: True и False Допустимые значения: True и False
desktop_notifications = True 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"