summaryrefslogtreecommitdiffstats
path: root/glances/plugins/glances_processlist.py
diff options
context:
space:
mode:
Diffstat (limited to 'glances/plugins/glances_processlist.py')
-rw-r--r--glances/plugins/glances_processlist.py250
1 files changed, 165 insertions, 85 deletions
diff --git a/glances/plugins/glances_processlist.py b/glances/plugins/glances_processlist.py
index 7a9efd57..37a27358 100644
--- a/glances/plugins/glances_processlist.py
+++ b/glances/plugins/glances_processlist.py
@@ -2,7 +2,7 @@
#
# This file is part of Glances.
#
-# SPDX-FileCopyrightText: 2022 Nicolas Hennion <nicolas@nicolargo.com>
+# SPDX-FileCopyrightText: 2023 Nicolas Hennion <nicolas@nicolargo.com>
#
# SPDX-License-Identifier: LGPL-3.0-only
#
@@ -19,7 +19,6 @@ from glances.processes import glances_processes, sort_stats
from glances.outputs.glances_unicode import unicode_message
from glances.plugins.glances_core import Plugin as CorePlugin
from glances.plugins.glances_plugin import GlancesPlugin
-from glances.programs import processes_to_programs
def seconds_to_hms(input_seconds):
@@ -48,7 +47,7 @@ def split_cmdline(bare_process_name, cmdline):
path, cmd = "", cmdline[0]
else:
path, cmd = os.path.split(cmdline[0])
- arguments = ' '.join(cmdline[1:]).replace('\n', ' ')
+ arguments = ' '.join(cmdline[1:])
return path, cmd, arguments
@@ -146,9 +145,10 @@ class Plugin(GlancesPlugin):
# Update stats using the standard system lib
# Note: Update is done in the processcount plugin
# Just return the processes list
- stats = glances_processes.getlist()
if self.args.programs:
- stats = processes_to_programs(stats)
+ stats = glances_processes.getlist(as_programs=True)
+ else:
+ stats = glances_processes.getlist()
elif self.input_method == 'snmp':
# No SNMP grab for processes
@@ -221,7 +221,7 @@ class Plugin(GlancesPlugin):
def _get_process_curses_vms(self, p, selected, args):
"""Return process VMS curses"""
- if key_exist_value_not_none_not_v('memory_info', p, ''):
+ if key_exist_value_not_none_not_v('memory_info', p, '', 1):
msg = self.layout_stat['virt'].format(self.auto_unit(p['memory_info'][1], low_precision=False))
ret = self.curse_add_line(msg, optional=True)
else:
@@ -231,7 +231,7 @@ class Plugin(GlancesPlugin):
def _get_process_curses_rss(self, p, selected, args):
"""Return process RSS curses"""
- if key_exist_value_not_none_not_v('memory_info', p, ''):
+ if key_exist_value_not_none_not_v('memory_info', p, '', 0):
msg = self.layout_stat['res'].format(self.auto_unit(p['memory_info'][0], low_precision=False))
ret = self.curse_add_line(msg, optional=True)
else:
@@ -353,11 +353,15 @@ 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,12 +408,13 @@ 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)
- arguments.replace('\r\n', ' ')
- arguments.replace('\n', ' ')
+ arguments = arguments.replace('\r\n', ' ')
+ arguments = arguments.replace('\n', ' ')
+ arguments = arguments.replace('\t', ' ')
if os.path.isdir(path) and not args.process_short_name:
msg = self.layout_stat['command'].format(path) + os.sep
ret.append(self.curse_add_line(msg, splittable=True))
@@ -428,74 +433,16 @@ 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 +454,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 +472,8 @@ 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 +487,127 @@ class Plugin(GlancesPlugin):
# Return the message with decoration
return ret
+ def __msg_curse_extended_process(self, ret, p):
+ """Get extended curses data for the selected process (see issue #2225)
+
+ The result depends of the process type (process or thread).
+
+ Input p is a dict with the following keys:
+ {'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',
+ 'cpu_min': 0.0,
+ 'cpu_max': 7.0,
+ 'cpu_mean': 3.2}
+ """
+ if self.args.programs:
+ self.__msg_curse_extended_process_program(ret, p)
+ else:
+ self.__msg_curse_extended_process_thread(ret, p)
+
+ def __msg_curse_extended_process_program(self, ret, p):
+ # Title
+ msg = "Pinned program {} ('e' to unpin)".format(p['name'])
+ ret.append(self.curse_add_line(msg, "TITLE"))
+
+ ret.append(self.curse_new_line())
+ ret.append(self.curse_new_line())
+
+ def __msg_curse_extended_process_thread(self, ret, p):
+ # Title
+ ret.append(self.curse_add_line("Pinned thread ", "TITLE"))
+ ret.append(self.curse_add_line(p['name'], "UNDERLINE"))
+ ret.append(self.curse_add_line(" ('e' to unpin)"))
+
+ # First line is CPU affinity
+ ret.append(self.curse_new_line())
+ ret.append(self.curse_add_line(' CPU Min/Max/Mean: '))
+ msg = '{: >7.1f}{: >7.1f}{: >7.1f}%'.format(p['cpu_min'], p['cpu_max'], p['cpu_mean'])
+ ret.append(self.curse_add_line(msg, decoration='INFO'))
+ if 'cpu_affinity' in p and p['cpu_affinity'] is not None:
+ ret.append(self.curse_add_line(' Affinity: '))
+ ret.append(self.curse_add_line(str(len(p['cpu_affinity'])), decoration='INFO'))
+ ret.append(self.curse_add_line(' cores', decoration='INFO'))
+ if 'ionice' in p and p['ionice'] is not None and hasattr(p['ionice'], 'ioclass'):
+ 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))
+
+ # Second line is memory info
+ ret.append(self.curse_new_line())
+ ret.append(self.curse_add_line(' MEM Min/Max/Mean: '))
+ msg = '{: >7.1f}{: >7.1f}{: >7.1f}%'.format(p['memory_min'], p['memory_max'], p['memory_mean'])
+ ret.append(self.curse_add_line(msg, decoration='INFO'))
+ if 'memory_info' in p and p['memory_info'] is not None:
+ ret.append(self.curse_add_line(' Memory info: '))
+ for k in p['memory_info']._asdict():
+ ret.append(
+ self.curse_add_line(
+ self.auto_unit(p['memory_info']._asdict()[k], low_precision=False),
+ decoration='INFO',
+ splittable=True,
+ )
+ )
+ ret.append(self.curse_add_line(' ' + k + ' ', splittable=True))
+ if 'memory_swap' in p and p['memory_swap'] is not None:
+ ret.append(
+ self.curse_add_line(
+ self.auto_unit(p['memory_swap'], low_precision=False), decoration='INFO', splittable=True
+ )
+ )
+ ret.append(self.curse_add_line(' swap ', splittable=True))
+
+ # Third line is for open files/network sessions
+ ret.append(self.curse_new_line())
+ ret.append(self.curse_add_line(' Open: '))
+ for stat_prefix in ['num_threads', 'num_fds', 'num_handles', 'tcp', 'udp']:
+ if stat_prefix in p and p[stat_prefix] is not None:
+ ret.append(self.curse_add_line(str(p[stat_prefix]), decoration='INFO'))
+ ret.append(self.curse_add_line(' {} '.format(stat_prefix.replace('num_', ''))))
+
+ 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'
@@ -578,10 +654,14 @@ class Plugin(GlancesPlugin):
msg, sort_style if process_sort_key == 'io_counters' else 'DEFAULT', optional=True, additional=True
)
)
- if not self.args.programs:
- msg = self.layout_header['command'].format('Command', "('k' to kill)" if args.is_standalone else "")
+ if args.is_standalone and not args.disable_cursor:
+ if self.args.programs:
+ shortkey = "('k' to kill)"
+ else:
+ shortkey = "('e' to pin | 'k' to kill)"
else:
- msg = self.layout_header['command'].format('Programs', "('k' to kill)" if args.is_standalone else "")
+ shortkey = ""
+ 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):