Refactored glances.processes.GlancesProcesses.update

Part of #2801.

Signed-off-by: Ariel Otilibili <otilibil@eurecom.fr>
This commit is contained in:
Ariel Otilibili 2024-11-11 13:09:31 +01:00
parent e1cd3ded23
commit 1fda541350

View File

@ -407,6 +407,136 @@ class GlancesProcesses:
and not self.args.disable_cursor
)
def get_sorted_attrs(self):
defaults = ['cpu_percent', 'cpu_times', 'memory_percent', 'name', 'status', 'num_threads']
optional = ['io_counters'] if not self.disable_io_counters else []
return defaults + optional
def get_displayed_attr(self):
defaults = ['memory_info', 'nice', 'pid']
optional = ['gids'] if not self.disable_gids else []
return defaults + optional
def get_cached_attrs(self):
return ['cmdline', 'username']
def maybe_add_cached_attrs(self, sorted_attrs, cached_attrs):
if self.cache_timer.finished():
sorted_attrs += cached_attrs
self.cache_timer.set(self.cache_timeout)
self.cache_timer.reset()
is_cached = False
else:
is_cached = True
return (is_cached, sorted_attrs)
def take_this_proc(self, p):
conditions = [
(BSD and p.info['name'] == 'idle'),
(WINDOWS and p.info['name'] == 'System Idle Process'),
(MACOS and p.info['name'] == 'kernel_task'),
(self.no_kernel_threads and LINUX and p.info['gids'].real == 0),
]
return not any(conditions)
def buils_proc_stats_lists(self, sorted_attrs):
return list(
filter(
self.take_this_proc,
psutil.process_iter(attrs=sorted_attrs, ad_value=None),
)
)
def get_selected_proc(self, position, proc):
# Get the selected process when the 'e' key is pressed
if self.is_selected_extended_process(position):
self.extended_process = proc
# 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']:
proc.update(self.get_extended_stats(self.extended_process))
self.extended_process = namedtuple_to_dict(proc)
return proc
def update_key_time_and_status(self, time_since_update, proc):
# PID is the key
proc['key'] = 'pid'
# Time since last update (for disk_io rate computation)
proc['time_since_update'] = time_since_update
# Process status (only keep the first char)
proc['status'] = str(proc['status'])[:1].upper()
return proc
def update_io_counters(self, proc):
# procstat['io_counters'] is a list:
# [read_bytes, write_bytes, read_bytes_old, write_bytes_old, io_tag]
# If io_tag = 0 > Access denied or first time (display "?")
# If io_tag = 1 > No access denied (display the IO rate)
if 'io_counters' in proc and proc['io_counters'] is not None:
io_new = [proc['io_counters'][2], proc['io_counters'][3]]
# For IO rate computation
# Append saved IO r/w bytes
try:
proc['io_counters'] = io_new + self.io_old[proc['pid']]
io_tag = 1
except KeyError:
proc['io_counters'] = io_new + [0, 0]
io_tag = 0
# then save the IO r/w bytes
self.io_old[proc['pid']] = io_new
else:
proc['io_counters'] = [0, 0] + [0, 0]
io_tag = 0
# Append the IO tag (for display)
proc['io_counters'] += [io_tag]
return proc
def maybe_add_cached_stats(self, is_cached, cached_attrs, proc):
if is_cached:
# Grab cached values (in case of a new incoming process)
if proc['pid'] not in self.processlist_cache:
try:
self.processlist_cache[proc['pid']] = psutil.Process(pid=proc['pid']).as_dict(
attrs=cached_attrs, ad_value=None
)
except psutil.NoSuchProcess:
pass
# Add cached value to current stat
try:
proc.update(self.processlist_cache[proc['pid']])
except KeyError:
pass
else:
# Save values to cache
try:
self.processlist_cache[proc['pid']] = {cached: proc[cached] for cached in cached_attrs}
except KeyError:
pass
return proc
def maybe_remove_non_running_procs(self, processlist):
pids_running = [p['pid'] for p in processlist]
pids_cached = list(self.processlist_cache.keys()).copy()
for pid in pids_cached:
if pid not in pids_running:
self.processlist_cache.pop(pid, None)
def compute_max_of_keys(self, processlist):
for k in self._max_values_list:
values_list = [i[k] for i in processlist if i[k] is not None]
if values_list:
self.set_max_values(k, max(values_list))
def update(self):
"""Update the processes stats."""
# Init new processes stats
@ -420,45 +550,30 @@ class GlancesProcesses:
time_since_update = getTimeSinceLastUpdate('process_disk')
# Grab standard stats
#####################
sorted_attrs = ['cpu_percent', 'cpu_times', 'memory_percent', 'name', 'status', 'num_threads']
displayed_attr = ['memory_info', 'nice', 'pid']
sorted_attrs = self.get_sorted_attrs()
# The following attributes are cached and only retrieve every self.cache_timeout seconds
# Warning: 'name' can not be cached because it is used for filtering
cached_attrs = ['cmdline', 'username']
cached_attrs = self.get_cached_attrs()
# Some stats are optional
if not self.disable_io_counters:
sorted_attrs.append('io_counters')
if not self.disable_gids:
displayed_attr.append('gids')
# Some stats are not sort key
# An optimisation can be done be only grabbed displayed_attr
# for displayed processes (but only in standalone mode...)
sorted_attrs.extend(displayed_attr)
# Some stats are cached (not necessary to be refreshed every time)
if self.cache_timer.finished():
sorted_attrs += cached_attrs
self.cache_timer.set(self.cache_timeout)
self.cache_timer.reset()
is_cached = False
else:
is_cached = True
sorted_attrs.extend(self.get_displayed_attr())
# Some stats are cached (not necessary to be refreshed every time)
is_cached, sorted_attrs = self.maybe_add_cached_attrs(sorted_attrs, cached_attrs)
# 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)
# It may be optimized with PsUtil 6+ (see issue #2755)
processlist = self.buils_proc_stats_lists(sorted_attrs)
# Build the processes stats list (it is why we need psutil>=5.3.0) (see issue #2755)
processlist = list(
filter(
lambda p: not (BSD and p.info['name'] == 'idle')
and not (WINDOWS and p.info['name'] == 'System Idle Process')
and not (MACOS and p.info['name'] == 'kernel_task')
and not (self.no_kernel_threads and LINUX and p.info['gids'].real == 0),
psutil.process_iter(attrs=sorted_attrs, ad_value=None),
)
)
# Only get the info key
# PsUtil 6+ no longer check PID reused #2755 so use is_running in the loop
# Note: not sure it is realy needed but CPU consumption look the same with or without it
processlist = [p.info for p in processlist if p.is_running()]
# Sort the processes list by the current sort_key
processlist = sort_stats(processlist, sorted_by=self.sort_key, reverse=True)
@ -472,78 +587,21 @@ class GlancesProcesses:
# Extended stats
################
# Get the selected process when the 'e' key is pressed
if self.is_selected_extended_process(position):
self.extended_process = proc
# 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']:
proc.update(self.get_extended_stats(self.extended_process))
self.extended_process = namedtuple_to_dict(proc)
# may update self.extended_process
proc = self.get_selected_proc(position, proc)
# Meta data
###########
# PID is the key
proc['key'] = 'pid'
# Time since last update (for disk_io rate computation)
proc['time_since_update'] = time_since_update
# Process status (only keep the first char)
proc['status'] = str(proc['status'])[:1].upper()
proc = self.update_key_time_and_status(time_since_update, proc)
# Process IO
# procstat['io_counters'] is a list:
# [read_bytes, write_bytes, read_bytes_old, write_bytes_old, io_tag]
# If io_tag = 0 > Access denied or first time (display "?")
# If io_tag = 1 > No access denied (display the IO rate)
if 'io_counters' in proc and proc['io_counters'] is not None:
io_new = [proc['io_counters'][2], proc['io_counters'][3]]
# For IO rate computation
# Append saved IO r/w bytes
try:
proc['io_counters'] = io_new + self.io_old[proc['pid']]
io_tag = 1
except KeyError:
proc['io_counters'] = io_new + [0, 0]
io_tag = 0
# then save the IO r/w bytes
self.io_old[proc['pid']] = io_new
else:
proc['io_counters'] = [0, 0] + [0, 0]
io_tag = 0
# Append the IO tag (for display)
proc['io_counters'] += [io_tag]
proc = self.update_io_counters(proc)
# Manage cached information
if is_cached:
# Grab cached values (in case of a new incoming process)
if proc['pid'] not in self.processlist_cache:
try:
self.processlist_cache[proc['pid']] = psutil.Process(pid=proc['pid']).as_dict(
attrs=cached_attrs, ad_value=None
)
except psutil.NoSuchProcess:
pass
# Add cached value to current stat
try:
proc.update(self.processlist_cache[proc['pid']])
except KeyError:
pass
else:
# Save values to cache
try:
self.processlist_cache[proc['pid']] = {cached: proc[cached] for cached in cached_attrs}
except KeyError:
pass
proc = self.maybe_add_cached_stats(is_cached, cached_attrs, proc)
# Remove non running process from the cache (avoid issue #2976)
pids_running = [p['pid'] for p in processlist]
pids_cached = list(self.processlist_cache.keys()).copy()
for pid in pids_cached:
if pid not in pids_running:
self.processlist_cache.pop(pid, None)
self.maybe_remove_non_running_procs(processlist)
# Filter and transform process export list
self.processlist_export = self.update_export_list(processlist)
@ -553,10 +611,7 @@ class GlancesProcesses:
# Compute the maximum value for keys in self._max_values_list: CPU, MEM
# Useful to highlight the processes with maximum values
for k in self._max_values_list:
values_list = [i[k] for i in processlist if i[k] is not None]
if values_list:
self.set_max_values(k, max(values_list))
self.compute_max_of_keys(processlist)
# Update the stats
self.processlist = processlist