diff options
author | Jeremy Szu <jeremy.szu@canonical.com> | 2021-01-08 00:21:32 +0800 |
---|---|---|
committer | Jeremy Szu <jeremy.szu@canonical.com> | 2021-01-08 00:21:32 +0800 |
commit | 90125cbb435846c4809d77223393dd54389276e6 (patch) | |
tree | 0f9ab2a03dd41d9e31da8e983ed1e5a0f2083fc9 | |
parent | 7748fd527657de4b90aedb738191e9500ec0be48 (diff) | |
parent | 350ca9e240d4b4c767bb71c1b9f9a4b22b309f2a (diff) |
Merge branch 'lp1903653' into HEAD
-rw-r--r--[-rwxr-xr-x] | bin/fan_reaction_test.py | 31 | ||||
-rw-r--r-- | tests/test_fan_reaction.py | 109 |
2 files changed, 137 insertions, 3 deletions
diff --git a/bin/fan_reaction_test.py b/bin/fan_reaction_test.py index c37054b..3b2257e 100755..100644 --- a/bin/fan_reaction_test.py +++ b/bin/fan_reaction_test.py @@ -30,20 +30,45 @@ class FanMonitor: """Device that reports fan RPM or something correlating to that.""" def __init__(self): """Use heuristics to find something that we can read.""" + self.hwmons = [] self._fan_paths = glob.glob('/sys/class/hwmon/hwmon*/fan*_input') - if not self._fan_paths: + # All entries (except name) under /sys/class/hwmon/hwmon/* are optional + # and should only be created in a given driver if the chip has + # the feature. + # Use fan*_input is because the "thinkpad_hwmon" driver is report + # fan_input only. If there is any driver has different implementation + # then may need to check other entries in the future. + for i in self._fan_paths: + device = os.path.join(os.path.dirname(i), 'device') + device_path = os.path.realpath(device) + # Get the class of pci device of hwmon whether is GPU. + if "pci" in device_path: + pci_class_path = os.path.join(device, 'class') + try: + with open(pci_class_path, 'r') as _file: + pci_class = _file.read().splitlines() + pci_device_class = ( + int(pci_class[0], base=16) >> 16) & 0xff + """Make sure the fan is not on graphic card""" + if pci_device_class == 3: + continue + except OSError: + print('Not able to access {}'.format(pci_class_path)) + continue + self.hwmons.append(i) + if not self.hwmons: print('Fan monitoring interface not found in SysFS') raise SystemExit(0) def get_rpm(self): result = {} - for p in self._fan_paths: + for p in self.hwmons: try: with open(p, 'rt') as f: fan_mon_name = os.path.relpath(p, '/sys/class/hwmon') result[fan_mon_name] = int(f.read()) except OSError: - print('Fan SysFS node dissappeared ({})'.format(p)) + print('Fan SysFS node disappeared ({})'.format(p)) return result def get_average_rpm(self, period): diff --git a/tests/test_fan_reaction.py b/tests/test_fan_reaction.py new file mode 100644 index 0000000..5701721 --- /dev/null +++ b/tests/test_fan_reaction.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +# Copyright 2020 Canonical Ltd. +# Written by: +# Sylvain Pineau <sylvain.pineau@canonical.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3, +# as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import unittest +from unittest import mock +from unittest.mock import patch, mock_open +import tempfile + +from fan_reaction_test import FanMonitor + + +class FanMonitorTests(unittest.TestCase): + + """Tests for several type of sysfs hwmon fan files.""" + + @mock.patch('glob.glob') + @mock.patch.object(os.path, 'relpath', autospec=True) + def test_simple(self, relpath_mock, glob_mock): + with tempfile.TemporaryDirectory() as fake_sysfs: + fan_input_file = os.path.join(fake_sysfs, 'fan1_input') + with open(fan_input_file, 'w') as f: + f.write('150') + glob_mock.return_value = [fan_input_file] + relpath_mock.side_effect = ['hwmon4/fan1_input'] + fan_mon = FanMonitor() + self.assertEqual(fan_mon.get_rpm(), {'hwmon4/fan1_input': 150}) + + @mock.patch('glob.glob') + @mock.patch.object(os.path, 'relpath', autospec=True) + def test_multiple(self, relpath_mock, glob_mock): + with tempfile.TemporaryDirectory() as fake_sysfs: + fan_input_file1 = os.path.join(fake_sysfs, 'fan1_input') + with open(fan_input_file1, 'w') as f1: + f1.write('150') + fan_input_file2 = os.path.join(fake_sysfs, 'fan2_input') + with open(fan_input_file2, 'w') as f2: + f2.write('1318') + glob_mock.return_value = [fan_input_file1, fan_input_file2] + relpath_mock.side_effect = [ + 'hwmon4/fan1_input', 'hwmon6/fan2_input'] + fan_mon = FanMonitor() + self.assertEqual( + fan_mon.get_rpm(), + {'hwmon4/fan1_input': 150, 'hwmon6/fan2_input': 1318}) + + @mock.patch('glob.glob') + @mock.patch('os.path.realpath') + def test_discard_gpu_fan(self, realpath_mock, glob_mock): + with tempfile.TemporaryDirectory() as fake_sysfs: + amdgpu_hwmon = os.path.join(fake_sysfs, 'amdgpu-1002-7340') + amdgpu_pci = os.path.join(amdgpu_hwmon, 'device') + os.makedirs(amdgpu_pci) + amdgpu_fan_input_file = os.path.join(amdgpu_hwmon, 'fan1_input') + with open(amdgpu_fan_input_file, 'w') as f: + f.write('65536') + glob_mock.return_value = [amdgpu_fan_input_file] + realpath_mock.return_value = \ + ("/sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0/" + "0000:02:00.0/0000:03:00.0") + # The following call is patching open(pci_class_path, 'r') + with patch("builtins.open", mock_open(read_data='0x030000')) as f: + with self.assertRaises(SystemExit) as cm: + FanMonitor() + the_exception = cm.exception + self.assertEqual(the_exception.code, 0) + + @mock.patch('glob.glob') + @mock.patch('os.path.realpath') + @mock.patch.object(os.path, 'relpath', autospec=True) + def test_discard_gpu_fan_keep_cpu_fan( + self, relpath_mock, realpath_mock, glob_mock + ): + with tempfile.TemporaryDirectory() as fake_sysfs: + amdgpu_hwmon = os.path.join(fake_sysfs, 'amdgpu-1002-7340') + amdgpu_pci = os.path.join(amdgpu_hwmon, 'device') + os.makedirs(amdgpu_pci) + amdgpu_fan_input_file = os.path.join(amdgpu_hwmon, 'fan1_input') + with open(amdgpu_fan_input_file, 'w') as f: + f.write('65536') + fan_input_file2 = os.path.join(fake_sysfs, 'fan2_input') + with open(fan_input_file2, 'w') as f2: + f2.write('412') + glob_mock.return_value = [amdgpu_fan_input_file, fan_input_file2] + realpath_mock.side_effect = [ + "/sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0/" + "0000:02:00.0/0000:03:00.0", "foo"] + relpath_mock.side_effect = ['hwmon6/fan2_input'] + # The following call is patching open(pci_class_path, 'r') + with patch("builtins.open", mock_open(read_data='0x030000')) as f: + fan_mon = FanMonitor() + self.assertEqual(len(fan_mon.hwmons), 1) + self.assertTrue(fan_mon.hwmons[0].endswith('fan2_input')) + self.assertEqual( + fan_mon.get_rpm(), {'hwmon6/fan2_input': 412}) |