diff --git a/nohang b/nohang index fc5eaa3..b5bd5ab 100755 --- a/nohang +++ b/nohang @@ -14,11 +14,19 @@ from signal import SIGKILL, SIGTERM sig_dict = {SIGKILL: 'SIGKILL', SIGTERM: 'SIGTERM'} + ''' nm = 30 nc = nm + 1 ''' +self_uid = os.geteuid() +self_pid = str(os.getpid()) + +wait_time = 0.1 +cache_time = 30 +cache_path = '/dev/shm/nohang_env_cache' + ########################################################################## @@ -40,42 +48,64 @@ def format_time(t): return '{} h {} min {} sec'.format(h, m, s) +def re_pid_environ(pid): + """ + read environ of 1 process + returns tuple with USER, DBUS, DISPLAY like follow: + ('user', 'DISPLAY=:0', + 'DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus') + returns None if these vars is not in /proc/[pid]/environ + """ + display_env = 'DISPLAY=' + dbus_env = 'DBUS_SESSION_BUS_ADDRESS=' + user_env = 'USER=' + try: + env = str(rline1('/proc/' + pid + '/environ')) + if display_env in env and dbus_env in env and user_env in env: + env_list = env.split('\x00') + + # iterating over a list of process environment variables + for i in env_list: + if i.startswith(user_env): + user = i + continue + + if i.startswith(display_env): + display = i[:10] + continue + + if i.startswith(dbus_env): + if ',guid=' in i: + return None + dbus = i + continue + + if i.startswith('HOME='): + # exclude Display Manager's user + if i.startswith('HOME=/var'): + return None + + env = user.partition('USER=')[2], display, dbus + return env + + except FileNotFoundError: + pass + except ProcessLookupError: + pass + + def root_notify_env(): - """ - Get environment for notification sending from root. - - Returns list [tuple username, tuple DISPLAY, tuple DBUS_SESSION_BUS_ADDRES] - """ - ps_output_list = Popen(['ps', 'ae'], stdout=PIPE - ).communicate()[0].decode().split('\n') - - lines_with_displays = [] - for line in ps_output_list: - if ' DISPLAY=' in line and ' DBUS_SESSION_BUS_ADDRES' \ - 'S=' in line and ' USER=' in line: - lines_with_displays.append(line) - - # list of tuples with needments - deus = [] - for i in lines_with_displays: - for i in i.split(' '): - if i.startswith('USER='): - user = i.strip('\n').split('=')[1] - continue - if i.startswith('DISPLAY='): - disp_value = i.strip('\n').split('=')[1][0:2] - disp = 'DISPLAY=' + disp_value - continue - if i.startswith('DBUS_SESSION_BUS_ADDRESS='): - dbus = i.strip('\n') - deus.append(tuple([user, disp, dbus])) - - # unique list of tuples - vult = [] - for user_env_tuple in set(deus): - vult.append(user_env_tuple) - - return vult + """return set(user, display, dbus)""" + unsorted_envs_list = [] + # iterates over processes, find processes with suitable env + for pid in os.listdir('/proc'): + if pid[0].isdecimal() is False: + continue + one_env = re_pid_environ(pid) + unsorted_envs_list.append(one_env) + env = set(unsorted_envs_list) + env.discard(None) + return env def string_to_float_convert_test(string): @@ -141,12 +171,12 @@ def rline1(path): for line in f: return line[:-1] -''' + def write(path, string): """Write string to path.""" with open(path, 'w') as f: f.write(string) -''' + def kib_to_mib(num): """Convert KiB values to MiB values.""" @@ -264,16 +294,26 @@ def send_notify_warn(): if root: # If nohang was started by root # send notification to all active users with special script + + + # теперь можно напрямую уведомлять из кэша если он не устарел + + Popen([ '/usr/bin/nohang_notify_low_mem', '--mem', low_mem_percent, '--pid', pid, '--name', name ]) + + else: # Or by regular user # send notification to user that runs this nohang - Popen(['notify-send', '--icon=dialog-warning', - '{}'.format(title), '{}'.format(body)]) + try: + Popen(['notify-send', '--icon=dialog-warning', + '{}'.format(title), '{}'.format(body)]).wait(wait_time) + except TimeoutExpired: + print('TimeoutExpired: ' + 'notify low mem') def send_notify(signal, name, pid): @@ -296,14 +336,19 @@ def send_notify(signal, name, pid): if len(b) > 0: for i in b: username, display_env, dbus_env = i[0], i[1], i[2] - Popen(['sudo', '-u', username, 'env', display_env, - dbus_env, 'notify-send', '--icon=dialog-warning', - '{}'.format(title), '{}'.format(body)]).wait(9) + try: + Popen(['sudo', '-u', username, 'env', display_env, + dbus_env, 'notify-send', '--icon=dialog-warning', + '{}'.format(title), '{}'.format(body)]).wait(wait_time) + except TimeoutExpired: + print('TimeoutExpired: ' + 'notify send signal') else: # send notification to user that runs this nohang - Popen(['notify-send', '--icon=dialog-warning', - '{}'.format(title), '{}'.format(body)]) - + try: + Popen(['notify-send', '--icon=dialog-warning', + '{}'.format(title), '{}'.format(body)]).wait(wait_time) + except TimeoutExpired: + print('BAAAR') def send_notify_etc(pid, name, command): """ @@ -322,9 +367,12 @@ def send_notify_etc(pid, name, command): if len(b) > 0: for i in b: username, display_env, dbus_env = i[0], i[1], i[2] - Popen(['sudo', '-u', username, 'env', display_env, - dbus_env, 'notify-send', '--icon=dialog-warning', - '{}'.format(title), '{}'.format(body)]) + try: + Popen(['sudo', '-u', username, 'env', display_env, + dbus_env, 'notify-send', '--icon=dialog-warning', + '{}'.format(title), '{}'.format(body)]).wait(wait_time) + except TimeoutExpired: + print('TimeoutExpired: notify run command') else: # send notification to user that runs this nohang Popen(['notify-send', '--icon=dialog-warning', '{}'.format(title), '{}' @@ -357,7 +405,7 @@ def fattest(): for pid in os.listdir('/proc'): # only directories whose names consist only of numbers, except /proc/1/ - if pid[0].isdecimal() is False or pid is '1': + if pid[0].isdecimal() is False or pid is '1' or pid is self_pid: continue # find and modify badness (if it needs) @@ -1183,8 +1231,7 @@ else: mla_res = '' -self_uid = os.geteuid() -self_pid = os.getpid() + if self_uid == 0: @@ -1314,7 +1361,7 @@ if print_config: mem_len = len(str(round(mem_total / 1024.0))) if gui_notifications or gui_low_memory_warnings: - from subprocess import Popen, PIPE + from subprocess import Popen, TimeoutExpired notify_sig_dict = {SIGKILL: 'Killing', SIGTERM: 'Terminating'} @@ -1332,8 +1379,81 @@ print('The duration of startup:', print('Monitoring started!') + + + + + + + + + + + + + +def save_env_cache(): + z = '{}\n'.format(int(time())) + a = root_notify_env() + #print(a) + for i in a: + z = z + '{}\x00{}\x00{}\n'.format(i[0], i[1], i[2]) + write(cache_path, z) + os.chmod(cache_path, 0000) + return a + + +def read_env_cache(): + x, y = [], [] + try: + with open(cache_path) as f: + for n, line in enumerate(f): + if n is 0: + t = line[:-1] + y.append(t) + continue + if n > 0: + x.append(line[:-1].split('\x00')) + except FileNotFoundError: + return None + y.append(x) + return y + + +def root_env_cache(): + cache = read_env_cache() + if cache is None: + print('cache not found, get new env and cache it') + return save_env_cache() + delta_t = time() - int(cache[0]) + if delta_t > cache_time: + print('cache time: {}, delta: {}, ' \ + 'get new env and cache it'.format( + cache_time, round(delta_t))) + save_env_cache() + return root_notify_env() + else: + print('cache time: {}, delta: {}, ' \ + 'get cached env'.format( + cache_time, round(delta_t))) + return cache[1] + + +t1 = time() +# root_env_cache() +t2 = time() +# print(t2 - t1) + + + + + + + stdout.flush() +#exit() + ########################################################################## diff --git a/nohang_notify_low_mem b/nohang_notify_low_mem index 57c5f8b..e02687c 100755 --- a/nohang_notify_low_mem +++ b/nohang_notify_low_mem @@ -1,15 +1,20 @@ #!/usr/bin/env python3 -# notify_low_mem --mem '14% 12%' --name 'stress' --pid '6666' +# nohang_notify_low_mem --mem '14% 12%' --name 'stress' --pid '6666' -# should be remaked without run `ps` +# need UID=0 # output: # Low memory: 14% 12% # Fattest process: 6666, stress +# need to remove this slow and fat parser from argparse import ArgumentParser -from subprocess import Popen, PIPE + +from os import listdir +from subprocess import Popen, TimeoutExpired + +wait_time = 0.1 parser = ArgumentParser() @@ -44,48 +49,74 @@ title = 'Low memory: {}'.format(mem) body = 'Fattest process: {}, {}'.format(pid, name) -# return list of tuples with -# username, DISPLAY and DBUS_SESSION_BUS_ADDRESS +display_env = 'DISPLAY=' +dbus_env = 'DBUS_SESSION_BUS_ADDRESS=' +user_env = 'USER=' + + +def rline1(path): + """read 1st line from path.""" + with open(path) as f: + for line in f: + return line + + +def re_pid_environ(pid): + """ + read environ of 1 process + returns tuple with USER, DBUS, DISPLAY like follow: + ('user', 'DISPLAY=:0', + 'DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus') + returns None if these vars is not in /proc/[pid]/environ + """ + try: + env = str(rline1('/proc/' + pid + '/environ')) + if display_env in env and dbus_env in env and user_env in env: + env_list = env.split('\x00') + + # iterating over a list of process environment variables + for i in env_list: + if i.startswith(user_env): + user = i + continue + + if i.startswith(display_env): + display = i[:10] + continue + + if i.startswith(dbus_env): + if ',guid=' in i: + return None + dbus = i + continue + + if i.startswith('HOME='): + # exclude Display Manager's user + if i.startswith('HOME=/var'): + return None + + env = user.partition('USER=')[2], display, dbus + return env + + except FileNotFoundError: + pass + except ProcessLookupError: + pass + + def root_notify_env(): + """return set(user, display, dbus)""" + unsorted_envs_list = [] + # iterates over processes, find processes with suitable env + for pid in listdir('/proc'): + if pid[0].isdecimal() is False: + continue + one_env = re_pid_environ(pid) + unsorted_envs_list.append(one_env) + env = set(unsorted_envs_list) + env.discard(None) + return env - ps_output_list = Popen(['ps', 'ae'], stdout=PIPE - ).communicate()[0].decode().split('\n') - - lines_with_displays = [] - for line in ps_output_list: - if ' DISPLAY=' in line and ' DBUS_SESSION_BUS_ADDRES' \ - 'S=' in line and ' USER=' in line: - lines_with_displays.append(line) - - # list of tuples with needments - deus = [] - for i in lines_with_displays: - for i in i.split(' '): - if i.startswith('USER='): - - - # .partition('=') !!! - user = i.strip('\n').split('=')[1] - continue - if i.startswith('DISPLAY='): - disp_value = i.strip('\n').split('=')[1][0:2] - - - - - # Здесь можно не склеивать, а сразу сделать пару ключ: значение - disp = 'DISPLAY=' + disp_value - continue - if i.startswith('DBUS_SESSION_BUS_ADDRESS='): - dbus = i.strip('\n') - deus.append(tuple([user, disp, dbus])) - - # unique list of tuples - vult = [] - for user_env_tuple in set(deus): - vult.append(user_env_tuple) - - return vult b = root_notify_env() @@ -98,11 +129,14 @@ if len(b) > 0: dbus_tuple = dbus_env.partition('=') display_key, display_value = display_tuple[0], display_tuple[2] dbus_key, dbus_value = dbus_tuple[0], dbus_tuple[2] - Popen(['sudo', '-u', username, - 'notify-send', '--icon=dialog-warning', - '{}'.format(title), '{}'.format(body) - ], env={ - display_key: display_value, dbus_key: dbus_value - }).wait(9) + try: + Popen(['sudo', '-u', username, + 'notify-send', '--icon=dialog-warning', + '{}'.format(title), '{}'.format(body) + ], env={ + display_key: display_value, dbus_key: dbus_value + }).wait(wait_time) + except TimeoutExpired: + print('TimeoutExpired: notify ' + username) else: print('Low memory warnings: nobody logged in with GUI. Nothing to do.')