From ecdafad8f03e73b0937438b1989f380c569bf43b Mon Sep 17 00:00:00 2001 From: Nicolargo Date: Wed, 6 Aug 2014 19:30:26 +0200 Subject: [PATCH 1/6] Add extended stats in the update finuntion of the glances_processes module --- glances/core/glances_processes.py | 48 +++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/glances/core/glances_processes.py b/glances/core/glances_processes.py index 48f214dc..af9027c2 100644 --- a/glances/core/glances_processes.py +++ b/glances/core/glances_processes.py @@ -84,8 +84,8 @@ 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) @@ -155,20 +155,39 @@ class GlancesProcesses(object): procstat['status'] = str(procstat['status'])[:1].upper() if extended_stats: - # Process network connections (TCP and UDP) (Experimental) + # 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'])) + + # 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() ]) + # !!! Only for dev + logger.debug("EXTENDED STATS: %s" % procstat) return procstat @@ -220,17 +239,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(): From 8e92419f3b300323dc5916941bc3379a280813bc Mon Sep 17 00:00:00 2001 From: Nicolargo Date: Mon, 11 Aug 2014 18:57:49 +0200 Subject: [PATCH 2/6] Add extended info --- glances/core/glances_processes.py | 11 +++++++++++ glances/plugins/glances_processlist.py | 22 ++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/glances/core/glances_processes.py b/glances/core/glances_processes.py index af9027c2..10f40134 100644 --- a/glances/core/glances_processes.py +++ b/glances/core/glances_processes.py @@ -92,6 +92,8 @@ class GlancesProcesses(object): 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 +128,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']] @@ -155,6 +159,8 @@ class GlancesProcesses(object): procstat['status'] = str(procstat['status'])[:1].upper() if extended_stats: + procstat['extended_stats'] = True + # CPU affinity # Memory extended # Number of context switch @@ -186,6 +192,11 @@ class GlancesProcesses(object): procstat['tcp'] = None procstat['udp'] = None + # IO Nice + # http://pythonhosted.org/psutil/#psutil.Process.ionice + if is_linux or is_windows: + procstat.update(proc.as_dict(attrs=['ionice'])) + # !!! Only for dev logger.debug("EXTENDED STATS: %s" % procstat) diff --git a/glances/plugins/glances_processlist.py b/glances/plugins/glances_processlist.py index de129e03..27ee5587 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,27 @@ class Plugin(GlancesPlugin): except UnicodeEncodeError: ret.append(self.curse_add_line("", splittable=True)) + # Add extended stats but only for the top processes + # !!! CPU consumption !!!! + if first: + # Left padding + xpad = ' ' * 13 + # First line is CPU affinity + ret.append(self.curse_new_line()) + msg = xpad + _('CPU affinity: ') + ','.join(str(i) for i in p['cpu_affinity']) + ret.append(self.curse_add_line(msg)) + # Second line is memory info + ret.append(self.curse_new_line()) + msg = xpad + _('Memory info: ') + msg += _('swap ') + self.auto_unit(p['memory_swap'], low_precision=False) + for k, v in p['memory_info_ex']._asdict().items(): + # Ignore rss and vms (already displayed) + if k not in ['rss', 'vms']: + msg += ', ' + k + ' ' + self.auto_unit(v, low_precision=False) + ret.append(self.curse_add_line(msg)) + # End of extended stats + first = False + # Return the message with decoration return ret From d2e525abe1fc0e66686c4268130b6cd9fc941d5e Mon Sep 17 00:00:00 2001 From: Nicolas Hennion Date: Wed, 13 Aug 2014 10:58:58 +0200 Subject: [PATCH 3/6] Add 3er line --- glances/plugins/glances_processlist.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/glances/plugins/glances_processlist.py b/glances/plugins/glances_processlist.py index 27ee5587..b5deca6b 100644 --- a/glances/plugins/glances_processlist.py +++ b/glances/plugins/glances_processlist.py @@ -240,7 +240,7 @@ class Plugin(GlancesPlugin): # Add extended stats but only for the top processes # !!! CPU consumption !!!! - if first: + if first and p['extended_stats']: # Left padding xpad = ' ' * 13 # First line is CPU affinity @@ -250,11 +250,24 @@ class Plugin(GlancesPlugin): # Second line is memory info ret.append(self.curse_new_line()) msg = xpad + _('Memory info: ') - msg += _('swap ') + self.auto_unit(p['memory_swap'], low_precision=False) for k, v in p['memory_info_ex']._asdict().items(): # Ignore rss and vms (already displayed) - if k not in ['rss', 'vms']: - msg += ', ' + k + ' ' + self.auto_unit(v, low_precision=False) + 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['tcp'] is not None: + msg += _('TCP ') + str(p['tcp']) + ' ' + if p['tcp'] is not None: + msg += _('UDP ') + str(p['udp']) + ' ' ret.append(self.curse_add_line(msg)) # End of extended stats first = False From a518eb4af36a4c55d128f891d3ab41e34ced4e30 Mon Sep 17 00:00:00 2001 From: Nicolas Hennion Date: Wed, 13 Aug 2014 17:27:41 +0200 Subject: [PATCH 4/6] extended_stats --- glances/core/glances_globals.py | 3 +-- glances/plugins/glances_processlist.py | 2 +- 2 files changed, 2 insertions(+), 3 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/plugins/glances_processlist.py b/glances/plugins/glances_processlist.py index b5deca6b..0a38291d 100644 --- a/glances/plugins/glances_processlist.py +++ b/glances/plugins/glances_processlist.py @@ -240,7 +240,7 @@ class Plugin(GlancesPlugin): # Add extended stats but only for the top processes # !!! CPU consumption !!!! - if first and p['extended_stats']: + if first and 'extended_stats' in p: # Left padding xpad = ' ' * 13 # First line is CPU affinity From ec7b0ac54f533a45f15e3763c67f8447117d355a Mon Sep 17 00:00:00 2001 From: Nicolargo Date: Thu, 14 Aug 2014 22:27:57 +0200 Subject: [PATCH 5/6] Add IO Nice level to top process --- glances/core/glances_processes.py | 7 +-- glances/plugins/glances_processlist.py | 66 ++++++++++++++++++++------ 2 files changed, 56 insertions(+), 17 deletions(-) diff --git a/glances/core/glances_processes.py b/glances/core/glances_processes.py index 10f40134..27a43701 100644 --- a/glances/core/glances_processes.py +++ b/glances/core/glances_processes.py @@ -175,6 +175,8 @@ class GlancesProcesses(object): # 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/ @@ -196,9 +198,8 @@ class GlancesProcesses(object): # http://pythonhosted.org/psutil/#psutil.Process.ionice if is_linux or is_windows: procstat.update(proc.as_dict(attrs=['ionice'])) - - # !!! Only for dev - logger.debug("EXTENDED STATS: %s" % procstat) + else: + procstat['ionice'] = None return procstat diff --git a/glances/plugins/glances_processlist.py b/glances/plugins/glances_processlist.py index 0a38291d..8cd4e2ef 100644 --- a/glances/plugins/glances_processlist.py +++ b/glances/plugins/glances_processlist.py @@ -239,24 +239,27 @@ class Plugin(GlancesPlugin): ret.append(self.curse_add_line("", splittable=True)) # Add extended stats but only for the top processes - # !!! CPU consumption !!!! + # !!! 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 - ret.append(self.curse_new_line()) - msg = xpad + _('CPU affinity: ') + ','.join(str(i) for i in p['cpu_affinity']) - ret.append(self.curse_add_line(msg)) + 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 - 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)) + 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: ') @@ -264,11 +267,46 @@ class Plugin(GlancesPlugin): 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['tcp'] is not None: + 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 From 277f6baaf016569dc9e732e6912fcd22144c46b1 Mon Sep 17 00:00:00 2001 From: Nicolargo Date: Fri, 15 Aug 2014 11:22:58 +0200 Subject: [PATCH 6/6] Add extended stats tag and key --- glances/core/glances_main.py | 2 ++ glances/core/glances_processes.py | 16 +++++++++++++++- glances/core/glances_standalone.py | 8 ++++++++ glances/outputs/glances_curses.py | 7 +++++++ glances/plugins/glances_help.py | 4 +++- glances/plugins/glances_plugin.py | 2 +- 6 files changed, 36 insertions(+), 3 deletions(-) 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 27a43701..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 @@ -158,7 +170,7 @@ 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: + if extended_stats and not self.disable_extended_tag: procstat['extended_stats'] = True # CPU affinity @@ -201,6 +213,8 @@ class GlancesProcesses(object): else: procstat['ionice'] = None + #logger.debug(procstat) + return procstat def update(self): 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