Skip to content

Commit edd7423

Browse files
committed
Address PR review: robustness, CF leaks, error handling
- Fix Net.stats() first-call KeyError with explicit PNIC_BEFORE check - Fix CF memory leak: CFRelease(delta) after parsing, remove unused _last_delta - Wrap SMC LoadLibrary in try/except for graceful degradation - Release IOService object in _open() to prevent handle leak - Use _available flag instead of falsy _conn check - Cache Gpu.is_available() result to log detection only once - Remove unused CPU/GPU P-state residency collection from _parse_delta - Replace all bare except: with except Exception: - Fix stats.py exit(0) → exit(1) for APPLE_SILICON misconfig - Add theme preview screenshot
1 parent 1289706 commit edd7423

3 files changed

Lines changed: 36 additions & 52 deletions

File tree

library/sensors/sensors_apple_silicon.py

Lines changed: 33 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,15 @@ def __init__(self):
5858
return
5959
self._initialized = True
6060
self._conn = None
61+
self._available = False
6162
self._smc_lock = threading.Lock()
62-
self._iokit = ctypes.cdll.LoadLibrary('/System/Library/Frameworks/IOKit.framework/IOKit')
63-
self._cf = ctypes.cdll.LoadLibrary('/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation')
64-
self._setup_functions()
65-
self._open()
63+
try:
64+
self._iokit = ctypes.cdll.LoadLibrary('/System/Library/Frameworks/IOKit.framework/IOKit')
65+
self._cf = ctypes.cdll.LoadLibrary('/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation')
66+
self._setup_functions()
67+
self._open()
68+
except Exception as e:
69+
logger.warning(f"SMC init failed: {e}")
6670

6771
def _setup_functions(self):
6872
iokit = self._iokit
@@ -80,6 +84,8 @@ def _setup_functions(self):
8084
iokit.IOServiceOpen.restype = ctypes.c_uint32
8185
iokit.IOServiceClose.argtypes = [ctypes.c_uint32]
8286
iokit.IOServiceClose.restype = ctypes.c_uint32
87+
iokit.IOObjectRelease.argtypes = [ctypes.c_uint32]
88+
iokit.IOObjectRelease.restype = ctypes.c_uint32
8389

8490
self._mach_task_self = ctypes.cdll.LoadLibrary('/usr/lib/libSystem.B.dylib').mach_task_self
8591
self._mach_task_self.restype = ctypes.c_uint32
@@ -102,10 +108,12 @@ def _open(self):
102108
return
103109
conn = ctypes.c_uint32()
104110
result = self._iokit.IOServiceOpen(service, self._mach_task_self(), 0, ctypes.byref(conn))
111+
self._iokit.IOObjectRelease(service)
105112
if result != 0:
106113
logger.warning(f"Could not open Apple SMC connection: {result:#x}")
107114
return
108115
self._conn = conn.value
116+
self._available = True
109117

110118
def _fcc(self, s):
111119
return int.from_bytes(s.encode('ascii'), byteorder='big')
@@ -121,7 +129,7 @@ def _smc_call(self, buf):
121129
return result, out
122130

123131
def read_key(self, key_name):
124-
if not self._conn:
132+
if not self._available:
125133
return None
126134
with self._smc_lock:
127135
try:
@@ -208,7 +216,6 @@ def __init__(self):
208216
self._sub = None
209217
self._channels = None
210218
self._last_sample = None
211-
self._last_delta = None
212219
self._last_time = None
213220
self._sample_interval = 1.0
214221
self._data_lock = threading.Lock()
@@ -339,7 +346,7 @@ def _sample_loop(self):
339346
parsed = self._parse_delta(delta, dt)
340347
with self._data_lock:
341348
self._parsed = parsed
342-
self._last_delta = delta
349+
self._cf.CFRelease(delta)
343350
self._cf.CFRelease(self._last_sample)
344351
else:
345352
self._cf.CFRelease(self._last_sample)
@@ -352,8 +359,6 @@ def _sample_loop(self):
352359
def _parse_delta(self, delta, dt):
353360
result = {
354361
'energy': {},
355-
'cpu_freq': math.nan,
356-
'gpu_freq': math.nan,
357362
'cpu_power': math.nan,
358363
'gpu_power': math.nan,
359364
'dram_power': math.nan,
@@ -374,11 +379,8 @@ def _parse_delta(self, delta, dt):
374379

375380
count = cf.CFArrayGetCount(channels_arr)
376381
energy_values = {}
377-
cpu_residencies = {}
378-
gpu_residencies = {}
379382

380383
SIMPLE_FORMAT = 1
381-
STATE_FORMAT = 2
382384

383385
for i in range(count):
384386
ch = cf.CFArrayGetValueAtIndex(channels_arr, i)
@@ -394,28 +396,6 @@ def _parse_delta(self, delta, dt):
394396
val = iorep.IOReportSimpleGetIntegerValue(ch, None)
395397
energy_values[name] = val
396398

397-
elif "CPU Stats" in group and fmt == STATE_FORMAT:
398-
state_count = iorep.IOReportStateGetCount(ch)
399-
if state_count > 0:
400-
states = {}
401-
for si in range(state_count):
402-
residency = iorep.IOReportStateGetResidency(ch, si)
403-
sname_cf = iorep.IOReportStateGetNameForIndex(ch, si)
404-
sname = self._cfstr_to_str(sname_cf)
405-
states[sname] = residency
406-
cpu_residencies[name] = states
407-
408-
elif "GPU Stats" in group and fmt == STATE_FORMAT:
409-
state_count = iorep.IOReportStateGetCount(ch)
410-
if state_count > 0:
411-
states = {}
412-
for si in range(state_count):
413-
residency = iorep.IOReportStateGetResidency(ch, si)
414-
sname_cf = iorep.IOReportStateGetNameForIndex(ch, si)
415-
sname = self._cfstr_to_str(sname_cf)
416-
states[sname] = residency
417-
gpu_residencies[name] = states
418-
419399
result['energy'] = energy_values
420400

421401
cpu_total_energy = 0
@@ -565,21 +545,21 @@ class Cpu(sensors.Cpu):
565545
def percentage(interval: float) -> float:
566546
try:
567547
return psutil.cpu_percent(interval=interval)
568-
except:
548+
except Exception:
569549
return math.nan
570550

571551
@staticmethod
572552
def frequency() -> float:
573553
try:
574554
return psutil.cpu_freq().current
575-
except:
555+
except Exception:
576556
return math.nan
577557

578558
@staticmethod
579559
def load() -> Tuple[float, float, float]:
580560
try:
581561
return psutil.getloadavg()
582-
except:
562+
except Exception:
583563
return math.nan, math.nan, math.nan
584564

585565
@staticmethod
@@ -675,13 +655,19 @@ def fan_percent() -> float:
675655
def frequency() -> float:
676656
return math.nan
677657

658+
_gpu_detected = None
659+
678660
@staticmethod
679661
def is_available() -> bool:
662+
if Gpu._gpu_detected is not None:
663+
return Gpu._gpu_detected
680664
agx = _get_agx()
681665
model = agx.get('model', '')
682666
if model:
683667
logger.info(f"Detected Apple GPU: {model} ({agx.get('gpu-core-count', '?')} cores)")
668+
Gpu._gpu_detected = True
684669
return True
670+
Gpu._gpu_detected = False
685671
return False
686672

687673

@@ -690,28 +676,28 @@ class Memory(sensors.Memory):
690676
def swap_percent() -> float:
691677
try:
692678
return psutil.swap_memory().percent
693-
except:
679+
except Exception:
694680
return math.nan
695681

696682
@staticmethod
697683
def virtual_percent() -> float:
698684
try:
699685
return psutil.virtual_memory().percent
700-
except:
686+
except Exception:
701687
return math.nan
702688

703689
@staticmethod
704690
def virtual_used() -> int:
705691
try:
706692
return psutil.virtual_memory().total - psutil.virtual_memory().available
707-
except:
693+
except Exception:
708694
return -1
709695

710696
@staticmethod
711697
def virtual_free() -> int:
712698
try:
713699
return psutil.virtual_memory().available
714-
except:
700+
except Exception:
715701
return -1
716702

717703

@@ -720,21 +706,21 @@ class Disk(sensors.Disk):
720706
def disk_usage_percent() -> float:
721707
try:
722708
return psutil.disk_usage("/").percent
723-
except:
709+
except Exception:
724710
return math.nan
725711

726712
@staticmethod
727713
def disk_used() -> int:
728714
try:
729715
return psutil.disk_usage("/").used
730-
except:
716+
except Exception:
731717
return -1
732718

733719
@staticmethod
734720
def disk_free() -> int:
735721
try:
736722
return psutil.disk_usage("/").free
737-
except:
723+
except Exception:
738724
return -1
739725

740726

@@ -750,17 +736,15 @@ def stats(if_name, interval) -> Tuple[int, int, int, int]:
750736

751737
if if_name != "":
752738
if if_name in pnic_after:
753-
try:
739+
if if_name in PNIC_BEFORE:
754740
upload_rate = (pnic_after[if_name].bytes_sent - PNIC_BEFORE[if_name].bytes_sent) / interval
755741
uploaded = pnic_after[if_name].bytes_sent
756742
download_rate = (pnic_after[if_name].bytes_recv - PNIC_BEFORE[if_name].bytes_recv) / interval
757743
downloaded = pnic_after[if_name].bytes_recv
758-
except:
759-
pass
760-
PNIC_BEFORE.update({if_name: pnic_after[if_name]})
744+
PNIC_BEFORE[if_name] = pnic_after[if_name]
761745
else:
762746
logger.warning("Network interface '%s' not found. Check names in config.yaml." % if_name)
763747

764748
return upload_rate, uploaded, download_rate, downloaded
765-
except:
749+
except Exception:
766750
return -1, -1, -1, -1

library/stats.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,9 @@
7474
else:
7575
logger.error("Apple Silicon sensor support is only available on Apple Silicon Macs")
7676
try:
77-
sys.exit(0)
78-
except:
79-
os._exit(0)
77+
sys.exit(1)
78+
except Exception:
79+
os._exit(1)
8080
elif HW_SENSORS == "AUTO":
8181
if platform.system() == 'Windows':
8282
import library.sensors.sensors_librehardwaremonitor as sensors
34.7 KB
Loading

0 commit comments

Comments
 (0)