Merge branch 'feature/issue403' into develop

pull/416/head
Nicolargo 2014-08-15 11:45:51 +02:00
commit 37edbfc5a1
8 changed files with 158 additions and 17 deletions

View File

@ -66,11 +66,10 @@ logger = glancesLogger()
# Instances shared between all Glances scripts # 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 from glances.core.glances_processes import GlancesProcesses
glances_processes = GlancesProcesses() glances_processes = GlancesProcesses()
# The global instance for the logs # The global instance for the logs
from glances.core.glances_logs import GlancesLogs from glances.core.glances_logs import GlancesLogs
glances_logs = GlancesLogs() glances_logs = GlancesLogs()

View File

@ -74,6 +74,8 @@ class GlancesMain(object):
dest='disable_sensors', help=_('disable sensors module')) dest='disable_sensors', help=_('disable sensors module'))
parser.add_argument('--disable-process', action='store_true', default=False, parser.add_argument('--disable-process', action='store_true', default=False,
dest='disable_process', help=_('disable process module')) 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, parser.add_argument('--disable-log', action='store_true', default=False,
dest='disable_log', help=_('disable log module')) dest='disable_log', help=_('disable log module'))
# CSV output feature # CSV output feature

View File

@ -52,6 +52,9 @@ class GlancesProcesses(object):
# Default is to enable the processes stats # Default is to enable the processes stats
self.disable_tag = False 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 # Maximum number of processes showed in the UI interface
# None if no limit # None if no limit
self.max_processes = None self.max_processes = None
@ -65,6 +68,15 @@ class GlancesProcesses(object):
"""Disable process stats.""" """Disable process stats."""
self.disable_tag = True 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): def set_max_processes(self, value):
"""Set the maximum number of processes showed in the UI interfaces""" """Set the maximum number of processes showed in the UI interfaces"""
self.max_processes = value self.max_processes = value
@ -84,14 +96,16 @@ class GlancesProcesses(object):
=> cpu_percent, memory_percent, io_counters, name => cpu_percent, memory_percent, io_counters, name
standard_stats: for all the displayed processes standard_stats: for all the displayed processes
=> username, cmdline, status, memory_info, cpu_times => username, cmdline, status, memory_info, cpu_times
extended_stats: only for top processes (!!! to be implemented) extended_stats: only for top processes (see issue #403)
=> connections (UDP/TCP), memory_swap => connections (UDP/TCP), memory_swap...
""" """
# Process ID (always) # Process ID (always)
procstat = proc.as_dict(attrs=['pid']) procstat = proc.as_dict(attrs=['pid'])
if mandatory_stats: if mandatory_stats:
procstat['mandatory_stats'] = True
# Process CPU, MEM percent and name # Process CPU, MEM percent and name
procstat.update(proc.as_dict(attrs=['cpu_percent', 'memory_percent', 'name'], ad_value='')) 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] procstat['io_counters'] += [io_tag]
if standard_stats: if standard_stats:
procstat['standard_stats'] = True
# Process username (cached with internal cache) # Process username (cached with internal cache)
try: try:
self.username_cache[procstat['pid']] 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.update(proc.as_dict(attrs=['status', 'nice', 'memory_info', 'cpu_times']))
procstat['status'] = str(procstat['status'])[:1].upper() procstat['status'] = str(procstat['status'])[:1].upper()
if extended_stats: if extended_stats and not self.disable_extended_tag:
# Process network connections (TCP and UDP) (Experimental) 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: try:
procstat['tcp'] = len(proc.connections(kind="tcp")) procstat['tcp'] = len(proc.connections(kind="tcp"))
procstat['udp'] = len(proc.connections(kind="udp")) procstat['udp'] = len(proc.connections(kind="udp"))
except: except:
procstat['tcp'] = 0 procstat['tcp'] = None
procstat['udp'] = 0 procstat['udp'] = None
# SWAP memory # IO Nice
# Only on Linux based OS # http://pythonhosted.org/psutil/#psutil.Process.ionice
# http://www.cyberciti.biz/faq/linux-which-process-is-using-swap/ if is_linux or is_windows:
if is_linux: procstat.update(proc.as_dict(attrs=['ionice']))
logger.debug(proc.memory_maps()) else:
procstat['memory_swap'] = sum([ v.swap for v in proc.memory_maps() ]) procstat['ionice'] = None
#logger.debug(procstat)
return procstat return procstat
@ -220,17 +265,22 @@ class GlancesProcesses(object):
# Sort the internal dict and cut the top N (Return a list of tuple) # Sort the internal dict and cut the top N (Return a list of tuple)
# tuple=key (proc), dict (returned by __get_process_stats) # tuple=key (proc), dict (returned by __get_process_stats)
processiter = sorted(processdict.items(), key=lambda x: x[1][self.getsortkey()], reverse=True) processiter = sorted(processdict.items(), key=lambda x: x[1][self.getsortkey()], reverse=True)
first = True
for i in processiter[0:self.get_max_processes()]: for i in processiter[0:self.get_max_processes()]:
# Already existing mandatory stats # Already existing mandatory stats
procstat = i[1] procstat = i[1]
# Update with standard stats # Update with standard stats
# and extended stats but only for TOP (first) process
procstat.update(self.__get_process_stats(i[0], procstat.update(self.__get_process_stats(i[0],
mandatory_stats=False, mandatory_stats=False,
standard_stats=True)) standard_stats=True,
extended_stats=first))
# Add a specific time_since_update stats for bitrate # Add a specific time_since_update stats for bitrate
procstat['time_since_update'] = time_since_update procstat['time_since_update'] = time_since_update
# Update process list # Update process list
self.processlist.append(procstat) self.processlist.append(procstat)
# Next...
first = False
else: else:
# Get all the processes # Get all the processes
for i in processdict.items(): for i in processdict.items():

View File

@ -43,6 +43,14 @@ class GlancesStandalone(object):
logger.warning(_("Maximum displayed processes is not configured (high CPU consumption)")) logger.warning(_("Maximum displayed processes is not configured (high CPU consumption)"))
glances_processes.set_max_processes(max_processes) 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 # Initial system informations update
self.stats.update() self.stats.update()

View File

@ -215,6 +215,13 @@ class GlancesCurses(object):
elif self.pressedkey == ord('d'): elif self.pressedkey == ord('d'):
# 'd' > Show/hide disk I/O stats # 'd' > Show/hide disk I/O stats
self.args.disable_diskio = not self.args.disable_diskio 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'): elif self.pressedkey == ord('f'):
# 'f' > Show/hide fs stats # 'f' > Show/hide fs stats
self.args.disable_fs = not self.args.disable_fs self.args.disable_fs = not self.args.disable_fs

View File

@ -124,7 +124,9 @@ class Plugin(GlancesPlugin):
msg = msg_col2.format("r", _("Reset history")) msg = msg_col2.format("r", _("Reset history"))
ret.append(self.curse_add_line(msg)) ret.append(self.curse_add_line(msg))
ret.append(self.curse_new_line()) 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)) ret.append(self.curse_add_line(msg))
# Return the message with decoration # Return the message with decoration

View File

@ -40,7 +40,7 @@ class GlancesPlugin(object):
"""Init the plugin of plugins class.""" """Init the plugin of plugins class."""
# Plugin name (= module name without glances_) # Plugin name (= module name without glances_)
self.plugin_name = self.__class__.__module__[len('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 # Init the args
self.args = args self.args = args

View File

@ -110,6 +110,7 @@ class Plugin(GlancesPlugin):
tag_proc_time = True tag_proc_time = True
# Loop over processes (sorted by the sort key previously compute) # Loop over processes (sorted by the sort key previously compute)
first = True
for p in self.sortlist(process_sort_key): for p in self.sortlist(process_sort_key):
ret.append(self.curse_new_line()) ret.append(self.curse_new_line())
# CPU # CPU
@ -237,6 +238,78 @@ class Plugin(GlancesPlugin):
except UnicodeEncodeError: except UnicodeEncodeError:
ret.append(self.curse_add_line("", splittable=True)) 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 the message with decoration
return ret return ret