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.
- amd_node_scraper-0.0.1.dist-info/LICENSE +21 -0
- amd_node_scraper-0.0.1.dist-info/METADATA +424 -0
- amd_node_scraper-0.0.1.dist-info/RECORD +197 -0
- amd_node_scraper-0.0.1.dist-info/WHEEL +5 -0
- amd_node_scraper-0.0.1.dist-info/entry_points.txt +2 -0
- amd_node_scraper-0.0.1.dist-info/top_level.txt +1 -0
- nodescraper/__init__.py +32 -0
- nodescraper/base/__init__.py +34 -0
- nodescraper/base/inbandcollectortask.py +118 -0
- nodescraper/base/inbanddataplugin.py +39 -0
- nodescraper/base/regexanalyzer.py +120 -0
- nodescraper/cli/__init__.py +29 -0
- nodescraper/cli/cli.py +511 -0
- nodescraper/cli/constants.py +27 -0
- nodescraper/cli/dynamicparserbuilder.py +171 -0
- nodescraper/cli/helper.py +517 -0
- nodescraper/cli/inputargtypes.py +129 -0
- nodescraper/configbuilder.py +123 -0
- nodescraper/configregistry.py +66 -0
- nodescraper/configs/node_status.json +19 -0
- nodescraper/connection/__init__.py +25 -0
- nodescraper/connection/inband/__init__.py +46 -0
- nodescraper/connection/inband/inband.py +171 -0
- nodescraper/connection/inband/inbandlocal.py +93 -0
- nodescraper/connection/inband/inbandmanager.py +151 -0
- nodescraper/connection/inband/inbandremote.py +173 -0
- nodescraper/connection/inband/sshparams.py +43 -0
- nodescraper/constants.py +26 -0
- nodescraper/enums/__init__.py +40 -0
- nodescraper/enums/eventcategory.py +89 -0
- nodescraper/enums/eventpriority.py +42 -0
- nodescraper/enums/executionstatus.py +44 -0
- nodescraper/enums/osfamily.py +34 -0
- nodescraper/enums/systeminteraction.py +41 -0
- nodescraper/enums/systemlocation.py +33 -0
- nodescraper/generictypes.py +36 -0
- nodescraper/interfaces/__init__.py +44 -0
- nodescraper/interfaces/connectionmanager.py +143 -0
- nodescraper/interfaces/dataanalyzertask.py +138 -0
- nodescraper/interfaces/datacollectortask.py +185 -0
- nodescraper/interfaces/dataplugin.py +356 -0
- nodescraper/interfaces/plugin.py +127 -0
- nodescraper/interfaces/resultcollator.py +56 -0
- nodescraper/interfaces/task.py +164 -0
- nodescraper/interfaces/taskresulthook.py +39 -0
- nodescraper/models/__init__.py +48 -0
- nodescraper/models/analyzerargs.py +93 -0
- nodescraper/models/collectorargs.py +30 -0
- nodescraper/models/connectionconfig.py +34 -0
- nodescraper/models/datamodel.py +171 -0
- nodescraper/models/datapluginresult.py +39 -0
- nodescraper/models/event.py +158 -0
- nodescraper/models/pluginconfig.py +38 -0
- nodescraper/models/pluginresult.py +39 -0
- nodescraper/models/systeminfo.py +44 -0
- nodescraper/models/taskresult.py +185 -0
- nodescraper/models/timerangeargs.py +38 -0
- nodescraper/pluginexecutor.py +274 -0
- nodescraper/pluginregistry.py +152 -0
- nodescraper/plugins/__init__.py +25 -0
- nodescraper/plugins/inband/__init__.py +25 -0
- nodescraper/plugins/inband/amdsmi/__init__.py +28 -0
- nodescraper/plugins/inband/amdsmi/amdsmi_analyzer.py +821 -0
- nodescraper/plugins/inband/amdsmi/amdsmi_collector.py +1313 -0
- nodescraper/plugins/inband/amdsmi/amdsmi_plugin.py +43 -0
- nodescraper/plugins/inband/amdsmi/amdsmidata.py +1002 -0
- nodescraper/plugins/inband/amdsmi/analyzer_args.py +50 -0
- nodescraper/plugins/inband/amdsmi/cper.py +65 -0
- nodescraper/plugins/inband/bios/__init__.py +29 -0
- nodescraper/plugins/inband/bios/analyzer_args.py +64 -0
- nodescraper/plugins/inband/bios/bios_analyzer.py +93 -0
- nodescraper/plugins/inband/bios/bios_collector.py +93 -0
- nodescraper/plugins/inband/bios/bios_plugin.py +43 -0
- nodescraper/plugins/inband/bios/biosdata.py +30 -0
- nodescraper/plugins/inband/cmdline/__init__.py +25 -0
- nodescraper/plugins/inband/cmdline/analyzer_args.py +80 -0
- nodescraper/plugins/inband/cmdline/cmdline_analyzer.py +113 -0
- nodescraper/plugins/inband/cmdline/cmdline_collector.py +77 -0
- nodescraper/plugins/inband/cmdline/cmdline_plugin.py +43 -0
- nodescraper/plugins/inband/cmdline/cmdlinedata.py +30 -0
- nodescraper/plugins/inband/device_enumeration/__init__.py +29 -0
- nodescraper/plugins/inband/device_enumeration/analyzer_args.py +73 -0
- nodescraper/plugins/inband/device_enumeration/device_enumeration_analyzer.py +81 -0
- nodescraper/plugins/inband/device_enumeration/device_enumeration_collector.py +176 -0
- nodescraper/plugins/inband/device_enumeration/device_enumeration_plugin.py +45 -0
- nodescraper/plugins/inband/device_enumeration/deviceenumdata.py +36 -0
- nodescraper/plugins/inband/dimm/__init__.py +25 -0
- nodescraper/plugins/inband/dimm/collector_args.py +31 -0
- nodescraper/plugins/inband/dimm/dimm_collector.py +151 -0
- nodescraper/plugins/inband/dimm/dimm_plugin.py +40 -0
- nodescraper/plugins/inband/dimm/dimmdata.py +30 -0
- nodescraper/plugins/inband/dkms/__init__.py +25 -0
- nodescraper/plugins/inband/dkms/analyzer_args.py +85 -0
- nodescraper/plugins/inband/dkms/dkms_analyzer.py +106 -0
- nodescraper/plugins/inband/dkms/dkms_collector.py +76 -0
- nodescraper/plugins/inband/dkms/dkms_plugin.py +43 -0
- nodescraper/plugins/inband/dkms/dkmsdata.py +33 -0
- nodescraper/plugins/inband/dmesg/__init__.py +28 -0
- nodescraper/plugins/inband/dmesg/analyzer_args.py +33 -0
- nodescraper/plugins/inband/dmesg/collector_args.py +39 -0
- nodescraper/plugins/inband/dmesg/dmesg_analyzer.py +503 -0
- nodescraper/plugins/inband/dmesg/dmesg_collector.py +164 -0
- nodescraper/plugins/inband/dmesg/dmesg_plugin.py +44 -0
- nodescraper/plugins/inband/dmesg/dmesgdata.py +116 -0
- nodescraper/plugins/inband/fabrics/__init__.py +28 -0
- nodescraper/plugins/inband/fabrics/fabrics_collector.py +726 -0
- nodescraper/plugins/inband/fabrics/fabrics_plugin.py +37 -0
- nodescraper/plugins/inband/fabrics/fabricsdata.py +140 -0
- nodescraper/plugins/inband/journal/__init__.py +28 -0
- nodescraper/plugins/inband/journal/collector_args.py +33 -0
- nodescraper/plugins/inband/journal/journal_collector.py +107 -0
- nodescraper/plugins/inband/journal/journal_plugin.py +40 -0
- nodescraper/plugins/inband/journal/journaldata.py +44 -0
- nodescraper/plugins/inband/kernel/__init__.py +25 -0
- nodescraper/plugins/inband/kernel/analyzer_args.py +64 -0
- nodescraper/plugins/inband/kernel/kernel_analyzer.py +91 -0
- nodescraper/plugins/inband/kernel/kernel_collector.py +129 -0
- nodescraper/plugins/inband/kernel/kernel_plugin.py +43 -0
- nodescraper/plugins/inband/kernel/kerneldata.py +32 -0
- nodescraper/plugins/inband/kernel_module/__init__.py +25 -0
- nodescraper/plugins/inband/kernel_module/analyzer_args.py +59 -0
- nodescraper/plugins/inband/kernel_module/kernel_module_analyzer.py +211 -0
- nodescraper/plugins/inband/kernel_module/kernel_module_collector.py +264 -0
- nodescraper/plugins/inband/kernel_module/kernel_module_data.py +60 -0
- nodescraper/plugins/inband/kernel_module/kernel_module_plugin.py +43 -0
- nodescraper/plugins/inband/memory/__init__.py +25 -0
- nodescraper/plugins/inband/memory/analyzer_args.py +45 -0
- nodescraper/plugins/inband/memory/memory_analyzer.py +98 -0
- nodescraper/plugins/inband/memory/memory_collector.py +330 -0
- nodescraper/plugins/inband/memory/memory_plugin.py +43 -0
- nodescraper/plugins/inband/memory/memorydata.py +90 -0
- nodescraper/plugins/inband/network/__init__.py +28 -0
- nodescraper/plugins/inband/network/network_collector.py +1828 -0
- nodescraper/plugins/inband/network/network_plugin.py +37 -0
- nodescraper/plugins/inband/network/networkdata.py +319 -0
- nodescraper/plugins/inband/nvme/__init__.py +28 -0
- nodescraper/plugins/inband/nvme/nvme_collector.py +167 -0
- nodescraper/plugins/inband/nvme/nvme_plugin.py +37 -0
- nodescraper/plugins/inband/nvme/nvmedata.py +45 -0
- nodescraper/plugins/inband/os/__init__.py +25 -0
- nodescraper/plugins/inband/os/analyzer_args.py +64 -0
- nodescraper/plugins/inband/os/os_analyzer.py +73 -0
- nodescraper/plugins/inband/os/os_collector.py +131 -0
- nodescraper/plugins/inband/os/os_plugin.py +43 -0
- nodescraper/plugins/inband/os/osdata.py +31 -0
- nodescraper/plugins/inband/package/__init__.py +25 -0
- nodescraper/plugins/inband/package/analyzer_args.py +48 -0
- nodescraper/plugins/inband/package/package_analyzer.py +253 -0
- nodescraper/plugins/inband/package/package_collector.py +273 -0
- nodescraper/plugins/inband/package/package_plugin.py +43 -0
- nodescraper/plugins/inband/package/packagedata.py +41 -0
- nodescraper/plugins/inband/pcie/__init__.py +29 -0
- nodescraper/plugins/inband/pcie/analyzer_args.py +63 -0
- nodescraper/plugins/inband/pcie/pcie_analyzer.py +1081 -0
- nodescraper/plugins/inband/pcie/pcie_collector.py +690 -0
- nodescraper/plugins/inband/pcie/pcie_data.py +2017 -0
- nodescraper/plugins/inband/pcie/pcie_plugin.py +43 -0
- nodescraper/plugins/inband/process/__init__.py +25 -0
- nodescraper/plugins/inband/process/analyzer_args.py +45 -0
- nodescraper/plugins/inband/process/collector_args.py +31 -0
- nodescraper/plugins/inband/process/process_analyzer.py +91 -0
- nodescraper/plugins/inband/process/process_collector.py +115 -0
- nodescraper/plugins/inband/process/process_plugin.py +46 -0
- nodescraper/plugins/inband/process/processdata.py +34 -0
- nodescraper/plugins/inband/rocm/__init__.py +25 -0
- nodescraper/plugins/inband/rocm/analyzer_args.py +66 -0
- nodescraper/plugins/inband/rocm/rocm_analyzer.py +100 -0
- nodescraper/plugins/inband/rocm/rocm_collector.py +205 -0
- nodescraper/plugins/inband/rocm/rocm_plugin.py +43 -0
- nodescraper/plugins/inband/rocm/rocmdata.py +62 -0
- nodescraper/plugins/inband/storage/__init__.py +25 -0
- nodescraper/plugins/inband/storage/analyzer_args.py +38 -0
- nodescraper/plugins/inband/storage/collector_args.py +31 -0
- nodescraper/plugins/inband/storage/storage_analyzer.py +152 -0
- nodescraper/plugins/inband/storage/storage_collector.py +110 -0
- nodescraper/plugins/inband/storage/storage_plugin.py +44 -0
- nodescraper/plugins/inband/storage/storagedata.py +70 -0
- nodescraper/plugins/inband/sysctl/__init__.py +29 -0
- nodescraper/plugins/inband/sysctl/analyzer_args.py +67 -0
- nodescraper/plugins/inband/sysctl/sysctl_analyzer.py +81 -0
- nodescraper/plugins/inband/sysctl/sysctl_collector.py +101 -0
- nodescraper/plugins/inband/sysctl/sysctl_plugin.py +43 -0
- nodescraper/plugins/inband/sysctl/sysctldata.py +42 -0
- nodescraper/plugins/inband/syslog/__init__.py +28 -0
- nodescraper/plugins/inband/syslog/syslog_collector.py +121 -0
- nodescraper/plugins/inband/syslog/syslog_plugin.py +37 -0
- nodescraper/plugins/inband/syslog/syslogdata.py +46 -0
- nodescraper/plugins/inband/uptime/__init__.py +25 -0
- nodescraper/plugins/inband/uptime/uptime_collector.py +88 -0
- nodescraper/plugins/inband/uptime/uptime_plugin.py +37 -0
- nodescraper/plugins/inband/uptime/uptimedata.py +31 -0
- nodescraper/resultcollators/__init__.py +25 -0
- nodescraper/resultcollators/tablesummary.py +159 -0
- nodescraper/taskresulthooks/__init__.py +28 -0
- nodescraper/taskresulthooks/filesystemloghook.py +88 -0
- nodescraper/typeutils.py +171 -0
- 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"]
|