diff options
Diffstat (limited to 'glances/plugins')
-rw-r--r-- | glances/plugins/glances_diskio.py | 12 | ||||
-rw-r--r-- | glances/plugins/glances_docker.py | 18 | ||||
-rw-r--r-- | glances/plugins/glances_fs.py | 2 | ||||
-rw-r--r-- | glances/plugins/glances_gpu.py | 6 | ||||
-rw-r--r-- | glances/plugins/glances_ip.py | 3 | ||||
-rw-r--r-- | glances/plugins/glances_memswap.py | 35 | ||||
-rw-r--r-- | glances/plugins/glances_network.py | 14 | ||||
-rw-r--r-- | glances/plugins/glances_now.py | 16 | ||||
-rw-r--r-- | glances/plugins/glances_plugin.py | 86 | ||||
-rw-r--r-- | glances/plugins/glances_processlist.py | 47 | ||||
-rw-r--r-- | glances/plugins/glances_quicklook.py | 10 |
11 files changed, 196 insertions, 53 deletions
diff --git a/glances/plugins/glances_diskio.py b/glances/plugins/glances_diskio.py index 52ddfebc..3fe66543 100644 --- a/glances/plugins/glances_diskio.py +++ b/glances/plugins/glances_diskio.py @@ -23,6 +23,7 @@ from __future__ import unicode_literals from glances.compat import nativestr, n from glances.timer import getTimeSinceLastUpdate from glances.plugins.glances_plugin import GlancesPlugin +from glances.logger import logger import psutil @@ -51,6 +52,9 @@ class Plugin(GlancesPlugin): # We want to display the stat in the curse interface self.display_curse = True + # Hide stats if it has never been != 0 + self.hide_zero = True + self.hide_zero_fields = ['read_bytes', 'write_bytes'] def get_key(self): """Return the key of the list.""" @@ -143,9 +147,12 @@ class Plugin(GlancesPlugin): # Call the father's method super(Plugin, self).update_views() + # Check if the stats should be hidden + self.update_views_hidden() + # Add specifics informations # Alert - for i in self.stats: + for i in self.get_raw(): disk_real_name = i['disk_name'] self.views[i[self.get_key()]]['read_bytes']['decoration'] = self.get_alert(int(i['read_bytes'] // i['time_since_update']), header=disk_real_name + '_rx') @@ -179,6 +186,9 @@ class Plugin(GlancesPlugin): ret.append(self.curse_add_line(msg)) # Disk list (sorted by name) for i in self.sorted_stats(): + # Hide stats if never be different from 0 (issue #1787) + if all([self.get_views(item=i[self.get_key()], key=f, option='hidden') for f in self.hide_zero_fields]): + continue # Is there an alias for the disk name ? disk_real_name = i['disk_name'] disk_name = self.has_alias(i['disk_name']) diff --git a/glances/plugins/glances_docker.py b/glances/plugins/glances_docker.py index 80c550df..82dd1693 100644 --- a/glances/plugins/glances_docker.py +++ b/glances/plugins/glances_docker.py @@ -81,6 +81,9 @@ class Plugin(GlancesPlugin): # The plgin can be disable using: args.disable_docker self.args = args + # Default config keys + self.config = config + # We want to display the stat in the curse interface self.display_curse = True @@ -207,6 +210,14 @@ class Plugin(GlancesPlugin): # Get stats for all containers stats['containers'] = [] for container in containers: + # Only show specific containers + if not self.is_show(nativestr(container.name)): + continue + + # Do not take hiden container into account + if self.is_hide(nativestr(container.name)): + continue + # Init the stats for the current container container_stats = {} # The key is the container name and not the Id @@ -524,8 +535,11 @@ class Plugin(GlancesPlugin): ret.append(self.curse_new_line()) # Header ret.append(self.curse_new_line()) - # Get the maximum containers name (cutted to 20 char max) - name_max_width = min(20, len(max(self.stats['containers'], key=lambda x: len(x['name']))['name'])) + # Get the maximum containers name + # Max size is configurable. See feature request #1723. + name_max_width = min(self.config.get_int_value('docker', 'max_name_size', default=20), + len(max(self.stats['containers'], + key=lambda x: len(x['name']))['name'])) msg = ' {:{width}}'.format('Name', width=name_max_width) ret.append(self.curse_add_line(msg)) msg = '{:>10}'.format('Status') diff --git a/glances/plugins/glances_fs.py b/glances/plugins/glances_fs.py index 6c1e618a..7d43d988 100644 --- a/glances/plugins/glances_fs.py +++ b/glances/plugins/glances_fs.py @@ -103,8 +103,6 @@ class Plugin(GlancesPlugin): return self.stats # Optionnal hack to allow logicals mounts points (issue #448) - # Ex: Had to put 'allow=zfs' in the [fs] section of the conf file - # to allow zfs monitoring for fstype in self.get_conf_value('allow'): try: fs_stat += [f for f in psutil.disk_partitions(all=True) if f.fstype.find(fstype) >= 0] diff --git a/glances/plugins/glances_gpu.py b/glances/plugins/glances_gpu.py index e0cdb96b..8c2905b1 100644 --- a/glances/plugins/glances_gpu.py +++ b/glances/plugins/glances_gpu.py @@ -2,7 +2,7 @@ # # This file is part of Glances. # -# Copyright (C) 2018 Kirby Banman <kirby.banman@gmail.com> +# Copyright (C) 2020 Kirby Banman <kirby.banman@gmail.com> # # 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 @@ -226,11 +226,11 @@ class Plugin(GlancesPlugin): id_msg = '{}'.format(gpu_stats['gpu_id']) try: proc_msg = '{:>3.0f}%'.format(gpu_stats['proc']) - except ValueError: + except (ValueError, TypeError): proc_msg = '{:>4}'.format('N/A') try: mem_msg = '{:>3.0f}%'.format(gpu_stats['mem']) - except ValueError: + except (ValueError, TypeError): mem_msg = '{:>4}'.format('N/A') msg = '{}: {} mem: {}'.format(id_msg, proc_msg, diff --git a/glances/plugins/glances_ip.py b/glances/plugins/glances_ip.py index 8aa38f7c..8a659bc4 100644 --- a/glances/plugins/glances_ip.py +++ b/glances/plugins/glances_ip.py @@ -150,6 +150,9 @@ class Plugin(GlancesPlugin): """ # Thanks to @Atticfire # See https://github.com/nicolargo/glances/issues/1417#issuecomment-469894399 + if ip is None: + # Correct issue #1528 + return 0 return sum(bin(int(x)).count('1') for x in ip.split('.')) diff --git a/glances/plugins/glances_memswap.py b/glances/plugins/glances_memswap.py index 42c367a8..87d1fb38 100644 --- a/glances/plugins/glances_memswap.py +++ b/glances/plugins/glances_memswap.py @@ -66,20 +66,24 @@ class Plugin(GlancesPlugin): if self.input_method == 'local': # Update stats using the standard system lib # Grab SWAP using the psutil swap_memory method - sm_stats = psutil.swap_memory() - - # Get all the swap stats (copy/paste of the psutil documentation) - # total: total swap memory in bytes - # used: used swap memory in bytes - # free: free swap memory in bytes - # percent: the percentage usage - # sin: the number of bytes the system has swapped in from disk (cumulative) - # sout: the number of bytes the system has swapped out from disk - # (cumulative) - for swap in ['total', 'used', 'free', 'percent', - 'sin', 'sout']: - if hasattr(sm_stats, swap): - stats[swap] = getattr(sm_stats, swap) + try: + sm_stats = psutil.swap_memory() + except RuntimeError: + # Crash on startup on Illumos when no swap is configured #1767 + pass + else: + # Get all the swap stats (copy/paste of the psutil documentation) + # total: total swap memory in bytes + # used: used swap memory in bytes + # free: free swap memory in bytes + # percent: the percentage usage + # sin: the number of bytes the system has swapped in from disk (cumulative) + # sout: the number of bytes the system has swapped out from disk + # (cumulative) + for swap in ['total', 'used', 'free', 'percent', + 'sin', 'sout']: + if hasattr(sm_stats, swap): + stats[swap] = getattr(sm_stats, swap) elif self.input_method == 'snmp': # Update stats using SNMP if self.short_system_name == 'windows': @@ -135,7 +139,8 @@ class Plugin(GlancesPlugin): # Add specifics informations # Alert and log - self.views['used']['decoration'] = self.get_alert_log(self.stats['used'], maximum=self.stats['total']) + if 'used' in self.stats and 'total' in self.stats: + self.views['used']['decoration'] = self.get_alert_log(self.stats['used'], maximum=self.stats['total']) def msg_curse(self, args=None, max_width=None): """Return the dict to display in the curse interface.""" diff --git a/glances/plugins/glances_network.py b/glances/plugins/glances_network.py index 00fc88b0..2adf3653 100644 --- a/glances/plugins/glances_network.py +++ b/glances/plugins/glances_network.py @@ -61,6 +61,9 @@ class Plugin(GlancesPlugin): # We want to display the stat in the curse interface self.display_curse = True + # Hide stats if it has never been != 0 + self.hide_zero = True + self.hide_zero_fields = ['rx', 'tx'] def get_key(self): """Return the key of the list.""" @@ -223,13 +226,17 @@ class Plugin(GlancesPlugin): # Call the father's method super(Plugin, self).update_views() + # Check if the stats should be hidden + self.update_views_hidden() + # Add specifics informations # Alert - for i in self.stats: + for i in self.get_raw(): ifrealname = i['interface_name'].split(':')[0] - # Convert rate in bps ( to be able to compare to interface speed) + # Convert rate in bps (to be able to compare to interface speed) bps_rx = int(i['rx'] // i['time_since_update'] * 8) bps_tx = int(i['tx'] // i['time_since_update'] * 8) + # Decorate the bitrate with the configuration file thresolds alert_rx = self.get_alert(bps_rx, header=ifrealname + '_rx') alert_tx = self.get_alert(bps_tx, header=ifrealname + '_tx') @@ -290,6 +297,9 @@ class Plugin(GlancesPlugin): # Do not display interface in down state (issue #765) if ('is_up' in i) and (i['is_up'] is False): continue + # Hide stats if never be different from 0 (issue #1787) + if all([self.get_views(item=i[self.get_key()], key=f, option='hidden') for f in self.hide_zero_fields]): + continue # Format stats # Is there an alias for the interface name ? ifrealname = i['interface_name'].split(':')[0] diff --git a/glances/plugins/glances_now.py b/glances/plugins/glances_now.py index 254d71b8..d3edb839 100644 --- a/glances/plugins/glances_now.py +++ b/glances/plugins/glances_now.py @@ -41,6 +41,12 @@ class Plugin(GlancesPlugin): # Set the message position self.align = 'bottom' + if args.strftime_format: + self.strftime = args.strftime_format + elif config is not None: + if 'global' in config.as_dict(): + self.strftime = config.as_dict()['global']['strftime_format'] + def reset(self): """Reset/init the stats.""" self.stats = '' @@ -49,10 +55,14 @@ class Plugin(GlancesPlugin): """Update current date/time.""" # Had to convert it to string because datetime is not JSON serializable # Add the time zone (issue #1249 / #1337 / #1598) - if (len(tzname[1]) > 6): - self.stats = strftime('%Y-%m-%d %H:%M:%S %z') + + if self.strftime: + self.stats = strftime(self.strftime) else: - self.stats = strftime('%Y-%m-%d %H:%M:%S %Z') + if (len(tzname[1]) > 6): + self.stats = strftime('%Y-%m-%d %H:%M:%S %z') + else: + self.stats = strftime('%Y-%m-%d %H:%M:%S %Z') return self.stats diff --git a/glances/plugins/glances_plugin.py b/glances/plugins/glances_plugin.py index 936db58c..5588263d 100644 --- a/glances/plugins/glances_plugin.py +++ b/glances/plugins/glances_plugin.py @@ -99,6 +99,11 @@ class GlancesPlugin(object): # Init the views self.views = dict() + # Hide stats if all the hide_zero_fields has never been != 0 + # Default is False, always display stats + self.hide_zero = False + self.hide_zero_fields = [] + # Init the stats self.stats_init_value = stats_init_value self.stats = None @@ -422,16 +427,61 @@ class GlancesPlugin(object): "Cannot get item({})=value({}) ({})".format(item, value, e)) return None + def update_views_hidden(self): + """If the self.hide_zero is set then update the hidden field of the view + It will check if all fields values are already be different from 0 + In this case, the hidden field is set to True + + Note: This function should be called by plugin (in the update_views method) + + Example (for network plugin): + __Init__ + self.hide_zero_fields = ['rx', 'tx'] + Update views + ... + self.update_views_hidden() + """ + if not self.hide_zero: + return False + if (isinstance(self.get_raw(), list) and + self.get_raw() is not None and + self.get_key() is not None): + # Stats are stored in a list of dict (ex: NETWORK, FS...) + for i in self.get_raw(): + if any([i[f] for f in self.hide_zero_fields]): + for f in self.hide_zero_fields: + self.views[i[self.get_key( + )]][f]['_zero'] = self.views[i[self.get_key()]][f]['hidden'] + for f in self.hide_zero_fields: + self.views[i[self.get_key( + )]][f]['hidden'] = self.views[i[self.get_key()]][f]['_zero'] and i[f] == 0 + elif isinstance(self.get_raw(), dict) and self.get_raw() is not None: + # + # Warning: This code has never been tested because + # no plugin with dict instance use the hidden function... + # vvvv + # + # Stats are stored in a dict (ex: CPU, LOAD...) + for key in listkeys(self.get_raw()): + if any([self.get_raw()[f] for f in self.hide_zero_fields]): + for f in self.hide_zero_fields: + self.views[f]['_zero'] = self.views[f]['hidden'] + for f in self.hide_zero_fields: + self.views[f]['hidden'] = self.views['_zero'] and self.views[f] == 0 + return True + def update_views(self): """Update the stats views. The V of MVC A dict of dict with the needed information to display the stats. Example for the stat xxx: - 'xxx': {'decoration': 'DEFAULT', - 'optional': False, - 'additional': False, - 'splittable': False} + 'xxx': {'decoration': 'DEFAULT', >>> The decoration of the stats + 'optional': False, >>> Is the stat optional + 'additional': False, >>> Is the stat provide additional information + 'splittable': False, >>> Is the stat can be cut (like process lon name) + 'hidden': False, >>> Is the stats should be hidden in the UI + '_zero': True} >>> For internal purpose only """ ret = {} @@ -440,12 +490,15 @@ class GlancesPlugin(object): self.get_key() is not None): # Stats are stored in a list of dict (ex: NETWORK, FS...) for i in self.get_raw(): + # i[self.get_key()] is the interface name (example for NETWORK) ret[i[self.get_key()]] = {} for key in listkeys(i): value = {'decoration': 'DEFAULT', 'optional': False, 'additional': False, - 'splittable': False} + 'splittable': False, + 'hidden': False, + '_zero': self.views[i[self.get_key()]][key]['_zero'] if i[self.get_key()] in self.views and key in self.views[i[self.get_key()]] else True} ret[i[self.get_key()]][key] = value elif isinstance(self.get_raw(), dict) and self.get_raw() is not None: # Stats are stored in a dict (ex: CPU, LOAD...) @@ -453,7 +506,9 @@ class GlancesPlugin(object): value = {'decoration': 'DEFAULT', 'optional': False, 'additional': False, - 'splittable': False} + 'splittable': False, + 'hidden': False, + '_zero': self.views[key]['_zero'] if key in self.views and '_zero' in self.views[key] else True} ret[key] = value self.views = ret @@ -752,6 +807,21 @@ class GlancesPlugin(object): except KeyError: return default + def is_show(self, value, header=""): + """Return True if the value is in the show configuration list. + If the show value is empty, return True (show by default) + + The show configuration list is defined in the glances.conf file. + It is a comma separed list of regexp. + Example for diskio: + show=sda.* + """ + # @TODO: possible optimisation: create a re.compile list + if self.get_conf_value('show', header=header) == []: + return True + else: + return any(j for j in [re.match(i, value) for i in self.get_conf_value('show', header=header)]) + def is_hide(self, value, header=""): """Return True if the value is in the hide configuration list. @@ -760,9 +830,7 @@ class GlancesPlugin(object): Example for diskio: hide=sda2,sda5,loop.* """ - # TODO: possible optimisation: create a re.compile list - # Old version (see issue #1691) - #return not all(j is None for j in [re.match(i, value.lower()) for i in self.get_conf_value('hide', header=header)]) + # @TODO: possible optimisation: create a re.compile list return any(j for j in [re.match(i, value) for i in self.get_conf_value('hide', header=header)]) def has_alias(self, header): diff --git a/glances/plugins/glances_processlist.py b/glances/plugins/glances_processlist.py index 47029f40..0abf02e0 100644 --- a/glances/plugins/glances_processlist.py +++ b/glances/plugins/glances_processlist.py @@ -70,12 +70,13 @@ class Plugin(GlancesPlugin): 'status': '{:>1} ', 'ior': '{:>4} ', 'iow': '{:<4} ', - 'command': '{}', + 'command': '{} {}', } # Define the stat layout of the processes list columns layout_stat = { - 'cpu': '{:<6.1f} ', + 'cpu': '{:<6.1f}', + 'cpu_no_digit': '{:<6.0f}', 'mem': '{:<5.1f} ', 'virt': '{:<5} ', 'res': '{:<5} ', @@ -172,19 +173,26 @@ class Plugin(GlancesPlugin): pass return 'DEFAULT' - def get_process_curses_data(self, p, first, args): + def get_process_curses_data(self, p, selected, args): """Get curses data to display for a process. - p is the process to display - - first is a tag=True if the process is the first on the list + - selected is a tag=True if the selected process """ ret = [self.curse_new_line()] + # When a process is selected: + # * display a special caracter at the beginning of the line + # * underline the command name + if args.is_standalone: + ret.append(self.curse_add_line('>' if selected else ' ', 'SELECTED')) # CPU if 'cpu_percent' in p and p['cpu_percent'] is not None and p['cpu_percent'] != '': + cpu_layout = self.layout_stat['cpu'] if p['cpu_percent'] < 100 else self.layout_stat['cpu_no_digit'] if args.disable_irix and self.nb_log_core != 0: - msg = self.layout_stat['cpu'].format(p['cpu_percent'] / float(self.nb_log_core)) + msg = cpu_layout.format( + p['cpu_percent'] / float(self.nb_log_core)) else: - msg = self.layout_stat['cpu'].format(p['cpu_percent']) + msg = cpu_layout.format(p['cpu_percent']) alert = self.get_alert(p['cpu_percent'], highlight_zero=False, is_max=(p['cpu_percent'] == self.max_values['cpu_percent']), @@ -321,28 +329,34 @@ class Plugin(GlancesPlugin): else: cmdline = '?' try: + process_decoration = 'PROCESS_SELECTED' if (selected and args.is_standalone) else 'PROCESS' if cmdline: path, cmd, arguments = split_cmdline(cmdline) + # Manage end of line in arguments (see #1692) + arguments.replace('\r\n', ' ') + arguments.replace('\n', ' ') if os.path.isdir(path) and not args.process_short_name: msg = self.layout_stat['command'].format(path) + os.sep ret.append(self.curse_add_line(msg, splittable=True)) - ret.append(self.curse_add_line(cmd, decoration='PROCESS', splittable=True)) + ret.append(self.curse_add_line( + cmd, decoration=process_decoration, splittable=True)) else: msg = self.layout_stat['command'].format(cmd) - ret.append(self.curse_add_line(msg, decoration='PROCESS', splittable=True)) + ret.append(self.curse_add_line( + msg, decoration=process_decoration, splittable=True)) if arguments: msg = ' ' + self.layout_stat['command'].format(arguments) ret.append(self.curse_add_line(msg, splittable=True)) else: msg = self.layout_stat['name'].format(p['name']) - ret.append(self.curse_add_line(msg, splittable=True)) + ret.append(self.curse_add_line(msg, decoration=process_decoration, splittable=True)) except (TypeError, UnicodeEncodeError) as e: # Avoid crach after running fine for several hours #1335 logger.debug("Can not decode command line '{}' ({})".format(cmdline, e)) ret.append(self.curse_add_line('', splittable=True)) # Add extended stats but only for the top processes - if first and 'extended_stats' in p and args.enable_process_extended: + if args.cursor_position == 0 and 'extended_stats' in p and args.enable_process_extended: # Left padding xpad = ' ' * 13 # First line is CPU affinity @@ -429,11 +443,13 @@ class Plugin(GlancesPlugin): # Process list # Loop over processes (sorted by the sort key previously compute) - first = True + i = 0 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 + ret.extend(self.get_process_curses_data( + p, i == args.cursor_position, args)) + i += 1 + + # A filter is set Display the stats summaries if glances_processes.process_filter is not None: if args.reset_minmax_tag: args.reset_minmax_tag = not args.reset_minmax_tag @@ -478,7 +494,8 @@ class Plugin(GlancesPlugin): ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'io_counters' else 'DEFAULT', optional=True, additional=True)) msg = self.layout_header['iow'].format('W/s') ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'io_counters' else 'DEFAULT', optional=True, additional=True)) - msg = self.layout_header['command'].format('Command') + msg = self.layout_header['command'].format('Command', + "('k' to kill)" if args.is_standalone else "") ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'name' else 'DEFAULT')) def __msg_curse_sum(self, ret, sep_char='_', mmm=None, args=None): diff --git a/glances/plugins/glances_quicklook.py b/glances/plugins/glances_quicklook.py index e49628e4..babca182 100644 --- a/glances/plugins/glances_quicklook.py +++ b/glances/plugins/glances_quicklook.py @@ -81,7 +81,11 @@ class Plugin(GlancesPlugin): stats['percpu'] = cpu_percent.get(percpu=True) # Use the psutil lib for the memory (virtual and swap) stats['mem'] = psutil.virtual_memory().percent - stats['swap'] = psutil.swap_memory().percent + try: + stats['swap'] = psutil.swap_memory().percent + except RuntimeError: + # Correct issue in Illumos OS (see #1767) + stats['swap'] = None elif self.input_method == 'snmp': # Not available pass @@ -99,8 +103,12 @@ class Plugin(GlancesPlugin): stats['cpu_name'] = cpu_info.get('brand', 'CPU') if 'hz_actual_raw' in cpu_info: stats['cpu_hz_current'] = cpu_info['hz_actual_raw'][0] + elif 'hz_actual' in cpu_info: + stats['cpu_hz_current'] = cpu_info['hz_actual'][0] if 'hz_advertised_raw' in cpu_info: stats['cpu_hz'] = cpu_info['hz_advertised_raw'][0] + elif 'hz_advertised' in cpu_info: + stats['cpu_hz'] = cpu_info['hz_advertised'][0] # Update the stats self.stats = stats |