Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
a6446ed
scaleout switch port
sunnyhe2 Jun 10, 2026
83a2773
rename
sunnyhe2 Jun 16, 2026
27a45ab
arista typo
sunnyhe2 Jun 22, 2026
83acfd5
cleanup
sunnyhe2 Jun 22, 2026
a086778
union add
sunnyhe2 Jun 22, 2026
365c143
Merge branch 'development' into sunny_switch_port
alexandraBara Jun 23, 2026
44ce6a1
Merge branch 'development' into sunny_switch_port
alexandraBara Jun 23, 2026
7a62353
added count/part
alexandraBara Jun 23, 2026
9cbf5e7
utest for new helper file
alexandraBara Jun 23, 2026
dbf5d77
Merge branch 'development' into dmesg_update
alexandraBara Jun 23, 2026
0162d14
mini-refactor
sunnyhe2 Jun 24, 2026
ddef498
CMD align
sunnyhe2 Jun 24, 2026
d02ebef
execution status NOT_RAN
sunnyhe2 Jun 24, 2026
fa29363
inlcuding cache and buffers in total free
alexandraBara Jun 25, 2026
82dffc8
hardened collector/analyzer+OS discovery for switch+utest
alexandraBara Jun 25, 2026
3ca3d6d
Merge pull request #240 from amd/alex_mem
alexandraBara Jun 25, 2026
7828618
added missing file
alexandraBara Jun 25, 2026
4d6e21f
Quick self-review and adjustments
graepaul Jun 25, 2026
890cfb0
Adding tests
graepaul Jun 25, 2026
95662cd
Updating to work with 3.9
graepaul Jun 25, 2026
290f842
Update Tests
graepaul Jun 25, 2026
f19bf89
docs: Update plugin documentation [automated]
github-actions[bot] Jun 26, 2026
7de82f9
Merge branch 'development' into dmesg_update
alexandraBara Jun 26, 2026
3403976
Merge branch 'development' into feature/plugin_registry_cache_adjustm…
alexandraBara Jun 26, 2026
d48a2da
Merge pull request #236 from amd/dmesg_update
alexandraBara Jun 26, 2026
67325b4
Merge branch 'development' into automated-plugin-docs-update
alexandraBara Jun 26, 2026
5c2a5aa
worked html_view into all plugins
alexandraBara Jun 26, 2026
46ec016
Merge pull request #242 from amd/automated-plugin-docs-update
alexandraBara Jun 26, 2026
718b988
Merge branch 'development' into feature/plugin_registry_cache_adjustm…
graepaul Jun 26, 2026
f8b0600
Merge branch 'development' into sunny_switch_port
alexandraBara Jun 29, 2026
5681eea
Update after PR comments
graepaul Jun 29, 2026
2c91ae7
Merge branch 'feature/plugin_registry_cache_adjustments' of github.co…
graepaul Jun 29, 2026
8faade7
Add copy
graepaul Jun 29, 2026
ced602b
Merge pull request #241 from amd/feature/plugin_registry_cache_adjust…
alexandraBara Jun 29, 2026
9c78927
Merge branch 'development' into sunny_switch_port
alexandraBara Jun 29, 2026
162064f
Merge pull request #232 from amd/sunny_switch_port
alexandraBara Jun 29, 2026
33e72e9
docs: Update plugin documentation [automated]
github-actions[bot] Jun 30, 2026
b089274
Merge pull request #243 from amd/automated-plugin-docs-update
alexandraBara Jun 30, 2026
6622d64
added ignore_rules
alexandraBara Jul 2, 2026
07e4ab7
utest fix
alexandraBara Jul 6, 2026
f921f50
subclass fix
alexandraBara Jul 6, 2026
6536551
Merge pull request #244 from amd/alex_mce
alexandraBara Jul 6, 2026
af2c867
update for github runners
alexandraBara Jul 6, 2026
883aa52
Merge pull request #245 from amd/alex_runners
alexandraBara Jul 6, 2026
f2ecee7
workflow update
alexandraBara Jul 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/code_quality_checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
container: python:3.9

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
fetch-depth: 0 # Fetch all history for pre-commit to work

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/functional-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
container: python:3.9

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5

- name: Install xmllint
run: |
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release-trusted-publisher.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:

steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0 # Fetch all history and tags
token: ${{ secrets.GITHUB_TOKEN }}
Expand All @@ -34,7 +34,7 @@ jobs:
git fetch --tags --force

- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: '3.9'

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/unit-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
container: python:3.9

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5

- name: Install xmllint
run: |
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/update-plugin-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
export HOME=/tmp/github-actions-home

- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
token: ${{ secrets.GITHUB_TOKEN }}

Expand All @@ -53,14 +53,14 @@ jobs:
pre-commit run --files docs/PLUGIN_DOC.md README.md || true

- name: Create Pull Request
uses: peter-evans/create-pull-request@v6
uses: peter-evans/create-pull-request@v8
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "docs: Update plugin documentation [automated]"
committer: "github-actions[bot] <github-actions[bot]@users.noreply.github.com>"
author: "github-actions[bot] <github-actions[bot]@users.noreply.github.com>"
branch: automated-plugin-docs-update
delete-branch: true
delete-branch: false
title: "docs: Update plugin documentation [automated]"
body: |
Automated plugin documentation update generated by workflow.
Expand Down
392 changes: 375 additions & 17 deletions docs/PLUGIN_DOC.md

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion nodescraper/base/inbandcollectortask.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ def _run_sut_cmd(
timeout: int = 300,
strip: bool = True,
log_artifact: bool = True,
html_view: Optional[bool] = None,
) -> CommandArtifact:
"""
Run a command on the SUT and return the result.
Expand All @@ -90,14 +91,18 @@ def _run_sut_cmd(
timeout (int, optional): command timeout in seconds. Defaults to 300.
strip (bool, optional): whether output should be stripped. Defaults to True.
log_artifact (bool, optional): whether we should log the command result. Defaults to True.
html_view (Optional[bool], optional): whether to include this command in HTML
artifacts. When omitted, uses collection_args.html_view.

Returns:
CommandArtifact: The result of the command execution, which includes stdout, stderr, and exit code.
"""
command_res = self.connection.run_command(
command=command, sudo=sudo, timeout=timeout, strip=strip
)
if log_artifact:
effective_html_view = self._effective_html_view(html_view)
if log_artifact or effective_html_view:
command_res.log_html = effective_html_view
self.result.artifacts.append(command_res)

return command_res
Expand Down
209 changes: 209 additions & 0 deletions nodescraper/base/match_ignore.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
###############################################################################
#
# MIT License
#
# Copyright (c) 2026 Advanced Micro Devices, Inc.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
###############################################################################
import re
from dataclasses import dataclass
from typing import Optional, Sequence, Union

_MCE_BANK_RE = re.compile(r"\bMC(?P<bank>\d+)_STATUS\b", re.IGNORECASE)

MceBankSpec = Union[int, str]
IgnoreMatchRuleSpec = dict[str, object]


@dataclass(frozen=True)
class ParsedIgnoreMatchRule:
line_regex: Optional[re.Pattern[str]] = None
match_regex: Optional[re.Pattern[str]] = None
messages: Optional[frozenset[str]] = None
mce_banks: Optional[frozenset[int]] = None


def parse_mce_bank_spec(spec: Sequence[MceBankSpec]) -> frozenset[int]:
"""Expand MCA bank ids and inclusive ranges into a set of bank numbers.

Args:
spec: Bank ids, bank ranges like "60-63", or an empty sequence.

Returns:
frozenset[int]: MCA bank numbers.
"""
banks: set[int] = set()
for entry in spec:
if isinstance(entry, int):
if entry < 0:
raise ValueError(f"Invalid MCE bank number: {entry}")
banks.add(entry)
continue

token = str(entry).strip()
if not token:
raise ValueError("Empty MCE bank entry")

if "-" in token:
start_text, end_text = token.split("-", 1)
start = int(start_text.strip())
end = int(end_text.strip())
if start < 0 or end < 0 or start > end:
raise ValueError(f"Invalid MCE bank range: {entry}")
banks.update(range(start, end + 1))
continue

bank = int(token)
if bank < 0:
raise ValueError(f"Invalid MCE bank number: {entry}")
banks.add(bank)

return frozenset(banks)


def extract_mce_bank_from_line(line: str) -> Optional[int]:
"""Return the MCA bank number from a dmesg line, if present.

Args:
line: Single dmesg log line.

Returns:
Optional[int]: MCA bank number, or None when the line has no MCn_STATUS token.
"""
match = _MCE_BANK_RE.search(line)
if match is None:
return None
return int(match.group("bank"))


def extract_mce_banks_from_text(text: str) -> frozenset[int]:
"""Return all MCA bank numbers referenced in text.

Args:
text: Log line or regex match text.

Returns:
frozenset[int]: MCA bank numbers found in text.
"""
return frozenset(int(match.group("bank")) for match in _MCE_BANK_RE.finditer(text))


def parse_ignore_match_rules(
spec: Optional[Sequence[IgnoreMatchRuleSpec]],
) -> tuple[list[ParsedIgnoreMatchRule], frozenset[int]]:
"""Parse ignore_match_rules config into compiled skip rules and ignored MCA banks.

Args:
spec: Rule dicts using line_regex, match_regex, message, and/or mce_banks.

Returns:
tuple[list[ParsedIgnoreMatchRule], frozenset[int]]: Parsed rules and all ignored MCA banks.
"""
if not spec:
return [], frozenset()

parsed_rules: list[ParsedIgnoreMatchRule] = []
ignored_mce_banks: set[int] = set()

for index, raw_rule in enumerate(spec):
if not isinstance(raw_rule, dict):
raise ValueError(f"ignore_match_rules[{index}] must be a dict")

line_regex = raw_rule.get("line_regex")
match_regex = raw_rule.get("match_regex")
message = raw_rule.get("message")
mce_banks = raw_rule.get("mce_banks")

if line_regex is None and match_regex is None and mce_banks is None:
raise ValueError(
f"ignore_match_rules[{index}] must specify at least one of "
"line_regex, match_regex, or mce_banks"
)

messages: Optional[frozenset[str]] = None
if message is not None:
if isinstance(message, str):
messages = frozenset({message})
elif isinstance(message, list) and all(isinstance(item, str) for item in message):
messages = frozenset(message)
else:
raise ValueError(
f"ignore_match_rules[{index}].message must be a string or list of strings"
)

parsed_mce_banks: Optional[frozenset[int]] = None
if mce_banks is not None:
if not isinstance(mce_banks, list):
raise ValueError(f"ignore_match_rules[{index}].mce_banks must be a list")
parsed_mce_banks = parse_mce_bank_spec(mce_banks)
ignored_mce_banks.update(parsed_mce_banks)

parsed_rules.append(
ParsedIgnoreMatchRule(
line_regex=re.compile(str(line_regex)) if line_regex is not None else None,
match_regex=re.compile(str(match_regex)) if match_regex is not None else None,
messages=messages,
mce_banks=parsed_mce_banks,
)
)

return parsed_rules, frozenset(ignored_mce_banks)


def should_ignore_match(
*,
line: str,
match_text: str,
error_regex_message: str,
rules: Sequence[ParsedIgnoreMatchRule],
) -> bool:
"""Return True when any ignore rule matches the current regex hit.

Args:
line: Full log line containing the match.
match_text: Regex match text.
error_regex_message: ErrorRegex.message for the pattern that matched.
rules: Parsed ignore rules; first matching rule wins.

Returns:
bool: True when the match should be skipped.
"""
for rule in rules:
if rule.messages is not None and error_regex_message not in rule.messages:
continue

if rule.mce_banks is not None:
banks = extract_mce_banks_from_text(match_text)
if not banks:
line_bank = extract_mce_bank_from_line(line)
banks = frozenset({line_bank}) if line_bank is not None else frozenset()
if not banks or not banks.issubset(rule.mce_banks):
continue

if rule.line_regex is not None and rule.line_regex.search(line) is None:
continue

if rule.match_regex is not None and rule.match_regex.search(match_text) is None:
continue

return True

return False
28 changes: 26 additions & 2 deletions nodescraper/base/redfishcollectortask.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,18 +68,23 @@ def _run_redfish_get(
self,
path: str,
log_artifact: bool = True,
html_view: Optional[bool] = None,
) -> RedfishGetResult:
"""Run a Redfish GET request and return the result.

Args:
path: Redfish URI path
log_artifact: If True, append the result to self.result.artifacts.
html_view: When set, controls HTML artifact output. When omitted, uses
collection_args.html_view.

Returns:
RedfishGetResult: path, success, data (or error), status_code.
"""
res = self.connection.run_get(path)
if log_artifact:
effective_html_view = self._effective_html_view(html_view)
if log_artifact or effective_html_view:
res.log_html = effective_html_view
self.result.artifacts.append(res)
return res

Expand All @@ -88,6 +93,7 @@ def _run_redfish_get_paged(
path: str,
max_pages: int = 200,
log_artifact: bool = True,
html_view: Optional[bool] = None,
) -> RedfishGetResult:
"""
Run a Redfish GET and follow Members@odata.nextLink pagination, merging all pages into a single response.
Expand All @@ -96,11 +102,29 @@ def _run_redfish_get_paged(
path (str): Redfish URI path.
max_pages (int, optional): safety cap on the number of pages to follow. Defaults to 200.
log_artifact (bool, optional): whether we should log the merged result. Defaults to True.
html_view (Optional[bool], optional): whether to include this request in HTML artifacts.
When omitted, uses collection_args.html_view.

Returns:
RedfishGetResult: path, success, merged data (or error), status_code.
"""
res = self.connection.run_get_paged(path, max_pages=max_pages)
if log_artifact:
effective_html_view = self._effective_html_view(html_view)
if log_artifact or effective_html_view:
res.log_html = effective_html_view
self.result.artifacts.append(res)
return res

def _append_redfish_artifact(
self,
res: RedfishGetResult,
*,
log_artifact: bool = True,
html_view: Optional[bool] = None,
) -> RedfishGetResult:
"""Append a Redfish GET result to task artifacts with log flags applied."""
effective_html_view = self._effective_html_view(html_view)
if log_artifact or effective_html_view:
res.log_html = effective_html_view
self.result.artifacts.append(res)
return res
Loading