mirror of
https://github.com/nicolargo/glances.git
synced 2025-01-06 01:35:47 +03:00
Merge branch 'develop' of github.com:nicolargo/glances into develop
This commit is contained in:
commit
5262524db3
File diff suppressed because it is too large
Load Diff
@ -32,7 +32,13 @@ Item (or event) is defined by:
|
||||
}
|
||||
"""
|
||||
|
||||
from pydantic.dataclasses import dataclass
|
||||
from glances.logger import logger
|
||||
|
||||
try:
|
||||
from pydantic.dataclasses import dataclass
|
||||
except ImportError as e:
|
||||
logger.warning(f"Missing Python Lib ({e}), EventList will be skipping data validation")
|
||||
from dataclasses import dataclass
|
||||
|
||||
from glances.processes import sort_stats
|
||||
|
||||
|
@ -11,7 +11,7 @@
|
||||
|
||||
import time
|
||||
from datetime import datetime
|
||||
from pydantic import RootModel
|
||||
from dataclasses import asdict
|
||||
|
||||
from glances.processes import glances_processes
|
||||
from glances.thresholds import glances_thresholds
|
||||
@ -201,7 +201,7 @@ class GlancesEventsList(object):
|
||||
|
||||
def get(self):
|
||||
"""Return the RAW events list."""
|
||||
return [RootModel[GlancesEvent](e).model_dump() for e in self.events_list]
|
||||
return [asdict(e) for e in self.events_list]
|
||||
|
||||
def len(self):
|
||||
"""Return the number of events in the logs list."""
|
||||
|
@ -18,7 +18,7 @@ from glances.exports.export import GlancesExport
|
||||
from influxdb import InfluxDBClient
|
||||
from influxdb.client import InfluxDBClientError
|
||||
|
||||
FIELD_TO_TAG = ['name', 'cmdline']
|
||||
FIELD_TO_TAG = ['name', 'cmdline', 'type']
|
||||
|
||||
|
||||
class Export(GlancesExport):
|
||||
|
@ -17,7 +17,7 @@ from glances.exports.export import GlancesExport
|
||||
|
||||
from influxdb_client import InfluxDBClient, WriteOptions
|
||||
|
||||
FIELD_TO_TAG = ['name', 'cmdline']
|
||||
FIELD_TO_TAG = ['name', 'cmdline', 'type']
|
||||
|
||||
|
||||
class Export(GlancesExport):
|
||||
|
@ -287,10 +287,13 @@ class _GlancesCurses(object):
|
||||
self.filter_color = curses.color_pair(9) | A_BOLD
|
||||
self.selected_color = curses.color_pair(10) | A_BOLD
|
||||
# Define separator line style
|
||||
curses.init_color(11, 500, 500, 500)
|
||||
curses.init_pair(11, curses.COLOR_BLACK, -1)
|
||||
self.separator = curses.color_pair(11)
|
||||
|
||||
try:
|
||||
curses.init_color(11, 500, 500, 500)
|
||||
curses.init_pair(11, curses.COLOR_BLACK, -1)
|
||||
self.separator = curses.color_pair(11)
|
||||
except Exception:
|
||||
# Catch exception in TMUX
|
||||
pass
|
||||
else:
|
||||
# The screen is NOT compatible with a colored design
|
||||
# switch to B&W text styles
|
||||
|
@ -16,8 +16,12 @@ from io import open
|
||||
import webbrowser
|
||||
from urllib.parse import urljoin
|
||||
|
||||
# Replace typing_extensions by typing when Python 3.8 support will be dropped
|
||||
from typing import Annotated
|
||||
try:
|
||||
from typing import Annotated
|
||||
except ImportError:
|
||||
# Only for Python 3.8
|
||||
# To be removed when Python 3.8 support will be dropped
|
||||
from typing_extensions import Annotated
|
||||
|
||||
from glances import __version__, __apiversion__
|
||||
from glances.password import GlancesPassword
|
||||
|
@ -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
|
||||
|
@ -13,18 +13,20 @@ import psutil
|
||||
|
||||
from glances.logger import logger
|
||||
from glances.plugins.plugin.model import GlancesPluginModel
|
||||
from glances.globals import LINUX
|
||||
|
||||
# Batinfo library (optional; Linux-only)
|
||||
batinfo_tag = True
|
||||
try:
|
||||
import batinfo
|
||||
except ImportError:
|
||||
logger.debug("batinfo library not found. Fallback to psutil.")
|
||||
if LINUX:
|
||||
batinfo_tag = True
|
||||
try:
|
||||
import batinfo
|
||||
except ImportError:
|
||||
logger.debug("batinfo library not found. Fallback to psutil.")
|
||||
batinfo_tag = False
|
||||
else:
|
||||
batinfo_tag = False
|
||||
|
||||
# Availability:
|
||||
# Linux, Windows, FreeBSD (psutil>=5.1.0)
|
||||
# macOS (psutil>=5.4.2)
|
||||
# PsUtil Sensors_battery available on Linux, Windows, FreeBSD, macOS
|
||||
psutil_tag = True
|
||||
try:
|
||||
psutil.sensors_battery()
|
||||
|
@ -22,6 +22,7 @@ podman; python_version >= "3.6"
|
||||
potsdb
|
||||
prometheus_client
|
||||
pycouchdb
|
||||
pydantic
|
||||
pygal
|
||||
pymdstat
|
||||
pymongo; python_version >= "3.7"
|
||||
|
@ -2,4 +2,3 @@ psutil>=5.6.7
|
||||
defusedxml
|
||||
packaging
|
||||
ujson>=5.4.0
|
||||
pydantic
|
||||
|
Loading…
Reference in New Issue
Block a user