amd-node-scraper 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. amd_node_scraper-0.0.1.dist-info/LICENSE +21 -0
  2. amd_node_scraper-0.0.1.dist-info/METADATA +424 -0
  3. amd_node_scraper-0.0.1.dist-info/RECORD +197 -0
  4. amd_node_scraper-0.0.1.dist-info/WHEEL +5 -0
  5. amd_node_scraper-0.0.1.dist-info/entry_points.txt +2 -0
  6. amd_node_scraper-0.0.1.dist-info/top_level.txt +1 -0
  7. nodescraper/__init__.py +32 -0
  8. nodescraper/base/__init__.py +34 -0
  9. nodescraper/base/inbandcollectortask.py +118 -0
  10. nodescraper/base/inbanddataplugin.py +39 -0
  11. nodescraper/base/regexanalyzer.py +120 -0
  12. nodescraper/cli/__init__.py +29 -0
  13. nodescraper/cli/cli.py +511 -0
  14. nodescraper/cli/constants.py +27 -0
  15. nodescraper/cli/dynamicparserbuilder.py +171 -0
  16. nodescraper/cli/helper.py +517 -0
  17. nodescraper/cli/inputargtypes.py +129 -0
  18. nodescraper/configbuilder.py +123 -0
  19. nodescraper/configregistry.py +66 -0
  20. nodescraper/configs/node_status.json +19 -0
  21. nodescraper/connection/__init__.py +25 -0
  22. nodescraper/connection/inband/__init__.py +46 -0
  23. nodescraper/connection/inband/inband.py +171 -0
  24. nodescraper/connection/inband/inbandlocal.py +93 -0
  25. nodescraper/connection/inband/inbandmanager.py +151 -0
  26. nodescraper/connection/inband/inbandremote.py +173 -0
  27. nodescraper/connection/inband/sshparams.py +43 -0
  28. nodescraper/constants.py +26 -0
  29. nodescraper/enums/__init__.py +40 -0
  30. nodescraper/enums/eventcategory.py +89 -0
  31. nodescraper/enums/eventpriority.py +42 -0
  32. nodescraper/enums/executionstatus.py +44 -0
  33. nodescraper/enums/osfamily.py +34 -0
  34. nodescraper/enums/systeminteraction.py +41 -0
  35. nodescraper/enums/systemlocation.py +33 -0
  36. nodescraper/generictypes.py +36 -0
  37. nodescraper/interfaces/__init__.py +44 -0
  38. nodescraper/interfaces/connectionmanager.py +143 -0
  39. nodescraper/interfaces/dataanalyzertask.py +138 -0
  40. nodescraper/interfaces/datacollectortask.py +185 -0
  41. nodescraper/interfaces/dataplugin.py +356 -0
  42. nodescraper/interfaces/plugin.py +127 -0
  43. nodescraper/interfaces/resultcollator.py +56 -0
  44. nodescraper/interfaces/task.py +164 -0
  45. nodescraper/interfaces/taskresulthook.py +39 -0
  46. nodescraper/models/__init__.py +48 -0
  47. nodescraper/models/analyzerargs.py +93 -0
  48. nodescraper/models/collectorargs.py +30 -0
  49. nodescraper/models/connectionconfig.py +34 -0
  50. nodescraper/models/datamodel.py +171 -0
  51. nodescraper/models/datapluginresult.py +39 -0
  52. nodescraper/models/event.py +158 -0
  53. nodescraper/models/pluginconfig.py +38 -0
  54. nodescraper/models/pluginresult.py +39 -0
  55. nodescraper/models/systeminfo.py +44 -0
  56. nodescraper/models/taskresult.py +185 -0
  57. nodescraper/models/timerangeargs.py +38 -0
  58. nodescraper/pluginexecutor.py +274 -0
  59. nodescraper/pluginregistry.py +152 -0
  60. nodescraper/plugins/__init__.py +25 -0
  61. nodescraper/plugins/inband/__init__.py +25 -0
  62. nodescraper/plugins/inband/amdsmi/__init__.py +28 -0
  63. nodescraper/plugins/inband/amdsmi/amdsmi_analyzer.py +821 -0
  64. nodescraper/plugins/inband/amdsmi/amdsmi_collector.py +1313 -0
  65. nodescraper/plugins/inband/amdsmi/amdsmi_plugin.py +43 -0
  66. nodescraper/plugins/inband/amdsmi/amdsmidata.py +1002 -0
  67. nodescraper/plugins/inband/amdsmi/analyzer_args.py +50 -0
  68. nodescraper/plugins/inband/amdsmi/cper.py +65 -0
  69. nodescraper/plugins/inband/bios/__init__.py +29 -0
  70. nodescraper/plugins/inband/bios/analyzer_args.py +64 -0
  71. nodescraper/plugins/inband/bios/bios_analyzer.py +93 -0
  72. nodescraper/plugins/inband/bios/bios_collector.py +93 -0
  73. nodescraper/plugins/inband/bios/bios_plugin.py +43 -0
  74. nodescraper/plugins/inband/bios/biosdata.py +30 -0
  75. nodescraper/plugins/inband/cmdline/__init__.py +25 -0
  76. nodescraper/plugins/inband/cmdline/analyzer_args.py +80 -0
  77. nodescraper/plugins/inband/cmdline/cmdline_analyzer.py +113 -0
  78. nodescraper/plugins/inband/cmdline/cmdline_collector.py +77 -0
  79. nodescraper/plugins/inband/cmdline/cmdline_plugin.py +43 -0
  80. nodescraper/plugins/inband/cmdline/cmdlinedata.py +30 -0
  81. nodescraper/plugins/inband/device_enumeration/__init__.py +29 -0
  82. nodescraper/plugins/inband/device_enumeration/analyzer_args.py +73 -0
  83. nodescraper/plugins/inband/device_enumeration/device_enumeration_analyzer.py +81 -0
  84. nodescraper/plugins/inband/device_enumeration/device_enumeration_collector.py +176 -0
  85. nodescraper/plugins/inband/device_enumeration/device_enumeration_plugin.py +45 -0
  86. nodescraper/plugins/inband/device_enumeration/deviceenumdata.py +36 -0
  87. nodescraper/plugins/inband/dimm/__init__.py +25 -0
  88. nodescraper/plugins/inband/dimm/collector_args.py +31 -0
  89. nodescraper/plugins/inband/dimm/dimm_collector.py +151 -0
  90. nodescraper/plugins/inband/dimm/dimm_plugin.py +40 -0
  91. nodescraper/plugins/inband/dimm/dimmdata.py +30 -0
  92. nodescraper/plugins/inband/dkms/__init__.py +25 -0
  93. nodescraper/plugins/inband/dkms/analyzer_args.py +85 -0
  94. nodescraper/plugins/inband/dkms/dkms_analyzer.py +106 -0
  95. nodescraper/plugins/inband/dkms/dkms_collector.py +76 -0
  96. nodescraper/plugins/inband/dkms/dkms_plugin.py +43 -0
  97. nodescraper/plugins/inband/dkms/dkmsdata.py +33 -0
  98. nodescraper/plugins/inband/dmesg/__init__.py +28 -0
  99. nodescraper/plugins/inband/dmesg/analyzer_args.py +33 -0
  100. nodescraper/plugins/inband/dmesg/collector_args.py +39 -0
  101. nodescraper/plugins/inband/dmesg/dmesg_analyzer.py +503 -0
  102. nodescraper/plugins/inband/dmesg/dmesg_collector.py +164 -0
  103. nodescraper/plugins/inband/dmesg/dmesg_plugin.py +44 -0
  104. nodescraper/plugins/inband/dmesg/dmesgdata.py +116 -0
  105. nodescraper/plugins/inband/fabrics/__init__.py +28 -0
  106. nodescraper/plugins/inband/fabrics/fabrics_collector.py +726 -0
  107. nodescraper/plugins/inband/fabrics/fabrics_plugin.py +37 -0
  108. nodescraper/plugins/inband/fabrics/fabricsdata.py +140 -0
  109. nodescraper/plugins/inband/journal/__init__.py +28 -0
  110. nodescraper/plugins/inband/journal/collector_args.py +33 -0
  111. nodescraper/plugins/inband/journal/journal_collector.py +107 -0
  112. nodescraper/plugins/inband/journal/journal_plugin.py +40 -0
  113. nodescraper/plugins/inband/journal/journaldata.py +44 -0
  114. nodescraper/plugins/inband/kernel/__init__.py +25 -0
  115. nodescraper/plugins/inband/kernel/analyzer_args.py +64 -0
  116. nodescraper/plugins/inband/kernel/kernel_analyzer.py +91 -0
  117. nodescraper/plugins/inband/kernel/kernel_collector.py +129 -0
  118. nodescraper/plugins/inband/kernel/kernel_plugin.py +43 -0
  119. nodescraper/plugins/inband/kernel/kerneldata.py +32 -0
  120. nodescraper/plugins/inband/kernel_module/__init__.py +25 -0
  121. nodescraper/plugins/inband/kernel_module/analyzer_args.py +59 -0
  122. nodescraper/plugins/inband/kernel_module/kernel_module_analyzer.py +211 -0
  123. nodescraper/plugins/inband/kernel_module/kernel_module_collector.py +264 -0
  124. nodescraper/plugins/inband/kernel_module/kernel_module_data.py +60 -0
  125. nodescraper/plugins/inband/kernel_module/kernel_module_plugin.py +43 -0
  126. nodescraper/plugins/inband/memory/__init__.py +25 -0
  127. nodescraper/plugins/inband/memory/analyzer_args.py +45 -0
  128. nodescraper/plugins/inband/memory/memory_analyzer.py +98 -0
  129. nodescraper/plugins/inband/memory/memory_collector.py +330 -0
  130. nodescraper/plugins/inband/memory/memory_plugin.py +43 -0
  131. nodescraper/plugins/inband/memory/memorydata.py +90 -0
  132. nodescraper/plugins/inband/network/__init__.py +28 -0
  133. nodescraper/plugins/inband/network/network_collector.py +1828 -0
  134. nodescraper/plugins/inband/network/network_plugin.py +37 -0
  135. nodescraper/plugins/inband/network/networkdata.py +319 -0
  136. nodescraper/plugins/inband/nvme/__init__.py +28 -0
  137. nodescraper/plugins/inband/nvme/nvme_collector.py +167 -0
  138. nodescraper/plugins/inband/nvme/nvme_plugin.py +37 -0
  139. nodescraper/plugins/inband/nvme/nvmedata.py +45 -0
  140. nodescraper/plugins/inband/os/__init__.py +25 -0
  141. nodescraper/plugins/inband/os/analyzer_args.py +64 -0
  142. nodescraper/plugins/inband/os/os_analyzer.py +73 -0
  143. nodescraper/plugins/inband/os/os_collector.py +131 -0
  144. nodescraper/plugins/inband/os/os_plugin.py +43 -0
  145. nodescraper/plugins/inband/os/osdata.py +31 -0
  146. nodescraper/plugins/inband/package/__init__.py +25 -0
  147. nodescraper/plugins/inband/package/analyzer_args.py +48 -0
  148. nodescraper/plugins/inband/package/package_analyzer.py +253 -0
  149. nodescraper/plugins/inband/package/package_collector.py +273 -0
  150. nodescraper/plugins/inband/package/package_plugin.py +43 -0
  151. nodescraper/plugins/inband/package/packagedata.py +41 -0
  152. nodescraper/plugins/inband/pcie/__init__.py +29 -0
  153. nodescraper/plugins/inband/pcie/analyzer_args.py +63 -0
  154. nodescraper/plugins/inband/pcie/pcie_analyzer.py +1081 -0
  155. nodescraper/plugins/inband/pcie/pcie_collector.py +690 -0
  156. nodescraper/plugins/inband/pcie/pcie_data.py +2017 -0
  157. nodescraper/plugins/inband/pcie/pcie_plugin.py +43 -0
  158. nodescraper/plugins/inband/process/__init__.py +25 -0
  159. nodescraper/plugins/inband/process/analyzer_args.py +45 -0
  160. nodescraper/plugins/inband/process/collector_args.py +31 -0
  161. nodescraper/plugins/inband/process/process_analyzer.py +91 -0
  162. nodescraper/plugins/inband/process/process_collector.py +115 -0
  163. nodescraper/plugins/inband/process/process_plugin.py +46 -0
  164. nodescraper/plugins/inband/process/processdata.py +34 -0
  165. nodescraper/plugins/inband/rocm/__init__.py +25 -0
  166. nodescraper/plugins/inband/rocm/analyzer_args.py +66 -0
  167. nodescraper/plugins/inband/rocm/rocm_analyzer.py +100 -0
  168. nodescraper/plugins/inband/rocm/rocm_collector.py +205 -0
  169. nodescraper/plugins/inband/rocm/rocm_plugin.py +43 -0
  170. nodescraper/plugins/inband/rocm/rocmdata.py +62 -0
  171. nodescraper/plugins/inband/storage/__init__.py +25 -0
  172. nodescraper/plugins/inband/storage/analyzer_args.py +38 -0
  173. nodescraper/plugins/inband/storage/collector_args.py +31 -0
  174. nodescraper/plugins/inband/storage/storage_analyzer.py +152 -0
  175. nodescraper/plugins/inband/storage/storage_collector.py +110 -0
  176. nodescraper/plugins/inband/storage/storage_plugin.py +44 -0
  177. nodescraper/plugins/inband/storage/storagedata.py +70 -0
  178. nodescraper/plugins/inband/sysctl/__init__.py +29 -0
  179. nodescraper/plugins/inband/sysctl/analyzer_args.py +67 -0
  180. nodescraper/plugins/inband/sysctl/sysctl_analyzer.py +81 -0
  181. nodescraper/plugins/inband/sysctl/sysctl_collector.py +101 -0
  182. nodescraper/plugins/inband/sysctl/sysctl_plugin.py +43 -0
  183. nodescraper/plugins/inband/sysctl/sysctldata.py +42 -0
  184. nodescraper/plugins/inband/syslog/__init__.py +28 -0
  185. nodescraper/plugins/inband/syslog/syslog_collector.py +121 -0
  186. nodescraper/plugins/inband/syslog/syslog_plugin.py +37 -0
  187. nodescraper/plugins/inband/syslog/syslogdata.py +46 -0
  188. nodescraper/plugins/inband/uptime/__init__.py +25 -0
  189. nodescraper/plugins/inband/uptime/uptime_collector.py +88 -0
  190. nodescraper/plugins/inband/uptime/uptime_plugin.py +37 -0
  191. nodescraper/plugins/inband/uptime/uptimedata.py +31 -0
  192. nodescraper/resultcollators/__init__.py +25 -0
  193. nodescraper/resultcollators/tablesummary.py +159 -0
  194. nodescraper/taskresulthooks/__init__.py +28 -0
  195. nodescraper/taskresulthooks/filesystemloghook.py +88 -0
  196. nodescraper/typeutils.py +171 -0
  197. nodescraper/utils.py +412 -0
@@ -0,0 +1,330 @@
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
+ ###############################################################################
26
+ import re
27
+ from typing import Optional
28
+
29
+ from nodescraper.base import InBandDataCollector
30
+ from nodescraper.enums import EventCategory, EventPriority, ExecutionStatus, OSFamily
31
+ from nodescraper.models import TaskResult
32
+
33
+ from .memorydata import (
34
+ LsmemData,
35
+ MemoryBlock,
36
+ MemoryDataModel,
37
+ MemorySummary,
38
+ NumaDistance,
39
+ NumaNode,
40
+ NumaTopology,
41
+ )
42
+
43
+
44
+ class MemoryCollector(InBandDataCollector[MemoryDataModel, None]):
45
+ """Collect memory usage details"""
46
+
47
+ DATA_MODEL = MemoryDataModel
48
+
49
+ CMD_WINDOWS = (
50
+ "wmic OS get FreePhysicalMemory /Value; wmic ComputerSystem get TotalPhysicalMemory /Value"
51
+ )
52
+ CMD = "free -b"
53
+ CMD_LSMEM = "lsmem"
54
+ CMD_NUMACTL = "numactl -H"
55
+
56
+ def collect_data(self, args=None) -> tuple[TaskResult, Optional[MemoryDataModel]]:
57
+ """
58
+ Collects memory usage details from the system.
59
+
60
+ Returns:
61
+ tuple[TaskResult, Optional[MemoryDataModel]]: tuple containing the task result and memory data model or None if data is not available.
62
+ """
63
+ mem_free, mem_total = None, None
64
+ if self.system_info.os_family == OSFamily.WINDOWS:
65
+ os_memory_cmd = self._run_sut_cmd(self.CMD_WINDOWS)
66
+ if os_memory_cmd.exit_code == 0:
67
+ mem_free = re.search(r"FreePhysicalMemory=(\d+)", os_memory_cmd.stdout).group(
68
+ 1
69
+ ) # bytes
70
+ mem_total = re.search(r"TotalPhysicalMemory=(\d+)", os_memory_cmd.stdout).group(1)
71
+ else:
72
+ os_memory_cmd = self._run_sut_cmd(self.CMD)
73
+ if os_memory_cmd.exit_code == 0:
74
+ pattern = r"Mem:\s+(\d\.?\d*\w+)\s+\d\.?\d*\w+\s+(\d\.?\d*\w+)"
75
+ mem_free = re.search(pattern, os_memory_cmd.stdout).group(2)
76
+ mem_total = re.search(pattern, os_memory_cmd.stdout).group(1)
77
+
78
+ if os_memory_cmd.exit_code != 0:
79
+ self._log_event(
80
+ category=EventCategory.OS,
81
+ description="Error checking available and total memory",
82
+ data={
83
+ "command": os_memory_cmd.command,
84
+ "exit_code": os_memory_cmd.exit_code,
85
+ "stderr": os_memory_cmd.stderr,
86
+ },
87
+ priority=EventPriority.ERROR,
88
+ console_log=True,
89
+ )
90
+
91
+ lsmem_data = None
92
+ if self.system_info.os_family != OSFamily.WINDOWS:
93
+ lsmem_cmd = self._run_sut_cmd(self.CMD_LSMEM)
94
+ if lsmem_cmd.exit_code == 0:
95
+ lsmem_data = self._parse_lsmem_output(lsmem_cmd.stdout)
96
+ if lsmem_data:
97
+ self._log_event(
98
+ category=EventCategory.OS,
99
+ description="lsmem output collected",
100
+ data={
101
+ "memory_blocks": len(lsmem_data.memory_blocks),
102
+ "total_online_memory": lsmem_data.summary.total_online_memory,
103
+ },
104
+ priority=EventPriority.INFO,
105
+ )
106
+ else:
107
+ self._log_event(
108
+ category=EventCategory.OS,
109
+ description="Failed to parse lsmem output",
110
+ priority=EventPriority.WARNING,
111
+ console_log=False,
112
+ )
113
+ else:
114
+ self._log_event(
115
+ category=EventCategory.OS,
116
+ description="Error running lsmem command",
117
+ data={
118
+ "command": lsmem_cmd.command,
119
+ "exit_code": lsmem_cmd.exit_code,
120
+ "stderr": lsmem_cmd.stderr,
121
+ },
122
+ priority=EventPriority.WARNING,
123
+ console_log=False,
124
+ )
125
+
126
+ # Collect NUMA topology information
127
+ numa_topology = None
128
+ if self.system_info.os_family != OSFamily.WINDOWS:
129
+ numactl_cmd = self._run_sut_cmd(self.CMD_NUMACTL)
130
+ if numactl_cmd.exit_code == 0:
131
+ numa_topology = self._parse_numactl_hardware(numactl_cmd.stdout)
132
+ if numa_topology:
133
+ self._log_event(
134
+ category=EventCategory.MEMORY,
135
+ description="NUMA topology collected",
136
+ data={
137
+ "available_nodes": numa_topology.available_nodes,
138
+ "node_count": len(numa_topology.nodes),
139
+ },
140
+ priority=EventPriority.INFO,
141
+ )
142
+ else:
143
+ self._log_event(
144
+ category=EventCategory.MEMORY,
145
+ description="Failed to parse numactl output",
146
+ priority=EventPriority.WARNING,
147
+ console_log=False,
148
+ )
149
+ else:
150
+ self._log_event(
151
+ category=EventCategory.MEMORY,
152
+ description="Error running numactl command",
153
+ data={
154
+ "command": numactl_cmd.command,
155
+ "exit_code": numactl_cmd.exit_code,
156
+ "stderr": numactl_cmd.stderr,
157
+ },
158
+ priority=EventPriority.WARNING,
159
+ console_log=False,
160
+ )
161
+
162
+ if mem_free and mem_total:
163
+ mem_data = MemoryDataModel(
164
+ mem_free=mem_free,
165
+ mem_total=mem_total,
166
+ lsmem_data=lsmem_data,
167
+ numa_topology=numa_topology,
168
+ )
169
+ self._log_event(
170
+ category=EventCategory.OS,
171
+ description="Free and total memory read",
172
+ data=mem_data.model_dump(),
173
+ priority=EventPriority.INFO,
174
+ )
175
+ self.result.message = f"Memory: mem_free={mem_free}, mem_total={mem_total}"
176
+ self.result.status = ExecutionStatus.OK
177
+ else:
178
+ mem_data = None
179
+ self.result.message = "Memory usage data not available"
180
+ self.result.status = ExecutionStatus.ERROR
181
+
182
+ return self.result, mem_data
183
+
184
+ def _parse_lsmem_output(self, output: str):
185
+ """
186
+ Parse lsmem command output into a structured LsmemData object.
187
+
188
+ Args:
189
+ output: Raw stdout from lsmem command
190
+
191
+ Returns:
192
+ LsmemData: Parsed lsmem data with memory blocks and summary information
193
+ """
194
+ lines = output.strip().split("\n")
195
+ memory_blocks = []
196
+ summary_dict = {}
197
+
198
+ for line in lines:
199
+ line = line.strip()
200
+ if not line:
201
+ continue
202
+
203
+ # Parse mem range lines (sample: "0x0000000000000000-0x000000007fffffff 2G online yes 0-15")
204
+ if line.startswith("0x"):
205
+ parts = line.split()
206
+ if len(parts) >= 4:
207
+ memory_blocks.append(
208
+ MemoryBlock(
209
+ range=parts[0],
210
+ size=parts[1],
211
+ state=parts[2],
212
+ removable=parts[3] if len(parts) > 3 else None,
213
+ block=parts[4] if len(parts) > 4 else None,
214
+ )
215
+ )
216
+ # Parse summary lines
217
+ elif ":" in line:
218
+ key, value = line.split(":", 1)
219
+ summary_dict[key.strip().lower().replace(" ", "_")] = value.strip()
220
+
221
+ summary = MemorySummary(
222
+ memory_block_size=summary_dict.get("memory_block_size"),
223
+ total_online_memory=summary_dict.get("total_online_memory"),
224
+ total_offline_memory=summary_dict.get("total_offline_memory"),
225
+ )
226
+
227
+ if not memory_blocks:
228
+ return None
229
+
230
+ return LsmemData(memory_blocks=memory_blocks, summary=summary)
231
+
232
+ def _parse_numactl_hardware(self, output: str):
233
+ """
234
+ Parse 'numactl -H' output into NumaTopology structure.
235
+
236
+ Args:
237
+ output: Raw stdout from numactl -H command
238
+
239
+ Returns:
240
+ NumaTopology object or None if parsing fails
241
+ """
242
+ lines = output.strip().split("\n")
243
+ available_nodes = []
244
+ nodes = []
245
+ distances = []
246
+ distance_matrix = {}
247
+
248
+ current_section = None
249
+
250
+ for line in lines:
251
+ line = line.strip()
252
+ if not line:
253
+ continue
254
+
255
+ # Parse available nodes line
256
+ if line.startswith("available:"):
257
+ match = re.search(r"available:\s*(\d+)\s+nodes?\s*\(([^)]+)\)", line)
258
+ if match:
259
+ node_range = match.group(2)
260
+ if "-" in node_range:
261
+ start, end = node_range.split("-")
262
+ available_nodes = list(range(int(start), int(end) + 1))
263
+ else:
264
+ available_nodes = [int(x.strip()) for x in node_range.split()]
265
+
266
+ # Parse node CPU line
267
+ elif line.startswith("node") and "cpus:" in line:
268
+ match = re.search(r"node\s+(\d+)\s+cpus:\s*(.+)", line)
269
+ if match:
270
+ node_id = int(match.group(1))
271
+ cpu_list_str = match.group(2).strip()
272
+ if cpu_list_str:
273
+ cpus = [int(x) for x in cpu_list_str.split()]
274
+ else:
275
+ cpus = []
276
+ nodes.append(NumaNode(node_id=node_id, cpus=cpus))
277
+
278
+ # Parse node memory size
279
+ elif line.startswith("node") and "size:" in line:
280
+ match = re.search(r"node\s+(\d+)\s+size:\s*(\d+)\s*MB", line)
281
+ if match:
282
+ node_id = int(match.group(1))
283
+ size_mb = int(match.group(2))
284
+ # Find existing node and update
285
+ for node in nodes:
286
+ if node.node_id == node_id:
287
+ node.memory_size_mb = size_mb
288
+ break
289
+
290
+ # Parse node free memory
291
+ elif line.startswith("node") and "free:" in line:
292
+ match = re.search(r"node\s+(\d+)\s+free:\s*(\d+)\s*MB", line)
293
+ if match:
294
+ node_id = int(match.group(1))
295
+ free_mb = int(match.group(2))
296
+ # Find existing node and update
297
+ for node in nodes:
298
+ if node.node_id == node_id:
299
+ node.memory_free_mb = free_mb
300
+ break
301
+
302
+ # Parse distance matrix
303
+ elif line.startswith("node distances:"):
304
+ current_section = "distances"
305
+
306
+ elif current_section == "distances":
307
+ if line.startswith("node") and ":" not in line:
308
+ continue
309
+ elif ":" in line:
310
+ parts = line.split(":")
311
+ if len(parts) == 2:
312
+ from_node = int(parts[0].strip())
313
+ dist_values = [int(x) for x in parts[1].split()]
314
+
315
+ distance_matrix[from_node] = {}
316
+ for to_node, dist in enumerate(dist_values):
317
+ distance_matrix[from_node][to_node] = dist
318
+ distances.append(
319
+ NumaDistance(from_node=from_node, to_node=to_node, distance=dist)
320
+ )
321
+
322
+ if not nodes:
323
+ return None
324
+
325
+ return NumaTopology(
326
+ available_nodes=available_nodes if available_nodes else [n.node_id for n in nodes],
327
+ nodes=nodes,
328
+ distances=distances,
329
+ distance_matrix=distance_matrix if distance_matrix else None,
330
+ )
@@ -0,0 +1,43 @@
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
+ ###############################################################################
26
+ from nodescraper.base import InBandDataPlugin
27
+
28
+ from .analyzer_args import MemoryAnalyzerArgs
29
+ from .memory_analyzer import MemoryAnalyzer
30
+ from .memory_collector import MemoryCollector
31
+ from .memorydata import MemoryDataModel
32
+
33
+
34
+ class MemoryPlugin(InBandDataPlugin[MemoryDataModel, None, MemoryAnalyzerArgs]):
35
+ """Plugin for collection and analysis of memory data"""
36
+
37
+ DATA_MODEL = MemoryDataModel
38
+
39
+ COLLECTOR = MemoryCollector
40
+
41
+ ANALYZER = MemoryAnalyzer
42
+
43
+ ANALYZER_ARGS = MemoryAnalyzerArgs
@@ -0,0 +1,90 @@
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
+ ###############################################################################
26
+ from typing import Optional
27
+
28
+ from pydantic import BaseModel
29
+
30
+ from nodescraper.models import DataModel
31
+
32
+
33
+ class MemoryBlock(BaseModel):
34
+ """Memory block information from lsmem"""
35
+
36
+ range: str
37
+ size: str
38
+ state: str
39
+ removable: Optional[str] = None
40
+ block: Optional[str] = None
41
+
42
+
43
+ class MemorySummary(BaseModel):
44
+ """Summary information from lsmem"""
45
+
46
+ memory_block_size: Optional[str] = None
47
+ total_online_memory: Optional[str] = None
48
+ total_offline_memory: Optional[str] = None
49
+
50
+
51
+ class LsmemData(BaseModel):
52
+ """Complete lsmem output data"""
53
+
54
+ memory_blocks: list[MemoryBlock]
55
+ summary: MemorySummary
56
+
57
+
58
+ class NumaNode(BaseModel):
59
+ """NUMA node information"""
60
+
61
+ node_id: int
62
+ cpus: list[int]
63
+ memory_size_mb: Optional[int] = None
64
+ memory_free_mb: Optional[int] = None
65
+
66
+
67
+ class NumaDistance(BaseModel):
68
+ """Distance between two NUMA nodes"""
69
+
70
+ from_node: int
71
+ to_node: int
72
+ distance: int
73
+
74
+
75
+ class NumaTopology(BaseModel):
76
+ """Complete NUMA topology from 'numactl --hardware'"""
77
+
78
+ available_nodes: list[int]
79
+ nodes: list[NumaNode]
80
+ distances: list[NumaDistance]
81
+ distance_matrix: Optional[dict[int, dict[int, int]]] = None
82
+
83
+
84
+ class MemoryDataModel(DataModel):
85
+ """Memory data model"""
86
+
87
+ mem_free: str
88
+ mem_total: str
89
+ lsmem_data: Optional[LsmemData] = None
90
+ numa_topology: Optional[NumaTopology] = None
@@ -0,0 +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
+ ###############################################################################
26
+ from .network_plugin import NetworkPlugin
27
+
28
+ __all__ = ["NetworkPlugin"]