Skip to content

Commit 48d770d

Browse files
19142 FIX Fix incorrect memory usage on ArubaOS-CX switches
SUP-29474 Change-Id: I0aaaa8786f271894706ad2f0c13c800a04ed6162
1 parent 7c7a8d9 commit 48d770d

3 files changed

Lines changed: 112 additions & 3 deletions

File tree

.werks/19142.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
[//]: # (werk v3)
2+
# Fix incorrect memory usage on ArubaOS-CX switches
3+
4+
key | value
5+
---------- | ---
6+
date | 2026-07-23T12:00:45.337909+00:00
7+
version | 3.0.0b1
8+
class | fix
9+
edition | community
10+
component | checks
11+
level | 1
12+
compatible | yes
13+
14+
Previously, the _Memory_ service could report an incorrect memory usage on
15+
ArubaOS-CX / HPE Aruba Networking switches, where the summary looked like this:
16+
17+
_Total (RAM + Swap): -7.54% - -573 MiB of 7.42 GiB RAM_
18+
19+
This happened because these switches already exclude cached memory from the
20+
reported usage, while Checkmk subtracted it once more. This has been fixed.
21+
These switches now report the correct memory usage automatically, without any
22+
configuration.

cmk/plugins/hr/agent_based/hr_mem.py

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,37 @@ def to_bytes(units: str) -> int:
9595
return parsed
9696

9797

98-
def aggregate_meminfo(parsed: PreParsed) -> memory.SectionMemUsed:
98+
# The "Physical memory" hrStorageUsed value is normally interpreted as
99+
# INCLUDING the reclaimable page cache (classic net-snmp / UCD, which reports
100+
# "MemTotal - MemFree"). Checkmk therefore subtracts the "Cached memory" entry
101+
# to obtain the real usage.
102+
#
103+
# ArubaOS-CX switches (HPE Aruba Networking) instead report the physical "used"
104+
# value with the cache already excluded (modern net-snmp "MemAvailable"
105+
# semantics), while still exposing a large "Cached memory" entry. There the
106+
# cached memory can exceed the reported "used" value, and subtracting it would
107+
# understate the usage or even push it below zero. The two cases cannot be told
108+
# apart from the HOST-RESOURCES-MIB values alone (RFC 2790 defines no
109+
# relationship between storage entries), so these devices are recognized by
110+
# their sysObjectID, which lives under the Aruba (enterprise 47196) wired switch
111+
# product arc, e.g. ...47196.4.1.1.1.100 (6300M) or ...4.1.1.1.309 (6200F).
112+
_CACHE_EXCLUDED_FROM_USED_SYS_OBJECT_IDS: tuple[str, ...] = (
113+
".1.3.6.1.4.1.47196.4.1.1.1.", # ArubaOS-CX switches (SUP-29474)
114+
)
115+
116+
117+
def _reports_cache_excluded_from_used(system_info: StringTable) -> bool:
118+
"""Whether the device's SNMP agent reports the physical memory usage with the
119+
page cache already excluded (so the cache must not be subtracted again)."""
120+
if not system_info or not system_info[0]:
121+
return False
122+
sys_object_id = system_info[0][0]
123+
return any(
124+
sys_object_id.startswith(prefix) for prefix in _CACHE_EXCLUDED_FROM_USED_SYS_OBJECT_IDS
125+
)
126+
127+
128+
def aggregate_meminfo(parsed: PreParsed, *, subtract_cache: bool = True) -> memory.SectionMemUsed:
99129
"""return a meminfo dict as expected by check_memory from mem.include"""
100130
meminfo: memory.SectionMemUsed = {"Cached": 0}
101131

@@ -115,7 +145,7 @@ def aggregate_meminfo(parsed: PreParsed) -> memory.SectionMemUsed:
115145
meminfo.setdefault("SwapTotal", size)
116146
meminfo.setdefault("SwapFree", (size - used))
117147

118-
if descr == "cached memory" and used > 0:
148+
if subtract_cache and descr == "cached memory" and used > 0:
119149
# Account for cached memory (this works at least for systems using
120150
# the UCD snmpd (such as Linux based applicances)
121151
# some devices report negative used cache values...
@@ -132,7 +162,10 @@ def parse_hr_mem(string_table: Sequence[StringTable]) -> memory.SectionMemUsed |
132162
if not any(size > 0 for _, size, __ in pre_parsed.get("RAM", [])):
133163
return None
134164

135-
section = aggregate_meminfo(pre_parsed)
165+
system_info = string_table[1] if len(string_table) > 1 else []
166+
subtract_cache = not _reports_cache_excluded_from_used(system_info)
167+
168+
section = aggregate_meminfo(pre_parsed, subtract_cache=subtract_cache)
136169
return section if section.get("MemTotal") else None
137170

138171

@@ -151,6 +184,12 @@ def parse_hr_mem(string_table: Sequence[StringTable]) -> memory.SectionMemUsed |
151184
"6", # hrStorageUsed
152185
],
153186
),
187+
SNMPTree(
188+
base=".1.3.6.1.2.1.1",
189+
oids=[
190+
"2.0", # sysObjectID
191+
],
192+
),
154193
],
155194
detect=ucd_hr_detection.USE_HR_MEM,
156195
)

tests/unit/cmk/plugins/hr/agent_based/test_hr_mem.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,54 @@ def test_hr_mem(
159159
assert hr_mem.pre_parse_hr_mem(string_table) == expected_parsed_data
160160

161161

162+
# hrStorageUsed of the "Physical memory" entry (1602976) is smaller than the
163+
# "Cached memory" entry (2190028) - i.e. the device reports "used" with the
164+
# cache already excluded. Data taken from a real ArubaOS-CX switch (SUP-29474).
165+
_ARUBA_HR_STORAGE: StringTable = [
166+
[".1.3.6.1.2.1.25.2.1.2", "Physical memory", "1024", "7784284", "1602976"],
167+
[".1.3.6.1.2.1.25.2.1.1", "Cached memory", "1024", "2190028", "2190028"],
168+
]
169+
170+
171+
# Real ArubaOS-CX sysObjectIDs from SUP-29474: JL658A 6300M and JL728B 6200F.
172+
_ARUBA_SYS_OBJECT_IDS = [".1.3.6.1.4.1.47196.4.1.1.1.100", ".1.3.6.1.4.1.47196.4.1.1.1.309"]
173+
174+
175+
@pytest.mark.parametrize("sys_object_id", _ARUBA_SYS_OBJECT_IDS)
176+
def test_reports_cache_excluded_from_used_aruba(sys_object_id: str) -> None:
177+
assert hr_mem._reports_cache_excluded_from_used([[sys_object_id]])
178+
179+
180+
def test_reports_cache_excluded_from_used_other_devices() -> None:
181+
# net-snmp / UCD device -> "used" includes the cache -> must not match.
182+
assert not hr_mem._reports_cache_excluded_from_used([[".1.3.6.1.4.1.8072.3.2.10"]])
183+
assert not hr_mem._reports_cache_excluded_from_used([])
184+
assert not hr_mem._reports_cache_excluded_from_used([[]])
185+
assert not hr_mem._reports_cache_excluded_from_used([[""]])
186+
187+
188+
def test_parse_hr_mem_subtracts_cache_by_default() -> None:
189+
# Unknown device -> classic net-snmp interpretation -> cache is subtracted.
190+
section = hr_mem.parse_hr_mem([_ARUBA_HR_STORAGE, [[".1.3.6.1.4.1.8072.3.2.10"]]])
191+
assert section is not None
192+
assert section["Cached"] == 2190028 * 1024
193+
194+
195+
@pytest.mark.parametrize("sys_object_id", _ARUBA_SYS_OBJECT_IDS)
196+
def test_parse_hr_mem_keeps_cache_for_cache_excluded_devices(sys_object_id: str) -> None:
197+
# Recognized ArubaOS-CX device -> cache must not be subtracted.
198+
section = hr_mem.parse_hr_mem([_ARUBA_HR_STORAGE, [[sys_object_id]]])
199+
assert section is not None
200+
assert section["Cached"] == 0
201+
202+
203+
def test_parse_hr_mem_without_system_info_subtracts_cache() -> None:
204+
# No sysObjectID fetched (e.g. old cached data) -> keep classic behavior.
205+
section = hr_mem.parse_hr_mem([_ARUBA_HR_STORAGE])
206+
assert section is not None
207+
assert section["Cached"] == 2190028 * 1024
208+
209+
162210
if __name__ == "__main__":
163211
# Please keep these lines - they make TDD easy and have no effect on normal test runs.
164212
# Just run this file from your IDE and dive into the code.

0 commit comments

Comments
 (0)