mirror of
https://github.com/home-assistant/core.git
synced 2025-08-31 02:11:32 +02:00
Add python3 open file descriptor sensor to Systemmonitor
This commit is contained in:
@@ -118,6 +118,17 @@ def get_ip_address(
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_python3_num_fds(
|
||||||
|
entity: SystemMonitorSensor,
|
||||||
|
) -> int | None:
|
||||||
|
"""Return num_fds from python3 process."""
|
||||||
|
processes = entity.coordinator.data.processes
|
||||||
|
for proc in processes:
|
||||||
|
if proc.name() == "python3":
|
||||||
|
return proc.num_fds()
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, kw_only=True)
|
@dataclass(frozen=True, kw_only=True)
|
||||||
class SysMonitorSensorEntityDescription(SensorEntityDescription):
|
class SysMonitorSensorEntityDescription(SensorEntityDescription):
|
||||||
"""Describes System Monitor sensor entities."""
|
"""Describes System Monitor sensor entities."""
|
||||||
@@ -369,6 +380,13 @@ SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription] = {
|
|||||||
value_fn=lambda entity: entity.coordinator.data.swap.percent,
|
value_fn=lambda entity: entity.coordinator.data.swap.percent,
|
||||||
add_to_update=lambda entity: ("swap", ""),
|
add_to_update=lambda entity: ("swap", ""),
|
||||||
),
|
),
|
||||||
|
"python3_num_fds": SysMonitorSensorEntityDescription(
|
||||||
|
key="python3_num_fds",
|
||||||
|
translation_key="python3_num_fds",
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
value_fn=get_python3_num_fds,
|
||||||
|
add_to_update=lambda entity: ("processes", ""),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -566,6 +584,18 @@ async def async_setup_entry(
|
|||||||
is_enabled,
|
is_enabled,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
if _type == "python3_num_fds":
|
||||||
|
argument = ""
|
||||||
|
loaded_resources.add(slugify(f"{_type}_{argument}"))
|
||||||
|
entities.append(
|
||||||
|
SystemMonitorSensor(
|
||||||
|
coordinator,
|
||||||
|
sensor_description,
|
||||||
|
entry.entry_id,
|
||||||
|
argument,
|
||||||
|
True, # Enabled by default
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# Ensure legacy imported disk_* resources are loaded if they are not part
|
# Ensure legacy imported disk_* resources are loaded if they are not part
|
||||||
# of mount points automatically discovered
|
# of mount points automatically discovered
|
||||||
|
@@ -100,6 +100,9 @@
|
|||||||
},
|
},
|
||||||
"swap_use_percent": {
|
"swap_use_percent": {
|
||||||
"name": "Swap usage"
|
"name": "Swap usage"
|
||||||
|
},
|
||||||
|
"python3_num_fds": {
|
||||||
|
"name": "Python3 open file descriptors"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -27,12 +27,13 @@ def mock_sys_platform() -> Generator[None]:
|
|||||||
class MockProcess(Process):
|
class MockProcess(Process):
|
||||||
"""Mock a Process class."""
|
"""Mock a Process class."""
|
||||||
|
|
||||||
def __init__(self, name: str, ex: bool = False) -> None:
|
def __init__(self, name: str, ex: bool = False, num_fds: int = 3) -> None:
|
||||||
"""Initialize the process."""
|
"""Initialize the process."""
|
||||||
super().__init__(1)
|
super().__init__(1)
|
||||||
self._name = name
|
self._name = name
|
||||||
self._ex = ex
|
self._ex = ex
|
||||||
self._create_time = 1708700400
|
self._create_time = 1708700400
|
||||||
|
self._num_fds = num_fds
|
||||||
|
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Return a name."""
|
"""Return a name."""
|
||||||
@@ -40,6 +41,10 @@ class MockProcess(Process):
|
|||||||
raise NoSuchProcess(1, self._name)
|
raise NoSuchProcess(1, self._name)
|
||||||
return self._name
|
return self._name
|
||||||
|
|
||||||
|
def num_fds(self):
|
||||||
|
"""Return open file descriptors."""
|
||||||
|
return self._num_fds
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_setup_entry() -> Generator[AsyncMock]:
|
def mock_setup_entry() -> Generator[AsyncMock]:
|
||||||
|
@@ -114,16 +114,6 @@
|
|||||||
# name: test_sensor[System Monitor Last boot - state]
|
# name: test_sensor[System Monitor Last boot - state]
|
||||||
'2024-02-24T15:00:00+00:00'
|
'2024-02-24T15:00:00+00:00'
|
||||||
# ---
|
# ---
|
||||||
# name: test_sensor[System Monitor Load (15 min) - attributes]
|
|
||||||
ReadOnlyDict({
|
|
||||||
'friendly_name': 'System Monitor Load (15 min)',
|
|
||||||
'icon': 'mdi:cpu-64-bit',
|
|
||||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
|
||||||
})
|
|
||||||
# ---
|
|
||||||
# name: test_sensor[System Monitor Load (15 min) - state]
|
|
||||||
'3'
|
|
||||||
# ---
|
|
||||||
# name: test_sensor[System Monitor Load (1 min) - attributes]
|
# name: test_sensor[System Monitor Load (1 min) - attributes]
|
||||||
ReadOnlyDict({
|
ReadOnlyDict({
|
||||||
'friendly_name': 'System Monitor Load (1 min)',
|
'friendly_name': 'System Monitor Load (1 min)',
|
||||||
@@ -134,6 +124,16 @@
|
|||||||
# name: test_sensor[System Monitor Load (1 min) - state]
|
# name: test_sensor[System Monitor Load (1 min) - state]
|
||||||
'1'
|
'1'
|
||||||
# ---
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Load (15 min) - attributes]
|
||||||
|
ReadOnlyDict({
|
||||||
|
'friendly_name': 'System Monitor Load (15 min)',
|
||||||
|
'icon': 'mdi:cpu-64-bit',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Load (15 min) - state]
|
||||||
|
'3'
|
||||||
|
# ---
|
||||||
# name: test_sensor[System Monitor Load (5 min) - attributes]
|
# name: test_sensor[System Monitor Load (5 min) - attributes]
|
||||||
ReadOnlyDict({
|
ReadOnlyDict({
|
||||||
'friendly_name': 'System Monitor Load (5 min)',
|
'friendly_name': 'System Monitor Load (5 min)',
|
||||||
@@ -322,6 +322,15 @@
|
|||||||
# name: test_sensor[System Monitor Processor use - state]
|
# name: test_sensor[System Monitor Processor use - state]
|
||||||
'10'
|
'10'
|
||||||
# ---
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Python3 open file descriptors - attributes]
|
||||||
|
ReadOnlyDict({
|
||||||
|
'friendly_name': 'System Monitor Python3 open file descriptors',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Python3 open file descriptors - state]
|
||||||
|
'3'
|
||||||
|
# ---
|
||||||
# name: test_sensor[System Monitor Swap free - attributes]
|
# name: test_sensor[System Monitor Swap free - attributes]
|
||||||
ReadOnlyDict({
|
ReadOnlyDict({
|
||||||
'device_class': 'data_size',
|
'device_class': 'data_size',
|
||||||
|
@@ -18,6 +18,8 @@ from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN
|
|||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import entity_registry as er
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
|
||||||
|
from .conftest import MockProcess
|
||||||
|
|
||||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||||
|
|
||||||
|
|
||||||
@@ -453,7 +455,7 @@ async def test_remove_obsolete_entities(
|
|||||||
mock_added_config_entry.entry_id
|
mock_added_config_entry.entry_id
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
== 37
|
== 38
|
||||||
)
|
)
|
||||||
|
|
||||||
entity_registry.async_update_entity(
|
entity_registry.async_update_entity(
|
||||||
@@ -494,7 +496,7 @@ async def test_remove_obsolete_entities(
|
|||||||
mock_added_config_entry.entry_id
|
mock_added_config_entry.entry_id
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
== 38
|
== 39
|
||||||
)
|
)
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
@@ -544,3 +546,56 @@ async def test_no_duplicate_disk_entities(
|
|||||||
assert disk_sensor.state == "60.0"
|
assert disk_sensor.state == "60.0"
|
||||||
|
|
||||||
assert "Platform systemmonitor does not generate unique IDs." not in caplog.text
|
assert "Platform systemmonitor does not generate unique IDs." not in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||||
|
async def test_python3_num_fds(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_psutil: Mock,
|
||||||
|
mock_os: Mock,
|
||||||
|
entity_registry: er.EntityRegistry,
|
||||||
|
snapshot: SnapshotAssertion,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
|
) -> None:
|
||||||
|
"""Test python3 open file descriptors sensor."""
|
||||||
|
mock_config_entry = MockConfigEntry(
|
||||||
|
title="System Monitor",
|
||||||
|
domain=DOMAIN,
|
||||||
|
data={},
|
||||||
|
options={
|
||||||
|
"resources": [
|
||||||
|
"disk_use_percent_/",
|
||||||
|
"disk_use_percent_/home/notexist/",
|
||||||
|
"memory_free_",
|
||||||
|
"network_out_eth0",
|
||||||
|
"process_python3",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
mock_config_entry.add_to_hass(hass)
|
||||||
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
num_fds_sensor = hass.states.get(
|
||||||
|
"sensor.system_monitor_python3_open_file_descriptors"
|
||||||
|
)
|
||||||
|
assert num_fds_sensor is not None
|
||||||
|
assert num_fds_sensor.state == "3"
|
||||||
|
assert num_fds_sensor.attributes == {
|
||||||
|
"state_class": "measurement",
|
||||||
|
"friendly_name": "System Monitor Python3 open file descriptors",
|
||||||
|
}
|
||||||
|
|
||||||
|
_process = MockProcess("python3", num_fds=5)
|
||||||
|
assert _process.num_fds() == 5
|
||||||
|
mock_psutil.process_iter.return_value = [_process]
|
||||||
|
|
||||||
|
freezer.tick(timedelta(minutes=1))
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done(wait_background_tasks=True)
|
||||||
|
|
||||||
|
num_fds_sensor = hass.states.get(
|
||||||
|
"sensor.system_monitor_python3_open_file_descriptors"
|
||||||
|
)
|
||||||
|
assert num_fds_sensor is not None
|
||||||
|
assert num_fds_sensor.state == "5"
|
||||||
|
Reference in New Issue
Block a user