add CLI options using sys.argv
This commit is contained in:
parent
5f211d765b
commit
c0cffa7e7a
20
README.md
20
README.md
@ -107,6 +107,19 @@ $ sudo systemctl start nohang
|
||||
$ sudo systemctl enable nohang
|
||||
```
|
||||
|
||||
## Command line options
|
||||
|
||||
```
|
||||
./nohang -h
|
||||
usage: nohang [-h] [-c CONFIG]
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
-c CONFIG, --config CONFIG
|
||||
path to the config file, default values:
|
||||
./nohang.conf, /etc/nohang/nohang.conf
|
||||
```
|
||||
|
||||
## How to configure nohang
|
||||
|
||||
The program can be configured by editing the [config file](https://github.com/hakavlad/nohang/blob/master/nohang.conf). The configuration includes the following sections:
|
||||
@ -205,13 +218,13 @@ Please create [issues](https://github.com/hakavlad/nohang/issues). Use cases, fe
|
||||
- [x] Handle all timeouts when notify-send starts
|
||||
- [x] Fix conf parsing: use of `line.partition('=')` instead of `line.split('=')`
|
||||
- [x] Add `oom-sort`
|
||||
- [x] Reduce memory usage (remove `import argparse`)
|
||||
- [x] Reduce memory usage and startup time (using `sys.argv` instead of `argparse`)
|
||||
- [x] Remove CLI options (need to add it again via `sys.argv`)
|
||||
- [x] Remove self-defense options from config, use systemd unit scheduling instead
|
||||
- [x] Add the ability to send any signal instead of SIGTERM for processes with certain names
|
||||
- [x] Handle `UnicodeDecodeError` if victim name consists of many unicode characters
|
||||
- [x] Fix `mlockall()` using `MCL_ONFAULT` and lock all memory by default
|
||||
- [ ] Add `PSI` support (using `/proc/pressure/memory`, need Linux 4.20+)
|
||||
- [x] Add initial support for `PSI` (using `/proc/pressure/memory`, need Linux 4.20+)
|
||||
- [ ] Redesign of the config
|
||||
- [ ] Decrease CPU usage: ignore `zram` by default
|
||||
- [ ] Improve user input validation
|
||||
@ -221,5 +234,4 @@ Please create [issues](https://github.com/hakavlad/nohang/issues). Use cases, fe
|
||||
- [x] Fix: replace `re.fullmatch()` by `re.search()`
|
||||
- [ ] Validation RE patterns at startup
|
||||
|
||||
- [v0.1](https://github.com/hakavlad/nohang/releases/tag/v0.1), 2018-11-23
|
||||
- 1st release
|
||||
- [v0.1](https://github.com/hakavlad/nohang/releases/tag/v0.1), 2018-11-23: Initial release
|
||||
|
211
nohang
211
nohang
@ -4,9 +4,48 @@ import os
|
||||
from ctypes import CDLL
|
||||
from time import sleep, time
|
||||
from operator import itemgetter
|
||||
from sys import stdout
|
||||
from sys import stdout, stderr, argv, exit
|
||||
from signal import SIGKILL, SIGTERM
|
||||
|
||||
|
||||
help_mess = """usage: nohang [-h] [-c CONFIG]
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
-c CONFIG, --config CONFIG
|
||||
path to the config file, default values:
|
||||
./nohang.conf, /etc/nohang/nohang.conf"""
|
||||
|
||||
|
||||
if len(argv) == 1:
|
||||
if os.path.exists('./nohang.conf'):
|
||||
config = cd = os.getcwd() + '/nohang.conf'
|
||||
else:
|
||||
config = '/etc/nohang/nohang.conf'
|
||||
|
||||
elif len(argv) == 2:
|
||||
if argv[1] == '--help' or argv[1] == '-h':
|
||||
print(help_mess)
|
||||
exit(1)
|
||||
else:
|
||||
print('Invalid CLI input')
|
||||
exit(1)
|
||||
|
||||
elif len(argv) > 3:
|
||||
print('Invalid CLI input')
|
||||
exit(1)
|
||||
|
||||
else:
|
||||
if argv[1] == '--config' or argv[1] == '-c':
|
||||
config = argv[2]
|
||||
else:
|
||||
print('Invalid option: {}'.format(argv[1]))
|
||||
exit(1)
|
||||
|
||||
|
||||
conf_err_mess = 'Invalid config. Exit.'
|
||||
|
||||
|
||||
start_time = time()
|
||||
|
||||
sig_dict = {SIGKILL: 'SIGKILL',
|
||||
@ -40,6 +79,7 @@ HR = '~' * 79
|
||||
# todo: make config option
|
||||
print_total_stat = True
|
||||
|
||||
|
||||
##########################################################################
|
||||
|
||||
# define functions
|
||||
@ -194,7 +234,7 @@ def conf_parse_string(param):
|
||||
else:
|
||||
print('All the necessary parameters must be in the config')
|
||||
print('There is no "{}" parameter in the config'.format(param))
|
||||
exit()
|
||||
exit(1)
|
||||
|
||||
|
||||
def conf_parse_bool(param):
|
||||
@ -211,14 +251,14 @@ def conf_parse_bool(param):
|
||||
elif param_str == 'False':
|
||||
return False
|
||||
else:
|
||||
print('Invalid value of the "{}" parameter.'.format(param_str))
|
||||
print('Invalid value of the "{}" parameter.'.format(param))
|
||||
print('Valid values are True and False.')
|
||||
print('Exit')
|
||||
exit()
|
||||
exit(1)
|
||||
else:
|
||||
print('All the necessary parameters must be in the config')
|
||||
print('There is no "{}" parameter in the config'.format(param_str))
|
||||
exit()
|
||||
print('There is no "{}" parameter in the config'.format(param))
|
||||
exit(1)
|
||||
|
||||
|
||||
def rline1(path):
|
||||
@ -325,6 +365,9 @@ def pid_to_uid(pid):
|
||||
return f_list[uid_index].split('\t')[2]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def notify_send_wait(title, body):
|
||||
'''GUI notifications with UID != 0'''
|
||||
with Popen(['notify-send', '--icon=dialog-warning', title, body]) as proc:
|
||||
@ -338,8 +381,6 @@ def notify_send_wait(title, body):
|
||||
def notify_helper(title, body):
|
||||
'''GUI notification with UID = 0'''
|
||||
|
||||
# os.system(notify_helper_path + ' foo bar &')
|
||||
|
||||
with Popen([notify_helper_path, title, body]) as proc:
|
||||
try:
|
||||
proc.wait(timeout=wait_time)
|
||||
@ -350,12 +391,16 @@ def notify_helper(title, body):
|
||||
title, body))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def send_notify_warn():
|
||||
"""
|
||||
Look for process with maximum 'badness' and warn user with notification.
|
||||
(implement Low memory warnings)
|
||||
"""
|
||||
|
||||
'''
|
||||
# find process with max badness
|
||||
fat_tuple = fattest()
|
||||
pid = fat_tuple[0]
|
||||
@ -376,7 +421,14 @@ def send_notify_warn():
|
||||
|
||||
# title = 'Low memory: {}'.format(low_mem_percent)
|
||||
title = 'Low memory'
|
||||
'''
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
'''
|
||||
body = 'Next victim: {}[{}]'.format(
|
||||
name.replace(
|
||||
# symbol '&' can break notifications in some themes,
|
||||
@ -384,6 +436,13 @@ def send_notify_warn():
|
||||
'&', '*'),
|
||||
pid
|
||||
)
|
||||
'''
|
||||
|
||||
|
||||
'''
|
||||
body = 'MemAvail: {}%\nSwapFree: {}%'.format(
|
||||
round(mem_available / mem_total * 100),
|
||||
round(swap_free / (swap_total + 0.1) * 100))
|
||||
|
||||
if root: # If nohang was started by root
|
||||
# send notification to all active users with special script
|
||||
@ -391,6 +450,22 @@ def send_notify_warn():
|
||||
else: # Or by regular user
|
||||
# send notification to user that runs this nohang
|
||||
notify_send_wait(title, body)
|
||||
'''
|
||||
|
||||
|
||||
b = """{} 'Low memory' 'MemAvail: {}%\nSwapFree: {}%' &""".format(
|
||||
notify_helper_path,
|
||||
round(mem_available / mem_total * 100),
|
||||
round(swap_free / (swap_total + 0.1) * 100)
|
||||
)
|
||||
|
||||
t0 = time()
|
||||
os.system(b)
|
||||
t1 = time()
|
||||
print('t:', t1 - t0)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def send_notify(signal, name, pid):
|
||||
@ -402,13 +477,14 @@ def send_notify(signal, name, pid):
|
||||
pid: str process pid
|
||||
"""
|
||||
title = 'Freeze prevention'
|
||||
body = '{} {}[{}]'.format(
|
||||
body = '<b>{}</b> [{}] <b>{}</b>'.format(
|
||||
notify_sig_dict[signal],
|
||||
pid,
|
||||
name.replace(
|
||||
# symbol '&' can break notifications in some themes,
|
||||
# therefore it is replaced by '*'
|
||||
'&', '*'),
|
||||
pid
|
||||
'&', '*'
|
||||
)
|
||||
)
|
||||
if root:
|
||||
# send notification to all active users with notify-send
|
||||
@ -899,7 +975,7 @@ def sleep_after_check_mem():
|
||||
stdout.flush()
|
||||
sleep(t)
|
||||
except KeyboardInterrupt:
|
||||
exit()
|
||||
exit(1)
|
||||
|
||||
|
||||
def calculate_percent(arg_key):
|
||||
@ -922,12 +998,12 @@ def calculate_percent(arg_key):
|
||||
mem_min_percent = string_to_float_convert_test(mem_min_percent)
|
||||
if mem_min_percent is None:
|
||||
print('Invalid {} value, not float\nExit'.format(arg_key))
|
||||
exit()
|
||||
exit(1)
|
||||
# Final validations...
|
||||
if mem_min_percent < 0 or mem_min_percent > 100:
|
||||
print(
|
||||
'{}, as percents value, out of range [0; 100]\nExit'.format(arg_key))
|
||||
exit()
|
||||
exit(1)
|
||||
|
||||
# mem_min_sigterm_percent is clean and valid float percentage. Can
|
||||
# translate into Kb
|
||||
@ -938,14 +1014,14 @@ def calculate_percent(arg_key):
|
||||
mem_min_mb = string_to_float_convert_test(mem_min[:-1].strip())
|
||||
if mem_min_mb is None:
|
||||
print('Invalid {} value, not float\nExit'.format(arg_key))
|
||||
exit()
|
||||
exit(1)
|
||||
mem_min_kb = mem_min_mb * 1024
|
||||
if mem_min_kb > mem_total:
|
||||
print(
|
||||
'{} value can not be greater then MemTotal ({} MiB)\nExit'.format(
|
||||
arg_key, round(
|
||||
mem_total / 1024)))
|
||||
exit()
|
||||
exit(1)
|
||||
mem_min_percent = mem_min_kb / mem_total * 100
|
||||
|
||||
else:
|
||||
@ -982,7 +1058,7 @@ for s in mem_list:
|
||||
|
||||
if mem_list_names[2] != 'MemAvailable':
|
||||
print('Your Linux kernel is too old, Linux 3.14+ requied\nExit')
|
||||
exit()
|
||||
exit(1)
|
||||
|
||||
swap_total_index = mem_list_names.index('SwapTotal')
|
||||
swap_free_index = swap_total_index + 1
|
||||
@ -1003,6 +1079,8 @@ vm_size_index = status_names.index('VmSize')
|
||||
vm_rss_index = status_names.index('VmRSS')
|
||||
vm_swap_index = status_names.index('VmSwap')
|
||||
uid_index = status_names.index('Uid')
|
||||
state_index = status_names.index('State')
|
||||
|
||||
|
||||
try:
|
||||
anon_index = status_names.index('RssAnon')
|
||||
@ -1023,7 +1101,7 @@ cd = os.getcwd()
|
||||
|
||||
'''
|
||||
|
||||
config = '/etc/nohang/nohang.conf'
|
||||
#config = '/etc/nohang/nohang.conf'
|
||||
|
||||
# config = 'nohang.conf'
|
||||
|
||||
@ -1075,7 +1153,7 @@ try:
|
||||
if len(etc_name) > 15:
|
||||
print('Invalid config, the length of the process '
|
||||
'name must not exceed 15 characters\nExit')
|
||||
exit()
|
||||
exit(1)
|
||||
etc_dict[etc_name] = etc_command
|
||||
|
||||
# NEED VALIDATION!
|
||||
@ -1095,16 +1173,21 @@ try:
|
||||
|
||||
except PermissionError:
|
||||
print('PermissionError', conf_err_mess)
|
||||
exit()
|
||||
exit(1)
|
||||
except UnicodeDecodeError:
|
||||
print('UnicodeDecodeError', conf_err_mess)
|
||||
exit()
|
||||
exit(1)
|
||||
except IsADirectoryError:
|
||||
print('IsADirectoryError', conf_err_mess)
|
||||
exit()
|
||||
exit(1)
|
||||
except IndexError:
|
||||
print('IndexError', conf_err_mess)
|
||||
exit()
|
||||
exit(1)
|
||||
except FileNotFoundError:
|
||||
print('FileNotFoundError', conf_err_mess)
|
||||
exit(1)
|
||||
|
||||
|
||||
|
||||
# print(processname_re_list)
|
||||
# print(cmdline_re_list)
|
||||
@ -1158,53 +1241,53 @@ 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()
|
||||
exit(1)
|
||||
if rate_mem <= 0:
|
||||
print('rate_mem MUST be > 0\nExit')
|
||||
exit()
|
||||
exit(1)
|
||||
else:
|
||||
print('rate_mem not in config\nExit')
|
||||
exit()
|
||||
exit(1)
|
||||
|
||||
|
||||
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()
|
||||
exit(1)
|
||||
if rate_swap <= 0:
|
||||
print('rate_swap MUST be > 0\nExit')
|
||||
exit()
|
||||
exit(1)
|
||||
else:
|
||||
print('rate_swap not in config\nExit')
|
||||
exit()
|
||||
exit(1)
|
||||
|
||||
|
||||
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()
|
||||
exit(1)
|
||||
if rate_zram <= 0:
|
||||
print('rate_zram MUST be > 0\nExit')
|
||||
exit()
|
||||
exit(1)
|
||||
else:
|
||||
print('rate_zram not in config\nExit')
|
||||
exit()
|
||||
exit(1)
|
||||
|
||||
|
||||
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()
|
||||
exit(1)
|
||||
|
||||
|
||||
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()
|
||||
exit(1)
|
||||
|
||||
|
||||
if 'min_delay_after_sigterm' in config_dict:
|
||||
@ -1212,13 +1295,13 @@ if 'min_delay_after_sigterm' in config_dict:
|
||||
config_dict['min_delay_after_sigterm'])
|
||||
if min_delay_after_sigterm is None:
|
||||
print('Invalid min_delay_after_sigterm value, not float\nExit')
|
||||
exit()
|
||||
exit(1)
|
||||
if min_delay_after_sigterm < 0:
|
||||
print('min_delay_after_sigterm must be positiv\nExit')
|
||||
exit()
|
||||
exit(1)
|
||||
else:
|
||||
print('min_delay_after_sigterm not in config\nExit')
|
||||
exit()
|
||||
exit(1)
|
||||
|
||||
|
||||
if 'min_delay_after_sigkill' in config_dict:
|
||||
@ -1226,13 +1309,13 @@ if 'min_delay_after_sigkill' in config_dict:
|
||||
config_dict['min_delay_after_sigkill'])
|
||||
if min_delay_after_sigkill is None:
|
||||
print('Invalid min_delay_after_sigkill value, not float\nExit')
|
||||
exit()
|
||||
exit(1)
|
||||
if min_delay_after_sigkill < 0:
|
||||
print('min_delay_after_sigkill must be positive\nExit')
|
||||
exit()
|
||||
exit(1)
|
||||
else:
|
||||
print('min_delay_after_sigkill not in config\nExit')
|
||||
exit()
|
||||
exit(1)
|
||||
|
||||
|
||||
if 'psi_avg10_sleep_time' in config_dict:
|
||||
@ -1240,13 +1323,13 @@ if 'psi_avg10_sleep_time' in config_dict:
|
||||
config_dict['psi_avg10_sleep_time'])
|
||||
if psi_avg10_sleep_time is None:
|
||||
print('Invalid psi_avg10_sleep_time value, not float\nExit')
|
||||
exit()
|
||||
exit(1)
|
||||
if psi_avg10_sleep_time < 0:
|
||||
print('psi_avg10_sleep_time must be positive\nExit')
|
||||
exit()
|
||||
exit(1)
|
||||
else:
|
||||
print('psi_avg10_sleep_time not in config\nExit')
|
||||
exit()
|
||||
exit(1)
|
||||
|
||||
|
||||
if 'sigkill_psi_avg10' in config_dict:
|
||||
@ -1254,13 +1337,13 @@ if 'sigkill_psi_avg10' in config_dict:
|
||||
config_dict['sigkill_psi_avg10'])
|
||||
if sigkill_psi_avg10 is None:
|
||||
print('Invalid sigkill_psi_avg10 value, not float\nExit')
|
||||
exit()
|
||||
exit(1)
|
||||
if sigkill_psi_avg10 < 0 or sigkill_psi_avg10 > 100:
|
||||
print('sigkill_psi_avg10 must be in the range [0; 100]\nExit')
|
||||
exit()
|
||||
exit(1)
|
||||
else:
|
||||
print('sigkill_psi_avg10 not in config\nExit')
|
||||
exit()
|
||||
exit(1)
|
||||
|
||||
|
||||
if 'sigterm_psi_avg10' in config_dict:
|
||||
@ -1268,13 +1351,13 @@ if 'sigterm_psi_avg10' in config_dict:
|
||||
config_dict['sigterm_psi_avg10'])
|
||||
if sigterm_psi_avg10 is None:
|
||||
print('Invalid sigterm_psi_avg10 value, not float\nExit')
|
||||
exit()
|
||||
exit(1)
|
||||
if sigterm_psi_avg10 < 0 or sigterm_psi_avg10 > 100:
|
||||
print('sigterm_psi_avg10 must be in the range [0; 100]\nExit')
|
||||
exit()
|
||||
exit(1)
|
||||
else:
|
||||
print('sigterm_psi_avg10 not in config\nExit')
|
||||
exit()
|
||||
exit(1)
|
||||
|
||||
|
||||
if 'min_badness' in config_dict:
|
||||
@ -1282,13 +1365,13 @@ if 'min_badness' in config_dict:
|
||||
config_dict['min_badness'])
|
||||
if min_badness is None:
|
||||
print('Invalid min_badness value, not integer\nExit')
|
||||
exit()
|
||||
exit(1)
|
||||
if min_badness < 0 or min_badness > 1000:
|
||||
print('Invalud min_badness value\nExit')
|
||||
exit()
|
||||
exit(1)
|
||||
else:
|
||||
print('min_badness not in config\nExit')
|
||||
exit()
|
||||
exit(1)
|
||||
|
||||
|
||||
if 'oom_score_adj_max' in config_dict:
|
||||
@ -1296,13 +1379,13 @@ if 'oom_score_adj_max' in config_dict:
|
||||
config_dict['oom_score_adj_max'])
|
||||
if oom_score_adj_max is None:
|
||||
print('Invalid oom_score_adj_max value, not integer\nExit')
|
||||
exit()
|
||||
exit(1)
|
||||
if oom_score_adj_max < 0 or oom_score_adj_max > 1000:
|
||||
print('Invalid oom_score_adj_max value\nExit')
|
||||
exit()
|
||||
exit(1)
|
||||
else:
|
||||
print('oom_score_adj_max not in config\nExit')
|
||||
exit()
|
||||
exit(1)
|
||||
|
||||
|
||||
if 'min_time_between_warnings' in config_dict:
|
||||
@ -1310,20 +1393,20 @@ if 'min_time_between_warnings' in config_dict:
|
||||
config_dict['min_time_between_warnings'])
|
||||
if min_time_between_warnings is None:
|
||||
print('Invalid min_time_between_warnings value, not float\nExit')
|
||||
exit()
|
||||
exit(1)
|
||||
if min_time_between_warnings < 1 or min_time_between_warnings > 300:
|
||||
print('min_time_between_warnings value out of range [1; 300]\nExit')
|
||||
exit()
|
||||
exit(1)
|
||||
else:
|
||||
print('min_time_between_warnings not in config\nExit')
|
||||
exit()
|
||||
exit(1)
|
||||
|
||||
|
||||
if 'swap_min_warnings' in config_dict:
|
||||
swap_min_warnings = config_dict['swap_min_warnings']
|
||||
else:
|
||||
print('swap_min_warnings not in config\nExit')
|
||||
exit()
|
||||
exit(1)
|
||||
|
||||
|
||||
##########################################################################
|
||||
@ -1344,12 +1427,12 @@ def get_swap_threshold_tuple(string):
|
||||
valid = string_to_float_convert_test(string[:-1])
|
||||
if valid is None:
|
||||
print('somewhere swap unit is not float_%')
|
||||
exit()
|
||||
exit(1)
|
||||
|
||||
value = float(string[:-1].strip())
|
||||
if value < 0 or value > 100:
|
||||
print('invalid value, must be from the range[0; 100] %')
|
||||
exit()
|
||||
exit(1)
|
||||
|
||||
return value, True
|
||||
|
||||
@ -1357,18 +1440,18 @@ def get_swap_threshold_tuple(string):
|
||||
valid = string_to_float_convert_test(string[:-1])
|
||||
if valid is None:
|
||||
print('somewhere swap unit is not float_M')
|
||||
exit()
|
||||
exit(1)
|
||||
|
||||
value = float(string[:-1].strip()) * 1024
|
||||
if value < 0:
|
||||
print('invalid unit in config (negative value)')
|
||||
exit()
|
||||
exit(1)
|
||||
|
||||
return value, False
|
||||
|
||||
else:
|
||||
print('Invalid config file. There are invalid units somewhere\nExit')
|
||||
exit()
|
||||
exit(1)
|
||||
|
||||
|
||||
swap_min_sigterm_tuple = get_swap_threshold_tuple(swap_min_sigterm)
|
||||
|
@ -4,7 +4,7 @@ After=sysinit.target
|
||||
Documentation=man:nohang(1) https://github.com/hakavlad/nohang
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/sbin/nohang
|
||||
ExecStart=/usr/sbin/nohang --config /etc/nohang/nohang.conf
|
||||
Slice=nohang.slice
|
||||
Restart=always
|
||||
ProtectSystem=strict
|
||||
|
Loading…
Reference in New Issue
Block a user