From f81ccfc24d3ebf7c10ce64eecc759289973f34c6 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Thu, 18 Jan 2018 21:43:00 +0100 Subject: [PATCH] Correct an issue on MacOS and SunOS for diskIO --- docs/cmds.rst | 4 - docs/man/glances.1 | 7 +- glances/main.py | 6 +- glances/processes.py | 162 ++-------------------------- glances/processes_tree.py | 217 -------------------------------------- glances/standalone.py | 12 +-- 6 files changed, 21 insertions(+), 387 deletions(-) delete mode 100644 glances/processes_tree.py diff --git a/docs/cmds.rst b/docs/cmds.rst index 9dc46690..eee4ba3b 100644 --- a/docs/cmds.rst +++ b/docs/cmds.rst @@ -186,10 +186,6 @@ Command-Line Options hide kernel threads in process list (not available on Windows) -.. option:: --tree - - display processes as a tree (Linux only) - .. option:: -b, --byte display network rate in byte per second diff --git a/docs/man/glances.1 b/docs/man/glances.1 index 7b052c20..7f00f003 100644 --- a/docs/man/glances.1 +++ b/docs/man/glances.1 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "GLANCES" "1" "Jan 17, 2018" "3.0_DEV" "Glances" +.TH "GLANCES" "1" "Jan 18, 2018" "3.0_DEV" "Glances" .SH NAME glances \- An eye on your system . @@ -273,11 +273,6 @@ hide kernel threads in process list (not available on Windows) .UNINDENT .INDENT 0.0 .TP -.B \-\-tree -display processes as a tree (Linux only) -.UNINDENT -.INDENT 0.0 -.TP .B \-b, \-\-byte display network rate in byte per second .UNINDENT diff --git a/glances/main.py b/glances/main.py index eebae777..471df47c 100644 --- a/glances/main.py +++ b/glances/main.py @@ -213,9 +213,9 @@ Examples of use: if not WINDOWS: parser.add_argument('--hide-kernel-threads', action='store_true', default=False, dest='no_kernel_threads', help='hide kernel threads in process list (not available on Windows)') - if LINUX: - parser.add_argument('--tree', action='store_true', default=False, - dest='process_tree', help='display processes as a tree (Linux only)') + # if LINUX: + # parser.add_argument('--tree', action='store_true', default=False, + # dest='process_tree', help='display processes as a tree (Linux only)') parser.add_argument('-b', '--byte', action='store_true', default=False, dest='byte', help='display network rate in byte per second') parser.add_argument('--diskio-show-ramfs', action='store_true', default=False, diff --git a/glances/processes.py b/glances/processes.py index 897acd62..859eebfd 100644 --- a/glances/processes.py +++ b/glances/processes.py @@ -21,9 +21,8 @@ import operator import os from glances.compat import iteritems, itervalues, listitems -from glances.globals import BSD, LINUX, MACOS, WINDOWS +from glances.globals import BSD, LINUX, MACOS, SUNOS, WINDOWS from glances.timer import Timer, getTimeSinceLastUpdate -from glances.processes_tree import ProcessTreeNode from glances.filter import GlancesFilter from glances.logger import logger @@ -407,11 +406,17 @@ class GlancesProcesses(object): if self.disable_tag: return + # Time since last update (for disk_io rate computation) + time_since_update = getTimeSinceLastUpdate('process_disk') + # Grab the stats mandatories_attr = ['cmdline', 'cpu_percent', 'cpu_times', - 'io_counters', 'memory_info', 'memory_percent', + 'memory_info', 'memory_percent', 'name', 'nice', 'pid', 'ppid', 'status', 'username'] + # io_counters is not available on macOS and Illumos/Solaris + if not MACOS and not SUNOS: + mandatories_attr += ['io_counters'] # and build the processes stats list self.processlist = [p.info for p in sorted(psutil.process_iter(attrs=mandatories_attr, ad_value=None), @@ -423,7 +428,7 @@ class GlancesProcesses(object): # Loop over processes and add metadata for proc in self.processlist: # Time since last update (for disk_io rate computation) - proc['time_since_update'] = getTimeSinceLastUpdate('process_disk') + proc['time_since_update'] = time_since_update # Process status (only keep the first char) proc['status'] = str(proc['status'])[:1].upper() @@ -431,10 +436,9 @@ class GlancesProcesses(object): # 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 = 0 > Access denied or first time (display "?") # If io_tag = 1 > No access denied (display the IO rate) - # Availability: all platforms except macOS and Illumos/Solaris - if proc['io_counters'] is not None: + 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 @@ -459,150 +463,6 @@ class GlancesProcesses(object): for k in self._max_values_list: self.set_max_values(k, max(i[k] for i in self.processlist)) - def update_OLD(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 - - # Get the time since last update - time_since_update = getTimeSinceLastUpdate('process_disk') - - # Reset the max dict - self.reset_max_values() - - # Update the maximum process ID (pid) number - self.processcount['pid_max'] = self.pid_max - - # Build an internal dict with only mandatories stats (sort keys) - processdict = {} - excluded_processes = set() - for proc in psutil.process_iter(): - # Ignore kernel threads if needed - if self.no_kernel_threads and not WINDOWS and is_kernel_thread(proc): - continue - - # If self.max_processes is None: Only retrieve mandatory stats - # Else: retrieve mandatory and standard stats - s = self.__get_process_stats(proc, - mandatory_stats=True, - standard_stats=self.max_processes is None) - # Check if s is note None (issue #879) - # ignore the 'idle' process on Windows and *BSD - # ignore the 'kernel_task' process on macOS - # waiting for upstream patch from psutil - if (s is None or - BSD and s['name'] == 'idle' or - WINDOWS and s['name'] == 'System Idle Process' or - MACOS and s['name'] == 'kernel_task'): - continue - # Continue to the next process if it has to be filtered - if self._filter.is_filtered(s): - excluded_processes.add(proc) - continue - - # Ok add the process to the list - processdict[proc] = s - # Update processcount (global statistics) - try: - self.processcount[str(proc.status())] += 1 - except KeyError: - # Key did not exist, create it - try: - self.processcount[str(proc.status())] = 1 - except psutil.NoSuchProcess: - pass - except psutil.NoSuchProcess: - pass - else: - self.processcount['total'] += 1 - # Update thread number (global statistics) - try: - self.processcount['thread'] += proc.num_threads() - except Exception: - pass - - if self._enable_tree: - self.process_tree = ProcessTreeNode.build_tree(processdict, - self.sort_key, - self.sort_reverse, - self.no_kernel_threads, - excluded_processes) - - for i, node in enumerate(self.process_tree): - # Only retreive stats for visible processes (max_processes) - if self.max_processes is not None and i >= self.max_processes: - break - - # add standard stats - new_stats = self.__get_process_stats(node.process, - mandatory_stats=False, - standard_stats=True, - extended_stats=False) - if new_stats is not None: - node.stats.update(new_stats) - - # Add a specific time_since_update stats for bitrate - node.stats['time_since_update'] = time_since_update - - else: - # Process optimization - # Only retreive stats for visible processes (max_processes) - if self.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) - try: - processiter = sorted(iteritems(processdict), - key=lambda x: x[1][self.sort_key], - reverse=self.sort_reverse) - except (KeyError, TypeError) as e: - logger.error("Cannot sort process list by {}: {}".format(self.sort_key, e)) - logger.error('{}'.format(listitems(processdict)[0])) - # Fallback to all process (issue #423) - processloop = iteritems(processdict) - first = False - else: - processloop = processiter[0:self.max_processes] - first = True - else: - # Get all processes stats - processloop = iteritems(processdict) - first = False - - for i in processloop: - # Already existing mandatory stats - procstat = i[1] - if self.max_processes is not None: - # 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 - # Update process list - self.processlist.append(procstat) - # Next... - first = False - - # Build the all processes list used by the AMPs - self.allprocesslist = [p for p in itervalues(processdict)] - - # Clean internals caches if timeout is reached - if self.cache_timer.finished(): - self.username_cache = {} - self.cmdline_cache = {} - # Restart the timer - self.cache_timer.reset() - def getcount(self): """Get the number of processes.""" return self.processcount diff --git a/glances/processes_tree.py b/glances/processes_tree.py deleted file mode 100644 index f73b44ea..00000000 --- a/glances/processes_tree.py +++ /dev/null @@ -1,217 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Glances. -# -# Copyright (C) 2017 Nicolargo -# -# 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 . - -import collections - -from glances.compat import iteritems - -import psutil - - -class ProcessTreeNode(object): - - """Represent a process tree. - - We avoid recursive algorithm to manipulate the tree because function - calls are expensive with CPython. - """ - - def __init__(self, process=None, stats=None, sort_key=None, sort_reverse=True, root=False): - self.process = process - self.stats = stats - self.children = [] - self.children_sorted = False - self.sort_key = sort_key - self.sort_reverse = sort_reverse - self.is_root = root - - def __str__(self): - """Return the tree as a string for debugging.""" - lines = [] - nodes_to_print = collections.deque([collections.deque([("#", self)])]) - while nodes_to_print: - indent_str, current_node = nodes_to_print[-1].pop() - if not nodes_to_print[-1]: - nodes_to_print.pop() - if current_node.is_root: - lines.append(indent_str) - else: - lines.append("%s[%s]" % - (indent_str, current_node.process.name())) - indent_str = " " * (len(lines[-1]) - 1) - children_nodes_to_print = collections.deque() - for child in current_node.children: - if child is current_node.children[-1]: - tree_char = "└─" - else: - tree_char = "├─" - children_nodes_to_print.appendleft( - (indent_str + tree_char, child)) - if children_nodes_to_print: - nodes_to_print.append(children_nodes_to_print) - return "\n".join(lines) - - def set_sorting(self, key, reverse): - """Set sorting key or func for use with __iter__. - - This affects the whole tree from this node. - """ - if self.sort_key != key or self.sort_reverse != reverse: - nodes_to_flag_unsorted = collections.deque([self]) - while nodes_to_flag_unsorted: - current_node = nodes_to_flag_unsorted.pop() - current_node.children_sorted = False - current_node.sort_key = key - current_node.reverse_sorting = reverse - nodes_to_flag_unsorted.extend(current_node.children) - - def get_weight(self): - """Return 'weight' of a process and all its children for sorting.""" - if self.sort_key == 'name' or self.sort_key == 'username': - return self.stats[self.sort_key] - - # sum ressource usage for self and children - total = 0 - nodes_to_sum = collections.deque([self]) - while nodes_to_sum: - current_node = nodes_to_sum.pop() - if isinstance(self.sort_key, collections.Callable): - total += self.sort_key(current_node.stats) - elif self.sort_key == "io_counters": - stats = current_node.stats[self.sort_key] - total += stats[0] - stats[2] + stats[1] - stats[3] - elif self.sort_key == "cpu_times": - total += sum(current_node.stats[self.sort_key]) - else: - total += current_node.stats[self.sort_key] - nodes_to_sum.extend(current_node.children) - - return total - - def __len__(self): - """Return the number of nodes in the tree.""" - total = 0 - nodes_to_sum = collections.deque([self]) - while nodes_to_sum: - current_node = nodes_to_sum.pop() - if not current_node.is_root: - total += 1 - nodes_to_sum.extend(current_node.children) - return total - - def __iter__(self): - """Iterator returning ProcessTreeNode in sorted order, recursively.""" - if not self.is_root: - yield self - if not self.children_sorted: - # optimization to avoid sorting twice (once when limiting the maximum processes to grab stats for, - # and once before displaying) - self.children.sort( - key=self.__class__.get_weight, reverse=self.sort_reverse) - self.children_sorted = True - for child in self.children: - for n in iter(child): - yield n - - def iter_children(self, exclude_incomplete_stats=True): - """Iterator returning ProcessTreeNode in sorted order. - - Return only children of this node, non recursive. - - If exclude_incomplete_stats is True, exclude processes not - having full statistics. It can happen after a resort (change of - sort key) because process stats are not grabbed immediately, but - only at next full update. - """ - if not self.children_sorted: - # optimization to avoid sorting twice (once when limiting the maximum processes to grab stats for, - # and once before displaying) - self.children.sort( - key=self.__class__.get_weight, reverse=self.sort_reverse) - self.children_sorted = True - for child in self.children: - if not exclude_incomplete_stats or "time_since_update" in child.stats: - yield child - - def find_process(self, process): - """Search in tree for the ProcessTreeNode owning process. - - Return it or None if not found. - """ - nodes_to_search = collections.deque([self]) - while nodes_to_search: - current_node = nodes_to_search.pop() - if not current_node.is_root and current_node.process.pid == process.pid: - return current_node - nodes_to_search.extend(current_node.children) - - @staticmethod - def build_tree(process_dict, sort_key, sort_reverse, hide_kernel_threads, excluded_processes): - """Build a process tree using using parent/child relationships. - - Return the tree root node. - """ - tree_root = ProcessTreeNode(root=True) - nodes_to_add_last = collections.deque() - - # first pass: add nodes whose parent are in the tree - for process, stats in iteritems(process_dict): - new_node = ProcessTreeNode(process, stats, sort_key, sort_reverse) - try: - parent_process = process.parent() - except psutil.NoSuchProcess: - # parent is dead, consider no parent - parent_process = None - if (parent_process is None) or (parent_process in excluded_processes): - # no parent, or excluded parent, add this node at the top level - tree_root.children.append(new_node) - else: - parent_node = tree_root.find_process(parent_process) - if parent_node is not None: - # parent is already in the tree, add a new child - parent_node.children.append(new_node) - else: - # parent is not in tree, add this node later - nodes_to_add_last.append(new_node) - - # next pass(es): add nodes to their parents if it could not be done in - # previous pass - while nodes_to_add_last: - # pop from left and append to right to avoid infinite loop - node_to_add = nodes_to_add_last.popleft() - try: - parent_process = node_to_add.process.parent() - except psutil.NoSuchProcess: - # parent is dead, consider no parent, add this node at the top - # level - tree_root.children.append(node_to_add) - else: - if (parent_process is None) or (parent_process in excluded_processes): - # no parent, or excluded parent, add this node at the top level - tree_root.children.append(node_to_add) - else: - parent_node = tree_root.find_process(parent_process) - if parent_node is not None: - # parent is already in the tree, add a new child - parent_node.children.append(node_to_add) - else: - # parent is not in tree, add this node later - nodes_to_add_last.append(node_to_add) - - return tree_root diff --git a/glances/standalone.py b/glances/standalone.py index 2db3a6d5..9d4fc67b 100644 --- a/glances/standalone.py +++ b/glances/standalone.py @@ -64,12 +64,12 @@ class GlancesStandalone(object): # Ignore kernel threads in process list glances_processes.disable_kernel_threads() - try: - if args.process_tree: - # Enable process tree view - glances_processes.enable_tree() - except AttributeError: - pass + # try: + # if args.process_tree: + # # Enable process tree view + # glances_processes.enable_tree() + # except AttributeError: + # pass # Initial system informations update self.stats.update()