From c9caab51f42efa8cde8d010be1a5741140d794fb Mon Sep 17 00:00:00 2001 From: nicolargo Date: Sat, 20 May 2023 18:44:42 +0200 Subject: Manage gauge in the main Plugin class - Related to #2414 --- glances/plugins/cpu/model.py | 43 +++++++++++++++--------------------- glances/plugins/plugin/model.py | 49 +++++++++++++++++++++++++++++++++++------ 2 files changed, 60 insertions(+), 32 deletions(-) diff --git a/glances/plugins/cpu/model.py b/glances/plugins/cpu/model.py index f555c8ae..8d728f22 100644 --- a/glances/plugins/cpu/model.py +++ b/glances/plugins/cpu/model.py @@ -9,7 +9,6 @@ """CPU plugin.""" -from glances.timer import getTimeSinceLastUpdate from glances.globals import LINUX, WINDOWS, SUNOS, iterkeys from glances.cpu_percent import cpu_percent from glances.plugins.core.model import PluginModel as CorePluginModel @@ -21,10 +20,13 @@ import psutil # description: human readable description # short_name: shortname to use un UI # unit: unit type -# rate: is it a rate ? If yes, // by time_since_update when displayed, +# rate: is it a rate ? If yes generate metadata with _gauge and _rate_per_sec # min_symbol: Auto unit should be used if value > than 1 'X' (K, M, G)... fields_description = { - 'total': {'description': 'Sum of all CPU percentages (except idle).', 'unit': 'percent'}, + 'total': { + 'description': 'Sum of all CPU percentages (except idle).', + 'unit': 'percent' + }, 'system': { 'description': 'percent time spent in kernel space. System CPU time is the \ time spent running code in the Operating System kernel.', @@ -99,8 +101,14 @@ another while ensuring that the tasks do not conflict.', 'min_symbol': 'K', 'short_name': 'sys_call', }, - 'cpucore': {'description': 'Total number of CPU core.', 'unit': 'number'}, - 'time_since_update': {'description': 'Number of seconds since last update.', 'unit': 'seconds'}, + 'cpucore': { + 'description': 'Total number of CPU core.', + 'unit': 'number' + }, + 'time_since_update': { + 'description': 'Number of seconds since last update.', + 'unit': 'seconds' + }, } # SNMP OID @@ -153,6 +161,7 @@ class PluginModel(GlancesPluginModel): except Exception: self.nb_log_core = 1 + @GlancesPluginModel._manage_gauge @GlancesPluginModel._check_decorator @GlancesPluginModel._log_result_decorator def update(self): @@ -181,7 +190,9 @@ class PluginModel(GlancesPluginModel): # Init new stats stats = self.get_init_value() + # Total is shared with perCPU plugin stats['total'] = cpu_percent.get() + # Standards stats # - user: time spent by normal processes executing in user mode; on Linux this also includes guest time # - system: time spent by processes executing in kernel mode @@ -209,30 +220,12 @@ class PluginModel(GlancesPluginModel): # - soft_interrupts: number of software interrupts since boot. Always set to 0 on Windows and SunOS. # - syscalls: number of system calls since boot. Always set to 0 on Linux. cpu_stats = psutil.cpu_stats() - - # By storing time data we enable Rx/s and Tx/s calculations in the - # XML/RPC API, which would otherwise be overly difficult work - # for users of the API - stats['time_since_update'] = getTimeSinceLastUpdate('cpu') + for stat in cpu_stats._fields: + stats[stat] = getattr(cpu_stats, stat) # Core number is needed to compute the CTX switch limit stats['cpucore'] = self.nb_log_core - # Previous CPU stats are stored in the cpu_stats_old variable - if not hasattr(self, 'cpu_stats_old'): - # Init the stats (needed to have the key name for export) - for stat in cpu_stats._fields: - # @TODO: better to set it to None but should refactor views and UI... - stats[stat] = 0 - else: - # Others calls... - for stat in cpu_stats._fields: - if getattr(cpu_stats, stat) is not None: - stats[stat] = getattr(cpu_stats, stat) - getattr(self.cpu_stats_old, stat) - - # Save stats to compute next step - self.cpu_stats_old = cpu_stats - return stats def update_snmp(self): diff --git a/glances/plugins/plugin/model.py b/glances/plugins/plugin/model.py index 3c2eb4b4..19dff97f 100644 --- a/glances/plugins/plugin/model.py +++ b/glances/plugins/plugin/model.py @@ -22,7 +22,7 @@ from glances.history import GlancesHistory from glances.logger import logger from glances.events import glances_events from glances.thresholds import glances_thresholds -from glances.timer import Counter, Timer +from glances.timer import Counter, Timer, getTimeSinceLastUpdate from glances.outputs.glances_unicode import unicode_message @@ -115,6 +115,7 @@ class GlancesPluginModel(object): # Init the stats self.stats_init_value = stats_init_value self.stats = None + self.stats_old = None self.reset() def __repr__(self): @@ -982,15 +983,15 @@ class GlancesPluginModel(object): else: unit_type = 'float' - # Is it a rate ? Yes, compute it thanks to the time_since_update key + # Is it a rate ? Yes, get the pre-computed rate value if ( - key in self.fields_description - and 'rate' in self.fields_description[key] - and self.fields_description[key]['rate'] is True + key in self.fields_description and + 'rate' in self.fields_description[key] and + self.fields_description[key]['rate'] is True ): - value = self.stats[key] // self.stats['time_since_update'] + value = self.stats.get(key + '_rate_per_sec', 0) else: - value = self.stats[key] + value = self.stats.get(key, 0) if width is None: msg_item = header + '{}'.format(key_name) + separator @@ -1138,6 +1139,40 @@ class GlancesPluginModel(object): return wrapper + def _manage_gauge(fct): + """Manage gauge decorator for update method.""" + + def wrapper(self, *args, **kw): + # Call the method + ret = fct(self, *args, **kw) + + # Add extra data to the stats + # time_since_update: time since the last update + ret['time_since_update'] = getTimeSinceLastUpdate(self.plugin_name) + + if self.stats_old: + # For all the field with the rate=True flag + for field in self.fields_description: + if 'rate' in self.fields_description[field] and self.fields_description[field]['rate'] is True: + # Create a new metadata with the gauge + ret[field + '_gauge'] = ret[field] + if field + '_gauge' in self.stats_old: + # The stat becomes the delta between the current and the previous value + ret[field] = ret[field] - self.stats_old[field + '_gauge'] + # Compute the rate + ret[field + '_rate_per_sec'] = ret[field] // ret['time_since_update'] + else: + # Avoid strange rate at the first run + ret[field] = 0 + + # Memorized the current stats for next run + self.stats_old = ret + + return ret + + return wrapper + # Mandatory to call the decorator in child classes _check_decorator = staticmethod(_check_decorator) _log_result_decorator = staticmethod(_log_result_decorator) + _manage_gauge = staticmethod(_manage_gauge) -- cgit v1.2.3 From 311ce5d00d49c2a30162b3f7b806749a2fd6dd60 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Sat, 20 May 2023 18:52:23 +0200 Subject: Avoid div by zero when rate is computed --- glances/__init__.py | 2 +- glances/plugins/plugin/model.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/glances/__init__.py b/glances/__init__.py index 9ae141aa..652f8f84 100644 --- a/glances/__init__.py +++ b/glances/__init__.py @@ -20,7 +20,7 @@ import sys # Global name # Version should start and end with a numerical char # See https://packaging.python.org/specifications/core-metadata/#version -__version__ = '4.0.0_beta01' +__version__ = '4.0.0b01' __author__ = 'Nicolas Hennion ' __license__ = 'LGPLv3' diff --git a/glances/plugins/plugin/model.py b/glances/plugins/plugin/model.py index 19dff97f..8d0aca91 100644 --- a/glances/plugins/plugin/model.py +++ b/glances/plugins/plugin/model.py @@ -1160,7 +1160,10 @@ class GlancesPluginModel(object): # The stat becomes the delta between the current and the previous value ret[field] = ret[field] - self.stats_old[field + '_gauge'] # Compute the rate - ret[field + '_rate_per_sec'] = ret[field] // ret['time_since_update'] + if ret['time_since_update'] > 0: + ret[field + '_rate_per_sec'] = ret[field] // ret['time_since_update'] + else: + ret[field] = 0 else: # Avoid strange rate at the first run ret[field] = 0 -- cgit v1.2.3 From ae42fa5a82b348d2083c650b213c60aab7951b82 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Sat, 20 May 2023 18:59:42 +0200 Subject: Rename decorator from manage_gauge to manage_rate --- glances/plugins/cpu/model.py | 2 +- glances/plugins/plugin/model.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/glances/plugins/cpu/model.py b/glances/plugins/cpu/model.py index 8d728f22..85a5a43a 100644 --- a/glances/plugins/cpu/model.py +++ b/glances/plugins/cpu/model.py @@ -161,7 +161,7 @@ class PluginModel(GlancesPluginModel): except Exception: self.nb_log_core = 1 - @GlancesPluginModel._manage_gauge + @GlancesPluginModel._manage_rate @GlancesPluginModel._check_decorator @GlancesPluginModel._log_result_decorator def update(self): diff --git a/glances/plugins/plugin/model.py b/glances/plugins/plugin/model.py index 8d0aca91..c5a91afa 100644 --- a/glances/plugins/plugin/model.py +++ b/glances/plugins/plugin/model.py @@ -1139,8 +1139,8 @@ class GlancesPluginModel(object): return wrapper - def _manage_gauge(fct): - """Manage gauge decorator for update method.""" + def _manage_rate(fct): + """Manage rate decorator for update method.""" def wrapper(self, *args, **kw): # Call the method @@ -1178,4 +1178,4 @@ class GlancesPluginModel(object): # Mandatory to call the decorator in child classes _check_decorator = staticmethod(_check_decorator) _log_result_decorator = staticmethod(_log_result_decorator) - _manage_gauge = staticmethod(_manage_gauge) + _manage_rate = staticmethod(_manage_rate) -- cgit v1.2.3 From 136036a04f32f823292d61cf27b6805e879d922a Mon Sep 17 00:00:00 2001 From: nicolargo Date: Sun, 21 May 2023 17:13:44 +0200 Subject: First refactor on disk IO but stats are not good. Bit rate higher thand 30 GB sometime... --- glances/plugins/cpu/model.py | 3 +- glances/plugins/diskio/model.py | 147 ++++++++++++++++++++++------------------ glances/plugins/plugin/model.py | 108 +++++++++++++++++++++-------- 3 files changed, 162 insertions(+), 96 deletions(-) diff --git a/glances/plugins/cpu/model.py b/glances/plugins/cpu/model.py index 85a5a43a..d59b3de9 100644 --- a/glances/plugins/cpu/model.py +++ b/glances/plugins/cpu/model.py @@ -18,10 +18,11 @@ import psutil # Fields description # description: human readable description -# short_name: shortname to use un UI +# short_name: shortname to use in UI # unit: unit type # rate: is it a rate ? If yes generate metadata with _gauge and _rate_per_sec # min_symbol: Auto unit should be used if value > than 1 'X' (K, M, G)... +# if a key field is defined, the value is used as a key in the stats dict fields_description = { 'total': { 'description': 'Sum of all CPU percentages (except idle).', diff --git a/glances/plugins/diskio/model.py b/glances/plugins/diskio/model.py index 3502b8a0..65131327 100644 --- a/glances/plugins/diskio/model.py +++ b/glances/plugins/diskio/model.py @@ -16,11 +16,52 @@ from glances.plugins.plugin.model import GlancesPluginModel import psutil +# Fields description +# description: human readable description +# short_name: shortname to use in UI +# unit: unit type +# rate: is it a rate ? If yes generate metadata with _gauge and _rate_per_sec +# min_symbol: Auto unit should be used if value > than 1 'X' (K, M, G)... +# if field has a key=True then this field will be used as iterator for the stat (dict of dict) +fields_description = { + 'disk_name': { + 'key': True, + 'description': 'Disk name.', + }, + 'read_count': { + 'description': 'Number of read since the last update.', + 'unit': 'number', + 'rate': True, + 'min_symbol': 'K', + }, + 'write_count': { + 'description': 'Number of write since the last update.', + 'unit': 'number', + 'rate': True, + 'min_symbol': 'K', + }, + 'read_bytes': { + 'description': 'Number of bytes read since the last update.', + 'unit': 'bytes', + 'rate': True, + 'min_symbol': 'K', + }, + 'write_bytes': { + 'description': 'Number of bytes write since the last update.', + 'unit': 'bytes', + 'rate': True, + 'min_symbol': 'K', + }, + 'time_since_update': { + 'description': 'Number of seconds since last update.', + 'unit': 'seconds' + }, +} # Define the history items list items_history_list = [ - {'name': 'read_bytes', 'description': 'Bytes read per second', 'y_unit': 'B/s'}, - {'name': 'write_bytes', 'description': 'Bytes write per second', 'y_unit': 'B/s'}, + {'name': 'read_bytes_rate_per_sec', 'description': 'Bytes read per second', 'y_unit': 'B/s'}, + {'name': 'write_bytes_rate_per_sec', 'description': 'Bytes write per second', 'y_unit': 'B/s'}, ] @@ -33,7 +74,9 @@ class PluginModel(GlancesPluginModel): def __init__(self, args=None, config=None): """Init the plugin.""" super(PluginModel, self).__init__( - args=args, config=config, items_history_list=items_history_list, stats_init_value=[] + args=args, config=config, + items_history_list=items_history_list, + fields_description=fields_description ) # We want to display the stat in the curse interface @@ -44,16 +87,13 @@ class PluginModel(GlancesPluginModel): self.hide_zero = config.get_bool_value(self.plugin_name, 'hide_zero', default=False) else: self.hide_zero = False - self.hide_zero_fields = ['read_bytes', 'write_bytes'] + self.hide_zero_fields = ['read_bytes_rate_per_sec', 'write_bytes_rate_per_sec'] # Force a first update because we need two update to have the first stat self.update() self.refresh_timer.set(0) - def get_key(self): - """Return the key of the list.""" - return 'disk_name' - + @GlancesPluginModel._manage_rate @GlancesPluginModel._check_decorator @GlancesPluginModel._log_result_decorator def update(self): @@ -75,57 +115,26 @@ class PluginModel(GlancesPluginModel): except Exception: return stats - # Previous disk IO stats are stored in the diskio_old variable - # By storing time data we enable Rx/s and Tx/s calculations in the - # XML/RPC API, which would otherwise be overly difficult work - # for users of the API - time_since_update = getTimeSinceLastUpdate('disk') - - diskio = diskio for disk in diskio: # By default, RamFS is not displayed (issue #714) - if self.args is not None and not self.args.diskio_show_ramfs and disk.startswith('ram'): + if self.args is not None and \ + not self.args.diskio_show_ramfs and disk.startswith('ram'): continue # Shall we display the stats ? if not self.is_display(disk): continue - # Compute count and bit rate - try: - diskstat = { - 'time_since_update': time_since_update, - 'disk_name': disk, - 'read_count': diskio[disk].read_count - self.diskio_old[disk].read_count, - 'write_count': diskio[disk].write_count - self.diskio_old[disk].write_count, - 'read_bytes': diskio[disk].read_bytes - self.diskio_old[disk].read_bytes, - 'write_bytes': diskio[disk].write_bytes - self.diskio_old[disk].write_bytes, - } - except (KeyError, AttributeError): - diskstat = { - 'time_since_update': time_since_update, - 'disk_name': disk, - 'read_count': 0, - 'write_count': 0, - 'read_bytes': 0, - 'write_bytes': 0, - } + # Convert disk stat to plain Python Dict + diskio_stats = {} + for key in diskio[disk]._fields: + diskio_stats[key] = getattr(diskio[disk], key) + stats[disk] = diskio_stats # Add alias if exist (define in the configuration file) if self.has_alias(disk) is not None: - diskstat['alias'] = self.has_alias(disk) - - # Add the dict key - diskstat['key'] = self.get_key() - - # Add the current disk stat to the list - stats.append(diskstat) + stats[disk]['alias'] = self.has_alias(disk) - # Save stats to compute next bitrate - try: - self.diskio_old = diskio - except (IOError, UnboundLocalError): - pass elif self.input_method == 'snmp': # Update stats using SNMP # No standard way for the moment... @@ -146,14 +155,14 @@ class PluginModel(GlancesPluginModel): # Add specifics information # Alert - 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' - ) - self.views[i[self.get_key()]]['write_bytes']['decoration'] = self.get_alert( - int(i['write_bytes'] // i['time_since_update']), header=disk_real_name + '_tx' - ) + for k, v in self.get_raw().items(): + if 'read_bytes_rate_per_sec' in v and 'write_bytes_rate_per_sec' in v: + self.views[k]['read_bytes_rate_per_sec']['decoration'] = self.get_alert( + int(v['read_bytes_rate_per_sec']), header=k + '_rx' + ) + self.views[k]['read_bytes_rate_per_sec']['decoration'] = self.get_alert( + int(v['write_bytes_rate_per_sec']), header=k + '_tx' + ) def msg_curse(self, args=None, max_width=None): """Return the dict to display in the curse interface.""" @@ -181,49 +190,53 @@ class PluginModel(GlancesPluginModel): msg = '{:>7}'.format('W/s') ret.append(self.curse_add_line(msg)) # Disk list (sorted by name) - for i in self.sorted_stats(): + # for i in self.sorted_stats(): + for k in sorted(self.stats): + v = self.stats[k] + if 'read_bytes_rate_per_sec' not in v or 'write_bytes_rate_per_sec' not in v: + 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]): + if all([self.get_views(item=k, key=f, option='hidden') for f in self.hide_zero_fields]): continue # Is there an alias for the disk name ? - disk_name = self.has_alias(i['disk_name']) if self.has_alias(i['disk_name']) else i['disk_name'] + disk_name = self.has_alias(k) if self.has_alias(k) else k # New line ret.append(self.curse_new_line()) if len(disk_name) > name_max_width: # Cut disk name if it is too long - disk_name = '_' + disk_name[-name_max_width + 1 :] + disk_name = '_' + disk_name[-name_max_width + 1:] msg = '{:{width}}'.format(nativestr(disk_name), width=name_max_width + 1) ret.append(self.curse_add_line(msg)) if args.diskio_iops: # count - txps = self.auto_unit(int(i['read_count'] // i['time_since_update'])) - rxps = self.auto_unit(int(i['write_count'] // i['time_since_update'])) + txps = self.auto_unit(v['read_count_rate_per_sec']) + rxps = self.auto_unit(v['write_count_rate_per_sec']) msg = '{:>7}'.format(txps) ret.append( self.curse_add_line( - msg, self.get_views(item=i[self.get_key()], key='read_count', option='decoration') + msg, self.get_views(item=k, key='read_count_rate_per_sec', option='decoration') ) ) msg = '{:>7}'.format(rxps) ret.append( self.curse_add_line( - msg, self.get_views(item=i[self.get_key()], key='write_count', option='decoration') + msg, self.get_views(item=k, key='write_count_rate_per_sec', option='decoration') ) ) else: # Bitrate - txps = self.auto_unit(int(i['read_bytes'] // i['time_since_update'])) - rxps = self.auto_unit(int(i['write_bytes'] // i['time_since_update'])) + txps = self.auto_unit(v['read_bytes_rate_per_sec']) + rxps = self.auto_unit(v['write_bytes_rate_per_sec']) msg = '{:>7}'.format(txps) ret.append( self.curse_add_line( - msg, self.get_views(item=i[self.get_key()], key='read_bytes', option='decoration') + msg, self.get_views(item=k, key='read_bytes_rate_per_sec', option='decoration') ) ) msg = '{:>7}'.format(rxps) ret.append( self.curse_add_line( - msg, self.get_views(item=i[self.get_key()], key='write_bytes', option='decoration') + msg, self.get_views(item=k, key='write_bytes_rate_per_sec', option='decoration') ) ) diff --git a/glances/plugins/plugin/model.py b/glances/plugins/plugin/model.py index c5a91afa..84572e3e 100644 --- a/glances/plugins/plugin/model.py +++ b/glances/plugins/plugin/model.py @@ -57,6 +57,8 @@ class GlancesPluginModel(object): self.stats). As an example, you can have a look on the mem plugin (for dict) or network (for list of dicts). + From version 4 of the API, the plugin should return a dict. + A plugin should implement: - the reset method: to set your self.stats variable to {} or [] - the update method: where your self.stats variable is set @@ -111,6 +113,13 @@ class GlancesPluginModel(object): # Init stats description self.fields_description = fields_description + # Get the key in case of stat return a dict of dict + self.key = None + if self.fields_description: + keys = [k for k in self.fields_description if 'key' in self.fields_description[k] and + self.fields_description[k]['key']] + if len(keys) == 1: + self.key = keys[0] # Init the stats self.stats_init_value = stats_init_value @@ -143,7 +152,7 @@ class GlancesPluginModel(object): def get_key(self): """Return the key of the list.""" - return None + return self.key def is_enabled(self, plugin_name=None): """Return true if plugin is enabled.""" @@ -186,7 +195,11 @@ class GlancesPluginModel(object): else: item_name = self.get_key() for i in self.get_items_history_list(): - if isinstance(self.get_export(), list): + if isinstance(self.get_export(), dict): + # TODO: To be implemented + pass + elif isinstance(self.get_export(), list): + # TODO: When implementation of feature/issue2414 is done, this code should be removed # Stats is a list of data # Iter through it (for example, iter through network interface) for l_export in self.get_export(): @@ -471,7 +484,30 @@ class GlancesPluginModel(object): """ ret = {} - if isinstance(self.get_raw(), list) and self.get_raw() is not None and self.get_key() is not None: + if isinstance(self.get_raw(), dict) and \ + self.get_raw() is not None and \ + self.get_key() is not None and \ + self.plugin_name == 'diskio': # TODO <== To be removed when feature/issue2414 is done + # Stats are stored in a dict of dict (ex: DISKIO...) + for key in self.get_raw(): + ret[key] = {} + for field in self.get_raw()[key]: + value = { + 'decoration': 'DEFAULT', + 'optional': False, + 'additional': False, + 'splittable': False, + 'hidden': False, + '_zero': self.views[key][field]['_zero'] + if key in self.views and + field in self.views[key] and + 'zero' in self.views[key][field] + else True, + } + ret[key][field] = value + pass + # TODO: should be removed when feature/issue2414 is done + elif 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(): # i[self.get_key()] is the interface name (example for NETWORK) @@ -830,7 +866,7 @@ class GlancesPluginModel(object): Example for diskio: show=sda.* """ - # @TODO: possible optimisation: create a re.compile list + # TODO: possible optimisation: create a re.compile list return any( j for j in [re.fullmatch(i.lower(), value.lower()) for i in self.get_conf_value('show', header=header)] ) @@ -843,7 +879,7 @@ class GlancesPluginModel(object): Example for diskio: hide=sda2,sda5,loop.* """ - # @TODO: possible optimisation: create a re.compile list + # TODO: possible optimisation: create a re.compile list return any( j for j in [re.fullmatch(i.lower(), value.lower()) for i in self.get_conf_value('hide', header=header)] ) @@ -1053,10 +1089,11 @@ class GlancesPluginModel(object): :low_precision: returns less decimal places potentially (default is False) sacrificing precision for more readability. :min_symbol: Do not approach if number < min_symbol (default is K) + :decimal_count: if set, force the number of decimal number (default is None) """ symbols = ('K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y') if min_symbol in symbols: - symbols = symbols[symbols.index(min_symbol) :] + symbols = symbols[symbols.index(min_symbol):] prefix = { 'Y': 1208925819614629174706176, 'Z': 1180591620717411303424, @@ -1068,6 +1105,9 @@ class GlancesPluginModel(object): 'K': 1024, } + if number == 0: + return '0' + for symbol in reversed(symbols): value = float(number) / prefix[symbol] if value > 1: @@ -1142,36 +1182,48 @@ class GlancesPluginModel(object): def _manage_rate(fct): """Manage rate decorator for update method.""" + def compute_rate(self, stat, stat_old, time_since_update): + # Add extra data to the stats time_since_update: time since the last update + stat['time_since_update'] = time_since_update + + # 1) set _gauge for all the rate fields + # 2) compute the _rate_per_sec + # 3) set the original field to the delta between the current and the previous value + for field in self.fields_description: + # For all the field with the rate=True flag + if 'rate' in self.fields_description[field] and self.fields_description[field]['rate'] is True: + # Create a new metadata with the gauge + stat[field + '_gauge'] = stat[field] + if field + '_gauge' in stat_old: + # The stat becomes the delta between the current and the previous value + stat[field] = stat[field] - stat_old[field + '_gauge'] + # Compute the rate + if stat['time_since_update'] > 0: + stat[field + '_rate_per_sec'] = stat[field] // stat['time_since_update'] + else: + stat[field] = 0 + else: + # Avoid strange rate at the first run + stat[field] = 0 + return stat + def wrapper(self, *args, **kw): # Call the method - ret = fct(self, *args, **kw) + stat= fct(self, *args, **kw) - # Add extra data to the stats - # time_since_update: time since the last update - ret['time_since_update'] = getTimeSinceLastUpdate(self.plugin_name) + time_since_update = getTimeSinceLastUpdate(self.plugin_name) if self.stats_old: - # For all the field with the rate=True flag - for field in self.fields_description: - if 'rate' in self.fields_description[field] and self.fields_description[field]['rate'] is True: - # Create a new metadata with the gauge - ret[field + '_gauge'] = ret[field] - if field + '_gauge' in self.stats_old: - # The stat becomes the delta between the current and the previous value - ret[field] = ret[field] - self.stats_old[field + '_gauge'] - # Compute the rate - if ret['time_since_update'] > 0: - ret[field + '_rate_per_sec'] = ret[field] // ret['time_since_update'] - else: - ret[field] = 0 - else: - # Avoid strange rate at the first run - ret[field] = 0 + if self.get_key() is None: + compute_rate(self, stat, self.stats_old, time_since_update) + else: + for item in stat: + compute_rate(self, stat[item], self.stats_old[item], time_since_update) # Memorized the current stats for next run - self.stats_old = ret + self.stats_old = stat - return ret + return stat return wrapper -- cgit v1.2.3 From ae11cd960dd80728297cd10f5de82fd20376ae38 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Sun, 21 May 2023 18:22:21 +0200 Subject: Bug in the wrapper... --- glances/plugins/cpu/model.py | 5 ++++- glances/plugins/diskio/model.py | 2 +- glances/plugins/plugin/model.py | 15 +++++++++++---- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/glances/plugins/cpu/model.py b/glances/plugins/cpu/model.py index d59b3de9..1b4eeaaa 100644 --- a/glances/plugins/cpu/model.py +++ b/glances/plugins/cpu/model.py @@ -150,7 +150,10 @@ class PluginModel(GlancesPluginModel): def __init__(self, args=None, config=None): """Init the CPU plugin.""" super(PluginModel, self).__init__( - args=args, config=config, items_history_list=items_history_list, fields_description=fields_description + args=args, + config=config, + items_history_list=items_history_list, + fields_description=fields_description ) # We want to display the stat in the curse interface diff --git a/glances/plugins/diskio/model.py b/glances/plugins/diskio/model.py index 65131327..923b74f0 100644 --- a/glances/plugins/diskio/model.py +++ b/glances/plugins/diskio/model.py @@ -143,7 +143,7 @@ class PluginModel(GlancesPluginModel): # Update the stats self.stats = stats - return self.stats + return stats def update_views(self): """Update stats views.""" diff --git a/glances/plugins/plugin/model.py b/glances/plugins/plugin/model.py index 84572e3e..161e4990 100644 --- a/glances/plugins/plugin/model.py +++ b/glances/plugins/plugin/model.py @@ -1106,6 +1106,7 @@ class GlancesPluginModel(object): } if number == 0: + # Avoid 0.0 return '0' for symbol in reversed(symbols): @@ -1209,7 +1210,9 @@ class GlancesPluginModel(object): def wrapper(self, *args, **kw): # Call the method - stat= fct(self, *args, **kw) + stat = fct(self, *args, **kw) + if self.get_key() and 'sda' in stat: + logger.info("stat (fct): {}".format(stat['sda'].get('read_bytes_gauge', None))) time_since_update = getTimeSinceLastUpdate(self.plugin_name) @@ -1217,11 +1220,15 @@ class GlancesPluginModel(object): if self.get_key() is None: compute_rate(self, stat, self.stats_old, time_since_update) else: - for item in stat: - compute_rate(self, stat[item], self.stats_old[item], time_since_update) + # logger.info("Compute rate for {}".format(self.plugin_name)) + # logger.info("stat_old: {}".format(self.stats_old['sda'].get('read_bytes_gauge', None))) + # logger.info("stat (before rate): {}".format(stat['sda'].get('read_bytes_gauge', None))) + for key in stat: + compute_rate(self, stat[key], self.stats_old[key], time_since_update) + # logger.info("stat (after rate): {}".format(stat['sda']['read_bytes_gauge'])) # Memorized the current stats for next run - self.stats_old = stat + self.stats_old = copy.deepcopy(stat) return stat -- cgit v1.2.3 From 1a197aa4067980cc06b23ea5aed159ccbb8d7edb Mon Sep 17 00:00:00 2001 From: nicolargo Date: Sat, 12 Aug 2023 10:45:51 +0200 Subject: Change log for future debug --- glances/plugins/plugin/model.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/glances/plugins/plugin/model.py b/glances/plugins/plugin/model.py index 161e4990..297c642e 100644 --- a/glances/plugins/plugin/model.py +++ b/glances/plugins/plugin/model.py @@ -1210,27 +1210,27 @@ class GlancesPluginModel(object): def wrapper(self, *args, **kw): # Call the method - stat = fct(self, *args, **kw) - if self.get_key() and 'sda' in stat: - logger.info("stat (fct): {}".format(stat['sda'].get('read_bytes_gauge', None))) + stats = fct(self, *args, **kw) + if self.get_key() and 'sda' in stats: + logger.info("stat (fct): {}".format(stats['sda'])) time_since_update = getTimeSinceLastUpdate(self.plugin_name) if self.stats_old: if self.get_key() is None: - compute_rate(self, stat, self.stats_old, time_since_update) + compute_rate(self, stats, self.stats_old, time_since_update) else: # logger.info("Compute rate for {}".format(self.plugin_name)) # logger.info("stat_old: {}".format(self.stats_old['sda'].get('read_bytes_gauge', None))) - # logger.info("stat (before rate): {}".format(stat['sda'].get('read_bytes_gauge', None))) - for key in stat: - compute_rate(self, stat[key], self.stats_old[key], time_since_update) - # logger.info("stat (after rate): {}".format(stat['sda']['read_bytes_gauge'])) + # logger.info("stat (before rate): {}".format(stats['sda'].get('read_bytes_gauge', None))) + for key in stats: + compute_rate(self, stats[key], self.stats_old[key], time_since_update) + # logger.info("stat (after rate): {}".format(stats['sda']['read_bytes_gauge'])) # Memorized the current stats for next run - self.stats_old = copy.deepcopy(stat) + self.stats_old = copy.deepcopy(stats) - return stat + return stats return wrapper -- cgit v1.2.3 From 255b57a18ad0f2322d25041a86511aa7d1c68df9 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Sat, 10 Feb 2024 17:19:24 +0100 Subject: Done for network --- conf/glances.conf | 2 +- glances/outputs/glances_stdout_apidoc.py | 10 + .../static/js/components/plugin-network.vue | 25 +- glances/outputs/static/public/glances.js | 2 +- glances/plugins/cpu/__init__.py | 3 + glances/plugins/diskio/__init__.py | 10 +- glances/plugins/network/__init__.py | 328 ++++++++------------- glances/plugins/plugin/model.py | 1 + 8 files changed, 153 insertions(+), 228 deletions(-) diff --git a/conf/glances.conf b/conf/glances.conf index 35122262..26f6bf1a 100644 --- a/conf/glances.conf +++ b/conf/glances.conf @@ -179,7 +179,7 @@ tx_careful=70 tx_warning=80 tx_critical=90 # Define the list of hidden network interfaces (comma-separated regexp) -#hide=docker.*,lo +hide=docker.*,lo # Define the list of wireless network interfaces to be show (comma-separated) #show=docker.* # It is possible to overwrite the bitrate thresholds per interface diff --git a/glances/outputs/glances_stdout_apidoc.py b/glances/outputs/glances_stdout_apidoc.py index f87f259b..9ccdded1 100644 --- a/glances/outputs/glances_stdout_apidoc.py +++ b/glances/outputs/glances_stdout_apidoc.py @@ -134,6 +134,7 @@ def print_plugin_description(plugin, stat): # For each plugins with a description print('Fields descriptions:') print('') + time_since_update = False for field, description in iteritems(stat.fields_description): print( '* **{}**: {} (unit is *{}*)'.format( @@ -147,6 +148,7 @@ def print_plugin_description(plugin, stat): ) ) if 'rate' in description and description['rate']: + time_since_update = True print('* **{}**: {} (unit is *{}* per second)'.format( field + '_rate_per_sec', (description['description'][:-1] @@ -165,6 +167,14 @@ def print_plugin_description(plugin, stat): if 'unit' in description else 'None' )) + + if time_since_update: + print('* **{}**: {} (unit is *{}*)'.format( + 'time_since_update', + 'Number of seconds since last update', + 'seconds' + )) + print('') else: logger.error('No fields_description variable defined for plugin {}'.format(plugin)) diff --git a/glances/outputs/static/js/components/plugin-network.vue b/glances/outputs/static/js/components/plugin-network.vue index 3eb42bcf..2eb2aedd 100644 --- a/glances/outputs/static/js/components/plugin-network.vue +++ b/glances/outputs/static/js/components/plugin-network.vue @@ -17,24 +17,24 @@
- {{ args.byte ? $filters.bytes(network.rx / network.time_since_update) : $filters.bits(network.rx / network.time_since_update) }} + {{ args.byte ? $filters.bytes(network.bytes_recv_rate_per_sec) : $filters.bits(network.bytes_recv_rate_per_sec) }}
- {{ args.byte ? $filters.bytes(network.tx / network.time_since_update) : $filters.bits(network.tx / network.time_since_update) }} + {{ args.byte ? $filters.bytes(network.bytes_sent_rate_per_sec) : $filters.bits(network.bytes_sent_rate_per_sec) }}
- {{ args.byte ? $filters.bytes(network.cx / network.time_since_update) : $filters.bits(network.cx / network.time_since_update) }} + {{ args.byte ? $filters.bytes(network.bytes_all_rate_per_sec) : $filters.bits(network.bytes_all_rate_per_sec) }}
- {{ args.byte ? $filters.bytes(network.cumulativeRx) : $filters.bits(network.cumulativeRx) }} + {{ args.byte ? $filters.bytes(network.bytes_recv) : $filters.bits(network.bytes_recv) }}
- {{ args.byte ? $filters.bytes(network.cumulativeTx) : $filters.bits(network.cumulativeTx) }} + {{ args.byte ? $filters.bytes(network.bytes_sent) : $filters.bits(network.bytes_sent) }}
- {{ args.byte ? $filters.bytes(network.cumulativeCx) : $filters.bits(network.cumulativeCx) }} + {{ args.byte ? $filters.bytes(network.bytes_all) : $filters.bits(network.bytes_all) }}
@@ -69,13 +69,12 @@ export default { const network = { interfaceName: networkData['interface_name'], ifname: alias ? alias : networkData['interface_name'], - rx: networkData['rx'], - tx: networkData['tx'], - cx: networkData['cx'], - time_since_update: networkData['time_since_update'], - cumulativeRx: networkData['cumulative_rx'], - cumulativeTx: networkData['cumulative_tx'], - cumulativeCx: networkData['cumulative_cx'] + bytes_recv_rate_per_sec: networkData['bytes_recv_rate_per_sec'], + bytes_sent_rate_per_sec: networkData['bytes_sent_rate_per_sec'], + bytes_all_rate_per_sec: networkData['bytes_all_rate_per_sec'], + bytes_recv: networkData['bytes_recv'], + bytes_sent: networkData['bytes_sent'], + bytes_all: networkData['bytes_all'] }; return network; diff --git a/glances/outputs/static/public/glances.js b/glances/outputs/static/public/glances.js index 9d321b93..8b34219f 100644 --- a/glances/outputs/static/public/glances.js +++ b/glances/outputs/static/public/glances.js @@ -28,4 +28,4 @@ function n(e){return"[object Object]"===Object.prototype.toString.call(e)}Object * https://jaywcjlove.github.io/hotkeys-js * Licensed under the MIT license */ -var mo="undefined"!=typeof navigator&&navigator.userAgent.toLowerCase().indexOf("firefox")>0;function bo(e,t,n,r){e.addEventListener?e.addEventListener(t,n,r):e.attachEvent&&e.attachEvent("on".concat(t),(function(){n(window.event)}))}function vo(e,t){for(var n=t.slice(0,t.length-1),r=0;r=0;)t[n-1]+=",",t.splice(n,1),n=t.lastIndexOf("");return t}for(var wo={backspace:8,"⌫":8,tab:9,clear:12,enter:13,"↩":13,return:13,esc:27,escape:27,space:32,left:37,up:38,right:39,down:40,del:46,delete:46,ins:45,insert:45,home:36,end:35,pageup:33,pagedown:34,capslock:20,num_0:96,num_1:97,num_2:98,num_3:99,num_4:100,num_5:101,num_6:102,num_7:103,num_8:104,num_9:105,num_multiply:106,num_add:107,num_enter:108,num_subtract:109,num_decimal:110,num_divide:111,"⇪":20,",":188,".":190,"/":191,"`":192,"-":mo?173:189,"=":mo?61:187,";":mo?59:186,"'":222,"[":219,"]":221,"\\":220},xo={"⇧":16,shift:16,"⌥":18,alt:18,option:18,"⌃":17,ctrl:17,control:17,"⌘":91,cmd:91,command:91},_o={16:"shiftKey",18:"altKey",17:"ctrlKey",91:"metaKey",shiftKey:16,ctrlKey:17,altKey:18,metaKey:91},ko={16:!1,18:!1,17:!1,91:!1},So={},Co=1;Co<20;Co++)wo["f".concat(Co)]=111+Co;var To=[],Ao=!1,Eo="all",Oo=[],Io=function(e){return wo[e.toLowerCase()]||xo[e.toLowerCase()]||e.toUpperCase().charCodeAt(0)};function Po(e){Eo=e||"all"}function No(){return Eo||"all"}var Lo=function(e){var t=e.key,n=e.scope,r=e.method,i=e.splitKey,s=void 0===i?"+":i;yo(t).forEach((function(e){var t=e.split(s),i=t.length,o=t[i-1],a="*"===o?"*":Io(o);if(So[a]){n||(n=No());var l=i>1?vo(xo,t):[];So[a]=So[a].filter((function(e){return!((!r||e.method===r)&&e.scope===n&&function(e,t){for(var n=e.length>=t.length?e:t,r=e.length>=t.length?t:e,i=!0,s=0;s0,ko)Object.prototype.hasOwnProperty.call(ko,s)&&(!ko[s]&&t.mods.indexOf(+s)>-1||ko[s]&&-1===t.mods.indexOf(+s))&&(i=!1);(0!==t.mods.length||ko[16]||ko[18]||ko[17]||ko[91])&&!i&&"*"!==t.shortcut||(t.keys=[],t.keys=t.keys.concat(To),!1===t.method(e,t)&&(e.preventDefault?e.preventDefault():e.returnValue=!1,e.stopPropagation&&e.stopPropagation(),e.cancelBubble&&(e.cancelBubble=!0)))}}function Mo(e,t){var n=So["*"],r=e.keyCode||e.which||e.charCode;if(jo.filter.call(this,e)){if(93!==r&&224!==r||(r=91),-1===To.indexOf(r)&&229!==r&&To.push(r),["ctrlKey","altKey","shiftKey","metaKey"].forEach((function(t){var n=_o[t];e[t]&&-1===To.indexOf(n)?To.push(n):!e[t]&&To.indexOf(n)>-1?To.splice(To.indexOf(n),1):"metaKey"===t&&e[t]&&3===To.length&&(e.ctrlKey||e.shiftKey||e.altKey||(To=To.slice(To.indexOf(n))))})),r in ko){for(var i in ko[r]=!0,xo)xo[i]===r&&(jo[i]=!0);if(!n)return}for(var s in ko)Object.prototype.hasOwnProperty.call(ko,s)&&(ko[s]=e[_o[s]]);e.getModifierState&&(!e.altKey||e.ctrlKey)&&e.getModifierState("AltGraph")&&(-1===To.indexOf(17)&&To.push(17),-1===To.indexOf(18)&&To.push(18),ko[17]=!0,ko[18]=!0);var o=No();if(n)for(var a=0;a1&&(i=vo(xo,e)),(e="*"===(e=e[e.length-1])?"*":Io(e))in So||(So[e]=[]),So[e].push({keyup:l,keydown:c,scope:s,mods:i,shortcut:r[a],method:n,key:r[a],splitKey:u,element:o});void 0!==o&&!function(e){return Oo.indexOf(e)>-1}(o)&&window&&(Oo.push(o),bo(o,"keydown",(function(e){Mo(e,o)}),d),Ao||(Ao=!0,bo(window,"focus",(function(){To=[]}),d)),bo(o,"keyup",(function(e){Mo(e,o),function(e){var t=e.keyCode||e.which||e.charCode,n=To.indexOf(t);if(n>=0&&To.splice(n,1),e.key&&"meta"===e.key.toLowerCase()&&To.splice(0,To.length),93!==t&&224!==t||(t=91),t in ko)for(var r in ko[t]=!1,xo)xo[r]===t&&(jo[r]=!1)}(e)}),d))}var Ro={getPressedKeyString:function(){return To.map((function(e){return t=e,Object.keys(wo).find((function(e){return wo[e]===t}))||function(e){return Object.keys(xo).find((function(t){return xo[t]===e}))}(e)||String.fromCharCode(e);var t}))},setScope:Po,getScope:No,deleteScope:function(e,t){var n,r;for(var i in e||(e=No()),So)if(Object.prototype.hasOwnProperty.call(So,i))for(n=So[i],r=0;r1&&void 0!==arguments[1]?arguments[1]:"all";Object.keys(So).forEach((function(n){So[n].filter((function(n){return n.scope===t&&n.shortcut===e})).forEach((function(e){e&&e.method&&e.method()}))}))},unbind:function(e){if(void 0===e)Object.keys(So).forEach((function(e){return delete So[e]}));else if(Array.isArray(e))e.forEach((function(e){e.key&&Lo(e)}));else if("object"==typeof e)e.key&&Lo(e);else if("string"==typeof e){for(var t=arguments.length,n=new Array(t>1?t-1:0),r=1;r=this.limits[e][l]){var c=l.lastIndexOf("_");return l.substring(c+1)+s}}return"ok"+s}getAlertLog(e,t,n,r){return this.getAlert(e,t,n,r,!0)}};const Ho=new class{data=void 0;init(e=60){let t;const n=()=>(Uo.status="PENDING",Promise.all([fetch("api/4/all",{method:"GET"}).then((e=>e.json())),fetch("api/4/all/views",{method:"GET"}).then((e=>e.json()))]).then((e=>{const t={stats:e[0],views:e[1],isBsd:"FreeBSD"===e[0].system.os_name,isLinux:"Linux"===e[0].system.os_name,isSunOS:"SunOS"===e[0].system.os_name,isMac:"Darwin"===e[0].system.os_name,isWindows:"Windows"===e[0].system.os_name};this.data=t,Uo.data=t,Uo.status="SUCCESS"})).catch((e=>{console.log(e),Uo.status="FAILURE"})).then((()=>{t&&clearTimeout(t),t=setTimeout(n,1e3*e)})));n(),fetch("api/4/all/limits",{method:"GET"}).then((e=>e.json())).then((e=>{$o.setLimits(e)})),fetch("api/4/args",{method:"GET"}).then((e=>e.json())).then(((e={})=>{Uo.args={...Uo.args,...e}})),fetch("api/4/config",{method:"GET"}).then((e=>e.json())).then(((e={})=>{Uo.config={...Uo.config,...e}}))}getData(){return this.data}};const Vo=new class{constructor(){this.favico=new(zo())({animation:"none"})}badge(e){this.favico.badge(e)}reset(){this.favico.reset()}},Go={key:0},Wo={class:"container-fluid"},Zo={class:"row"},Ko={class:"col-sm-12 col-lg-24"},Qo=wi("div",{class:"row"}," ",-1),Xo={class:"row"},Jo={class:"col-sm-12 col-lg-24"},Yo=wi("div",{class:"row"}," ",-1),ea={class:"divTable",style:{width:"100%"}},ta={class:"divTableBody"},na={class:"divTableRow"},ra={class:"divTableHead"},ia={class:"divTableHead"},sa={class:"divTableHead"},oa={class:"divTableHead"},aa={class:"divTableRow"},la={class:"divTableCell"},ca={class:"divTableCell"},ua={class:"divTableCell"},da={class:"divTableCell"},fa={class:"divTableRow"},pa={class:"divTableCell"},ha={class:"divTableCell"},ga={class:"divTableCell"},ma={class:"divTableCell"},ba={class:"divTableRow"},va={class:"divTableCell"},ya={class:"divTableCell"},wa={class:"divTableCell"},xa={class:"divTableCell"},_a={class:"divTableRow"},ka={class:"divTableCell"},Sa={class:"divTableCell"},Ca={class:"divTableCell"},Ta={class:"divTableCell"},Aa={class:"divTableRow"},Ea={class:"divTableCell"},Oa={class:"divTableCell"},Ia={class:"divTableCell"},Pa={class:"divTableCell"},Na={class:"divTableRow"},La={class:"divTableCell"},Da={class:"divTableCell"},Ma={class:"divTableCell"},ja={class:"divTableCell"},Ra={class:"divTableRow"},qa={class:"divTableCell"},Ba={class:"divTableCell"},Ua={class:"divTableCell"},Fa={class:"divTableCell"},za={class:"divTableRow"},$a=wi("div",{class:"divTableCell"}," ",-1),Ha={class:"divTableCell"},Va={class:"divTableCell"},Ga={class:"divTableCell"},Wa={class:"divTableRow"},Za=wi("div",{class:"divTableCell"}," ",-1),Ka={class:"divTableCell"},Qa={class:"divTableCell"},Xa={class:"divTableCell"},Ja={class:"divTableRow"},Ya=wi("div",{class:"divTableCell"}," ",-1),el={class:"divTableCell"},tl={class:"divTableCell"},nl={class:"divTableCell"},rl={class:"divTableRow"},il=wi("div",{class:"divTableCell"}," ",-1),sl={class:"divTableCell"},ol=wi("div",{class:"divTableCell"}," ",-1),al={class:"divTableCell"},ll={class:"divTableRow"},cl=wi("div",{class:"divTableCell"}," ",-1),ul={class:"divTableCell"},dl=wi("div",{class:"divTableCell"}," ",-1),fl=wi("div",{class:"divTableCell"}," ",-1),pl={class:"divTableRow"},hl=wi("div",{class:"divTableCell"}," ",-1),gl={class:"divTableCell"},ml=wi("div",{class:"divTableCell"}," ",-1),bl=wi("div",{class:"divTableCell"}," ",-1),vl={class:"divTableRow"},yl=wi("div",{class:"divTableCell"}," ",-1),wl={class:"divTableCell"},xl=wi("div",{class:"divTableCell"}," ",-1),_l=wi("div",{class:"divTableCell"}," ",-1),kl={class:"divTableRow"},Sl=wi("div",{class:"divTableCell"}," ",-1),Cl={class:"divTableCell"},Tl=wi("div",{class:"divTableCell"}," ",-1),Al=wi("div",{class:"divTableCell"}," ",-1),El={class:"divTableRow"},Ol=wi("div",{class:"divTableCell"}," ",-1),Il={class:"divTableCell"},Pl=wi("div",{class:"divTableCell"}," ",-1),Nl=wi("div",{class:"divTableCell"}," ",-1),Ll={class:"divTableRow"},Dl=wi("div",{class:"divTableCell"}," ",-1),Ml={class:"divTableCell"},jl=wi("div",{class:"divTableCell"}," ",-1),Rl=wi("div",{class:"divTableCell"}," ",-1),ql={class:"divTableRow"},Bl=wi("div",{class:"divTableCell"}," ",-1),Ul={class:"divTableCell"},Fl=wi("div",{class:"divTableCell"}," ",-1),zl=wi("div",{class:"divTableCell"}," ",-1),$l={class:"divTableRow"},Hl=wi("div",{class:"divTableCell"}," ",-1),Vl={class:"divTableCell"},Gl=wi("div",{class:"divTableCell"}," ",-1),Wl=wi("div",{class:"divTableCell"}," ",-1),Zl={class:"divTableRow"},Kl=wi("div",{class:"divTableCell"}," ",-1),Ql={class:"divTableCell"},Xl=wi("div",{class:"divTableCell"}," ",-1),Jl=wi("div",{class:"divTableCell"}," ",-1),Yl=wi("div",null,[wi("p",null,[Si(" For an exhaustive list of key bindings, "),wi("a",{href:"https://glances.readthedocs.io/en/latest/cmds.html#interactive-commands"},"click here"),Si(". ")])],-1),ec=wi("div",null,[wi("p",null,[Si("Press "),wi("b",null,"h"),Si(" to came back to Glances.")])],-1);const tc={data:()=>({help:void 0}),mounted(){fetch("api/4/help",{method:"GET"}).then((e=>e.json())).then((e=>this.help=e))}};var nc=n(3744);const rc=(0,nc.Z)(tc,[["render",function(e,t,n,r,i,s){return i.help?(li(),pi("div",Go,[wi("div",Wo,[wi("div",Zo,[wi("div",Ko,pe(i.help.version)+" "+pe(i.help.psutil_version),1)]),Qo,wi("div",Xo,[wi("div",Jo,pe(i.help.configuration_file),1)]),Yo]),wi("div",ea,[wi("div",ta,[wi("div",na,[wi("div",ra,pe(i.help.header_sort.replace(":","")),1),wi("div",ia,pe(i.help.header_show_hide.replace(":","")),1),wi("div",sa,pe(i.help.header_toggle.replace(":","")),1),wi("div",oa,pe(i.help.header_miscellaneous.replace(":","")),1)]),wi("div",aa,[wi("div",la,pe(i.help.sort_auto),1),wi("div",ca,pe(i.help.show_hide_application_monitoring),1),wi("div",ua,pe(i.help.toggle_bits_bytes),1),wi("div",da,pe(i.help.misc_erase_process_filter),1)]),wi("div",fa,[wi("div",pa,pe(i.help.sort_cpu),1),wi("div",ha,pe(i.help.show_hide_diskio),1),wi("div",ga,pe(i.help.toggle_count_rate),1),wi("div",ma,pe(i.help.misc_generate_history_graphs),1)]),wi("div",ba,[wi("div",va,pe(i.help.sort_io_rate),1),wi("div",ya,pe(i.help.show_hide_containers),1),wi("div",wa,pe(i.help.toggle_used_free),1),wi("div",xa,pe(i.help.misc_help),1)]),wi("div",_a,[wi("div",ka,pe(i.help.sort_mem),1),wi("div",Sa,pe(i.help.show_hide_top_extended_stats),1),wi("div",Ca,pe(i.help.toggle_bar_sparkline),1),wi("div",Ta,pe(i.help.misc_accumulate_processes_by_program),1)]),wi("div",Aa,[wi("div",Ea,pe(i.help.sort_process_name),1),wi("div",Oa,pe(i.help.show_hide_filesystem),1),wi("div",Ia,pe(i.help.toggle_separate_combined),1),wi("div",Pa,pe(i.help.misc_kill_process)+" - N/A in WebUI ",1)]),wi("div",Na,[wi("div",La,pe(i.help.sort_cpu_times),1),wi("div",Da,pe(i.help.show_hide_gpu),1),wi("div",Ma,pe(i.help.toggle_live_cumulative),1),wi("div",ja,pe(i.help.misc_reset_processes_summary_min_max),1)]),wi("div",Ra,[wi("div",qa,pe(i.help.sort_user),1),wi("div",Ba,pe(i.help.show_hide_ip),1),wi("div",Ua,pe(i.help.toggle_linux_percentage),1),wi("div",Fa,pe(i.help.misc_quit),1)]),wi("div",za,[$a,wi("div",Ha,pe(i.help.show_hide_tcp_connection),1),wi("div",Va,pe(i.help.toggle_cpu_individual_combined),1),wi("div",Ga,pe(i.help.misc_reset_history),1)]),wi("div",Wa,[Za,wi("div",Ka,pe(i.help.show_hide_alert),1),wi("div",Qa,pe(i.help.toggle_gpu_individual_combined),1),wi("div",Xa,pe(i.help.misc_delete_warning_alerts),1)]),wi("div",Ja,[Ya,wi("div",el,pe(i.help.show_hide_network),1),wi("div",tl,pe(i.help.toggle_short_full),1),wi("div",nl,pe(i.help.misc_delete_warning_and_critical_alerts),1)]),wi("div",rl,[il,wi("div",sl,pe(i.help.sort_cpu_times),1),ol,wi("div",al,pe(i.help.misc_edit_process_filter_pattern)+" - N/A in WebUI ",1)]),wi("div",ll,[cl,wi("div",ul,pe(i.help.show_hide_irq),1),dl,fl]),wi("div",pl,[hl,wi("div",gl,pe(i.help.show_hide_raid_plugin),1),ml,bl]),wi("div",vl,[yl,wi("div",wl,pe(i.help.show_hide_sensors),1),xl,_l]),wi("div",kl,[Sl,wi("div",Cl,pe(i.help.show_hide_wifi_module),1),Tl,Al]),wi("div",El,[Ol,wi("div",Il,pe(i.help.show_hide_processes),1),Pl,Nl]),wi("div",Ll,[Dl,wi("div",Ml,pe(i.help.show_hide_left_sidebar),1),jl,Rl]),wi("div",ql,[Bl,wi("div",Ul,pe(i.help.show_hide_quick_look),1),Fl,zl]),wi("div",$l,[Hl,wi("div",Vl,pe(i.help.show_hide_cpu_mem_swap),1),Gl,Wl]),wi("div",Zl,[Kl,wi("div",Ql,pe(i.help.show_hide_all),1),Xl,Jl])])]),Yl,ec])):Ti("v-if",!0)}]]),ic={class:"plugin"},sc={id:"alerts"},oc={key:0,class:"title"},ac={key:1,class:"title"},lc={id:"alert"},cc={class:"table"},uc={class:"table-cell text-left"};var dc=n(6486);const fc={props:{data:{type:Object}},computed:{stats(){return this.data.stats.alert},alerts(){return(this.stats||[]).map((e=>{const t={};var n=(new Date).getTimezoneOffset();if(t.state=e.state,t.type=e.type,t.begin=1e3*e.begin-60*n*1e3,t.end=1e3*e.end-60*n*1e3,t.ongoing=-1==e.end,t.min=e.min,t.avg=e.avg,t.max=e.max,t.top=e.top.join(", "),!t.ongoing){const e=t.end-t.begin,n=parseInt(e/1e3%60),r=parseInt(e/6e4%60),i=parseInt(e/36e5%24);t.duration=(0,dc.padStart)(i,2,"0")+":"+(0,dc.padStart)(r,2,"0")+":"+(0,dc.padStart)(n,2,"0")}return t}))},hasAlerts(){return this.countAlerts>0},countAlerts(){return this.alerts.length},hasOngoingAlerts(){return this.countOngoingAlerts>0},countOngoingAlerts(){return this.alerts.filter((({ongoing:e})=>e)).length}},watch:{countOngoingAlerts(){this.countOngoingAlerts?Vo.badge(this.countOngoingAlerts):Vo.reset()}},methods:{formatDate:e=>new Date(e).toISOString().slice(0,19).replace(/[^\d-:]/," ")}},pc=(0,nc.Z)(fc,[["render",function(e,t,n,r,i,s){return li(),pi("div",ic,[wi("section",sc,[s.hasAlerts?(li(),pi("span",oc," Warning or critical alerts (last "+pe(s.countAlerts)+" entries) ",1)):(li(),pi("span",ac,"No warning or critical alert detected"))]),wi("section",lc,[wi("div",cc,[(li(!0),pi(ni,null,pr(s.alerts,((t,n)=>(li(),pi("div",{class:"table-row",key:n},[wi("div",uc,[Si(pe(s.formatDate(t.begin))+" "+pe(t.tz)+" ("+pe(t.ongoing?"ongoing":t.duration)+") - ",1),On(wi("span",null,pe(t.state)+" on ",513),[[Ds,!t.ongoing]]),wi("span",{class:ce(t.state.toLowerCase())},pe(t.type),3),Si(" ("+pe(e.$filters.number(t.max,1))+") "+pe(t.top),1)])])))),128))])])])}]]),hc={key:0,id:"cloud",class:"plugin"},gc={class:"title"};const mc={props:{data:{type:Object}},computed:{stats(){return this.data.stats.cloud},provider(){return void 0!==this.stats.id?`${stats.platform}`:null},instance(){const{stats:e}=this;return void 0!==this.stats.id?`${e.type} instance ${e.name} (${e.region})`:null}}},bc=(0,nc.Z)(mc,[["render",function(e,t,n,r,i,s){return s.instance||s.provider?(li(),pi("section",hc,[wi("span",gc,pe(s.provider),1),Si(" "+pe(s.instance),1)])):Ti("v-if",!0)}]]),vc={class:"plugin",id:"connections"},yc=wi("div",{class:"table-row"},[wi("div",{class:"table-cell text-left title"},"TCP CONNECTIONS"),wi("div",{class:"table-cell"})],-1),wc={class:"table-row"},xc=wi("div",{class:"table-cell text-left"},"Listen",-1),_c=wi("div",{class:"table-cell"},null,-1),kc={class:"table-cell"},Sc={class:"table-row"},Cc=wi("div",{class:"table-cell text-left"},"Initiated",-1),Tc=wi("div",{class:"table-cell"},null,-1),Ac={class:"table-cell"},Ec={class:"table-row"},Oc=wi("div",{class:"table-cell text-left"},"Established",-1),Ic=wi("div",{class:"table-cell"},null,-1),Pc={class:"table-cell"},Nc={class:"table-row"},Lc=wi("div",{class:"table-cell text-left"},"Terminated",-1),Dc=wi("div",{class:"table-cell"},null,-1),Mc={class:"table-cell"},jc={class:"table-row"},Rc=wi("div",{class:"table-cell text-left"},"Tracked",-1),qc=wi("div",{class:"table-cell"},null,-1);const Bc={props:{data:{type:Object}},computed:{stats(){return this.data.stats.connections},view(){return this.data.views.connections},listen(){return this.stats.LISTEN},initiated(){return this.stats.initiated},established(){return this.stats.ESTABLISHED},terminated(){return this.stats.terminated},tracked(){return{count:this.stats.nf_conntrack_count,max:this.stats.nf_conntrack_max}}},methods:{getDecoration(e){if(void 0!==this.view[e])return this.view[e].decoration.toLowerCase()}}},Uc=(0,nc.Z)(Bc,[["render",function(e,t,n,r,i,s){return li(),pi("section",vc,[yc,wi("div",wc,[xc,_c,wi("div",kc,pe(s.listen),1)]),wi("div",Sc,[Cc,Tc,wi("div",Ac,pe(s.initiated),1)]),wi("div",Ec,[Oc,Ic,wi("div",Pc,pe(s.established),1)]),wi("div",Nc,[Lc,Dc,wi("div",Mc,pe(s.terminated),1)]),wi("div",jc,[Rc,qc,wi("div",{class:ce(["table-cell",s.getDecoration("nf_conntrack_percent")])},pe(s.tracked.count)+"/"+pe(s.tracked.max),3)])])}]]),Fc={id:"cpu",class:"plugin"},zc={class:"row"},$c={class:"col-sm-24 col-md-12 col-lg-8"},Hc={class:"table"},Vc={class:"table-row"},Gc=wi("div",{class:"table-cell