Correct an issue on MacOS and SunOS for diskIO

This commit is contained in:
nicolargo 2018-01-18 21:43:00 +01:00
parent 01c6fbe80f
commit f81ccfc24d
6 changed files with 21 additions and 387 deletions

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -1,217 +0,0 @@
# -*- coding: utf-8 -*-
#
# This file is part of Glances.
#
# Copyright (C) 2017 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 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

View File

@ -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()