| Server IP : 93.86.61.54 / Your IP : 216.73.216.156 Web Server : Apache/2.4.62 (Ubuntu) System : Linux rasin.ddns.net 6.8.0-124-generic #124~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Tue May 26 21:05:19 UTC x86_64 User : www-data ( 33) PHP Version : 8.4.22 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /lib/python3/dist-packages/glances/ |
Upload File : |
# -*- coding: utf-8 -*-
#
# This file is part of Glances.
#
# Copyright (C) 2021 Nicolargo <nicolas@nicolargo.com>
#
# Glances is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Glances is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
from glances.compat import iterkeys
from glances.globals import BSD, LINUX, MACOS, WINDOWS
from glances.timer import Timer, getTimeSinceLastUpdate
from glances.filter import GlancesFilter
from glances.logger import logger
import psutil
# This constant defines the list of available processes sort key
sort_processes_key_list = ['cpu_percent',
'memory_percent',
'username',
'cpu_times',
'io_counters',
'name']
class GlancesProcesses(object):
"""Get processed stats using the psutil library."""
def __init__(self, cache_timeout=60):
"""Init the class to collect stats about processes."""
# Add internals caches because psutil do not cache all the stats
# See: https://code.google.com/p/psutil/issues/detail?id=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
self.cache_timer = Timer(0)
# Init the io dict
# key = pid
# value = [ read_bytes_old, write_bytes_old ]
self.io_old = {}
# Init stats
self.auto_sort = None
self._sort_key = None
# Default processes sort key is 'auto'
# Can be overwrite from the configuration file (issue#1536) => See glances_processlist.py init
self.set_sort_key('auto', auto=True)
self.processlist = []
self.reset_processcount()
# Cache is a dict with key=pid and value = dict of cached value
self.processlist_cache = {}
# Tag to enable/disable the processes stats (to reduce the Glances CPU consumption)
# Default is to enable the processes stats
self.disable_tag = False
# Extended stats for top process is enable by default
self.disable_extended_tag = False
# Test if the system can grab io_counters
try:
p = psutil.Process()
p.io_counters()
except Exception as e:
logger.warning('PsUtil can not grab processes io_counters ({})'.format(e))
self.disable_io_counters = True
else:
logger.debug('PsUtil can grab processes io_counters')
self.disable_io_counters = False
# Test if the system can grab gids
try:
p = psutil.Process()
p.gids()
except Exception as e:
logger.warning('PsUtil can not grab processes gids ({})'.format(e))
self.disable_gids = True
else:
logger.debug('PsUtil can grab processes gids')
self.disable_gids = False
# Maximum number of processes showed in the UI (None if no limit)
self._max_processes = None
# Process filter is a regular expression
self._filter = GlancesFilter()
# Whether or not to hide kernel threads
self.no_kernel_threads = False
# Store maximums values in a dict
# Used in the UI to highlight the maximum value
self._max_values_list = ('cpu_percent', 'memory_percent')
# { 'cpu_percent': 0.0, 'memory_percent': 0.0 }
self._max_values = {}
self.reset_max_values()
def reset_processcount(self):
"""Reset the global process count"""
self.processcount = {'total': 0,
'running': 0,
'sleeping': 0,
'thread': 0,
'pid_max': None}
def update_processcount(self, plist):
"""Update the global process count from the current processes list"""
# Update the maximum process ID (pid) number
self.processcount['pid_max'] = self.pid_max
# For each key in the processcount dict
# count the number of processes with the same status
for k in iterkeys(self.processcount):
self.processcount[k] = len(list(filter(lambda v: v['status'] is k,
plist)))
# Compute thread
self.processcount['thread'] = sum(i['num_threads'] for i in plist
if i['num_threads'] is not None)
# Compute total
self.processcount['total'] = len(plist)
def enable(self):
"""Enable process stats."""
self.disable_tag = False
self.update()
def disable(self):
"""Disable process stats."""
self.disable_tag = True
def enable_extended(self):
"""Enable extended process stats."""
self.disable_extended_tag = False
self.update()
def disable_extended(self):
"""Disable extended process stats."""
self.disable_extended_tag = True
@property
def pid_max(self):
"""
Get the maximum PID value.
On Linux, the value is read from the `/proc/sys/kernel/pid_max` file.
From `man 5 proc`:
The default value for this file, 32768, results in the same range of
PIDs as on earlier kernels. On 32-bit platfroms, 32768 is the maximum
value for pid_max. On 64-bit systems, pid_max can be set to any value
up to 2^22 (PID_MAX_LIMIT, approximately 4 million).
If the file is unreadable or not available for whatever reason,
returns None.
Some other OSes:
- On FreeBSD and macOS the maximum is 99999.
- On OpenBSD >= 6.0 the maximum is 99999 (was 32766).
- On NetBSD the maximum is 30000.
:returns: int or None
"""
if LINUX:
# XXX: waiting for https://github.com/giampaolo/psutil/issues/720
try:
with open('/proc/sys/kernel/pid_max', 'rb') as f:
return int(f.read())
except (OSError, IOError):
return None
else:
return None
@property
def processes_count(self):
"""Get the current number of processes showed in the UI."""
return min(self._max_processes - 2, glances_processes.processcount['total'] - 1)
@property
def max_processes(self):
"""Get the maximum number of processes showed in the UI."""
return self._max_processes
@max_processes.setter
def max_processes(self, value):
"""Set the maximum number of processes showed in the UI."""
self._max_processes = value
@property
def process_filter_input(self):
"""Get the process filter (given by the user)."""
return self._filter.filter_input
@property
def process_filter(self):
"""Get the process filter (current apply filter)."""
return self._filter.filter
@process_filter.setter
def process_filter(self, value):
"""Set the process filter."""
self._filter.filter = value
@property
def process_filter_key(self):
"""Get the process filter key."""
return self._filter.filter_key
@property
def process_filter_re(self):
"""Get the process regular expression compiled."""
return self._filter.filter_re
def disable_kernel_threads(self):
"""Ignore kernel threads in process list."""
self.no_kernel_threads = True
@property
def sort_reverse(self):
"""Return True to sort processes in reverse 'key' order, False instead."""
if self.sort_key == 'name' or self.sort_key == 'username':
return False
return True
def max_values(self):
"""Return the max values dict."""
return self._max_values
def get_max_values(self, key):
"""Get the maximum values of the given stat (key)."""
return self._max_values[key]
def set_max_values(self, key, value):
"""Set the maximum value for a specific stat (key)."""
self._max_values[key] = value
def reset_max_values(self):
"""Reset the maximum values dict."""
self._max_values = {}
for k in self._max_values_list:
self._max_values[k] = 0.0
def update(self):
"""Update the processes stats."""
# Reset the stats
self.processlist = []
self.reset_processcount()
# Do not process if disable tag is set
if self.disable_tag:
return
# Time since last update (for disk_io rate computation)
time_since_update = getTimeSinceLastUpdate('process_disk')
# Grab standard stats
#####################
sorted_attrs = ['cpu_percent', 'cpu_times',
'memory_percent', 'name',
'status', 'status', 'num_threads']
displayed_attr = ['memory_info', 'nice', 'pid', 'ppid']
cached_attrs = ['cmdline', 'username']
# Some stats are optionals
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 grabed 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
# Build the processes stats list (it is why we need psutil>=5.3.0)
self.processlist = [p.info
for p in psutil.process_iter(attrs=sorted_attrs,
ad_value=None)
# OS-related processes filter
if 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
# Kernel threads filter
not (self.no_kernel_threads and LINUX and p.info['gids'].real == 0)]
# Sort the processes list by the current sort_key
self.processlist = sort_stats(self.processlist,
sortedby=self.sort_key,
reverse=True)
# Update the processcount
self.update_processcount(self.processlist)
# Loop over processes and add metadata
first = True
for proc in self.processlist:
# Get extended stats, only for top processes (see issue #403).
if first and not self.disable_extended_tag:
# - cpu_affinity (Linux, Windows, FreeBSD)
# - ionice (Linux and Windows > Vista)
# - num_ctx_switches (not available on Illumos/Solaris)
# - num_fds (Unix-like)
# - num_handles (Windows)
# - memory_maps (only swap, Linux)
# https://www.cyberciti.biz/faq/linux-which-process-is-using-swap/
# - connections (TCP and UDP)
extended = {}
try:
top_process = psutil.Process(proc['pid'])
extended_stats = ['cpu_affinity', 'ionice',
'num_ctx_switches']
if LINUX:
# num_fds only avalable on Unix system (see issue #1351)
extended_stats += ['num_fds']
if WINDOWS:
extended_stats += ['num_handles']
# Get the extended stats
extended = top_process.as_dict(attrs=extended_stats,
ad_value=None)
if LINUX:
try:
extended['memory_swap'] = sum([v.swap for v in top_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).
extended['memory_swap'] = None
try:
extended['tcp'] = len(top_process.connections(kind="tcp"))
extended['udp'] = len(top_process.connections(kind="udp"))
except (psutil.AccessDenied, psutil.NoSuchProcess):
# Manage issue1283 (psutil.AccessDenied)
extended['tcp'] = None
extended['udp'] = None
except (psutil.NoSuchProcess, ValueError, AttributeError) as e:
logger.error('Can not grab extended stats ({})'.format(e))
extended['extended_stats'] = False
else:
logger.debug('Grab extended stats for process {}'.format(proc['pid']))
extended['extended_stats'] = True
proc.update(extended)
first = False
# /End of extended stats
# 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()
# 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'].read_bytes,
proc['io_counters'].write_bytes]
# 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]
# 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
self.processlist_cache[proc['pid']] = { cached: proc[cached] for cached in cached_attrs }
# Apply filter
self.processlist = [p for p in self.processlist if not (self._filter.is_filtered(p))]
# Compute the maximum value for keys in self._max_values_list: CPU, MEM
# Usefull to highlight the processes with maximum values
for k in self._max_values_list:
values_list = [i[k] for i in self.processlist if i[k] is not None]
if values_list != []:
self.set_max_values(k, max(values_list))
def getcount(self):
"""Get the number of processes."""
return self.processcount
def getlist(self, sortedby=None):
"""Get the processlist."""
return self.processlist
@property
def sort_key(self):
"""Get the current sort key."""
return self._sort_key
def set_sort_key(self, key, auto=True):
"""Set the current sort key."""
if key == 'auto':
self.auto_sort = True
self._sort_key = 'cpu_percent'
else:
self.auto_sort = auto
self._sort_key = key
def kill(self, pid, timeout=3):
"""Kill process with pid"""
assert pid != os.getpid(), "Glances can kill itself..."
p = psutil.Process(pid)
logger.debug('Send kill signal to process: {}'.format(p))
p.kill()
return p.wait(timeout)
def weighted(value):
"""Manage None value in dict value."""
return -float('inf') if value is None else value
def _sort_io_counters(process,
sortedby='io_counters',
sortedby_secondary='memory_percent'):
"""Specific case for io_counters
Sum of io_r + io_w"""
return process[sortedby][0] - process[sortedby][2] + process[sortedby][1] - process[sortedby][3]
def _sort_cpu_times(process,
sortedby='cpu_times',
sortedby_secondary='memory_percent'):
""" Specific case for cpu_times
Patch for "Sorting by process time works not as expected #1321"
By default PsUtil only takes user time into account
see (https://github.com/giampaolo/psutil/issues/1339)
The following implementation takes user and system time into account"""
return process[sortedby][0] + process[sortedby][1]
def _sort_lambda(sortedby='cpu_percent',
sortedby_secondary='memory_percent'):
"""Return a sort lambda function for the sortedbykey"""
ret = None
if sortedby == 'io_counters':
ret = _sort_io_counters
elif sortedby == 'cpu_times':
ret = _sort_cpu_times
return ret
def sort_stats(stats,
sortedby='cpu_percent',
sortedby_secondary='memory_percent',
reverse=True):
"""Return the stats (dict) sorted by (sortedby).
Reverse the sort if reverse is True.
"""
if sortedby is None and sortedby_secondary is None:
# No need to sort...
return stats
# Check if a specific sort should be done
sort_lambda = _sort_lambda(sortedby=sortedby,
sortedby_secondary=sortedby_secondary)
if sort_lambda is not None:
# Specific sort
try:
stats.sort(key=sort_lambda, reverse=reverse)
except Exception:
# If an error is detected, fallback to cpu_percent
stats.sort(key=lambda process: (weighted(process['cpu_percent']),
weighted(process[sortedby_secondary])),
reverse=reverse)
else:
# Standard sort
try:
stats.sort(key=lambda process: (weighted(process[sortedby]),
weighted(process[sortedby_secondary])),
reverse=reverse)
except (KeyError, TypeError):
# Fallback to name
stats.sort(key=lambda process: process['name'] if process['name'] is not None else '~',
reverse=False)
return stats
glances_processes = GlancesProcesses()