提交 4f2e051b 编写于 作者: A Alessio Sergi

Add sort processes by USER

上级 3d89e34c
......@@ -10,10 +10,14 @@ Changes:
* Glances doesn't provide a system-wide configuration file by default anymore.
Just copy it in any of the supported locations. See glances-doc.html for
more information.
* The default key bindings have been changed to:
- 'u': sort processes by USER
- 'U': show cumulative network I/O
Enhancements and new features:
* Implement a 'quick look' plugin (issue #505)
* Add sort processes by USER (issue #531)
* Add a new IP information plugin (issue #509)
* Add RabbitMQ export module (issue #540 Thk to @Katyucha)
* Add a quiet mode (-q), can be useful using with export module
......
......@@ -266,6 +266,8 @@ The following commands (key pressed) are supported while in Glances:
``T``
View network I/O as combination
``u``
Sort processes by USER
``U``
View cumulative network I/O
``w``
Delete finished warning log messages
......
......@@ -19,7 +19,6 @@
# Import Python lib
import collections
import operator
import os
import re
......@@ -50,13 +49,13 @@ class ProcessTreeNode(object):
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, root=False):
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.reverse_sorting = (self.sort_key != "name")
self.sort_reverse = sort_reverse
self.is_root = root
def __str__(self):
......@@ -87,7 +86,7 @@ class ProcessTreeNode(object):
def set_sorting(self, key, reverse):
""" Set sorting key or func for user with __iter__ (affects the whole tree from this node). """
if self.sort_key != key or self.reverse_sorting != reverse:
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()
......@@ -98,7 +97,7 @@ class ProcessTreeNode(object):
def get_weight(self):
""" Return "weight" of a process and all its children for sorting. """
if self.sort_key == "name":
if self.sort_key == 'name' or self.sort_key == 'username':
return self.stats[self.sort_key]
# sum ressource usage for self and children
......@@ -138,7 +137,7 @@ class ProcessTreeNode(object):
# 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.reverse_sorting)
key=self.__class__.get_weight, reverse=self.sort_reverse)
self.children_sorted = True
for child in self.children:
for n in iter(child):
......@@ -156,7 +155,7 @@ class ProcessTreeNode(object):
# 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.reverse_sorting)
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:
......@@ -172,14 +171,14 @@ class ProcessTreeNode(object):
nodes_to_search.extend(current_node.children)
@staticmethod
def build_tree(process_dict, sort_key, hide_kernel_threads):
def build_tree(process_dict, sort_key, sort_reverse, hide_kernel_threads):
""" Build a process tree using using parent/child relationships, and 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 process_dict.items():
new_node = ProcessTreeNode(process, stats, sort_key)
new_node = ProcessTreeNode(process, stats, sort_key, sort_reverse)
try:
parent_process = process.parent()
except psutil.NoSuchProcess:
......@@ -347,6 +346,14 @@ class GlancesProcesses(object):
""" Return True if process tree is enabled, False instead. """
return self._enable_tree
@property
def sort_reverse(self):
"""Return True to sort processes in reverse 'key' order, False instead."""
if self.sort_key == 'name' or self.sort_key == 'username':
return False
return True
def __get_mandatory_stats(self, proc, procstat):
"""
Get mandatory_stats: need for the sorting/filter step
......@@ -355,8 +362,9 @@ class GlancesProcesses(object):
procstat['mandatory_stats'] = True
# Process CPU, MEM percent and name
procstat.update(
proc.as_dict(attrs=['cpu_percent', 'memory_percent', 'name', 'cpu_times'], ad_value=''))
procstat.update(proc.as_dict(
attrs=['username', 'cpu_percent', 'memory_percent',
'name', 'cpu_times'], ad_value=''))
if procstat['cpu_percent'] == '' or procstat['memory_percent'] == '':
# Do not display process if we cannot get the basic
# cpu_percent or memory_percent stats
......@@ -611,6 +619,7 @@ class GlancesProcesses(object):
if self._enable_tree:
self.process_tree = ProcessTreeNode.build_tree(processdict,
self.sort_key,
self.sort_reverse,
self.no_kernel_threads)
for i, node in enumerate(self.process_tree):
......@@ -636,8 +645,9 @@ class GlancesProcesses(object):
# 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(
processdict.items(), key=lambda x: x[1][self.sort_key], reverse=True)
processiter = sorted(processdict.items(),
key=lambda x: x[1][self.sort_key],
reverse=self.sort_reverse)
except (KeyError, TypeError) as e:
logger.error("Cannot sort process list by {0}: {1}".format(self.sort_key, e))
logger.error("%s" % str(processdict.items()[0]))
......@@ -707,36 +717,5 @@ class GlancesProcesses(object):
def sort_key(self, key):
"""Set the current sort key."""
self._sort_key = key
if not self.auto_sort and self._enable_tree and self.process_tree is not None:
self.process_tree.set_sorting(key, key != "name")
def getsortlist(self, sortedby=None):
"""Get the sorted processlist."""
if sortedby is None:
# No need to sort...
return self.processlist
sortedreverse = True
if sortedby == 'name':
sortedreverse = False
if sortedby == 'io_counters':
# Specific case for io_counters
# Sum of io_r + io_w
try:
# Sort process by IO rate (sum IO read + IO write)
self.processlist.sort(key=lambda process: process[sortedby][0] -
process[sortedby][2] + process[sortedby][1] -
process[sortedby][3],
reverse=sortedreverse)
except Exception:
self.processlist.sort(key=operator.itemgetter('cpu_percent'),
reverse=sortedreverse)
else:
# Others sorts
self.processlist.sort(key=operator.itemgetter(sortedby),
reverse=sortedreverse)
return self.processlist
glances_processes = GlancesProcesses()
......@@ -328,7 +328,11 @@ class _GlancesCurses(object):
# 'T' > View network traffic as sum Rx+Tx
self.args.network_sum = not self.args.network_sum
elif self.pressedkey == ord('u'):
# 'u' > View cumulative network IO (instead of bitrate)
# 'u' > Sort processes by USER
glances_processes.auto_sort = False
glances_processes.sort_key = 'username'
elif self.pressedkey == ord('U'):
# 'U' > View cumulative network I/O (instead of bitrate)
self.args.network_cumul = not self.args.network_cumul
elif self.pressedkey == ord('w'):
# 'w' > Delete finished warning logs
......
......@@ -89,44 +89,49 @@ class Plugin(GlancesPlugin):
msg = msg_col2.format("w", _("Delete warning alerts"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
msg = msg_col.format("p", _("Sort processes by name"))
msg = msg_col.format("u", _("Sort processes by USER"))
ret.append(self.curse_add_line(msg))
msg = msg_col2.format("x", _("Delete warning and critical alerts"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
msg = msg_col.format("i", _("Sort processes by I/O rate"))
msg = msg_col.format("p", _("Sort processes by name"))
ret.append(self.curse_add_line(msg))
msg = msg_col2.format("1", _("Global CPU or per-CPU stats"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
msg = msg_col.format("t", _("Sort processes by CPU times"))
msg = msg_col.format("i", _("Sort processes by I/O rate"))
ret.append(self.curse_add_line(msg))
msg = msg_col2.format("h", _("Show/hide this help screen"))
msg = msg_col2.format("D", _("Enable/disable Docker stats"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
msg = msg_col.format("d", _("Show/hide disk I/O stats"))
msg = msg_col.format("t", _("Sort processes by TIME"))
ret.append(self.curse_add_line(msg))
msg = msg_col2.format("T", _("View network I/O as combination"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
msg = msg_col.format("d", _("Show/hide disk I/O stats"))
ret.append(self.curse_add_line(msg))
msg = msg_col2.format("U", _("View cumulative network I/O"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
msg = msg_col.format("f", _("Show/hide filesystem stats"))
ret.append(self.curse_add_line(msg))
msg = msg_col2.format("u", _("View cumulative network I/O"))
msg = msg_col2.format("F", _("Show filesystem free space"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
msg = msg_col.format("n", _("Show/hide network stats"))
ret.append(self.curse_add_line(msg))
msg = msg_col2.format("F", _("Show filesystem free space"))
msg = msg_col2.format("g", _("Generate graphs for current history"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
msg = msg_col.format("s", _("Show/hide sensors stats"))
ret.append(self.curse_add_line(msg))
msg = msg_col2.format("g", _("Generate graphs for current history"))
msg = msg_col2.format("r", _("Reset history"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
msg = msg_col.format("2", _("Show/hide left sidebar"))
ret.append(self.curse_add_line(msg))
msg = msg_col2.format("r", _("Reset history"))
msg = msg_col2.format("h", _("Show/hide this help screen"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
msg = msg_col.format("z", _("Enable/disable processes stats"))
......@@ -134,19 +139,15 @@ class Plugin(GlancesPlugin):
msg = msg_col2.format("q", _("Quit (Esc and Ctrl-C also work)"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
msg = msg_col.format("e", _("Enable/disable top extended stats"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
msg = msg_col.format("/", _("Enable/disable short processes name"))
msg = msg_col.format("3", _("Enable/disable quick look plugin"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
msg = msg_col.format("D", _("Enable/disable Docker stats"))
msg = msg_col.format("e", _("Enable/disable top extended stats"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
msg = msg_col.format("3", _("Enable/disable Quicklook plugin"))
msg = msg_col.format("/", _("Enable/disable short processes name"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
ret.append(self.curse_new_line())
msg = '{0}: {1}'.format("ENTER", _("Edit the process filter pattern"))
......
......@@ -379,7 +379,7 @@ class Plugin(GlancesPlugin):
msg = '{0:>6}'.format(_("PID"))
ret.append(self.curse_add_line(msg))
msg = ' {0:10}'.format(_("USER"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'username' else 'DEFAULT'))
msg = '{0:>4}'.format(_("NI"))
ret.append(self.curse_add_line(msg))
msg = '{0:>2}'.format(_("S"))
......@@ -397,14 +397,13 @@ class Plugin(GlancesPlugin):
self.tag_proc_time = True
if glances_processes.is_tree_enabled():
ret.extend(self.get_process_tree_curses_data(self.sortstats(process_sort_key),
args,
first_level=True,
max_node_count=glances_processes.max_processes))
ret.extend(self.get_process_tree_curses_data(
self.sort_stats(process_sort_key), args, first_level=True,
max_node_count=glances_processes.max_processes))
else:
# Loop over processes (sorted by the sort key previously compute)
first = True
for p in self.sortstats(process_sort_key):
for p in self.sort_stats(process_sort_key):
ret.extend(self.get_process_curses_data(p, first, args))
# End of extended stats
first = False
......@@ -412,16 +411,12 @@ class Plugin(GlancesPlugin):
# Return the message with decoration
return ret
def sortstats(self, sortedby=None):
def sort_stats(self, sortedby=None):
"""Return the stats sorted by sortedby variable."""
if sortedby is None:
# No need to sort...
return self.stats
sortedreverse = True
if sortedby == 'name':
sortedreverse = False
tree = glances_processes.is_tree_enabled()
if sortedby == 'io_counters' and not tree:
......@@ -432,18 +427,18 @@ class Plugin(GlancesPlugin):
self.stats.sort(key=lambda process: process[sortedby][0] -
process[sortedby][2] + process[sortedby][1] -
process[sortedby][3],
reverse=sortedreverse)
reverse=glances_processes.sort_reverse)
except Exception:
self.stats.sort(key=operator.itemgetter('cpu_percent'),
reverse=sortedreverse)
reverse=glances_processes.sort_reverse)
else:
# Others sorts
if tree:
self.stats.set_sorting(sortedby, sortedreverse)
self.stats.set_sorting(sortedby, glances_processes.sort_reverse)
else:
try:
self.stats.sort(key=operator.itemgetter(sortedby),
reverse=sortedreverse)
reverse=glances_processes.sort_reverse)
except (KeyError, TypeError):
self.stats.sort(key=operator.itemgetter('name'),
reverse=False)
......
......@@ -205,6 +205,9 @@ Sort process by CPU times (TIME+)
View network I/O as combination
.TP
.B u
Sort processes by USER
.TP
.B U
View cumulative network I/O
.TP
.B w
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册