mirror of
https://github.com/nicolargo/glances.git
synced 2024-12-23 01:01:31 +03:00
First version but UI should be improved and when user is in program mode, it did not work...
This commit is contained in:
parent
8bdbed3331
commit
9614e2bb19
@ -236,6 +236,13 @@ Examples of use:
|
||||
dest='enable_separator',
|
||||
help='enable separator in the UI',
|
||||
),
|
||||
parser.add_argument(
|
||||
'--disable-cursor',
|
||||
action='store_true',
|
||||
default=False,
|
||||
dest='disable_cursor',
|
||||
help='disable cursor (process selection) in the UI',
|
||||
),
|
||||
# Sort processes list
|
||||
parser.add_argument(
|
||||
'--sort-processes',
|
||||
@ -700,6 +707,11 @@ Examples of use:
|
||||
logger.critical("Process filter is only available in standalone mode")
|
||||
sys.exit(2)
|
||||
|
||||
# Cursor option is only available in standalone mode
|
||||
if not args.disable_cursor and not self.is_standalone():
|
||||
logger.critical("Cursor is only available in standalone mode")
|
||||
sys.exit(2)
|
||||
|
||||
# Disable HDDTemp if sensors are disabled
|
||||
if getattr(self.args, 'disable_sensors', False):
|
||||
disable(self.args, 'hddtemp')
|
||||
|
@ -414,6 +414,8 @@ class _GlancesCurses(object):
|
||||
glances_processes.disable_extended()
|
||||
else:
|
||||
glances_processes.enable_extended()
|
||||
# When a process is selected (and only in standalone mode), disable the cursor
|
||||
self.args.disable_cursor = self.args.enable_process_extended and self.args.is_standalone
|
||||
elif self.pressedkey == ord('E'):
|
||||
# 'E' > Erase the process filter
|
||||
glances_processes.process_filter = None
|
||||
@ -451,11 +453,11 @@ class _GlancesCurses(object):
|
||||
# ">" (right arrow) navigation through process sort
|
||||
next_sort = (self.loop_position() + 1) % len(self._sort_loop)
|
||||
glances_processes.set_sort_key(self._sort_loop[next_sort], False)
|
||||
elif self.pressedkey == curses.KEY_UP or self.pressedkey == 65:
|
||||
elif self.pressedkey == curses.KEY_UP or self.pressedkey == 65 and not self.args.disable_cursor:
|
||||
# 'UP' > Up in the server list
|
||||
if self.args.cursor_position > 0:
|
||||
self.args.cursor_position -= 1
|
||||
elif self.pressedkey == curses.KEY_DOWN or self.pressedkey == 66:
|
||||
elif self.pressedkey == curses.KEY_DOWN or self.pressedkey == 66 and not self.args.disable_cursor:
|
||||
# 'DOWN' > Down in the server list
|
||||
# if self.args.cursor_position < glances_processes.max_processes - 2:
|
||||
if self.args.cursor_position < glances_processes.processes_count:
|
||||
|
@ -353,11 +353,11 @@ class Plugin(GlancesPlugin):
|
||||
- selected is a tag=True if p is the selected process
|
||||
"""
|
||||
ret = [self.curse_new_line()]
|
||||
|
||||
# When a process is selected:
|
||||
# * display a special character at the beginning of the line
|
||||
# * underline the command name
|
||||
if args.is_standalone:
|
||||
ret.append(self.curse_add_line(unicode_message('PROCESS_SELECTOR') if selected else ' ', 'SELECTED'))
|
||||
ret.append(self.curse_add_line(unicode_message('PROCESS_SELECTOR') if (selected and not args.disable_cursor) else ' ', 'SELECTED'))
|
||||
|
||||
# CPU
|
||||
ret.append(self._get_process_curses_cpu(p, selected, args))
|
||||
@ -404,7 +404,7 @@ class Plugin(GlancesPlugin):
|
||||
cmdline = p.get('cmdline', '?')
|
||||
|
||||
try:
|
||||
process_decoration = 'PROCESS_SELECTED' if (selected and args.is_standalone) else 'PROCESS'
|
||||
process_decoration = 'PROCESS_SELECTED' if (selected and not args.disable_cursor) else 'PROCESS'
|
||||
if cmdline:
|
||||
path, cmd, arguments = split_cmdline(bare_process_name, cmdline)
|
||||
# Manage end of line in arguments (see #1692)
|
||||
@ -428,74 +428,14 @@ class Plugin(GlancesPlugin):
|
||||
logger.debug("Can not decode command line '{}' ({})".format(cmdline, e))
|
||||
ret.append(self.curse_add_line('', splittable=True))
|
||||
|
||||
# Add extended stats but only for the top processes
|
||||
if args.cursor_position == 0 and 'extended_stats' in p and args.enable_process_extended:
|
||||
# Left padding
|
||||
xpad = ' ' * 13
|
||||
# First line is CPU affinity
|
||||
if 'cpu_affinity' in p and p['cpu_affinity'] is not None:
|
||||
ret.append(self.curse_new_line())
|
||||
msg = xpad + 'CPU affinity: ' + str(len(p['cpu_affinity'])) + ' cores'
|
||||
ret.append(self.curse_add_line(msg, splittable=True))
|
||||
# Second line is memory info
|
||||
if 'memory_info' in p and p['memory_info'] is not None:
|
||||
ret.append(self.curse_new_line())
|
||||
msg = '{}Memory info: {}'.format(xpad, p['memory_info'])
|
||||
if 'memory_swap' in p and p['memory_swap'] is not None:
|
||||
msg += ' swap ' + self.auto_unit(p['memory_swap'], low_precision=False)
|
||||
ret.append(self.curse_add_line(msg, splittable=True))
|
||||
# Third line is for open files/network sessions
|
||||
msg = ''
|
||||
if 'num_threads' in p and p['num_threads'] is not None:
|
||||
msg += str(p['num_threads']) + ' threads '
|
||||
if 'num_fds' in p and p['num_fds'] is not None:
|
||||
msg += str(p['num_fds']) + ' files '
|
||||
if 'num_handles' in p and p['num_handles'] is not None:
|
||||
msg += str(p['num_handles']) + ' handles '
|
||||
if 'tcp' in p and p['tcp'] is not None:
|
||||
msg += str(p['tcp']) + ' TCP '
|
||||
if 'udp' in p and p['udp'] is not None:
|
||||
msg += str(p['udp']) + ' UDP'
|
||||
if msg != '':
|
||||
ret.append(self.curse_new_line())
|
||||
msg = xpad + 'Open: ' + msg
|
||||
ret.append(self.curse_add_line(msg, splittable=True))
|
||||
# Fourth line is IO nice level (only Linux and Windows OS)
|
||||
if 'ionice' in p and p['ionice'] is not None and hasattr(p['ionice'], 'ioclass'):
|
||||
ret.append(self.curse_new_line())
|
||||
msg = xpad + 'IO nice: '
|
||||
k = 'Class is '
|
||||
v = p['ionice'].ioclass
|
||||
# Linux: The scheduling class. 0 for none, 1 for real time, 2 for best-effort, 3 for idle.
|
||||
# Windows: On Windows only ioclass is used and it can be set to 2 (normal), 1 (low) or 0 (very low).
|
||||
if WINDOWS:
|
||||
if v == 0:
|
||||
msg += k + 'Very Low'
|
||||
elif v == 1:
|
||||
msg += k + 'Low'
|
||||
elif v == 2:
|
||||
msg += 'No specific I/O priority'
|
||||
else:
|
||||
msg += k + str(v)
|
||||
else:
|
||||
if v == 0:
|
||||
msg += 'No specific I/O priority'
|
||||
elif v == 1:
|
||||
msg += k + 'Real Time'
|
||||
elif v == 2:
|
||||
msg += k + 'Best Effort'
|
||||
elif v == 3:
|
||||
msg += k + 'IDLE'
|
||||
else:
|
||||
msg += k + str(v)
|
||||
# value is a number which goes from 0 to 7.
|
||||
# The higher the value, the lower the I/O priority of the process.
|
||||
if hasattr(p['ionice'], 'value') and p['ionice'].value != 0:
|
||||
msg += ' (value %s/7)' % str(p['ionice'].value)
|
||||
ret.append(self.curse_add_line(msg, splittable=True))
|
||||
|
||||
return ret
|
||||
|
||||
def is_selected_process(self, args):
|
||||
return args.is_standalone and \
|
||||
self.args.enable_process_extended and \
|
||||
args.cursor_position is not None and \
|
||||
glances_processes.extended_process is not None
|
||||
|
||||
def msg_curse(self, args=None, max_width=None):
|
||||
"""Return the dict to display in the curse interface."""
|
||||
# Init the return message
|
||||
@ -507,6 +447,16 @@ class Plugin(GlancesPlugin):
|
||||
|
||||
# Compute the sort key
|
||||
process_sort_key = glances_processes.sort_key
|
||||
processes_list_sorted = self.__sort_stats(process_sort_key)
|
||||
|
||||
# Display extended stats for selected process
|
||||
#############################################
|
||||
|
||||
if self.is_selected_process(args):
|
||||
self.__msg_curse_extended_process(ret, glances_processes.extended_process)
|
||||
|
||||
# Display others processes list
|
||||
###############################
|
||||
|
||||
# Header
|
||||
self.__msg_curse_header(ret, process_sort_key, args)
|
||||
@ -515,10 +465,10 @@ class Plugin(GlancesPlugin):
|
||||
# Loop over processes (sorted by the sort key previously compute)
|
||||
# This is a Glances bottleneck (see flame graph),
|
||||
# get_process_curses_data should be optimzed
|
||||
i = 0
|
||||
for p in self.__sort_stats(process_sort_key):
|
||||
ret.extend(self.get_process_curses_data(p, i == args.cursor_position, args))
|
||||
i += 1
|
||||
for position, process in enumerate(processes_list_sorted):
|
||||
ret.extend(self.get_process_curses_data(process,
|
||||
position == args.cursor_position,
|
||||
args))
|
||||
|
||||
# A filter is set Display the stats summaries
|
||||
if glances_processes.process_filter is not None:
|
||||
@ -532,6 +482,95 @@ class Plugin(GlancesPlugin):
|
||||
# Return the message with decoration
|
||||
return ret
|
||||
|
||||
def __msg_curse_extended_process(self, ret, p):
|
||||
"""Get extended curses data tfor the selected process (see issue #2225)
|
||||
p: the process dict
|
||||
{'status': 'S',
|
||||
'memory_info': pmem(rss=466890752, vms=3365347328, shared=68153344, text=659456, lib=0, data=774647808, dirty=0),
|
||||
'pid': 4980,
|
||||
'io_counters': [165385216, 0, 165385216, 0, 1],
|
||||
'num_threads': 20,
|
||||
'nice': 0,
|
||||
'memory_percent': 5.958135664449709,
|
||||
'cpu_percent': 0.0,
|
||||
'gids': pgids(real=1000, effective=1000, saved=1000),
|
||||
'cpu_times': pcputimes(user=696.38, system=119.98, children_user=0.0, children_system=0.0, iowait=0.0),
|
||||
'name': 'WebExtensions',
|
||||
'key': 'pid',
|
||||
'time_since_update': 2.1997854709625244,
|
||||
'cmdline': ['/snap/firefox/2154/usr/lib/firefox/firefox', '-contentproc', '-childID', '...'],
|
||||
'username': 'nicolargo'}
|
||||
"""
|
||||
# Title
|
||||
msg = 'Selected {} {}'.format('program' if self.args.programs else 'thread',
|
||||
p['name'])
|
||||
ret.append(self.curse_add_line(msg, "TITLE"))
|
||||
|
||||
# First line is CPU affinity
|
||||
if 'cpu_affinity' in p and p['cpu_affinity'] is not None:
|
||||
ret.append(self.curse_new_line())
|
||||
msg = 'CPU affinity: ' + str(len(p['cpu_affinity'])) + ' cores'
|
||||
ret.append(self.curse_add_line(msg, splittable=True))
|
||||
# Second line is memory info
|
||||
if 'memory_info' in p and p['memory_info'] is not None:
|
||||
ret.append(self.curse_new_line())
|
||||
msg = 'Memory info: {}'.format(p['memory_info'])
|
||||
if 'memory_swap' in p and p['memory_swap'] is not None:
|
||||
msg += ' swap ' + self.auto_unit(p['memory_swap'], low_precision=False)
|
||||
ret.append(self.curse_add_line(msg, splittable=True))
|
||||
# Third line is for open files/network sessions
|
||||
msg = ''
|
||||
if 'num_threads' in p and p['num_threads'] is not None:
|
||||
msg += str(p['num_threads']) + ' threads '
|
||||
if 'num_fds' in p and p['num_fds'] is not None:
|
||||
msg += str(p['num_fds']) + ' files '
|
||||
if 'num_handles' in p and p['num_handles'] is not None:
|
||||
msg += str(p['num_handles']) + ' handles '
|
||||
if 'tcp' in p and p['tcp'] is not None:
|
||||
msg += str(p['tcp']) + ' TCP '
|
||||
if 'udp' in p and p['udp'] is not None:
|
||||
msg += str(p['udp']) + ' UDP'
|
||||
if msg != '':
|
||||
ret.append(self.curse_new_line())
|
||||
msg = 'Open: ' + msg
|
||||
ret.append(self.curse_add_line(msg, splittable=True))
|
||||
# Fourth line is IO nice level (only Linux and Windows OS)
|
||||
if 'ionice' in p and p['ionice'] is not None and hasattr(p['ionice'], 'ioclass'):
|
||||
ret.append(self.curse_new_line())
|
||||
msg = 'IO nice: '
|
||||
k = 'Class is '
|
||||
v = p['ionice'].ioclass
|
||||
# Linux: The scheduling class. 0 for none, 1 for real time, 2 for best-effort, 3 for idle.
|
||||
# Windows: On Windows only ioclass is used and it can be set to 2 (normal), 1 (low) or 0 (very low).
|
||||
if WINDOWS:
|
||||
if v == 0:
|
||||
msg += k + 'Very Low'
|
||||
elif v == 1:
|
||||
msg += k + 'Low'
|
||||
elif v == 2:
|
||||
msg += 'No specific I/O priority'
|
||||
else:
|
||||
msg += k + str(v)
|
||||
else:
|
||||
if v == 0:
|
||||
msg += 'No specific I/O priority'
|
||||
elif v == 1:
|
||||
msg += k + 'Real Time'
|
||||
elif v == 2:
|
||||
msg += k + 'Best Effort'
|
||||
elif v == 3:
|
||||
msg += k + 'IDLE'
|
||||
else:
|
||||
msg += k + str(v)
|
||||
# value is a number which goes from 0 to 7.
|
||||
# The higher the value, the lower the I/O priority of the process.
|
||||
if hasattr(p['ionice'], 'value') and p['ionice'].value != 0:
|
||||
msg += ' (value %s/7)' % str(p['ionice'].value)
|
||||
ret.append(self.curse_add_line(msg, splittable=True))
|
||||
|
||||
ret.append(self.curse_new_line())
|
||||
ret.append(self.curse_new_line())
|
||||
|
||||
def __msg_curse_header(self, ret, process_sort_key, args=None):
|
||||
"""Build the header and add it to the ret dict."""
|
||||
sort_style = 'SORT'
|
||||
|
@ -37,6 +37,10 @@ class GlancesProcesses(object):
|
||||
|
||||
def __init__(self, cache_timeout=60):
|
||||
"""Init the class to collect stats about processes."""
|
||||
# Init the args, coming from the GlancesStandalone class
|
||||
# Should be set by the set_args method
|
||||
self.args = None
|
||||
|
||||
# Add internals caches because psutil do not cache all the stats
|
||||
# See: https://github.com/giampaolo/psutil/issues/462
|
||||
self.username_cache = {}
|
||||
@ -70,6 +74,7 @@ class GlancesProcesses(object):
|
||||
|
||||
# Extended stats for top process is enable by default
|
||||
self.disable_extended_tag = False
|
||||
self.extended_process = None
|
||||
|
||||
# Test if the system can grab io_counters
|
||||
try:
|
||||
@ -109,6 +114,10 @@ class GlancesProcesses(object):
|
||||
self._max_values = {}
|
||||
self.reset_max_values()
|
||||
|
||||
def set_args(self, args):
|
||||
"""Set args."""
|
||||
self.args = args
|
||||
|
||||
def reset_processcount(self):
|
||||
"""Reset the global process count"""
|
||||
self.processcount = {'total': 0, 'running': 0, 'sleeping': 0, 'thread': 0, 'pid_max': None}
|
||||
@ -247,6 +256,64 @@ class GlancesProcesses(object):
|
||||
for k in self._max_values_list:
|
||||
self._max_values[k] = 0.0
|
||||
|
||||
def get_extended_stats(self, proc):
|
||||
"""Get the extended stats for the given PID."""
|
||||
# - cpu_affinity (Linux, Windows, FreeBSD)
|
||||
# - ionice (Linux and Windows > Vista)
|
||||
# - num_ctx_switches (not available on Illumos/Solaris)
|
||||
# - num_fds (Unix-like)
|
||||
# - num_handles (Windows)
|
||||
# - memory_maps (only swap, Linux)
|
||||
# https://www.cyberciti.biz/faq/linux-which-process-is-using-swap/
|
||||
# - connections (TCP and UDP)
|
||||
ret = {}
|
||||
try:
|
||||
selected_process = psutil.Process(proc['pid'])
|
||||
extended_stats = ['cpu_affinity', 'ionice', 'num_ctx_switches']
|
||||
if LINUX:
|
||||
# num_fds only available on Unix system (see issue #1351)
|
||||
extended_stats += ['num_fds']
|
||||
if WINDOWS:
|
||||
extended_stats += ['num_handles']
|
||||
|
||||
# Get the extended stats
|
||||
ret = selected_process.as_dict(attrs=extended_stats, ad_value=None)
|
||||
|
||||
if LINUX:
|
||||
try:
|
||||
ret['memory_swap'] = sum([v.swap for v in selected_process.memory_maps()])
|
||||
except (psutil.NoSuchProcess, KeyError):
|
||||
# (KeyError catch for issue #1551)
|
||||
pass
|
||||
except (psutil.AccessDenied, NotImplementedError):
|
||||
# NotImplementedError: /proc/${PID}/smaps file doesn't exist
|
||||
# on kernel < 2.6.14 or CONFIG_MMU kernel configuration option
|
||||
# is not enabled (see psutil #533/glances #413).
|
||||
ret['memory_swap'] = None
|
||||
try:
|
||||
ret['tcp'] = len(selected_process.connections(kind="tcp"))
|
||||
ret['udp'] = len(selected_process.connections(kind="udp"))
|
||||
except (psutil.AccessDenied, psutil.NoSuchProcess):
|
||||
# Manage issue1283 (psutil.AccessDenied)
|
||||
ret['tcp'] = None
|
||||
ret['udp'] = None
|
||||
except (psutil.NoSuchProcess, ValueError, AttributeError) as e:
|
||||
logger.error('Can not grab extended stats ({})'.format(e))
|
||||
ret['extended_stats'] = False
|
||||
self.extended_process = None
|
||||
else:
|
||||
logger.debug('Grab extended stats for process {}'.format(proc['pid']))
|
||||
ret['extended_stats'] = True
|
||||
return ret
|
||||
|
||||
def is_selected_process(self, position):
|
||||
"""Return True if the process is the selected one."""
|
||||
return self.args.enable_process_extended and \
|
||||
not self.disable_extended_tag and \
|
||||
hasattr(self.args, 'cursor_position') and \
|
||||
position == self.args.cursor_position and \
|
||||
not self.args.disable_cursor
|
||||
|
||||
def update(self):
|
||||
"""Update the processes stats."""
|
||||
# Reset the stats
|
||||
@ -303,59 +370,26 @@ class GlancesProcesses(object):
|
||||
# Update the processcount
|
||||
self.update_processcount(self.processlist)
|
||||
|
||||
# Loop over processes and add metadata
|
||||
first = True
|
||||
for proc in self.processlist:
|
||||
# Get extended stats, only for top processes (see issue #403).
|
||||
if first and not self.disable_extended_tag:
|
||||
# - cpu_affinity (Linux, Windows, FreeBSD)
|
||||
# - ionice (Linux and Windows > Vista)
|
||||
# - num_ctx_switches (not available on Illumos/Solaris)
|
||||
# - num_fds (Unix-like)
|
||||
# - num_handles (Windows)
|
||||
# - memory_maps (only swap, Linux)
|
||||
# https://www.cyberciti.biz/faq/linux-which-process-is-using-swap/
|
||||
# - connections (TCP and UDP)
|
||||
extended = {}
|
||||
try:
|
||||
top_process = psutil.Process(proc['pid'])
|
||||
extended_stats = ['cpu_affinity', 'ionice', 'num_ctx_switches']
|
||||
if LINUX:
|
||||
# num_fds only available on Unix system (see issue #1351)
|
||||
extended_stats += ['num_fds']
|
||||
if WINDOWS:
|
||||
extended_stats += ['num_handles']
|
||||
# Loop over processes and :
|
||||
# - add extended stats for selected process
|
||||
# - add metadata
|
||||
for position, proc in enumerate(self.processlist):
|
||||
# Extended stats
|
||||
################
|
||||
|
||||
# Get the extended stats
|
||||
extended = top_process.as_dict(attrs=extended_stats, ad_value=None)
|
||||
# Get the selected process
|
||||
if self.is_selected_process(position):
|
||||
# logger.info('Selected process: {}'.format(proc))
|
||||
self.extended_process = proc
|
||||
|
||||
if LINUX:
|
||||
try:
|
||||
extended['memory_swap'] = sum([v.swap for v in top_process.memory_maps()])
|
||||
except (psutil.NoSuchProcess, KeyError):
|
||||
# (KeyError catch for issue #1551)
|
||||
pass
|
||||
except (psutil.AccessDenied, NotImplementedError):
|
||||
# NotImplementedError: /proc/${PID}/smaps file doesn't exist
|
||||
# on kernel < 2.6.14 or CONFIG_MMU kernel configuration option
|
||||
# is not enabled (see psutil #533/glances #413).
|
||||
extended['memory_swap'] = None
|
||||
try:
|
||||
extended['tcp'] = len(top_process.connections(kind="tcp"))
|
||||
extended['udp'] = len(top_process.connections(kind="udp"))
|
||||
except (psutil.AccessDenied, psutil.NoSuchProcess):
|
||||
# Manage issue1283 (psutil.AccessDenied)
|
||||
extended['tcp'] = None
|
||||
extended['udp'] = None
|
||||
except (psutil.NoSuchProcess, ValueError, AttributeError) as e:
|
||||
logger.error('Can not grab extended stats ({})'.format(e))
|
||||
extended['extended_stats'] = False
|
||||
else:
|
||||
logger.debug('Grab extended stats for process {}'.format(proc['pid']))
|
||||
extended['extended_stats'] = True
|
||||
proc.update(extended)
|
||||
first = False
|
||||
# /End of extended stats
|
||||
# Grab extended stats only for the selected process (see issue #2225)
|
||||
if self.extended_process is not None and \
|
||||
proc['pid'] == self.extended_process['pid']:
|
||||
self.extended_process = proc
|
||||
proc.update(self.get_extended_stats(self.extended_process))
|
||||
|
||||
# Meta data
|
||||
###########
|
||||
|
||||
# PID is the key
|
||||
proc['key'] = 'pid'
|
||||
|
@ -51,6 +51,9 @@ class GlancesStandalone(object):
|
||||
self.display_modules_list()
|
||||
sys.exit(0)
|
||||
|
||||
# The args is needed to get the selected process in the process list (Curses mode)
|
||||
glances_processes.set_args(args)
|
||||
|
||||
# If process extended stats is disabled by user
|
||||
if not args.enable_process_extended:
|
||||
logger.debug("Extended stats for top process are disabled")
|
||||
|
Loading…
Reference in New Issue
Block a user