add support for fine-grained style control

This commit is contained in:
mingyang 2020-12-14 16:54:49 +08:00
parent 7c304c688b
commit 5f57991308
3 changed files with 140 additions and 50 deletions

1
.gitignore vendored
View File

@ -3,6 +3,7 @@
__pycache__/ __pycache__/
*.py[cod] *.py[cod]
*$py.class *$py.class
try.py
# C extensions # C extensions
*.so *.so

View File

@ -26,6 +26,32 @@ def save_config(config):
with open(configdir, 'w+', encoding='utf-8') as f: with open(configdir, 'w+', encoding='utf-8') as f:
json.dump(config, f, ensure_ascii=False, indent=4) json.dump(config, f, ensure_ascii=False, indent=4)
# style format: |c|l:15|r|c:14rl:13|
def parse_style(style):
components = []
limits = []
while len(style) > 0:
ch = style[0]
if ch == '|':
components.append(ch)
style = style[1:]
continue
elif ch in ['l', 'r', 'c']:
limit = None
style = style[1:]
if style[0] == ':':
style = style[1:]
digits = ''
while style[0].isdigit():
digits += style[0]
style = style[1:]
if digits != '':
limit = int(digits)
components.append(ch)
limits.append(limit)
style = ''.join(components)
return style, limits
if __name__ == '__main__': if __name__ == '__main__':
stat = GPUStat() stat = GPUStat()
@ -35,11 +61,13 @@ if __name__ == '__main__':
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('--profile', '-p', default=None, type=str, help='profile keyword, corresponding configuration are saved in ~/.gpuutil.conf') parser.add_argument('--profile', '-p', default=None, type=str, help='profile keyword, corresponding configuration are saved in ~/.gpuutil.conf')
parser.add_argument('--cols', '-c', type=csv2list, help='colums to show') parser.add_argument('--cols', '-c', type=csv2list, help='colums to show')
parser.add_argument('--style', '-sty', type=parse_style, help='column style')
parser.add_argument('--show-process', '-sp', default=True, type=str2bool, help='whether show process or not') parser.add_argument('--show-process', '-sp', default=True, type=str2bool, help='whether show process or not')
parser.add_argument('--save', default=False, action="store_true", help='save config to profile') parser.add_argument('--save', default=False, action="store_true", help='save config to profile')
args = parser.parse_args() args = parser.parse_args()
cols = args.cols if args.cols is not None else recommended_cols cols = args.cols if args.cols is not None else recommended_cols
show_process = args.show_process show_process = args.show_process
style, limit = args.style
unexpected_cols = [] unexpected_cols = []
for col in cols: for col in cols:
if col not in avaliable_cols: if col not in avaliable_cols:
@ -50,7 +78,9 @@ if __name__ == '__main__':
if args.save: if args.save:
params = { params = {
"cols": cols, "cols": cols,
"show-process": show_process "show-process": show_process,
"style": style,
"limit": limit
} }
profile = args.profile if args.profile is not None else input('Please input your profile name:\n>>> ') profile = args.profile if args.profile is not None else input('Please input your profile name:\n>>> ')
config = load_config() config = load_config()
@ -62,6 +92,13 @@ if __name__ == '__main__':
params = config[args.profile] params = config[args.profile]
cols = params["cols"] cols = params["cols"]
show_process = params["show-process"] show_process = params["show-process"]
style = None
limit = None
if "style" in params:
style = params["style"]
if "limit" in params:
limit = params["limit"]
else: else:
raise ValueError('Profile do not exist.\nAvaliable Profiles:{0}'.format(','.join(list(config.keys())))) raise ValueError('Profile do not exist.\nAvaliable Profiles:{0}'.format(','.join(list(config.keys()))))
stat.show(enabled_cols = cols, show_command=show_process) stat.show(enabled_cols = cols, colsty=style, colsz=limit, show_command=show_process)

View File

@ -168,50 +168,93 @@ def get_basic_process_info_windows():
} }
return processes return processes
def draw_table(table, header_line = 0, c_align = 'r', h_align='c', delemeter = ' | ', joint_delemeter = '-+-'): def draw_table(table, rowsty=None, colsty=None, colsz = None):
# calculate max lengths. def justify(s, align, width):
num_columns = len(table[0]) if align == 'c':
def cvt_align(align, num_columns): s = s.center(width)
if type(align) is str: elif align == 'r':
if len(align) == 1: s = s.rjust(width)
return [align] * num_columns elif align == 'l':
elif len(align) == num_columns: s = s.ljust(width)
return list(align) return s
else:
raise ValueError('align flag length mismatch') num_cols = len(table[0])
else: if rowsty is None:
return align rowsty = '|' + '|'.join(['c']*len(table)) + '|'
c_align = cvt_align(c_align, num_columns) if colsty is None:
h_align = cvt_align(h_align, num_columns) colsty = '|' + '|'.join(['c']*num_cols) + '|'
max_lengths = [0] * num_columns # check tables.
for row in table:
if len(row) != num_cols:
raise ValueError('different cols!')
col_width = [0] * num_cols
if colsz is None:
colsz = [None] * num_cols
# collect widths.
for row in table: for row in table:
for i, col in enumerate(row): for i, col in enumerate(row):
if len(col) > max_lengths[i]: col = str(col)
max_lengths[i] = len(col) width = len(col)
width = sum(max_lengths) + num_columns * len(delemeter) + 1 if colsz[i] is not None and colsz[i] < width:
hline = '+' width = colsz[i]
hline += joint_delemeter.join(['-' * length for length in max_lengths]) if width > col_width[i]:
hline += '+\n' col_width[i] = width
info = hline # prepare vline.
for i, row in enumerate(table): vline = []
info += '|' colaligns = []
row_just = [] col_pos = 0
align = h_align if i <= header_line else c_align delemeter = ' '
for w, col, a in zip(max_lengths, row, align): for ch in colsty:
if a == 'c': if ch == '|':
row_just.append(col.center(w)) vline.append('+')
elif a == 'l': elif ch in ['c', 'l', 'r']:
row_just.append(col.ljust(w)) colaligns.append(ch)
elif a == 'r': vline.append('-' * col_width[col_pos])
row_just.append(col.rjust(w)) col_pos += 1
info += delemeter.join(row_just) vline = delemeter.join(vline)
info += '|\n' table_to_draw = []
if i == header_line: row_pos = 0
info += hline for ch in rowsty:
info += hline if ch == '|':
return info table_to_draw.append("vline")
elif ch in ['c', 'l', 'r']:
table_to_draw.append(table[row_pos])
row_pos += 1;
strings = []
for row in table_to_draw:
if type(row) is str:
strings.append(vline)
continue
new_row = []
max_cols = 1
for word, align, width in zip(row, colaligns, col_width):
cols = []
lines = word.split('\n')
for line in lines:
while len(line) > 0:
cols.append(line[:width])
line = line[width:]
cols = [justify(col, align, width) for col in cols]
if len(cols) > max_cols:
max_cols = len(cols)
new_row.append(cols)
for cols, width in zip(new_row, col_width):
empty = ' ' * width
while len(cols) < max_cols:
cols.append(empty)
rows = list(zip(*new_row))
for row in rows:
cols_to_drawn = []
col_pos = 0
for ch in colsty:
if ch == '|':
cols_to_drawn.append('|')
elif ch in ['c', 'r', 'l']:
cols_to_drawn.append(row[col_pos])
col_pos += 1
strings.append(delemeter.join(cols_to_drawn))
return '\n'.join(strings)
class GPUStat(): class GPUStat():
def __init__(self): def __init__(self):
@ -255,7 +298,7 @@ class GPUStat():
gpu['id'] = i gpu['id'] = i
self.gpus.append(gpu) self.gpus.append(gpu)
def show(self, enabled_cols = ['ID', 'Fan', 'Temp', 'Pwr', 'Freq', 'Util', 'Vmem', 'Users'], show_command=True): def show(self, enabled_cols = ['ID', 'Fan', 'Temp', 'Pwr', 'Freq', 'Util', 'Vmem', 'Users'], colsty=None, colsz=None, show_command=True):
self.parse() self.parse()
gpu_infos = [] gpu_infos = []
# stats = { # stats = {
@ -273,11 +316,20 @@ class GPUStat():
# "mem_free": stat['memory']['free'].split(' ')[0].strip() # "mem_free": stat['memory']['free'].split(' ')[0].strip()
# } # }
for gpu in self.gpus: for gpu in self.gpus:
process_fmt = '{user}({pid})' # process_fmt = '{user}({pid})'
process_info = ','.join([process_fmt.format( # process_info = ','.join([process_fmt.format(
user = proc['user'], # user = proc['user'],
# pid = proc['pid']
# ) for proc in gpu['processes']])
process_fmt = '{user}({pids})'
users_process = {}
for proc in gpu['processes']:
user = proc['user']
pid = proc['pid'] pid = proc['pid']
) for proc in gpu['processes']]) if user not in users_process:
users_process[user] = []
users_process[user].append(pid)
process_info = ','.join(process_fmt.format(user=user, pids = '|'.join(users_process[user])) for user in users_process)
info_gpu = { info_gpu = {
'ID': '{0}'.format(str(gpu['id'])), 'ID': '{0}'.format(str(gpu['id'])),
'Fan': '{0} %'.format(gpu['fan_speed'].split(' ')[0].strip()), 'Fan': '{0} %'.format(gpu['fan_speed'].split(' ')[0].strip()),
@ -307,7 +359,7 @@ class GPUStat():
for info in gpu_infos: for info in gpu_infos:
this_row = [info[key] for key in enabled_cols] this_row = [info[key] for key in enabled_cols]
info_table.append(this_row) info_table.append(this_row)
info = draw_table(info_table, header_line=0, delemeter=' | ', joint_delemeter='-+-', c_align=c_align) info = draw_table(info_table, rowsty='|c|{0}|'.format('c'*(len(info_table)-1)), colsty=colsty, colsz=colsz)
if show_command: if show_command:
procs = {} procs = {}
for gpu in self.gpus: for gpu in self.gpus: