summaryrefslogtreecommitdiffstats
path: root/glances/processes.py
diff options
context:
space:
mode:
Diffstat (limited to 'glances/processes.py')
-rw-r--r--glances/processes.py143
1 files changed, 83 insertions, 60 deletions
diff --git a/glances/processes.py b/glances/processes.py
index 7ba12eb7..a3069e0b 100644
--- a/glances/processes.py
+++ b/glances/processes.py
@@ -2,7 +2,7 @@
#
# This file is part of Glances.
#
-# SPDX-FileCopyrightText: 2023 Nicolas Hennion <nicolas@nicolargo.com>
+# SPDX-FileCopyrightText: 2024 Nicolas Hennion <nicolas@nicolargo.com>
#
# SPDX-License-Identifier: LGPL-3.0-only
#
@@ -18,6 +18,8 @@ from glances.logger import logger
import psutil
+psutil_version_info = tuple([int(num) for num in psutil.__version__.split('.')])
+
# This constant defines the list of available processes sort key
sort_processes_key_list = ['cpu_percent', 'memory_percent', 'username', 'cpu_times', 'io_counters', 'name']
@@ -42,11 +44,6 @@ class GlancesProcesses(object):
# 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 = {}
- self.cmdline_cache = {}
-
# The internals caches will be cleaned each 'cache_timeout' seconds
self.cache_timeout = cache_timeout
# First iteration, no cache
@@ -300,66 +297,92 @@ class GlancesProcesses(object):
ret = {}
try:
- # Get the extended stats
+ logger.debug('Grab extended stats for process {}'.format(proc['pid']))
+
+ # Get PID of the selected process
selected_process = psutil.Process(proc['pid'])
+
+ # Get the extended stats for the selected process
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
+ # Get memory swap for the selected process (Linux Only)
+ ret['memory_swap'] = self.__get_extended_memory_swap(selected_process)
+
+ # Get number of TCP and UDP network connections for the selected process
+ ret['tcp'], ret['udp'] = self.__get_extended_connections(selected_process)
except (psutil.NoSuchProcess, ValueError, AttributeError) as e:
logger.error('Can not grab extended stats ({})'.format(e))
self.extended_process = None
ret['extended_stats'] = False
else:
- logger.debug('Grab extended stats for process {}'.format(proc['pid']))
-
# Compute CPU and MEM min/max/mean
- for stat_prefix in ['cpu', 'memory']:
- if stat_prefix + '_min' not in self.extended_process:
- ret[stat_prefix + '_min'] = proc[stat_prefix + '_percent']
- else:
- ret[stat_prefix + '_min'] = (
- proc[stat_prefix + '_percent']
- if proc[stat_prefix + '_min'] > proc[stat_prefix + '_percent']
- else proc[stat_prefix + '_min']
- )
- if stat_prefix + '_max' not in self.extended_process:
- ret[stat_prefix + '_max'] = proc[stat_prefix + '_percent']
- else:
- ret[stat_prefix + '_max'] = (
- proc[stat_prefix + '_percent']
- if proc[stat_prefix + '_max'] < proc[stat_prefix + '_percent']
- else proc[stat_prefix + '_max']
- )
- if stat_prefix + '_mean_sum' not in self.extended_process:
- ret[stat_prefix + '_mean_sum'] = proc[stat_prefix + '_percent']
- else:
- ret[stat_prefix + '_mean_sum'] = proc[stat_prefix + '_mean_sum'] + proc[stat_prefix + '_percent']
- if stat_prefix + '_mean_counter' not in self.extended_process:
- ret[stat_prefix + '_mean_counter'] = 1
- else:
- ret[stat_prefix + '_mean_counter'] = proc[stat_prefix + '_mean_counter'] + 1
- ret[stat_prefix + '_mean'] = ret[stat_prefix + '_mean_sum'] / ret[stat_prefix + '_mean_counter']
-
+ # Merge the returned dict with the current on
+ ret.update(self.__get_min_max_mean(proc))
+ self.extended_process = ret
ret['extended_stats'] = True
return namedtuple_to_dict(ret)
+ def __get_min_max_mean(self, proc, prefix=['cpu', 'memory']):
+ """Return the min/max/mean for the given process"""
+ ret = {}
+ for stat_prefix in prefix:
+ min_key = stat_prefix + '_min'
+ max_key = stat_prefix + '_max'
+ mean_sum_key = stat_prefix + '_mean_sum'
+ mean_counter_key = stat_prefix + '_mean_counter'
+ if min_key not in self.extended_process:
+ ret[min_key] = proc[stat_prefix + '_percent']
+ else:
+ ret[min_key] = min(proc[stat_prefix + '_percent'], self.extended_process[min_key])
+ if max_key not in self.extended_process:
+ ret[max_key] = proc[stat_prefix + '_percent']
+ else:
+ ret[max_key] = max(proc[stat_prefix + '_percent'], self.extended_process[max_key])
+ if mean_sum_key not in self.extended_process:
+ ret[mean_sum_key] = proc[stat_prefix + '_percent']
+ else:
+ ret[mean_sum_key] = self.extended_process[mean_sum_key] + proc[stat_prefix + '_percent']
+ if mean_counter_key not in self.extended_process:
+ ret[mean_counter_key] = 1
+ else:
+ ret[mean_counter_key] = self.extended_process[mean_counter_key] + 1
+ ret[stat_prefix + '_mean'] = ret[mean_sum_key] / ret[mean_counter_key]
+ return ret
+
+ def __get_extended_memory_swap(self, process):
+ """Return the memory swap for the given process"""
+ if not LINUX:
+ return None
+ try:
+ memory_swap = sum([v.swap for v in 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).
+ memory_swap = None
+ return memory_swap
+
+ def __get_extended_connections(self, process):
+ """Return a tuple with (tcp, udp) connections count
+ The code is compliant with both PsUtil<6 and Psutil>=6
+ """
+ try:
+ # Hack for issue #2754 (PsUtil 6+)
+ if psutil_version_info[0] >= 6:
+ tcp = len(process.net_connections(kind="tcp"))
+ udp = len(process.net_connections(kind="udp"))
+ else:
+ tcp = len(process.connections(kind="tcp"))
+ udp = len(process.connections(kind="udp"))
+ except (psutil.AccessDenied, psutil.NoSuchProcess):
+ # Manage issue1283 (psutil.AccessDenied)
+ tcp = None
+ udp = None
+ return tcp, udp
+
def is_selected_extended_process(self, position):
"""Return True if the process is the selected one for extended stats."""
return (
@@ -389,7 +412,8 @@ class GlancesProcesses(object):
#####################
sorted_attrs = ['cpu_percent', 'cpu_times', 'memory_percent', 'name', 'status', 'num_threads']
displayed_attr = ['memory_info', 'nice', 'pid']
- # 'name' can not be cached because it is used for filtering
+ # The following attributes are cached and only retreive every self.cache_timeout seconds
+ # Warning: 'name' can not be cached because it is used for filtering
cached_attrs = ['cmdline', 'username']
# Some stats are optional
@@ -412,7 +436,7 @@ class GlancesProcesses(object):
# Build the processes stats list (it is why we need psutil>=5.3.0)
# This is one of the main bottleneck of Glances (see flame graph)
- # Filter processes
+ # It may be optimized with PsUtil 6+ (see issue #2755)
processlist = list(
filter(
lambda p: not (BSD and p.info['name'] == 'idle')
@@ -525,16 +549,14 @@ class GlancesProcesses(object):
"""Return the process list after filtering and transformation (namedtuple to dict)."""
if self._filter.filter is None:
return list_of_namedtuple_to_list_of_dict(processlist)
- ret = list(filter(lambda p: self._filter.is_filtered(p),
- processlist))
+ ret = list(filter(lambda p: self._filter.is_filtered(p), processlist))
return list_of_namedtuple_to_list_of_dict(ret)
def update_export_list(self, processlist):
"""Return the process export list after filtering and transformation (namedtuple to dict)."""
if self._filter_export.filter == []:
return []
- ret = list(filter(lambda p: self._filter_export.is_filtered(p),
- processlist))
+ ret = list(filter(lambda p: self._filter_export.is_filtered(p), processlist))
return list_of_namedtuple_to_list_of_dict(ret)
def get_count(self):
@@ -639,6 +661,7 @@ def _sort_lambda(sorted_by='cpu_percent', sorted_by_secondary='memory_percent'):
def sort_stats(stats, sorted_by='cpu_percent', sorted_by_secondary='memory_percent', reverse=True):
"""Return the stats (dict) sorted by (sorted_by).
+ A secondary sort key should be specified.
Reverse the sort if reverse is True.
"""