mirror of
https://github.com/nicolargo/glances.git
synced 2024-12-29 20:21:35 +03:00
Correct an issue on MacOS and SunOS for diskIO
This commit is contained in:
parent
01c6fbe80f
commit
f81ccfc24d
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user