mirror of
https://github.com/nicolargo/glances.git
synced 2026-07-30 23:06:22 -04:00
Merge branch 'develop' into develop-v5
This commit is contained in:
@@ -79,6 +79,8 @@ class ArmGPU:
|
||||
self.device_folders = get_device_list(drm_root_folder)
|
||||
# State for delta-based proc% computation
|
||||
self._last_sample: dict[str, tuple[int, int]] = {}
|
||||
# Denominator for GPU mem% -- static system property, read once. See #3611.
|
||||
self._mem_capacity_bytes = get_mem_capacity_bytes(os.path.join(proc_root_folder, 'meminfo'))
|
||||
|
||||
def exit(self):
|
||||
"""Close ARM GPU class."""
|
||||
@@ -97,7 +99,7 @@ class ArmGPU:
|
||||
'key': 'gpu_id',
|
||||
'gpu_id': f'arm{index}',
|
||||
'name': get_device_name(driver),
|
||||
'mem': compute_mem_percent(snapshot),
|
||||
'mem': compute_mem_percent(snapshot, self._mem_capacity_bytes),
|
||||
'proc': self._compute_proc_percent(device, snapshot),
|
||||
'temperature': get_temperature(device),
|
||||
'fan_speed': None,
|
||||
@@ -367,12 +369,59 @@ def aggregate_fdinfo(
|
||||
return per_device
|
||||
|
||||
|
||||
def compute_mem_percent(snapshot: dict | None) -> int | None:
|
||||
"""Return memory usage in % from a per-device fdinfo snapshot."""
|
||||
def _parse_meminfo(text: str) -> dict[str, int]:
|
||||
"""Parse /proc/meminfo -> {field: bytes}. Kernel reports values in kB (KiB)."""
|
||||
fields: dict[str, int] = {}
|
||||
for line in text.splitlines():
|
||||
key, _, value = line.partition(':')
|
||||
parts = value.split()
|
||||
if not parts:
|
||||
continue
|
||||
try:
|
||||
fields[key.strip()] = int(parts[0]) * 1024
|
||||
except ValueError:
|
||||
continue
|
||||
return fields
|
||||
|
||||
|
||||
def get_mem_capacity_bytes(meminfo_path: str = '/proc/meminfo') -> int | None:
|
||||
"""Denominator for ARM/v3d GPU memory %, or None to keep legacy behaviour.
|
||||
|
||||
WHY NOT drm-total-memory: on Raspberry Pi 5 (BCM2712, DRM/V3D), the fdinfo
|
||||
field ``drm-total-memory`` reports the firmware CMA reservation (``gpu_mem``
|
||||
in config.txt, often 4 MiB headless) -- NOT a GPU capacity. The Pi 5 uses
|
||||
unified memory: v3d buffers are allocated on demand from system RAM through
|
||||
DRM/CMA, so used/drm-total pins near 93% while idle. Do NOT revert the
|
||||
denominator to drm-total-memory. See issue #3611.
|
||||
|
||||
Cascade:
|
||||
1. CmaTotal (the pool the DRM/CMA allocator draws from), if present and > 0
|
||||
2. MemTotal (kernel without CMA / GPU not on the CMA path; unified memory)
|
||||
3. None -> caller falls back to drm-total-memory (legacy, pre-#3611 output)
|
||||
"""
|
||||
text = read_file(meminfo_path)
|
||||
if text is None:
|
||||
return None
|
||||
fields = _parse_meminfo(text)
|
||||
if fields.get('CmaTotal', 0) > 0:
|
||||
return fields['CmaTotal']
|
||||
if fields.get('MemTotal', 0) > 0:
|
||||
return fields['MemTotal']
|
||||
return None
|
||||
|
||||
|
||||
def compute_mem_percent(snapshot: dict | None, capacity_bytes: int | None = None) -> int | None:
|
||||
"""Return memory usage in % from a per-device fdinfo snapshot.
|
||||
|
||||
``capacity_bytes`` (CmaTotal/MemTotal, see get_mem_capacity_bytes) is the
|
||||
denominator. When None (e.g. /proc/meminfo unreadable), fall back to the
|
||||
fdinfo ``drm-total-memory`` total -- the pre-#3611 behaviour, preserved
|
||||
byte-for-byte.
|
||||
"""
|
||||
if snapshot is None:
|
||||
return None
|
||||
total = snapshot.get('mem_total_bytes', 0)
|
||||
used = snapshot.get('mem_used_bytes', 0)
|
||||
total = capacity_bytes if capacity_bytes else snapshot.get('mem_total_bytes', 0)
|
||||
if total <= 0:
|
||||
return None
|
||||
return max(0, min(100, round(used / total * 100)))
|
||||
|
||||
4
tests-data/plugins/gpu/arm/proc/meminfo
Normal file
4
tests-data/plugins/gpu/arm/proc/meminfo
Normal file
@@ -0,0 +1,4 @@
|
||||
MemTotal: 8192000 kB
|
||||
MemFree: 512000 kB
|
||||
CmaTotal: 3072 kB
|
||||
CmaFree: 1024 kB
|
||||
@@ -19,6 +19,7 @@ from glances.plugins.gpu.cards.arm import (
|
||||
compute_mem_percent,
|
||||
get_device_list,
|
||||
get_device_name,
|
||||
get_mem_capacity_bytes,
|
||||
parse_fdinfo,
|
||||
)
|
||||
|
||||
@@ -111,6 +112,16 @@ class TestArmComputeHelpers:
|
||||
snapshot = {'mem_total_bytes': 100, 'mem_used_bytes': 500}
|
||||
assert compute_mem_percent(snapshot) == 100
|
||||
|
||||
def test_compute_mem_percent_capacity_denominator(self):
|
||||
# capacity_bytes (CmaTotal/MemTotal) overrides the fdinfo drm-total.
|
||||
snapshot = {'mem_total_bytes': 100, 'mem_used_bytes': 500}
|
||||
assert compute_mem_percent(snapshot, capacity_bytes=1000) == 50
|
||||
|
||||
def test_compute_mem_percent_capacity_clamped(self):
|
||||
# used > capacity -> v3d spilled past the CMA pool; clamp to 100.
|
||||
snapshot = {'mem_total_bytes': 100, 'mem_used_bytes': 500}
|
||||
assert compute_mem_percent(snapshot, capacity_bytes=100) == 100
|
||||
|
||||
def test_get_device_name_known(self):
|
||||
assert get_device_name('panthor') == 'Mali (Panthor)'
|
||||
assert get_device_name('msm') == 'Adreno (msm)'
|
||||
@@ -119,6 +130,38 @@ class TestArmComputeHelpers:
|
||||
assert get_device_name('unknown-driver-xyz') == 'ARM GPU'
|
||||
|
||||
|
||||
class TestArmMemCapacity:
|
||||
"""Denominator cascade for GPU mem% (issue #3611). Mocks /proc/meminfo."""
|
||||
|
||||
def _write_meminfo(self, tmp_path, content):
|
||||
path = tmp_path / 'meminfo'
|
||||
path.write_text(content)
|
||||
return str(path)
|
||||
|
||||
def test_cma_total_present(self, tmp_path):
|
||||
# CmaTotal > 0 -> denominator is CmaTotal (in bytes: kB * 1024).
|
||||
path = self._write_meminfo(
|
||||
tmp_path,
|
||||
"MemTotal: 8192000 kB\nCmaTotal: 262144 kB\n",
|
||||
)
|
||||
assert get_mem_capacity_bytes(path) == 262144 * 1024
|
||||
|
||||
def test_cma_total_zero_falls_back_to_mem_total(self, tmp_path):
|
||||
path = self._write_meminfo(
|
||||
tmp_path,
|
||||
"MemTotal: 8192000 kB\nCmaTotal: 0 kB\n",
|
||||
)
|
||||
assert get_mem_capacity_bytes(path) == 8192000 * 1024
|
||||
|
||||
def test_cma_total_absent_falls_back_to_mem_total(self, tmp_path):
|
||||
path = self._write_meminfo(tmp_path, "MemTotal: 8192000 kB\n")
|
||||
assert get_mem_capacity_bytes(path) == 8192000 * 1024
|
||||
|
||||
def test_meminfo_unreadable_returns_none(self, tmp_path):
|
||||
# Non-Linux / restricted container -> None -> caller keeps legacy path.
|
||||
assert get_mem_capacity_bytes(str(tmp_path / 'does-not-exist')) is None
|
||||
|
||||
|
||||
@pytest.mark.skipif(not LINUX, reason="ARM GPU backend is Linux-only")
|
||||
class TestArmBackendDiscovery:
|
||||
"""Discovery tests against the committed test fixtures."""
|
||||
@@ -149,7 +192,9 @@ class TestArmBackendDiscovery:
|
||||
assert stats[0]['fan_speed'] is None
|
||||
|
||||
def test_mem_aggregated_from_fdinfo(self, arm_backend):
|
||||
# Two clients: 1024 + 512 KiB used over 2048 + 1024 KiB total = 50%
|
||||
# Numerator: 1024 + 512 = 1536 KiB resident (from fdinfo).
|
||||
# Denominator: CmaTotal = 3072 KiB (from the proc/meminfo fixture, #3611).
|
||||
# -> 1536 / 3072 = 50%
|
||||
stats = arm_backend.get_device_stats()
|
||||
assert stats[0]['mem'] == 50
|
||||
|
||||
|
||||
Reference in New Issue
Block a user