summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNicolargo <nicolas@nicolargo.com>2014-08-15 11:45:51 +0200
committerNicolargo <nicolas@nicolargo.com>2014-08-15 11:45:51 +0200
commit37edbfc5a18f2bf424b1f34f2d158d03f18983c0 (patch)
treefadab1cb72ba94ff4c1ff542fbd42f769960c822
parentc26a7703d53f4f28f9115b784c4a249b9dd4b9ec (diff)
parent277f6baaf016569dc9e732e6912fcd22144c46b1 (diff)
Merge branch 'feature/issue403' into develop
-rw-r--r--glances/core/glances_globals.py3
-rw-r--r--glances/core/glances_main.py2
-rw-r--r--glances/core/glances_processes.py76
-rw-r--r--glances/core/glances_standalone.py8
-rw-r--r--glances/outputs/glances_curses.py7
-rw-r--r--glances/plugins/glances_help.py4
-rw-r--r--glances/plugins/glances_plugin.py2
-rw-r--r--glances/plugins/glances_processlist.py73
8 files changed, 158 insertions, 17 deletions
diff --git a/glances/core/glances_globals.py b/glances/core/glances_globals.py
index 3ad4c3d3..6a9ecda6 100644
--- a/glances/core/glances_globals.py
+++ b/glances/core/glances_globals.py
@@ -66,11 +66,10 @@ logger = glancesLogger()
# Instances shared between all Glances scripts
# ============================================
-# glances_processes for processcount and processlist plugins
+# Glances_processes for processcount and processlist plugins
from glances.core.glances_processes import GlancesProcesses
glances_processes = GlancesProcesses()
# The global instance for the logs
from glances.core.glances_logs import GlancesLogs
glances_logs = GlancesLogs()
-
diff --git a/glances/core/glances_main.py b/glances/core/glances_main.py
index 7dc2f66a..c7b9b90a 100644
--- a/glances/core/glances_main.py
+++ b/glances/core/glances_main.py
@@ -74,6 +74,8 @@ class GlancesMain(object):
dest='disable_sensors', help=_('disable sensors module'))
parser.add_argument('--disable-process', action='store_true', default=False,
dest='disable_process', help=_('disable process module'))
+ parser.add_argument('--disable-process-extended', action='store_true', default=False,
+ dest='disable_process_extended', help=_('disable extended stats on top process'))
parser.add_argument('--disable-log', action='store_true', default=False,
dest='disable_log', help=_('disable log module'))
# CSV output feature
diff --git a/glances/core/glances_processes.py b/glances/core/glances_processes.py
index 48f214dc..ef32e138 100644
--- a/glances/core/glances_processes.py
+++ b/glances/core/glances_processes.py
@@ -52,6 +52,9 @@ class GlancesProcesses(object):
# Default is to enable the processes stats
self.disable_tag = False
+ # Extended stats for top process is enable by default
+ self.disable_extended_tag = False
+
# Maximum number of processes showed in the UI interface
# None if no limit
self.max_processes = None
@@ -65,6 +68,15 @@ class GlancesProcesses(object):
"""Disable process stats."""
self.disable_tag = True
+ def enable_extended(self):
+ """Enable extended process stats."""
+ self.disable_extended_tag = False
+ self.update()
+
+ def disable_extended(self):
+ """Disable extended process stats."""
+ self.disable_extended_tag = True
+
def set_max_processes(self, value):
"""Set the maximum number of processes showed in the UI interfaces"""
self.max_processes = value
@@ -84,14 +96,16 @@ class GlancesProcesses(object):
=> cpu_percent, memory_percent, io_counters, name
standard_stats: for all the displayed processes
=> username, cmdline, status, memory_info, cpu_times
- extended_stats: only for top processes (!!! to be implemented)
- => connections (UDP/TCP), memory_swap
+ extended_stats: only for top processes (see issue #403)
+ => connections (UDP/TCP), memory_swap...
"""
# Process ID (always)
procstat = proc.as_dict(attrs=['pid'])
if mandatory_stats:
+ procstat['mandatory_stats'] = True
+
# Process CPU, MEM percent and name
procstat.update(proc.as_dict(attrs=['cpu_percent', 'memory_percent', 'name'], ad_value=''))
@@ -126,6 +140,8 @@ class GlancesProcesses(object):
procstat['io_counters'] += [io_tag]
if standard_stats:
+ procstat['standard_stats'] = True
+
# Process username (cached with internal cache)
try:
self.username_cache[procstat['pid']]
@@ -154,21 +170,50 @@ class GlancesProcesses(object):
procstat.update(proc.as_dict(attrs=['status', 'nice', 'memory_info', 'cpu_times']))
procstat['status'] = str(procstat['status'])[:1].upper()
- if extended_stats:
- # Process network connections (TCP and UDP) (Experimental)
+ if extended_stats and not self.disable_extended_tag:
+ procstat['extended_stats'] = True
+
+ # CPU affinity
+ # Memory extended
+ # Number of context switch
+ # Number of file descriptors (Unix only)
+ # Threads number
+ procstat.update(proc.as_dict(attrs=['cpu_affinity',
+ 'memory_info_ex',
+ 'num_ctx_switches',
+ 'num_fds',
+ 'num_threads']))
+
+ # Number of handles (Windows only)
+ if is_windows:
+ procstat.update(proc.as_dict(attrs=['num_handles']))
+ else:
+ procstat['num_handles'] = None
+
+ # SWAP memory (Only on Linux based OS)
+ # http://www.cyberciti.biz/faq/linux-which-process-is-using-swap/
+ if is_linux:
+ try:
+ procstat['memory_swap'] = sum([v.swap for v in proc.memory_maps()])
+ except psutil.AccessDenied:
+ procstat['memory_swap'] = None
+
+ # Process network connections (TCP and UDP)
try:
procstat['tcp'] = len(proc.connections(kind="tcp"))
procstat['udp'] = len(proc.connections(kind="udp"))
except:
- procstat['tcp'] = 0
- procstat['udp'] = 0
+ procstat['tcp'] = None
+ procstat['udp'] = None
- # SWAP memory
- # Only on Linux based OS
- # http://www.cyberciti.biz/faq/linux-which-process-is-using-swap/
- if is_linux:
- logger.debug(proc.memory_maps())
- procstat['memory_swap'] = sum([ v.swap for v in proc.memory_maps() ])
+ # IO Nice
+ # http://pythonhosted.org/psutil/#psutil.Process.ionice
+ if is_linux or is_windows:
+ procstat.update(proc.as_dict(attrs=['ionice']))
+ else:
+ procstat['ionice'] = None
+
+ #logger.debug(procstat)
return procstat
@@ -220,17 +265,22 @@ 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)
processiter = sorted(processdict.items(), key=lambda x: x[1][self.getsortkey()], reverse=True)
+ first = True
for i in processiter[0:self.get_max_processes()]:
# Already existing mandatory stats
procstat = i[1]
# Update with standard stats
+ # and extended stats but only for TOP (first) process
procstat.update(self.__get_process_stats(i[0],
mandatory_stats=False,
- standard_stats=True))
+ standard_stats=True,
+ extended_stats=first))
# 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
else:
# Get all the processes
for i in processdict.items():
diff --git a/glances/core/glances_standalone.py b/glances/core/glances_standalone.py
index d40a7216..f74f120d 100644
--- a/glances/core/glances_standalone.py
+++ b/glances/core/glances_standalone.py
@@ -43,6 +43,14 @@ class GlancesStandalone(object):
logger.warning(_("Maximum displayed processes is not configured (high CPU consumption)"))
glances_processes.set_max_processes(max_processes)
+ # If process extended stats is disabled by user
+ if args.disable_process_extended:
+ logger.info(_("Extended stats for top process is disabled"))
+ glances_processes.disable_extended()
+ else:
+ logger.debug(_("Extended stats for top process is enabled (default behavor)"))
+ glances_processes.enable_extended()
+
# Initial system informations update
self.stats.update()
diff --git a/glances/outputs/glances_curses.py b/glances/outputs/glances_curses.py
index 046dfefd..5784cf29 100644
--- a/glances/outputs/glances_curses.py
+++ b/glances/outputs/glances_curses.py
@@ -215,6 +215,13 @@ class GlancesCurses(object):
elif self.pressedkey == ord('d'):
# 'd' > Show/hide disk I/O stats
self.args.disable_diskio = not self.args.disable_diskio
+ elif self.pressedkey == ord('e'):
+ # 'e' > Enable/Disable extended stats for top process
+ self.args.disable_process_extended = not self.args.disable_process_extended
+ if self.args.disable_process_extended:
+ glances_processes.disable_extended()
+ else:
+ glances_processes.enable_extended()
elif self.pressedkey == ord('f'):
# 'f' > Show/hide fs stats
self.args.disable_fs = not self.args.disable_fs
diff --git a/glances/plugins/glances_help.py b/glances/plugins/glances_help.py
index 17a66250..9ceff591 100644
--- a/glances/plugins/glances_help.py
+++ b/glances/plugins/glances_help.py
@@ -124,7 +124,9 @@ class Plugin(GlancesPlugin):
msg = msg_col2.format("r", _("Reset history"))
ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line())
- msg = msg_col.format("q", _("Quit (Esc and Ctrl-C also work)"))
+ msg = msg_col.format("e", _("Enable/disable top extended stats"))
+ ret.append(self.curse_add_line(msg))
+ msg = msg_col2.format("q", _("Quit (Esc and Ctrl-C also work)"))
ret.append(self.curse_add_line(msg))
# Return the message with decoration
diff --git a/glances/plugins/glances_plugin.py b/glances/plugins/glances_plugin.py
index cc94261d..29071ed3 100644
--- a/glances/plugins/glances_plugin.py
+++ b/glances/plugins/glances_plugin.py
@@ -40,7 +40,7 @@ class GlancesPlugin(object):
"""Init the plugin of plugins class."""
# Plugin name (= module name without glances_)
self.plugin_name = self.__class__.__module__[len('glances_'):]
- logger.debug(_("Init plugin %s") % self.plugin_name)
+ # logger.debug(_("Init plugin %s") % self.plugin_name)
# Init the args
self.args = args
diff --git a/glances/plugins/glances_processlist.py b/glances/plugins/glances_processlist.py
index de129e03..8cd4e2ef 100644
--- a/glances/plugins/glances_processlist.py
+++ b/glances/plugins/glances_processlist.py
@@ -110,6 +110,7 @@ class Plugin(GlancesPlugin):
tag_proc_time = True
# Loop over processes (sorted by the sort key previously compute)
+ first = True
for p in self.sortlist(process_sort_key):
ret.append(self.curse_new_line())
# CPU
@@ -237,6 +238,78 @@ class Plugin(GlancesPlugin):
except UnicodeEncodeError:
ret.append(self.curse_add_line("", splittable=True))
+ # Add extended stats but only for the top processes
+ # !!! CPU consumption ???
+ # TODO: extended stats into the web interface
+ if first and 'extended_stats' in p:
+ # Left padding
+ xpad = ' ' * 13
+ # First line is CPU affinity
+ if p['cpu_affinity'] is not None:
+ ret.append(self.curse_new_line())
+ msg = xpad + _('CPU affinity: ') + str(len(p['cpu_affinity'])) + _(' cores')
+ ret.append(self.curse_add_line(msg))
+ # Second line is memory info
+ if p['memory_info_ex'] is not None:
+ ret.append(self.curse_new_line())
+ msg = xpad + _('Memory info: ')
+ for k, v in p['memory_info_ex']._asdict().items():
+ # Ignore rss and vms (already displayed)
+ if k not in ['rss', 'vms'] and v is not None:
+ msg += k + ' ' + self.auto_unit(v, low_precision=False) + ' '
+ if p['memory_swap'] is not None:
+ msg += _('swap ') + self.auto_unit(p['memory_swap'], low_precision=False)
+ ret.append(self.curse_add_line(msg))
+ # Third line is for openned files/network sessions
+ ret.append(self.curse_new_line())
+ msg = xpad + _('Openned: ')
+ if p['num_threads'] is not None:
+ msg += _('threads ') + str(p['num_threads']) + ' '
+ if p['num_fds'] is not None:
+ msg += _('files ') + str(p['num_fds']) + ' '
+ if p['num_handles'] is not None:
+ msg += _('handles ') + str(p['num_handles']) + ' '
+ if p['tcp'] is not None:
+ msg += _('TCP ') + str(p['tcp']) + ' '
+ if p['udp'] is not None:
+ msg += _('UDP ') + str(p['udp']) + ' '
+ ret.append(self.curse_add_line(msg))
+ # Fouth line is IO nice level (only Linux and Windows OS)
+ if p['ionice'] is not None:
+ ret.append(self.curse_new_line())
+ msg = xpad + _('IO nice: ')
+ k = _('Class is ')
+ v = p['ionice'].ioclass
+ # Linux: The scheduling class. 0 for none, 1 for real time, 2 for best-effort, 3 for idle.
+ # Windows: On Windows only ioclass is used and it can be set to 2 (normal), 1 (low) or 0 (very low).
+ if is_windows:
+ if v == 0:
+ msg += k + 'Very Low'
+ elif v == 1:
+ msg += k + 'Low'
+ elif v == 2:
+ msg += _('No specific I/O priority')
+ else:
+ msg += k + str(v)
+ else:
+ if v == 0:
+ msg += _('No specific I/O priority')
+ elif v == 1:
+ msg += k + 'Real Time'
+ elif v == 2:
+ msg += k + 'Best Effort'
+ elif v == 3:
+ msg += k + 'IDLE'
+ else:
+ msg += k + str(v)
+ # value is a number which goes from 0 to 7.
+ # The higher the value, the lower the I/O priority of the process.
+ if hasattr(p['ionice'], 'value') and p['ionice'].value != 0:
+ msg += _(' (value %s/7)') % str(p['ionice'].value)
+ ret.append(self.curse_add_line(msg))
+ # End of extended stats
+ first = False
+
# Return the message with decoration
return ret