Skip to content

Commit f5e6097

Browse files
Merge pull request #82 from amd/alex_mod
KernelModulePlugin update
2 parents dc4723f + 902b816 commit f5e6097

File tree

7 files changed

+575
-5
lines changed

7 files changed

+575
-5
lines changed

nodescraper/plugins/inband/kernel_module/kernel_module_collector.py

Lines changed: 106 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,12 @@
2626
from typing import Optional
2727

2828
from nodescraper.base import InBandDataCollector
29+
from nodescraper.connection.inband import TextFileArtifact
2930
from nodescraper.connection.inband.inband import CommandArtifact
3031
from nodescraper.enums import EventCategory, EventPriority, ExecutionStatus, OSFamily
3132
from nodescraper.models import TaskResult
3233

33-
from .kernel_module_data import KernelModuleDataModel
34+
from .kernel_module_data import KernelModuleDataModel, ModuleInfo, ModuleParameter
3435

3536

3637
class KernelModuleCollector(InBandDataCollector[KernelModuleDataModel, None]):
@@ -39,6 +40,7 @@ class KernelModuleCollector(InBandDataCollector[KernelModuleDataModel, None]):
3940
DATA_MODEL = KernelModuleDataModel
4041
CMD_WINDOWS = "wmic os get Version /Value"
4142
CMD = "cat /proc/modules"
43+
CMD_MODINFO_AMDGPU = "modinfo amdgpu"
4244

4345
def parse_proc_modules(self, output: dict) -> dict:
4446
"""Parse command output and return dict of modules
@@ -60,6 +62,77 @@ def parse_proc_modules(self, output: dict) -> dict:
6062
}
6163
return modules
6264

65+
def _parse_modinfo(self, output: str) -> Optional[ModuleInfo]:
66+
"""Parse modinfo command output into structured ModuleInfo
67+
68+
Args:
69+
output (str): modinfo command output
70+
71+
Returns:
72+
Optional[ModuleInfo]: parsed module information or None if parsing fails
73+
"""
74+
if not output or not output.strip():
75+
return None
76+
77+
module_info = ModuleInfo()
78+
79+
for line in output.splitlines():
80+
line = line.strip()
81+
if not line or ":" not in line:
82+
continue
83+
84+
field, _, value = line.partition(":")
85+
field = field.strip()
86+
value = value.strip()
87+
88+
if field == "filename":
89+
module_info.filename = value
90+
elif field == "version":
91+
module_info.version = value
92+
elif field == "license":
93+
module_info.license = value
94+
elif field == "description":
95+
module_info.description = value
96+
elif field == "author":
97+
module_info.author.append(value)
98+
elif field == "firmware":
99+
module_info.firmware.append(value)
100+
elif field == "srcversion":
101+
module_info.srcversion = value
102+
elif field == "depends":
103+
if value:
104+
module_info.depends = [dep.strip() for dep in value.split(",") if dep.strip()]
105+
elif field == "name":
106+
module_info.name = value
107+
elif field == "vermagic":
108+
module_info.vermagic = value
109+
elif field == "sig_id":
110+
module_info.sig_id = value
111+
elif field == "signer":
112+
module_info.signer = value
113+
elif field == "sig_key":
114+
module_info.sig_key = value
115+
elif field == "sig_hashalgo":
116+
module_info.sig_hashalgo = value
117+
elif field == "parm":
118+
param_name, param_desc = value.split(":", 1) if ":" in value else (value, "")
119+
param_name = param_name.strip()
120+
param_desc = param_desc.strip()
121+
122+
param_type = None
123+
if param_desc and "(" in param_desc and ")" in param_desc:
124+
type_start = param_desc.rfind("(")
125+
type_end = param_desc.rfind(")")
126+
if type_start < type_end:
127+
param_type = param_desc[type_start + 1 : type_end].strip()
128+
param_desc = param_desc[:type_start].strip()
129+
130+
module_info.parm.append(
131+
ModuleParameter(name=param_name, type=param_type, description=param_desc)
132+
)
133+
134+
return module_info
135+
63136
def get_module_parameters(self, module_name: str) -> dict:
64137
"""Fetches parameter names and values for a given kernel module using _run_sut_cmd
65138
@@ -143,8 +216,39 @@ def collect_data(self, args=None) -> tuple[TaskResult, Optional[KernelModuleData
143216
else:
144217
kernel_modules = self.collect_all_module_info()
145218

219+
amdgpu_modinfo = None
220+
if self.system_info.os_family != OSFamily.WINDOWS:
221+
# Collect and parse modinfo amdgpu output
222+
modinfo_res = self._run_sut_cmd(self.CMD_MODINFO_AMDGPU)
223+
if modinfo_res.exit_code == 0 and modinfo_res.stdout:
224+
amdgpu_modinfo = self._parse_modinfo(modinfo_res.stdout)
225+
if amdgpu_modinfo:
226+
self.result.artifacts.append(
227+
TextFileArtifact(filename="modinfo_amdgpu.txt", contents=modinfo_res.stdout)
228+
)
229+
else:
230+
self._log_event(
231+
category=EventCategory.OS,
232+
description="Could not parse modinfo amdgpu output",
233+
data={"command": modinfo_res.command},
234+
priority=EventPriority.WARNING,
235+
)
236+
else:
237+
self._log_event(
238+
category=EventCategory.OS,
239+
description="Could not collect modinfo amdgpu output",
240+
data={
241+
"command": modinfo_res.command,
242+
"exit_code": modinfo_res.exit_code,
243+
"stderr": modinfo_res.stderr,
244+
},
245+
priority=EventPriority.WARNING,
246+
)
247+
146248
if kernel_modules:
147-
km_data = KernelModuleDataModel(kernel_modules=kernel_modules)
249+
km_data = KernelModuleDataModel(
250+
kernel_modules=kernel_modules, amdgpu_modinfo=amdgpu_modinfo
251+
)
148252
self._log_event(
149253
category="KERNEL_READ",
150254
description="Kernel modules read",

nodescraper/plugins/inband/kernel_module/kernel_module_data.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,37 @@
2424
#
2525
###############################################################################
2626

27+
from typing import Optional
28+
29+
from pydantic import BaseModel, Field
30+
2731
from nodescraper.models import DataModel
2832

2933

34+
class ModuleParameter(BaseModel):
35+
name: str
36+
type: Optional[str] = None
37+
description: Optional[str] = None
38+
39+
40+
class ModuleInfo(BaseModel):
41+
filename: Optional[str] = None
42+
version: Optional[str] = None
43+
license: Optional[str] = None
44+
description: Optional[str] = None
45+
author: list[str] = Field(default_factory=list)
46+
firmware: list[str] = Field(default_factory=list)
47+
srcversion: Optional[str] = None
48+
depends: list[str] = Field(default_factory=list)
49+
name: Optional[str] = None
50+
vermagic: Optional[str] = None
51+
sig_id: Optional[str] = None
52+
signer: Optional[str] = None
53+
sig_key: Optional[str] = None
54+
sig_hashalgo: Optional[str] = None
55+
parm: list[ModuleParameter] = Field(default_factory=list)
56+
57+
3058
class KernelModuleDataModel(DataModel):
3159
kernel_modules: dict
60+
amdgpu_modinfo: Optional[ModuleInfo] = None

test/unit/plugin/test_amdsmi_collector.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,28 @@
1+
###############################################################################
2+
#
3+
# MIT License
4+
#
5+
# Copyright (c) 2025 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+
###############################################################################
126
import json
227
from typing import Any
328
from unittest.mock import MagicMock

test/unit/plugin/test_kernel_module_analyzer.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,28 @@
1+
###############################################################################
2+
#
3+
# MIT License
4+
#
5+
# Copyright (c) 2025 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+
###############################################################################
126
import pytest
227

328
from nodescraper.enums.eventcategory import EventCategory

0 commit comments

Comments
 (0)