исправлен алгоритм, улучшен вывод, добавлены опции командной строки, дополнен конфиг, дополнен ман

This commit is contained in:
Alexey Avramov 2018-06-09 22:13:01 +09:00
parent dbcbadb4c3
commit e2e314474c
7 changed files with 219 additions and 134 deletions

View File

@ -2,7 +2,7 @@
The No Hang Daemon The No Hang Daemon
================== ==================
`Nohang` - аналог [earlyoom](https://github.com/rfjakob/earlyoom) с поддержкой `zram` и `SIGTERM`. При дефиците доступной памяти `hohang` корректно завершает наиболее прожорливые процессы сигналом `SIGTERM`, тем самым препятствуя зависанию, а также избыточному убийству процессов ядерным `OOM killer`'ом. `Nohang` - аналог [earlyoom](https://github.com/rfjakob/earlyoom) с поддержкой `zram` и `SIGTERM`. При дефиците доступной памяти `nohang` корректно завершает наиболее прожорливые процессы сигналом `SIGTERM`, тем самым препятствуя зависанию, а также избыточному убийству процессов ядерным `OOM killer`'ом.
### Зачем это нужно? ### Зачем это нужно?
@ -25,25 +25,25 @@ https://www.linux.org.ru/forum/talks/12684213?lastmod=1466676523241#comment-1268
"И IRL ты никогда не знаешь, в какой момент момент твои данные перестанут умещаться в оперативку. Потому zram -- удел embedded систем, где это может быть детерминировано." "И IRL ты никогда не знаешь, в какой момент момент твои данные перестанут умещаться в оперативку. Потому zram -- удел embedded систем, где это может быть детерминировано."
https://2ch.hk/s/res/2310304.html#2311483, https://archive.li/idixk https://2ch.hk/s/res/2310304.html#2311483, https://archive.li/idixk
Nohang позволяет избавиться от перечисленных выше проблем, корректно завершая наиболее прожорливые процессы сигналом `SIGTERM` не дожидаясь когда система "встанет колом". `Nohang` позволяет не бояться зависаний при использовании `zram`. `Nohang` позволяет избавиться от перечисленных выше проблем, корректно завершая наиболее прожорливые процессы сигналом `SIGTERM` не дожидаясь когда система "встанет колом". `Nohang` позволяет не бояться зависаний при использовании `zram`.
### Зачем нужен nohang, если уже есть earlyoom? ### Зачем нужен nohang, если уже есть earlyoom?
- `earlyoom` завершает (точнее убивает) процессы исключительно с помощью сигнала `SIGKILL`, в то время как `nohang` дает возможность сначала отправлять `SIGTERM`, и только если процесс не реагирует на `SIGTERM` - отправляется сигнал `SIGKILL`. - `earlyoom` завершает (точнее убивает) процессы исключительно с помощью сигнала `SIGKILL`, в то время как `nohang` дает возможность сначала отправлять `SIGTERM`, и только если процесс не реагирует на `SIGTERM` - отправляется сигнал `SIGKILL`.
- `earlyoom` не поддерживает работу со `zram` и не реагирует на общую долю `zram` в памяти (`mem_used_total`). Это может привести к тому, что система все также встанет колом, как если бы `earlyoom` и не было (если `disksize` большой, а энтропия сжимаемых данных велика). `Nohang` позволяет избавиться от этой проблемы. По умолчанию если доля zram достигнет 60% памяти - будет отправлен сигнал `SIGTERM` процессу с наибольшим `oom_score`. - `earlyoom` не поддерживает работу со `zram` и не реагирует на общую долю `zram` в памяти (`mem_used_total`). Это может привести к тому, что система все также встанет колом, как если бы `earlyoom` и не было (если `disksize` большой, а энтропия сжимаемых данных велика). `Nohang` позволяет избавиться от этой проблемы. По умолчанию если доля `zram` достигнет 60% памяти - будет отправлен сигнал `SIGTERM` процессу с наибольшим `oom_score`.
### Особенности ### Особенности
- задача - препятствовать зависанию системы при нехватке доступной памяти, а также корректное завершение процессов с целью увеличения объема доступной памяти - задача - препятствовать зависанию системы при нехватке доступной памяти, а также корректное завершение процессов с целью увеличения объема доступной памяти
- демон на python3, RSS около 12 MiB - демон на Python 3, VmRSS около 12 MiB
- требуется Linux 3.14+ и Python 3.4+ - требуется `Linux 3.14+` и `Python 3.4+`
- периодически проверяет размеры доступной памяти, при дефиците памяти отправляет `SIGKILL` или `SIGTERM` процессу с наибольшим `oom_score` - периодически проверяет размеры доступной памяти, при дефиците памяти отправляет `SIGKILL` или `SIGTERM` процессу с наибольшим `oom_score`
- поддержка работы со `zram`, возможность реакции на `mem_used_total` - поддержка работы со `zram`, возможность реакции на `mem_used_total`
- удобный конфиг с возможностью тонкой настройки - удобный конфиг с возможностью тонкой настройки
- возможность раздельного задания уровней `MemAvailable`, `SwapFree`, `mem_used_total` для отпраки `SIGTERM` и `SIGKILL`, возможность задания в %, KiB, MiB, GiB - возможность раздельного задания уровней `MemAvailable`, `SwapFree`, `mem_used_total` для отпраки `SIGTERM` и `SIGKILL`, возможность задания в процентах (%), кибибайтах (K), мебибайтах (M), гибибайтах (G)
- возможность снижения `oom_score_adj` процессов, чьи `oom_score_adj` завышены (актуально для chromium) - возможность снижения `oom_score_adj` процессов, чьи `oom_score_adj` завышены (актуально для `chromium`)
- лучший алгоритм выбора периодов между проверками доступной памяти: при больших объемах доступной памяти нет смысла проверять ее состояние часто, поэтому период проверки уменьшается по мере уменьшения размера доступной памяти - лучший алгоритм выбора периодов между проверками доступной памяти: при больших объемах доступной памяти нет смысла проверять ее состояние часто, поэтому период проверки уменьшается по мере уменьшения размера доступной памяти
- интенсивность мониторинга можно гибко настраивать (параметры конфига `rate_mem`, `rate_swap`, `rate_zram`) - интенсивность мониторинга можно гибко настраивать (параметры конфига `rate_mem`, `rate_swap`, `rate_zram`)
- память заблокирована с помощью `mlockall()` для предотвращения своппинга процесса - по умолчанию память заблокирована с помощью `mlockall()` для предотвращения своппинга процесса
- по умолчанию высокий приоритет процесса `nice -20`, может регулироваться через конфиг - по умолчанию высокий приоритет процесса `nice -20`, может регулироваться через конфиг
- предотвращение самоубийства с помощью `self_oom_score_adj = -1000` - предотвращение самоубийства с помощью `self_oom_score_adj = -1000`
- возможность задания `oom_score_min` для предотвращения убийства невиновных - возможность задания `oom_score_min` для предотвращения убийства невиновных
@ -51,12 +51,20 @@ Nohang позволяет избавиться от перечисленных
- наличие `man` страницы - наличие `man` страницы
- наличие установщика для пользователей `systemd` - наличие установщика для пользователей `systemd`
- протестировано на `Debian 9 x86_64`, `Debian 8 i386`, `Fedora 28 x86_64` - протестировано на `Debian 9 x86_64`, `Debian 8 i386`, `Fedora 28 x86_64`
- вывод отчета об убийствах такого вида - пример вывода с отчетом об успешной отпраке сигнала:
``` ```
2018-Jun-07 04:55:16 Mem: 0 M, Swap: 454 M, Zram: 488 M MemAvail: 0M 0.0%, SwapFree: 985M 8.4%, MemUsedZram: 625M 10.6%
mem_available < mem_term_level and swap_free < swap_term_level MemAvail: 0M 0.0%, SwapFree: 962M 8.2%, MemUsedZram: 626M 10.6%
Try to send signal 15 to process python3, Pid 7281, oom_score 893 MemAvail: 9M 0.1%, SwapFree: 939M 8.0%, MemUsedZram: 626M 10.7%
MemAvailable 9M < 353M and SwapFree 939M < 940M
Try to send signal 15 to process python3, Pid 3392, oom_score 818
Success Success
MemAvail: 29M 0.5%, SwapFree: 2866M 24.4%, MemUsedZram: 582M 9.9%
MemAvail: 77M 1.3%, SwapFree: 5037M 42.9%, MemUsedZram: 532M 9.1%
MemAvail: 168M 2.9%, SwapFree: 8956M 76.2%, MemUsedZram: 441M 7.5%
MemAvail: 5006M 85.2%, SwapFree: 10632M 90.5%, MemUsedZram: 356M 6.1%
MemAvail: 5000M 85.1%, SwapFree: 10633M 90.5%, MemUsedZram: 356M 6.1%
``` ```
### Установка и удаление для пользователей systemd ### Установка и удаление для пользователей systemd
@ -78,7 +86,7 @@ sudo ./uninstall.sh
``` ```
### Настройка ### Настройка
Nohang настраивается с помощью [конфига](https://github.com/hakavlad/nohang/blob/master/nohang.conf), расположенного после установки `Nohang` настраивается с помощью [конфига](https://github.com/hakavlad/nohang/blob/master/nohang.conf), расположенного после установки
по адресу по адресу
``` ```
/etc/nohang/nohang.conf /etc/nohang/nohang.conf
@ -86,6 +94,5 @@ Nohang настраивается с помощью [конфига](https://git
К опциям прилагается описание. Отредактируйте значения параметров в соответствии с вашими предпочтениями. К опциям прилагается описание. Отредактируйте значения параметров в соответствии с вашими предпочтениями.
### Известные баги ### Известные баги
На самом деле алгоритм кривой, и киллер может вовремя не запуститься. Дождитесь исправлений. Текущая версия программы - пререлизная, ждите выпуска стабильной 0.1 версии. Известных нет, если найдете - пишите в [Issues](https://github.com/hakavlad/nohang/issues).

View File

@ -1,20 +1,20 @@
#!/bin/bash -v #!/bin/bash -v
cp nohang /usr/local/bin/ cp -f nohang /usr/local/bin/
chmod 755 /usr/local/bin/nohang chmod 755 /usr/local/bin/nohang
mkdir /etc/nohang mkdir /etc/nohang
chmod 755 /etc/nohang chmod 755 /etc/nohang
cp nohang.conf /etc/nohang/ cp -f nohang.conf /etc/nohang/
chmod 644 /etc/nohang/nohang.conf chmod 644 /etc/nohang/nohang.conf
gzip -k nohang.1 gzip -k nohang.1
mkdir /usr/local/share/man/man1 mkdir /usr/local/share/man/man1
chmod 755 /usr/local/share/man/man1 chmod 755 /usr/local/share/man/man1
cp nohang.1.gz /usr/local/share/man/man1/ cp -f nohang.1.gz /usr/local/share/man/man1/
chmod 644 /usr/local/share/man/man1/nohang.1.gz chmod 644 /usr/local/share/man/man1/nohang.1.gz
cp nohang.service /etc/systemd/system/ cp -f nohang.service /etc/systemd/system/
chmod 644 /etc/systemd/system/nohang.service chmod 644 /etc/systemd/system/nohang.service
systemctl daemon-reload systemctl daemon-reload
systemctl enable nohang systemctl enable nohang

251
nohang
View File

@ -1,29 +1,50 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# nohang - no hang daemon # Nohang - No Hang Daemon
import os import os
from ctypes import CDLL from ctypes import CDLL
from operator import itemgetter from operator import itemgetter
from signal import SIGKILL, SIGTERM from signal import SIGKILL, SIGTERM
from time import gmtime, strftime, sleep, time from time import gmtime, strftime, sleep, time
import argparse
if os.path.exists('./nohang.conf'):
parser = argparse.ArgumentParser()
parser.add_argument(
'-c',
'--config',
help='path to config file, default values: ./nohang.conf, /etc/nohang/nohang.conf',
default=None,
type=str
)
arg_config = parser.parse_args().config
# ветвление требует переработки и лучшей обработки исключений
if arg_config != None:
if os.path.exists:
config = arg_config
else:
print('Указанный конфиг файл {} не существует!'.format(arg_config))
exit()
elif os.path.exists('./nohang.conf'):
config = './nohang.conf' config = './nohang.conf'
print('config: {}'.format(config))
elif os.path.exists('/etc/nohang/nohang.conf'): elif os.path.exists('/etc/nohang/nohang.conf'):
config = '/etc/nohang/nohang.conf' config = '/etc/nohang/nohang.conf'
print('config: {}'.format(config))
else: else:
print('укажите путь к конфигу опцией --config') print('Пожалуйста, укажите путь к конфигу опцией -c или --config')
exit() exit()
# найден экспериментально, требует уточнения с разными ядрами и архитектурами
zram_disksize_factor = 0.0042 zram_disksize_factor = 0.0042
########################################################################################### ###########################################################################################
def decrease_oom_score_adj(oom_score_adj_before, oom_score_adj_after): def decrease_oom_score_adj(oom_score_adj_before, oom_score_adj_after):
print('decrease oom_score_adj...') #print('Decrease oom_score_adj...')
# цикл для наполнения oom_list # цикл для наполнения oom_list
for i in os.listdir('/proc'): for i in os.listdir('/proc'):
@ -40,6 +61,7 @@ def decrease_oom_score_adj(oom_score_adj_before, oom_score_adj_after):
except ProcessLookupError: except ProcessLookupError:
pass pass
# чтение первой строки файла # чтение первой строки файла
def rline1(path): def rline1(path):
with open(path) as f: with open(path) as f:
@ -53,6 +75,7 @@ def write(path, string):
f.write(string) f.write(string)
# возвращает словарь с параметрами из конфига
def config_parser(config): def config_parser(config):
if os.path.exists(config): if os.path.exists(config):
try: try:
@ -73,24 +96,10 @@ def config_parser(config):
return 1 return 1
def sig_level_to_kb(string): def to_percent(num):
if string.endswith('%'): return str(round(num * 100, 1)).rjust(5, ' ')
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, выравнивание по правому краю # K -> M, выравнивание по правому краю
def human(num): def human(num):
return str(round(num / 1024)).rjust(5, ' ') return str(round(num / 1024)).rjust(5, ' ')
@ -148,7 +157,7 @@ def find_victim(signal):
if decrease_oom_score_adj_enable and root: if decrease_oom_score_adj_enable and root:
decrease_oom_score_adj(oom_score_adj_before, oom_score_adj_after) decrease_oom_score_adj(oom_score_adj_before, oom_score_adj_after)
print('find victim...') #print('Find victim...')
oom_list = [] oom_list = []
@ -184,15 +193,15 @@ def find_victim(signal):
try: try:
os.kill(int(pid), signal) os.kill(int(pid), signal)
print('Success\n') print('Success')
except ProcessLookupError: except ProcessLookupError:
print('No such process\n') print('No such process')
except PermissionError: except PermissionError:
print('Operation not permitted\n') print('Operation not permitted')
else: else:
print('\noom_score {} < oom_score_min {}\n'.format(oom_score, oom_score_min)) print('oom_score {} < oom_score_min {}'.format(oom_score, oom_score_min))
########################################################################################### ###########################################################################################
@ -202,7 +211,6 @@ def find_victim(signal):
# ищем позиции # ищем позиции
with open('/proc/meminfo') as file: with open('/proc/meminfo') as file:
mem_list = file.readlines() mem_list = file.readlines()
@ -219,13 +227,11 @@ swap_free_index = swap_total_index + 1
mem_total = int(mem_list[0].split(':')[1].split(' ')[-2]) mem_total = int(mem_list[0].split(':')[1].split(' ')[-2])
###############################################################################################
config_dict = config_parser(config) config_dict = config_parser(config)
print(config_dict, '\n')
if config_dict is 1: if config_dict is 1:
print('config {} does not exists'.format(config)) print('config {} does not exists'.format(config))
@ -277,12 +283,6 @@ else:
swap_min_sigkill = config_dict['swap_min_sigkill'] swap_min_sigkill = config_dict['swap_min_sigkill']
print('swap_min_sigkill: {}'.format(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: if 'zram_max_sigterm' in config_dict:
zram_max_sigterm = config_dict['zram_max_sigterm'] zram_max_sigterm = config_dict['zram_max_sigterm']
print('zram_max_sigterm: {}'.format(zram_max_sigterm)) print('zram_max_sigterm: {}'.format(zram_max_sigterm))
@ -295,7 +295,6 @@ else:
min_delay_after_sigterm = float(config_dict['min_delay_after_sigterm']) min_delay_after_sigterm = float(config_dict['min_delay_after_sigterm'])
print('min_delay_after_sigterm: {}'.format(min_delay_after_sigterm)) print('min_delay_after_sigterm: {}'.format(min_delay_after_sigterm))
if 'min_delay_after_sigkill' in config_dict: if 'min_delay_after_sigkill' in config_dict:
min_delay_after_sigkill = float(config_dict['min_delay_after_sigkill']) min_delay_after_sigkill = float(config_dict['min_delay_after_sigkill'])
print('min_delay_after_sigkill: {}'.format(min_delay_after_sigkill)) print('min_delay_after_sigkill: {}'.format(min_delay_after_sigkill))
@ -318,54 +317,70 @@ else:
oom_score_adj_after = config_dict['oom_score_adj_after'] oom_score_adj_after = config_dict['oom_score_adj_after']
print('oom_score_adj_after: {}'.format(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))
def sig_level_to_kb(string):
if string.endswith('%'):
return float(string[:-1].strip()) / 100 * mem_total
if string.endswith('K'):
return float(string[:-1].strip())
if string.endswith('M'):
return float(string[:-1].strip()) * 1024
if string.endswith('G'):
return float(string[:-1].strip()) * 1048576
mem_min_sigterm_kb = sig_level_to_kb(mem_min_sigterm) mem_min_sigterm_kb = sig_level_to_kb(mem_min_sigterm)
mem_min_sigkill_kb = sig_level_to_kb(mem_min_sigkill) 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_sigterm_kb = sig_level_to_kb(zram_max_sigterm)
zram_max_sigkill_kb = sig_level_to_kb(zram_max_sigkill) 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
if string.endswith('K'):
return float(string[:-1].strip())
if string.endswith('M'):
return float(string[:-1].strip()) * 1024
if string.endswith('G'):
return float(string[:-1].strip()) * 1048576
# получаем число килобайт или кортеж с процентами
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
# print('\nswap term is percent:', swap_term_is_percent)
# print('swap kill is percent:', swap_kill_is_percent)
print("\ncurrent process's effective user id", os.geteuid()) print("\ncurrent process's effective user id", os.geteuid())
if os.geteuid() == 0: if os.geteuid() == 0:
@ -408,6 +423,8 @@ except OSError:
def kib_to_mib(num):
return round(num / 1024.0)
########################################################################################### ###########################################################################################
@ -430,8 +447,15 @@ while True:
swap_free = int(line.split(':')[1].split(' ')[-2]) swap_free = int(line.split(':')[1].split(' ')[-2])
break break
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 disksize_sum = 0
mem_used_total_sum = 0 mem_used_total_sum = 0
@ -444,67 +468,90 @@ while True:
disksize_sum += int(stat[0]) disksize_sum += int(stat[0])
mem_used_total_sum += int(stat[1]) mem_used_total_sum += int(stat[1])
full_zram = ( mem_used_zram = (
mem_used_total_sum + disksize_sum * zram_disksize_factor mem_used_total_sum + disksize_sum * zram_disksize_factor
) / 1024.0 ) / 1024.0
# если не печатать периоды, то можно это вынести в конец
t_mem = mem_available / 1024.0 / 1024.0 / rate_mem
t_swap = swap_free / 1024.0 / 1024.0 / rate_swap
# fullzram может превысить 09, будет отриц значение t_mem = mem_available / 1000000.0 / rate_mem
# memtotal * 0.9 - это фактически макс память для зрам t_swap = swap_free / 10000000.0 / rate_swap
t_zram = (mem_total * 0.8 - full_zram) / 1024.0 / 1024.0 / rate_zram t_zram = (mem_total * 0.8 - mem_used_zram) / 1000000.0 / rate_zram
if t_zram <= 0: if t_zram < 0.01:
t_zram = 0.01 t_zram = 0.01
t1 = t_mem + t_swap t_mem_swap = t_mem + t_swap
t2 = t_mem + t_zram t_mem_zram = t_mem + t_zram
# используем наименьший if t_mem_swap <= t_mem_zram:
if t1 <= t2: t = t_mem_swap
t = t1
else: else:
t = t2 t = t_mem_zram
# печать состояния памяти
print( print(
'{} Mem: {} M, Swap: {} M, Zram: {} M'.format( 'MemAvail: {}M {}%, SwapFree: {}M {}%, MemUsedZram: {}M {}%'.format(
strftime("%Y-%b-%d %H:%M:%S", gmtime()),
human(mem_available), human(mem_available),
to_percent(mem_available / mem_total),
human(swap_free), human(swap_free),
human(full_zram) to_percent(swap_free / (swap_total + 0.0001)),
human(mem_used_zram),
to_percent(mem_used_zram / mem_total)
) )
) )
# 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('mem_available < mem_min_sigkill and swap_free < swap_min_sigkill') print(
'MemAvailable {}M < {}M and SwapFree {}M < {}M'.format(
kib_to_mib(mem_available),
kib_to_mib(mem_min_sigkill_kb),
kib_to_mib(swap_free),
kib_to_mib(swap_min_sigkill_kb)
)
)
find_victim(SIGKILL) find_victim(SIGKILL)
sleep(min_delay_after_sigkill) sleep(min_delay_after_sigkill)
continue continue
if full_zram >= zram_max_sigkill_kb: # MEM ZRAM KILL
print('full_zram > zram_max_sigkill') if mem_used_zram >= zram_max_sigkill_kb:
print(
'MemUsedZram {}M > {}M'.format(
kib_to_mib(mem_used_zram),
kib_to_mib(zram_max_sigkill_kb)
)
)
find_victim(SIGKILL) find_victim(SIGKILL)
sleep(min_delay_after_sigkill) sleep(min_delay_after_sigkill)
continue continue
# 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('mem_available < mem_min_sigterm and swap_free < swap_min_sigterm') print(
'MemAvailable {}M < {}M and SwapFree {}M < {}M'.format(
kib_to_mib(mem_available),
kib_to_mib(mem_min_sigterm_kb),
kib_to_mib(swap_free),
kib_to_mib(swap_min_sigterm_kb)
)
)
find_victim(SIGTERM) find_victim(SIGTERM)
sleep(min_delay_after_sigterm) sleep(min_delay_after_sigterm)
if full_zram >= zram_max_sigterm_kb: # MEM ZRAM TERM
print('zram_part > zram_max_sigterm') if mem_used_zram >= zram_max_sigterm_kb:
print(
'MemUsedZram {}M > {}M'.format(
kib_to_mib(mem_used_zram),
kib_to_mib(zram_max_sigterm_kb)
)
)
find_victim(SIGTERM) find_victim(SIGTERM)
sleep(min_delay_after_sigterm) sleep(min_delay_after_sigterm)
# вариант - перенести задержку в фц поиска жертв
sleep(t) sleep(t)

View File

@ -2,11 +2,21 @@
.SH NAME .SH NAME
nohang \- no hang daemon nohang \- no hang daemon
.SH SYNOPSIS .SH SYNOPSIS
.B nohang .B nohang
.RB [ OPTION ]... .RB [ OPTION ]...
.SH DESCRIPTION .SH DESCRIPTION
See https://github.com/hakavlad/nohang Nohang - аналог earlyoom с поддержкой zram и SIGTERM. При дефиците доступной памяти nohang корректно завершает наиболее прожорливые процессы сигналом SIGTERM, тем самым препятствуя зависанию, а также избыточному убийству процессов ядерным OOM killer'ом. See https://github.com/hakavlad/nohang
.SH OPTIONS
.TP
.BI \-c " CONFIG",
.BI \-\-config " CONFIG"
path to config file, default values:
.I ./nohang.conf,
.I /etc/nohang/nohang.conf
.TP
.BI \-h,
.BI \-\-help
show help message and exit

View File

@ -36,6 +36,16 @@ self_oom_score_adj = -1000
Уменьшение коэффициентов способно снизить нагрузку на Уменьшение коэффициентов способно снизить нагрузку на
прцессор и увеличить периоды между проверками памяти. прцессор и увеличить периоды между проверками памяти.
Почему три коэффициента, а не один? - Потому что
скорость наполнения свопа обычно ниже скорости наполнения RAM.
Можно для свопа задать более низкую интенсивность
мониторинга без ущерба для предотвращения нехватки памяти
и тем самым снизить нагрузку на процессор.
В дефолтных настройках на данной интенсивности демон работает
очень хорошо, перехватывая резкие скачки потребления памяти.
Можете тестировать
rate_mem = 6 rate_mem = 6
rate_swap = 2 rate_swap = 2
rate_zram = 1 rate_zram = 1
@ -44,10 +54,16 @@ rate_zram = 1
Задание уровней доступной памяти, ниже которых происходит Задание уровней доступной памяти, ниже которых происходит
отправка сигналов SIGTERM или SIGKILL. отправка сигналов SIGTERM или SIGKILL.
Сигнал отправляется если MemAvailable и SwapFree одновременно Сигнал отправляется если MemAvailable и SwapFree одновременно
опустятся ниже соответствующих значений. опустятся ниже соответствующих значений.
Значения могут быть выражены в процентах (%), Значения могут быть выражены в процентах (%),
кибибайтах (KiB), мебибайтах (MiB) или гибибайтах (GiB). кибибайтах (K), мебибайтах (M) или гибибайтах (G).
Например:
swap_min_sigterm = 8 %
mem_min_sigterm = 0.5 G
swap_min_sigkill = 200 M
mem_min_sigterm = 6 % mem_min_sigterm = 6 %
mem_min_sigkill = 3 % mem_min_sigkill = 3 %
@ -60,7 +76,7 @@ swap_min_sigkill = 4 %
система виснет или запускается OOM killer. система виснет или запускается OOM killer.
По мере увеличения доли zram в памяти может падать По мере увеличения доли zram в памяти может падать
отзывчивость системы. отзывчивость системы.
Может также задаваться в %, KiB, MiB, GiB Может также задаваться в %, K, M, G
zram_max_sigterm = 60 % zram_max_sigterm = 60 %
zram_max_sigkill = 65 % zram_max_sigkill = 65 %
@ -100,6 +116,11 @@ decrease_oom_score_adj_enable = no
oom_score_adj_before = 50 oom_score_adj_before = 50
oom_score_adj_after = 10 oom_score_adj_after = 10
#####################################################################
#####################################################################
#####################################################################
#####################################################################
##################################################################### #####################################################################
Значения по умолчанию Значения по умолчанию
@ -124,7 +145,7 @@ oom_score_adj_after = 10
min_delay_after_sigterm = 0.1 min_delay_after_sigterm = 0.1
min_delay_after_sigkill = 3 min_delay_after_sigkill = 3
decrease_oom_score_adj_enable = yes decrease_oom_score_adj_enable = no
oom_score_adj_before = 50 oom_score_adj_before = 50
oom_score_adj_after = 10 oom_score_adj_after = 10

View File

@ -1,7 +1,7 @@
#!/bin/bash -v #!/bin/bash -v
systemctl stop nohang systemctl stop nohang
systemctl disable nohang systemctl disable nohang
rm /usr/local/share/man/man1/nohang.1.gz rm -f /usr/local/share/man/man1/nohang.1.gz
rm /etc/systemd/system/nohang.service rm -f /etc/systemd/system/nohang.service
rm -r /etc/nohang rm -rf /etc/nohang
rm /usr/local/bin/nohang rm -f /usr/local/bin/nohang

View File

@ -1,6 +1,6 @@
#!/bin/bash -v #!/bin/bash -v
systemctl stop nohang systemctl stop nohang
systemctl disable nohang systemctl disable nohang
rm /usr/local/share/man/man1/nohang.1.gz rm -f /usr/local/share/man/man1/nohang.1.gz
rm /etc/systemd/system/nohang.service rm -f /etc/systemd/system/nohang.service
rm /usr/local/bin/nohang rm -f /usr/local/bin/nohang