summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authornicolargo <nicolashennion@gmail.com>2024-05-02 18:20:58 +0200
committernicolargo <nicolashennion@gmail.com>2024-05-02 18:20:58 +0200
commit39be7f554b3f462be3da8c8e665b01ed43c546c5 (patch)
treed2785a3ac471a3e3a62948e9221fb1b013259178
parent8608e97a9f7b4302690dbffdaea7f763a5d016af (diff)
Stats update is now threaded / Sensors are sorted by label/Alias in the UI
-rw-r--r--.github/workflows/build.yml3
-rw-r--r--glances/exports/export.py18
-rw-r--r--glances/plugins/plugin/model.py10
-rw-r--r--glances/plugins/sensors/__init__.py136
-rw-r--r--glances/stats.py39
5 files changed, 131 insertions, 75 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index a66f9e01..077561ee 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -11,7 +11,8 @@ env:
# Alpine image platform: https://hub.docker.com/_/alpine
DOCKER_PLATFORMS: linux/amd64,linux/arm64/v8,linux/arm/v6,linux/arm/v7
# Ubuntu image platforms list: https://hub.docker.com/_/ubuntu
- DOCKER_PLATFORMS_UBUNTU: linux/amd64,linux/arm64/v8,linux/arm/v7
+ # linux/arm/v7 do not work (Cargo/Rust not available)
+ DOCKER_PLATFORMS_UBUNTU: linux/amd64,linux/arm64/v8
on:
workflow_call:
diff --git a/glances/exports/export.py b/glances/exports/export.py
index fe710ee0..83753c8c 100644
--- a/glances/exports/export.py
+++ b/glances/exports/export.py
@@ -14,8 +14,8 @@ I am your father...
"""
from glances.globals import json_dumps
-
from glances.globals import NoOptionError, NoSectionError, iteritems, iterkeys
+from glances.timer import Counter
from glances.logger import logger
@@ -58,6 +58,22 @@ class GlancesExport(object):
# Save last export list
self._last_exported_list = None
+ def _log_result_decorator(fct):
+ """Log (DEBUG) the result of the function fct."""
+
+ def wrapper(*args, **kw):
+ counter = Counter()
+ ret = fct(*args, **kw)
+ duration = counter.get()
+ logger.debug(
+ "{} {} {} return {} in {} seconds".format(
+ args[0].__class__.__name__, args[0].__class__.__module__, fct.__name__, ret, duration
+ )
+ )
+ return ret
+
+ return wrapper
+
def exit(self):
"""Close the export module."""
logger.debug("Finalise export interface %s" % self.export_name)
diff --git a/glances/plugins/plugin/model.py b/glances/plugins/plugin/model.py
index e711fb3f..9e94f6ed 100644
--- a/glances/plugins/plugin/model.py
+++ b/glances/plugins/plugin/model.py
@@ -402,7 +402,7 @@ class GlancesPluginModel(object):
def get_stats(self):
"""Return the stats object in JSON format."""
- return json_dumps(self.stats)
+ return json_dumps(self.get_raw())
def get_json(self):
"""Return the stats object in JSON format."""
@@ -413,14 +413,14 @@ class GlancesPluginModel(object):
Stats should be a list of dict (processlist, network...)
"""
- return dictlist(self.stats, item)
+ return dictlist(self.get_raw(), item)
def get_stats_item(self, item):
"""Return the stats object for a specific item in JSON format.
Stats should be a list of dict (processlist, network...)
"""
- return json_dumps_dictlist(self.stats, item)
+ return json_dumps_dictlist(self.get_raw(), item)
def get_raw_stats_value(self, item, value):
"""Return the stats object for a specific item=value.
@@ -428,13 +428,13 @@ class GlancesPluginModel(object):
Return None if the item=value does not exist
Return None if the item is not a list of dict
"""
- if not isinstance(self.stats, list):
+ if not isinstance(self.get_raw(), list):
return None
else:
if (not isinstance(value, int) and not isinstance(value, float)) and value.isdigit():
value = int(value)
try:
- return {value: [i for i in self.stats if i[item] == value]}
+ return {value: [i for i in self.get_raw() if i[item] == value]}
except (KeyError, ValueError) as e:
logger.error("Cannot get item({})=value({}) ({})".format(item, value, e))
return None
diff --git a/glances/plugins/sensors/__init__.py b/glances/plugins/sensors/__init__.py
index 1eec639c..70438875 100644
--- a/glances/plugins/sensors/__init__.py
+++ b/glances/plugins/sensors/__init__.py
@@ -11,6 +11,7 @@
import psutil
import warnings
+import threading
from glances.logger import logger
from glances.globals import iteritems, to_fahrenheit
@@ -26,6 +27,12 @@ SENSOR_TEMP_UNIT = 'C'
SENSOR_FAN_TYPE = 'fan_speed'
SENSOR_FAN_UNIT = 'R'
+SENSOR_HDDTEMP_TYPE = 'temperature_hdd'
+SENSOR_HDDTEMP_UNIT = 'C'
+
+SENSORS_BATTERY_TYPE = 'battery'
+SENSORS_BATTERY_UNIT = '%'
+
# Define the default refresh multiplicator
# Default value is 3 * Glances refresh time
# Can be overwritten by the refresh option in the sensors section of the glances.conf file
@@ -66,8 +73,8 @@ class PluginModel(GlancesPluginModel):
"""Glances sensors plugin.
The stats list includes both sensors and hard disks stats, if any.
- The sensors are already grouped by chip type and then sorted by name.
- The hard disks are already sorted by name.
+ The sensors are already grouped by chip type and then sorted by label.
+ The hard disks are already sorted by label.
"""
def __init__(self, args=None, config=None):
@@ -108,6 +115,55 @@ class PluginModel(GlancesPluginModel):
"""Return the key of the list."""
return 'label'
+ def __get_temperature(self, stats, index):
+ try:
+ temperature = self.__set_type(self.glances_grab_sensors.get(SENSOR_TEMP_TYPE), SENSOR_TEMP_TYPE)
+ except Exception as e:
+ logger.error("Cannot grab sensors temperatures (%s)" % e)
+ else:
+ stats[index] = self.__transform_sensors(temperature)
+
+ def __get_fan_speed(self, stats, index):
+ try:
+ fan_speed = self.__set_type(self.glances_grab_sensors.get(SENSOR_FAN_TYPE), SENSOR_FAN_TYPE)
+ except Exception as e:
+ logger.error("Cannot grab FAN speed (%s)" % e)
+ else:
+ stats[index] = self.__transform_sensors(fan_speed)
+
+ def __get_hddtemp(self, stats, index):
+ try:
+ hddtemp = self.__set_type(self.hddtemp_plugin.update(), SENSOR_HDDTEMP_TYPE)
+ except Exception as e:
+ logger.error("Cannot grab HDD temperature (%s)" % e)
+ else:
+ stats[index] = self.__transform_sensors(hddtemp)
+
+ def __get_bat_percent(self, stats, index):
+ try:
+ bat_percent = self.__set_type(self.batpercent_plugin.update(), SENSORS_BATTERY_TYPE)
+ except Exception as e:
+ logger.error("Cannot grab battery percent (%s)" % e)
+ else:
+ stats[index] = self.__transform_sensors(bat_percent)
+
+ def __transform_sensors(self, threads_stats):
+ """Hide, alias and sort the result"""
+ stats_transformed = []
+ for stat in threads_stats:
+ # Hide sensors configured in the hide ou show configuration key
+ if not self.is_display(stat["label"].lower()):
+ continue
+ # Set alias for sensors
+ stat["label"] = self.__get_alias(stat)
+ # Add the stat to the stats_transformed list
+ stats_transformed.append(stat)
+ # Remove duplicates thanks to https://stackoverflow.com/a/9427216/1919431
+ stats_transformed = [dict(t) for t in {tuple(d.items()) for d in stats_transformed}]
+ # Sort by label
+ stats_transformed = sorted(stats_transformed, key=lambda d: d['label'])
+ return stats_transformed
+
@GlancesPluginModel._check_decorator
@GlancesPluginModel._log_result_decorator
def update(self):
@@ -116,60 +172,38 @@ class PluginModel(GlancesPluginModel):
stats = self.get_init_value()
if self.input_method == 'local':
- # Update stats using the dedicated lib
- stats = []
- # Get the temperature
- try:
- temperature = self.__set_type(self.glances_grab_sensors.get(SENSOR_TEMP_TYPE), SENSOR_TEMP_TYPE)
- except Exception as e:
- logger.error("Cannot grab sensors temperatures (%s)" % e)
- else:
- # Append temperature
- stats.extend(temperature)
- # Get the FAN speed
- try:
- fan_speed = self.__set_type(self.glances_grab_sensors.get(SENSOR_FAN_TYPE), SENSOR_FAN_TYPE)
- except Exception as e:
- logger.error("Cannot grab FAN speed (%s)" % e)
- else:
- # Append FAN speed
- stats.extend(fan_speed)
- # Update HDDtemp stats
- try:
- hddtemp = self.__set_type(self.hddtemp_plugin.update(), 'temperature_hdd')
- except Exception as e:
- logger.error("Cannot grab HDD temperature (%s)" % e)
- else:
- # Append HDD temperature
- stats.extend(hddtemp)
- # Update batteries stats
- try:
- bat_percent = self.__set_type(self.batpercent_plugin.update(), 'battery')
- except Exception as e:
- logger.error("Cannot grab battery percent (%s)" % e)
- else:
- # Append Batteries %
- stats.extend(bat_percent)
-
+ threads_stats = [None] * 4
+ threads = [
+ threading.Thread(name=SENSOR_TEMP_TYPE,
+ target=self.__get_temperature,
+ args=(threads_stats, 0)),
+ threading.Thread(name=SENSOR_FAN_TYPE,
+ target=self.__get_fan_speed,
+ args=(threads_stats, 1)),
+ threading.Thread(name=SENSOR_HDDTEMP_TYPE,
+ target=self.__get_hddtemp,
+ args=(threads_stats, 2)),
+ threading.Thread(name=SENSORS_BATTERY_TYPE,
+ target=self.__get_bat_percent,
+ args=(threads_stats, 3))
+ ]
+ # Start threads in //
+ for t in threads:
+ t.start()
+ # Wait threads are finished
+ for t in threads:
+ t.join()
+ # Merge the results
+ for s in threads_stats:
+ stats.extend(s)
elif self.input_method == 'snmp':
# Update stats using SNMP
# No standard:
# http://www.net-snmp.org/wiki/index.php/Net-SNMP_and_lm-sensors_on_Ubuntu_10.04
pass
- # Global change on stats
- stats_transformed = []
- for stat in stats:
- # Hide sensors configured in the hide ou show configuration key
- if not self.is_display(stat["label"].lower()):
- continue
- # Set alias for sensors
- stat["label"] = self.__get_alias(stat)
- # Add the stat to the stats_transformed list
- stats_transformed.append(stat)
-
# Update the stats
- self.stats = stats_transformed
+ self.stats = stats
return self.stats
@@ -270,7 +304,7 @@ class PluginModel(GlancesPluginModel):
# Stats
for i in self.stats:
# Do not display anything if no battery are detected
- if i['type'] == 'battery' and i['value'] == []:
+ if i['type'] == SENSORS_BATTERY_TYPE and i['value'] == []:
continue
# New line
ret.append(self.curse_new_line())
@@ -282,7 +316,7 @@ class PluginModel(GlancesPluginModel):
self.curse_add_line(msg, self.get_views(item=i[self.get_key()], key='value', option='decoration'))
)
else:
- if args.fahrenheit and i['type'] != 'battery' and i['type'] != SENSOR_FAN_TYPE:
+ if args.fahrenheit and i['type'] != SENSORS_BATTERY_TYPE and i['type'] != SENSOR_FAN_TYPE:
trend = ''
value = to_fahrenheit(i['value'])
unit = 'F'
diff --git a/glances/stats.py b/glances/stats.py
index 336476e7..8da68ba3 100644
--- a/glances/stats.py
+++ b/glances/stats.py
@@ -50,7 +50,7 @@ class GlancesStats(object):
# Check if the attribute starts with 'get'
if item.startswith('getViews'):
# Get the plugin name
- plugname = item[len('getViews') :].lower()
+ plugname = item[len('getViews'):].lower()
# Get the plugin instance
plugin = self._plugins[plugname]
if hasattr(plugin, 'get_json_views'):
@@ -61,7 +61,7 @@ class GlancesStats(object):
raise AttributeError(item)
elif item.startswith('get'):
# Get the plugin name
- plugname = item[len('get') :].lower()
+ plugname = item[len('get'):].lower()
# Get the plugin instance
plugin = self._plugins[plugname]
if hasattr(plugin, 'get_stats'):
@@ -260,21 +260,26 @@ class GlancesStats(object):
for p in self._plugins:
self._plugins[p].load_limits(config)
+ def __update_plugin(self, p):
+ """Update stats, history and views for the given plugin name p"""
+ self._plugins[p].update()
+ self._plugins[p].update_stats_history()
+ self._plugins[p].update_views()
+
def update(self):
- """Wrapper method to update the stats."""
- # For standalone and server modes
- # For each plugins, call the update method
- for p in self._plugins:
- if self._plugins[p].is_disabled():
- # If current plugin is disable
- # then continue to next plugin
- continue
- # Update the stats...
- self._plugins[p].update()
- # ... the history
- self._plugins[p].update_stats_history()
- # ... and the views
- self._plugins[p].update_views()
+ """Wrapper method to update the stats.
+
+ Only called by standalone and server modes
+ """
+ threads = []
+ # Start update of all enable plugins
+ for p in self.getPluginsList(enable=True):
+ thread = threading.Thread(target=self.__update_plugin, args=(p,))
+ thread.start()
+ threads.append(thread)
+ # Wait the end of the update
+ for t in threads:
+ t.join()
def export(self, input_stats=None):
"""Export all the stats.
@@ -288,7 +293,7 @@ class GlancesStats(object):
input_stats = input_stats or {}
- for e in self._exports:
+ for e in self.getExportsList(enable=True):
logger.debug("Export stats using the %s module" % e)
thread = threading.Thread(target=self._exports[e].update, args=(input_stats,))
thread.start()