예제 #1
0
def test_triggers_good_temp(report):
    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),
                )
            },
        ),
        report=report,
    )
    with t:
        assert not t.is_alerting
        t.check(
            {
                TempName("mobo"): TempStatus(
                    temp=TempCelsius(34.0),
                    min=TempCelsius(40.0),
                    max=TempCelsius(50.0),
                    panic=TempCelsius(60.0),
                    threshold=None,
                    is_panic=False,
                    is_threshold=False,
                )
            }
        )
        assert not t.is_alerting
예제 #2
0
def test_panic_on_empty_temp(report, sense_exec_shell_command):
    t = PanicTrigger(
        global_commands=AlertCommands(
            enter_cmd="printf '@%s' enter", leave_cmd="printf '@%s' leave"
        ),
        temp_commands={
            TempName("mobo"): AlertCommands(
                enter_cmd=None, leave_cmd="printf '@%s' mobo leave"
            )
        },
        report=report,
    )

    with sense_exec_shell_command(trigger) as (mock_exec_shell_command, get_stdout):
        with t:
            assert not t.is_alerting
            assert 0 == mock_exec_shell_command.call_count
            t.check({TempName("mobo"): None})
            assert t.is_alerting

            assert mock_exec_shell_command.call_args_list == [
                call("printf '@%s' enter")
            ]
            assert ["@enter"] == get_stdout()
            mock_exec_shell_command.reset_mock()

        assert not t.is_alerting
        assert mock_exec_shell_command.call_args_list == [
            call("printf '@%s' mobo leave"),
            call("printf '@%s' leave"),
        ]
        assert ["@mobo@leave", "@leave"] == get_stdout()
예제 #3
0
def test_threshold_on_empty_temp(report):
    t = ThresholdTrigger(
        global_commands=AlertCommands(enter_cmd=None, leave_cmd=None),
        temp_commands={TempName("mobo"): AlertCommands(enter_cmd=None, leave_cmd=None)},
        report=report,
    )
    with t:
        assert not t.is_alerting
        t.check({TempName("mobo"): None})
        assert not t.is_alerting
    assert not t.is_alerting
예제 #4
0
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
예제 #5
0
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
예제 #6
0
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),
            ],
        )
    }
예제 #7
0
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)
예제 #8
0
        mocked_triggers = manager.triggers  # type: MagicMock
        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


@pytest.mark.parametrize(
    "temps, mappings, expected_fan_speeds",
    [
        (
            {
                TempName("cpu"):
                TempStatus(
                    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(
예제 #9
0
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)],
            )
        },
    )
예제 #10
0
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)],
            )
        },
    )
예제 #11
0
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)],
            ),
        },
    )
예제 #12
0
def test_readonly_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

[readonly_fan: cpu]
pwm = /sys/class/hwmon/hwmon0/device/pwm1
fan_input = /sys/class/hwmon/hwmon0/device/fan1_input
"""
    parsed = parse_config(path_from_str(config), daemon_cli_config)
    assert parsed == ParsedConfig(
        arduino_connections={},
        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={},
        readonly_fans={
            ReadonlyFanName("cpu"): ReadonlyPWMFanNorm(
                fan_speed=LinuxFanSpeed(
                    FanInputDevice("/sys/class/hwmon/hwmon0/device/fan1_input")
                ),
                pwm_read=LinuxFanPWMRead(
                    PWMDevice("/sys/class/hwmon/hwmon0/device/pwm1")
                ),
            )
        },
        temps={
            TempName("mobo"): FilteredTemp(
                temp=FileTemp(
                    "/sys/class/hwmon/hwmon0/device/temp1_input",
                    min=None,
                    max=None,
                    panic=None,
                    threshold=None,
                ),
                filter=NullFilter(),
            )
        },
        mappings={},
    )