First version but only for the TUI standalone mode

This commit is contained in:
nicolargo 2024-11-10 20:00:40 +01:00
parent e1cd3ded23
commit 34520ca45b
8 changed files with 153 additions and 89 deletions

View File

@ -410,6 +410,10 @@ disable=False
# Should be one of the following: # Should be one of the following:
# cpu_percent, memory_percent, io_counters, name, cpu_times, username # cpu_percent, memory_percent, io_counters, name, cpu_times, username
#sort_key=memory_percent #sort_key=memory_percent
# List of stats to disable (not grabed and not display)
# Stats that can be disabled: cpu_percent,memory_info,memory_percent,username,cpu_times,num_threads,nice,status,io_counters,cmdline
# Stats that can not be disable: pid,name
#disable_stats=cpu_percent,memory_info,memory_percent,username,cpu_times,num_threads,nice,status,io_counters,cmdline
# Define CPU/MEM (per process) thresholds in % # Define CPU/MEM (per process) thresholds in %
# Default values if not defined: 50/70/90 # Default values if not defined: 50/70/90
cpu_careful=50 cpu_careful=50

View File

@ -18,7 +18,7 @@ from glances import __apiversion__, __version__, psutil_version
from glances.config import Config from glances.config import Config
from glances.globals import WINDOWS, disable, enable from glances.globals import WINDOWS, disable, enable
from glances.logger import LOG_FILENAME, logger from glances.logger import LOG_FILENAME, logger
from glances.processes import sort_processes_key_list from glances.processes import sort_processes_stats_list
class GlancesMain: class GlancesMain:
@ -269,8 +269,8 @@ Examples of use:
parser.add_argument( parser.add_argument(
'--sort-processes', '--sort-processes',
dest='sort_processes_key', dest='sort_processes_key',
choices=sort_processes_key_list, choices=sort_processes_stats_list,
help='Sort processes by: {}'.format(', '.join(sort_processes_key_list)), help='Sort processes by: {}'.format(', '.join(sort_processes_stats_list)),
) )
# Display processes list by program name and not by thread # Display processes list by program name and not by thread
parser.add_argument( parser.add_argument(

View File

@ -16,7 +16,7 @@ from glances.globals import MACOS, WINDOWS, disable, enable, itervalues, natives
from glances.logger import logger from glances.logger import logger
from glances.outputs.glances_colors import GlancesColors from glances.outputs.glances_colors import GlancesColors
from glances.outputs.glances_unicode import unicode_message from glances.outputs.glances_unicode import unicode_message
from glances.processes import glances_processes, sort_processes_key_list from glances.processes import glances_processes, sort_processes_stats_list
from glances.timer import Timer from glances.timer import Timer
# Import curses library for "normal" operating system # Import curses library for "normal" operating system
@ -97,7 +97,7 @@ class _GlancesCurses:
# 'DOWN' > Down in the server list # 'DOWN' > Down in the server list
} }
_sort_loop = sort_processes_key_list _sort_loop = sort_processes_stats_list
# Define top menu # Define top menu
_top = ['quicklook', 'cpu', 'percpu', 'gpu', 'mem', 'memswap', 'load'] _top = ['quicklook', 'cpu', 'percpu', 'gpu', 'mem', 'memswap', 'load']

View File

@ -117,6 +117,22 @@ class PluginModel(GlancesPluginModel):
stats is a list stats is a list
""" """
# Default list of processes stats to be grabbed / displayed
# Can be altered by glances_processes.disable_stats
enable_stats = [
'cpu_percent',
'memory_percent',
'memory_info', # vms and rss
'pid',
'username',
'cpu_times',
'num_threads',
'nice',
'status',
'io_counters', # ior and iow
'cmdline',
]
# Define the header layout of the processes list columns # Define the header layout of the processes list columns
layout_header = { layout_header = {
'cpu': '{:<6} ', 'cpu': '{:<6} ',
@ -176,19 +192,8 @@ class PluginModel(GlancesPluginModel):
# Use to optimize space (see https://github.com/nicolargo/glances/issues/959) # Use to optimize space (see https://github.com/nicolargo/glances/issues/959)
self.pid_max = glances_processes.pid_max self.pid_max = glances_processes.pid_max
# Set the default sort key if it is defined in the configuration file # Load the config file
if config is not None and 'processlist' in config.as_dict(): self.load(args, config)
if 'sort_key' in config.as_dict()['processlist']:
logger.debug(
'Configuration overwrites processes sort key by {}'.format(
config.as_dict()['processlist']['sort_key']
)
)
glances_processes.set_sort_key(config.as_dict()['processlist']['sort_key'], False)
if 'export' in config.as_dict()['processlist']:
glances_processes.export_process_filter = config.as_dict()['processlist']['export']
if args.export:
logger.info("Export process filter is set to: {}".format(config.as_dict()['processlist']['export']))
# The default sort key could also be overwrite by command line (see #1903) # The default sort key could also be overwrite by command line (see #1903)
if args and args.sort_processes_key is not None: if args and args.sort_processes_key is not None:
@ -196,6 +201,27 @@ class PluginModel(GlancesPluginModel):
# Note: 'glances_processes' is already init in the processes.py script # Note: 'glances_processes' is already init in the processes.py script
def load(self, args, config):
# Set the default sort key if it is defined in the configuration file
if config is None or 'processlist' not in config.as_dict():
return
if 'sort_key' in config.as_dict()['processlist']:
logger.debug(
'Configuration overwrites processes sort key by {}'.format(config.as_dict()['processlist']['sort_key'])
)
glances_processes.set_sort_key(config.as_dict()['processlist']['sort_key'], False)
if 'export' in config.as_dict()['processlist']:
glances_processes.export_process_filter = config.as_dict()['processlist']['export']
if args.export:
logger.info("Export process filter is set to: {}".format(config.as_dict()['processlist']['export']))
if 'disable_stats' in config.as_dict()['processlist']:
logger.info(
'Followings processes stats wil not be displayed: {}'.format(
config.as_dict()['processlist']['disable_stats']
)
)
glances_processes.disable_stats = config.as_dict()['processlist']['disable_stats'].split(',')
def get_key(self): def get_key(self):
"""Return the key of the list.""" """Return the key of the list."""
return 'pid' return 'pid'
@ -255,7 +281,7 @@ class PluginModel(GlancesPluginModel):
pass pass
return 'DEFAULT' return 'DEFAULT'
def _get_process_curses_cpu(self, p, selected, args): def _get_process_curses_cpu_percent(self, p, selected, args):
"""Return process CPU curses""" """Return process CPU curses"""
if key_exist_value_not_none_not_v('cpu_percent', p, ''): if key_exist_value_not_none_not_v('cpu_percent', p, ''):
cpu_layout = self.layout_stat['cpu'] if p['cpu_percent'] < 100 else self.layout_stat['cpu_no_digit'] cpu_layout = self.layout_stat['cpu'] if p['cpu_percent'] < 100 else self.layout_stat['cpu_no_digit']
@ -275,7 +301,7 @@ class PluginModel(GlancesPluginModel):
ret = self.curse_add_line(msg) ret = self.curse_add_line(msg)
return ret return ret
def _get_process_curses_mem(self, p, selected, args): def _get_process_curses_memory_percent(self, p, selected, args):
"""Return process MEM curses""" """Return process MEM curses"""
if key_exist_value_not_none_not_v('memory_percent', p, ''): if key_exist_value_not_none_not_v('memory_percent', p, ''):
msg = self.layout_stat['mem'].format(p['memory_percent']) msg = self.layout_stat['mem'].format(p['memory_percent'])
@ -311,6 +337,12 @@ class PluginModel(GlancesPluginModel):
ret = self.curse_add_line(msg) ret = self.curse_add_line(msg)
return ret return ret
def _get_process_curses_memory_info(self, p, selected, args):
return [
self._get_process_curses_vms(p, selected, args),
self._get_process_curses_rss(p, selected, args),
]
def _get_process_curses_pid(self, p, selected, args): def _get_process_curses_pid(self, p, selected, args):
"""Return process PID curses""" """Return process PID curses"""
if not self.args.programs: if not self.args.programs:
@ -334,7 +366,7 @@ class PluginModel(GlancesPluginModel):
msg = self.layout_header['user'].format('?') msg = self.layout_header['user'].format('?')
return self.curse_add_line(msg) return self.curse_add_line(msg)
def _get_process_curses_time(self, p, selected, args): def _get_process_curses_cpu_times(self, p, selected, args):
"""Return process time curses""" """Return process time curses"""
cpu_times = p['cpu_times'] cpu_times = p['cpu_times']
try: try:
@ -365,7 +397,7 @@ class PluginModel(GlancesPluginModel):
return self.curse_add_line(msg, optional=True) return self.curse_add_line(msg, optional=True)
def _get_process_curses_thread(self, p, selected, args): def _get_process_curses_num_threads(self, p, selected, args):
"""Return process thread curses""" """Return process thread curses"""
if 'num_threads' in p: if 'num_threads' in p:
num_threads = p['num_threads'] num_threads = p['num_threads']
@ -422,14 +454,14 @@ class PluginModel(GlancesPluginModel):
ret = self.curse_add_line(msg, optional=True, additional=True) ret = self.curse_add_line(msg, optional=True, additional=True)
return ret return ret
def _get_process_curses_io(self, p, selected, args): def _get_process_curses_io_counters(self, p, selected, args):
return [ return [
self._get_process_curses_io_read_write(p, selected, args, rorw='ior'), self._get_process_curses_io_read_write(p, selected, args, rorw='ior'),
self._get_process_curses_io_read_write(p, selected, args, rorw='iow'), self._get_process_curses_io_read_write(p, selected, args, rorw='iow'),
] ]
def _get_process_curses_command(self, p, selected, args): def _get_process_curses_cmdline(self, p, selected, args):
"""Return process command curses""" """Return process cmdline curses"""
ret = [] ret = []
# If no command line for the process is available, fallback to the bare process name instead # If no command line for the process is available, fallback to the bare process name instead
bare_process_name = p['name'] bare_process_name = p['name']
@ -476,20 +508,7 @@ class PluginModel(GlancesPluginModel):
) )
) )
for stat in [ for stat in [i for i in self.enable_stats if i not in glances_processes.disable_stats]:
'cpu',
'mem',
'vms',
'rss',
'pid',
'username',
'time',
'thread',
'nice',
'status',
'io',
'command',
]:
msg = getattr(self, f'_get_process_curses_{stat}')(p, selected, args) msg = getattr(self, f'_get_process_curses_{stat}')(p, selected, args)
if isinstance(msg, list): if isinstance(msg, list):
# ex: _get_process_curses_command return a list, so extend # ex: _get_process_curses_command return a list, so extend
@ -718,48 +737,61 @@ class PluginModel(GlancesPluginModel):
"""Build the header and add it to the ret dict.""" """Build the header and add it to the ret dict."""
sort_style = 'SORT' sort_style = 'SORT'
if args.disable_irix and 0 < self.nb_log_core < 10: display_stats = [i for i in self.enable_stats if i not in glances_processes.disable_stats]
msg = self.layout_header['cpu'].format('CPU%/' + str(self.nb_log_core))
elif args.disable_irix and self.nb_log_core != 0: if 'cpu_percent' in display_stats:
msg = self.layout_header['cpu'].format('CPU%/C') if args.disable_irix and 0 < self.nb_log_core < 10:
else: msg = self.layout_header['cpu'].format('CPU%/' + str(self.nb_log_core))
msg = self.layout_header['cpu'].format('CPU%') elif args.disable_irix and self.nb_log_core != 0:
ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'cpu_percent' else 'DEFAULT')) msg = self.layout_header['cpu'].format('CPU%/C')
msg = self.layout_header['mem'].format('MEM%') else:
ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'memory_percent' else 'DEFAULT')) msg = self.layout_header['cpu'].format('CPU%')
msg = self.layout_header['virt'].format('VIRT') ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'cpu_percent' else 'DEFAULT'))
ret.append(self.curse_add_line(msg, optional=True))
msg = self.layout_header['res'].format('RES') if 'memory_percent' in display_stats:
ret.append(self.curse_add_line(msg, optional=True)) msg = self.layout_header['mem'].format('MEM%')
if not self.args.programs: ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'memory_percent' else 'DEFAULT'))
msg = self.layout_header['pid'].format('PID', width=self.__max_pid_size()) if 'memory_info' in display_stats:
else: msg = self.layout_header['virt'].format('VIRT')
msg = self.layout_header['pid'].format('NPROCS', width=self.__max_pid_size()) ret.append(self.curse_add_line(msg, optional=True))
ret.append(self.curse_add_line(msg)) msg = self.layout_header['res'].format('RES')
msg = self.layout_header['user'].format('USER') ret.append(self.curse_add_line(msg, optional=True))
ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'username' else 'DEFAULT')) if 'pid' in display_stats:
msg = self.layout_header['time'].format('TIME+') if not self.args.programs:
ret.append( msg = self.layout_header['pid'].format('PID', width=self.__max_pid_size())
self.curse_add_line(msg, sort_style if process_sort_key == 'cpu_times' else 'DEFAULT', optional=True) else:
) msg = self.layout_header['pid'].format('NPROCS', width=self.__max_pid_size())
msg = self.layout_header['thread'].format('THR') ret.append(self.curse_add_line(msg))
ret.append(self.curse_add_line(msg)) if 'username' in display_stats:
msg = self.layout_header['nice'].format('NI') msg = self.layout_header['user'].format('USER')
ret.append(self.curse_add_line(msg)) ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'username' else 'DEFAULT'))
msg = self.layout_header['status'].format('S') if 'cpu_times' in display_stats:
ret.append(self.curse_add_line(msg)) msg = self.layout_header['time'].format('TIME+')
msg = self.layout_header['ior'].format('R/s') ret.append(
ret.append( self.curse_add_line(msg, sort_style if process_sort_key == 'cpu_times' else 'DEFAULT', optional=True)
self.curse_add_line(
msg, sort_style if process_sort_key == 'io_counters' else 'DEFAULT', optional=True, additional=True
) )
) if 'num_threads' in display_stats:
msg = self.layout_header['iow'].format('W/s') msg = self.layout_header['thread'].format('THR')
ret.append( ret.append(self.curse_add_line(msg))
self.curse_add_line( if 'nice' in display_stats:
msg, sort_style if process_sort_key == 'io_counters' else 'DEFAULT', optional=True, additional=True msg = self.layout_header['nice'].format('NI')
ret.append(self.curse_add_line(msg))
if 'status' in display_stats:
msg = self.layout_header['status'].format('S')
ret.append(self.curse_add_line(msg))
if 'io_counters' in display_stats:
msg = self.layout_header['ior'].format('R/s')
ret.append(
self.curse_add_line(
msg, sort_style if process_sort_key == 'io_counters' else 'DEFAULT', optional=True, additional=True
)
)
msg = self.layout_header['iow'].format('W/s')
ret.append(
self.curse_add_line(
msg, sort_style if process_sort_key == 'io_counters' else 'DEFAULT', optional=True, additional=True
)
) )
)
if args.is_standalone and not args.disable_cursor: if args.is_standalone and not args.disable_cursor:
if self.args.programs: if self.args.programs:
shortkey = "('k' to kill)" shortkey = "('k' to kill)"
@ -767,8 +799,9 @@ class PluginModel(GlancesPluginModel):
shortkey = "('e' to pin | 'k' to kill)" shortkey = "('e' to pin | 'k' to kill)"
else: else:
shortkey = "" shortkey = ""
msg = self.layout_header['command'].format("Programs" if self.args.programs else "Command", shortkey) if 'cmdline' in display_stats:
ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'name' else 'DEFAULT')) msg = self.layout_header['command'].format("Programs" if self.args.programs else "Command", shortkey)
ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'name' else 'DEFAULT'))
def __msg_curse_sum(self, ret, sep_char='_', mmm=None, args=None): def __msg_curse_sum(self, ret, sep_char='_', mmm=None, args=None):
""" """

View File

@ -18,8 +18,11 @@ from glances.timer import Timer, getTimeSinceLastUpdate
psutil_version_info = tuple([int(num) for num in psutil.__version__.split('.')]) psutil_version_info = tuple([int(num) for num in psutil.__version__.split('.')])
# This constant defines the list of mandatory processes stats. Thoses stats can not be disabled by the user
mandatory_processes_stats_list = ['pid', 'name']
# This constant defines the list of available processes sort key # This constant defines the list of available processes sort key
sort_processes_key_list = ['cpu_percent', 'memory_percent', 'username', 'cpu_times', 'io_counters', 'name'] sort_processes_stats_list = ['cpu_percent', 'memory_percent', 'username', 'cpu_times', 'io_counters', 'name']
# Sort dictionary for human # Sort dictionary for human
sort_for_human = { sort_for_human = {
@ -38,7 +41,7 @@ class GlancesProcesses:
def __init__(self, cache_timeout=60): def __init__(self, cache_timeout=60):
"""Init the class to collect stats about processes.""" """Init the class to collect stats about processes."""
# Init the args, coming from the GlancesStandalone class # Init the args, coming from the classes derived from GlancesMode
# Should be set by the set_args method # Should be set by the set_args method
self.args = None self.args = None
@ -96,6 +99,9 @@ class GlancesProcesses:
self._max_values = {} self._max_values = {}
self.reset_max_values() self.reset_max_values()
# Set the key's list be disabled in order to only display specific attribte in the process list
self.disable_stats = []
def _test_grab(self): def _test_grab(self):
"""Test somes optionals features""" """Test somes optionals features"""
# Test if the system can grab io_counters # Test if the system can grab io_counters
@ -143,9 +149,12 @@ class GlancesProcesses:
# For each key in the processcount dict # For each key in the processcount dict
# count the number of processes with the same status # count the number of processes with the same status
for k in iterkeys(self.processcount): for k in iterkeys(self.processcount):
self.processcount[k] = len(list(filter(lambda v: v['status'] is k, plist))) self.processcount[k] = len(list(filter(lambda v: v.get('status', '?') is k, plist)))
# Compute thread # Compute thread
self.processcount['thread'] = sum(i['num_threads'] for i in plist if i['num_threads'] is not None) try:
self.processcount['thread'] = sum(i['num_threads'] for i in plist if i['num_threads'] is not None)
except KeyError:
self.processcount['thread'] = None
# Compute total # Compute total
self.processcount['total'] = len(plist) self.processcount['total'] = len(plist)
@ -215,7 +224,15 @@ class GlancesProcesses:
"""Set the maximum number of processes showed in the UI.""" """Set the maximum number of processes showed in the UI."""
self._max_processes = value self._max_processes = value
# Process filter @property
def disable_stats(self):
"""Set disable_stats list"""
return self._disable_stats
@disable_stats.setter
def disable_stats(self, stats_list):
"""Set disable_stats list"""
self._disable_stats = [i for i in stats_list if i not in mandatory_processes_stats_list]
@property @property
def process_filter_input(self): def process_filter_input(self):
@ -445,6 +462,9 @@ class GlancesProcesses:
else: else:
is_cached = True is_cached = True
# Remove attributes set by the user in the config file (see #1524)
sorted_attrs = [i for i in sorted_attrs if i not in self.disable_stats]
# Build the processes stats list (it is why we need psutil>=5.3.0) (see issue #2755) # Build the processes stats list (it is why we need psutil>=5.3.0) (see issue #2755)
processlist = list( processlist = list(
filter( filter(
@ -491,7 +511,7 @@ class GlancesProcesses:
proc['time_since_update'] = time_since_update proc['time_since_update'] = time_since_update
# Process status (only keep the first char) # Process status (only keep the first char)
proc['status'] = str(proc['status'])[:1].upper() proc['status'] = str(proc.get('status', '?'))[:1].upper()
# Process IO # Process IO
# procstat['io_counters'] is a list: # procstat['io_counters'] is a list:
@ -553,7 +573,7 @@ class GlancesProcesses:
# Compute the maximum value for keys in self._max_values_list: CPU, MEM # Compute the maximum value for keys in self._max_values_list: CPU, MEM
# Useful to highlight the processes with maximum values # Useful to highlight the processes with maximum values
for k in self._max_values_list: for k in [i for i in self._max_values_list if i not in self.disable_stats]:
values_list = [i[k] for i in processlist if i[k] is not None] values_list = [i[k] for i in processlist if i[k] is not None]
if values_list: if values_list:
self.set_max_values(k, max(values_list)) self.set_max_values(k, max(values_list))

View File

@ -17,6 +17,7 @@ from defusedxml import xmlrpc
from glances import __version__ from glances import __version__
from glances.logger import logger from glances.logger import logger
from glances.processes import glances_processes
from glances.servers_list_dynamic import GlancesAutoDiscoverClient from glances.servers_list_dynamic import GlancesAutoDiscoverClient
from glances.stats_server import GlancesStatsServer from glances.stats_server import GlancesStatsServer
from glances.timer import Timer from glances.timer import Timer
@ -176,6 +177,9 @@ class GlancesServer:
# Args # Args
self.args = args self.args = args
# Set the args for the glances_processes instance
glances_processes.set_args(args)
# Init the XML RPC server # Init the XML RPC server
try: try:
self.server = GlancesXMLRPCServer(args.bind_address, args.port, requestHandler, config=config) self.server = GlancesXMLRPCServer(args.bind_address, args.port, requestHandler, config=config)

View File

@ -48,7 +48,7 @@ class GlancesStandalone:
self.display_modules_list() self.display_modules_list()
sys.exit(0) sys.exit(0)
# The args is needed to get the selected process in the process list (Curses mode) # Set the args for the glances_processes instance
glances_processes.set_args(args) glances_processes.set_args(args)
# If process extended stats is disabled by user # If process extended stats is disabled by user

View File

@ -25,6 +25,9 @@ class GlancesWebServer:
# Ignore kernel threads in process list # Ignore kernel threads in process list
glances_processes.disable_kernel_threads() glances_processes.disable_kernel_threads()
# Set the args for the glances_processes instance
glances_processes.set_args(args)
# Initial system information update # Initial system information update
self.stats.update() self.stats.update()