Add support for a configuration file.

Adding a coniguration file (/etc/sysmon.ini).  This will allow users to
no monitor the temperature of speciic drives by ignoring them, as well
as ignoring the performance from specific drives.

For instance on a system running mdraid, you could ignore performance on
all of the drives that make up the raid array, and ignore the
temperature of the raid device.
This commit is contained in:
Jeff Curless
2025-11-02 16:10:22 -05:00
parent 927f00f655
commit a1ac933238
4 changed files with 129 additions and 23 deletions

82
monitor/configfile.py Normal file
View File

@@ -0,0 +1,82 @@
import configparser
class ConfigClass:
'''
Handle a .INI style configuration file. Every function is designed to not
crash, and always return a default of the items are not present.
Currently support read-only.
'''
def __init__( self, filename ):
self.filename = filename
self.config = configparser.ConfigParser()
self.readFile = False
self._openConfig()
def _openConfig(self) -> None:
'''
Open,, and read in the configuation file. If the file does not exist, keep
trying to reopen the file until the file does exist. While this approach does
not always help, it does allow for an application polls the configuration file
occasionally.
'''
if not self.readFile:
_result = self.config.read( self.filename )
if len(_result) > 0:
self.readFile = True
def getValue( self, section : str, key : str, default="" ) -> str:
'''
This routine obtains the value of the key within the specified section, if there
is such a item.
Parameter:
section - Name of the section to look for
key - Key of the value desired
default - Value to return if there is no key
Returns:
The value of the key from the section read.
'''
value = default
self._openConfig()
try:
value = self.config[section][key].replace('"','').strip()
except:
value = default
return value
def getValueAsList( self, section : str, name : str, default = [] ) -> list[str]:
'''
This routine looks for the key in the specified section and returns the data if
it exists, if not the default value is returned.
Parameters:
section - Section to look for
key - Key to return the value of
default - If they key or section does not exist, return this value
Returns:
a List of items
'''
value = default
self._openConfig()
try:
temp = self.config[section][name]
value = [ n.replace('"','').strip() for n in temp.split(",")]
except:
value = default
return value
if __name__ == "__main__":
cfg = ConfigClass( "test.ini" )
print( f"Value = {cfg.getValue( 'temperature', 'ignore' )}" )
print( f"Value = {cfg.getValueAsList( 'temperature', 'ignore' )}" )
print( f"Value = {cfg.getValue( 'performance', 'ignore' )}" )
print( f"Value = {cfg.getValueAsList( 'performance', 'ignore' )}" )
cfg = ConfigClass( "missingfile.ini" )

View File

@@ -9,13 +9,11 @@ Requires: PyQt5 (including QtCharts)
import sys import sys
from systemsupport import CPUInfo, CPULoad, multiDriveStat from systemsupport import CPUInfo, CPULoad, multiDriveStat
from configfile import ConfigClass
# -------------------------- # --------------------------
# Globals # Globals
# -------------------------- # --------------------------
cpuinfo = CPUInfo()
cpuload = CPULoad()
multiDrive = multiDriveStat()
# -------------------------- # --------------------------
# UI # UI
@@ -25,6 +23,7 @@ from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtGui import QPainter from PyQt5.QtGui import QPainter
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QGridLayout from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QGridLayout
from PyQt5.QtChart import QChart, QChartView, QLineSeries, QValueAxis from PyQt5.QtChart import QChart, QChartView, QLineSeries, QValueAxis
from PyQt5 import QtGui
class RollingChart(QWidget): class RollingChart(QWidget):
''' '''
@@ -260,6 +259,16 @@ class MonitorWindow(QMainWindow):
def __init__(self, refresh_ms: int = 1000, window = 120, parent=None): def __init__(self, refresh_ms: int = 1000, window = 120, parent=None):
super().__init__(parent) super().__init__(parent)
# Get all the filters loaded
self.config = ConfigClass("/etc/sysmon.ini")
self.driveTempFilter = self.config.getValueAsList( 'temperature', 'ignore' )
self.drivePerfFilter = self.config.getValueAsList( 'performance', 'ignore' )
# Get supporting objects
self.cpuinfo = CPUInfo()
self.cpuload = CPULoad()
self.multiDrive = multiDriveStat()
self.setWindowTitle("System Monitor") self.setWindowTitle("System Monitor")
self.setMinimumSize(900, 900) self.setMinimumSize(900, 900)
@@ -273,14 +282,16 @@ class MonitorWindow(QMainWindow):
# Charts # Charts
self.use_chart = RollingChart( self.use_chart = RollingChart(
title="CPU Utilization", title="CPU Utilization",
series_defs=[ (name, None) for name in cpuload.cpuNames ], series_defs=[ (name, None) for name in self.cpuload.cpuNames ],
y_min=0, y_max=100, y_min=0, y_max=100,
window=120 window=120
) )
series = [("CPU", None)] series = [("CPU", None)]
for name in multiDrive.drives: for name in self.multiDrive.drives:
series.append( (name,None) ) if not name in self.driveTempFilter:
series.append( (name,None) )
self.cpu_chart = RollingChart( self.cpu_chart = RollingChart(
title="Temperature (°C)", title="Temperature (°C)",
series_defs= series, series_defs= series,
@@ -296,9 +307,10 @@ class MonitorWindow(QMainWindow):
) )
series = [] series = []
for name in multiDrive.drives: for name in self.multiDrive.drives:
series.append( (f"{name} Read", None) ) if not name in self.drivePerfFilter:
series.append( (f"{name} Write", None ) ) series.append( (f"{name} Read", None) )
series.append( (f"{name} Write", None ) )
self.io_chart = RollingChartDynamic( self.io_chart = RollingChartDynamic(
title="Disk I/O", title="Disk I/O",
@@ -331,39 +343,42 @@ class MonitorWindow(QMainWindow):
# Obtain the current fan speed # Obtain the current fan speed
try: try:
fan_speed = cpuinfo.CPUFanSpeed fan_speed = self.cpuinfo.CPUFanSpeed
except Exception: except Exception:
fan_speed = None fan_speed = None
# Setup the temperature for the CPU and Drives
temperatures = [] temperatures = []
try: try:
temperatures.append( float(cpuinfo.temperature) ) temperatures.append( float(self.cpuinfo.temperature) )
except Exception: except Exception:
temperatures.append( 0.0 ) temperatures.append( 0.0 )
# Obtain the NVMe device temperature # Obtain the drive temperatures
try: try:
for _drive in multiDrive.drives: for _drive in self.multiDrive.drives:
temperatures.append( multiDrive.driveTemp( _drive ) ) if not _drive in self.driveTempFilter:
temperatures.append( self.multiDrive.driveTemp( _drive ) )
except Exception: except Exception:
temperatures = [ 0.0 for _ in multiDrive.drives ] temperatures = [ 0.0 for _ in self.multiDrive.drives ]
# Obtain the NVMe Device read and write rates # Obtain the NVMe Device read and write rates
try: try:
rwData = [] rwData = []
drives = multiDrive.readWriteBytes() drives = self.multiDrive.readWriteBytes()
for drive in drives: for drive in drives:
rwData.append( float(drives[drive][0])) if not drive in self.drivePerfFilter:
rwData.append( float(drives[drive][1])) rwData.append( float(drives[drive][0]))
rwData.append( float(drives[drive][1]))
except Exception : except Exception :
rwData = [ None, None ] rwData = [ None, None ]
# Get the CPU load precentages # Get the CPU load precentages
try: try:
p = cpuload.getPercentages() p = self.cpuload.getPercentages()
values = [p[name] for name in cpuload.cpuNames] values = [p[name] for name in self.cpuload.cpuNames]
except Exception: except Exception:
values = [ None for name in cpuload.cpuNames ] values = [ None for name in self.cpuload.cpuNames ]
# Append to charts # Append to charts
self.cpu_chart.append( temperatures ) self.cpu_chart.append( temperatures )

View File

@@ -124,14 +124,18 @@ class multiDriveStat():
If there is a missing drive from the filter, that drive is eliminated. If there is a missing drive from the filter, that drive is eliminated.
''' '''
def __init__(self): def __init__(self,driveIgnoreList : list[str]=[]):
# #
# Get all drives # Get all drives
# #
self._drives = [] self._drives = []
with os.popen( 'ls -1 /sys/block | grep -v -e loop -e ram') as command: with os.popen( 'ls -1 /sys/block | grep -v -e loop -e ram') as command:
lsblk_raw = command.read() lsblk_raw = command.read()
self._drives = [ l for l in lsblk_raw.split('\n') if l] for l in lsblk_raw.split('\n'):
if len(l) == 0:
continue
if not l in driveIgnoreList:
self._drives.append( l )
self._stats = [ DriveStats(_) for _ in self._drives ] self._stats = [ DriveStats(_) for _ in self._drives ]
@property @property

5
monitor/test.ini Normal file
View File

@@ -0,0 +1,5 @@
[temperature]
ignore = "mmcblk0"
[performance]
ignore = "mmcblk0"