Stats update is now threaded / Sensors are sorted by label/Alias in the UI

pull/2758/head
nicolargo 2024-05-02 18:20:58 +02:00
parent 8608e97a9f
commit 39be7f554b
5 changed files with 131 additions and 75 deletions

View File

@ -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:

View File

@ -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)

View File

@ -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

View File

@ -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'

View File

@ -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()