Skip to content

Commit 379b3df

Browse files
committed
Add one-shot mode and docker-given totals to stats API
- Add one-shot query option to container stats to skip the calculated CPU percentage window, returning docker-given totals directly. - Refactor container_stats() with translatable, structured Docker errors and timeout handling instead of generic Docker exceptions. - Translate low-level Docker errors into component-flavored errors (NotRunningError/StatsTimeoutError/UnknownError) for Home Assistant, Supervisor, and each plugin (Audio, Cli, CoreDNS, Multicast, Observer) as well as apps, mirroring the existing App.stats() pattern for consistent, informative API responses. - Add a shared api_return_stats() helper to remove duplicated stats handling logic across the 8 stats API endpoints. - DockerContainerNotFoundError now inherits from DockerNotFound.
1 parent f753e2c commit 379b3df

27 files changed

Lines changed: 739 additions & 235 deletions

supervisor/api/apps.py

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,9 @@
2323
ATTR_AUTH_API,
2424
ATTR_AUTO_UPDATE,
2525
ATTR_AVAILABLE,
26-
ATTR_BLK_READ,
27-
ATTR_BLK_WRITE,
2826
ATTR_BOOT,
2927
ATTR_BUILD,
3028
ATTR_CHANGELOG,
31-
ATTR_CPU_PERCENT,
3229
ATTR_DESCRIPTON,
3330
ATTR_DETACHED,
3431
ATTR_DEVICES,
@@ -61,14 +58,10 @@
6158
ATTR_LOGO,
6259
ATTR_LONG_DESCRIPTION,
6360
ATTR_MACHINE,
64-
ATTR_MEMORY_LIMIT,
65-
ATTR_MEMORY_PERCENT,
66-
ATTR_MEMORY_USAGE,
6761
ATTR_NAME,
6862
ATTR_NETWORK,
6963
ATTR_NETWORK_DESCRIPTION,
70-
ATTR_NETWORK_RX,
71-
ATTR_NETWORK_TX,
64+
ATTR_ONE_SHOT,
7265
ATTR_OPTIONS,
7366
ATTR_PRIVILEGED,
7467
ATTR_PROTECTED,
@@ -115,7 +108,7 @@
115108
)
116109
from ..validate import docker_ports
117110
from .const import ATTR_BOOT_CONFIG, ATTR_REMOVE_CONFIG, ATTR_SIGNED
118-
from .utils import api_process, api_validate, json_loads
111+
from .utils import api_process, api_return_stats, api_validate, json_loads
119112

120113
_LOGGER: logging.Logger = logging.getLogger(__name__)
121114

@@ -456,18 +449,10 @@ async def stats(self, request: web.Request) -> dict[str, Any]:
456449
"""Return resource information."""
457450
app = self.get_app_for_request(request)
458451

459-
stats: DockerStats = await app.stats()
452+
one_shot = ATTR_ONE_SHOT in request.query
453+
stats: DockerStats = await app.stats(one_shot=one_shot)
460454

461-
return {
462-
ATTR_CPU_PERCENT: stats.cpu_percent,
463-
ATTR_MEMORY_USAGE: stats.memory_usage,
464-
ATTR_MEMORY_LIMIT: stats.memory_limit,
465-
ATTR_MEMORY_PERCENT: stats.memory_percent,
466-
ATTR_NETWORK_RX: stats.network_rx,
467-
ATTR_NETWORK_TX: stats.network_tx,
468-
ATTR_BLK_READ: stats.blk_read,
469-
ATTR_BLK_WRITE: stats.blk_write,
470-
}
455+
return api_return_stats(stats)
471456

472457
@api_process
473458
async def uninstall(self, request: web.Request) -> None:

supervisor/api/audio.py

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,12 @@
1313
ATTR_ACTIVE,
1414
ATTR_APPLICATION,
1515
ATTR_AUDIO,
16-
ATTR_BLK_READ,
17-
ATTR_BLK_WRITE,
1816
ATTR_CARD,
19-
ATTR_CPU_PERCENT,
2017
ATTR_HOST,
2118
ATTR_INDEX,
2219
ATTR_INPUT,
23-
ATTR_MEMORY_LIMIT,
24-
ATTR_MEMORY_PERCENT,
25-
ATTR_MEMORY_USAGE,
2620
ATTR_NAME,
27-
ATTR_NETWORK_RX,
28-
ATTR_NETWORK_TX,
21+
ATTR_ONE_SHOT,
2922
ATTR_OUTPUT,
3023
ATTR_UPDATE_AVAILABLE,
3124
ATTR_VERSION,
@@ -36,7 +29,7 @@
3629
from ..exceptions import APIError
3730
from ..host.sound import StreamType
3831
from ..validate import version_tag
39-
from .utils import api_process, api_validate
32+
from .utils import api_process, api_return_stats, api_validate
4033

4134
_LOGGER: logging.Logger = logging.getLogger(__name__)
4235

@@ -88,18 +81,10 @@ async def info(self, request: web.Request) -> dict[str, Any]:
8881
@api_process
8982
async def stats(self, request: web.Request) -> dict[str, Any]:
9083
"""Return resource information."""
91-
stats = await self.sys_plugins.audio.stats()
84+
one_shot = ATTR_ONE_SHOT in request.query
85+
stats = await self.sys_plugins.audio.stats(one_shot=one_shot)
9286

93-
return {
94-
ATTR_CPU_PERCENT: stats.cpu_percent,
95-
ATTR_MEMORY_USAGE: stats.memory_usage,
96-
ATTR_MEMORY_LIMIT: stats.memory_limit,
97-
ATTR_MEMORY_PERCENT: stats.memory_percent,
98-
ATTR_NETWORK_RX: stats.network_rx,
99-
ATTR_NETWORK_TX: stats.network_tx,
100-
ATTR_BLK_READ: stats.blk_read,
101-
ATTR_BLK_WRITE: stats.blk_write,
102-
}
87+
return api_return_stats(stats)
10388

10489
@api_process
10590
async def update(self, request: web.Request) -> None:

supervisor/api/cli.py

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,14 @@
88
import voluptuous as vol
99

1010
from ..const import (
11-
ATTR_BLK_READ,
12-
ATTR_BLK_WRITE,
13-
ATTR_CPU_PERCENT,
14-
ATTR_MEMORY_LIMIT,
15-
ATTR_MEMORY_PERCENT,
16-
ATTR_MEMORY_USAGE,
17-
ATTR_NETWORK_RX,
18-
ATTR_NETWORK_TX,
11+
ATTR_ONE_SHOT,
1912
ATTR_UPDATE_AVAILABLE,
2013
ATTR_VERSION,
2114
ATTR_VERSION_LATEST,
2215
)
2316
from ..coresys import CoreSysAttributes
2417
from ..validate import version_tag
25-
from .utils import api_process, api_validate
18+
from .utils import api_process, api_return_stats, api_validate
2619

2720
_LOGGER: logging.Logger = logging.getLogger(__name__)
2821

@@ -44,18 +37,10 @@ async def info(self, request: web.Request) -> dict[str, Any]:
4437
@api_process
4538
async def stats(self, request: web.Request) -> dict[str, Any]:
4639
"""Return resource information."""
47-
stats = await self.sys_plugins.cli.stats()
40+
one_shot = ATTR_ONE_SHOT in request.query
41+
stats = await self.sys_plugins.cli.stats(one_shot=one_shot)
4842

49-
return {
50-
ATTR_CPU_PERCENT: stats.cpu_percent,
51-
ATTR_MEMORY_USAGE: stats.memory_usage,
52-
ATTR_MEMORY_LIMIT: stats.memory_limit,
53-
ATTR_MEMORY_PERCENT: stats.memory_percent,
54-
ATTR_NETWORK_RX: stats.network_rx,
55-
ATTR_NETWORK_TX: stats.network_tx,
56-
ATTR_BLK_READ: stats.blk_read,
57-
ATTR_BLK_WRITE: stats.blk_write,
58-
}
43+
return api_return_stats(stats)
5944

6045
@api_process
6146
async def update(self, request: web.Request) -> None:

supervisor/api/dns.py

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,9 @@
99
import voluptuous as vol
1010

1111
from ..const import (
12-
ATTR_BLK_READ,
13-
ATTR_BLK_WRITE,
14-
ATTR_CPU_PERCENT,
1512
ATTR_HOST,
1613
ATTR_LOCALS,
17-
ATTR_MEMORY_LIMIT,
18-
ATTR_MEMORY_PERCENT,
19-
ATTR_MEMORY_USAGE,
20-
ATTR_NETWORK_RX,
21-
ATTR_NETWORK_TX,
14+
ATTR_ONE_SHOT,
2215
ATTR_SERVERS,
2316
ATTR_UPDATE_AVAILABLE,
2417
ATTR_VERSION,
@@ -28,7 +21,7 @@
2821
from ..exceptions import APIError
2922
from ..validate import dns_server_list, version_tag
3023
from .const import ATTR_FALLBACK, ATTR_LLMNR, ATTR_MDNS
31-
from .utils import api_process, api_validate
24+
from .utils import api_process, api_return_stats, api_validate
3225

3326
_LOGGER: logging.Logger = logging.getLogger(__name__)
3427

@@ -83,18 +76,10 @@ async def options(self, request: web.Request) -> None:
8376
@api_process
8477
async def stats(self, request: web.Request) -> dict[str, Any]:
8578
"""Return resource information."""
86-
stats = await self.sys_plugins.dns.stats()
79+
one_shot = ATTR_ONE_SHOT in request.query
80+
stats = await self.sys_plugins.dns.stats(one_shot=one_shot)
8781

88-
return {
89-
ATTR_CPU_PERCENT: stats.cpu_percent,
90-
ATTR_MEMORY_USAGE: stats.memory_usage,
91-
ATTR_MEMORY_LIMIT: stats.memory_limit,
92-
ATTR_MEMORY_PERCENT: stats.memory_percent,
93-
ATTR_NETWORK_RX: stats.network_rx,
94-
ATTR_NETWORK_TX: stats.network_tx,
95-
ATTR_BLK_READ: stats.blk_read,
96-
ATTR_BLK_WRITE: stats.blk_write,
97-
}
82+
return api_return_stats(stats)
9883

9984
@api_process
10085
async def update(self, request: web.Request) -> None:

supervisor/api/homeassistant.py

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,13 @@
1414
ATTR_AUDIO_OUTPUT,
1515
ATTR_BACKUP,
1616
ATTR_BACKUPS_EXCLUDE_DATABASE,
17-
ATTR_BLK_READ,
18-
ATTR_BLK_WRITE,
1917
ATTR_BOOT,
20-
ATTR_CPU_PERCENT,
2118
ATTR_DUPLICATE_LOG_FILE,
2219
ATTR_IMAGE,
2320
ATTR_IP_ADDRESS,
2421
ATTR_JOB_ID,
2522
ATTR_MACHINE,
26-
ATTR_MEMORY_LIMIT,
27-
ATTR_MEMORY_PERCENT,
28-
ATTR_MEMORY_USAGE,
29-
ATTR_NETWORK_RX,
30-
ATTR_NETWORK_TX,
23+
ATTR_ONE_SHOT,
3124
ATTR_PORT,
3225
ATTR_REFRESH_TOKEN,
3326
ATTR_SSL,
@@ -40,7 +33,7 @@
4033
from ..exceptions import APIDBMigrationInProgress, APIError
4134
from ..validate import docker_image, network_port, version_tag
4235
from .const import ATTR_BACKGROUND, ATTR_FORCE, ATTR_SAFE_MODE
43-
from .utils import api_process, api_validate, background_task
36+
from .utils import api_process, api_return_stats, api_validate, background_task
4437

4538
_LOGGER: logging.Logger = logging.getLogger(__name__)
4639

@@ -162,20 +155,10 @@ async def options(self, request: web.Request) -> None:
162155
@api_process
163156
async def stats(self, request: web.Request) -> dict[str, Any]:
164157
"""Return resource information."""
165-
stats = await self.sys_homeassistant.core.stats()
166-
if not stats:
167-
raise APIError("No stats available")
158+
one_shot = ATTR_ONE_SHOT in request.query
159+
stats = await self.sys_homeassistant.core.stats(one_shot=one_shot)
168160

169-
return {
170-
ATTR_CPU_PERCENT: stats.cpu_percent,
171-
ATTR_MEMORY_USAGE: stats.memory_usage,
172-
ATTR_MEMORY_LIMIT: stats.memory_limit,
173-
ATTR_MEMORY_PERCENT: stats.memory_percent,
174-
ATTR_NETWORK_RX: stats.network_rx,
175-
ATTR_NETWORK_TX: stats.network_tx,
176-
ATTR_BLK_READ: stats.blk_read,
177-
ATTR_BLK_WRITE: stats.blk_write,
178-
}
161+
return api_return_stats(stats)
179162

180163
@api_process
181164
async def update(self, request: web.Request) -> dict[str, str] | None:

supervisor/api/multicast.py

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,15 @@
99
import voluptuous as vol
1010

1111
from ..const import (
12-
ATTR_BLK_READ,
13-
ATTR_BLK_WRITE,
14-
ATTR_CPU_PERCENT,
15-
ATTR_MEMORY_LIMIT,
16-
ATTR_MEMORY_PERCENT,
17-
ATTR_MEMORY_USAGE,
18-
ATTR_NETWORK_RX,
19-
ATTR_NETWORK_TX,
12+
ATTR_ONE_SHOT,
2013
ATTR_UPDATE_AVAILABLE,
2114
ATTR_VERSION,
2215
ATTR_VERSION_LATEST,
2316
)
2417
from ..coresys import CoreSysAttributes
2518
from ..exceptions import APIError
2619
from ..validate import version_tag
27-
from .utils import api_process, api_validate
20+
from .utils import api_process, api_return_stats, api_validate
2821

2922
_LOGGER: logging.Logger = logging.getLogger(__name__)
3023

@@ -46,18 +39,10 @@ async def info(self, request: web.Request) -> dict[str, Any]:
4639
@api_process
4740
async def stats(self, request: web.Request) -> dict[str, Any]:
4841
"""Return resource information."""
49-
stats = await self.sys_plugins.multicast.stats()
42+
one_shot = ATTR_ONE_SHOT in request.query
43+
stats = await self.sys_plugins.multicast.stats(one_shot=one_shot)
5044

51-
return {
52-
ATTR_CPU_PERCENT: stats.cpu_percent,
53-
ATTR_MEMORY_USAGE: stats.memory_usage,
54-
ATTR_MEMORY_LIMIT: stats.memory_limit,
55-
ATTR_MEMORY_PERCENT: stats.memory_percent,
56-
ATTR_NETWORK_RX: stats.network_rx,
57-
ATTR_NETWORK_TX: stats.network_tx,
58-
ATTR_BLK_READ: stats.blk_read,
59-
ATTR_BLK_WRITE: stats.blk_write,
60-
}
45+
return api_return_stats(stats)
6146

6247
@api_process
6348
async def update(self, request: web.Request) -> None:

supervisor/api/observer.py

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,15 @@
88
import voluptuous as vol
99

1010
from ..const import (
11-
ATTR_BLK_READ,
12-
ATTR_BLK_WRITE,
13-
ATTR_CPU_PERCENT,
1411
ATTR_HOST,
15-
ATTR_MEMORY_LIMIT,
16-
ATTR_MEMORY_PERCENT,
17-
ATTR_MEMORY_USAGE,
18-
ATTR_NETWORK_RX,
19-
ATTR_NETWORK_TX,
12+
ATTR_ONE_SHOT,
2013
ATTR_UPDATE_AVAILABLE,
2114
ATTR_VERSION,
2215
ATTR_VERSION_LATEST,
2316
)
2417
from ..coresys import CoreSysAttributes
2518
from ..validate import version_tag
26-
from .utils import api_process, api_validate
19+
from .utils import api_process, api_return_stats, api_validate
2720

2821
_LOGGER: logging.Logger = logging.getLogger(__name__)
2922

@@ -46,18 +39,10 @@ async def info(self, request: web.Request) -> dict[str, Any]:
4639
@api_process
4740
async def stats(self, request: web.Request) -> dict[str, Any]:
4841
"""Return resource information."""
49-
stats = await self.sys_plugins.observer.stats()
42+
one_shot = ATTR_ONE_SHOT in request.query
43+
stats = await self.sys_plugins.observer.stats(one_shot=one_shot)
5044

51-
return {
52-
ATTR_CPU_PERCENT: stats.cpu_percent,
53-
ATTR_MEMORY_USAGE: stats.memory_usage,
54-
ATTR_MEMORY_LIMIT: stats.memory_limit,
55-
ATTR_MEMORY_PERCENT: stats.memory_percent,
56-
ATTR_NETWORK_RX: stats.network_rx,
57-
ATTR_NETWORK_TX: stats.network_tx,
58-
ATTR_BLK_READ: stats.blk_read,
59-
ATTR_BLK_WRITE: stats.blk_write,
60-
}
45+
return api_return_stats(stats)
6146

6247
@api_process
6348
async def update(self, request: web.Request) -> None:

0 commit comments

Comments
 (0)