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
nodescraper/cli/cli.py
ADDED
|
@@ -0,0 +1,511 @@
|
|
|
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 datetime
|
|
28
|
+
import json
|
|
29
|
+
import logging
|
|
30
|
+
import os
|
|
31
|
+
import platform
|
|
32
|
+
import sys
|
|
33
|
+
from typing import Optional
|
|
34
|
+
|
|
35
|
+
import nodescraper
|
|
36
|
+
from nodescraper.cli.constants import DEFAULT_CONFIG, META_VAR_MAP
|
|
37
|
+
from nodescraper.cli.dynamicparserbuilder import DynamicParserBuilder
|
|
38
|
+
from nodescraper.cli.helper import (
|
|
39
|
+
dump_results_to_csv,
|
|
40
|
+
generate_reference_config,
|
|
41
|
+
generate_reference_config_from_logs,
|
|
42
|
+
generate_summary,
|
|
43
|
+
get_plugin_configs,
|
|
44
|
+
get_system_info,
|
|
45
|
+
log_system_info,
|
|
46
|
+
parse_describe,
|
|
47
|
+
parse_gen_plugin_config,
|
|
48
|
+
)
|
|
49
|
+
from nodescraper.cli.inputargtypes import ModelArgHandler, json_arg, log_path_arg
|
|
50
|
+
from nodescraper.configregistry import ConfigRegistry
|
|
51
|
+
from nodescraper.constants import DEFAULT_LOGGER
|
|
52
|
+
from nodescraper.enums import ExecutionStatus, SystemInteractionLevel, SystemLocation
|
|
53
|
+
from nodescraper.models import SystemInfo
|
|
54
|
+
from nodescraper.pluginexecutor import PluginExecutor
|
|
55
|
+
from nodescraper.pluginregistry import PluginRegistry
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def build_parser(
|
|
59
|
+
plugin_reg: PluginRegistry,
|
|
60
|
+
config_reg: ConfigRegistry,
|
|
61
|
+
) -> tuple[argparse.ArgumentParser, dict[str, tuple[argparse.ArgumentParser, dict]]]:
|
|
62
|
+
"""Build an argument parser
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
plugin_reg (PluginRegistry): registry of plugins
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
tuple[argparse.ArgumentParser, dict[str, tuple[argparse.ArgumentParser, dict]]]: tuple containing main
|
|
69
|
+
parser and subparsers for each plugin module
|
|
70
|
+
"""
|
|
71
|
+
parser = argparse.ArgumentParser(
|
|
72
|
+
description="node scraper CLI",
|
|
73
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
parser.add_argument(
|
|
77
|
+
"--version",
|
|
78
|
+
action="version",
|
|
79
|
+
version=f"%(prog)s {nodescraper.__version__}",
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
parser.add_argument(
|
|
83
|
+
"--sys-name", default=platform.node(), help="System name", metavar=META_VAR_MAP[str]
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
parser.add_argument(
|
|
87
|
+
"--sys-location",
|
|
88
|
+
type=str.upper,
|
|
89
|
+
choices=[e.name for e in SystemLocation],
|
|
90
|
+
default="LOCAL",
|
|
91
|
+
help="Location of target system",
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
parser.add_argument(
|
|
95
|
+
"--sys-interaction-level",
|
|
96
|
+
type=str.upper,
|
|
97
|
+
choices=[e.name for e in SystemInteractionLevel],
|
|
98
|
+
default="INTERACTIVE",
|
|
99
|
+
help="Specify system interaction level, used to determine the type of actions that plugins can perform",
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
parser.add_argument(
|
|
103
|
+
"--sys-sku",
|
|
104
|
+
type=str.upper,
|
|
105
|
+
required=False,
|
|
106
|
+
help="Manually specify SKU of system",
|
|
107
|
+
metavar=META_VAR_MAP[str],
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
parser.add_argument(
|
|
111
|
+
"--sys-platform",
|
|
112
|
+
type=str,
|
|
113
|
+
required=False,
|
|
114
|
+
help="Specify system platform",
|
|
115
|
+
metavar=META_VAR_MAP[str],
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
parser.add_argument(
|
|
119
|
+
"--plugin-configs",
|
|
120
|
+
type=str,
|
|
121
|
+
nargs="*",
|
|
122
|
+
help=f"built-in config names or paths to plugin config JSONs.\nAvailable built-in configs: {', '.join(config_reg.configs.keys())}",
|
|
123
|
+
metavar=META_VAR_MAP[str],
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
parser.add_argument(
|
|
127
|
+
"--system-config",
|
|
128
|
+
type=ModelArgHandler(SystemInfo).process_file_arg,
|
|
129
|
+
required=False,
|
|
130
|
+
help="Path to system config json",
|
|
131
|
+
metavar=META_VAR_MAP[str],
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
parser.add_argument(
|
|
135
|
+
"--connection-config",
|
|
136
|
+
type=json_arg,
|
|
137
|
+
required=False,
|
|
138
|
+
help="Path to connection config json",
|
|
139
|
+
metavar=META_VAR_MAP[str],
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
parser.add_argument(
|
|
143
|
+
"--log-path",
|
|
144
|
+
default=".",
|
|
145
|
+
type=log_path_arg,
|
|
146
|
+
help="Specifies local path for node scraper logs, use 'None' to disable logging",
|
|
147
|
+
metavar=META_VAR_MAP[str],
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
parser.add_argument(
|
|
151
|
+
"--log-level",
|
|
152
|
+
default="INFO",
|
|
153
|
+
choices=logging._nameToLevel,
|
|
154
|
+
help="Change python log level",
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
parser.add_argument(
|
|
158
|
+
"--gen-reference-config",
|
|
159
|
+
dest="reference_config",
|
|
160
|
+
action="store_true",
|
|
161
|
+
help="Generate reference config from system. Writes to ./reference_config.json.",
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
parser.add_argument(
|
|
165
|
+
"--skip-sudo",
|
|
166
|
+
dest="skip_sudo",
|
|
167
|
+
action="store_true",
|
|
168
|
+
help="Skip plugins that require sudo permissions",
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
subparsers = parser.add_subparsers(dest="subcmd", help="Subcommands")
|
|
172
|
+
|
|
173
|
+
summary_parser = subparsers.add_parser(
|
|
174
|
+
"summary",
|
|
175
|
+
help="Generates summary csv file",
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
summary_parser.add_argument(
|
|
179
|
+
"--search-path",
|
|
180
|
+
dest="search_path",
|
|
181
|
+
type=log_path_arg,
|
|
182
|
+
help="Path to node-scraper previously generated results.",
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
summary_parser.add_argument(
|
|
186
|
+
"--output-path",
|
|
187
|
+
dest="output_path",
|
|
188
|
+
type=log_path_arg,
|
|
189
|
+
help="Specifies path for summary.csv.",
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
run_plugin_parser = subparsers.add_parser(
|
|
193
|
+
"run-plugins",
|
|
194
|
+
help="Run a series of plugins",
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
describe_parser = subparsers.add_parser(
|
|
198
|
+
"describe",
|
|
199
|
+
help="Display details on a built-in config or plugin",
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
describe_parser.add_argument(
|
|
203
|
+
"type",
|
|
204
|
+
choices=["config", "plugin"],
|
|
205
|
+
help="Type of object to describe (config or plugin)",
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
describe_parser.add_argument(
|
|
209
|
+
"name",
|
|
210
|
+
nargs="?",
|
|
211
|
+
help="Name of the config or plugin to describe",
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
config_builder_parser = subparsers.add_parser(
|
|
215
|
+
"gen-plugin-config",
|
|
216
|
+
help="Generate a config for a plugin or list of plugins",
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
config_builder_parser.add_argument(
|
|
220
|
+
"--gen-reference-config-from-logs",
|
|
221
|
+
dest="reference_config_from_logs",
|
|
222
|
+
type=log_path_arg,
|
|
223
|
+
help="Generate reference config from previous run logfiles. Writes to --output-path/reference_config.json if provided, otherwise ./reference_config.json.",
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
config_builder_parser.add_argument(
|
|
227
|
+
"--plugins",
|
|
228
|
+
nargs="*",
|
|
229
|
+
choices=list(plugin_reg.plugins.keys()),
|
|
230
|
+
help="Plugins to generate config for",
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
config_builder_parser.add_argument(
|
|
234
|
+
"--built-in-configs",
|
|
235
|
+
nargs="*",
|
|
236
|
+
choices=list(config_reg.configs.keys()),
|
|
237
|
+
help="Built in config names",
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
config_builder_parser.add_argument(
|
|
241
|
+
"--output-path",
|
|
242
|
+
default=os.getcwd(),
|
|
243
|
+
help="Directory to store config",
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
config_builder_parser.add_argument(
|
|
247
|
+
"--config-name",
|
|
248
|
+
default="plugin_config.json",
|
|
249
|
+
help="Name of config file",
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
plugin_subparsers = run_plugin_parser.add_subparsers(
|
|
253
|
+
dest="plugin_name", help="Available plugins"
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
plugin_subparser_map = {}
|
|
257
|
+
for plugin_name, plugin_class in plugin_reg.plugins.items():
|
|
258
|
+
plugin_subparser = plugin_subparsers.add_parser(
|
|
259
|
+
plugin_name,
|
|
260
|
+
help=f"Run {plugin_name} plugin",
|
|
261
|
+
)
|
|
262
|
+
try:
|
|
263
|
+
parser_builder = DynamicParserBuilder(plugin_subparser, plugin_class)
|
|
264
|
+
model_type_map = parser_builder.build_plugin_parser()
|
|
265
|
+
except Exception as e:
|
|
266
|
+
print(f"Exception building arg parsers for {plugin_name}: {str(e)}") # noqa: T201
|
|
267
|
+
continue
|
|
268
|
+
plugin_subparser_map[plugin_name] = (plugin_subparser, model_type_map)
|
|
269
|
+
|
|
270
|
+
return parser, plugin_subparser_map
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def setup_logger(log_level: str = "INFO", log_path: Optional[str] = None) -> logging.Logger:
|
|
274
|
+
"""set up root logger when using the CLI
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
log_level (str): log level to use
|
|
278
|
+
log_path (Optional[str]): optional path to filesystem log location
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
logging.Logger: logger intstance
|
|
282
|
+
"""
|
|
283
|
+
log_level = getattr(logging, log_level, "INFO")
|
|
284
|
+
|
|
285
|
+
handlers = [logging.StreamHandler(stream=sys.stdout)]
|
|
286
|
+
|
|
287
|
+
if log_path:
|
|
288
|
+
log_file_name = os.path.join(log_path, "nodescraper.log")
|
|
289
|
+
handlers.append(
|
|
290
|
+
logging.FileHandler(filename=log_file_name, mode="wt", encoding="utf-8"),
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
logging.basicConfig(
|
|
294
|
+
force=True,
|
|
295
|
+
level=log_level,
|
|
296
|
+
format="%(asctime)25s %(levelname)10s %(name)25s | %(message)s",
|
|
297
|
+
datefmt="%Y-%m-%d %H:%M:%S %Z",
|
|
298
|
+
handlers=handlers,
|
|
299
|
+
encoding="utf-8",
|
|
300
|
+
)
|
|
301
|
+
logging.root.setLevel(logging.INFO)
|
|
302
|
+
logging.getLogger("paramiko").setLevel(logging.ERROR)
|
|
303
|
+
|
|
304
|
+
logger = logging.getLogger(DEFAULT_LOGGER)
|
|
305
|
+
|
|
306
|
+
return logger
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def process_args(
|
|
310
|
+
raw_arg_input: list[str], plugin_names: list[str]
|
|
311
|
+
) -> tuple[list[str], dict[str, list[str]]]:
|
|
312
|
+
"""separate top level args from plugin args
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
raw_arg_input (list[str]): list of all arg input
|
|
316
|
+
plugin_names (list[str]): list of plugin names
|
|
317
|
+
|
|
318
|
+
Returns:
|
|
319
|
+
tuple[list[str], dict[str, list[str]]]: tuple of top level args
|
|
320
|
+
and dict of plugin name to plugin args
|
|
321
|
+
"""
|
|
322
|
+
top_level_args = raw_arg_input
|
|
323
|
+
|
|
324
|
+
try:
|
|
325
|
+
plugin_arg_index = raw_arg_input.index("run-plugins")
|
|
326
|
+
except ValueError:
|
|
327
|
+
plugin_arg_index = -1
|
|
328
|
+
|
|
329
|
+
plugin_arg_map = {}
|
|
330
|
+
invalid_plugins = []
|
|
331
|
+
if plugin_arg_index != -1 and plugin_arg_index != len(raw_arg_input) - 1:
|
|
332
|
+
top_level_args = raw_arg_input[: plugin_arg_index + 1]
|
|
333
|
+
plugin_args = raw_arg_input[plugin_arg_index + 1 :]
|
|
334
|
+
|
|
335
|
+
# handle help case
|
|
336
|
+
if plugin_args == ["-h"]:
|
|
337
|
+
top_level_args += plugin_args
|
|
338
|
+
else:
|
|
339
|
+
cur_plugin = None
|
|
340
|
+
for arg in plugin_args:
|
|
341
|
+
# Handle comma-separated plugin names (but not arguments)
|
|
342
|
+
if not arg.startswith("-") and "," in arg:
|
|
343
|
+
# Split comma-separated plugin names
|
|
344
|
+
for potential_plugin in arg.split(","):
|
|
345
|
+
potential_plugin = potential_plugin.strip()
|
|
346
|
+
if potential_plugin in plugin_names:
|
|
347
|
+
plugin_arg_map[potential_plugin] = []
|
|
348
|
+
cur_plugin = potential_plugin
|
|
349
|
+
elif potential_plugin:
|
|
350
|
+
# Track invalid plugin names to log event later
|
|
351
|
+
invalid_plugins.append(potential_plugin)
|
|
352
|
+
elif arg in plugin_names:
|
|
353
|
+
plugin_arg_map[arg] = []
|
|
354
|
+
cur_plugin = arg
|
|
355
|
+
elif cur_plugin:
|
|
356
|
+
plugin_arg_map[cur_plugin].append(arg)
|
|
357
|
+
elif not arg.startswith("-"):
|
|
358
|
+
# Track invalid plugin names to log event later
|
|
359
|
+
invalid_plugins.append(arg)
|
|
360
|
+
return (top_level_args, plugin_arg_map, invalid_plugins)
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def main(arg_input: Optional[list[str]] = None):
|
|
364
|
+
"""Main entry point for the CLI
|
|
365
|
+
|
|
366
|
+
Args:
|
|
367
|
+
arg_input (Optional[list[str]], optional): list of args to parse. Defaults to None.
|
|
368
|
+
"""
|
|
369
|
+
if arg_input is None:
|
|
370
|
+
arg_input = sys.argv[1:]
|
|
371
|
+
|
|
372
|
+
plugin_reg = PluginRegistry()
|
|
373
|
+
|
|
374
|
+
config_reg = ConfigRegistry()
|
|
375
|
+
parser, plugin_subparser_map = build_parser(plugin_reg, config_reg)
|
|
376
|
+
|
|
377
|
+
try:
|
|
378
|
+
top_level_args, plugin_arg_map, invalid_plugins = process_args(
|
|
379
|
+
arg_input, list(plugin_subparser_map.keys())
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
parsed_args = parser.parse_args(top_level_args)
|
|
383
|
+
system_info = get_system_info(parsed_args)
|
|
384
|
+
sname = system_info.name.lower().replace("-", "_").replace(".", "_")
|
|
385
|
+
timestamp = datetime.datetime.now().strftime("%Y_%m_%d-%I_%M_%S_%p")
|
|
386
|
+
|
|
387
|
+
if parsed_args.log_path and parsed_args.subcmd not in ["gen-plugin-config", "describe"]:
|
|
388
|
+
log_path = os.path.join(
|
|
389
|
+
parsed_args.log_path,
|
|
390
|
+
f"scraper_logs_{sname}_{timestamp}",
|
|
391
|
+
)
|
|
392
|
+
os.makedirs(log_path)
|
|
393
|
+
else:
|
|
394
|
+
log_path = None
|
|
395
|
+
|
|
396
|
+
logger = setup_logger(parsed_args.log_level, log_path)
|
|
397
|
+
if log_path:
|
|
398
|
+
logger.info("Log path: %s", log_path)
|
|
399
|
+
|
|
400
|
+
# Log warning if invalid plugin names were provided
|
|
401
|
+
if invalid_plugins:
|
|
402
|
+
logger.warning(
|
|
403
|
+
"Invalid plugin name(s) ignored: %s. Use 'describe plugin' to list available plugins.",
|
|
404
|
+
", ".join(invalid_plugins),
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
if parsed_args.subcmd == "summary":
|
|
408
|
+
generate_summary(parsed_args.search_path, parsed_args.output_path, logger)
|
|
409
|
+
sys.exit(0)
|
|
410
|
+
|
|
411
|
+
if parsed_args.subcmd == "describe":
|
|
412
|
+
parse_describe(parsed_args, plugin_reg, config_reg, logger)
|
|
413
|
+
|
|
414
|
+
if parsed_args.subcmd == "gen-plugin-config":
|
|
415
|
+
|
|
416
|
+
if parsed_args.reference_config_from_logs:
|
|
417
|
+
ref_config = generate_reference_config_from_logs(
|
|
418
|
+
parsed_args.reference_config_from_logs, plugin_reg, logger
|
|
419
|
+
)
|
|
420
|
+
output_path = os.getcwd()
|
|
421
|
+
if parsed_args.output_path:
|
|
422
|
+
output_path = parsed_args.output_path
|
|
423
|
+
path = os.path.join(output_path, "reference_config.json")
|
|
424
|
+
try:
|
|
425
|
+
with open(path, "w") as f:
|
|
426
|
+
json.dump(
|
|
427
|
+
ref_config.model_dump(mode="json", exclude_none=True),
|
|
428
|
+
f,
|
|
429
|
+
indent=2,
|
|
430
|
+
)
|
|
431
|
+
logger.info("Reference config written to: %s", path)
|
|
432
|
+
except Exception as exp:
|
|
433
|
+
logger.error(exp)
|
|
434
|
+
sys.exit(0)
|
|
435
|
+
|
|
436
|
+
parse_gen_plugin_config(parsed_args, plugin_reg, config_reg, logger)
|
|
437
|
+
|
|
438
|
+
parsed_plugin_args = {}
|
|
439
|
+
for plugin, plugin_args in plugin_arg_map.items():
|
|
440
|
+
try:
|
|
441
|
+
parsed_plugin_args[plugin] = plugin_subparser_map[plugin][0].parse_args(plugin_args)
|
|
442
|
+
except Exception as e:
|
|
443
|
+
logger.error("%s exception parsing args for plugin: %s", str(e), plugin)
|
|
444
|
+
|
|
445
|
+
if not parsed_plugin_args and not parsed_args.plugin_configs:
|
|
446
|
+
logger.info(
|
|
447
|
+
"No plugins config args specified, running default config: %s", DEFAULT_CONFIG
|
|
448
|
+
)
|
|
449
|
+
plugin_configs = [DEFAULT_CONFIG]
|
|
450
|
+
else:
|
|
451
|
+
plugin_configs = parsed_args.plugin_configs or []
|
|
452
|
+
|
|
453
|
+
plugin_config_inst_list = get_plugin_configs(
|
|
454
|
+
plugin_config_input=plugin_configs,
|
|
455
|
+
system_interaction_level=parsed_args.sys_interaction_level,
|
|
456
|
+
built_in_configs=config_reg.configs,
|
|
457
|
+
parsed_plugin_args=parsed_plugin_args,
|
|
458
|
+
plugin_subparser_map=plugin_subparser_map,
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
if parsed_args.skip_sudo:
|
|
462
|
+
plugin_config_inst_list[-1].global_args.setdefault("collection_args", {})[
|
|
463
|
+
"skip_sudo"
|
|
464
|
+
] = True
|
|
465
|
+
|
|
466
|
+
log_system_info(log_path, system_info, logger)
|
|
467
|
+
except Exception as e:
|
|
468
|
+
parser.error(str(e))
|
|
469
|
+
|
|
470
|
+
plugin_executor = PluginExecutor(
|
|
471
|
+
logger=logger,
|
|
472
|
+
plugin_configs=plugin_config_inst_list,
|
|
473
|
+
connections=parsed_args.connection_config,
|
|
474
|
+
system_info=system_info,
|
|
475
|
+
log_path=log_path,
|
|
476
|
+
plugin_registry=plugin_reg,
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
try:
|
|
480
|
+
results = plugin_executor.run_queue()
|
|
481
|
+
|
|
482
|
+
dump_results_to_csv(results, sname, log_path, timestamp, logger)
|
|
483
|
+
|
|
484
|
+
if parsed_args.reference_config:
|
|
485
|
+
ref_config = generate_reference_config(results, plugin_reg, logger)
|
|
486
|
+
if log_path:
|
|
487
|
+
path = os.path.join(log_path, "reference_config.json")
|
|
488
|
+
else:
|
|
489
|
+
path = os.path.join(os.getcwd(), "reference_config.json")
|
|
490
|
+
try:
|
|
491
|
+
with open(path, "w") as f:
|
|
492
|
+
json.dump(
|
|
493
|
+
ref_config.model_dump(mode="json", exclude_none=True),
|
|
494
|
+
f,
|
|
495
|
+
indent=2,
|
|
496
|
+
)
|
|
497
|
+
logger.info("Reference config written to: %s", path)
|
|
498
|
+
except Exception as exp:
|
|
499
|
+
logger.error(exp)
|
|
500
|
+
|
|
501
|
+
if any(result.status > ExecutionStatus.WARNING for result in results):
|
|
502
|
+
sys.exit(1)
|
|
503
|
+
else:
|
|
504
|
+
sys.exit(0)
|
|
505
|
+
except KeyboardInterrupt:
|
|
506
|
+
logger.info("Received Ctrl+C. Shutting down...")
|
|
507
|
+
sys.exit(130)
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
if __name__ == "__main__":
|
|
511
|
+
main()
|
|
@@ -0,0 +1,27 @@
|
|
|
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
|
+
META_VAR_MAP = {int: "INT", bool: "BOOL", dict: "JSON_STRING", float: "FLOAT", str: "STRING"}
|
|
27
|
+
DEFAULT_CONFIG = "NodeStatus"
|