Skip to content

Commit

Permalink
Merge pull request #3029 from ariel-anieli/glances-processes
Browse files Browse the repository at this point in the history
Refactored `glances.processes.GlancesProcesses.update`, version 2
  • Loading branch information
nicolargo authored Dec 8, 2024
2 parents af808c4 + cb53393 commit 8258101
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 97 deletions.
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
year = datetime.utcfromtimestamp(int(os.environ['SOURCE_DATE_EPOCH'])).year
except (KeyError, ValueError):
year = datetime.now().year
copyright = '%d, %s' % (year, author)
copyright = f'{year}, {author}'

# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
Expand Down
231 changes: 135 additions & 96 deletions glances/processes.py
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,118 @@ def is_selected_extended_process(self, position):
and not self.args.disable_cursor
)

def build_process_list(self, 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
return sort_stats(processlist, sorted_by=self.sort_key, reverse=True)

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):
# 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(self.get_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

return is_cached, sorted_attrs

def get_pid_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.get('status', '?'))[:1].upper()

return proc

def get_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 update(self):
"""Update the processes stats."""
# Init new processes stats
Expand All @@ -438,49 +550,17 @@ def update(self):

# 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
is_cached, sorted_attrs = self.maybe_add_cached_attrs(sorted_attrs, cached_attrs)

# 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)
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)
processlist = self.build_process_list(sorted_attrs)

# Update the processcount
self.update_processcount(processlist)
Expand All @@ -503,67 +583,16 @@ def update(self):

# 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.get('status', '?'))[:1].upper()
proc = self.get_pid_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.get_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.remove_non_running_procs(processlist)

# Filter and transform process export list
self.processlist_export = self.update_export_list(processlist)
Expand All @@ -573,16 +602,26 @@ def update(self):

# Compute the maximum value for keys in self._max_values_list: CPU, MEM
# Useful to highlight the processes with maximum values
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]
if values_list:
self.set_max_values(k, max(values_list))
self.compute_max_value(processlist)

# Update the stats
self.processlist = processlist

return self.processlist

def compute_max_value(self, processlist):
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]
if values_list:
self.set_max_values(k, max(values_list))

def 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 update_list(self, processlist):
"""Return the process list after filtering and transformation (namedtuple to dict)."""
if self._filter.filter is None:
Expand Down

0 comments on commit 8258101

Please sign in to comment.