1014 lines
31 KiB
Python
Executable File
1014 lines
31 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
|
||
# Nohang - The No Hang Daemon for Linux
|
||
|
||
################################################################################
|
||
|
||
### импорты
|
||
|
||
import os
|
||
from ctypes import CDLL
|
||
from operator import itemgetter
|
||
from time import sleep
|
||
from argparse import ArgumentParser
|
||
|
||
################################################################################
|
||
|
||
### задание констант
|
||
|
||
version = 'unknown'
|
||
|
||
sig_dict = {9:'SIGKILL', 15:'SIGTERM'}
|
||
|
||
# директория, в которой запущен скрипт
|
||
cd = os.getcwd()
|
||
|
||
# где искать конфиг, если не указан через опцию -c/--config CONFIG
|
||
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"
|
||
|
||
# означает, что при задани zram disksize = 10000M доступная память
|
||
# уменьшится на 42M
|
||
# найден экспериментально, требует уточнения с разными ядрами и архитектурами
|
||
# на небольших дисксайзах (до гигабайта) может быть больше, до 0.0045
|
||
# создатель модуля zram утверждает, что zram_disksize_factor доожен быть 0.001
|
||
# ("zram uses about 0.1% of the size of the disk"
|
||
# - https://www.kernel.org/doc/Documentation/blockdev/zram.txt),
|
||
# но это утверждение противоречит опытным данным
|
||
zram_disksize_factor = 0.0042
|
||
|
||
################################################################################
|
||
|
||
### задание функций
|
||
|
||
|
||
def string_to_float_convert_test(string):
|
||
try:
|
||
return float(string)
|
||
except ValueError:
|
||
return None
|
||
|
||
|
||
def string_to_int_convert_test(string):
|
||
try:
|
||
return int(string)
|
||
except ValueError:
|
||
return None
|
||
|
||
|
||
def func_decrease_oom_score_adj(oom_score_adj_before, oom_score_adj_after):
|
||
# цикл для наполнения oom_list
|
||
for i in os.listdir('/proc'):
|
||
|
||
# пропускаем элементы, не состоящие только из цифр
|
||
if i.isdigit() is not True:
|
||
continue
|
||
|
||
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'
|
||
)
|
||
except FileNotFoundError:
|
||
pass
|
||
except ProcessLookupError:
|
||
pass
|
||
|
||
|
||
# чтение первой строки файла
|
||
def rline1(path):
|
||
with open(path) as f:
|
||
for line in f:
|
||
return line[:-1]
|
||
|
||
|
||
# запись в файл
|
||
def write(path, string):
|
||
with open(path, 'w') as f:
|
||
f.write(string)
|
||
|
||
|
||
def kib_to_mib(num):
|
||
return round(num / 1024.0)
|
||
|
||
|
||
def percent(num):
|
||
return round(num * 100, 1)
|
||
|
||
|
||
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, ' ')
|
||
|
||
|
||
# K -> M, выравнивание по правому краю
|
||
def human(num, lenth):
|
||
return str(round(num / 1024)).rjust(lenth, ' ')
|
||
|
||
|
||
# возвращает disksize и mem_used_total по zram id
|
||
def zram_stat(zram_id):
|
||
try:
|
||
disksize = rline1('/sys/block/' + zram_id + '/disksize')
|
||
except FileNotFoundError:
|
||
return '0', '0'
|
||
if disksize == ['0\n']:
|
||
return '0', '0'
|
||
try:
|
||
mm_stat = rline1('/sys/block/' + zram_id + '/mm_stat').split(' ')
|
||
mm_stat_list = []
|
||
for i in mm_stat:
|
||
if i != '':
|
||
mm_stat_list.append(i)
|
||
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
|
||
|
||
|
||
# имя через пид
|
||
def pid_to_name(pid):
|
||
try:
|
||
with open('/proc/' + pid + '/status') as f:
|
||
for line in f:
|
||
return line[:-1].split('\t')[1]
|
||
except FileNotFoundError:
|
||
return '<unknown>'
|
||
except ProcessLookupError:
|
||
return '<unknown>'
|
||
|
||
|
||
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
|
||
)
|
||
|
||
oom_list = []
|
||
for i in os.listdir('/proc'):
|
||
if i.isdigit() is not True:
|
||
continue
|
||
try:
|
||
oom_score = int(rline1('/proc/' + i + '/oom_score'))
|
||
except FileNotFoundError:
|
||
oom_score = 0
|
||
except ProcessLookupError:
|
||
oom_score = 0
|
||
oom_list.append((i, oom_score))
|
||
|
||
# получаем список пар (pid, oom_score)
|
||
pid_tuple_list = sorted(oom_list, key=itemgetter(1), reverse=True)[0]
|
||
oom_score = pid_tuple_list[1]
|
||
|
||
# посылаем сигнал
|
||
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
|
||
)
|
||
)
|
||
|
||
try:
|
||
os.kill(int(pid), signal)
|
||
print(' Success')
|
||
|
||
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)
|
||
|
||
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.0 sudo -u {} {} -i dialog-warnig "Nohang tried to prevent OOM" -t {} -u critical '.format(
|
||
i, notify_command, notify_time
|
||
)
|
||
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
|
||
)
|
||
)
|
||
os.system(user_notify_command + info)
|
||
|
||
|
||
except ProcessLookupError:
|
||
print(' No such process')
|
||
except PermissionError:
|
||
print(' Operation not permitted')
|
||
|
||
else:
|
||
print(' oom_score {} < oom_score_min {}'.format(
|
||
oom_score, oom_score_min)
|
||
)
|
||
|
||
# спать всегда или только при успешной отправке сигнала?
|
||
if signal is 9:
|
||
if print_sleep_periods:
|
||
print(' sleep', min_delay_after_sigkill)
|
||
sleep(min_delay_after_sigterm)
|
||
else:
|
||
if print_sleep_periods:
|
||
print(' sleep', min_delay_after_sigterm)
|
||
sleep(min_delay_after_sigterm)
|
||
|
||
|
||
################################################################################
|
||
|
||
### поиск позиций
|
||
|
||
|
||
# ищем позиции
|
||
with open('/proc/meminfo') as file:
|
||
mem_list = file.readlines()
|
||
|
||
mem_list_names = []
|
||
for s in mem_list:
|
||
mem_list_names.append(s.split(':')[0])
|
||
|
||
if mem_list_names[2] != 'MemAvailable':
|
||
print('Your Linux kernel is too old, 3.14+ requie\nExit')
|
||
exit()
|
||
|
||
swap_total_index = mem_list_names.index('SwapTotal')
|
||
swap_free_index = swap_total_index + 1
|
||
|
||
mem_total = int(mem_list[0].split(':')[1].split(' ')[-2])
|
||
|
||
|
||
# еще найти позиции VmRSS & VmSwap
|
||
|
||
|
||
################################################################################
|
||
|
||
### получение пути к конфигу
|
||
|
||
# парсинг аргументов командной строки
|
||
parser = ArgumentParser()
|
||
parser.add_argument(
|
||
'-c',
|
||
'--config',
|
||
help="""path to the config file, default values:
|
||
./nohang.conf, /etc/nohang/nohang.conf""",
|
||
default=None,
|
||
type=str
|
||
)
|
||
|
||
arg_config = parser.parse_args().config
|
||
|
||
|
||
if arg_config is None:
|
||
|
||
config = None
|
||
for i in default_configs:
|
||
if os.path.exists(i):
|
||
config = i
|
||
break
|
||
if config is None:
|
||
print('По дефолтным путям конфиг не найден\nExit', conf_err_mess)
|
||
exit()
|
||
|
||
else:
|
||
|
||
if os.path.exists(arg_config):
|
||
config = arg_config
|
||
else:
|
||
print("File {} doesn't exists{}".format(arg_config, conf_err_mess))
|
||
exit()
|
||
|
||
|
||
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:
|
||
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:
|
||
a = line.split('=')
|
||
config_dict[a[0].strip()] = a[1].strip()
|
||
except PermissionError:
|
||
print('PermissionError', conf_err_mess)
|
||
exit()
|
||
except UnicodeDecodeError:
|
||
print('UnicodeDecodeError', conf_err_mess)
|
||
exit()
|
||
except IsADirectoryError:
|
||
print('IsADirectoryError', conf_err_mess)
|
||
exit()
|
||
except IndexError:
|
||
print('IndexError', conf_err_mess)
|
||
exit()
|
||
|
||
|
||
################################################################################
|
||
|
||
### извлечение параметров из словаря
|
||
### проверка наличия всех необходимых параметров
|
||
### валидация всех параметров
|
||
|
||
|
||
|
||
if 'print_config' in config_dict:
|
||
print_config = config_dict['print_config']
|
||
if print_config == 'True':
|
||
print_config = True
|
||
elif print_config == 'False':
|
||
print_config = False
|
||
else:
|
||
print(
|
||
'Invalid print_config value {} (should be True or Fa' \
|
||
'lse)\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':
|
||
print_mem_check_results = True
|
||
elif print_mem_check_results == 'False':
|
||
print_mem_check_results = False
|
||
else:
|
||
print(
|
||
'Invalid print_mem_check_results value {} (should be True o' \
|
||
'r 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':
|
||
print_sleep_periods = True
|
||
elif print_sleep_periods == 'False':
|
||
print_sleep_periods = False
|
||
else:
|
||
print(
|
||
'Invalid print_sleep_periods value {} (should be Tru' \
|
||
'e or False)\nExit'.format(
|
||
print_sleep_periods
|
||
)
|
||
)
|
||
exit()
|
||
else:
|
||
print('print_sleep_periods not in config\nExit')
|
||
exit()
|
||
|
||
|
||
if 'mlockall' in config_dict:
|
||
mlockall = config_dict['mlockall']
|
||
if mlockall == 'True':
|
||
mlockall = True
|
||
elif mlockall == 'False':
|
||
mlockall = False
|
||
else:
|
||
print(
|
||
'Invalid mlockall value {} (should be True or False)\nExit'.format(
|
||
mlockall
|
||
)
|
||
)
|
||
exit()
|
||
else:
|
||
print('mlockall not in config\nExit')
|
||
exit()
|
||
|
||
|
||
if 'self_nice' in config_dict:
|
||
self_nice = config_dict['self_nice']
|
||
if string_to_int_convert_test(self_nice) is None:
|
||
print('Invalid self_nice value, not integer\nExit')
|
||
exit()
|
||
else:
|
||
self_nice = int(self_nice)
|
||
if self_nice < -20 or self_nice > 19:
|
||
print('Недопустимое значение self_nice\nExit')
|
||
exit()
|
||
else:
|
||
print('self_nice not in config\nExit')
|
||
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)
|
||
if self_oom_score_adj is None:
|
||
print('Invalid self_oom_score_adj value, not integer\nExit')
|
||
exit()
|
||
else:
|
||
if self_oom_score_adj < -1000 or self_oom_score_adj > 1000:
|
||
print('Недопустимое значение self_oom_score_adj\nExit')
|
||
exit()
|
||
else:
|
||
print('self_oom_score_adj not in config\nExit')
|
||
exit()
|
||
|
||
|
||
if 'rate_mem' in config_dict:
|
||
rate_mem = string_to_float_convert_test(config_dict['rate_mem'])
|
||
if rate_mem is None:
|
||
print('Invalid rate_mem value, not float\nExit')
|
||
exit()
|
||
rate_mem = float(rate_mem)
|
||
if rate_mem <= 0:
|
||
print('rate_mem должен быть положительным\nExit')
|
||
exit()
|
||
else:
|
||
print('rate_mem not in config\nExit')
|
||
exit()
|
||
|
||
|
||
if 'rate_swap' in config_dict:
|
||
rate_swap = string_to_float_convert_test(config_dict['rate_swap'])
|
||
if rate_swap is None:
|
||
print('Invalid rate_swap value, not float\nExit')
|
||
exit()
|
||
rate_swap = float(rate_swap)
|
||
if rate_swap <= 0:
|
||
print('rate_swap должен быть положительным\nExit')
|
||
exit()
|
||
else:
|
||
print('rate_swap not in config\nExit')
|
||
exit()
|
||
|
||
|
||
if 'rate_zram' in config_dict:
|
||
rate_zram = string_to_float_convert_test(config_dict['rate_zram'])
|
||
if rate_zram is None:
|
||
print('Invalid rate_zram value, not float\nExit')
|
||
exit()
|
||
rate_zram = float(rate_zram)
|
||
if rate_zram <= 0:
|
||
print('rate_zram должен быть положительным\nExit')
|
||
exit()
|
||
else:
|
||
print('rate_zram not in config\nExit')
|
||
exit()
|
||
|
||
|
||
|
||
if 'mem_min_sigterm' in config_dict:
|
||
mem_min_sigterm = config_dict['mem_min_sigterm']
|
||
else:
|
||
print('mem_min_sigterm not in config\nExit')
|
||
exit()
|
||
|
||
|
||
if 'mem_min_sigkill' in config_dict:
|
||
mem_min_sigkill = config_dict['mem_min_sigkill']
|
||
else:
|
||
print('mem_min_sigkill not in config\nExit')
|
||
exit()
|
||
|
||
|
||
if 'swap_min_sigterm' in config_dict:
|
||
swap_min_sigterm = config_dict['swap_min_sigterm']
|
||
else:
|
||
print('swap_min_sigterm not in config\nExit')
|
||
exit()
|
||
|
||
|
||
if 'swap_min_sigkill' in config_dict:
|
||
swap_min_sigkill = config_dict['swap_min_sigkill']
|
||
else:
|
||
print('swap_min_sigkill not in config\nExit')
|
||
exit()
|
||
|
||
|
||
if 'zram_max_sigterm' in config_dict:
|
||
zram_max_sigterm = config_dict['zram_max_sigterm']
|
||
else:
|
||
print('zram_max_sigterm not in config\nExit')
|
||
exit()
|
||
|
||
|
||
if 'zram_max_sigkill' in config_dict:
|
||
zram_max_sigkill = config_dict['zram_max_sigkill']
|
||
else:
|
||
print('zram_max_sigkill not in config\nExit')
|
||
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'])
|
||
if min_delay_after_sigterm is None:
|
||
print('Invalid min_delay_after_sigterm value, not float\nExit')
|
||
exit()
|
||
min_delay_after_sigterm = float(min_delay_after_sigterm)
|
||
if min_delay_after_sigterm < 0:
|
||
print('min_delay_after_sigterm должен быть неотрицательным\nExit')
|
||
exit()
|
||
else:
|
||
print('min_delay_after_sigterm not in config\nExit')
|
||
exit()
|
||
|
||
|
||
if 'min_delay_after_sigkill' in config_dict:
|
||
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()
|
||
min_delay_after_sigkill = float(min_delay_after_sigkill)
|
||
if min_delay_after_sigkill < 0:
|
||
print('min_delay_after_sigkill должен быть неотрицательным\nExit')
|
||
exit()
|
||
else:
|
||
print('min_delay_after_sigkill not in config\nExit')
|
||
exit()
|
||
|
||
|
||
if 'oom_score_min' in config_dict:
|
||
oom_score_min = config_dict['oom_score_min']
|
||
if string_to_int_convert_test(oom_score_min) is None:
|
||
print('Invalid oom_score_min value, not integer\nExit')
|
||
exit()
|
||
else:
|
||
oom_score_min = int(oom_score_min)
|
||
if oom_score_min < 0 or oom_score_min > 1000:
|
||
print('Недопустимое значение oom_score_min\nExit')
|
||
exit()
|
||
else:
|
||
print('oom_score_min not in config\nExit')
|
||
exit()
|
||
|
||
|
||
if 'decrease_oom_score_adj' in config_dict:
|
||
decrease_oom_score_adj = config_dict['decrease_oom_score_adj']
|
||
if decrease_oom_score_adj == 'True':
|
||
decrease_oom_score_adj = True
|
||
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
|
||
)
|
||
)
|
||
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')
|
||
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')
|
||
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')
|
||
exit()
|
||
|
||
|
||
|
||
if 'desktop_notifications' in config_dict:
|
||
desktop_notifications = config_dict['desktop_notifications']
|
||
if desktop_notifications == 'True':
|
||
desktop_notifications = True
|
||
from subprocess import Popen, PIPE
|
||
elif desktop_notifications == 'False':
|
||
desktop_notifications = False
|
||
else:
|
||
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):
|
||
if string.endswith('%'):
|
||
return float(string[:-1].strip()) / 100 * mem_total
|
||
elif string.endswith('K'):
|
||
return float(string[:-1].strip())
|
||
elif string.endswith('M'):
|
||
return float(string[:-1].strip()) * 1024
|
||
elif string.endswith('G'):
|
||
return float(string[:-1].strip()) * 1048576
|
||
else:
|
||
print(
|
||
'Конфиг инвалид, где-то неверно указаны единицы измерения\nExit'
|
||
)
|
||
exit()
|
||
|
||
|
||
mem_min_sigterm_kb = sig_level_to_kb(mem_min_sigterm)
|
||
mem_min_sigkill_kb = sig_level_to_kb(mem_min_sigkill)
|
||
zram_max_sigterm_kb = sig_level_to_kb(zram_max_sigterm)
|
||
zram_max_sigkill_kb = sig_level_to_kb(zram_max_sigkill)
|
||
|
||
|
||
# возвращает число килобайт при задании в конфиге абсолютного значения,
|
||
# или кортеж с числом процентов
|
||
def sig_level_to_kb_swap(string):
|
||
|
||
if string.endswith('%'):
|
||
return float(string[:-1].strip()), True
|
||
|
||
elif string.endswith('K'):
|
||
return float(string[:-1].strip())
|
||
elif string.endswith('M'):
|
||
return float(string[:-1].strip()) * 1024
|
||
elif string.endswith('G'):
|
||
return float(string[:-1].strip()) * 1048576
|
||
else:
|
||
print('Конфиг инвалид, где-то неверно указаны единицы измерения\nExit')
|
||
exit()
|
||
|
||
|
||
# получаем число килобайт или кортеж с процентами
|
||
swap_min_sigterm_swap = sig_level_to_kb_swap(swap_min_sigterm)
|
||
swap_min_sigkill_swap = sig_level_to_kb_swap(swap_min_sigkill)
|
||
|
||
if type(swap_min_sigterm_swap) is tuple:
|
||
swap_term_is_percent = True
|
||
swap_min_sigterm_percent = swap_min_sigterm_swap[0]
|
||
else:
|
||
swap_term_is_percent = False
|
||
swap_min_sigterm_kb = swap_min_sigterm_swap
|
||
|
||
if type(swap_min_sigkill_swap) is tuple:
|
||
swap_kill_is_percent = True
|
||
swap_min_sigkill_percent = swap_min_sigkill_swap[0]
|
||
else:
|
||
swap_kill_is_percent = False
|
||
swap_min_sigkill_kb = swap_min_sigkill_swap
|
||
|
||
|
||
|
||
################################################################################
|
||
|
||
### самозащита и печать конфига
|
||
|
||
|
||
# повышаем приоритет
|
||
try:
|
||
os.nice(self_nice)
|
||
self_nice_result = 'OK'
|
||
except PermissionError:
|
||
self_nice_result = 'Fail'
|
||
pass
|
||
|
||
# возможность запрета самоубийства
|
||
try:
|
||
with open('/proc/self/oom_score_adj', 'w') as file:
|
||
file.write('{}\n'.format(self_oom_score_adj))
|
||
self_oom_score_adj_result = 'OK'
|
||
except PermissionError:
|
||
pass
|
||
self_oom_score_adj_result = 'Fail'
|
||
except OSError:
|
||
self_oom_score_adj_result = 'Fail'
|
||
pass
|
||
|
||
# запрет своппинга процесса
|
||
if mlockall:
|
||
result = CDLL('libc.so.6', use_errno=True).mlockall(3)
|
||
if result is 0:
|
||
mla_res = 'OK'
|
||
else:
|
||
mla_res = 'Fail'
|
||
else:
|
||
mla_res = ''
|
||
|
||
|
||
if os.geteuid() == 0:
|
||
root = True
|
||
decrease_res = 'OK'
|
||
else:
|
||
root = False
|
||
decrease_res = 'Impossible'
|
||
|
||
|
||
|
||
if print_config:
|
||
|
||
print('\nI. VERBOSITY')
|
||
print('print_config: {}'.format(print_config))
|
||
print('print_mem_check_results: {}'.format(print_mem_check_results))
|
||
print('print_sleep_periods: {}'.format(print_sleep_periods))
|
||
|
||
print('\nII. SELF PROTECTION')
|
||
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))
|
||
print('rate_swap: {}'.format(rate_swap))
|
||
print('rate_zram: {}'.format(rate_zram))
|
||
|
||
print('\nIV. ПОРОГИ ДЛЯ ОТПРАВКИ СИГНАЛОВ')
|
||
print('mem_min_sigterm: {}'.format(mem_min_sigterm))
|
||
print('mem_min_sigkill: {}'.format(mem_min_sigkill))
|
||
print('swap_min_sigterm: {}'.format(swap_min_sigterm))
|
||
print('swap_min_sigkill: {}'.format(swap_min_sigkill))
|
||
print('zram_max_sigterm: {}'.format(zram_max_sigterm))
|
||
print('zram_max_sigkill: {}'.format(zram_max_sigkill))
|
||
|
||
print('\nV. THE PREVENTION OF KILLING INNOCENT VICTIMS')
|
||
print('min_delay_after_sigterm: {}'.format(min_delay_after_sigterm))
|
||
print('min_delay_after_sigkill: {}'.format(min_delay_after_sigkill))
|
||
print('oom_score_min: {}'.format(oom_score_min))
|
||
|
||
# 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('\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
|
||
mem_len = len(str(round(mem_total / 1024.0)))
|
||
|
||
|
||
print('\nStart monitoring...')
|
||
|
||
################################################################################
|
||
|
||
### цикл проверки уровней доступной памяти
|
||
|
||
while True:
|
||
|
||
#decrease_oom_score_adj(oom_score_adj_before, oom_score_adj_after)
|
||
# находим mem_available, swap_total, swap_free
|
||
with open('/proc/meminfo') as f:
|
||
for n, line in enumerate(f):
|
||
if n is 2:
|
||
mem_available = int(line.split(':')[1].split(' ')[-2])
|
||
continue
|
||
if n is swap_total_index:
|
||
swap_total = int(line.split(':')[1].split(' ')[-2])
|
||
continue
|
||
if n is swap_free_index:
|
||
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
|
||
|
||
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
|
||
for dev in os.listdir('/sys/block'):
|
||
if dev.startswith('zram'):
|
||
stat = zram_stat(dev)
|
||
disksize_sum += int(stat[0])
|
||
mem_used_total_sum += int(stat[1])
|
||
mem_used_zram = (
|
||
mem_used_total_sum + disksize_sum * zram_disksize_factor
|
||
) / 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:
|
||
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))
|
||
)
|
||
)
|
||
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)
|
||
)
|
||
)
|
||
|
||
|
||
# если swap_min_sigkill задан в абсолютной величине и Swap_total = 0
|
||
if swap_total > swap_min_sigkill_kb:
|
||
swap_sigkill_pc = percent(swap_min_sigkill_kb / (swap_total + 1))
|
||
else:
|
||
swap_sigkill_pc = '-'
|
||
|
||
if swap_total > swap_min_sigterm_kb:
|
||
swap_sigterm_pc = percent(swap_min_sigterm_kb / (swap_total + 1))
|
||
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(
|
||
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)
|
||
)
|
||
)
|
||
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
|
||
)
|
||
)
|
||
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)
|
||
)
|
||
)
|
||
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
|
||
if t_zram < 0.01:
|
||
t_zram = 0.01
|
||
|
||
t_mem_swap = t_mem + t_swap
|
||
t_mem_zram = t_mem + t_zram
|
||
|
||
if t_mem_swap <= t_mem_zram:
|
||
t = t_mem_swap
|
||
else:
|
||
t = t_mem_zram
|
||
|
||
try:
|
||
if print_sleep_periods:
|
||
print('sleep', round(t, 3))
|
||
sleep(t)
|
||
except KeyboardInterrupt:
|
||
exit()
|
||
|