Skip to content

Commit 8258438

Browse files
Merge pull request #11 from amd/development
development -> main merge
2 parents 63033e3 + a739ad1 commit 8258438

25 files changed

Lines changed: 387 additions & 137 deletions

.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: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
1-
# Contribute to Scraper
1+
# Contribute to Node Scraper
22

33
AMD values and encourages contributions to our code and documentation. If you want to contribute
44
to our repository, first review the following guidance.
55

66
## Development workflow
77

8-
Scraper uses GitHub to host code, collaborate, and manage version control. We use pull requests (PRs)
8+
Node Scraper uses GitHub to host code, collaborate, and manage version control. We use pull requests (PRs)
99
for all changes within our repository. We use
10-
[GitHub issues](https://github.com/<placeholder>/issues) to track known issues, such as
10+
[GitHub issues](https://github.com/amd/node-scraper/issues) to track known issues, such as
1111
bugs.
1212

1313
### Issue tracking
1414

1515
Before filing a new issue, search the
16-
[existing issues](https://github.com/<placeholder>/issues) to make sure your issue isn't
16+
[existing issues](https://github.com/amd/node-scraper/issues) to make sure your issue isn't
1717
already listed.
1818

1919
General issue guidelines:
@@ -33,12 +33,12 @@ General issue guidelines:
3333
### Pull requests
3434

3535
When you create a pull request, you should target the default branch. Our repository uses the
36-
**develop** branch as the default integration branch.
36+
**development** branch as the default integration branch.
3737

3838
When creating a PR, use the following process.
3939

4040
* Identify the issue you want to fix
41-
* Target the default branch (usually the **develop** branch) for integration
41+
* Target the default branch (usually the **development** branch) for integration
4242
* Ensure your code builds successfully
4343
* Do not break existing test cases
4444
* New functionality is only merged with new unit tests
@@ -52,12 +52,8 @@ When creating a PR, use the following process.
5252
> By creating a PR, you agree to allow your contribution to be licensed under the
5353
> terms of the LICENSE.txt file.
5454
55-
### New feature development
56-
57-
Use the [GitHub Discussion forum](https://github.com/<placeholder>/discussions)
58-
5955
### Documentation
6056

61-
Submit Scraper documentation changes to our
62-
[documentation](https://github.com/<placeholder>). You must update
57+
Submit Node Scraper documentation changes to our
58+
[documentation](https://github.com/amd/node-scraper/development/README.md). You must update
6359
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: 48 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,7 @@ def parse_gen_plugin_config(
510510
sys.exit(1)
511511

512512

513-
def log_system_info(log_path: str, system_info: SystemInfo, logger: logging.Logger):
513+
def log_system_info(log_path: str | None, system_info: SystemInfo, logger: logging.Logger):
514514
"""dump system info object to json log
515515
516516
Args:
@@ -544,54 +544,61 @@ def main(arg_input: Optional[list[str]] = None):
544544
config_reg = ConfigRegistry()
545545
parser, plugin_subparser_map = build_parser(plugin_reg, config_reg)
546546

547-
top_level_args, plugin_arg_map = process_args(arg_input, list(plugin_subparser_map.keys()))
548-
549-
parsed_args = parser.parse_args(top_level_args)
550-
551-
if parsed_args.log_path and parsed_args.subcmd not in ["gen-plugin-config", "describe"]:
552-
log_path = os.path.join(
553-
parsed_args.log_path,
554-
f"scraper_logs_{datetime.datetime.now().strftime('%Y_%m_%d-%I_%M_%S_%p')}",
555-
)
556-
os.makedirs(log_path)
557-
else:
558-
log_path = None
559-
560-
logger = setup_logger(parsed_args.log_level, log_path)
561-
if log_path:
562-
logger.info("Log path: %s", log_path)
563-
564-
if parsed_args.subcmd == "describe":
565-
parse_describe(parsed_args, plugin_reg, config_reg, logger)
566-
567-
if parsed_args.subcmd == "gen-plugin-config":
568-
parse_gen_plugin_config(parsed_args, plugin_reg, config_reg, logger)
569-
570-
parsed_plugin_args = {}
571-
for plugin, plugin_args in plugin_arg_map.items():
572-
try:
573-
parsed_plugin_args[plugin] = plugin_subparser_map[plugin][0].parse_args(plugin_args)
574-
except Exception:
575-
logger.exception("Exception parsing args for plugin: %s", plugin)
547+
try:
548+
top_level_args, plugin_arg_map = process_args(arg_input, list(plugin_subparser_map.keys()))
576549

577-
if not parsed_plugin_args and not parsed_args.plugin_configs:
578-
logger.info("No plugins config args specified, running default config: %s", DEFAULT_CONFIG)
579-
plugin_configs = [DEFAULT_CONFIG]
580-
else:
581-
plugin_configs = parsed_args.plugin_configs or []
550+
parsed_args = parser.parse_args(top_level_args)
582551

583-
system_info = get_system_info(parsed_args)
584-
log_system_info(log_path, system_info, logger)
552+
if parsed_args.log_path and parsed_args.subcmd not in ["gen-plugin-config", "describe"]:
553+
log_path = os.path.join(
554+
parsed_args.log_path,
555+
f"scraper_logs_{datetime.datetime.now().strftime('%Y_%m_%d-%I_%M_%S_%p')}",
556+
)
557+
os.makedirs(log_path)
558+
else:
559+
log_path = None
560+
561+
logger = setup_logger(parsed_args.log_level, log_path)
562+
if log_path:
563+
logger.info("Log path: %s", log_path)
564+
565+
if parsed_args.subcmd == "describe":
566+
parse_describe(parsed_args, plugin_reg, config_reg, logger)
567+
568+
if parsed_args.subcmd == "gen-plugin-config":
569+
parse_gen_plugin_config(parsed_args, plugin_reg, config_reg, logger)
570+
571+
parsed_plugin_args = {}
572+
for plugin, plugin_args in plugin_arg_map.items():
573+
try:
574+
parsed_plugin_args[plugin] = plugin_subparser_map[plugin][0].parse_args(plugin_args)
575+
except Exception as e:
576+
logger.error("%s exception parsing args for plugin: %s", str(e), plugin)
577+
578+
if not parsed_plugin_args and not parsed_args.plugin_configs:
579+
logger.info(
580+
"No plugins config args specified, running default config: %s", DEFAULT_CONFIG
581+
)
582+
plugin_configs = [DEFAULT_CONFIG]
583+
else:
584+
plugin_configs = parsed_args.plugin_configs or []
585585

586-
plugin_executor = PluginExecutor(
587-
logger=logger,
588-
plugin_configs=get_plugin_configs(
586+
plugin_config_inst_list = get_plugin_configs(
589587
plugin_config_input=plugin_configs,
590588
system_interaction_level=parsed_args.sys_interaction_level,
591589
built_in_configs=config_reg.configs,
592590
parsed_plugin_args=parsed_plugin_args,
593591
plugin_subparser_map=plugin_subparser_map,
594-
),
592+
)
593+
594+
system_info = get_system_info(parsed_args)
595+
log_system_info(log_path, system_info, logger)
596+
except Exception as e:
597+
parser.error(str(e))
598+
599+
plugin_executor = PluginExecutor(
600+
logger=logger,
601+
plugin_configs=plugin_config_inst_list,
595602
connections=parsed_args.connection_config,
596603
system_info=system_info,
597604
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/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
)

0 commit comments

Comments
 (0)