Add process to allow modified smartctl commands

This commit is contained in:
Jeff Curless
2025-11-19 23:12:54 -05:00
parent 1d07d8196a
commit adab150746
4 changed files with 102 additions and 31 deletions

View File

@@ -22,10 +22,10 @@ class ConfigClass:
occasionally. occasionally.
''' '''
if not self.readFile: try:
_result = self.config.read( self.filename ) _result = self.config.read( self.filename )
if len(_result) > 0: except Exception as error:
self.readFile = True print( f"{error}" )
def getValue( self, section : str, key : str, default="" ) -> str: def getValue( self, section : str, key : str, default="" ) -> str:
''' '''
@@ -41,7 +41,6 @@ class ConfigClass:
The value of the key from the section read. The value of the key from the section read.
''' '''
value = default value = default
self._openConfig()
try: try:
value = self.config[section][key].replace('"','').strip() value = self.config[section][key].replace('"','').strip()
except: except:
@@ -62,7 +61,6 @@ class ConfigClass:
a List of items a List of items
''' '''
value = default value = default
self._openConfig()
try: try:
temp = self.config[section][name] temp = self.config[section][name]
value = [ n.replace('"','').strip() for n in temp.split(",")] value = [ n.replace('"','').strip() for n in temp.split(",")]
@@ -77,6 +75,9 @@ if __name__ == "__main__":
print( f"Value = {cfg.getValue( 'performance', 'ignore' )}" ) print( f"Value = {cfg.getValue( 'performance', 'ignore' )}" )
print( f"Value = {cfg.getValueAsList( 'performance', 'ignore' )}" ) print( f"Value = {cfg.getValueAsList( 'performance', 'ignore' )}" )
drive = cfg.getValue( 'smartctl', 'sda' )
print( f"drive = {drive}" )
cfg = ConfigClass( "missingfile.ini" ) cfg = ConfigClass( "missingfile.ini" )

View File

@@ -8,13 +8,16 @@ Requires: PyQt5 (including QtCharts)
""" """
import sys import sys
from systemsupport import CPUInfo, CPULoad, multiDriveStat from systemsupport import CPUInfo, CPULoad, multiDriveStat, CaseFan
from configfile import ConfigClass from configfile import ConfigClass
# -------------------------- # --------------------------
# Globals # Globals
# -------------------------- # --------------------------
MIN_WIDTH = 1000
MIN_HEIGHT = 800
# -------------------------- # --------------------------
# UI # UI
# -------------------------- # --------------------------
@@ -265,12 +268,14 @@ class MonitorWindow(QMainWindow):
self.drivePerfFilter = self.config.getValueAsList( 'performance', 'ignore' ) self.drivePerfFilter = self.config.getValueAsList( 'performance', 'ignore' )
# Get supporting objects # Get supporting objects
self.cpuinfo = CPUInfo() self.cpuinfo = CPUInfo()
self.cpuload = CPULoad() self.cpuload = CPULoad()
self.casefan = CaseFan()
self.caseFanPin = None
self.multiDrive = multiDriveStat() self.multiDrive = multiDriveStat()
self.setWindowTitle("System Monitor") self.setWindowTitle("System Monitor")
self.setMinimumSize(900, 900) self.setMinimumSize(MIN_WIDTH, MIN_HEIGHT)
central = QWidget(self) central = QWidget(self)
grid = QGridLayout(central) grid = QGridLayout(central)
@@ -299,18 +304,22 @@ class MonitorWindow(QMainWindow):
window=window window=window
) )
casefan = self.config.getValue( "cooling", "casefan", None ) if self.cpuinfo.model == 5:
if casefan is None: self.caseFanPin = self.config.getValue( "cooling", "casefan", None )
series = [("RPM",None)] if self.caseFanPin is None:
else: series = [("CPU",None)]
series = [("CPU", None),("CaseFan",None)] else:
self.casefan.setTACHPin( self.caseFanPin)
series = [("CPU",None),("CaseFan",None)]
self.fan_chart = RollingChart( self.fan_chart = RollingChart(
title="Fan Speed", title="Fan Speed",
series_defs=series, series_defs=series,
y_min=0,y_max=6000, y_min=0,y_max=6000,
window=window window=window
) )
else:
self.fan_chart = None
series = [] series = []
for name in self.multiDrive.drives: for name in self.multiDrive.drives:
@@ -328,8 +337,11 @@ class MonitorWindow(QMainWindow):
# Layout: 2x2 grid (CPU, NVMe on top; IO full width bottom) # Layout: 2x2 grid (CPU, NVMe on top; IO full width bottom)
grid.addWidget(self.use_chart, 0, 0, 1, 2 ) grid.addWidget(self.use_chart, 0, 0, 1, 2 )
grid.addWidget(self.io_chart, 1, 0, 1, 2 ) grid.addWidget(self.io_chart, 1, 0, 1, 2 )
grid.addWidget(self.cpu_chart, 2, 0, 1, 1 ) if self.fan_chart:
grid.addWidget(self.fan_chart, 2, 1, 1, 1 ) grid.addWidget(self.cpu_chart, 2, 0, 1, 1 )
grid.addWidget(self.fan_chart, 2, 1, 1, 1 )
else:
grid.addWidget(self.cpu_chart, 2, 0, 1, 2 )
# Get the initial information from the syste # Get the initial information from the syste
self.refresh_metrics() self.refresh_metrics()
@@ -350,11 +362,14 @@ class MonitorWindow(QMainWindow):
# Obtain the current fan speed # Obtain the current fan speed
if self.cpuinfo.model == 5: if self.cpuinfo.model == 5:
try: try:
fan_speed = self.cpuinfo.CPUFanSpeed if self.caseFanPin:
fan_speed = [self.cpuinfo.CPUFanSpeed,self.casefan.speed]
else:
fan_speed = [self.cpuinfo.CPUFanSpeed]
except Exception: except Exception:
fan_speed = None fan_speed = [None,None]
else: else:
fan_speed = None fan_speed = [None,None]
# Setup the temperature for the CPU and Drives # Setup the temperature for the CPU and Drives
temperatures = [] temperatures = []
@@ -392,7 +407,8 @@ class MonitorWindow(QMainWindow):
# Append to charts # Append to charts
self.cpu_chart.append( temperatures ) self.cpu_chart.append( temperatures )
self.fan_chart.append([fan_speed]) if self.fan_chart:
self.fan_chart.append( fan_speed )
self.io_chart.append( rwData ) self.io_chart.append( rwData )
self.use_chart.append( values ) self.use_chart.append( values )
@@ -404,4 +420,3 @@ def main():
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@@ -84,6 +84,7 @@ class DriveStats:
def name(self) -> str: def name(self) -> str:
return self._device return self._device
@property
def readAllStats( self ) -> list[int]: def readAllStats( self ) -> list[int]:
''' '''
read all of the drive statisics from sysfs for the device. read all of the drive statisics from sysfs for the device.
@@ -93,19 +94,24 @@ class DriveStats:
''' '''
return self._getStats() return self._getStats()
@property
def readSectors( self )-> int: def readSectors( self )-> int:
return self._getStats()[DriveStats.READ_SECTORS] return self._getStats()[DriveStats.READ_SECTORS]
@property
def writeSectors( self ) -> int: def writeSectors( self ) -> int:
return self._getStats()[DriveStats.WRITE_SECTORS] return self._getStats()[DriveStats.WRITE_SECTORS]
@property
def discardSectors( self ) -> int: def discardSectors( self ) -> int:
return self._getStats()[DriveStats.DISCARD_SECTORS] return self._getStats()[DriveStats.DISCARD_SECTORS]
@property
def readWriteSectors( self ) -> tuple[int,int]: def readWriteSectors( self ) -> tuple[int,int]:
curData = self._getStats() curData = self._getStats()
return (curData[DriveStats.READ_SECTORS],curData[DriveStats.WRITE_SECTORS]) return (curData[DriveStats.READ_SECTORS],curData[DriveStats.WRITE_SECTORS])
@property
def readWriteBytes( self ) -> tuple[int,int]: def readWriteBytes( self ) -> tuple[int,int]:
curData = self._getStats() curData = self._getStats()
return (curData[DriveStats.READ_SECTORS]*512,curData[DriveStats.WRITE_SECTORS]*512) return (curData[DriveStats.READ_SECTORS]*512,curData[DriveStats.WRITE_SECTORS]*512)
@@ -168,6 +174,19 @@ class multiDriveStat():
return 0 return 0
def driveTemp(self,_drive:str, extracmd = None) -> float: def driveTemp(self,_drive:str, extracmd = None) -> float:
'''
Get the drive temperature using smart data. There are three basic temperature
settings we can read, smart ID 194, 190 and the Temperature: value. These are
depenent on the drive, so look for all of them, and depending on the result, we
get the value.
Parameters:
_drive : The device we wish to scan
extraCmd : An optional additional command to send to the device.
Returns:
The temperature as a float, or zero if there is an error.
'''
smartOutRaw = "" smartOutRaw = ""
if extracmd is None: if extracmd is None:
cmd = f'sudo smartctl -A /dev/{_drive}' cmd = f'sudo smartctl -A /dev/{_drive}'
@@ -208,7 +227,7 @@ class multiDriveStat():
''' '''
curData = {} curData = {}
for _ in self._stats: for _ in self._stats:
curData[_.name] = _.readWriteSectors() curData[_.name] = _.readWriteSectors
return curData return curData
def readWriteBytes( self ) -> dict[str,tuple[int,int]]: def readWriteBytes( self ) -> dict[str,tuple[int,int]]:
@@ -218,7 +237,7 @@ class multiDriveStat():
''' '''
curData = {} curData = {}
for _ in self._stats: for _ in self._stats:
curData[_.name] = _.readWriteBytes() curData[_.name] = _.readWriteBytes
return curData return curData
class CPUInfo: class CPUInfo:
@@ -230,14 +249,26 @@ class CPUInfo:
self._cputemp = CPUTemperature() self._cputemp = CPUTemperature()
def _cpuModel( self ) -> int: def _cpuModel( self ) -> int:
'''
Check for the cpu model. Scan cpuinfo to see if we can locate a string that
matches something we are looking for.
Return:
Model of the Raspberry PI. This treats the Comput Modules the same as
standard model B's
'''
with os.popen( "grep Model /proc/cpuinfo" ) as command: with os.popen( "grep Model /proc/cpuinfo" ) as command:
data = command.read() data = command.read()
if "Compute Module 5" in data: if "Compute Module 5" in data:
return 5 return 5
elif "Raspberry Pi 4" in data:
return 4
elif "Raspberry Pi 5" in data: elif "Raspberry Pi 5" in data:
return 5 return 5
elif "Raspberry Pi 4" in data:
return 4
elif "Compute Module 4" in data:
return 4
elif "Raspberry Pi 3" in data:
return 3
else: else:
return 0 return 0
@@ -382,6 +413,20 @@ class CPULoad:
''' '''
return len(self._previousData) return len(self._previousData)
class CaseFan:
'''
Class used to monitor a TACH from a case fan.
'''
def __init__( self, pin=None ):
self.tach = pin
self.rpm = 0
def setTACHPin( self, pin = int):
self.tach = pin
@property
def speed( self ) -> float:
return self.rpm
if __name__ == "__main__": if __name__ == "__main__":
@@ -399,6 +444,11 @@ if __name__ == "__main__":
print( f"CPU Fan Speed = {cpuinfo.CPUFanSpeed}" ) print( f"CPU Fan Speed = {cpuinfo.CPUFanSpeed}" )
print( f"CPU Model = {cpuinfo.model}" ) print( f"CPU Model = {cpuinfo.model}" )
caseFan = CaseFan( 18 )
print( f"RPM = {caseFan.speed}" )
time.sleep(1)
print( f"RPM = {caseFan.speed}" )
test = multiDriveStat() test = multiDriveStat()
print( test.drives ) print( test.drives )
for drive in test.drives: for drive in test.drives:

View File

@@ -3,3 +3,8 @@
[performance] [performance]
ignore = "mmcblk0" ignore = "mmcblk0"
[smartctl]
sda="foobar"
sda="duplicate"
sdb='hello'