Skip to content

Commit 8dfaaf9

Browse files
authored
Add/improve support for Glances v4 container & network format and improve v4 unit tests (#42)
* Add support for Glances v4 container format * Reformat with black * Code cleanup * Add unit tests for Glances v4 and more robust network sensor
1 parent e2b5572 commit 8dfaaf9

File tree

2 files changed

+98
-32
lines changed

2 files changed

+98
-32
lines changed

glances_api/__init__.py

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ def __init__(
4141
self.password = password
4242
self.verify_ssl = verify_ssl
4343
self.httpx_client = httpx_client
44+
self.version = version
4445

4546
async def get_data(self, endpoint: str) -> None:
4647
"""Retrieve the data."""
@@ -154,27 +155,39 @@ async def get_ha_sensor_data(self) -> dict[str, Any]:
154155
if networks := self.data.get("network"):
155156
sensor_data["network"] = {}
156157
for network in networks:
157-
time_since_update = network["time_since_update"]
158-
# New name of network sensors in Glances v4
159-
rx = network.get("bytes_recv_rate_per_sec")
160-
tx = network.get("bytes_sent_rate_per_sec")
161-
# Compatibility with Glances v3
162-
if rx is None and (rx_bytes := network.get("rx")) is not None:
163-
rx = round(rx_bytes / time_since_update)
164-
if tx is None and (tx_bytes := network.get("tx")) is not None:
165-
tx = round(tx_bytes / time_since_update)
158+
rx = tx = None
159+
if self.version <= 3:
160+
time_since_update = network["time_since_update"]
161+
if (rx_bytes := network.get("rx")) is not None:
162+
rx = round(rx_bytes / time_since_update)
163+
if (tx_bytes := network.get("tx")) is not None:
164+
tx = round(tx_bytes / time_since_update)
165+
else:
166+
# New network sensors in Glances v4
167+
rx = network.get("bytes_recv_rate_per_sec")
168+
tx = network.get("bytes_sent_rate_per_sec")
166169
sensor_data["network"][network["interface_name"]] = {
167170
"is_up": network.get("is_up"),
168171
"rx": rx,
169172
"tx": tx,
170173
"speed": round(network["speed"] / 1024**3, 1),
171174
}
172-
data = self.data.get("dockers") or self.data.get("containers")
173-
if data and (containers_data := data.get("containers")):
175+
containers_data = None
176+
if self.version <= 3:
177+
# Glances v3 and earlier provide a dict, with containers inside a list in this dict
178+
# Key is "dockers" in 3.3 and before, and "containers" in 3.4
179+
data = self.data.get("dockers") or self.data.get("containers")
180+
containers_data = data.get("containers") if data else None
181+
else:
182+
# Glances v4 provides a list of containers
183+
containers_data = self.data.get("containers")
184+
if containers_data:
174185
active_containers = [
175186
container
176187
for container in containers_data
177-
if container["Status"] == "running"
188+
# "status" since Glance v4, "Status" in v3 and earlier
189+
if container.get("status") == "running"
190+
or container.get("Status") == "running"
178191
]
179192
sensor_data["docker"] = {"docker_active": len(active_containers)}
180193
cpu_use = 0.0

tests/test_responses.py

Lines changed: 73 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -251,21 +251,6 @@
251251
"time_since_update": 1.55433297157288,
252252
"tx": 0,
253253
},
254-
{
255-
"bytes_sent": 1070106,
256-
"bytes_recv": 163781155,
257-
"speed": 1048576000,
258-
"key": "interface_name",
259-
"interface_name": "eth0_v4",
260-
"bytes_all": 164851261,
261-
"time_since_update": 25.680001497268677,
262-
"bytes_recv_gauge": 5939087689,
263-
"bytes_recv_rate_per_sec": 6377770.0,
264-
"bytes_sent_gauge": 82538934,
265-
"bytes_sent_rate_per_sec": 41670.0,
266-
"bytes_all_gauge": 6021626623,
267-
"bytes_all_rate_per_sec": 6419441.0,
268-
},
269254
],
270255
"sensors": [
271256
{
@@ -307,7 +292,6 @@
307292
"eth0": {"is_up": True, "rx": 3953, "tx": 5995, "speed": 9.8},
308293
"tunl0": {"is_up": False, "rx": 0.0, "tx": 0.0, "speed": 0.0},
309294
"sit0": {"is_up": False, "rx": 0.0, "tx": 0.0, "speed": 0.0},
310-
"eth0_v4": {"is_up": None, "rx": 6377770.0, "speed": 1.0, "tx": 41670.0},
311295
},
312296
"docker": {"docker_active": 2, "docker_cpu_use": 77.2, "docker_memory_use": 1149.6},
313297
"uptime": "3 days, 10:25:20",
@@ -332,6 +316,69 @@
332316
},
333317
}
334318

319+
RESPONSE_V4: dict[str, Any] = {
320+
"containers": [
321+
{
322+
"key": "name",
323+
"name": "container1",
324+
"id": "1234",
325+
"status": "running",
326+
"created": "2024-06-07T09:21:57.688106748Z",
327+
"command": "./command",
328+
"image": ["image1/latest"],
329+
"io": {},
330+
"memory": {},
331+
"network": {},
332+
"cpu": {"total": 0.37484029484029485},
333+
"cpu_percent": 0.37484029484029485,
334+
"memory_usage": None,
335+
"uptime": "28 secs",
336+
"engine": "docker",
337+
},
338+
{
339+
"key": "name",
340+
"name": "container2",
341+
"id": "5678",
342+
"status": "running",
343+
"created": "2023-08-23T21:54:50.745112185Z",
344+
"command": "./command",
345+
"image": ["image2:latest"],
346+
"io": {"cumulative_ior": 36413440, "cumulative_iow": 0},
347+
"memory": {},
348+
"network": {"cumulative_rx": 12012442, "cumulative_tx": 45791653},
349+
"cpu": {"total": 0.0},
350+
"cpu_percent": 0.0,
351+
"memory_usage": None,
352+
"uptime": "3 days",
353+
"engine": "docker",
354+
},
355+
],
356+
"network": [
357+
{
358+
"bytes_sent": 1070106,
359+
"bytes_recv": 163781155,
360+
"speed": 1048576000,
361+
"key": "interface_name",
362+
"interface_name": "eth0",
363+
"bytes_all": 164851261,
364+
"time_since_update": 25.680001497268677,
365+
"bytes_recv_gauge": 5939087689,
366+
"bytes_recv_rate_per_sec": 6377770.0,
367+
"bytes_sent_gauge": 82538934,
368+
"bytes_sent_rate_per_sec": 41670.0,
369+
"bytes_all_gauge": 6021626623,
370+
"bytes_all_rate_per_sec": 6419441.0,
371+
},
372+
],
373+
}
374+
375+
HA_SENSOR_DATA_V4: dict[str, Any] = {
376+
"docker": {"docker_active": 2, "docker_cpu_use": 0.4, "docker_memory_use": 0.0},
377+
"network": {
378+
"eth0": {"is_up": None, "rx": 6377770.0, "speed": 1.0, "tx": 41670.0},
379+
},
380+
}
381+
335382

336383
@pytest.mark.asyncio
337384
async def test_non_existing_endpoint(httpx_mock: HTTPXMock) -> None:
@@ -369,14 +416,20 @@ async def test_exisiting_endpoint(httpx_mock: HTTPXMock) -> None:
369416

370417

371418
@pytest.mark.asyncio
372-
async def test_ha_sensor_data(httpx_mock: HTTPXMock) -> None:
419+
@pytest.mark.parametrize(
420+
("version", "response", "expected"),
421+
[(3, RESPONSE, HA_SENSOR_DATA), (4, RESPONSE_V4, HA_SENSOR_DATA_V4)],
422+
)
423+
async def test_ha_sensor_data(
424+
httpx_mock: HTTPXMock, version: int, response: dict, expected: dict
425+
) -> None:
373426
"""Test the return value for ha sensors."""
374-
httpx_mock.add_response(json=RESPONSE)
427+
httpx_mock.add_response(json=response)
375428

376-
client = Glances()
429+
client = Glances(version=version)
377430
result = await client.get_ha_sensor_data()
378431

379-
assert result == HA_SENSOR_DATA
432+
assert result == expected
380433

381434

382435
@pytest.mark.asyncio

0 commit comments

Comments
 (0)