Skip to content

Commit

Permalink
[TEMPERATURE] Adds support for BSD and derivatives (using sysctl)
Browse files Browse the repository at this point in the history
> See related : #69 & #95.
  • Loading branch information
Samuel FORESTIER committed Mar 9, 2021
1 parent d7e3aa2 commit 93de654
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 7 deletions.
42 changes: 38 additions & 4 deletions archey/entries/temperature.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import json
import logging
import os
import platform
import re

from glob import iglob
Expand All @@ -14,7 +16,8 @@
class Temperature(Entry):
"""
Tries to compute an average temperature from `sensors` (LM-Sensors).
If not available, falls back on system thermal zones files.
If not available, falls back on system thermal zones files (GNU/Linux)
or `sysctl` output for BSD and derivatives systems.
On Raspberry devices, retrieves temperature from the `vcgencmd` binary.
"""
def __init__(self, *args, **kwargs):
Expand All @@ -25,14 +28,19 @@ def __init__(self, *args, **kwargs):
# Tries `sensors` at first.
self._run_sensors(self.options.get('sensors_chipsets', []))

# On error (list still empty), checks for system thermal zones files.
# On error (list still empty)...
if not self._temps:
self._poll_thermal_zones()
if platform.system() == 'Linux':
# ... checks for system thermal zones files on GNU/Linux.
self._poll_thermal_zones()
else:
# ... or tries `sysctl` calls (available on BSD and derivatives).
self._run_sysctl_dev_cpu()

# Tries `vcgencmd` for Raspberry devices.
self._run_vcgencmd()

# No value could be fetched...
# No value could be fetched, leave `self.value` to `None`.
if not self._temps:
return

Expand Down Expand Up @@ -105,6 +113,32 @@ def _poll_thermal_zones(self):
if temp != 0.0:
self._temps.append(temp)

def _run_sysctl_dev_cpu(self):
# Tries to get temperatures from each CPU core sensor.
try:
sysctl_output = check_output(
['sysctl', '-n'] + \
[f'dev.cpu.{i}.temperature' for i in range(os.cpu_count())],
stderr=PIPE, universal_newlines=True
)
except FileNotFoundError:
# `sysctl` does not seem to be available on this system.
return
except CalledProcessError as error_message:
logging.warning(
'[sysctl]: Couldn\'t fetch temperature from CPU sensors (%s). '
'Please be sure to load the corresponding kernel driver beforehand '
'(`kldload coretemp` for Intel or `kldload amdtemp` for AMD`).',
(error_message.stderr or 'unknown error').rstrip()
)
return

for temp in sysctl_output.splitlines():
# Strip any temperature unit from output (some drivers may add it).
temp = float(temp.rstrip('C'))
if temp != 0.0:
self._temps.append(temp)

def _run_vcgencmd(self):
# Let's try to retrieve a value from the Broadcom chip on Raspberry.
try:
Expand Down
58 changes: 55 additions & 3 deletions archey/test/entries/test_archey_temperature.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@


class TestTemperatureEntry(unittest.TestCase, CustomAssertions):
"""
Based on `sensors`, `vcgencmd` and thermal files, this module verifies temperature computations.
"""
"""This module verifies Archey temperature detection"""
def setUp(self):
self.temperature_mock = HelperMethods.entry_mock(Temperature)
self.temperature_mock._temps = [] # pylint: disable=protected-access
Expand Down Expand Up @@ -157,6 +155,60 @@ def test_poll_thermal_zones(self, iglob_mock):
os.remove(tmp_file.name)
## END POSTLUDE ##

@patch(
'archey.entries.temperature.os.cpu_count',
return_value=4 # Mocks a quad-cores CPU system.
)
@patch(
'archey.entries.temperature.check_output',
side_effect=[
# First case (`sysctl` won't be available).
FileNotFoundError(),
# Second run (`sysctl` will fail).
CalledProcessError(1, 'sysctl'),
# Third case (OK).
"""\
42C
42C
45C
45C
""",
# Fourth case (partially-OK).
"""\
42
40
0
38
"""])
def test_run_sysctl_dev_cpu(self, _, __):
"""Test for `sysctl` output only"""
# pylint: disable=protected-access
# First case.
Temperature._run_sysctl_dev_cpu(self.temperature_mock)
self.assertListEmpty(self.temperature_mock._temps)

# Second case.
Temperature._run_sysctl_dev_cpu(self.temperature_mock)
self.assertListEmpty(self.temperature_mock._temps)

# Third case.
Temperature._run_sysctl_dev_cpu(self.temperature_mock)
self.assertListEqual(
self.temperature_mock._temps,
[42.0, 42.0, 45.0, 45.0]
)

# Reset internal object here.
self.temperature_mock._temps = []

# Fourth case.
Temperature._run_sysctl_dev_cpu(self.temperature_mock)
self.assertListEqual(
self.temperature_mock._temps,
[42.0, 40.0, 38.0]
)
# pylint: enable=protected-access

@patch(
'archey.entries.temperature.check_output',
side_effect=[
Expand Down

0 comments on commit 93de654

Please sign in to comment.