Skip to content

Commit 1a28c9b

Browse files
committed
merged main
2 parents a9752cf + 8258438 commit 1a28c9b

File tree

19 files changed

+359
-126
lines changed

19 files changed

+359
-126
lines changed

.github/workflows/code_quality_checks.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
# workflow to validate code formatting using pre-commit hooks
22
name: Code Quality Check
33

4+
permissions:
5+
contents: read
6+
47
on: [pull_request]
58

69
jobs:

CONTRIBUITING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,4 @@ When creating a PR, use the following process.
5656

5757
Submit Node Scraper documentation changes to our
5858
[documentation](https://github.com/amd/node-scraper/development/README.md). You must update
59-
documentation related to any new feature or API contribution.
59+
documentation related to any new feature or API contribution.

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2025 AMD HPC Application Performance Team
3+
Copyright (c) 2025 Advanced Micro Devices, Inc.
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

Lines changed: 93 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,47 @@
11
# Node Scraper
2-
Node Scraper is a tool which performs automated data collection and analysis for the purposes of system debug.
2+
Node Scraper is a tool which performs automated data collection and analysis for the purposes of
3+
system debug.
34

45
## Installation
56
### Install From Source
6-
Node Scraper requires Python 3.10+ for installation. After cloning this repository, call dev-setup.sh script with 'source'. This script creates an editable install of Node Scraper in a python virtual environment and also configures the pre-commit hooks for the project.
7+
Node Scraper requires Python 3.10+ for installation. After cloning this repository,
8+
call dev-setup.sh script with 'source'. This script creates an editable install of Node Scraper in
9+
a python virtual environment and also configures the pre-commit hooks for the project.
710

811
```sh
912
source dev-setup.sh
1013
```
1114

1215
## CLI Usage
13-
The Node Scraper CLI can be used to run Node Scraper plugins on a target system. The following CLI options are available:
16+
The Node Scraper CLI can be used to run Node Scraper plugins on a target system. The following CLI
17+
options are available:
1418

1519
```sh
16-
usage: node-scraper [-h] [--sys-name STRING] [--sys-location {LOCAL,REMOTE}] [--sys-interaction-level {PASSIVE,INTERACTIVE,DISRUPTIVE}]
17-
[--sys-sku STRING] [--sys-platform STRING] [--plugin-config STRING] [--system-config STRING] [--connection-config STRING]
18-
[--log-path STRING] [--log-level {CRITICAL,FATAL,ERROR,WARN,WARNING,INFO,DEBUG,NOTSET}]
19-
{run-plugins,gen-plugin-config,describe} ...
20+
usage: node-scraper [-h] [--sys-name STRING] [--sys-location {LOCAL,REMOTE}] [--sys-interaction-level {PASSIVE,INTERACTIVE,DISRUPTIVE}] [--sys-sku STRING] [--sys-platform STRING] [--plugin-configs [STRING ...]]
21+
[--system-config STRING] [--connection-config STRING] [--log-path STRING] [--log-level {CRITICAL,FATAL,ERROR,WARN,WARNING,INFO,DEBUG,NOTSET}]
22+
{run-plugins,describe,gen-plugin-config} ...
2023

21-
Error scraper CLI
24+
node scraper CLI
2225

2326
positional arguments:
24-
{run-plugins,gen-plugin-config,describe}
27+
{run-plugins,describe,gen-plugin-config}
2528
Subcommands
2629
run-plugins Run a series of plugins
27-
gen-plugin-config Generate a config for a plugin or list of plugins
2830
describe Display details on a built-in config or plugin
31+
gen-plugin-config Generate a config for a plugin or list of plugins
2932

3033
options:
3134
-h, --help show this help message and exit
32-
--sys-name STRING System name (default: MKM-L1-LANDRE53)
35+
--sys-name STRING System name (default: TheraC55)
3336
--sys-location {LOCAL,REMOTE}
3437
Location of target system (default: LOCAL)
3538
--sys-interaction-level {PASSIVE,INTERACTIVE,DISRUPTIVE}
3639
Specify system interaction level, used to determine the type of actions that plugins can perform (default: INTERACTIVE)
3740
--sys-sku STRING Manually specify SKU of system (default: None)
3841
--sys-platform STRING
3942
Specify system platform (default: None)
40-
--plugin-config STRING
41-
Path to plugin config json (default: None)
43+
--plugin-configs [STRING ...]
44+
built-in config names or paths to plugin config JSONs. Available built-in configs: NodeStatus (default: None)
4245
--system-config STRING
4346
Path to system config json (default: None)
4447
--connection-config STRING
@@ -49,7 +52,8 @@ options:
4952

5053
```
5154
52-
The plugins to run can be specified in two ways, using a plugin JSON config file or using the 'run-plugins' sub command. These two options are not mutually exclusive and can be used together.
55+
The plugins to run can be specified in two ways, using a plugin JSON config file or using the
56+
'run-plugins' sub command. These two options are not mutually exclusive and can be used together.
5357
5458
---
5559
@@ -80,7 +84,13 @@ node-scraper describe plugin <plugin-name>
8084
---
8185
8286
### Plugin Configs
83-
A plugin JSON config should follow the structure of the plugin config model defined here. The globals field is a dictionary of global key-value pairs; values in globals will be passed to any plugin that supports the corresponding key. The plugins field should be a dictionary mapping plugin names to sub-dictionaries of plugin arguments. Lastly, the result_collators attribute is used to define result collator classes that will be run on the plugin results. By default, the CLI adds the TableSummary result collator, which prints a summary of each plugin’s results in a tabular format to the console.
87+
A plugin JSON config should follow the structure of the plugin config model defined here.
88+
The globals field is a dictionary of global key-value pairs; values in globals will be passed to
89+
any plugin that supports the corresponding key. The plugins field should be a dictionary mapping
90+
plugin names to sub-dictionaries of plugin arguments. Lastly, the result_collators attribute is
91+
used to define result collator classes that will be run on the plugin results. By default, the CLI
92+
adds the TableSummary result collator, which prints a summary of each plugin’s results in a
93+
tabular format to the console.
8494
8595
```json
8696
{
@@ -101,13 +111,15 @@ A plugin JSON config should follow the structure of the plugin config model defi
101111
```
102112
103113
### 'gen-plugin-config' sub command
104-
The 'gen-plugin-config' sub command can be used to generate a plugin config JSON file for a plugin or list of plugins that can then be customized. Plugin arguments which have default values will be prepopulated in the JSON file, arguments without default values will have a value of 'null'.
114+
The 'gen-plugin-config' sub command can be used to generate a plugin config JSON file for a plugin
115+
or list of plugins that can then be customized. Plugin arguments which have default values will be
116+
prepopulated in the JSON file, arguments without default values will have a value of 'null'.
105117
106118
#### 'gen-plugin-config' Examples
107119
108120
Generate a config for the DmesgPlugin:
109121
```sh
110-
node-scraper gen-plugin-config DmesgPlugin
122+
node-scraper gen-plugin-config --plugins DmesgPlugin
111123
```
112124
113125
This would produce the following config:
@@ -134,9 +146,12 @@ This would produce the following config:
134146
```
135147
136148
### 'run-plugins' sub command
137-
The plugins to run and their associated arguments can also be specified directly on the CLI using the 'run-plugins' sub-command. Using this sub-command you can specify a plugin name followed by the arguments for that particular plugin. Multiple plugins can be specified at once.
149+
The plugins to run and their associated arguments can also be specified directly on the CLI using
150+
the 'run-plugins' sub-command. Using this sub-command you can specify a plugin name followed by
151+
the arguments for that particular plugin. Multiple plugins can be specified at once.
138152
139-
You can view the available arguments for a particular plugin by running `node-scraper run-plugins <plugin-name> -h`:
153+
You can view the available arguments for a particular plugin by running
154+
`node-scraper run-plugins <plugin-name> -h`:
140155
```sh
141156
usage: node-scraper run-plugins BiosPlugin [-h] [--collection {True,False}] [--analysis {True,False}] [--system-interaction-level STRING]
142157
[--data STRING] [--exp-bios-version [STRING ...]] [--regex-match {True,False}]
@@ -175,3 +190,62 @@ Use plugin configs and 'run-plugins'
175190
```sh
176191
node-scraper run-plugins BiosPlugin
177192
```
193+
194+
195+
### '--plugin-configs' example
196+
A plugin config can be used to compare the system data against the config specifications:
197+
```sh
198+
node-scraper --plugin-configs plugin_config.json
199+
```
200+
Here is an example of a comprehensive plugin config that specifies analyzer args for each plugin:
201+
```json
202+
{
203+
"global_args": {},
204+
"plugins": {
205+
"BiosPlugin": {
206+
"analysis_args": {
207+
"exp_bios_version": "3.5"
208+
}
209+
},
210+
"CmdlinePlugin": {
211+
"analysis_args": {
212+
"cmdline": "imgurl=test NODE=nodename selinux=0 serial console=ttyS1,115200 console=tty0",
213+
"required_cmdline" : "selinux=0"
214+
}
215+
},
216+
"DkmsPlugin": {
217+
"analysis_args": {
218+
"dkms_status": "amdgpu/6.11",
219+
"dkms_version" : "dkms-3.1",
220+
"regex_match" : true
221+
}
222+
},
223+
"KernelPlugin": {
224+
"analysis_args": {
225+
"exp_kernel": "5.11-generic"
226+
}
227+
},
228+
"OsPlugin": {
229+
"analysis_args": {
230+
"exp_os": "Ubuntu 22.04.2 LTS"
231+
}
232+
},
233+
"PackagePlugin": {
234+
"analysis_args": {
235+
"exp_package_ver": {
236+
"gcc": "11.4.0"
237+
},
238+
"regex_match": false
239+
}
240+
},
241+
"RocmPlugin": {
242+
"analysis_args": {
243+
"exp_rocm": "6.5"
244+
}
245+
}
246+
},
247+
"result_collators": {},
248+
"name": "plugin_config",
249+
"desc": "My golden config"
250+
}
251+
```

nodescraper/cli/cli.py

Lines changed: 43 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -518,7 +518,7 @@ def parse_gen_plugin_config(
518518
sys.exit(1)
519519

520520

521-
def log_system_info(log_path: str, system_info: SystemInfo, logger: logging.Logger):
521+
def log_system_info(log_path: str | None, system_info: SystemInfo, logger: logging.Logger):
522522
"""dump system info object to json log
523523
524524
Args:
@@ -595,54 +595,61 @@ def main(arg_input: Optional[list[str]] = None):
595595
config_reg = ConfigRegistry()
596596
parser, plugin_subparser_map = build_parser(plugin_reg, config_reg)
597597

598-
top_level_args, plugin_arg_map = process_args(arg_input, list(plugin_subparser_map.keys()))
598+
try:
599+
top_level_args, plugin_arg_map = process_args(arg_input, list(plugin_subparser_map.keys()))
599600

600-
parsed_args = parser.parse_args(top_level_args)
601+
parsed_args = parser.parse_args(top_level_args)
601602

602-
if parsed_args.log_path and parsed_args.subcmd not in ["gen-plugin-config", "describe"]:
603-
log_path = os.path.join(
604-
parsed_args.log_path,
605-
f"scraper_logs_{datetime.datetime.now().strftime('%Y_%m_%d-%I_%M_%S_%p')}",
606-
)
607-
os.makedirs(log_path)
608-
else:
609-
log_path = None
610-
611-
logger = setup_logger(parsed_args.log_level, log_path)
612-
if log_path:
613-
logger.info("Log path: %s", log_path)
603+
if parsed_args.log_path and parsed_args.subcmd not in ["gen-plugin-config", "describe"]:
604+
log_path = os.path.join(
605+
parsed_args.log_path,
606+
f"scraper_logs_{datetime.datetime.now().strftime('%Y_%m_%d-%I_%M_%S_%p')}",
607+
)
608+
os.makedirs(log_path)
609+
else:
610+
log_path = None
614611

615-
if parsed_args.subcmd == "describe":
616-
parse_describe(parsed_args, plugin_reg, config_reg, logger)
612+
logger = setup_logger(parsed_args.log_level, log_path)
613+
if log_path:
614+
logger.info("Log path: %s", log_path)
617615

618-
if parsed_args.subcmd == "gen-plugin-config":
619-
parse_gen_plugin_config(parsed_args, plugin_reg, config_reg, logger)
616+
if parsed_args.subcmd == "describe":
617+
parse_describe(parsed_args, plugin_reg, config_reg, logger)
620618

621-
parsed_plugin_args = {}
622-
for plugin, plugin_args in plugin_arg_map.items():
623-
try:
624-
parsed_plugin_args[plugin] = plugin_subparser_map[plugin][0].parse_args(plugin_args)
625-
except Exception:
626-
logger.exception("Exception parsing args for plugin: %s", plugin)
619+
if parsed_args.subcmd == "gen-plugin-config":
620+
parse_gen_plugin_config(parsed_args, plugin_reg, config_reg, logger)
627621

628-
if not parsed_plugin_args and not parsed_args.plugin_configs:
629-
logger.info("No plugins config args specified, running default config: %s", DEFAULT_CONFIG)
630-
plugin_configs = [DEFAULT_CONFIG]
631-
else:
632-
plugin_configs = parsed_args.plugin_configs or []
622+
parsed_plugin_args = {}
623+
for plugin, plugin_args in plugin_arg_map.items():
624+
try:
625+
parsed_plugin_args[plugin] = plugin_subparser_map[plugin][0].parse_args(plugin_args)
626+
except Exception as e:
627+
logger.error("%s exception parsing args for plugin: %s", str(e), plugin)
633628

634-
system_info = get_system_info(parsed_args)
635-
log_system_info(log_path, system_info, logger)
629+
if not parsed_plugin_args and not parsed_args.plugin_configs:
630+
logger.info(
631+
"No plugins config args specified, running default config: %s", DEFAULT_CONFIG
632+
)
633+
plugin_configs = [DEFAULT_CONFIG]
634+
else:
635+
plugin_configs = parsed_args.plugin_configs or []
636636

637-
plugin_executor = PluginExecutor(
638-
logger=logger,
639-
plugin_configs=get_plugin_configs(
637+
plugin_config_inst_list = get_plugin_configs(
640638
plugin_config_input=plugin_configs,
641639
system_interaction_level=parsed_args.sys_interaction_level,
642640
built_in_configs=config_reg.configs,
643641
parsed_plugin_args=parsed_plugin_args,
644642
plugin_subparser_map=plugin_subparser_map,
645-
),
643+
)
644+
645+
system_info = get_system_info(parsed_args)
646+
log_system_info(log_path, system_info, logger)
647+
except Exception as e:
648+
parser.error(str(e))
649+
650+
plugin_executor = PluginExecutor(
651+
logger=logger,
652+
plugin_configs=plugin_config_inst_list,
646653
connections=parsed_args.connection_config,
647654
system_info=system_info,
648655
log_path=log_path,

nodescraper/cli/inputargtypes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ def process_file_arg(self, file_path: str) -> TModelType:
104104
return self.model(**data)
105105
except ValidationError as e:
106106
raise argparse.ArgumentTypeError(
107-
f"Validation errors when processing {file_path}: {e.errors()}"
107+
f"Validation errors when processing {file_path}: {e.errors(include_url=False)}"
108108
) from e
109109

110110

nodescraper/interfaces/analyzerargs.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,16 @@
2626
from abc import ABC, abstractmethod
2727
from typing import Optional
2828

29-
from pydantic import BaseModel, ConfigDict, Field
29+
from pydantic import BaseModel, Field
3030

3131
from nodescraper.models.datamodel import DataModel
3232

3333

3434
class AnalyzerArgs(BaseModel, ABC):
3535
data_model: Optional["DataModel"] = Field(default=None)
3636
# model_config = {"exclude_none": True}
37-
model_config = ConfigDict(exclude_node=True)
37+
# model_config = ConfigDict(exclude_noe=True)
38+
model_config = {"extra": "forbid", "exclude_node": True}
3839

3940
@classmethod
4041
@abstractmethod

nodescraper/interfaces/dataanalyzertask.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,12 @@ def wrapper(
7676
if not analyze_arg_model:
7777
raise ValueError("No model defined for analysis args")
7878
args = analyze_arg_model(**args) # type: ignore
79-
8079
func(analyzer, data, args)
8180
except ValidationError as exception:
8281
analyzer._log_event(
8382
category=EventCategory.RUNTIME,
8483
description="Validation error during analysis",
85-
data=get_exception_traceback(exception),
84+
data={"errors": exception.errors(include_url=False)},
8685
priority=EventPriority.CRITICAL,
8786
console_log=True,
8887
)

nodescraper/interfaces/datacollectortask.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
from nodescraper.interfaces.task import SystemCompatibilityError, Task
4242
from nodescraper.models import DataModel, SystemInfo, TaskResult
4343
from nodescraper.typeutils import TypeUtils
44-
from nodescraper.utils import get_exception_details, get_exception_traceback
44+
from nodescraper.utils import get_exception_traceback
4545

4646
from .connectionmanager import TConnection
4747
from .taskresulthook import TaskResultHook
@@ -76,7 +76,7 @@ def wrapper(
7676
collector._log_event(
7777
category=EventCategory.RUNTIME,
7878
description="Pydantic validation error",
79-
data=get_exception_details(exception),
79+
data={"errors": exception.errors(include_url=False)},
8080
priority=EventPriority.CRITICAL,
8181
console_log=True,
8282
)

0 commit comments

Comments
 (0)