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,129 @@
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 argparse
27
+ import json
28
+ from typing import Generic, Optional, Type
29
+
30
+ from pydantic import ValidationError
31
+
32
+ from nodescraper.generictypes import TModelType
33
+
34
+
35
+ def log_path_arg(log_path: str) -> Optional[str]:
36
+ """Type function for a log path arg, allows 'none' to be specified to disable logging
37
+
38
+ Args:
39
+ log_path (str): log path string
40
+
41
+ Returns:
42
+ Optional[str]: log path or None
43
+ """
44
+ if log_path.lower() == "none":
45
+ return None
46
+ return log_path
47
+
48
+
49
+ def bool_arg(str_input: str) -> bool:
50
+ """Converts a string arg input into a bool
51
+
52
+ Args:
53
+ str_input (str): string input
54
+
55
+ Returns:
56
+ bool: bool value for string
57
+ """
58
+ if str_input.lower() == "true":
59
+ return True
60
+ elif str_input.lower() == "false":
61
+ return False
62
+ raise argparse.ArgumentTypeError("Invalid input, boolean value (True or False) expected")
63
+
64
+
65
+ def dict_arg(str_input: str) -> dict:
66
+ """converts a json string into a dict
67
+
68
+ Args:
69
+ str_input (str): input string
70
+
71
+ Raises:
72
+ argparse.ArgumentTypeError: if error was seen when loading string into json dict
73
+
74
+ Returns:
75
+ dict: dict representation of the json string
76
+ """
77
+ try:
78
+ return json.loads(str_input)
79
+ except json.JSONDecodeError as e:
80
+ raise argparse.ArgumentTypeError("Invalid json input for arg") from e
81
+
82
+
83
+ class ModelArgHandler(Generic[TModelType]):
84
+ """Class to handle loading json files into pydantic models"""
85
+
86
+ def __init__(self, model: Type[TModelType]) -> None:
87
+ self.model = model
88
+
89
+ def process_file_arg(self, file_path: str) -> TModelType:
90
+ """load a json file into a pydantic model
91
+
92
+ Args:
93
+ file_path (str): json file path
94
+
95
+ Raises:
96
+ argparse.ArgumentTypeError: If validation errors were seen when building model
97
+
98
+ Returns:
99
+ TModelType: model instance
100
+ """
101
+ data = json_arg(file_path)
102
+ try:
103
+ return self.model(**data)
104
+ except ValidationError as e:
105
+ raise argparse.ArgumentTypeError(
106
+ f"Validation errors when processing {file_path}: {e.errors(include_url=False)}"
107
+ ) from e
108
+
109
+
110
+ def json_arg(json_path: str) -> dict:
111
+ """loads a json file into a dict
112
+
113
+ Args:
114
+ json_path (str): path to json file
115
+
116
+ Raises:
117
+ argparse.ArgumentTypeError: If file does not exist or could not be decoded
118
+
119
+ Returns:
120
+ dict: output dict
121
+ """
122
+ try:
123
+ with open(json_path, "r", encoding="utf-8") as input_file:
124
+ data = json.load(input_file)
125
+ return data
126
+ except json.JSONDecodeError as e:
127
+ raise argparse.ArgumentTypeError(f"File {json_path} contains invalid JSON") from e
128
+ except FileNotFoundError as e:
129
+ raise argparse.ArgumentTypeError(f"Unable to find file: {json_path}") from e
@@ -0,0 +1,123 @@
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 enum
27
+ import logging
28
+ from typing import Any, Optional, Type, Union
29
+
30
+ from pydantic import BaseModel
31
+
32
+ from nodescraper.interfaces import PluginInterface
33
+ from nodescraper.models import DataModel, PluginConfig
34
+ from nodescraper.pluginregistry import PluginRegistry
35
+ from nodescraper.typeutils import TypeData, TypeUtils
36
+
37
+
38
+ class ConfigBuilder:
39
+ """Class used to dynamically generate plugin configs"""
40
+
41
+ def __init__(self, plugin_registry: PluginRegistry, logger: Optional[logging.Logger] = None):
42
+ self.plugin_registry = plugin_registry
43
+ self.logger = logger if logger else logging.getLogger()
44
+
45
+ def gen_config(self, plugin_names: list[str]) -> PluginConfig:
46
+ """Generate a plugin config dict for a list of plugin names
47
+
48
+ Args:
49
+ plugin_names (list[str]): list of plugin names to include in the config
50
+
51
+ Returns:
52
+ dict: plugin config dict
53
+ """
54
+ config = PluginConfig()
55
+ for plugin in plugin_names:
56
+ if plugin in self.plugin_registry.plugins:
57
+ config.plugins[plugin] = self._build_plugin_config(
58
+ self.plugin_registry.plugins[plugin]
59
+ )
60
+ else:
61
+ self.logger.warning("No plugin found with name: %s", plugin)
62
+ return config
63
+
64
+ @classmethod
65
+ def _build_plugin_config(cls, plugin_class: Type[PluginInterface]) -> dict:
66
+ type_map = TypeUtils.get_func_arg_types(plugin_class.run, plugin_class)
67
+ config = {}
68
+
69
+ for arg, arg_data in type_map.items():
70
+ cls._update_config(arg, arg_data, config)
71
+
72
+ return config
73
+
74
+ @classmethod
75
+ def _update_config(cls, config_key, type_data: TypeData, config: dict):
76
+ if config_key in ["self", "preserve_connection", "max_event_priority_level"]:
77
+ return
78
+
79
+ type_class_map = {
80
+ type_class.type_class: type_class for type_class in type_data.type_classes
81
+ }
82
+ if type(None) in type_class_map:
83
+ return
84
+
85
+ model_arg = next(
86
+ (
87
+ type_class
88
+ for type_class in type_class_map
89
+ if (isinstance(type_class, type) and issubclass(type_class, BaseModel))
90
+ and not issubclass(type_class, DataModel)
91
+ ),
92
+ None,
93
+ )
94
+
95
+ if model_arg:
96
+ model_config = {}
97
+ for attr, attr_data in TypeUtils.get_model_types(model_arg).items():
98
+ cls._update_config(attr, attr_data, model_config)
99
+ config[config_key] = model_config
100
+ else:
101
+ config[config_key] = cls._process_value(type_data.default)
102
+
103
+ @classmethod
104
+ def _process_value(cls, value: Any) -> Optional[Union[dict, str, int, float, list]]:
105
+ if isinstance(value, enum.Enum):
106
+ return value.name
107
+
108
+ if isinstance(value, dict):
109
+ return_dict = {}
110
+ for key, val in value.items():
111
+ return_dict[key] = cls._process_value(val)
112
+
113
+ elif not isinstance(
114
+ value,
115
+ (
116
+ str,
117
+ int,
118
+ float,
119
+ ),
120
+ ):
121
+ return None
122
+
123
+ return value
@@ -0,0 +1,66 @@
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 json
27
+ import os
28
+ from pathlib import Path
29
+ from typing import Optional
30
+
31
+ from pydantic import ValidationError
32
+
33
+ from nodescraper.models import PluginConfig
34
+
35
+
36
+ class ConfigRegistry:
37
+ """Class to load json plugin configs into models"""
38
+
39
+ INTERNAL_SEARCH_PATH = os.path.join(os.path.dirname(__file__), "configs")
40
+
41
+ def __init__(self, config_path: Optional[str] = None) -> None:
42
+ self.configs = {}
43
+ self.load_configs(config_path)
44
+
45
+ def load_configs(self, config_path: Optional[str] = None):
46
+ """load plugin config json files into pydantic models
47
+
48
+ Args:
49
+ config_path (Optional[str], optional): Path in which to search for config files. Defaults to None.
50
+ """
51
+ if not config_path:
52
+ config_path = self.INTERNAL_SEARCH_PATH
53
+
54
+ config_path = Path(config_path)
55
+
56
+ for config_file in config_path.glob("*.json"):
57
+ with open(config_file, "r", encoding="utf-8") as in_file:
58
+ try:
59
+ file_data = json.load(in_file)
60
+ config_model = PluginConfig(**file_data)
61
+ if config_model.name:
62
+ self.configs[config_model.name] = config_model
63
+ else:
64
+ self.configs[config_file.name] = config_model
65
+ except (ValidationError, json.JSONDecodeError):
66
+ pass
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "NodeStatus",
3
+ "desc": "Check configuration and status of the node",
4
+ "global_args": {},
5
+ "plugins": {
6
+ "BiosPlugin": {},
7
+ "CmdlinePlugin": {},
8
+ "DimmPlugin": {},
9
+ "DkmsPlugin": {},
10
+ "DmesgPlugin": {},
11
+ "KernelPlugin": {},
12
+ "MemoryPlugin": {},
13
+ "OsPlugin": {},
14
+ "RocmPlugin": {},
15
+ "StoragePlugin": {},
16
+ "UptimePlugin": {}
17
+ },
18
+ "result_collators": {}
19
+ }
@@ -0,0 +1,25 @@
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
+ ###############################################################################
@@ -0,0 +1,46 @@
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 .inband import (
27
+ BaseFileArtifact,
28
+ BinaryFileArtifact,
29
+ CommandArtifact,
30
+ InBandConnection,
31
+ TextFileArtifact,
32
+ )
33
+ from .inbandlocal import LocalShell
34
+ from .inbandmanager import InBandConnectionManager
35
+ from .sshparams import SSHConnectionParams
36
+
37
+ __all__ = [
38
+ "SSHConnectionParams",
39
+ "LocalShell",
40
+ "InBandConnectionManager",
41
+ "InBandConnection",
42
+ "BaseFileArtifact",
43
+ "TextFileArtifact",
44
+ "BinaryFileArtifact",
45
+ "CommandArtifact",
46
+ ]
@@ -0,0 +1,171 @@
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 abc
27
+ import os
28
+ from typing import Optional
29
+
30
+ from pydantic import BaseModel
31
+
32
+
33
+ class CommandArtifact(BaseModel):
34
+ """Artifact for the result of shell command execution"""
35
+
36
+ command: str
37
+ stdout: str
38
+ stderr: str
39
+ exit_code: int
40
+
41
+
42
+ class BaseFileArtifact(BaseModel, abc.ABC):
43
+ """Base class for files"""
44
+
45
+ filename: str
46
+
47
+ @abc.abstractmethod
48
+ def log_model(self, log_path: str) -> None:
49
+ """Write file to path
50
+
51
+ Args:
52
+ log_path (str): Path for file
53
+ """
54
+ pass
55
+
56
+ @abc.abstractmethod
57
+ def contents_str(self) -> str:
58
+ pass
59
+
60
+ @classmethod
61
+ def from_bytes(
62
+ cls,
63
+ filename: str,
64
+ raw_contents: bytes,
65
+ encoding: Optional[str] = "utf-8",
66
+ strip: bool = True,
67
+ ) -> "BaseFileArtifact":
68
+ """factory method
69
+
70
+ Args:
71
+ filename (str): name of file to be read
72
+ raw_contents (bytes): Raw file content
73
+ encoding (Optional[str], optional): Optional encoding. Defaults to "utf-8".
74
+ strip (bool, optional): Remove padding. Defaults to True.
75
+
76
+ Returns:
77
+ BaseFileArtifact: _Returns instance of Artifact file
78
+ """
79
+ if encoding is None:
80
+ return BinaryFileArtifact(filename=filename, contents=raw_contents)
81
+
82
+ try:
83
+ text = raw_contents.decode(encoding)
84
+ return TextFileArtifact(filename=filename, contents=text.strip() if strip else text)
85
+ except UnicodeDecodeError:
86
+ return BinaryFileArtifact(filename=filename, contents=raw_contents)
87
+
88
+
89
+ class TextFileArtifact(BaseFileArtifact):
90
+ """Class for text file artifacts"""
91
+
92
+ contents: str
93
+
94
+ def log_model(self, log_path: str) -> None:
95
+ """Write file to disk
96
+
97
+ Args:
98
+ log_path (str): Path for file
99
+ """
100
+ path = os.path.join(log_path, self.filename)
101
+ with open(path, "w", encoding="utf-8") as f:
102
+ f.write(self.contents)
103
+
104
+ def contents_str(self) -> str:
105
+ """Get content as str
106
+
107
+ Returns:
108
+ str: Str instance of file content
109
+ """
110
+ return self.contents
111
+
112
+
113
+ class BinaryFileArtifact(BaseFileArtifact):
114
+ """Class for binary file artifacts"""
115
+
116
+ contents: bytes
117
+
118
+ def log_model(self, log_path: str) -> None:
119
+ """Write file to disk
120
+
121
+ Args:
122
+ log_path (str): Path for file
123
+ """
124
+ log_name = os.path.join(log_path, self.filename)
125
+ with open(log_name, "wb") as f:
126
+ f.write(self.contents)
127
+
128
+ def contents_str(self) -> str:
129
+ """File content
130
+
131
+ Returns:
132
+ str: Str instance of file content
133
+ """
134
+ try:
135
+ return self.contents.decode("utf-8")
136
+ except UnicodeDecodeError:
137
+ return f"<binary data: {len(self.contents)} bytes>"
138
+
139
+
140
+ class InBandConnection(abc.ABC):
141
+
142
+ @abc.abstractmethod
143
+ def run_command(
144
+ self, command: str, sudo: bool = False, timeout: int = 300, strip: bool = True
145
+ ) -> CommandArtifact:
146
+ """Run an in band shell command
147
+
148
+ Args:
149
+ command (str): command to run
150
+ sudo (bool, optional): run command with sudo (Linux only). Defaults to False.
151
+ timeout (int, optional): timeout for command in seconds. Defaults to 300.
152
+ strip (bool, optional): strip output of command. Defaults to True.
153
+
154
+ Returns:
155
+ CommandArtifact: command result object
156
+ """
157
+
158
+ @abc.abstractmethod
159
+ def read_file(
160
+ self, filename: str, encoding: str = "utf-8", strip: bool = True
161
+ ) -> BaseFileArtifact:
162
+ """Read a file into a BaseFileArtifact
163
+
164
+ Args:
165
+ filename (str): filename
166
+ encoding (str, optional): encoding to use when opening file. Defaults to "utf-8".
167
+ strip (bool): automatically strip file contents
168
+
169
+ Returns:
170
+ BaseFileArtifact: file artifact
171
+ """
@@ -0,0 +1,93 @@
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 os
27
+ import subprocess
28
+
29
+ from .inband import (
30
+ BaseFileArtifact,
31
+ CommandArtifact,
32
+ InBandConnection,
33
+ )
34
+
35
+
36
+ class LocalShell(InBandConnection):
37
+
38
+ def run_command(
39
+ self, command: str, sudo: bool = False, timeout: int = 300, strip: bool = True
40
+ ) -> CommandArtifact:
41
+ """Run a local in band shell command
42
+
43
+ Args:
44
+ command (str): command to run
45
+ sudo (bool, optional): run command with sudo (Linux only). Defaults to False.
46
+ timeout (int, optional): timeout for command in seconds. Defaults to 300.
47
+ strip (bool, optional): strip output of command. Defaults to True.
48
+
49
+ Returns:
50
+ CommandArtifact: command result object
51
+ """
52
+ if sudo:
53
+ command = f"sudo {command}"
54
+
55
+ res = subprocess.run(
56
+ command,
57
+ encoding="utf-8",
58
+ shell=True,
59
+ errors="replace",
60
+ timeout=timeout,
61
+ capture_output=True,
62
+ check=False,
63
+ )
64
+
65
+ return CommandArtifact(
66
+ command=command,
67
+ stdout=res.stdout.strip() if strip else res.stdout,
68
+ stderr=res.stderr.strip() if strip else res.stderr,
69
+ exit_code=res.returncode,
70
+ )
71
+
72
+ def read_file(
73
+ self, filename: str, encoding: str = "utf-8", strip: bool = True
74
+ ) -> BaseFileArtifact:
75
+ """Read a local file into a BaseFileArtifact
76
+
77
+ Args:
78
+ filename (str): filename
79
+ encoding (str, optional): encoding to use when opening file. Defaults to "utf-8".
80
+ strip (bool): automatically strip file contents
81
+
82
+ Returns:
83
+ BaseFileArtifact: file artifact
84
+ """
85
+ with open(filename, "rb") as f:
86
+ raw_contents = f.read()
87
+
88
+ return BaseFileArtifact.from_bytes(
89
+ filename=os.path.basename(filename),
90
+ raw_contents=raw_contents,
91
+ encoding=encoding,
92
+ strip=strip,
93
+ )