Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion homeassistant/components/portainer/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@
PortainerConnectionError,
PortainerTimeoutError,
)
from pyportainer.models.docker import DockerContainer, DockerContainerStats
from pyportainer.models.docker import (
DockerContainer,
DockerContainerStats,
DockerSystemDF,
)
from pyportainer.models.docker_inspect import DockerInfo, DockerVersion
from pyportainer.models.portainer import Endpoint

Expand Down Expand Up @@ -43,6 +47,7 @@ class PortainerCoordinatorData:
containers: dict[str, PortainerContainerData]
docker_version: DockerVersion
docker_info: DockerInfo
docker_system_df: DockerSystemDF


@dataclass(slots=True)
Expand Down Expand Up @@ -146,6 +151,7 @@ async def _async_update_data(self) -> dict[int, PortainerCoordinatorData]:
containers = await self.portainer.get_containers(endpoint.id)
docker_version = await self.portainer.docker_version(endpoint.id)
docker_info = await self.portainer.docker_info(endpoint.id)
docker_system_df = await self.portainer.docker_system_df(endpoint.id)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As this integration grows more and more, we may consider not downloading all the data every time, but only the data that is needed.

systemmonitor implements such a pattern:

self._initial_update: bool = True
self.update_subscribers: dict[tuple[str, str], set[str]] = (
self.set_subscribers_tuples(arguments)
)
def set_subscribers_tuples(
self, arguments: list[str]
) -> dict[tuple[str, str], set[str]]:
"""Set tuples in subscribers dictionary."""
_disk_defaults: dict[tuple[str, str], set[str]] = {}
for argument in arguments:
_disk_defaults[("disks", argument)] = set()
return {
**_disk_defaults,
("addresses", ""): set(),
("battery", ""): set(),
("boot", ""): set(),
("cpu_percent", ""): set(),
("fan_speed", ""): set(),
("io_counters", ""): set(),
("load", ""): set(),
("memory", ""): set(),
("processes", ""): set(),
("swap", ""): set(),
("temperatures", ""): set(),
}

_data = await self.hass.async_add_executor_job(self.update_data)
load: tuple = (None, None, None)
if self.update_subscribers[("load", "")] or self._initial_update:
load = os.getloadavg()
_LOGGER.debug("Load: %s", load)
cpu_percent: float | None = None
if self.update_subscribers[("cpu_percent", "")] or self._initial_update:
cpu_percent = self._psutil.cpu_percent(interval=None)
_LOGGER.debug("cpu_percent: %s", cpu_percent)

async def async_added_to_hass(self) -> None:
"""When added to hass."""
self.coordinator.update_subscribers[
self.entity_description.add_to_update(self)
].add(self.entity_id)
return await super().async_added_to_hass()
async def async_will_remove_from_hass(self) -> None:
"""When removed from hass."""
self.coordinator.update_subscribers[
self.entity_description.add_to_update(self)
].remove(self.entity_id)
return await super().async_will_remove_from_hass()


container_map: dict[str, PortainerContainerData] = {}

Expand Down Expand Up @@ -210,6 +216,7 @@ async def _async_update_data(self) -> dict[int, PortainerCoordinatorData]:
containers=container_map,
docker_version=docker_version,
docker_info=docker_info,
docker_system_df=docker_system_df,
)

self._async_add_remove_endpoints(mapped_endpoints)
Expand Down
15 changes: 15 additions & 0 deletions homeassistant/components/portainer/icons.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
"architecture": {
"default": "mdi:cpu-64-bit"
},
"container_disk_usage_reclaimable": {
"default": "mdi:file-restore"
},
"container_disk_usage_total_size": {
"default": "mdi:harddisk"
},
"containers_count": {
"default": "mdi:database"
},
Expand All @@ -31,6 +37,12 @@
"image": {
"default": "mdi:docker"
},
"image_disk_usage_reclaimable": {
"default": "mdi:file-restore"
},
"image_disk_usage_total_size": {
"default": "mdi:harddisk"
},
"images_count": {
"default": "mdi:image-multiple"
},
Expand All @@ -54,6 +66,9 @@
},
"operating_system_version": {
"default": "mdi:alpha-v-box"
},
"volume_disk_usage_total_size": {
"default": "mdi:harddisk"
}
},
"switch": {
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/portainer/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
"integration_type": "hub",
"iot_class": "local_polling",
"quality_scale": "bronze",
"requirements": ["pyportainer==1.0.19"]
"requirements": ["pyportainer==1.0.21"]
}
50 changes: 50 additions & 0 deletions homeassistant/components/portainer/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,56 @@ class PortainerEndpointSensorEntityDescription(SensorEntityDescription):
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
),
PortainerEndpointSensorEntityDescription(
key="container_disk_usage_reclaimable",
translation_key="container_disk_usage_reclaimable",
value_fn=lambda data: data.docker_system_df.container_disk_usage.reclaimable,
device_class=SensorDeviceClass.DATA_SIZE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfInformation.BYTES,
suggested_unit_of_measurement=UnitOfInformation.MEBIBYTES,
entity_category=EntityCategory.DIAGNOSTIC,
),
PortainerEndpointSensorEntityDescription(
key="container_disk_usage_total_size",
translation_key="container_disk_usage_total_size",
value_fn=lambda data: data.docker_system_df.container_disk_usage.total_size,
device_class=SensorDeviceClass.DATA_SIZE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfInformation.BYTES,
suggested_unit_of_measurement=UnitOfInformation.MEBIBYTES,
entity_category=EntityCategory.DIAGNOSTIC,
),
PortainerEndpointSensorEntityDescription(
key="image_disk_usage_reclaimable",
translation_key="image_disk_usage_reclaimable",
value_fn=lambda data: data.docker_system_df.image_disk_usage.reclaimable,
device_class=SensorDeviceClass.DATA_SIZE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfInformation.BYTES,
suggested_unit_of_measurement=UnitOfInformation.MEBIBYTES,
entity_category=EntityCategory.DIAGNOSTIC,
),
PortainerEndpointSensorEntityDescription(
key="image_disk_usage_total_size",
translation_key="image_disk_usage_total_size",
value_fn=lambda data: data.docker_system_df.image_disk_usage.total_size,
device_class=SensorDeviceClass.DATA_SIZE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfInformation.BYTES,
suggested_unit_of_measurement=UnitOfInformation.MEBIBYTES,
entity_category=EntityCategory.DIAGNOSTIC,
),
PortainerEndpointSensorEntityDescription(
key="volume_disk_usage_total",
translation_key="volume_disk_usage_total_size",
value_fn=lambda data: data.docker_system_df.volume_disk_usage.total_size,
device_class=SensorDeviceClass.DATA_SIZE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfInformation.BYTES,
suggested_unit_of_measurement=UnitOfInformation.MEBIBYTES,
entity_category=EntityCategory.DIAGNOSTIC,
),
)


Expand Down
15 changes: 15 additions & 0 deletions homeassistant/components/portainer/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@
"architecture": {
"name": "Architecture"
},
"container_disk_usage_reclaimable": {
"name": "Container disk usage reclaimable"
},
"container_disk_usage_total_size": {
"name": "Container disk usage total size"
},
"containers_count": {
"name": "Container count"
},
Expand All @@ -92,6 +98,12 @@
"image": {
"name": "Image"
},
"image_disk_usage_reclaimable": {
"name": "Image disk usage reclaimable"
},
"image_disk_usage_total_size": {
"name": "Image disk usage total size"
},
"images_count": {
"name": "Image count"
},
Expand All @@ -115,6 +127,9 @@
},
"operating_system_version": {
"name": "Operating system version"
},
"volume_disk_usage_total_size": {
"name": "Volume disk usage total size"
}
},
"switch": {
Expand Down
2 changes: 1 addition & 1 deletion requirements_all.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion requirements_test_all.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 8 additions & 1 deletion tests/components/portainer/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
from collections.abc import Generator
from unittest.mock import AsyncMock, patch

from pyportainer.models.docker import DockerContainer, DockerContainerStats
from pyportainer.models.docker import (
DockerContainer,
DockerContainerStats,
DockerSystemDF,
)
from pyportainer.models.docker_inspect import DockerInfo, DockerVersion
from pyportainer.models.portainer import Endpoint
import pytest
Expand Down Expand Up @@ -63,6 +67,9 @@ def mock_portainer_client() -> Generator[AsyncMock]:
client.container_stats.return_value = DockerContainerStats.from_dict(
load_json_value_fixture("container_stats.json", DOMAIN)
)
client.docker_system_df.return_value = DockerSystemDF.from_dict(
load_json_value_fixture("docker_system_df.json", DOMAIN)
)

client.restart_container = AsyncMock(return_value=None)

Expand Down
30 changes: 30 additions & 0 deletions tests/components/portainer/fixtures/docker_system_df.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"ImagesDiskUsage": {
"ActiveCount": 1,
"TotalCount": 4,
"Reclaimable": 12345678,
"TotalSize": 98765432,
"Items": []
},
"ContainersDiskUsage": {
"ActiveCount": 1,
"TotalCount": 4,
"Reclaimable": 12345678,
"TotalSize": 98765432,
"Items": []
},
"VolumesDiskUsage": {
"ActiveCount": 1,
"TotalCount": 4,
"Reclaimable": 12345678,
"TotalSize": 98765432,
"Items": []
},
"BuildCacheDiskUsage": {
"ActiveCount": 1,
"TotalCount": 4,
"Reclaimable": 12345678,
"TotalSize": 98765432,
"Items": []
}
}
Loading