2626from typing import Optional
2727
2828from nodescraper .base import InBandDataCollector
29+ from nodescraper .connection .inband import TextFileArtifact
2930from nodescraper .connection .inband .inband import CommandArtifact
3031from nodescraper .enums import EventCategory , EventPriority , ExecutionStatus , OSFamily
3132from nodescraper .models import TaskResult
3233
33- from .kernel_module_data import KernelModuleDataModel
34+ from .kernel_module_data import KernelModuleDataModel , ModuleInfo , ModuleParameter
3435
3536
3637class 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" ,
0 commit comments