Skip to content

Commit

Permalink
Add/improve support for Glances v4 container & network format and imp…
Browse files Browse the repository at this point in the history
…rove 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
  • Loading branch information
wittypluck authored Jun 9, 2024
1 parent e2b5572 commit 8dfaaf9
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 32 deletions.
37 changes: 25 additions & 12 deletions glances_api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def __init__(
self.password = password
self.verify_ssl = verify_ssl
self.httpx_client = httpx_client
self.version = version

async def get_data(self, endpoint: str) -> None:
"""Retrieve the data."""
Expand Down Expand Up @@ -154,27 +155,39 @@ async def get_ha_sensor_data(self) -> dict[str, Any]:
if networks := self.data.get("network"):
sensor_data["network"] = {}
for network in networks:
time_since_update = network["time_since_update"]
# New name of network sensors in Glances v4
rx = network.get("bytes_recv_rate_per_sec")
tx = network.get("bytes_sent_rate_per_sec")
# Compatibility with Glances v3
if rx is None and (rx_bytes := network.get("rx")) is not None:
rx = round(rx_bytes / time_since_update)
if tx is None and (tx_bytes := network.get("tx")) is not None:
tx = round(tx_bytes / time_since_update)
rx = tx = None
if self.version <= 3:
time_since_update = network["time_since_update"]
if (rx_bytes := network.get("rx")) is not None:
rx = round(rx_bytes / time_since_update)
if (tx_bytes := network.get("tx")) is not None:
tx = round(tx_bytes / time_since_update)
else:
# New network sensors in Glances v4
rx = network.get("bytes_recv_rate_per_sec")
tx = network.get("bytes_sent_rate_per_sec")
sensor_data["network"][network["interface_name"]] = {
"is_up": network.get("is_up"),
"rx": rx,
"tx": tx,
"speed": round(network["speed"] / 1024**3, 1),
}
data = self.data.get("dockers") or self.data.get("containers")
if data and (containers_data := data.get("containers")):
containers_data = None
if self.version <= 3:
# Glances v3 and earlier provide a dict, with containers inside a list in this dict
# Key is "dockers" in 3.3 and before, and "containers" in 3.4
data = self.data.get("dockers") or self.data.get("containers")
containers_data = data.get("containers") if data else None
else:
# Glances v4 provides a list of containers
containers_data = self.data.get("containers")
if containers_data:
active_containers = [
container
for container in containers_data
if container["Status"] == "running"
# "status" since Glance v4, "Status" in v3 and earlier
if container.get("status") == "running"
or container.get("Status") == "running"
]
sensor_data["docker"] = {"docker_active": len(active_containers)}
cpu_use = 0.0
Expand Down
93 changes: 73 additions & 20 deletions tests/test_responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,21 +251,6 @@
"time_since_update": 1.55433297157288,
"tx": 0,
},
{
"bytes_sent": 1070106,
"bytes_recv": 163781155,
"speed": 1048576000,
"key": "interface_name",
"interface_name": "eth0_v4",
"bytes_all": 164851261,
"time_since_update": 25.680001497268677,
"bytes_recv_gauge": 5939087689,
"bytes_recv_rate_per_sec": 6377770.0,
"bytes_sent_gauge": 82538934,
"bytes_sent_rate_per_sec": 41670.0,
"bytes_all_gauge": 6021626623,
"bytes_all_rate_per_sec": 6419441.0,
},
],
"sensors": [
{
Expand Down Expand Up @@ -307,7 +292,6 @@
"eth0": {"is_up": True, "rx": 3953, "tx": 5995, "speed": 9.8},
"tunl0": {"is_up": False, "rx": 0.0, "tx": 0.0, "speed": 0.0},
"sit0": {"is_up": False, "rx": 0.0, "tx": 0.0, "speed": 0.0},
"eth0_v4": {"is_up": None, "rx": 6377770.0, "speed": 1.0, "tx": 41670.0},
},
"docker": {"docker_active": 2, "docker_cpu_use": 77.2, "docker_memory_use": 1149.6},
"uptime": "3 days, 10:25:20",
Expand All @@ -332,6 +316,69 @@
},
}

RESPONSE_V4: dict[str, Any] = {
"containers": [
{
"key": "name",
"name": "container1",
"id": "1234",
"status": "running",
"created": "2024-06-07T09:21:57.688106748Z",
"command": "./command",
"image": ["image1/latest"],
"io": {},
"memory": {},
"network": {},
"cpu": {"total": 0.37484029484029485},
"cpu_percent": 0.37484029484029485,
"memory_usage": None,
"uptime": "28 secs",
"engine": "docker",
},
{
"key": "name",
"name": "container2",
"id": "5678",
"status": "running",
"created": "2023-08-23T21:54:50.745112185Z",
"command": "./command",
"image": ["image2:latest"],
"io": {"cumulative_ior": 36413440, "cumulative_iow": 0},
"memory": {},
"network": {"cumulative_rx": 12012442, "cumulative_tx": 45791653},
"cpu": {"total": 0.0},
"cpu_percent": 0.0,
"memory_usage": None,
"uptime": "3 days",
"engine": "docker",
},
],
"network": [
{
"bytes_sent": 1070106,
"bytes_recv": 163781155,
"speed": 1048576000,
"key": "interface_name",
"interface_name": "eth0",
"bytes_all": 164851261,
"time_since_update": 25.680001497268677,
"bytes_recv_gauge": 5939087689,
"bytes_recv_rate_per_sec": 6377770.0,
"bytes_sent_gauge": 82538934,
"bytes_sent_rate_per_sec": 41670.0,
"bytes_all_gauge": 6021626623,
"bytes_all_rate_per_sec": 6419441.0,
},
],
}

HA_SENSOR_DATA_V4: dict[str, Any] = {
"docker": {"docker_active": 2, "docker_cpu_use": 0.4, "docker_memory_use": 0.0},
"network": {
"eth0": {"is_up": None, "rx": 6377770.0, "speed": 1.0, "tx": 41670.0},
},
}


@pytest.mark.asyncio
async def test_non_existing_endpoint(httpx_mock: HTTPXMock) -> None:
Expand Down Expand Up @@ -369,14 +416,20 @@ async def test_exisiting_endpoint(httpx_mock: HTTPXMock) -> None:


@pytest.mark.asyncio
async def test_ha_sensor_data(httpx_mock: HTTPXMock) -> None:
@pytest.mark.parametrize(
("version", "response", "expected"),
[(3, RESPONSE, HA_SENSOR_DATA), (4, RESPONSE_V4, HA_SENSOR_DATA_V4)],
)
async def test_ha_sensor_data(
httpx_mock: HTTPXMock, version: int, response: dict, expected: dict
) -> None:
"""Test the return value for ha sensors."""
httpx_mock.add_response(json=RESPONSE)
httpx_mock.add_response(json=response)

client = Glances()
client = Glances(version=version)
result = await client.get_ha_sensor_data()

assert result == HA_SENSOR_DATA
assert result == expected


@pytest.mark.asyncio
Expand Down

0 comments on commit 8dfaaf9

Please sign in to comment.