Skip to content

Commit a47540f

Browse files
committed
fixes, updates, rename, functional tests
1 parent 1338b8a commit a47540f

15 files changed

Lines changed: 684 additions & 510 deletions

docs/THP_TO_SYSFS_DESIGN_PROPOSAL.md

Lines changed: 0 additions & 87 deletions
This file was deleted.

nodescraper/plugins/inband/thp/__init__.py renamed to nodescraper/plugins/inband/sys_settings/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
# SOFTWARE.
2424
#
2525
###############################################################################
26-
from .analyzer_args import ThpAnalyzerArgs
27-
from .thp_plugin import ThpPlugin
26+
from .analyzer_args import SysfsCheck, SysSettingsAnalyzerArgs
27+
from .sys_settings_plugin import SysSettingsPlugin
2828

29-
__all__ = ["ThpPlugin", "ThpAnalyzerArgs"]
29+
__all__ = ["SysSettingsPlugin", "SysSettingsAnalyzerArgs", "SysfsCheck"]
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
###############################################################################
2+
#
3+
# MIT License
4+
#
5+
# Copyright (c) 2026 Advanced Micro Devices, Inc.
6+
#
7+
# Permission is hereby granted, free of charge, to any person obtaining a copy
8+
# of this software and associated documentation files (the "Software"), to deal
9+
# in the Software without restriction, including without limitation the rights
10+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
# copies of the Software, and to permit persons to whom the Software is
12+
# furnished to do so, subject to the following conditions:
13+
#
14+
# The above copyright notice and this permission notice shall be included in all
15+
# copies or substantial portions of the Software.
16+
#
17+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23+
# SOFTWARE.
24+
#
25+
###############################################################################
26+
from typing import Optional
27+
28+
from pydantic import BaseModel
29+
30+
from nodescraper.models import AnalyzerArgs
31+
32+
33+
class SysfsCheck(BaseModel):
34+
"""One sysfs check: path to read, acceptable values, and display name.
35+
36+
If expected is an empty list, the check is treated as passing (no constraint).
37+
"""
38+
39+
path: str
40+
expected: list[str]
41+
name: str
42+
43+
44+
class SysSettingsAnalyzerArgs(AnalyzerArgs):
45+
"""Sysfs settings for analysis via a list of checks (path, expected values, name).
46+
47+
The path in each check is the sysfs path to read; the collector uses these paths
48+
when collection_args is derived from analysis_args (e.g. by the plugin).
49+
"""
50+
51+
checks: Optional[list[SysfsCheck]] = None
52+
53+
def paths_to_collect(self) -> list[str]:
54+
"""Return the unique sysfs paths from checks, for use by the collector.
55+
56+
Returns:
57+
List of unique path strings from self.checks, preserving order of first occurrence.
58+
"""
59+
if not self.checks:
60+
return []
61+
seen = set()
62+
out = []
63+
for c in self.checks:
64+
p = c.path.rstrip("/")
65+
if p not in seen:
66+
seen.add(p)
67+
out.append(c.path)
68+
return out
Lines changed: 32 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,32 @@
1-
###############################################################################
2-
#
3-
# MIT License
4-
#
5-
# Copyright (c) 2026 Advanced Micro Devices, Inc.
6-
#
7-
# Permission is hereby granted, free of charge, to any person obtaining a copy
8-
# of this software and associated documentation files (the "Software"), to deal
9-
# in the Software without restriction, including without limitation the rights
10-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11-
# copies of the Software, and to permit persons to whom the Software is
12-
# furnished to do so, subject to the following conditions:
13-
#
14-
# The above copyright notice and this permission notice shall be included in all
15-
# copies or substantial portions of the Software.
16-
#
17-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23-
# SOFTWARE.
24-
#
25-
###############################################################################
26-
from nodescraper.base import InBandDataPlugin
27-
28-
from .analyzer_args import ThpAnalyzerArgs
29-
from .thp_analyzer import ThpAnalyzer
30-
from .thp_collector import ThpCollector
31-
from .thpdata import ThpDataModel
32-
33-
34-
class ThpPlugin(InBandDataPlugin[ThpDataModel, None, ThpAnalyzerArgs]):
35-
"""Plugin to collect and optionally analyze transparent huge pages (THP) settings."""
36-
37-
DATA_MODEL = ThpDataModel
38-
COLLECTOR = ThpCollector
39-
ANALYZER = ThpAnalyzer
40-
ANALYZER_ARGS = ThpAnalyzerArgs
1+
###############################################################################
2+
#
3+
# MIT License
4+
#
5+
# Copyright (c) 2026 Advanced Micro Devices, Inc.
6+
#
7+
# Permission is hereby granted, free of charge, to any person obtaining a copy
8+
# of this software and associated documentation files (the "Software"), to deal
9+
# in the Software without restriction, including without limitation the rights
10+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
# copies of the Software, and to permit persons to whom the Software is
12+
# furnished to do so, subject to the following conditions:
13+
#
14+
# The above copyright notice and this permission notice shall be included in all
15+
# copies or substantial portions of the Software.
16+
#
17+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23+
# SOFTWARE.
24+
#
25+
###############################################################################
26+
from pydantic import BaseModel
27+
28+
29+
class SysSettingsCollectorArgs(BaseModel):
30+
"""Collection args for SysSettingsCollector: list of sysfs paths to read."""
31+
32+
paths: list[str] = []
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
###############################################################################
2+
#
3+
# MIT License
4+
#
5+
# Copyright (c) 2026 Advanced Micro Devices, Inc.
6+
#
7+
# Permission is hereby granted, free of charge, to any person obtaining a copy
8+
# of this software and associated documentation files (the "Software"), to deal
9+
# in the Software without restriction, including without limitation the rights
10+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
# copies of the Software, and to permit persons to whom the Software is
12+
# furnished to do so, subject to the following conditions:
13+
#
14+
# The above copyright notice and this permission notice shall be included in all
15+
# copies or substantial portions of the Software.
16+
#
17+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23+
# SOFTWARE.
24+
#
25+
###############################################################################
26+
from typing import Optional, cast
27+
28+
from nodescraper.enums import EventCategory, EventPriority, ExecutionStatus
29+
from nodescraper.interfaces import DataAnalyzer
30+
from nodescraper.models import TaskResult
31+
32+
from .analyzer_args import SysSettingsAnalyzerArgs
33+
from .sys_settings_data import SysSettingsDataModel
34+
35+
36+
def _get_actual_for_path(data: SysSettingsDataModel, path: str) -> Optional[str]:
37+
"""Return the actual value from the data model for the given sysfs path.
38+
39+
Args:
40+
data: Collected sysfs readings (path -> value).
41+
path: Sysfs path (with or without trailing slash).
42+
43+
Returns:
44+
Normalized value for that path, or None if not present.
45+
"""
46+
value = data.readings.get(path) or data.readings.get(path.rstrip("/"))
47+
return (value or "").strip().lower() if value is not None else None
48+
49+
50+
class SysSettingsAnalyzer(DataAnalyzer[SysSettingsDataModel, SysSettingsAnalyzerArgs]):
51+
"""Check sysfs settings against expected values from the checks list."""
52+
53+
DATA_MODEL = SysSettingsDataModel
54+
55+
def analyze_data(
56+
self, data: SysSettingsDataModel, args: Optional[SysSettingsAnalyzerArgs] = None
57+
) -> TaskResult:
58+
"""Compare sysfs data to expected settings from args.checks.
59+
60+
Args:
61+
data: Collected sysfs readings to check.
62+
args: Analyzer args with checks (path, expected, name). If None or no checks, returns OK.
63+
64+
Returns:
65+
TaskResult with status OK if all checks pass, ERROR if any mismatch or missing path.
66+
"""
67+
mismatches = {}
68+
69+
if not args or not args.checks:
70+
self.result.status = ExecutionStatus.OK
71+
self.result.message = "No checks configured."
72+
return self.result
73+
74+
for check in args.checks:
75+
actual = _get_actual_for_path(data, check.path)
76+
if actual is None:
77+
mismatches[check.name] = {
78+
"path": check.path,
79+
"expected": check.expected,
80+
"actual": None,
81+
"reason": "path not collected by this plugin",
82+
}
83+
continue
84+
85+
if not check.expected:
86+
continue
87+
expected_normalized = [e.strip().lower() for e in check.expected]
88+
if actual not in expected_normalized:
89+
raw = data.readings.get(check.path) or data.readings.get(check.path.rstrip("/"))
90+
mismatches[check.name] = {
91+
"path": check.path,
92+
"expected": check.expected,
93+
"actual": raw,
94+
}
95+
96+
if mismatches:
97+
self.result.status = ExecutionStatus.ERROR
98+
parts = []
99+
for name, info in mismatches.items():
100+
path = info.get("path", "")
101+
expected = info.get("expected")
102+
actual = cast(Optional[str], info.get("actual"))
103+
reason = info.get("reason")
104+
if reason:
105+
part = f"{name} ({path})"
106+
else:
107+
part = f"{name} ({path}): expected one of {expected}, actual {repr(actual)}"
108+
parts.append(part)
109+
self.result.message = "Sysfs mismatch: " + "; ".join(parts)
110+
self._log_event(
111+
category=EventCategory.OS,
112+
description="Sysfs mismatch detected",
113+
data=mismatches,
114+
priority=EventPriority.ERROR,
115+
console_log=True,
116+
)
117+
else:
118+
self._log_event(
119+
category=EventCategory.OS,
120+
description="Sysfs settings match expected",
121+
priority=EventPriority.INFO,
122+
console_log=True,
123+
)
124+
self.result.status = ExecutionStatus.OK
125+
self.result.message = "Sysfs settings as expected."
126+
127+
return self.result

0 commit comments

Comments
 (0)