def test_set_fan_speeds(report): mocked_fans = OrderedDict( [ (FanName("test1"), MagicMock(spec=PWMFanNorm)), (FanName("test2"), MagicMock(spec=PWMFanNorm)), (FanName("test3"), MagicMock(spec=PWMFanNorm)), (FanName("test4"), MagicMock(spec=PWMFanNorm)), ] ) for fan in mocked_fans.values(): fan.set.return_value = 240 fan.get_speed.return_value = 942 fan.is_pwm_stopped = BasePWMFan.is_pwm_stopped fans = Fans(mocked_fans, report=report) with fans: fans._ensure_fan_is_failing(FanName("test2"), Exception("test")) fans.set_fan_speeds( { FanName("test1"): PWMValueNorm(0.42), FanName("test2"): PWMValueNorm(0.42), FanName("test3"): PWMValueNorm(0.42), FanName("test4"): PWMValueNorm(0.42), } ) assert [1, 0, 1, 1] == [f.set.call_count for f in mocked_fans.values()]
def test_manager(report): mocked_case_fan = MagicMock(spec=PWMFanNorm)() mocked_mobo_temp = MagicMock(spec=FileTemp)() mocked_metrics = MagicMock(spec=Metrics)() with ExitStack() as stack: stack.enter_context( patch.object(afancontrol.manager, "Triggers", spec=Triggers)) manager = Manager( arduino_connections={}, fans={FanName("case"): mocked_case_fan}, readonly_fans={}, temps={TempName("mobo"): mocked_mobo_temp}, mappings={ MappingName("1"): FansTempsRelation( temps=[TempName("mobo")], fans=[FanSpeedModifier(fan=FanName("case"), modifier=0.6)], ) }, report=report, triggers_config=TriggerConfig( global_commands=Actions( panic=AlertCommands(enter_cmd=None, leave_cmd=None), threshold=AlertCommands(enter_cmd=None, leave_cmd=None), ), temp_commands={ TempName("mobo"): Actions( panic=AlertCommands(enter_cmd=None, leave_cmd=None), threshold=AlertCommands(enter_cmd=None, leave_cmd=None), ) }, ), metrics=mocked_metrics, ) stack.enter_context(manager) manager.tick() mocked_triggers = cast(MagicMock, manager.triggers) assert mocked_triggers.check.call_count == 1 assert mocked_case_fan.__enter__.call_count == 1 assert mocked_metrics.__enter__.call_count == 1 assert mocked_metrics.tick.call_count == 1 assert mocked_case_fan.__exit__.call_count == 1 assert mocked_metrics.__exit__.call_count == 1
def test_prometheus_faulty_fans_dont_break_metrics_collection( requests_session): mocked_fan = MagicMock(spec=PWMFanNorm)() mocked_triggers = MagicMock(spec=Triggers)() mocked_report = MagicMock(spec=Report)() port = random.randint(20000, 50000) metrics = PrometheusMetrics("127.0.0.1:%s" % port) with metrics: mocked_triggers.panic_trigger.is_alerting = False mocked_triggers.threshold_trigger.is_alerting = False mocked_fan.pwm_line_start = 100 mocked_fan.pwm_line_end = 240 mocked_fan.get_speed.side_effect = IOError mocked_fan.get_raw.side_effect = IOError # Must not raise despite the PWMFan methods raising above: metrics.tick( temps={TempName("failingtemp"): None}, fans=Fans(fans={FanName("test"): mocked_fan}, report=mocked_report), triggers=mocked_triggers, ) resp = requests_session.get("http://127.0.0.1:%s/metrics" % port) assert resp.status_code == 200 assert 'fan_pwm_line_start{fan_name="test"} 100.0' in resp.text assert 'fan_pwm_line_end{fan_name="test"} 240.0' in resp.text assert 'fan_rpm{fan_name="test"} NaN' in resp.text assert 'fan_pwm{fan_name="test"} NaN' in resp.text assert 'fan_pwm_normalized{fan_name="test"} NaN' in resp.text assert 'fan_is_failing{fan_name="test"} 0.0' in resp.text assert "is_panic 0.0" in resp.text assert "is_threshold 0.0" in resp.text
def test_multiline_mapping(): daemon_cli_config = DaemonCLIConfig( pidfile=None, logfile=None, exporter_listen_host=None ) config = """ [daemon] [actions] [temp:cpu] type = file path = /sys/class/hwmon/hwmon0/device/temp1_input [temp:mobo] type = file path = /sys/class/hwmon/hwmon0/device/temp2_input [fan: case] pwm = /sys/class/hwmon/hwmon0/device/pwm2 fan_input = /sys/class/hwmon/hwmon0/device/fan2_input [fan: hdd] pwm = /sys/class/hwmon/hwmon0/device/pwm2 fan_input = /sys/class/hwmon/hwmon0/device/fan2_input [mapping:1] fans = case*0.6, hdd, temps = mobo, cpu """ parsed = parse_config(path_from_str(config), daemon_cli_config) assert parsed.mappings == { MappingName("1"): FansTempsRelation( temps=[TempName("mobo"), TempName("cpu")], fans=[ FanSpeedModifier(fan=FanName("case"), modifier=0.6), FanSpeedModifier(fan=FanName("hdd"), modifier=1.0), ], ) }
def test_smoke(report, is_fan_failing): fan = MagicMock(spec=PWMFanNorm) fans = Fans({FanName("test"): fan}, report=report) fan.set = lambda pwm_norm: int(255 * pwm_norm) fan.get_speed.return_value = 0 if is_fan_failing else 942 fan.is_pwm_stopped = BasePWMFan.is_pwm_stopped with fans: assert 1 == fan.__enter__.call_count fans.check_speeds() fans.set_all_to_full_speed() fans.set_fan_speeds({FanName("test"): PWMValueNorm(0.42)}) assert fan.get_speed.call_count == 1 if is_fan_failing: assert fans._failed_fans == {"test"} assert fans._stopped_fans == set() else: assert fans._failed_fans == set() assert fans._stopped_fans == set() assert 1 == fan.__exit__.call_count
def test_prometheus_metrics(requests_session): mocked_fan = MagicMock(spec=PWMFanNorm)() mocked_triggers = MagicMock(spec=Triggers)() mocked_report = MagicMock(spec=Report)() port = random.randint(20000, 50000) metrics = PrometheusMetrics("127.0.0.1:%s" % port) with metrics: resp = requests_session.get("http://127.0.0.1:%s/metrics" % port) assert resp.status_code == 200 assert "is_threshold 0.0" in resp.text with metrics.measure_tick(): sleep(0.01) resp = requests_session.get("http://127.0.0.1:%s/metrics" % port) assert resp.status_code == 200 assert "tick_duration_count 1.0" in resp.text assert "tick_duration_sum 0." in resp.text mocked_triggers.panic_trigger.is_alerting = True mocked_triggers.threshold_trigger.is_alerting = False mocked_fan.pwm_line_start = 100 mocked_fan.pwm_line_end = 240 mocked_fan.get_speed.return_value = 999 mocked_fan.get_raw.return_value = 142 mocked_fan.get = types.MethodType(PWMFanNorm.get, mocked_fan) mocked_fan.pwmfan.max_pwm = 255 metrics.tick( temps={ TempName("goodtemp"): TempStatus( temp=TempCelsius(74.0), min=TempCelsius(40.0), max=TempCelsius(50.0), panic=TempCelsius(60.0), threshold=None, is_panic=True, is_threshold=False, ), TempName("failingtemp"): None, }, fans=Fans(fans={FanName("test"): mocked_fan}, report=mocked_report), triggers=mocked_triggers, ) resp = requests_session.get("http://127.0.0.1:%s/metrics" % port) assert resp.status_code == 200 print(resp.text) assert 'temperature_current{temp_name="failingtemp"} NaN' in resp.text assert 'temperature_current{temp_name="goodtemp"} 74.0' in resp.text assert 'temperature_is_failing{temp_name="failingtemp"} 1.0' in resp.text assert 'temperature_is_failing{temp_name="goodtemp"} 0.0' in resp.text assert 'fan_rpm{fan_name="test"} 999.0' in resp.text assert 'fan_pwm{fan_name="test"} 142.0' in resp.text assert 'fan_pwm_normalized{fan_name="test"} 0.556' in resp.text assert 'fan_is_failing{fan_name="test"} 0.0' in resp.text assert "is_panic 1.0" in resp.text assert "is_threshold 0.0" in resp.text assert "last_metrics_tick_seconds_ago 0." in resp.text with pytest.raises(IOError): requests_session.get("http://127.0.0.1:%s/metrics" % port)
min=TempCelsius(30), max=TempCelsius(50), temp=TempCelsius((50 - 30) * 0.42 + 30), panic=None, threshold=None, is_panic=False, is_threshold=False, ), TempName("hdd"): None, # a failing sensor }, { MappingName("all"): FansTempsRelation( temps=[TempName("cpu"), TempName("hdd")], fans=[FanSpeedModifier(fan=FanName("rear"), modifier=1.0)], ) }, { FanName("rear"): PWMValueNorm(1.0) }, ), ( { TempName("cpu"): TempStatus( min=TempCelsius(30), max=TempCelsius(50), temp=TempCelsius((50 - 30) * 0.42 + 30), panic=None, threshold=None,
def test_pkg_conf(pkg_conf: Path): daemon_cli_config = DaemonCLIConfig(pidfile=None, logfile=None, exporter_listen_host=None) parsed = parse_config(pkg_conf, daemon_cli_config) assert parsed == ParsedConfig( daemon=DaemonConfig( pidfile="/run/afancontrol.pid", logfile="/var/log/afancontrol.log", interval=5, exporter_listen_host=None, ), report_cmd= ('printf "Subject: %s\nTo: %s\n\n%b" ' '"afancontrol daemon report: %REASON%" root "%MESSAGE%" | sendmail -t' ), triggers=TriggerConfig( global_commands=Actions( panic=AlertCommands(enter_cmd=None, leave_cmd=None), threshold=AlertCommands(enter_cmd=None, leave_cmd=None), ), temp_commands={ TempName("mobo"): Actions( panic=AlertCommands(enter_cmd=None, leave_cmd=None), threshold=AlertCommands(enter_cmd=None, leave_cmd=None), ) }, ), fans={ FanName("hdd"): PWMFanNorm( LinuxPWMFan( PWMDevice("/sys/class/hwmon/hwmon0/device/pwm2"), FanInputDevice( "/sys/class/hwmon/hwmon0/device/fan2_input"), ), pwm_line_start=PWMValue(100), pwm_line_end=PWMValue(240), never_stop=False, ) }, temps={ TempName("mobo"): FileTemp( "/sys/class/hwmon/hwmon0/device/temp1_input", min=TempCelsius(30.0), max=TempCelsius(40.0), panic=None, threshold=None, ) }, mappings={ MappingName("1"): FansTempsRelation( temps=[TempName("mobo")], fans=[FanSpeedModifier(fan=FanName("hdd"), modifier=0.6)], ) }, )
def test_minimal_config() -> None: daemon_cli_config = DaemonCLIConfig(pidfile=None, logfile=None, exporter_listen_host=None) config = """ [daemon] [actions] [temp:mobo] type = file path = /sys/class/hwmon/hwmon0/device/temp1_input [fan: case] pwm = /sys/class/hwmon/hwmon0/device/pwm2 fan_input = /sys/class/hwmon/hwmon0/device/fan2_input [mapping:1] fans = case*0.6, temps = mobo """ parsed = parse_config(path_from_str(config), daemon_cli_config) assert parsed == ParsedConfig( daemon=DaemonConfig( pidfile="/run/afancontrol.pid", logfile=None, exporter_listen_host=None, interval=5, ), report_cmd= ('printf "Subject: %s\nTo: %s\n\n%b" ' '"afancontrol daemon report: %REASON%" root "%MESSAGE%" | sendmail -t' ), triggers=TriggerConfig( global_commands=Actions( panic=AlertCommands(enter_cmd=None, leave_cmd=None), threshold=AlertCommands(enter_cmd=None, leave_cmd=None), ), temp_commands={ TempName("mobo"): Actions( panic=AlertCommands(enter_cmd=None, leave_cmd=None), threshold=AlertCommands(enter_cmd=None, leave_cmd=None), ) }, ), fans={ FanName("case"): PWMFanNorm( LinuxPWMFan( PWMDevice("/sys/class/hwmon/hwmon0/device/pwm2"), FanInputDevice( "/sys/class/hwmon/hwmon0/device/fan2_input"), ), pwm_line_start=PWMValue(100), pwm_line_end=PWMValue(240), never_stop=True, ) }, temps={ TempName("mobo"): FileTemp( "/sys/class/hwmon/hwmon0/device/temp1_input", min=None, max=None, panic=None, threshold=None, ) }, mappings={ MappingName("1"): FansTempsRelation( temps=[TempName("mobo")], fans=[FanSpeedModifier(fan=FanName("case"), modifier=0.6)], ) }, )
def test_example_conf(example_conf: Path): daemon_cli_config = DaemonCLIConfig(pidfile=None, logfile=None, exporter_listen_host=None) parsed = parse_config(example_conf, daemon_cli_config) assert parsed == ParsedConfig( daemon=DaemonConfig( pidfile="/run/afancontrol.pid", logfile="/var/log/afancontrol.log", exporter_listen_host="127.0.0.1:8083", interval=5, ), report_cmd= ('printf "Subject: %s\nTo: %s\n\n%b" ' '"afancontrol daemon report: %REASON%" root "%MESSAGE%" | sendmail -t' ), triggers=TriggerConfig( global_commands=Actions( panic=AlertCommands(enter_cmd=None, leave_cmd=None), threshold=AlertCommands(enter_cmd=None, leave_cmd=None), ), temp_commands={ TempName("hdds"): Actions( panic=AlertCommands(enter_cmd=None, leave_cmd=None), threshold=AlertCommands(enter_cmd=None, leave_cmd=None), ), TempName("mobo"): Actions( panic=AlertCommands(enter_cmd=None, leave_cmd=None), threshold=AlertCommands(enter_cmd=None, leave_cmd=None), ), }, ), fans={ FanName("cpu"): PWMFanNorm( LinuxPWMFan( PWMDevice("/sys/class/hwmon/hwmon0/device/pwm1"), FanInputDevice( "/sys/class/hwmon/hwmon0/device/fan1_input"), ), pwm_line_start=PWMValue(100), pwm_line_end=PWMValue(240), never_stop=True, ), FanName("hdd"): PWMFanNorm( LinuxPWMFan( PWMDevice("/sys/class/hwmon/hwmon0/device/pwm2"), FanInputDevice( "/sys/class/hwmon/hwmon0/device/fan2_input"), ), pwm_line_start=PWMValue(100), pwm_line_end=PWMValue(240), never_stop=False, ), FanName("my_arduino_fan"): PWMFanNorm( ArduinoPWMFan( ArduinoConnection( ArduinoName("mymicro"), "/dev/ttyACM0", # linux # "/dev/cu.usbmodem14201", # macos baudrate=115200, status_ttl=5, ), pwm_pin=ArduinoPin(9), tacho_pin=ArduinoPin(3), ), pwm_line_start=PWMValue(100), pwm_line_end=PWMValue(240), never_stop=True, ), }, temps={ TempName("hdds"): HDDTemp( "/dev/sd?", min=TempCelsius(35.0), max=TempCelsius(48.0), panic=TempCelsius(55.0), threshold=None, hddtemp_bin="hddtemp", ), TempName("mobo"): FileTemp( "/sys/class/hwmon/hwmon0/device/temp1_input", min=TempCelsius(30.0), max=TempCelsius(40.0), panic=None, threshold=None, ), }, mappings={ MappingName("1"): FansTempsRelation( temps=[TempName("mobo"), TempName("hdds")], fans=[ FanSpeedModifier(fan=FanName("cpu"), modifier=1.0), FanSpeedModifier(fan=FanName("hdd"), modifier=0.6), FanSpeedModifier(fan=FanName("my_arduino_fan"), modifier=0.222), ], ), MappingName("2"): FansTempsRelation( temps=[TempName("hdds")], fans=[FanSpeedModifier(fan=FanName("hdd"), modifier=1.0)], ), }, )