diff --git a/docs/PLUGIN_DOC.md b/docs/PLUGIN_DOC.md
index deebb1a6..fe6612ae 100644
--- a/docs/PLUGIN_DOC.md
+++ b/docs/PLUGIN_DOC.md
@@ -17,14 +17,14 @@
| KernelPlugin | sh -c 'uname -a' sh -c 'cat /proc/sys/kernel/numa_balancing' wmic os get Version /Value | **Analyzer Args:** - `exp_kernel`: Union[str, list] — Expected kernel version string(s) to match (e.g. from uname -a). - `exp_numa`: Optional[int] — Expected value for kernel.numa_balancing (e.g. 0 or 1). - `regex_match`: bool — If True, match exp_kernel as regex; otherwise exact match. | - | [KernelDataModel](#KernelDataModel-Model) | [KernelCollector](#Collector-Class-KernelCollector) | [KernelAnalyzer](#Data-Analyzer-Class-KernelAnalyzer) |
| KernelModulePlugin | cat /proc/modules modinfo amdgpu wmic os get Version /Value | **Analyzer Args:** - `kernel_modules`: dict[str, dict] — Expected kernel module name -> {version, etc.}. Analyzer checks collected modules match. - `regex_filter`: list[str] — List of regex patterns to filter which collected modules are checked (default: amd). | - | [KernelModuleDataModel](#KernelModuleDataModel-Model) | [KernelModuleCollector](#Collector-Class-KernelModuleCollector) | [KernelModuleAnalyzer](#Data-Analyzer-Class-KernelModuleAnalyzer) |
| MemoryPlugin | free -b lsmem numactl -H wmic OS get FreePhysicalMemory /Value; wmic ComputerSystem get TotalPhysicalMemory /Value | **Analyzer Args:** - `ratio`: float — Required free-memory ratio (0-1). Analysis fails if free/total < ratio. - `memory_threshold`: str — Minimum free memory required (e.g. '30Gi', '1T'). Used when ratio is not sufficient. | - | [MemoryDataModel](#MemoryDataModel-Model) | [MemoryCollector](#Collector-Class-MemoryCollector) | [MemoryAnalyzer](#Data-Analyzer-Class-MemoryAnalyzer) |
-| NetworkPlugin | ip addr show curl ethtool -S {interface} ethtool {interface} lldpcli show neighbor lldpctl ip neighbor show ping rdma link -j ip route show ip rule show wget | **Built-in Regexes:** - tx_pfc_frames is non-zero: `^tx_pfc_frames$` - tx_pfc_ena_frames_pri* is non-zero: `^tx_pfc_ena_frames_pri\d+$` - pfc_pri*_tx_transitions is non-zero: `^pfc_pri\d+_tx_transitions$` **Analyzer Args:** - `error_regex`: Union[list[nodescraper.base.regexanalyzer.ErrorRegex], list[dict], NoneType] — Custom error regex patterns; each item can be ErrorRegex or dict with category/pattern. | **Collection Args:** - `html_view`: bool — When true, include logged command artifacts in command_artifacts.html using human-readable output. Arista collectors... - `url`: Optional[str] — Optional URL to probe for network connectivity (used with netprobe). - `netprobe`: Optional[Literal['ping', 'wget', 'curl']] — Tool to use for network connectivity probe: ping, wget, or curl. | [NetworkDataModel](#NetworkDataModel-Model) | [NetworkCollector](#Collector-Class-NetworkCollector) | [NetworkAnalyzer](#Data-Analyzer-Class-NetworkAnalyzer) |
+| NetworkPlugin | ip addr show curl ethtool -S {interface} ethtool {interface} lldpcli show neighbor lldpctl ip neighbor show ping rdma link -j ip route show ip rule show wget | **Analyzer Args:** - `error_regex`: Union[list[nodescraper.base.regexanalyzer.ErrorRegex], list[dict], NoneType] — Custom error regex patterns; each item can be ErrorRegex or dict with category/pattern. | **Collection Args:** - `html_view`: bool — When true, include logged command artifacts in command_artifacts.html using human-readable output. Arista collectors... - `url`: Optional[str] — Optional URL to probe for network connectivity (used with netprobe). - `netprobe`: Optional[Literal['ping', 'wget', 'curl']] — Tool to use for network connectivity probe: ping, wget, or curl. - `exclusion_regex`: Optional[list[str]] — Regex patterns matched (search) against netdev/interface names; any match is skipped (ethtool not run against it). | [NetworkDataModel](#NetworkDataModel-Model) | [NetworkCollector](#Collector-Class-NetworkCollector) | [NetworkAnalyzer](#Data-Analyzer-Class-NetworkAnalyzer) |
| NicPlugin | niccli --listdev niccli --list niccli --list_devices niccli -dev {device_num} nvm -getoption pcie_relaxed_ordering niccli --dev {device_num} nvm --getoption pcie_relaxed_ordering niccli -dev {device_num} nvm -getoption performance_profile niccli --dev {device_num} nvm --getoption performance_profile niccli -dev {device_num} nvm -getoption support_rdma -scope 0 niccli -dev {device_num} getqos niccli --dev {device_num} nvm --getoption support_rdma --scope 0 niccli --dev {device_num} qos --ets --show niccli --version nicctl show card nicctl --version nicctl show card flash partition --json nicctl show card interrupts --json nicctl show card logs --non-persistent nicctl show card logs --boot-fault nicctl show card logs --persistent nicctl show card profile --json nicctl show card time --json nicctl show card statistics packet-buffer summary --json nicctl show lif statistics --json nicctl show lif internal queue-to-ud-pinning nicctl show pipeline internal anomalies nicctl show pipeline internal rsq-ring nicctl show pipeline internal statistics memory nicctl show port fsm nicctl show port transceiver --json nicctl show port statistics --json nicctl show port internal mac nicctl show qos headroom --json nicctl show rdma queue --json nicctl show rdma queue-pair --detail --json nicctl show version firmware nicctl show dcqcn nicctl show environment nicctl show lif nicctl show pcie ats nicctl show port nicctl show qos nicctl show rdma statistics nicctl show version host-software nicctl show dcqcn --card {card_id} --json nicctl show card hardware-config --card {card_id} | **Analyzer Args:** - `expected_values`: Optional[Dict[str, Dict[str, Any]]] — Per-command expected checks keyed by canonical key (see command_to_canonical_key). - `performance_profile_expected`: str — Expected Broadcom performance_profile value (case-insensitive). Default RoCE. - `support_rdma_disabled_values`: List[str] — Values that indicate RDMA is not supported (case-insensitive). - `pcie_relaxed_ordering_expected`: str — Expected Broadcom pcie_relaxed_ordering value (e.g. 'Relaxed ordering = enabled'); checked case-insensitively. Defaul... - `expected_qos_prio_map`: Optional[Dict[Any, Any]] — Expected priority-to-TC map (e.g. {0: 0, 1: 1}; keys may be int or str in config). Checked per device when set. - `expected_qos_pfc_enabled`: Optional[int] — Expected PFC enabled value (0/1 or bitmask). Checked per device when set. - `expected_qos_tsa_map`: Optional[Dict[Any, Any]] — Expected TSA map for ETS (e.g. {0: 'ets', 1: 'strict'}; keys may be int or str in config). Checked per device when set. - `expected_qos_tc_bandwidth`: Optional[List[int]] — Expected TC bandwidth percentages. Checked per device when set. - `require_qos_consistent_across_adapters`: bool — When True and no expected_qos_* are set, require all adapters to have the same prio_map, pfc_enabled, and tsa_map. - `nicctl_log_error_regex`: Optional[List[Dict[str, Any]]] — Optional list of error patterns for nicctl show card logs. | **Collection Args:** - `html_view`: bool — When true, include logged command artifacts in command_artifacts.html using human-readable output. Arista collectors... - `commands`: Optional[List[str]] — Optional list of niccli/nicctl commands to run. When None, default command set is used. - `use_sudo_niccli`: bool — If True, run niccli commands with sudo when required. - `use_sudo_nicctl`: bool — If True, run nicctl commands with sudo when required. | [NicDataModel](#NicDataModel-Model) | [NicCollector](#Collector-Class-NicCollector) | [NicAnalyzer](#Data-Analyzer-Class-NicAnalyzer) |
| NvmePlugin | nvme smart-log {dev} nvme error-log {dev} --log-entries=256 nvme id-ctrl {dev} nvme id-ns {dev}{ns} nvme fw-log {dev} nvme self-test-log {dev} nvme get-log {dev} --log-id=6 --log-len=512 nvme telemetry-log {dev} --output-file={dev}_{f_name} nvme list -o json | - | - | [NvmeDataModel](#NvmeDataModel-Model) | [NvmeCollector](#Collector-Class-NvmeCollector) | - |
| OsPlugin | sh -c '( lsb_release -ds || (cat /etc/*release | grep PRETTY_NAME) || uname -om ) 2>/dev/null | head -n1' cat /etc/*release | grep VERSION_ID wmic os get Version /value wmic os get Caption /Value | **Analyzer Args:** - `exp_os`: Union[str, list] — Expected OS name/version string(s) to match (e.g. from lsb_release or /etc/os-release). - `exact_match`: bool — If True, require exact match for exp_os; otherwise substring match. | - | [OsDataModel](#OsDataModel-Model) | [OsCollector](#Collector-Class-OsCollector) | [OsAnalyzer](#Data-Analyzer-Class-OsAnalyzer) |
| PackagePlugin | dnf list --installed dpkg-query -W pacman -Q cat /etc/*release wmic product get name,version | **Analyzer Args:** - `exp_package_ver`: Dict[str, Optional[str]] — Map package name -> expected version (None = any version). Checked against installed packages. - `regex_match`: bool — If True, match package versions with regex; otherwise exact or prefix match. - `rocm_regex`: Optional[str] — Optional regex to identify ROCm package version (used when enable_rocm_regex is True). - `enable_rocm_regex`: bool — If True, use rocm_regex (or default pattern) to extract ROCm version for checks. | - | [PackageDataModel](#PackageDataModel-Model) | [PackageCollector](#Collector-Class-PackageCollector) | [PackageAnalyzer](#Data-Analyzer-Class-PackageAnalyzer) |
| PciePlugin | lspci -d {vendor_id}: -nn lspci -x lspci -xxxx lspci -PP lspci -PP -d {vendor_id}:{dev_id} lspci -PP -D -d {vendor_id}:{dev_id} lspci -PP -D lspci -vvv lspci -vvvt | **Analyzer Args:** - `exp_speed`: int — Expected PCIe link speed (generation 1–5). - `exp_width`: int — Expected PCIe link width in lanes (1–16). - `exp_sriov_count`: int — Expected SR-IOV virtual function count. - `exp_gpu_count_override`: Optional[int] — Override expected GPU count for validation. - `exp_max_payload_size`: Union[Dict[int, int], int, NoneType] — Expected max payload size: int for all devices, or dict keyed by device ID. - `exp_max_rd_req_size`: Union[Dict[int, int], int, NoneType] — Expected max read request size: int for all devices, or dict keyed by device ID. - `exp_ten_bit_tag_req_en`: Union[Dict[int, int], int, NoneType] — Expected 10-bit tag request enable: int for all devices, or dict keyed by device ID. | - | [PcieDataModel](#PcieDataModel-Model) | [PcieCollector](#Collector-Class-PcieCollector) | [PcieAnalyzer](#Data-Analyzer-Class-PcieAnalyzer) |
| ProcessPlugin | top -b -n 1 rocm-smi --showpids top -b -n 1 -o %CPU | **Analyzer Args:** - `max_kfd_processes`: int — Maximum allowed number of KFD (Kernel Fusion Driver) processes; 0 disables the check. - `max_cpu_usage`: float — Maximum allowed CPU usage (percent) for process checks. | **Collection Args:** - `html_view`: bool — When true, include logged command artifacts in command_artifacts.html using human-readable output. Arista collectors... - `top_n_process`: int — Number of top processes by CPU usage to collect (e.g. for top -b -n 1 -o %%CPU). | [ProcessDataModel](#ProcessDataModel-Model) | [ProcessCollector](#Collector-Class-ProcessCollector) | [ProcessAnalyzer](#Data-Analyzer-Class-ProcessAnalyzer) |
-| RdmaPlugin | rdma link -j rdma dev rdma link rdma statistic -j | - | - | [RdmaDataModel](#RdmaDataModel-Model) | [RdmaCollector](#Collector-Class-RdmaCollector) | [RdmaAnalyzer](#Data-Analyzer-Class-RdmaAnalyzer) |
+| RdmaPlugin | rdma link -j rdma dev rdma link rdma statistic -j | **Analyzer Args:** - `exclusion_regex`: Optional[list[str]] — Regex patterns matched against an interface netdev; matching interfaces are skipped. | - | [RdmaDataModel](#RdmaDataModel-Model) | [RdmaCollector](#Collector-Class-RdmaCollector) | [RdmaAnalyzer](#Data-Analyzer-Class-RdmaAnalyzer) |
| RegexSearchPlugin | - | Runs RegexSearchAnalyzer: user-defined patterns via analysis_args.error_regex (same shape as Dmesg). Emits regex match events with optional per-file source in the description when scanning directories. **Analyzer Args:** - `error_regex`: Optional[list[dict[str, Any]]] — Regex patterns to search for; each dict may include regex (str), message, event_category, event_priority (same as Dme... - `interval_to_collapse_event`: int — Seconds within which repeated events are collapsed into one. - `num_timestamps`: int — Number of timestamps to include per event in output. | - | [RegexSearchData](#RegexSearchData-Model) | - | [RegexSearchAnalyzer](#Data-Analyzer-Class-RegexSearchAnalyzer) |
| RocmPlugin | {rocm_path}/opencl/bin/*/clinfo env | grep -Ei 'rocm|hsa|hip|mpi|openmp|ucx|miopen' ls /sys/class/kfd/kfd/proc/ grep -i -E 'rocm' /etc/ld.so.conf.d/* {rocm_path}/bin/rocminfo ls -v -d {rocm_path}* ls -v -d {rocm_path}-[3-7]* | tail -1 ldconfig -p | grep -i -E 'rocm' grep . -H -r -i {rocm_path}/.info/* | **Analyzer Args:** - `exp_rocm`: Union[str, list] — Expected ROCm version string(s) to match (e.g. from rocminfo). - `exp_rocm_latest`: str — Expected 'latest' ROCm path or version string for versioned installs. - `exp_rocm_sub_versions`: dict[str, Union[str, list]] — Map sub-version name (e.g. version_rocm) to expected string or list of allowed strings. | **Collection Args:** - `html_view`: bool — When true, include logged command artifacts in command_artifacts.html using human-readable output. Arista collectors... - `rocm_path`: str — Base path to ROCm installation (e.g. /opt/rocm). Used for rocminfo, clinfo, and version discovery. | [RocmDataModel](#RocmDataModel-Model) | [RocmCollector](#Collector-Class-RocmCollector) | [RocmAnalyzer](#Data-Analyzer-Class-RocmAnalyzer) |
| StoragePlugin | sh -c 'df -lH -B1 | grep -v 'boot'' wmic LogicalDisk Where DriveType="3" Get DeviceId,Size,FreeSpace | - | **Collection Args:** - `html_view`: bool — When true, include logged command artifacts in command_artifacts.html using human-readable output. Arista collectors... - `skip_sudo`: bool — If True, do not use sudo when running df and related storage commands. | [StorageDataModel](#StorageDataModel-Model) | [StorageCollector](#Collector-Class-StorageCollector) | [StorageAnalyzer](#Data-Analyzer-Class-StorageAnalyzer) |
@@ -44,7 +44,6 @@
| RedfishEndpointPlugin | Redfish GET: explicit paths from collection_args.uris (parallel when max_workers>1). Optional paged GET following the Members collection OData nextLink field when follow_next_link is true. Redfish GET tree: when discover_tree is true, walks from api_root using OData resource id links and Members navigation (depth and endpoint caps from collection_args). | For each entry in analysis_args.checks, reads JSON paths in collected responses and compares values to constraints (eq, min/max, anyOf, regex, etc.). URI key "*" runs checks against every collected response body. **Analyzer Args:** - `checks`: dict[str, dict[str, Union[int, float, str, bool, dict[str, Any]]]] — Map: URI or '*' -> { property_path: constraint }. URI keys must match a key in the collected responses (exact match).... | **Collection Args:** - `html_view`: bool — When true, include logged command artifacts in command_artifacts.html using human-readable output. Arista collectors... - `uris`: list[str] — Redfish URIs to GET. Ignored when discover_tree is True. - `discover_tree`: bool — If True, discover endpoints from the BMC Redfish tree (service root and links) instead of using uris. - `tree_max_depth`: int — When discover_tree is True: max traversal depth (1=service root only, 2=root + collections, 3=+ members). - `tree_max_endpoints`: int — When discover_tree is True: max endpoints to discover (0=no limit). - `max_workers`: int — Max concurrent GETs (1=sequential). Use >1 for async endpoint fetches. - `follow_next_link`: bool — If True, follow Redfish Members collection OData nextLink pagination for each URI and merge all pages into a single r... - `max_pages`: int — When follow_next_link is True: safety cap on the number of pages to follow per URI (default 200). | [RedfishEndpointDataModel](#RedfishEndpointDataModel-Model) | [RedfishEndpointCollector](#Collector-Class-RedfishEndpointCollector) | [RedfishEndpointAnalyzer](#Data-Analyzer-Class-RedfishEndpointAnalyzer) |
| RedfishOemDiagPlugin | Redfish LogService.CollectDiagnosticData for each entry in collection_args.oem_diagnostic_types (collection_args.log_service_path selects the LogService). Optional binary archives under the plugin log path when log_path is set. | Summarizes success/failure per OEM diagnostic type from collected results. When analysis_args.require_all_success is true, fails the run if any type failed collection. **Analyzer Args:** - `require_all_success`: bool — If True, analysis fails when any OEM type collection failed. | **Collection Args:** - `html_view`: bool — When true, include logged command artifacts in command_artifacts.html using human-readable output. Arista collectors... - `log_service_path`: str — Redfish path to the LogService (e.g. DiagLogs). - `oem_diagnostic_types_allowable`: Optional[list[str]] — Allowable OEM diagnostic types for this architecture/BMC. When set, used for validation and as default for oem_diagno... - `oem_diagnostic_types`: list[str] — OEM diagnostic types to collect. When empty and oem_diagnostic_types_allowable is set, defaults to that list. - `task_timeout_s`: int — Max seconds to wait for each BMC task. | [RedfishOemDiagDataModel](#RedfishOemDiagDataModel-Model) | [RedfishOemDiagCollector](#Collector-Class-RedfishOemDiagCollector) | [RedfishOemDiagAnalyzer](#Data-Analyzer-Class-RedfishOemDiagAnalyzer) |
| ServiceabilityPluginMI3XX | - | **Analyzer Args:** - `hub_python_module`: Optional[str] — Import path for the hub module (class implements hub_analyze_method); hub_options forwards kwargs. - `hub_display_name`: Optional[str] — Optional label for analyzer status messages. - `afid_sag_path`: Optional[str] — Path to hub config (e.g. AFID_SAG.json); passed as hub_init_path_kwarg. - `hub_init_path_kwarg`: str — Hub __init__ keyword that receives afid_sag_path. - `hub_analyze_method`: str — Hub method called with rf_events first (default get_service_info). - `skip_hub`: bool — If True, only build afid_events without running the service hub. - `cper_decode_module`: Optional[str] — Module import path for CPER decoding when events include CPER attachments. - `cper_decode_method`: str — Callable on cper_decode_module: file-like CPER in, (return_code, decode_dict) out. - `hub_options`: Optional[dict[str, Any]] — Extra kwargs for hub __init__ and analyze; collected cper_data overrides cper_data key. - `from_ac_cycle`: int — from_ac_cycle kwarg for the hub analyze call (merged after hub_options). - `from_date`: Optional[str] — Optional from_date for the hub analyze call (merged after hub_options). - `designation_serials`: Optional[dict[str, str]] — Optional designation_serials for the hub analyze call (merged after hub_options). - `suppress_service_actions`: Optional[list[str]] — Optional suppress_service_actions for the hub analyze call (merged after hub_options). | **Collection Args:** - `html_view`: bool — When true, include logged command artifacts in command_artifacts.html using human-readable output. Arista collectors... - `uri`: Optional[str] — Optional alias for ``rf_event_log_uri``. When both ``uri`` and ``rf_event_log_uri`` are explicitly set to non-empty v... - `rf_event_log_uri`: str — Redfish URI for the event log ``Entries`` collection. - `rf_chassis_devices`: Optional[List[str]] — Chassis designations for Assembly GETs; required with ``rf_assembly_uri_template``. - `rf_assembly_uri_template`: Optional[str] — Redfish URI template containing ``{device}`` for each chassis Assembly resource. - `rf_firmware_bundle_uri`: Optional[str] — Redfish URI for firmware bundle inventory when subclasses extract component details. - `follow_next_link`: bool — If True, follow Members@odata.nextLink up to max_pages; else single GET. - `max_pages`: int — Safety cap on the number of pages when following event log pagination. - `top`: Optional[int] — Most recent N entries via $skip after count probe; None collects full window. - `reference_time`: Optional[str] — Optional ISO-8601 date or date-time used with time_operator (e.g. 2026-05-17 or 2026-05-17T13:01:00). - `time_operator`: Optional[Literal['>', '>=', '<', '<=', '==']] — Comparison operator applied when reference_time is set. | [ServiceabilityDataModel](#ServiceabilityDataModel-Model) | [MI3XXCollector](#Collector-Class-MI3XXCollector) | [MI3XXAnalyzer](#Data-Analyzer-Class-MI3XXAnalyzer) |
-| ServiceabilityPluginBase | - | - | **Collection Args:** - `html_view`: bool — When true, include logged command artifacts in command_artifacts.html using human-readable output. Arista collectors... | [ServiceabilityDataModel](#ServiceabilityDataModel-Model) | [ServiceabilityCollectorBase](#Collector-Class-ServiceabilityCollectorBase) | - |
# Collectors
@@ -1314,20 +1313,6 @@ Collect MI3XX BMC Redfish data: event log members (with pagination), firmware in
ServiceabilityDataModel
-## Collector Class ServiceabilityCollectorBase
-
-### Description
-
-OOB Redfish collection skeleton; subclasses implement filtering, CPER handling, and JSON parsing.
-
-**Bases**: ['RedfishDataCollector', 'Generic']
-
-**Link to code**: [serviceability_collector.py](https://github.com/amd/node-scraper/blob/HEAD/nodescraper/plugins/serviceability/serviceability_collector.py)
-
-### Provides Data
-
-ServiceabilityDataModel
-
# Data Models
## GenericCollectionDataModel Model
@@ -2134,7 +2119,7 @@ Check memory usage is within the maximum allowed used memory
### Description
-Check network statistics for errors (PFC and other network error counters).
+Check network statistics for errors.
**Bases**: ['RegexAnalyzer']
@@ -2142,18 +2127,7 @@ Check network statistics for errors (PFC and other network error counters).
### Class Variables
-- **ERROR_REGEX**: `[
- regex=re.compile('^tx_pfc_frames$') message='tx_pfc_frames is non-zero' event_category= event_priority=,
- regex=re.compile('^tx_pfc_ena_frames_pri\\d+$') message='tx_pfc_ena_frames_pri* is non-zero' event_category= event_priority=,
- regex=re.compile('^pfc_pri\\d+_tx_transitions$') message='pfc_pri*_tx_transitions is non-zero' event_category= event_priority=
-]`
-
-### Regex Patterns
-
-- **Built-in Regexes:**
-- - tx_pfc_frames is non-zero: `^tx_pfc_frames$`
-- - tx_pfc_ena_frames_pri* is non-zero: `^tx_pfc_ena_frames_pri\d+$`
-- - pfc_pri*_tx_transitions is non-zero: `^pfc_pri\d+_tx_transitions$`
+- **ERROR_REGEX**: `[]`
## Data Analyzer Class NicAnalyzer
@@ -2601,6 +2575,20 @@ Arguments for PCIe analyzer
- **max_kfd_processes**: `int` — Maximum allowed number of KFD (Kernel Fusion Driver) processes; 0 disables the check.
- **max_cpu_usage**: `float` — Maximum allowed CPU usage (percent) for process checks.
+## Analyzer Args Class RdmaAnalyzerArgs
+
+### Description
+
+Arguments for the RDMA analyzer.
+
+**Bases**: ['AnalyzerArgs']
+
+**Link to code**: [analyzer_args.py](https://github.com/amd/node-scraper/blob/HEAD/nodescraper/plugins/inband/rdma/analyzer_args.py)
+
+### Annotations / fields
+
+- **exclusion_regex**: `Optional[list[str]]` — Regex patterns matched against an interface netdev; matching interfaces are skipped.
+
## Analyzer Args Class RegexSearchAnalyzerArgs
### Description
diff --git a/docs/generate_plugin_doc_bundle.py b/docs/generate_plugin_doc_bundle.py
index cd9897b0..75c4d412 100644
--- a/docs/generate_plugin_doc_bundle.py
+++ b/docs/generate_plugin_doc_bundle.py
@@ -49,6 +49,8 @@
# ``nodescraper.plugins`` itself does not match every module starting with that string.
PLUGIN_MODULE_PREFIX = f"{PACKAGE_PLUGINS_ROOT}."
DEFAULT_PACKAGES = (PACKAGE_PLUGINS_ROOT,)
+# OOB plugin stubs with no concrete COLLECTOR/ANALYZER; document subclasses only.
+PLUGIN_DOC_IGNORE_CLASSES = frozenset({"ServiceabilityPluginBase"})
def get_attr(obj: Any, name: str, default: Any = None) -> Any:
@@ -230,6 +232,8 @@ def plugins_for_package_prefix(base_classes: Iterable[type], package_prefix: str
continue
if not is_concrete_plugin_class(cls):
continue
+ if cls.__name__ in PLUGIN_DOC_IGNORE_CLASSES:
+ continue
found.append(cls)
return found
diff --git a/nodescraper/base/regexanalyzer.py b/nodescraper/base/regexanalyzer.py
index 1fab952d..1d7adf61 100644
--- a/nodescraper/base/regexanalyzer.py
+++ b/nodescraper/base/regexanalyzer.py
@@ -138,6 +138,34 @@ def _line_at_match_position(self, content: str, match_start: int) -> str:
line_end = len(content)
return content[line_start:line_end]
+ def _line_index_at_position(self, content: str, position: int) -> int:
+ if position <= 0:
+ return 0
+ return content.count("\n", 0, position)
+
+ def _match_intersects_skip_lines(
+ self,
+ content: str,
+ match_start: int,
+ match_end: int,
+ skip_line_indices: frozenset[int],
+ ) -> bool:
+ if not skip_line_indices:
+ return False
+ start_line = self._line_index_at_position(content, match_start)
+ end_line = self._line_index_at_position(content, max(match_start, match_end - 1))
+ return any(line_idx in skip_line_indices for line_idx in range(start_line, end_line + 1))
+
+ def _match_on_skipped_line(
+ self,
+ content: str,
+ match_start: int,
+ skip_line_indices: frozenset[int],
+ ) -> bool:
+ if not skip_line_indices:
+ return False
+ return self._line_index_at_position(content, match_start) in skip_line_indices
+
def _should_ignore_regex_match(
self,
content: str,
@@ -245,6 +273,7 @@ def check_all_regexes(
num_timestamps: int = 3,
interval_to_collapse_event: int = 60,
ignore_match_rules: Optional[Sequence[ParsedIgnoreMatchRule]] = None,
+ skip_line_indices: Optional[frozenset[int]] = None,
) -> list[RegexEvent]:
"""Iterate over all ERROR_REGEX and check content for any matches
@@ -262,6 +291,7 @@ def check_all_regexes(
num_timestamps (int, optional): maximum number of timestamps to keep for each event. Defaults to 3.
interval_to_collapse_event (int, optional): time interval in seconds to collapse events. Defaults to 60.
ignore_match_rules (Optional[Sequence[ParsedIgnoreMatchRule]], optional): Parsed skip rules. Defaults to None.
+ skip_line_indices (Optional[frozenset[int]], optional): Line indices to skip entirely. Defaults to None.
Returns:
list[RegexEvent]: list of regex event objects
@@ -296,10 +326,22 @@ def _is_within_interval(new_timestamp_str: str, existing_timestamps: list[str])
return False
skip_rules = list(ignore_match_rules) if ignore_match_rules else []
+ suppressed_lines = skip_line_indices or frozenset()
for error_regex_obj in error_regex:
- for match_obj in error_regex_obj.regex.finditer(content):
+ search_from = 0
+ while search_from <= len(content):
+ match_obj = error_regex_obj.regex.search(content, search_from)
+ if match_obj is None:
+ break
raw_match = match_obj.group(0)
+ if self._match_on_skipped_line(
+ content,
+ match_obj.start(),
+ suppressed_lines,
+ ):
+ search_from = match_obj.start() + 1
+ continue
if self._should_ignore_regex_match(
content,
match_obj.start(),
@@ -307,6 +349,7 @@ def _is_within_interval(new_timestamp_str: str, existing_timestamps: list[str])
error_regex_obj.message,
skip_rules,
):
+ search_from = match_obj.start() + 1
continue
# Extract timestamp from the line where match occurs
@@ -360,6 +403,8 @@ def _is_within_interval(new_timestamp_str: str, existing_timestamps: list[str])
regex_event_list.append(new_event)
+ search_from = match_obj.end()
+
all_events = list(regex_map.values()) if group else regex_event_list
# Prune timestamp lists to keep only first N and last N timestamps
diff --git a/nodescraper/command_artifact_html.py b/nodescraper/command_artifact_html.py
index 5bf3c5be..18e93606 100644
--- a/nodescraper/command_artifact_html.py
+++ b/nodescraper/command_artifact_html.py
@@ -121,6 +121,17 @@
}
pre.stderr { color: var(--fail-fg); }
.no-results { color: var(--muted); padding: 24px; text-align: center; display: none; }
+ .copy-btn {
+ flex: 0 0 auto;
+ display: inline-flex; align-items: center; justify-content: center;
+ background: var(--bg); color: var(--muted);
+ border: 1px solid var(--border); border-radius: 6px;
+ padding: 5px 7px; cursor: pointer; line-height: 0;
+ transition: color .15s ease, border-color .15s ease, background .15s ease;
+ }
+ .copy-btn:hover { background: var(--panel-hover); border-color: var(--accent); color: var(--text); }
+ .copy-btn.copied { color: var(--ok-fg); border-color: var(--ok-fg); }
+ .copy-btn svg { display: block; }
@@ -143,6 +154,9 @@
▸{command}exit {exit_code}
+