From 98d1e5cc4cd379cd894cbb9338c9b52d24a06ff8 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Sun, 20 Mar 2016 14:31:37 +0100 Subject: [PATCH] Split processes and processes_tree --- glances/processes.py | 197 +--------------------------------- glances/processes_tree.py | 217 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 219 insertions(+), 195 deletions(-) create mode 100644 glances/processes_tree.py diff --git a/glances/processes.py b/glances/processes.py index 3e5126bc..7e27a002 100644 --- a/glances/processes.py +++ b/glances/processes.py @@ -2,7 +2,7 @@ # # This file is part of Glances. # -# Copyright (C) 2015 Nicolargo +# Copyright (C) 2016 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 @@ -17,7 +17,6 @@ # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . -import collections import os import re @@ -25,6 +24,7 @@ from glances.compat import iteritems, itervalues, listitems from glances.globals import BSD, LINUX, OSX, WINDOWS from glances.logger import logger from glances.timer import Timer, getTimeSinceLastUpdate +from glances.processes_tree import ProcessTreeNode import psutil @@ -39,199 +39,6 @@ def is_kernel_thread(proc): return False -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 - - class GlancesProcesses(object): """Get processed stats using the psutil library.""" diff --git a/glances/processes_tree.py b/glances/processes_tree.py new file mode 100644 index 00000000..b3a35c68 --- /dev/null +++ b/glances/processes_tree.py @@ -0,0 +1,217 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Glances. +# +# Copyright (C) 2016 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