From 646ffb9d46975df1c58307803fcd325ac6a5a3a5 Mon Sep 17 00:00:00 2001 From: actionless Date: Sun, 16 Dec 2018 19:00:37 +0100 Subject: [PATCH 1/3] refactor(oom-sort): separate code into logical parts --- oom-sort | 257 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 163 insertions(+), 94 deletions(-) diff --git a/oom-sort b/oom-sort index 1b9e5f8..e32724f 100755 --- a/oom-sort +++ b/oom-sort @@ -3,120 +3,189 @@ sort processes by oom_score """ -# нужна еще валидация cli ввода +# @TODO: user input validation -from operator import itemgetter from os import listdir from argparse import ArgumentParser -parser = ArgumentParser() -parser.add_argument( - '--num', - '-n', - help="""max number of lines; default: 99999""", - default=None, - type=str -) +def parse_arguments(): + """ + parse CLI args + """ + parser = ArgumentParser() + parser.add_argument( + '--num', + '-n', + help="""max number of lines; default: 99999""", + default=None, + type=str + ) + parser.add_argument( + '--len', + '-l', + help="""max cmdline length; default: 99999""", + default=None, + type=str + ) + return parser.parse_args() -parser.add_argument( - '--len', - '-l', - help="""max cmdline length; default: 99999""", - default=None, - type=str -) -args = parser.parse_args() - -display_cmdline = args.len -num_lines = args.num -if num_lines == None: - num_lines = 99999 - -if display_cmdline == None: - display_cmdline = 99999 - -def human(num): +def human_readable(num): '''KiB to MiB''' return str(round(num / 1024.0)).rjust(6, ' ') -with open('/proc/self/status') as file: - status_list = file.readlines() -# список имен из /proc/*/status для дальнейшего поиска позиций VmRSS and VmSwap -status_names = [] -for s in status_list: - status_names.append(s.split(':')[0]) +class TableIndexes: # pylint: disable=too-few-public-methods + """ + table headers from /proc/*/status for further + searching positions of VmRSS and VmSwap in each process output + """ -vm_rss_index = status_names.index('VmRSS') -vm_swap_index = status_names.index('VmSwap') -uid_index = status_names.index('Uid') + def __init__(self): + with open('/proc/self/status') as status_file: + status_list = status_file.readlines() -oom_list = [] -for pid in listdir('/proc'): - # пропускаем элементы, состоящие не из цифр и PID 1 - if pid.isdigit() is not True or pid == '1': - continue - try: - with open('/proc/' + pid + '/cmdline') as file: - try: - cmdline = file.readlines()[0].replace('\x00', ' ') - except IndexError: - continue - with open('/proc/' + pid + '/oom_score') as file: - oom_score = int(file.readlines()[0][:-1]) - with open('/proc/' + pid + '/oom_score_adj') as file: - oom_score_adj = int(file.readlines()[0][:-1]) - except FileNotFoundError: - continue - except ProcessLookupError: - continue + status_names = [] + for line in status_list: + status_names.append(line.split(':')[0]) - oom_list.append((pid, oom_score, oom_score_adj, cmdline)) + self.vm_rss = status_names.index('VmRSS') + self.vm_swap = status_names.index('VmSwap') + self.uid = status_names.index('Uid') -# list sorted by oom_score -oom_list_sorted = sorted(oom_list, key=itemgetter(1), reverse=True) -if display_cmdline == '0': - print('oom_score oom_score_adj UID PID Name VmRSS VmSwap') - print('--------- ------------- ----- ----- --------------- -------- --------') -else: - print('oom_score oom_score_adj UID PID Name VmRSS VmSwap cmdline') - print('--------- ------------- ----- ----- --------------- -------- -------- -------') +INDEX = TableIndexes() -# итерируемся по сортированному списку oom_score, печатая name, pid etc -for i in oom_list_sorted[:int(num_lines)]: - pid = i[0] - oom_score = i[1] - oom_score_adj = i[2] - cmdline = i[3].strip() - try: - # читать часть файла не дальше VmSwap - когда-нибудь - with open('/proc/' + pid + '/status') as file: - status_list = file.readlines() +class ProcessInfo: - vm_rss = int(status_list[vm_rss_index].split(':')[1].split(' ')[-2]) - vm_swap = int(status_list[vm_swap_index].split(':')[1].split(' ')[-2]) - name = status_list[0][:-1].split('\t')[1] - uid = status_list[uid_index].split('\t')[1] + pid = None + cmdline = None + oom_score = None + oom_score_adj = None - except FileNotFoundError: - continue + name = None + uid = None + vm_rss = None + vm_swap = None - except ProcessLookupError: - continue + @classmethod + def from_pid(cls, pid): + """ + create ProcessInfo instance reading process info from /proc/{pid}/ + """ + info = cls() + info.pid = pid + try: + with open('/proc/' + pid + '/cmdline') as file: + try: + info.cmdline = file.readlines()[0].replace('\x00', ' ') + except IndexError: + return None + with open('/proc/' + pid + '/oom_score') as file: + info.oom_score = int(file.readlines()[0][:-1]) + with open('/proc/' + pid + '/oom_score_adj') as file: + info.oom_score_adj = int(file.readlines()[0][:-1]) + except FileNotFoundError: + return None + except ProcessLookupError: + return None + return info - print( - '{} {} {} {} {} {} M {} M {}'.format( - str(oom_score).rjust(9), - str(oom_score_adj).rjust(13), - uid.rjust(5), - str(pid).rjust(5), - name.ljust(15), - human(vm_rss), - human(vm_swap), - cmdline[:int(display_cmdline)] + def read_status(self): + """ + return True if process have info in /proc/{pid}/status + """ + try: + # @TODO: stop reading file after VmSwap value retrieved + with open('/proc/' + self.pid + '/status') as file: + status_list = file.readlines() + self.vm_rss = int( + status_list[INDEX.vm_rss].split(':')[1].split(' ')[-2] + ) + self.vm_swap = int( + status_list[INDEX.vm_swap].split(':')[1].split(' ')[-2] + ) + self.name = status_list[0][:-1].split('\t')[1] + self.uid = status_list[INDEX.uid].split('\t')[1] + except FileNotFoundError: + return False + except ProcessLookupError: + return False + return True + + def format_output(self, display_cmdline): + """ + format output for printing + """ + return '{} {} {} {} {} {} M {} M {}'.format( + str(self.oom_score).rjust(9), + str(self.oom_score_adj).rjust(13), + self.uid.rjust(5), + str(self.pid).rjust(5), + self.name.ljust(15), + human_readable(self.vm_rss), + human_readable(self.vm_swap), + self.cmdline[:int(display_cmdline)] ) - ) + + +class Application: + """ + oom-sort application + """ + + oom_list = None + + def __init__(self): + args = parse_arguments() + + self.num_lines = args.num + if self.num_lines is None: + self.num_lines = 99999 + + self.display_cmdline = args.len + if self.display_cmdline is None: + self.display_cmdline = 99999 + + def print_stats(self): + """ + print processes stats sorted by OOM score + """ + + oom_list = [] + for pid in listdir('/proc'): + # skip non-numeric entries and PID 1 + if pid.isdigit() is not True or pid == '1': + continue + proc_info = ProcessInfo.from_pid(pid) + if proc_info: + oom_list.append(proc_info) + oom_list.sort(key=lambda p: p.oom_score, reverse=True) + + if self.display_cmdline == '0': + print('oom_score oom_score_adj UID PID Name VmRSS VmSwap') + print('--------- ------------- ----- ----- --------------- -------- --------') + else: + print('oom_score oom_score_adj UID PID Name VmRSS VmSwap cmdline') + print('--------- ------------- ----- ----- --------------- -------- -------- -------') + + # iterate through list sorted by oom_score and print name, pid, etc + for proc_info in oom_list[:int(self.num_lines)]: + if proc_info.read_status(): + print( + proc_info.format_output( + display_cmdline=self.display_cmdline + ) + ) + + def main(self): + """ + application entrypoint + """ + self.print_stats() + + +if __name__ == "__main__": + Application().main() From 0b2be659d1dc30fba555b10e99021e734f88ee07 Mon Sep 17 00:00:00 2001 From: actionless Date: Sun, 16 Dec 2018 19:10:49 +0100 Subject: [PATCH 2/3] feat(oom-sort): add -r/--refresh flag --- oom-sort | 42 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/oom-sort b/oom-sort index e32724f..8ac2194 100755 --- a/oom-sort +++ b/oom-sort @@ -7,6 +7,7 @@ sort processes by oom_score from os import listdir from argparse import ArgumentParser +from time import sleep def parse_arguments(): @@ -28,14 +29,31 @@ def parse_arguments(): default=None, type=str ) + parser.add_argument( + '--refresh', + '-r', + help='refresh interval (0 to disable); default: 0. ' + 'Use it with --num/-n to also limit the output length', + default=0, + type=int + ) return parser.parse_args() def human_readable(num): - '''KiB to MiB''' + """ + KiB to MiB + """ return str(round(num / 1024.0)).rjust(6, ' ') +def clear_screen(): + """ + print ANSI sequence to clear the screen + """ + print('\033c') + + class TableIndexes: # pylint: disable=too-few-public-methods """ table headers from /proc/*/status for further @@ -149,6 +167,8 @@ class Application: if self.display_cmdline is None: self.display_cmdline = 99999 + self.refresh_interval = args.refresh + def print_stats(self): """ print processes stats sorted by OOM score @@ -180,11 +200,29 @@ class Application: ) ) + def oom_top(self): + """ + show `top`-like refreshing stats + """ + while True: + try: + clear_screen() + print("Refreshing each {} seconds, press to interrupt:".format( + self.refresh_interval + )) + self.print_stats() + sleep(self.refresh_interval) + except KeyboardInterrupt: + break + def main(self): """ application entrypoint """ - self.print_stats() + if not self.refresh_interval: + self.print_stats() + else: + self.oom_top() if __name__ == "__main__": From 58cfed83a279450e6668945094960e1bab976376 Mon Sep 17 00:00:00 2001 From: actionless Date: Sun, 16 Dec 2018 19:15:32 +0100 Subject: [PATCH 3/3] refactor(oom-sort): improve ArgParser usage --- oom-sort | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/oom-sort b/oom-sort index 8ac2194..71a1939 100755 --- a/oom-sort +++ b/oom-sort @@ -19,15 +19,15 @@ def parse_arguments(): '--num', '-n', help="""max number of lines; default: 99999""", - default=None, - type=str + default=99999, + type=int ) parser.add_argument( '--len', '-l', help="""max cmdline length; default: 99999""", - default=None, - type=str + default=99999, + type=int ) parser.add_argument( '--refresh', @@ -145,7 +145,7 @@ class ProcessInfo: self.name.ljust(15), human_readable(self.vm_rss), human_readable(self.vm_swap), - self.cmdline[:int(display_cmdline)] + self.cmdline[:display_cmdline] ) @@ -158,15 +158,8 @@ class Application: def __init__(self): args = parse_arguments() - self.num_lines = args.num - if self.num_lines is None: - self.num_lines = 99999 - self.display_cmdline = args.len - if self.display_cmdline is None: - self.display_cmdline = 99999 - self.refresh_interval = args.refresh def print_stats(self): @@ -184,7 +177,7 @@ class Application: oom_list.append(proc_info) oom_list.sort(key=lambda p: p.oom_score, reverse=True) - if self.display_cmdline == '0': + if self.display_cmdline == 0: print('oom_score oom_score_adj UID PID Name VmRSS VmSwap') print('--------- ------------- ----- ----- --------------- -------- --------') else: @@ -192,7 +185,7 @@ class Application: print('--------- ------------- ----- ----- --------------- -------- -------- -------') # iterate through list sorted by oom_score and print name, pid, etc - for proc_info in oom_list[:int(self.num_lines)]: + for proc_info in oom_list[:self.num_lines]: if proc_info.read_status(): print( proc_info.format_output(