diff options
Diffstat (limited to 'glances/core/glances_processes.py')
-rw-r--r-- | glances/core/glances_processes.py | 430 |
1 files changed, 317 insertions, 113 deletions
diff --git a/glances/core/glances_processes.py b/glances/core/glances_processes.py index 8913206d..3d75cbdd 100644 --- a/glances/core/glances_processes.py +++ b/glances/core/glances_processes.py @@ -17,10 +17,13 @@ # 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/>. -from glances.core.glances_globals import is_bsd, is_mac, is_windows +# Import Glances lib +from glances.core.glances_globals import is_linux, is_bsd, is_mac, is_windows, logger from glances.core.glances_timer import Timer, getTimeSinceLastUpdate +# Import Python lib import psutil +import re class GlancesProcesses(object): @@ -44,7 +47,7 @@ class GlancesProcesses(object): self.io_old = {} # Init stats - self.processsort = 'cpu_percent' + self.resetsort() self.processlist = [] self.processcount = {'total': 0, 'running': 0, 'sleeping': 0, 'thread': 0} @@ -52,6 +55,20 @@ class GlancesProcesses(object): # 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 + + # Maximum number of processes showed in the UI interface + # None if no limit + self.max_processes = None + + # Process filter is a regular expression + self.process_filter = None + self.process_filter_re = None + + # !!! ONLY FOR TEST + # self.set_process_filter('.*python.*') + def enable(self): """Enable process stats.""" self.disable_tag = False @@ -61,99 +78,231 @@ class GlancesProcesses(object): """Disable process stats.""" self.disable_tag = True - def __get_process_stats(self, proc): - """Get process stats.""" - procstat = {} + 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 - # Process ID - procstat['pid'] = proc.pid + def set_max_processes(self, value): + """Set the maximum number of processes showed in the UI interfaces""" + self.max_processes = value + return self.max_processes - # Process name (cached by PSUtil) - try: - procstat['name'] = proc.name() - except psutil.AccessDenied: - procstat['name'] = "" + def get_max_processes(self): + """Get the maximum number of processes showed in the UI interfaces""" + return self.max_processes - # Process username (cached with internal cache) - try: - self.username_cache[procstat['pid']] - except: + def set_process_filter(self, value): + """Set the process filter""" + logger.info(_("Set process filter to %s") % value) + self.process_filter = value + if value is not None: + try: + self.process_filter_re = re.compile(value) + logger.debug(_("Process filter regular expression compilation OK: %s") % self.get_process_filter()) + except: + logger.error(_("Can not compile process filter regular expression: %s") % value) + self.process_filter_re = None + else: + self.process_filter_re = None + return self.process_filter + + def get_process_filter(self): + """Get the process filter""" + return self.process_filter + + def get_process_filter_re(self): + """Get the process regular expression compiled""" + return self.process_filter_re + + def is_filtered(self, value): + """Return True if the value should be filtered""" + if self.get_process_filter() is None: + # No filter => Not filtered + return False + else: + # logger.debug(self.get_process_filter() + " <> " + value + " => " + str(self.get_process_filter_re().match(value) is None)) + return self.get_process_filter_re().match(value) is None + + def __get_process_stats(self, proc, + mandatory_stats=True, + standard_stats=True, + extended_stats=False): + """ + Get process stats of the proc processes (proc is returned psutil.process_iter()) + mandatory_stats: need for the sorting/filter step + => cpu_percent, memory_percent, io_counters, name, cmdline + standard_stats: for all the displayed processes + => username, status, memory_info, cpu_times + extended_stats: only for top processes (see issue #403) + => connections (UDP/TCP), memory_swap... + """ + + # Process ID (always) + procstat = proc.as_dict(attrs=['pid']) + + if mandatory_stats: + procstat['mandatory_stats'] = True + + # Process CPU, MEM percent and name + procstat.update(proc.as_dict(attrs=['cpu_percent', 'memory_percent', 'name'], ad_value='')) + + # Process command line (cached with internal cache) + try: + self.cmdline_cache[procstat['pid']] + except KeyError: + # Patch for issue #391 + try: + self.cmdline_cache[procstat['pid']] = ' '.join(proc.cmdline()) + except (AttributeError, psutil.AccessDenied, UnicodeDecodeError): + self.cmdline_cache[procstat['pid']] = "" + procstat['cmdline'] = self.cmdline_cache[procstat['pid']] + + # 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 (display "?") + # If io_tag = 1 > No access denied (display the IO rate) + # Note Disk IO stat not available on Mac OS + if not is_mac: + try: + # Get the process IO counters + proc_io = proc.io_counters() + io_new = [proc_io.read_bytes, proc_io.write_bytes] + except (psutil.AccessDenied, psutil.NoSuchProcess): + # Access denied to process IO (no root account) + # NoSuchProcess (process die between first and second grab) + # Put 0 in all values (for sort) and io_tag = 0 (for display) + procstat['io_counters'] = [0, 0] + [0, 0] + io_tag = 0 + else: + # For IO rate computation + # Append saved IO r/w bytes + try: + procstat['io_counters'] = io_new + self.io_old[procstat['pid']] + except KeyError: + procstat['io_counters'] = io_new + [0, 0] + # then save the IO r/w bytes + self.io_old[procstat['pid']] = io_new + io_tag = 1 + + # Append the IO tag (for display) + procstat['io_counters'] += [io_tag] + + if standard_stats: + procstat['standard_stats'] = True + + # Process username (cached with internal cache) try: - self.username_cache[procstat['pid']] = proc.username() - except (KeyError, psutil.AccessDenied): + self.username_cache[procstat['pid']] + except KeyError: try: - self.username_cache[procstat['pid']] = proc.uids().real - except (KeyError, AttributeError, psutil.AccessDenied): - self.username_cache[procstat['pid']] = "?" - procstat['username'] = self.username_cache[procstat['pid']] - - # Process command line (cached with internal cache) - try: - self.cmdline_cache[procstat['pid']] - except: - self.cmdline_cache[procstat['pid']] = ' '.join(proc.cmdline()) - procstat['cmdline'] = self.cmdline_cache[procstat['pid']] - - # Process status - procstat['status'] = str(proc.status())[:1].upper() - - # Process nice - try: - procstat['nice'] = proc.nice() - except psutil.AccessDenied: - procstat['nice'] = None - - # Process memory - procstat['memory_info'] = proc.memory_info() - procstat['memory_percent'] = proc.memory_percent() - - # Process CPU - procstat['cpu_times'] = proc.cpu_times() - procstat['cpu_percent'] = proc.cpu_percent(interval=0) - - # Process network connections (TCP and UDP) (Experimental) - # !!! High CPU consumption - # try: - # procstat['tcp'] = len(proc.connections(kind="tcp")) - # procstat['udp'] = len(proc.connections(kind="udp")) - # except: - # procstat['tcp'] = 0 - # procstat['udp'] = 0 - - # 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 (display "?") - # If io_tag = 1 > No access denied (display the IO rate) - # Note Disk IO stat not available on Mac OS - if not is_mac: + self.username_cache[procstat['pid']] = proc.username() + except psutil.NoSuchProcess: + self.username_cache[procstat['pid']] = "?" + except (KeyError, psutil.AccessDenied): + try: + self.username_cache[procstat['pid']] = proc.uids().real + except (KeyError, AttributeError, psutil.AccessDenied): + self.username_cache[procstat['pid']] = "?" + procstat['username'] = self.username_cache[procstat['pid']] + + # Process status, nice, memory_info and cpu_times try: - # Get the process IO counters - proc_io = proc.io_counters() - io_new = [proc_io.read_bytes, proc_io.write_bytes] - except psutil.AccessDenied: - # Access denied to process IO (no root account) - # Put 0 in all values (for sort) and io_tag = 0 (for display) - procstat['io_counters'] = [0, 0] + [0, 0] - io_tag = 0 + procstat.update(proc.as_dict(attrs=['status', 'nice', 'memory_info', 'cpu_times'])) + except psutil.NoSuchProcess: + pass else: - # For IO rate computation - # Append saved IO r/w bytes + procstat['status'] = str(procstat['status'])[:1].upper() + + if extended_stats and not self.disable_extended_tag: + procstat['extended_stats'] = True + + # CPU affinity (Windows and Linux only) + try: + procstat.update(proc.as_dict(attrs=['cpu_affinity'])) + except psutil.NoSuchProcess: + pass + except AttributeError: + procstat['cpu_affinity'] = None + # Memory extended + try: + procstat.update(proc.as_dict(attrs=['memory_info_ex'])) + except psutil.NoSuchProcess: + pass + except AttributeError: + procstat['memory_info_ex'] = None + # Number of context switch + try: + procstat.update(proc.as_dict(attrs=['num_ctx_switches'])) + except psutil.NoSuchProcess: + pass + except AttributeError: + procstat['num_ctx_switches'] = None + # Number of file descriptors (Unix only) + try: + procstat.update(proc.as_dict(attrs=['num_fds'])) + except psutil.NoSuchProcess: + pass + except AttributeError: + procstat['num_fds'] = None + # Threads number + try: + procstat.update(proc.as_dict(attrs=['num_threads'])) + except psutil.NoSuchProcess: + pass + except AttributeError: + procstat['num_threads'] = None + + # Number of handles (Windows only) + if is_windows: try: - procstat['io_counters'] = io_new + self.io_old[procstat['pid']] - except KeyError: - procstat['io_counters'] = io_new + [0, 0] - # then save the IO r/w bytes - self.io_old[procstat['pid']] = io_new - io_tag = 1 + procstat.update(proc.as_dict(attrs=['num_handles'])) + except psutil.NoSuchProcess: + pass + else: + procstat['num_handles'] = None - # Append the IO tag (for display) - procstat['io_counters'] += [io_tag] + # SWAP memory (Only on Linux based OS) + # http://www.cyberciti.biz/faq/linux-which-process-is-using-swap/ + if is_linux: + try: + procstat['memory_swap'] = sum([v.swap for v in proc.memory_maps()]) + except psutil.NoSuchProcess: + pass + except psutil.AccessDenied: + procstat['memory_swap'] = None + + # Process network connections (TCP and UDP) + try: + procstat['tcp'] = len(proc.connections(kind="tcp")) + procstat['udp'] = len(proc.connections(kind="udp")) + except: + procstat['tcp'] = None + procstat['udp'] = None + + # IO Nice + # http://pythonhosted.org/psutil/#psutil.Process.ionice + if is_linux or is_windows: + try: + procstat.update(proc.as_dict(attrs=['ionice'])) + except psutil.NoSuchProcess: + pass + else: + procstat['ionice'] = None + + #logger.debug(procstat) return procstat def update(self): - """Update the processes stats.""" + """ + Update the processes stats + """ # Reset the stats self.processlist = [] self.processcount = {'total': 0, 'running': 0, 'sleeping': 0, 'thread': 0} @@ -165,37 +314,71 @@ class GlancesProcesses(object): # Get the time since last update time_since_update = getTimeSinceLastUpdate('process_disk') - # For each existing process... + # Build an internal dict with only mandatories stats (sort keys) + processdict = {} for proc in psutil.process_iter(): + # If self.get_max_processes() is None: Only retreive mandatory stats + # Else: retreive mandatoryadn standard stast + s = self.__get_process_stats(proc, + mandatory_stats=True, + standard_stats=self.get_max_processes() is None) + # Continue to the next process if it has to be filtered + if s is None or (self.is_filtered(s['cmdline']) and self.is_filtered(s['name'])): + continue + # Ok add the process to the list + processdict[proc] = s + # ignore the 'idle' process on Windows and *BSD + # ignore the 'kernel_task' process on OS X + # waiting for upstream patch from psutil + if (is_bsd and processdict[proc]['name'] == 'idle' or + is_windows and processdict[proc]['name'] == 'System Idle Process' or + is_mac and processdict[proc]['name'] == 'kernel_task'): + continue + # Update processcount (global statistics) try: - # Get stats using the PSUtil - procstat = self.__get_process_stats(proc) + self.processcount[str(proc.status())] += 1 + except KeyError: + # Key did not exist, create it + self.processcount[str(proc.status())] = 1 + else: + self.processcount['total'] += 1 + # Update thread number (global statistics) + try: + self.processcount['thread'] += proc.num_threads() + except: + pass + + if self.get_max_processes() is not None: + # Sort the internal dict and cut the top N (Return a list of tuple) + # tuple=key (proc), dict (returned by __get_process_stats) + processiter = sorted(processdict.items(), key=lambda x: x[1][self.getsortkey()], reverse=True) + first = True + for i in processiter[0:self.get_max_processes()]: + # Already existing mandatory stats + procstat = i[1] + # Update with standard stats + # and extended stats but only for TOP (first) process + s = self.__get_process_stats(i[0], + mandatory_stats=False, + standard_stats=True, + extended_stats=first) + if s is None: + continue + procstat.update(s) # Add a specific time_since_update stats for bitrate procstat['time_since_update'] = time_since_update - # ignore the 'idle' process on Windows and *BSD - # ignore the 'kernel_task' process on OS X - # waiting for upstream patch from psutil - if (is_bsd and procstat['name'] == 'idle' or - is_windows and procstat['name'] == 'System Idle Process' or - is_mac and procstat['name'] == 'kernel_task'): - continue - # Update processcount (global statistics) - try: - self.processcount[str(proc.status())] += 1 - except KeyError: - # Key did not exist, create it - self.processcount[str(proc.status())] = 1 - else: - self.processcount['total'] += 1 - # Update thread number (global statistics) - try: - self.processcount['thread'] += proc.num_threads() - except: - pass - except (psutil.NoSuchProcess, psutil.AccessDenied): - continue - else: - # Update processlist + # Update process list + self.processlist.append(procstat) + # Next... + first = False + else: + # Get all the processes + for i in processdict.items(): + # Already existing mandatory and standard stats + procstat = i[1] + # Add a specific time_since_update stats for bitrate + procstat['time_since_update'] = time_since_update + # Update process list self.processlist.append(procstat) # Clean internals caches if timeout is reached @@ -214,13 +397,34 @@ class GlancesProcesses(object): return self.processlist def getsortkey(self): + """Get the current sort key""" + if self.getmanualsortkey() is not None: + return self.getmanualsortkey() + else: + return self.getautosortkey() + + def getmanualsortkey(self): + """Get the current sort key for manual sort.""" + return self.processmanualsort + + def getautosortkey(self): """Get the current sort key for automatic sort.""" - return self.processsort + return self.processautosort + + def setmanualsortkey(self, sortedby): + """Set the current sort key for manual sort.""" + self.processmanualsort = sortedby + return self.processmanualsort - def setsortkey(self, sortedby): + def setautosortkey(self, sortedby): """Set the current sort key for automatic sort.""" - self.processsort = sortedby - return self.processsort + self.processautosort = sortedby + return self.processautosort + + def resetsort(self): + """Set the default sort: Auto""" + self.setmanualsortkey(None) + self.setautosortkey('cpu_percent') def getsortlist(self, sortedby=None): """Get the sorted processlist.""" |