1
This commit is contained in:
parent
e9b6ba7724
commit
c329f92c2c
BIN
.README.md.kate-swp
Normal file
BIN
.README.md.kate-swp
Normal file
Binary file not shown.
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2018 hakavlad
|
Copyright (c) 2018 Alexey Avramov
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
71
README.md
71
README.md
@ -1,2 +1,69 @@
|
|||||||
# nohang
|
|
||||||
No hang daemon for Linux
|
The No Hang Daemon
|
||||||
|
==================
|
||||||
|
|
||||||
|
Nohang - аналог earlyoom с поддержкой zram и SIGTERM.
|
||||||
|
|
||||||
|
Особенности:
|
||||||
|
============
|
||||||
|
- задача - препятствовать зависанию системы при нехватке доступной памяти, а также корректное завершение процессов с целью увеличения объема доступной памяти
|
||||||
|
- демон на python3, RSS около 12 MiB
|
||||||
|
- требуется Python 3.4+ и Linux 3.14+
|
||||||
|
- периодически проверяет размеры доступной памяти, при дефиците памяти отправляет SIGKILL или SIGTERM процессу с наибольшим oom_score
|
||||||
|
- поддержка работы со zram, возможность реакции на mem_used_total
|
||||||
|
- удобный конфиг с возможностью тонкой настройки
|
||||||
|
- возможность раздельного задания уровней MemAv, SwFree, mem_used_total для отпраки SIGTERM и SIGKILL, возможность задания в %, KiB, MiB, GiB
|
||||||
|
- возможность снижения oom_score_adj процессов, чьи oom_score_adj завышены (актуально для chromium)
|
||||||
|
- лучший алгоритм выбора периодов между проверками доступной памяти: при больших объемах доступной памяти нет смысла проверять ее состояние часто, поэтому период проверки уменьшается по мере уменьшения размера доступной памяти
|
||||||
|
- интенсивность мониторинга можно гибко настраивать (параметры конфига rate_mem, rate_swap, rate_zram)
|
||||||
|
- память заблокирована с помощью mlockall() для предотвращения своппинга процесса
|
||||||
|
- по умолчанию высокий приоритет процесса nice -20, может регулироваться через конфиг
|
||||||
|
- предотвращение самоубийства с помощью self_oom_score_adj = -1000
|
||||||
|
- возможность задания oom_score_min для предотвращения убийства невиновных
|
||||||
|
- min_delay_after_sigkill для предотвращения массовых убийств
|
||||||
|
- вывод отчета об убийствах такого вида:
|
||||||
|
2018-Jun-07 04:55:16 Mem: 0 M, Swap: 454 M, Zram: 488 M
|
||||||
|
mem_available < mem_term_level and swap_free < swap_term_level
|
||||||
|
Try to send signal 15 to process python3, Pid 7281, oom_score 893
|
||||||
|
Success
|
||||||
|
- наличие системд юнита
|
||||||
|
- ман страница сделана
|
||||||
|
- инсталлятор и деинсталлятор есть
|
||||||
|
|
||||||
|
|
||||||
|
Протестировано на Debian 9 x86_64, Debian 9 x86, Debian 8 x86, Fedora 28 x86_64.
|
||||||
|
|
||||||
|
Установка
|
||||||
|
=========
|
||||||
|
git clone https://github.com/hakavlad/nohang.git
|
||||||
|
cd nohang
|
||||||
|
sudo ./install.sh
|
||||||
|
|
||||||
|
Удаление вместе с конфигом
|
||||||
|
==========================
|
||||||
|
sudo ./purge.sh
|
||||||
|
|
||||||
|
Удалить всё, кроме конфига
|
||||||
|
==========================
|
||||||
|
sudo ./uninstall.sh
|
||||||
|
|
||||||
|
Настройка
|
||||||
|
=========
|
||||||
|
Nohang настраивается с помощью конфига, расположенного после установки
|
||||||
|
по адресу /etc/nohang/nohang.cong
|
||||||
|
К опциям прилагается описание.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
19
install.sh
Executable file
19
install.sh
Executable file
@ -0,0 +1,19 @@
|
|||||||
|
#!/bin/bash -v
|
||||||
|
|
||||||
|
cp nohang /usr/local/bin/
|
||||||
|
chmod 755 /usr/local/bin/nohang
|
||||||
|
|
||||||
|
mkdir /etc/nohang
|
||||||
|
chmod 755 /etc/nohang
|
||||||
|
cp nohang.conf /etc/nohang/
|
||||||
|
chmod 644 /etc/nohang/nohang.conf
|
||||||
|
|
||||||
|
gzip -k nohang.1
|
||||||
|
cp nohang.1.gz /usr/local/share/man/man1/
|
||||||
|
chmod 644 /usr/local/share/man/man1/nohang.1.gz
|
||||||
|
|
||||||
|
cp nohang.service /etc/systemd/system/
|
||||||
|
chmod 644 /etc/systemd/system/nohang.service
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable nohang
|
||||||
|
systemctl restart nohang
|
510
nohang
Executable file
510
nohang
Executable file
@ -0,0 +1,510 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# nohang - no hang daemon
|
||||||
|
|
||||||
|
import os
|
||||||
|
from ctypes import CDLL
|
||||||
|
from operator import itemgetter
|
||||||
|
from signal import SIGKILL, SIGTERM
|
||||||
|
from time import gmtime, strftime, sleep, time
|
||||||
|
|
||||||
|
if os.path.exists('./nohang.conf'):
|
||||||
|
config = './nohang.conf'
|
||||||
|
print('config: {}'.format(config))
|
||||||
|
elif os.path.exists('/etc/nohang/nohang.conf'):
|
||||||
|
config = '/etc/nohang/nohang.conf'
|
||||||
|
print('config: {}'.format(config))
|
||||||
|
else:
|
||||||
|
print('укажите путь к конфигу опцией --config')
|
||||||
|
exit()
|
||||||
|
|
||||||
|
zram_disksize_factor = 0.0042
|
||||||
|
|
||||||
|
###########################################################################################
|
||||||
|
|
||||||
|
def decrease_oom_score_adj(oom_score_adj_before, oom_score_adj_after):
|
||||||
|
print('decrease oom_score_adj...')
|
||||||
|
# цикл для наполнения 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 config_parser(config):
|
||||||
|
if os.path.exists(config):
|
||||||
|
try:
|
||||||
|
with open(config) as f:
|
||||||
|
name_value_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('=')
|
||||||
|
name_value_dict[a[0].strip()] = a[1].strip()
|
||||||
|
return name_value_dict
|
||||||
|
except PermissionError:
|
||||||
|
return 2
|
||||||
|
else:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
def sig_level_to_kb(string):
|
||||||
|
if string.endswith('%'):
|
||||||
|
return mem_total * float(string[:-1].strip()) / 100
|
||||||
|
if string.endswith('KiB'):
|
||||||
|
return float(string[:-3].strip())
|
||||||
|
if string.endswith('MiB'):
|
||||||
|
return float(string[:-3].strip()) * 1024
|
||||||
|
if string.endswith('GiB'):
|
||||||
|
return float(string[:-3].strip()) * 1048576
|
||||||
|
|
||||||
|
|
||||||
|
# перевод дроби в проценты
|
||||||
|
def percent(num):
|
||||||
|
a = str(round(num * 100, 1)).split('.')
|
||||||
|
a0 = a[0].rjust(3, ' ')
|
||||||
|
a1 = a[1]
|
||||||
|
return '{}.{}'.format(a0, a1)
|
||||||
|
|
||||||
|
# K -> M, выравнивание по правому краю
|
||||||
|
def human(num):
|
||||||
|
return str(round(num / 1024)).rjust(5, ' ')
|
||||||
|
|
||||||
|
|
||||||
|
# возвращает disksize и mem_used_total по zram id
|
||||||
|
def zram_stat(zram_id):
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open('/sys/block/' + zram_id + '/disksize') as file:
|
||||||
|
disksize = file.readlines()
|
||||||
|
except FileNotFoundError:
|
||||||
|
return '0', '0'
|
||||||
|
|
||||||
|
if disksize == ['0\n']:
|
||||||
|
return '0', '0'
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
with open('/sys/block/' + zram_id + '/mm_stat') as file:
|
||||||
|
mm_stat = file.readlines()[0][:-1].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:
|
||||||
|
|
||||||
|
with open('/sys/block/' + zram_id + '/mem_used_total') as file:
|
||||||
|
mem_used_total = file.readlines()[0][:-1]
|
||||||
|
|
||||||
|
return disksize[0][:-1], 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(signal):
|
||||||
|
|
||||||
|
if decrease_oom_score_adj_enable and root:
|
||||||
|
decrease_oom_score_adj(oom_score_adj_before, oom_score_adj_after)
|
||||||
|
|
||||||
|
print('find victim...')
|
||||||
|
|
||||||
|
oom_list = []
|
||||||
|
|
||||||
|
for i in os.listdir('/proc'):
|
||||||
|
|
||||||
|
if i.isdigit() is not True:
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open('/proc/' + i + '/oom_score') as file:
|
||||||
|
oom_score = int(file.readlines()[0][:-1])
|
||||||
|
except FileNotFoundError:
|
||||||
|
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 signal {} to process {}, Pid {}, oom_score {}'.format(
|
||||||
|
signal, name, pid, oom_score
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
os.kill(int(pid), signal)
|
||||||
|
print('Success\n')
|
||||||
|
except ProcessLookupError:
|
||||||
|
print('No such process\n')
|
||||||
|
except PermissionError:
|
||||||
|
print('Operation not permitted\n')
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
print('\noom_score {} < oom_score_min {}\n'.format(oom_score, oom_score_min))
|
||||||
|
|
||||||
|
|
||||||
|
###########################################################################################
|
||||||
|
|
||||||
|
|
||||||
|
# START
|
||||||
|
|
||||||
|
|
||||||
|
# ищем позиции
|
||||||
|
|
||||||
|
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), bye!')
|
||||||
|
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])
|
||||||
|
|
||||||
|
###############################################################################################
|
||||||
|
|
||||||
|
config_dict = config_parser(config)
|
||||||
|
|
||||||
|
print(config_dict, '\n')
|
||||||
|
|
||||||
|
|
||||||
|
if config_dict is 1:
|
||||||
|
print('config {} does not exists'.format(config))
|
||||||
|
|
||||||
|
elif config_dict is 2:
|
||||||
|
print('cannot read config {}, permission error'.format(config))
|
||||||
|
|
||||||
|
else:
|
||||||
|
print('config: {}\n'.format(config))
|
||||||
|
|
||||||
|
if 'mlockall' in config_dict:
|
||||||
|
mlockall = config_dict['mlockall']
|
||||||
|
if mlockall == 'yes':
|
||||||
|
mlockall = True
|
||||||
|
print('mlockall: {}'.format(mlockall))
|
||||||
|
|
||||||
|
if 'self_nice' in config_dict:
|
||||||
|
self_nice = int(config_dict['self_nice'])
|
||||||
|
print('self_nice: {}'.format(self_nice))
|
||||||
|
|
||||||
|
if 'self_oom_score_adj' in config_dict:
|
||||||
|
self_oom_score_adj = int(config_dict['self_oom_score_adj'])
|
||||||
|
print('self_oom_score_adj: {}'.format(self_oom_score_adj))
|
||||||
|
|
||||||
|
if 'rate_mem' in config_dict:
|
||||||
|
rate_mem = float(config_dict['rate_mem'])
|
||||||
|
print('rate_mem: {}'.format(rate_mem))
|
||||||
|
|
||||||
|
if 'rate_swap' in config_dict:
|
||||||
|
rate_swap = float(config_dict['rate_swap'])
|
||||||
|
print('rate_swap: {}'.format(rate_swap))
|
||||||
|
|
||||||
|
if 'rate_zram' in config_dict:
|
||||||
|
rate_zram = float(config_dict['rate_zram'])
|
||||||
|
print('rate_zram: {}'.format(rate_zram))
|
||||||
|
|
||||||
|
if 'mem_min_sigterm' in config_dict:
|
||||||
|
mem_min_sigterm = config_dict['mem_min_sigterm']
|
||||||
|
print('mem_min_sigterm: {}'.format(mem_min_sigterm))
|
||||||
|
|
||||||
|
if 'mem_min_sigkill' in config_dict:
|
||||||
|
mem_min_sigkill = config_dict['mem_min_sigkill']
|
||||||
|
print('mem_min_sigkill: {}'.format(mem_min_sigkill))
|
||||||
|
|
||||||
|
if 'swap_min_sigterm' in config_dict:
|
||||||
|
swap_min_sigterm = config_dict['swap_min_sigterm']
|
||||||
|
print('swap_min_sigterm: {}'.format(swap_min_sigterm))
|
||||||
|
|
||||||
|
if 'swap_min_sigkill' in config_dict:
|
||||||
|
swap_min_sigkill = config_dict['swap_min_sigkill']
|
||||||
|
print('swap_min_sigkill: {}'.format(swap_min_sigkill))
|
||||||
|
|
||||||
|
if 'check_zram' in config_dict:
|
||||||
|
check_zram = config_dict['check_zram']
|
||||||
|
if check_zram == 'yes':
|
||||||
|
check_zram = True
|
||||||
|
print('check_zram: {}'.format(check_zram))
|
||||||
|
|
||||||
|
if 'zram_max_sigterm' in config_dict:
|
||||||
|
zram_max_sigterm = config_dict['zram_max_sigterm']
|
||||||
|
print('zram_max_sigterm: {}'.format(zram_max_sigterm))
|
||||||
|
|
||||||
|
if 'zram_max_sigkill' in config_dict:
|
||||||
|
zram_max_sigkill = config_dict['zram_max_sigkill']
|
||||||
|
print('zram_max_sigkill: {}'.format(zram_max_sigkill))
|
||||||
|
|
||||||
|
if 'min_delay_after_sigterm' in config_dict:
|
||||||
|
min_delay_after_sigterm = float(config_dict['min_delay_after_sigterm'])
|
||||||
|
print('min_delay_after_sigterm: {}'.format(min_delay_after_sigterm))
|
||||||
|
|
||||||
|
|
||||||
|
if 'min_delay_after_sigkill' in config_dict:
|
||||||
|
min_delay_after_sigkill = float(config_dict['min_delay_after_sigkill'])
|
||||||
|
print('min_delay_after_sigkill: {}'.format(min_delay_after_sigkill))
|
||||||
|
|
||||||
|
if 'oom_score_min' in config_dict:
|
||||||
|
oom_score_min = int(config_dict['oom_score_min'])
|
||||||
|
print('oom_score_min: {}'.format(oom_score_min))
|
||||||
|
|
||||||
|
if 'decrease_oom_score_adj_enable' in config_dict:
|
||||||
|
decrease_oom_score_adj_enable = config_dict['decrease_oom_score_adj_enable']
|
||||||
|
if decrease_oom_score_adj_enable == 'yes':
|
||||||
|
decrease_oom_score_adj_enable = True
|
||||||
|
print('decrease_oom_score_adj_enable: {}'.format(decrease_oom_score_adj_enable))
|
||||||
|
|
||||||
|
if 'oom_score_adj_before' in config_dict:
|
||||||
|
oom_score_adj_before = int(config_dict['oom_score_adj_before'])
|
||||||
|
print('oom_score_adj_before: {}'.format(oom_score_adj_before))
|
||||||
|
|
||||||
|
if 'oom_score_adj_after' in config_dict:
|
||||||
|
oom_score_adj_after = config_dict['oom_score_adj_after']
|
||||||
|
print('oom_score_adj_after: {}'.format(oom_score_adj_after))
|
||||||
|
|
||||||
|
if 'use_lists' in config_dict:
|
||||||
|
use_lists = config_dict['use_lists']
|
||||||
|
if use_lists == 'yes':
|
||||||
|
use_lists = True
|
||||||
|
print('use_lists: {}'.format(use_lists))
|
||||||
|
|
||||||
|
if 'white_list' in config_dict:
|
||||||
|
white_list = config_dict['white_list'].split(',')
|
||||||
|
for i in range(len(white_list)):
|
||||||
|
white_list[i] = white_list[i].strip()
|
||||||
|
print('white_list: {}'.format(white_list))
|
||||||
|
|
||||||
|
if 'avoid_list' in config_dict:
|
||||||
|
avoid_list = config_dict['avoid_list'].split(',')
|
||||||
|
for i in range(len(avoid_list)):
|
||||||
|
avoid_list[i] = avoid_list[i].strip()
|
||||||
|
print('avoid_list: {}'.format(avoid_list))
|
||||||
|
|
||||||
|
if 'avoid_ratio' in config_dict:
|
||||||
|
avoid_ratio = float(config_dict['avoid_ratio'])
|
||||||
|
print('avoid_ratio: {}'.format(avoid_ratio))
|
||||||
|
|
||||||
|
if 'black_list' in config_dict:
|
||||||
|
black_list = config_dict['black_list'].split(',')
|
||||||
|
for i in range(len(black_list)):
|
||||||
|
black_list[i] = black_list[i].strip()
|
||||||
|
print('black_list: {}'.format(black_list))
|
||||||
|
|
||||||
|
if 'prefer_list' in config_dict:
|
||||||
|
prefer_list = config_dict['prefer_list'].split(',')
|
||||||
|
for i in range(len(prefer_list)):
|
||||||
|
prefer_list[i] = prefer_list[i].strip()
|
||||||
|
print('prefer_list: {}'.format(prefer_list))
|
||||||
|
|
||||||
|
if 'prefer_ratio' in config_dict:
|
||||||
|
prefer_ratio = float(config_dict['prefer_ratio'])
|
||||||
|
print('prefer_ratio: {}\n'.format(prefer_ratio))
|
||||||
|
|
||||||
|
|
||||||
|
mem_min_sigterm_kb = sig_level_to_kb(mem_min_sigterm)
|
||||||
|
mem_min_sigkill_kb = sig_level_to_kb(mem_min_sigkill)
|
||||||
|
swap_min_sigterm_kb = sig_level_to_kb(swap_min_sigterm)
|
||||||
|
swap_min_sigkill_kb = sig_level_to_kb(swap_min_sigkill)
|
||||||
|
zram_max_sigterm_kb = sig_level_to_kb(zram_max_sigterm)
|
||||||
|
zram_max_sigkill_kb = sig_level_to_kb(zram_max_sigkill)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
print("\ncurrent process's effective user id", os.geteuid())
|
||||||
|
|
||||||
|
if os.geteuid() == 0:
|
||||||
|
root = True
|
||||||
|
else:
|
||||||
|
root = False
|
||||||
|
|
||||||
|
print()
|
||||||
|
|
||||||
|
# lock all memory for prevent swapping
|
||||||
|
if mlockall:
|
||||||
|
print('mlockall = yes')
|
||||||
|
print('try to lock memory...')
|
||||||
|
result = CDLL('libc.so.6', use_errno=True).mlockall(3)
|
||||||
|
if result is 0:
|
||||||
|
print('memory locked!', 'result', result)
|
||||||
|
else:
|
||||||
|
print('cannot lock memory!', 'result', result)
|
||||||
|
else:
|
||||||
|
print('mlockall != yes')
|
||||||
|
|
||||||
|
|
||||||
|
# повышаем приоритет
|
||||||
|
try:
|
||||||
|
os.nice(self_nice)
|
||||||
|
print('self_nice = {}'.format(self_nice))
|
||||||
|
except PermissionError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# запрещаем самоубийство по возможности
|
||||||
|
try:
|
||||||
|
with open('/proc/self/oom_score_adj', 'w') as file:
|
||||||
|
file.write('{}\n'.format(self_oom_score_adj))
|
||||||
|
print('self_oom_score_adj = {}'.format(self_oom_score_adj))
|
||||||
|
except PermissionError:
|
||||||
|
pass
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###########################################################################################
|
||||||
|
|
||||||
|
print()
|
||||||
|
|
||||||
|
# рабочий цикл
|
||||||
|
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 == 2:
|
||||||
|
mem_available = int(line.split(':')[1].split(' ')[-2])
|
||||||
|
continue
|
||||||
|
if n == swap_total_index:
|
||||||
|
swap_total = int(line.split(':')[1].split(' ')[-2])
|
||||||
|
continue
|
||||||
|
if n == swap_free_index:
|
||||||
|
swap_free = int(line.split(':')[1].split(' ')[-2])
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
# тут находим фулл зрам
|
||||||
|
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])
|
||||||
|
|
||||||
|
full_zram = (
|
||||||
|
mem_used_total_sum + disksize_sum * zram_disksize_factor
|
||||||
|
) / 1024.0
|
||||||
|
|
||||||
|
|
||||||
|
# если не печатать периоды, то можно это вынести в конец
|
||||||
|
t_mem = mem_available / 1024.0 / 1024.0 / rate_mem
|
||||||
|
|
||||||
|
t_swap = swap_free / 1024.0 / 1024.0 / rate_swap
|
||||||
|
|
||||||
|
# fullzram может превысить 09, будет отриц значение
|
||||||
|
# memtotal * 0.9 - это фактически макс память для зрам
|
||||||
|
t_zram = (mem_total * 0.8 - full_zram) / 1024.0 / 1024.0 / rate_zram
|
||||||
|
if t_zram <= 0:
|
||||||
|
t_zram = 0.01
|
||||||
|
|
||||||
|
t1 = t_mem + t_swap
|
||||||
|
t2 = t_mem + t_zram
|
||||||
|
|
||||||
|
# используем наименьший
|
||||||
|
if t1 <= t2:
|
||||||
|
t = t1
|
||||||
|
else:
|
||||||
|
t = t2
|
||||||
|
|
||||||
|
|
||||||
|
print(
|
||||||
|
'{} Mem: {} M, Swap: {} M, Zram: {} M'.format(
|
||||||
|
strftime("%Y-%b-%d %H:%M:%S", gmtime()),
|
||||||
|
human(mem_available),
|
||||||
|
human(swap_free),
|
||||||
|
human(full_zram)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if mem_available <= mem_min_sigkill_kb and swap_free <= swap_min_sigkill_kb:
|
||||||
|
print('mem_available < mem_min_sigkill and swap_free < swap_min_sigkill')
|
||||||
|
find_victim(SIGKILL)
|
||||||
|
sleep(min_delay_after_sigkill)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if full_zram >= zram_max_sigkill_kb:
|
||||||
|
print('full_zram > zram_max_sigkill')
|
||||||
|
find_victim(SIGKILL)
|
||||||
|
sleep(min_delay_after_sigkill)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if mem_available <= mem_min_sigterm_kb and swap_free <= swap_min_sigterm_kb:
|
||||||
|
print('mem_available < mem_min_sigterm and swap_free < swap_min_sigterm')
|
||||||
|
find_victim(SIGTERM)
|
||||||
|
sleep(min_delay_after_sigterm)
|
||||||
|
|
||||||
|
if full_zram >= zram_max_sigterm_kb:
|
||||||
|
print('zram_part > zram_max_sigterm')
|
||||||
|
find_victim(SIGTERM)
|
||||||
|
sleep(min_delay_after_sigterm)
|
||||||
|
# вариант - перенести задержку в фц поиска жертв
|
||||||
|
|
||||||
|
sleep(t)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
12
nohang.1
Normal file
12
nohang.1
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
.TH nohang 1
|
||||||
|
.SH NAME
|
||||||
|
nohang \- no hang daemon
|
||||||
|
|
||||||
|
|
||||||
|
.SH SYNOPSIS
|
||||||
|
.B nohang
|
||||||
|
.RB [ OPTION ]...
|
||||||
|
|
||||||
|
|
||||||
|
.SH DESCRIPTION
|
||||||
|
See https://github.com/hakvlad/nohang
|
130
nohang.conf
Normal file
130
nohang.conf
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
|
||||||
|
Nohang config file
|
||||||
|
|
||||||
|
Комментариями являются строки, начинающиеся
|
||||||
|
с решёток, пробелов и табуляций.
|
||||||
|
|
||||||
|
В конце конфига перечислены значения по умолчанию.
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
|
||||||
|
Заблокировать процесс в памяти для запрета своппинга процесса.
|
||||||
|
yes для блокировки процесса в памяти,
|
||||||
|
no или любое другое значение - не блокировать
|
||||||
|
|
||||||
|
mlockall = yes
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
|
||||||
|
Повысить приоритет процесса, установив niceness -20
|
||||||
|
Допустимые значения - целые числа из диапазона [-20; 19]
|
||||||
|
|
||||||
|
self_nice = -20
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
|
||||||
|
Задать oom_score_adj для процесса.
|
||||||
|
Задание значения -1000 запретит самоубийство.
|
||||||
|
Допустимые значения - целые числа из диапазона [-1000; 1000]
|
||||||
|
|
||||||
|
self_oom_score_adj = -1000
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
|
||||||
|
Коэффициенты, влияющие на интенсивность мониторинга.
|
||||||
|
Допустимыми значениями являются положительные числа.
|
||||||
|
Уменьшение коэффициентов способно снизить нагрузку на
|
||||||
|
прцессор и увеличить периоды между проверками памяти.
|
||||||
|
|
||||||
|
rate_mem = 6
|
||||||
|
rate_swap = 2
|
||||||
|
rate_zram = 1
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
|
||||||
|
Задание уровней доступной памяти, ниже которых происходит
|
||||||
|
отправка сигналов SIGTERM или SIGKILL.
|
||||||
|
Сигнал отправляется если MemAvailable и SwapFree одновременно
|
||||||
|
опустятся ниже соответствующих значений.
|
||||||
|
Значения могут быть выражены в процентах (%),
|
||||||
|
кибибайтах (KiB), мебибайтах (MiB) или гибибайтах (GiB).
|
||||||
|
|
||||||
|
mem_min_sigterm = 6 %
|
||||||
|
mem_min_sigkill = 3 %
|
||||||
|
swap_min_sigterm = 8 %
|
||||||
|
swap_min_sigkill = 4 %
|
||||||
|
|
||||||
|
Задание общей доли zram в памяти, при превышении которой
|
||||||
|
происходит отправка соответствующих сигналов.
|
||||||
|
Экспериментально удалось добиться доли зрам в 95%, при которой
|
||||||
|
система виснет или запускается OOM killer.
|
||||||
|
По мере увеличения доли zram в памяти может падать
|
||||||
|
отзывчивость системы.
|
||||||
|
Может также задаваться в %, KiB, MiB, GiB
|
||||||
|
|
||||||
|
zram_max_sigterm = 60 %
|
||||||
|
zram_max_sigkill = 65 %
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
|
||||||
|
Минимальное значение oom_score, которым должен обладать
|
||||||
|
процесс для того, чтобы ему был отправлен сигнал.
|
||||||
|
Позволяет предотвратить убийство невиновных если что-то
|
||||||
|
пойдет не так.
|
||||||
|
Значение должно быть целым числом из диапазона [0; 1000]
|
||||||
|
|
||||||
|
oom_score_min = 10
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
|
||||||
|
Мнинмальная задержка после отправки соответствующих сигналов
|
||||||
|
для предотвращения риска убийства сразу множества процессов.
|
||||||
|
Должно быть неотрицательным числом.
|
||||||
|
|
||||||
|
min_delay_after_sigterm = 0.1
|
||||||
|
min_delay_after_sigkill = 3
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
|
||||||
|
Процессы браузера chromium обычно имеют oom_score_adj
|
||||||
|
200 или 300. Это приводит к тому, что процессы хрома умирают
|
||||||
|
первыми вместо действительно тяжелых процессов.
|
||||||
|
|
||||||
|
Если параметр decrease_oom_score_adj_enable установлен
|
||||||
|
в значение yes, то у процессов, имеющих oom_score_adj выше
|
||||||
|
oom_score_adj_before значение oom_score_adj будет опущено
|
||||||
|
до oom_score_adj_after перед поиском жертвы.
|
||||||
|
|
||||||
|
decrease_oom_score_adj_enable = no
|
||||||
|
|
||||||
|
oom_score_adj_before = 50
|
||||||
|
oom_score_adj_after = 10
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
|
||||||
|
Значения по умолчанию
|
||||||
|
|
||||||
|
mlockall = yes
|
||||||
|
self_nice = -20
|
||||||
|
self_oom_score_adj = -1000
|
||||||
|
|
||||||
|
rate_mem = 6
|
||||||
|
rate_swap = 2
|
||||||
|
rate_zram = 1
|
||||||
|
|
||||||
|
mem_min_sigterm = 6 %
|
||||||
|
mem_min_sigkill = 3 %
|
||||||
|
swap_min_sigterm = 8 %
|
||||||
|
swap_min_sigkill = 4 %
|
||||||
|
zram_max_sigterm = 60 %
|
||||||
|
zram_max_sigkill = 65 %
|
||||||
|
|
||||||
|
oom_score_min = 10
|
||||||
|
|
||||||
|
min_delay_after_sigterm = 0.1
|
||||||
|
min_delay_after_sigkill = 3
|
||||||
|
|
||||||
|
decrease_oom_score_adj_enable = yes
|
||||||
|
oom_score_adj_before = 50
|
||||||
|
oom_score_adj_after = 10
|
||||||
|
|
14
nohang.service
Normal file
14
nohang.service
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
[Unit]
|
||||||
|
Description=No hang daemon
|
||||||
|
After=sysinit.target
|
||||||
|
Documentation=man:nohang(1) https://github.com/hakvlad/nohang
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
PIDFile=/run/nohang.pid
|
||||||
|
Restart=always
|
||||||
|
ExecStart=/usr/local/bin/nohang
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
7
purge.sh
Executable file
7
purge.sh
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/bash -v
|
||||||
|
systemctl stop nohang
|
||||||
|
systemctl disable nohang
|
||||||
|
rm /usr/local/share/man/man1/nohang.1.gz
|
||||||
|
rm /etc/systemd/system/nohang.service
|
||||||
|
rm -r /etc/nohang
|
||||||
|
rm /usr/local/bin/nohang
|
6
uninstall.sh
Executable file
6
uninstall.sh
Executable file
@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/bash -v
|
||||||
|
systemctl stop nohang
|
||||||
|
systemctl disable nohang
|
||||||
|
rm /usr/local/share/man/man1/nohang.1.gz
|
||||||
|
rm /etc/systemd/system/nohang.service
|
||||||
|
rm /usr/local/bin/nohang
|
Loading…
Reference in New Issue
Block a user