diff options
Diffstat (limited to 'glances/processes.py')
-rw-r--r-- | glances/processes.py | 143 |
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. """ |