From ef9694cd6ada357c56b338bb5aa069575a7c397b Mon Sep 17 00:00:00 2001 From: nicolargo Date: Sat, 20 Jan 2024 14:56:57 +0100 Subject: First version of the #2662 - Need to study how to display the swap --- conf/glances.conf | 4 +++ glances/outputs/glances_bars.py | 31 ++++++++++------ glances/plugins/load/__init__.py | 66 +++++++++++++++++++++++------------ glances/plugins/quicklook/__init__.py | 49 ++++++++++++++++++-------- 4 files changed, 102 insertions(+), 48 deletions(-) diff --git a/conf/glances.conf b/conf/glances.conf index 4f07a8b0..75512743 100644 --- a/conf/glances.conf +++ b/conf/glances.conf @@ -57,6 +57,10 @@ mem_critical=90 swap_careful=50 swap_warning=70 swap_critical=90 +# Source: http://blog.scoutapp.com/articles/2009/07/31/understanding-load-averages +load_careful=70 +load_warning=100 +load_critical=500 [system] # This plugin display the first line in the Glances UI with: diff --git a/glances/outputs/glances_bars.py b/glances/outputs/glances_bars.py index 75899480..8e803fd6 100644 --- a/glances/outputs/glances_bars.py +++ b/glances/outputs/glances_bars.py @@ -28,7 +28,12 @@ class Bar(object): sys.stdout.flush() """ - def __init__(self, size, percentage_char='|', empty_char=' ', pre_char='[', post_char=']', with_text=True): + def __init__(self, size, + percentage_char='|', + empty_char=' ', + pre_char='[', post_char=']', + display_value=True, + min_value=0, max_value=100): # Build curses_bars self.__curses_bars = [empty_char] * 5 + [percentage_char] * 5 # Bar size @@ -36,20 +41,20 @@ class Bar(object): # Bar current percent self.__percent = 0 # Min and max value - self.min_value = 0 - self.max_value = 100 + self.min_value = min_value + self.max_value = max_value # Char used for the decoration self.__pre_char = pre_char self.__post_char = post_char self.__empty_char = empty_char - self.__with_text = with_text + self.__display_value = display_value @property def size(self, with_decoration=False): # Return the bar size, with or without decoration if with_decoration: return self.__size - if self.__with_text: + if self.__display_value: return self.__size - 6 @property @@ -58,10 +63,8 @@ class Bar(object): @percent.setter def percent(self, value): - if value <= self.min_value: + if value < self.min_value: value = self.min_value - if value >= self.max_value: - value = self.max_value self.__percent = value @property @@ -74,14 +77,20 @@ class Bar(object): def get(self): """Return the bars.""" - frac, whole = modf(self.size * self.percent / 100.0) + value = self.percent + if value > self.max_value: + value = self.max_value + frac, whole = modf(self.size * value / 100.0) ret = self.__curses_bars[8] * int(whole) if frac > 0: ret += self.__curses_bars[int(frac * 8)] whole += 1 ret += self.__empty_char * int(self.size - whole) - if self.__with_text: - ret = '{}{:5.1f}%'.format(ret, self.percent) + if self.__display_value: + if self.percent > self.max_value: + ret = '{}>{:4.0f}%'.format(ret, self.max_value) + else: + ret = '{}{:5.1f}%'.format(ret, self.percent) return ret def __str__(self): diff --git a/glances/plugins/load/__init__.py b/glances/plugins/load/__init__.py index 292821dd..cc936078 100644 --- a/glances/plugins/load/__init__.py +++ b/glances/plugins/load/__init__.py @@ -58,6 +58,14 @@ items_history_list = [ {'name': 'min15', 'description': '15 minutes load'}, ] +# Get the number of logical CPU core only once +# the variable is also shared with the QuickLook plugin +try: + nb_log_core = CorePluginModel().update()["log"] +except Exception as e: + logger.warning('Error: Can not retrieve the CPU core number (set it to 1) ({})'.format(e)) + nb_log_core = 1 + class PluginModel(GlancesPluginModel): """Glances load plugin. @@ -74,24 +82,6 @@ class PluginModel(GlancesPluginModel): # We want to display the stat in the curse interface self.display_curse = True - # Call CorePluginModel in order to display the core number - try: - self.nb_log_core = CorePluginModel(args=self.args).update()["log"] - except Exception as e: - logger.warning('Error: Can not retrieve the CPU core number (set it to 1) ({})'.format(e)) - self.nb_log_core = 1 - - def _getloadavg(self): - """Get load average. On both Linux and Windows thanks to PsUtil""" - try: - return psutil.getloadavg() - except (AttributeError, OSError): - pass - try: - return os.getloadavg() - except (AttributeError, OSError): - return None - @GlancesPluginModel._check_decorator @GlancesPluginModel._log_result_decorator def update(self): @@ -103,11 +93,16 @@ class PluginModel(GlancesPluginModel): # Update stats using the standard system lib # Get the load using the os standard lib - load = self._getloadavg() + load = get_load_average() if load is None: stats = self.get_init_value() else: - stats = {'min1': load[0], 'min5': load[1], 'min15': load[2], 'cpucore': self.nb_log_core} + stats = { + 'min1': load[0], + 'min5': load[1], + 'min15': load[2], + 'cpucore': get_nb_log_core() + } elif self.input_method == 'snmp': # Update stats using SNMP @@ -122,7 +117,7 @@ class PluginModel(GlancesPluginModel): for k, v in iteritems(stats): stats[k] = float(v) - stats['cpucore'] = self.nb_log_core + stats['cpucore'] = get_nb_log_core() # Update the stats self.stats = stats @@ -170,9 +165,9 @@ class PluginModel(GlancesPluginModel): ret.append(self.curse_new_line()) msg = '{:7}'.format('{} min'.format(load_time)) ret.append(self.curse_add_line(msg)) - if args.disable_irix and self.nb_log_core != 0: + if args.disable_irix and get_nb_log_core() != 0: # Enable Irix mode for load (see issue #1554) - load_stat = self.stats['min{}'.format(load_time)] / self.nb_log_core * 100 + load_stat = self.stats['min{}'.format(load_time)] / get_nb_log_core() * 100 msg = '{:>5.1f}%'.format(load_stat) else: # Default mode for load @@ -181,3 +176,28 @@ class PluginModel(GlancesPluginModel): ret.append(self.curse_add_line(msg, self.get_views(key='min{}'.format(load_time), option='decoration'))) return ret + + +def get_nb_log_core(): + """Get the number of logical CPU core.""" + return nb_log_core + + +def get_load_average(percent: bool = False): + """Get load average. On both Linux and Windows thanks to PsUtil + + if percent is True, return the load average in percent + Ex: if you only have one CPU core and the load average is 1.0, then return 100%""" + load_average = None + try: + load_average = psutil.getloadavg() + except (AttributeError, OSError): + try: + load_average = os.getloadavg() + except (AttributeError, OSError): + pass + + if load_average and percent: + return tuple([i / get_nb_log_core() * 100 for i in load_average]) + else: + return load_average diff --git a/glances/plugins/quicklook/__init__.py b/glances/plugins/quicklook/__init__.py index 862eb243..083a5939 100644 --- a/glances/plugins/quicklook/__init__.py +++ b/glances/plugins/quicklook/__init__.py @@ -2,7 +2,7 @@ # # This file is part of Glances. # -# SPDX-FileCopyrightText: 2022 Nicolas Hennion +# SPDX-FileCopyrightText: 2024 Nicolas Hennion # # SPDX-License-Identifier: LGPL-3.0-only # @@ -11,6 +11,7 @@ from glances.logger import logger from glances.cpu_percent import cpu_percent +from glances.plugins.load import get_load_average, get_nb_log_core from glances.outputs.glances_bars import Bar from glances.outputs.glances_sparklines import Sparkline from glances.plugins.plugin.model import GlancesPluginModel @@ -84,11 +85,20 @@ class PluginModel(GlancesPluginModel): # Grab quicklook stats: CPU, MEM and SWAP if self.input_method == 'local': - # Get the latest CPU percent value + # Get system information + cpu_info = cpu_percent.get_info() + stats['cpu_name'] = cpu_info['cpu_name'] + stats['cpu_hz_current'] = ( + self._mhz_to_hz(cpu_info['cpu_hz_current']) if cpu_info['cpu_hz_current'] is not None else None + ) + stats['cpu_hz'] = self._mhz_to_hz(cpu_info['cpu_hz']) if cpu_info['cpu_hz'] is not None else None + + # Get the CPU percent value (global and per core) + # Stats is shared across all plugins stats['cpu'] = cpu_percent.get() stats['percpu'] = cpu_percent.get(percpu=True) - # Use the psutil lib for the memory (virtual and swap) + # Get the virtual and swap memory stats['mem'] = psutil.virtual_memory().percent try: stats['swap'] = psutil.swap_memory().percent @@ -96,13 +106,14 @@ class PluginModel(GlancesPluginModel): # Correct issue in Illumos OS (see #1767) stats['swap'] = None - # Get additional information - cpu_info = cpu_percent.get_info() - stats['cpu_name'] = cpu_info['cpu_name'] - stats['cpu_hz_current'] = ( - self._mhz_to_hz(cpu_info['cpu_hz_current']) if cpu_info['cpu_hz_current'] is not None else None - ) - stats['cpu_hz'] = self._mhz_to_hz(cpu_info['cpu_hz']) if cpu_info['cpu_hz'] is not None else None + # Get load + stats['cpucore'] = get_nb_log_core() + try: + # Load average is a tuple (1 min, 5 min, 15 min) + # Process only the 15 min value + stats['load'] = get_load_average(percent=True)[2] + except (TypeError, IndexError): + stats['load'] = None elif self.input_method == 'snmp': # Not available @@ -118,12 +129,16 @@ class PluginModel(GlancesPluginModel): # Call the father's method super(PluginModel, self).update_views() - # Add specifics information - # Alert only + # Alert for CPU, MEM and SWAP for key in ['cpu', 'mem', 'swap']: if key in self.stats: self.views[key]['decoration'] = self.get_alert(self.stats[key], header=key) + # Alert for LOAD + self.views['load']['decoration'] = self.get_alert( + self.stats['load'], header='load' + ) + def msg_curse(self, args=None, max_width=10): """Return the list to display in the UI.""" # Init the return message @@ -145,9 +160,13 @@ class PluginModel(GlancesPluginModel): sparkline_tag = data.available if not sparkline_tag: # Fallback to bar if Sparkline module is not installed - data = Bar(max_width, percentage_char=self.get_conf_value('percentage_char', default=['|'])[0]) + data = Bar(max_width, + percentage_char=self.get_conf_value('percentage_char', default=['|'])[0]) # Build the string message + ########################## + + # System information if 'cpu_name' in self.stats and 'cpu_hz_current' in self.stats and 'cpu_hz' in self.stats: msg_name = self.stats['cpu_name'] if self.stats['cpu_hz_current'] and self.stats['cpu_hz']: @@ -160,7 +179,9 @@ class PluginModel(GlancesPluginModel): ret.append(self.curse_add_line(msg_name)) ret.append(self.curse_add_line(msg_freq)) ret.append(self.curse_new_line()) - for key in ['cpu', 'mem', 'swap']: + + # Loop over CPU, MEM and LOAD + for key in ['cpu', 'mem', 'load']: if key == 'cpu' and args.percpu: if sparkline_tag: raw_cpu = self.get_raw_history(item='percpu', nb=data.size) -- cgit v1.2.3