|
|
|
@ -8,30 +8,34 @@
|
|
|
|
|
#
|
|
|
|
|
|
|
|
|
|
"""Sensors plugin."""
|
|
|
|
|
from enum import Enum
|
|
|
|
|
from concurrent.futures import ThreadPoolExecutor
|
|
|
|
|
from typing import List, Dict, Literal, Any
|
|
|
|
|
|
|
|
|
|
import psutil
|
|
|
|
|
import warnings
|
|
|
|
|
import threading
|
|
|
|
|
|
|
|
|
|
from glances.logger import logger
|
|
|
|
|
from glances.globals import iteritems, to_fahrenheit
|
|
|
|
|
from glances.globals import to_fahrenheit
|
|
|
|
|
from glances.timer import Counter
|
|
|
|
|
from glances.plugins.sensors.sensor.glances_batpercent import PluginModel as BatPercentPluginModel
|
|
|
|
|
from glances.plugins.sensors.sensor.glances_hddtemp import PluginModel as HddTempPluginModel
|
|
|
|
|
from glances.outputs.glances_unicode import unicode_message
|
|
|
|
|
from glances.plugins.plugin.model import GlancesPluginModel
|
|
|
|
|
|
|
|
|
|
SENSOR_TEMP_TYPE = 'temperature_core'
|
|
|
|
|
SENSOR_TEMP_UNIT = 'C'
|
|
|
|
|
|
|
|
|
|
SENSOR_FAN_TYPE = 'fan_speed'
|
|
|
|
|
SENSOR_FAN_UNIT = 'R'
|
|
|
|
|
class SensorType(str, Enum):
|
|
|
|
|
# Switch to `enum.StrEnum` when we only support py311+
|
|
|
|
|
CPU_TEMP = 'temperature_core'
|
|
|
|
|
FAN_SPEED = 'fan_speed'
|
|
|
|
|
HDD_TEMP = 'temperature_hdd'
|
|
|
|
|
BATTERY = 'battery'
|
|
|
|
|
|
|
|
|
|
SENSOR_HDDTEMP_TYPE = 'temperature_hdd'
|
|
|
|
|
SENSOR_HDDTEMP_UNIT = 'C'
|
|
|
|
|
|
|
|
|
|
SENSORS_BATTERY_TYPE = 'battery'
|
|
|
|
|
SENSORS_BATTERY_UNIT = '%'
|
|
|
|
|
CPU_TEMP_UNIT = 'C'
|
|
|
|
|
FAN_SPEED_UNIT = 'R'
|
|
|
|
|
HDD_TEMP_UNIT = 'C'
|
|
|
|
|
BATTERY_UNIT = '%'
|
|
|
|
|
|
|
|
|
|
# Define the default refresh multiplicator
|
|
|
|
|
# Default value is 3 * Glances refresh time
|
|
|
|
@ -82,28 +86,38 @@ class PluginModel(GlancesPluginModel):
|
|
|
|
|
super(PluginModel, self).__init__(
|
|
|
|
|
args=args, config=config, stats_init_value=[], fields_description=fields_description
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
start_duration = Counter()
|
|
|
|
|
|
|
|
|
|
# Init the sensor class
|
|
|
|
|
start_duration.reset()
|
|
|
|
|
# Hotfix! Refactor to use only one `GlancesGrabSensors` later
|
|
|
|
|
self.glances_grab_sensors_fan_speed = GlancesGrabSensors()
|
|
|
|
|
self.glances_grab_sensors_temperature = GlancesGrabSensors()
|
|
|
|
|
logger.debug("Generic sensor plugin init duration: {} seconds".format(start_duration.get()))
|
|
|
|
|
glances_grab_sensors_cpu_temp = GlancesGrabSensors(SensorType.CPU_TEMP)
|
|
|
|
|
logger.debug("CPU Temp sensor plugin init duration: {} seconds".format(start_duration.get()))
|
|
|
|
|
|
|
|
|
|
# Instance for the HDDTemp Plugin in order to display the hard disks
|
|
|
|
|
# temperatures
|
|
|
|
|
start_duration.reset()
|
|
|
|
|
self.hddtemp_plugin = HddTempPluginModel(args=args, config=config)
|
|
|
|
|
glances_grab_sensors_fan_speed = GlancesGrabSensors(SensorType.FAN_SPEED)
|
|
|
|
|
logger.debug("Fan speed sensor plugin init duration: {} seconds".format(start_duration.get()))
|
|
|
|
|
|
|
|
|
|
# Instance for the HDDTemp Plugin in order to display the hard disks temperatures
|
|
|
|
|
start_duration.reset()
|
|
|
|
|
hddtemp_plugin = HddTempPluginModel(args=args, config=config)
|
|
|
|
|
logger.debug("HDDTemp sensor plugin init duration: {} seconds".format(start_duration.get()))
|
|
|
|
|
|
|
|
|
|
# Instance for the BatPercent in order to display the batteries
|
|
|
|
|
# capacities
|
|
|
|
|
# Instance for the BatPercent in order to display the batteries capacities
|
|
|
|
|
start_duration.reset()
|
|
|
|
|
self.batpercent_plugin = BatPercentPluginModel(args=args, config=config)
|
|
|
|
|
batpercent_plugin = BatPercentPluginModel(args=args, config=config)
|
|
|
|
|
logger.debug("Battery sensor plugin init duration: {} seconds".format(start_duration.get()))
|
|
|
|
|
|
|
|
|
|
self.sensors_grab_map: Dict[SensorType, Any] = {}
|
|
|
|
|
|
|
|
|
|
if glances_grab_sensors_cpu_temp.init:
|
|
|
|
|
self.sensors_grab_map[SensorType.CPU_TEMP] = glances_grab_sensors_cpu_temp
|
|
|
|
|
|
|
|
|
|
if glances_grab_sensors_fan_speed.init:
|
|
|
|
|
self.sensors_grab_map[SensorType.FAN_SPEED] = glances_grab_sensors_fan_speed
|
|
|
|
|
|
|
|
|
|
self.sensors_grab_map[SensorType.HDD_TEMP] = hddtemp_plugin
|
|
|
|
|
self.sensors_grab_map[SensorType.BATTERY] = batpercent_plugin
|
|
|
|
|
|
|
|
|
|
# We want to display the stat in the curse interface
|
|
|
|
|
self.display_curse = True
|
|
|
|
|
|
|
|
|
@ -115,37 +129,15 @@ class PluginModel(GlancesPluginModel):
|
|
|
|
|
"""Return the key of the list."""
|
|
|
|
|
return 'label'
|
|
|
|
|
|
|
|
|
|
def __get_temperature(self, stats, index):
|
|
|
|
|
def __get_sensor_data(self, sensor_type: SensorType) -> List[Dict]:
|
|
|
|
|
try:
|
|
|
|
|
temperature = self.__set_type(self.glances_grab_sensors_temperature.get(SENSOR_TEMP_TYPE), SENSOR_TEMP_TYPE)
|
|
|
|
|
data = self.sensors_grab_map[sensor_type].update()
|
|
|
|
|
data = self.__set_type(data, sensor_type)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error("Cannot grab sensors temperatures (%s)" % e)
|
|
|
|
|
logger.error(f"Cannot grab sensors `{sensor_type}` ({e})")
|
|
|
|
|
return []
|
|
|
|
|
else:
|
|
|
|
|
stats[index] = self.__transform_sensors(temperature)
|
|
|
|
|
|
|
|
|
|
def __get_fan_speed(self, stats, index):
|
|
|
|
|
try:
|
|
|
|
|
fan_speed = self.__set_type(self.glances_grab_sensors_fan_speed.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)
|
|
|
|
|
return self.__transform_sensors(data)
|
|
|
|
|
|
|
|
|
|
def __transform_sensors(self, threads_stats):
|
|
|
|
|
"""Hide, alias and sort the result"""
|
|
|
|
@ -172,22 +164,17 @@ class PluginModel(GlancesPluginModel):
|
|
|
|
|
stats = self.get_init_value()
|
|
|
|
|
|
|
|
|
|
if self.input_method == 'local':
|
|
|
|
|
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()
|
|
|
|
|
with ThreadPoolExecutor(max_workers=len(self.sensors_grab_map)) as executor:
|
|
|
|
|
logger.debug(f"Sensors enabled sub plugins: {list(self.sensors_grab_map.keys())}")
|
|
|
|
|
futures = {t: executor.submit(self.__get_sensor_data, t) for t in self.sensors_grab_map.keys()}
|
|
|
|
|
|
|
|
|
|
# Merge the results
|
|
|
|
|
for s in threads_stats:
|
|
|
|
|
stats.extend(s)
|
|
|
|
|
for sensor_type, future in futures.items():
|
|
|
|
|
try:
|
|
|
|
|
stats.extend(future.result())
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"Cannot parse sensors data for `{sensor_type}` ({e})")
|
|
|
|
|
|
|
|
|
|
elif self.input_method == 'snmp':
|
|
|
|
|
# Update stats using SNMP
|
|
|
|
|
# No standard:
|
|
|
|
@ -209,7 +196,7 @@ class PluginModel(GlancesPluginModel):
|
|
|
|
|
else:
|
|
|
|
|
return stats["label"]
|
|
|
|
|
|
|
|
|
|
def __set_type(self, stats, sensor_type):
|
|
|
|
|
def __set_type(self, stats: List[Dict[str, Any]], sensor_type: SensorType) -> List[Dict[str, Any]]:
|
|
|
|
|
"""Set the plugin type.
|
|
|
|
|
|
|
|
|
|
4 types of stats is possible in the sensors plugin:
|
|
|
|
@ -237,10 +224,10 @@ class PluginModel(GlancesPluginModel):
|
|
|
|
|
if not i['value']:
|
|
|
|
|
continue
|
|
|
|
|
# Alert processing
|
|
|
|
|
if i['type'] == SENSOR_TEMP_TYPE:
|
|
|
|
|
if self.is_limit('critical', stat_name=SENSOR_TEMP_TYPE + '_' + i['label']):
|
|
|
|
|
if i['type'] == SensorType.CPU_TEMP:
|
|
|
|
|
if self.is_limit('critical', stat_name=SensorType.CPU_TEMP + '_' + i['label']):
|
|
|
|
|
# By default use the thresholds configured in the glances.conf file (see #2058)
|
|
|
|
|
alert = self.get_alert(current=i['value'], header=SENSOR_TEMP_TYPE + '_' + i['label'])
|
|
|
|
|
alert = self.get_alert(current=i['value'], header=SensorType.CPU_TEMP + '_' + i['label'])
|
|
|
|
|
else:
|
|
|
|
|
# Else use the system thresholds
|
|
|
|
|
if i['critical'] is None:
|
|
|
|
@ -253,7 +240,7 @@ class PluginModel(GlancesPluginModel):
|
|
|
|
|
alert = 'WARNING'
|
|
|
|
|
else:
|
|
|
|
|
alert = 'OK'
|
|
|
|
|
elif i['type'] == SENSORS_BATTERY_TYPE:
|
|
|
|
|
elif i['type'] == SensorType.BATTERY:
|
|
|
|
|
# Battery is in %
|
|
|
|
|
alert = self.get_alert(current=100 - i['value'], header=i['type'])
|
|
|
|
|
else:
|
|
|
|
@ -297,7 +284,7 @@ class PluginModel(GlancesPluginModel):
|
|
|
|
|
# Stats
|
|
|
|
|
for i in self.stats:
|
|
|
|
|
# Do not display anything if no battery are detected
|
|
|
|
|
if i['type'] == SENSORS_BATTERY_TYPE and i['value'] == []:
|
|
|
|
|
if i['type'] == SensorType.BATTERY and i['value'] == []:
|
|
|
|
|
continue
|
|
|
|
|
# New line
|
|
|
|
|
ret.append(self.curse_new_line())
|
|
|
|
@ -309,7 +296,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'] != SENSORS_BATTERY_TYPE and i['type'] != SENSOR_FAN_TYPE:
|
|
|
|
|
if args.fahrenheit and i['type'] != SensorType.BATTERY and i['type'] != SensorType.FAN_SPEED:
|
|
|
|
|
trend = ''
|
|
|
|
|
value = to_fahrenheit(i['value'])
|
|
|
|
|
unit = 'F'
|
|
|
|
@ -334,73 +321,42 @@ class PluginModel(GlancesPluginModel):
|
|
|
|
|
class GlancesGrabSensors(object):
|
|
|
|
|
"""Get sensors stats."""
|
|
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
def __init__(self, sensor_type: Literal[SensorType.FAN_SPEED, SensorType.CPU_TEMP]):
|
|
|
|
|
"""Init sensors stats."""
|
|
|
|
|
# Temperatures
|
|
|
|
|
self.init_temp = False
|
|
|
|
|
self.sensor_temps = {}
|
|
|
|
|
self.sensor_type = sensor_type
|
|
|
|
|
self.sensor_unit = CPU_TEMP_UNIT if self.sensor_type == SensorType.CPU_TEMP else FAN_SPEED_UNIT
|
|
|
|
|
|
|
|
|
|
self.init = False
|
|
|
|
|
try:
|
|
|
|
|
# psutil>=5.1.0, Linux-only
|
|
|
|
|
self.sensor_temps = psutil.sensors_temperatures()
|
|
|
|
|
self.__fetch_psutil()
|
|
|
|
|
self.init = True
|
|
|
|
|
except AttributeError:
|
|
|
|
|
logger.debug("Cannot grab temperatures. Platform not supported.")
|
|
|
|
|
else:
|
|
|
|
|
self.init_temp = True
|
|
|
|
|
logger.debug(f"Cannot grab {sensor_type}. Platform not supported.")
|
|
|
|
|
|
|
|
|
|
def __fetch_psutil(self) -> Dict[str, list]:
|
|
|
|
|
if self.sensor_type == SensorType.CPU_TEMP:
|
|
|
|
|
# Solve an issue #1203 concerning a RunTimeError warning message displayed
|
|
|
|
|
# in the curses interface.
|
|
|
|
|
warnings.filterwarnings("ignore")
|
|
|
|
|
|
|
|
|
|
# Fans
|
|
|
|
|
self.init_fan = False
|
|
|
|
|
self.sensor_fans = {}
|
|
|
|
|
try:
|
|
|
|
|
# psutil>=5.1.0, Linux-only
|
|
|
|
|
return psutil.sensors_temperatures()
|
|
|
|
|
|
|
|
|
|
if self.sensor_type == SensorType.FAN_SPEED:
|
|
|
|
|
# psutil>=5.2.0, Linux-only
|
|
|
|
|
self.sensor_fans = psutil.sensors_fans()
|
|
|
|
|
except AttributeError:
|
|
|
|
|
logger.debug("Cannot grab fans speed. Platform not supported.")
|
|
|
|
|
else:
|
|
|
|
|
self.init_fan = True
|
|
|
|
|
return psutil.sensors_fans()
|
|
|
|
|
|
|
|
|
|
# Init the stats
|
|
|
|
|
self.reset()
|
|
|
|
|
raise ValueError(f"Unsupported sensor_type: {self.sensor_type}")
|
|
|
|
|
|
|
|
|
|
def reset(self):
|
|
|
|
|
"""Reset/init the stats."""
|
|
|
|
|
self.sensors_list = []
|
|
|
|
|
|
|
|
|
|
def __update__(self):
|
|
|
|
|
def update(self) -> List[dict]:
|
|
|
|
|
"""Update the stats."""
|
|
|
|
|
# Reset the list
|
|
|
|
|
self.reset()
|
|
|
|
|
|
|
|
|
|
if not self.init_temp:
|
|
|
|
|
return self.sensors_list
|
|
|
|
|
if not self.init:
|
|
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
# Temperatures sensors
|
|
|
|
|
self.sensors_list.extend(self.build_sensors_list(SENSOR_TEMP_UNIT))
|
|
|
|
|
|
|
|
|
|
# Fans sensors
|
|
|
|
|
self.sensors_list.extend(self.build_sensors_list(SENSOR_FAN_UNIT))
|
|
|
|
|
|
|
|
|
|
return self.sensors_list
|
|
|
|
|
|
|
|
|
|
def build_sensors_list(self, type):
|
|
|
|
|
"""Build the sensors list depending of the type.
|
|
|
|
|
|
|
|
|
|
type: SENSOR_TEMP_UNIT or SENSOR_FAN_UNIT
|
|
|
|
|
|
|
|
|
|
output: a list
|
|
|
|
|
"""
|
|
|
|
|
ret = []
|
|
|
|
|
if type == SENSOR_TEMP_UNIT and self.init_temp:
|
|
|
|
|
input_list = self.sensor_temps
|
|
|
|
|
self.sensor_temps = psutil.sensors_temperatures()
|
|
|
|
|
elif type == SENSOR_FAN_UNIT and self.init_fan:
|
|
|
|
|
input_list = self.sensor_fans
|
|
|
|
|
self.sensor_fans = psutil.sensors_fans()
|
|
|
|
|
else:
|
|
|
|
|
return ret
|
|
|
|
|
for chip_name, chip in iteritems(input_list):
|
|
|
|
|
data = self.__fetch_psutil()
|
|
|
|
|
for chip_name, chip in data.items():
|
|
|
|
|
label_index = 1
|
|
|
|
|
for chip_name_index, feature in enumerate(chip):
|
|
|
|
|
sensors_current = {}
|
|
|
|
@ -413,8 +369,9 @@ class GlancesGrabSensors(object):
|
|
|
|
|
else:
|
|
|
|
|
sensors_current['label'] = feature.label
|
|
|
|
|
# Sensors value, limit and unit
|
|
|
|
|
sensors_current['unit'] = type
|
|
|
|
|
sensors_current['value'] = int(getattr(feature, 'current', 0) if getattr(feature, 'current', 0) else 0)
|
|
|
|
|
sensors_current['unit'] = self.sensor_unit
|
|
|
|
|
sensors_current['value'] = int(
|
|
|
|
|
getattr(feature, 'current', 0) if getattr(feature, 'current', 0) else 0)
|
|
|
|
|
system_warning = getattr(feature, 'high', None)
|
|
|
|
|
system_critical = getattr(feature, 'critical', None)
|
|
|
|
|
sensors_current['warning'] = int(system_warning) if system_warning is not None else None
|
|
|
|
@ -422,16 +379,3 @@ class GlancesGrabSensors(object):
|
|
|
|
|
# Add sensor to the list
|
|
|
|
|
ret.append(sensors_current)
|
|
|
|
|
return ret
|
|
|
|
|
|
|
|
|
|
def get(self, sensor_type=SENSOR_TEMP_TYPE):
|
|
|
|
|
"""Get sensors list."""
|
|
|
|
|
self.__update__()
|
|
|
|
|
if sensor_type == SENSOR_TEMP_TYPE:
|
|
|
|
|
ret = [s for s in self.sensors_list if s['unit'] == SENSOR_TEMP_UNIT]
|
|
|
|
|
elif sensor_type == SENSOR_FAN_TYPE:
|
|
|
|
|
ret = [s for s in self.sensors_list if s['unit'] == SENSOR_FAN_UNIT]
|
|
|
|
|
else:
|
|
|
|
|
# Unknown type
|
|
|
|
|
logger.debug("Unknown sensor type %s" % sensor_type)
|
|
|
|
|
ret = []
|
|
|
|
|
return ret
|
|
|
|
|