refactor(oom-sort): separate code into logical parts

This commit is contained in:
actionless 2018-12-16 19:00:37 +01:00
parent 74640febf8
commit 646ffb9d46

257
oom-sort
View File

@ -3,120 +3,189 @@
sort processes by oom_score sort processes by oom_score
""" """
# нужна еще валидация cli ввода # @TODO: user input validation
from operator import itemgetter
from os import listdir from os import listdir
from argparse import ArgumentParser from argparse import ArgumentParser
parser = ArgumentParser()
parser.add_argument( def parse_arguments():
'--num', """
'-n', parse CLI args
help="""max number of lines; default: 99999""", """
default=None, parser = ArgumentParser()
type=str 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() def human_readable(num):
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):
'''KiB to MiB''' '''KiB to MiB'''
return str(round(num / 1024.0)).rjust(6, ' ') return str(round(num / 1024.0)).rjust(6, ' ')
with open('/proc/self/status') as file:
status_list = file.readlines()
# список имен из /proc/*/status для дальнейшего поиска позиций VmRSS and VmSwap class TableIndexes: # pylint: disable=too-few-public-methods
status_names = [] """
for s in status_list: table headers from /proc/*/status for further
status_names.append(s.split(':')[0]) searching positions of VmRSS and VmSwap in each process output
"""
vm_rss_index = status_names.index('VmRSS') def __init__(self):
vm_swap_index = status_names.index('VmSwap') with open('/proc/self/status') as status_file:
uid_index = status_names.index('Uid') status_list = status_file.readlines()
oom_list = [] status_names = []
for pid in listdir('/proc'): for line in status_list:
# пропускаем элементы, состоящие не из цифр и PID 1 status_names.append(line.split(':')[0])
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
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': INDEX = TableIndexes()
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('--------- ------------- ----- ----- --------------- -------- -------- -------')
# итерируемся по сортированному списку 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: class ProcessInfo:
# читать часть файла не дальше VmSwap - когда-нибудь
with open('/proc/' + pid + '/status') as file:
status_list = file.readlines()
vm_rss = int(status_list[vm_rss_index].split(':')[1].split(' ')[-2]) pid = None
vm_swap = int(status_list[vm_swap_index].split(':')[1].split(' ')[-2]) cmdline = None
name = status_list[0][:-1].split('\t')[1] oom_score = None
uid = status_list[uid_index].split('\t')[1] oom_score_adj = None
except FileNotFoundError: name = None
continue uid = None
vm_rss = None
vm_swap = None
except ProcessLookupError: @classmethod
continue 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( def read_status(self):
'{} {} {} {} {} {} M {} M {}'.format( """
str(oom_score).rjust(9), return True if process have info in /proc/{pid}/status
str(oom_score_adj).rjust(13), """
uid.rjust(5), try:
str(pid).rjust(5), # @TODO: stop reading file after VmSwap value retrieved
name.ljust(15), with open('/proc/' + self.pid + '/status') as file:
human(vm_rss), status_list = file.readlines()
human(vm_swap), self.vm_rss = int(
cmdline[:int(display_cmdline)] 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()