From a3b791ed07ee2f408beb1b606f05ce9b7e7c3f8a Mon Sep 17 00:00:00 2001 From: niratner Date: Mon, 20 Apr 2026 16:15:05 -0400 Subject: [PATCH 1/8] Created a new input to the dmesg analyzer allowing for a list of rules which can change the priority of regex events which the given rules. --- .../plugins/inband/dmesg/analyzer_args.py | 10 ++ .../plugins/inband/dmesg/dmesg_analyzer.py | 65 ++++++++ test/unit/plugin/test_dmesg_analyzer.py | 154 ++++++++++++++++++ 3 files changed, 229 insertions(+) diff --git a/nodescraper/plugins/inband/dmesg/analyzer_args.py b/nodescraper/plugins/inband/dmesg/analyzer_args.py index cd9ba765..b68aec27 100644 --- a/nodescraper/plugins/inband/dmesg/analyzer_args.py +++ b/nodescraper/plugins/inband/dmesg/analyzer_args.py @@ -52,3 +52,13 @@ class DmesgAnalyzerArgs(TimeRangeAnalysisArgs): default=None, description="Custom error regex patterns; each item can be ErrorRegex or dict with category/pattern.", ) + priority_override_rules: Optional[list[dict]] = Field( + default=None, + description=( + "Rules to override the priority of matched ErrorRegex objects. " + "Each rule is a dict where all keys except 'new_priority' and 'match_all' " + "are filter fields matched against ErrorRegex attributes. " + "'new_priority' must be an EventPriority name (e.g. 'WARNING', 'ERROR') " + "or 'NO_CHANGE' to leave the priority unchanged." + ), + ) diff --git a/nodescraper/plugins/inband/dmesg/dmesg_analyzer.py b/nodescraper/plugins/inband/dmesg/dmesg_analyzer.py index ccfe9ce0..f63d56bb 100644 --- a/nodescraper/plugins/inband/dmesg/dmesg_analyzer.py +++ b/nodescraper/plugins/inband/dmesg/dmesg_analyzer.py @@ -535,6 +535,62 @@ def _norm(s: str) -> str: return True return False + def resolve_priority( + self, + regex_obj: ErrorRegex, + priority_override_rules: list[dict], + ) -> EventPriority | None: + """ + Walk the priority_override_rules in order (first-match-wins). + All keys in each rule except 'new_priority' and 'match_all' are treated + as filter fields compared against ErrorRegex attributes. Filter values + may be a single value or a list of values (match if any value matches). + Enum fields are compared by their name. Returns the overriding + EventPriority, or None to keep the original. + + Example rule format: + { + "message": ["mode 1 reset failed", "mode 2 reset failed"], + "new_priority": "NO_CHANGE" + } + { + "event_category": "RAS", + "new_priority": "WARNING" + } + """ + + _NO_CHANGE = "NO_CHANGE" + _EXCLUDED_KEYS = {"new_priority", "match_all"} + + for rule in priority_override_rules: + filter_fields = {key: value for key, value in rule.items() if key not in _EXCLUDED_KEYS} + + matched = True + # check for matches in all fields of the current rule + for field, filter_value in filter_fields.items(): + obj_value = getattr(regex_obj, field, None) + + # Normalize enum values to their name for string comparison + if hasattr(obj_value, "name"): + obj_value = obj_value.name + + if isinstance(filter_value, list): + if obj_value not in filter_value: + matched = False + break + else: + if obj_value != filter_value: + matched = False + break + + if matched: # return on encountering first fully matched rule + new_priority = rule.get("new_priority", _NO_CHANGE) + if new_priority == _NO_CHANGE: + return None + return EventPriority[new_priority] + + return None # if no rules are matched, return None + def analyze_data( self, data: DmesgData, @@ -555,6 +611,15 @@ def analyze_data( final_error_regex = self._convert_and_extend_error_regex(args.error_regex, self.ERROR_REGEX) + if args.priority_override_rules: + updated_regex = [] + for regex_obj in final_error_regex: + new_priority = self.resolve_priority(regex_obj, args.priority_override_rules) + if new_priority is not None: + regex_obj = regex_obj.model_copy(update={"event_priority": new_priority}) + updated_regex.append(regex_obj) + final_error_regex = updated_regex + if args.analysis_range_start or args.analysis_range_end: self.logger.info( "Filtering dmesg using range %s - %s", diff --git a/test/unit/plugin/test_dmesg_analyzer.py b/test/unit/plugin/test_dmesg_analyzer.py index c14b090c..27ff231d 100644 --- a/test/unit/plugin/test_dmesg_analyzer.py +++ b/test/unit/plugin/test_dmesg_analyzer.py @@ -25,7 +25,10 @@ ############################################################################### import datetime import pathlib +import re +from nodescraper.base.regexanalyzer import ErrorRegex +from nodescraper.enums.eventcategory import EventCategory from nodescraper.enums.eventpriority import EventPriority from nodescraper.enums.executionstatus import ExecutionStatus from nodescraper.plugins.inband.dmesg.analyzer_args import DmesgAnalyzerArgs @@ -708,6 +711,157 @@ def test_custom_regex_empty_list(system_info): assert res.events[0].description == "Out of memory error" +def test_resolve_priority_no_match(system_info): + """No rule matches → returns None (keep original priority).""" + analyzer = DmesgAnalyzer(system_info=system_info) + regex_obj = ErrorRegex( + regex=re.compile(r"GPU reset failed"), + message="GPU reset failed", + event_category=EventCategory.RAS, + ) + rules = [{"event_category": "SW_DRIVER", "new_priority": "WARNING"}] + assert analyzer.resolve_priority(regex_obj, rules) is None + + +def test_resolve_priority_match_by_category(system_info): + """Rule with event_category filter matches and returns the new priority.""" + analyzer = DmesgAnalyzer(system_info=system_info) + regex_obj = ErrorRegex( + regex=re.compile(r"GPU reset failed"), + message="GPU reset failed", + event_category=EventCategory.RAS, + ) + rules = [{"event_category": "RAS", "new_priority": "WARNING"}] + result = analyzer.resolve_priority(regex_obj, rules) + assert result == EventPriority.WARNING + + +def test_resolve_priority_match_by_message_list(system_info): + """Rule with a list for message matches when the object's message is in the list.""" + analyzer = DmesgAnalyzer(system_info=system_info) + regex_obj = ErrorRegex( + regex=re.compile(r"Mode2 reset failed"), + message="Mode 2 Reset Failed", + event_category=EventCategory.RAS, + ) + rules = [ + { + "message": ["Mode 2 Reset Failed", "GPU reset failed"], + "new_priority": "WARNING", + } + ] + result = analyzer.resolve_priority(regex_obj, rules) + assert result == EventPriority.WARNING + + +def test_resolve_priority_no_change(system_info): + """new_priority=NO_CHANGE → returns None (keep original priority).""" + analyzer = DmesgAnalyzer(system_info=system_info) + regex_obj = ErrorRegex( + regex=re.compile(r"GPU reset failed"), + message="GPU reset failed", + event_category=EventCategory.RAS, + ) + rules = [{"event_category": "RAS", "new_priority": "NO_CHANGE"}] + assert analyzer.resolve_priority(regex_obj, rules) is None + + +def test_resolve_priority_first_match_wins(system_info): + """First matching rule wins; subsequent matching rules are ignored.""" + analyzer = DmesgAnalyzer(system_info=system_info) + regex_obj = ErrorRegex( + regex=re.compile(r"GPU reset failed"), + message="GPU reset failed", + event_category=EventCategory.RAS, + ) + rules = [ + {"event_category": "RAS", "new_priority": "WARNING"}, + {"event_category": "RAS", "new_priority": "ERROR"}, + ] + result = analyzer.resolve_priority(regex_obj, rules) + assert result == EventPriority.WARNING + + +def test_resolve_priority_multiple_filter_fields(system_info): + """All filter fields must match (AND logic).""" + analyzer = DmesgAnalyzer(system_info=system_info) + # Matches both category AND message + regex_obj = ErrorRegex( + regex=re.compile(r"GPU reset failed"), + message="GPU reset failed", + event_category=EventCategory.RAS, + ) + rules = [ + {"event_category": "RAS", "message": "GPU reset failed", "new_priority": "WARNING"}, + ] + assert analyzer.resolve_priority(regex_obj, rules) == EventPriority.WARNING + + # Does NOT match because message differs + rules_mismatch = [ + {"event_category": "RAS", "message": "ACA Error", "new_priority": "WARNING"}, + ] + assert analyzer.resolve_priority(regex_obj, rules_mismatch) is None + + +def test_priority_override_rules_in_analyze_data(system_info): + """priority_override_rules passed via DmesgAnalyzerArgs overrides matched regex priorities.""" + dmesg_data = DmesgData( + dmesg_content=( + # RAS event — default ERROR, should become WARNING + "kern :err : 2024-10-07T10:17:15,145363-04:00 " + "amdgpu 0000:0c:00.0: amdgpu: socket: 4 1 correctable hardware errors detected in total in gfx block\n" + # SW_DRIVER event — default ERROR, should stay ERROR (no matching rule) + "kern :err : 2024-10-07T10:17:15,145363-04:00 IO_PAGE_FAULT\n" + ) + ) + + analyzer = DmesgAnalyzer(system_info=system_info) + res = analyzer.analyze_data( + dmesg_data, + args=DmesgAnalyzerArgs( + check_unknown_dmesg_errors=False, + priority_override_rules=[ + {"event_category": "RAS", "new_priority": "WARNING"}, + ], + ), + ) + + assert res.status == ExecutionStatus.ERROR + ras_events = [e for e in res.events if e.category == "RAS"] + sw_events = [e for e in res.events if e.category == "SW_DRIVER"] + + assert all( + e.priority == EventPriority.WARNING for e in ras_events + ), f"Expected all RAS events to be WARNING, got {[e.priority for e in ras_events]}" + assert all( + e.priority == EventPriority.ERROR for e in sw_events + ), f"Expected SW_DRIVER events to remain ERROR, got {[e.priority for e in sw_events]}" + + +def test_priority_override_no_change_keeps_original(system_info): + """NO_CHANGE rule leaves the original event priority intact.""" + dmesg_data = DmesgData( + dmesg_content=( + "kern :err : 2024-10-07T10:17:15,145363-04:00 " + "amdgpu 0000:0c:00.0: amdgpu: socket: 4 1 correctable hardware errors detected in total in gfx block\n" + ) + ) + + analyzer = DmesgAnalyzer(system_info=system_info) + res = analyzer.analyze_data( + dmesg_data, + args=DmesgAnalyzerArgs( + check_unknown_dmesg_errors=False, + priority_override_rules=[ + {"event_category": "RAS", "new_priority": "NO_CHANGE"}, + ], + ), + ) + + assert len(res.events) == 1 + assert res.events[0].priority == EventPriority.ERROR + + def test_custom_regex_with_multiline_pattern(system_info): """Test custom regex that should NOT match across multiple dmesg lines (each line processed separately)""" dmesg_data = DmesgData( From f91b146b927a495e6fb8f15c5d51faaca7d18422 Mon Sep 17 00:00:00 2001 From: niratner Date: Mon, 20 Apr 2026 16:50:43 -0400 Subject: [PATCH 2/8] Added 'match_all' flag to dmesg analyzer priority_override_rules to allow a rule to match everything. Can be used as a default rule. --- .../plugins/inband/dmesg/dmesg_analyzer.py | 34 +++++----- test/unit/plugin/test_dmesg_analyzer.py | 63 +++++++++++++++++++ 2 files changed, 81 insertions(+), 16 deletions(-) diff --git a/nodescraper/plugins/inband/dmesg/dmesg_analyzer.py b/nodescraper/plugins/inband/dmesg/dmesg_analyzer.py index f63d56bb..7fae9c07 100644 --- a/nodescraper/plugins/inband/dmesg/dmesg_analyzer.py +++ b/nodescraper/plugins/inband/dmesg/dmesg_analyzer.py @@ -566,22 +566,24 @@ def resolve_priority( filter_fields = {key: value for key, value in rule.items() if key not in _EXCLUDED_KEYS} matched = True - # check for matches in all fields of the current rule - for field, filter_value in filter_fields.items(): - obj_value = getattr(regex_obj, field, None) - - # Normalize enum values to their name for string comparison - if hasattr(obj_value, "name"): - obj_value = obj_value.name - - if isinstance(filter_value, list): - if obj_value not in filter_value: - matched = False - break - else: - if obj_value != filter_value: - matched = False - break + # if match_all is True, don't check attributes, simply move to priority update + if rule.get("match_all", False) is False: + # check for matches in all fields of the current rule + for field, filter_value in filter_fields.items(): + obj_value = getattr(regex_obj, field, None) + + # Normalize enum values to their name for string comparison + if hasattr(obj_value, "name"): + obj_value = obj_value.name + + if isinstance(filter_value, list): + if obj_value not in filter_value: + matched = False + break + else: + if obj_value != filter_value: + matched = False + break if matched: # return on encountering first fully matched rule new_priority = rule.get("new_priority", _NO_CHANGE) diff --git a/test/unit/plugin/test_dmesg_analyzer.py b/test/unit/plugin/test_dmesg_analyzer.py index 27ff231d..968c0b04 100644 --- a/test/unit/plugin/test_dmesg_analyzer.py +++ b/test/unit/plugin/test_dmesg_analyzer.py @@ -803,6 +803,69 @@ def test_resolve_priority_multiple_filter_fields(system_info): assert analyzer.resolve_priority(regex_obj, rules_mismatch) is None +def test_resolve_priority_match_all_matches_any_regex(system_info): + """match_all=True with no other filter fields always matches any ErrorRegex.""" + analyzer = DmesgAnalyzer(system_info=system_info) + for regex_obj in [ + ErrorRegex( + regex=re.compile(r"GPU reset failed"), + message="GPU reset failed", + event_category=EventCategory.RAS, + ), + ErrorRegex( + regex=re.compile(r"IO_PAGE_FAULT"), + message="I/O Page Fault", + event_category=EventCategory.SW_DRIVER, + ), + ]: + result = analyzer.resolve_priority( + regex_obj, [{"match_all": True, "new_priority": "WARNING"}] + ) + assert ( + result == EventPriority.WARNING + ), f"Expected WARNING for {regex_obj.message}, got {result}" + + +def test_resolve_priority_match_all_ignores_non_matching_filters(system_info): + """match_all=True ignores filter fields that would otherwise not match.""" + analyzer = DmesgAnalyzer(system_info=system_info) + regex_obj = ErrorRegex( + regex=re.compile(r"GPU reset failed"), + message="GPU reset failed", + event_category=EventCategory.RAS, + ) + # event_category is RAS, but filter says SW_DRIVER — would normally NOT match. + # match_all=True should bypass this check and still apply the rule. + result = analyzer.resolve_priority( + regex_obj, + [{"match_all": True, "event_category": "SW_DRIVER", "new_priority": "WARNING"}], + ) + assert result == EventPriority.WARNING + + +def test_resolve_priority_match_all_false_still_filters(system_info): + """match_all=False (explicit) falls through to normal filter logic.""" + analyzer = DmesgAnalyzer(system_info=system_info) + regex_obj = ErrorRegex( + regex=re.compile(r"GPU reset failed"), + message="GPU reset failed", + event_category=EventCategory.RAS, + ) + # match_all=False with a non-matching filter → should NOT match + result = analyzer.resolve_priority( + regex_obj, + [{"match_all": False, "event_category": "SW_DRIVER", "new_priority": "WARNING"}], + ) + assert result is None + + # match_all=False with a matching filter → should match + result = analyzer.resolve_priority( + regex_obj, + [{"match_all": False, "event_category": "RAS", "new_priority": "WARNING"}], + ) + assert result == EventPriority.WARNING + + def test_priority_override_rules_in_analyze_data(system_info): """priority_override_rules passed via DmesgAnalyzerArgs overrides matched regex priorities.""" dmesg_data = DmesgData( From 3787fe0d0bc3b0f9a1f4cc72f56db102669175b3 Mon Sep 17 00:00:00 2001 From: niratner Date: Tue, 21 Apr 2026 11:42:10 -0400 Subject: [PATCH 3/8] updated syntax of optional return value in dmesg plugin analyzer function --- nodescraper/plugins/inband/dmesg/dmesg_analyzer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodescraper/plugins/inband/dmesg/dmesg_analyzer.py b/nodescraper/plugins/inband/dmesg/dmesg_analyzer.py index 7fae9c07..65dc668d 100644 --- a/nodescraper/plugins/inband/dmesg/dmesg_analyzer.py +++ b/nodescraper/plugins/inband/dmesg/dmesg_analyzer.py @@ -539,7 +539,7 @@ def resolve_priority( self, regex_obj: ErrorRegex, priority_override_rules: list[dict], - ) -> EventPriority | None: + ) -> Optional[EventPriority]: """ Walk the priority_override_rules in order (first-match-wins). All keys in each rule except 'new_priority' and 'match_all' are treated From 66faa69ee6d2f449a1de985553e0bc588da67999 Mon Sep 17 00:00:00 2001 From: niratner Date: Tue, 21 Apr 2026 13:54:08 -0400 Subject: [PATCH 4/8] Updated README with usage example, updated resolve_priority docstring and changed return from None to the original priority if it doesn't change --- README.md | 10 +++++++ .../plugins/inband/dmesg/dmesg_analyzer.py | 30 ++++++++++++------- test/unit/plugin/test_dmesg_analyzer.py | 16 +++++----- 3 files changed, 37 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index eda8dea4..ebc95b8f 100644 --- a/README.md +++ b/README.md @@ -337,6 +337,16 @@ You can extend the built-in error detection with custom regex patterns. Create a "event_category": "SW_DRIVER", "event_priority": 4 } + ], + "priority_override_rules": [ + { + "message": "Application Crash", + "new_priority": "ERROR" + }, + { + "event_category": "SW_DRIVER", + "new_priority": "WARNING" + } ] } } diff --git a/nodescraper/plugins/inband/dmesg/dmesg_analyzer.py b/nodescraper/plugins/inband/dmesg/dmesg_analyzer.py index 65dc668d..a790cfd0 100644 --- a/nodescraper/plugins/inband/dmesg/dmesg_analyzer.py +++ b/nodescraper/plugins/inband/dmesg/dmesg_analyzer.py @@ -539,14 +539,14 @@ def resolve_priority( self, regex_obj: ErrorRegex, priority_override_rules: list[dict], - ) -> Optional[EventPriority]: - """ + ) -> EventPriority: + """Determine the new priority of an ErrorRegex based on provided rules + Walk the priority_override_rules in order (first-match-wins). - All keys in each rule except 'new_priority' and 'match_all' are treated - as filter fields compared against ErrorRegex attributes. Filter values - may be a single value or a list of values (match if any value matches). - Enum fields are compared by their name. Returns the overriding - EventPriority, or None to keep the original. + Each rule should be a dict with only these keys allowed: + 1. Any attribute of an ErrorRegex object by which to filter. Currently this include "regex", "message", "event_category", "event_priority". This key should match to a string or a list (match if any value in the list matches). + 2. "new_priority": str. The string value of any EventPriority enum, or "NO_CHANGE", to determine the updated priority of the regex_obj if it matches the given rule. + 3. "match_all": bool. Determines if the rule will automatically match for any regex_obj. Will ignore any provided filters if given. Example rule format: { @@ -557,11 +557,20 @@ def resolve_priority( "event_category": "RAS", "new_priority": "WARNING" } + + Args: + regex_obj (ErrorRegex): The ErrorRegex object to have its priority updated + priority_override_rules (list[dict]): The list of rules which determine what the updated priority should be + + Returns: + EventPriority: The new priority of the event. Returns the original priority if no rule matches or the matched rule specifies NO_CHANGE """ _NO_CHANGE = "NO_CHANGE" _EXCLUDED_KEYS = {"new_priority", "match_all"} + current_priority = regex_obj.event_priority + for rule in priority_override_rules: filter_fields = {key: value for key, value in rule.items() if key not in _EXCLUDED_KEYS} @@ -588,10 +597,10 @@ def resolve_priority( if matched: # return on encountering first fully matched rule new_priority = rule.get("new_priority", _NO_CHANGE) if new_priority == _NO_CHANGE: - return None + return current_priority return EventPriority[new_priority] - return None # if no rules are matched, return None + return current_priority # if no rules are matched, keep the current priority def analyze_data( self, @@ -617,8 +626,7 @@ def analyze_data( updated_regex = [] for regex_obj in final_error_regex: new_priority = self.resolve_priority(regex_obj, args.priority_override_rules) - if new_priority is not None: - regex_obj = regex_obj.model_copy(update={"event_priority": new_priority}) + regex_obj = regex_obj.model_copy(update={"event_priority": new_priority}) updated_regex.append(regex_obj) final_error_regex = updated_regex diff --git a/test/unit/plugin/test_dmesg_analyzer.py b/test/unit/plugin/test_dmesg_analyzer.py index 968c0b04..7aaeb850 100644 --- a/test/unit/plugin/test_dmesg_analyzer.py +++ b/test/unit/plugin/test_dmesg_analyzer.py @@ -712,7 +712,7 @@ def test_custom_regex_empty_list(system_info): def test_resolve_priority_no_match(system_info): - """No rule matches → returns None (keep original priority).""" + """No rule matches → returns the original priority unchanged.""" analyzer = DmesgAnalyzer(system_info=system_info) regex_obj = ErrorRegex( regex=re.compile(r"GPU reset failed"), @@ -720,7 +720,7 @@ def test_resolve_priority_no_match(system_info): event_category=EventCategory.RAS, ) rules = [{"event_category": "SW_DRIVER", "new_priority": "WARNING"}] - assert analyzer.resolve_priority(regex_obj, rules) is None + assert analyzer.resolve_priority(regex_obj, rules) == EventPriority.ERROR def test_resolve_priority_match_by_category(system_info): @@ -755,7 +755,7 @@ def test_resolve_priority_match_by_message_list(system_info): def test_resolve_priority_no_change(system_info): - """new_priority=NO_CHANGE → returns None (keep original priority).""" + """new_priority=NO_CHANGE → returns the original priority unchanged.""" analyzer = DmesgAnalyzer(system_info=system_info) regex_obj = ErrorRegex( regex=re.compile(r"GPU reset failed"), @@ -763,7 +763,7 @@ def test_resolve_priority_no_change(system_info): event_category=EventCategory.RAS, ) rules = [{"event_category": "RAS", "new_priority": "NO_CHANGE"}] - assert analyzer.resolve_priority(regex_obj, rules) is None + assert analyzer.resolve_priority(regex_obj, rules) == EventPriority.ERROR def test_resolve_priority_first_match_wins(system_info): @@ -796,11 +796,11 @@ def test_resolve_priority_multiple_filter_fields(system_info): ] assert analyzer.resolve_priority(regex_obj, rules) == EventPriority.WARNING - # Does NOT match because message differs + # Does NOT match because message differs → returns original priority rules_mismatch = [ {"event_category": "RAS", "message": "ACA Error", "new_priority": "WARNING"}, ] - assert analyzer.resolve_priority(regex_obj, rules_mismatch) is None + assert analyzer.resolve_priority(regex_obj, rules_mismatch) == EventPriority.ERROR def test_resolve_priority_match_all_matches_any_regex(system_info): @@ -851,12 +851,12 @@ def test_resolve_priority_match_all_false_still_filters(system_info): message="GPU reset failed", event_category=EventCategory.RAS, ) - # match_all=False with a non-matching filter → should NOT match + # match_all=False with a non-matching filter → returns original priority result = analyzer.resolve_priority( regex_obj, [{"match_all": False, "event_category": "SW_DRIVER", "new_priority": "WARNING"}], ) - assert result is None + assert result == EventPriority.ERROR # match_all=False with a matching filter → should match result = analyzer.resolve_priority( From 5b73934a9e15d2d2d0a4eb192081144e04ce6fe3 Mon Sep 17 00:00:00 2001 From: niratner Date: Thu, 23 Apr 2026 12:08:52 -0400 Subject: [PATCH 5/8] Moved the functionality to update ErrorRegex priorities based on rules into a function which can be used whenever making new ErrorRegex object. This function is now being used after creating the 'Unknown dmesg errors' --- .../plugins/inband/dmesg/dmesg_analyzer.py | 64 +++++++++++++------ 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/nodescraper/plugins/inband/dmesg/dmesg_analyzer.py b/nodescraper/plugins/inband/dmesg/dmesg_analyzer.py index a790cfd0..5e49da60 100644 --- a/nodescraper/plugins/inband/dmesg/dmesg_analyzer.py +++ b/nodescraper/plugins/inband/dmesg/dmesg_analyzer.py @@ -535,7 +535,32 @@ def _norm(s: str) -> str: return True return False - def resolve_priority( + def update_error_regex_priorities( + self, + error_regexes: list[ErrorRegex], + priority_override_rules: list[dict], + ) -> list[EventPriority]: + """Updates the priorities of a list of ErrorRegex options based on given priority rules + + Args: + error_regexes (list[ErrorRegex]): A list of ErrorRegex objects to have their priorities updated + priority_override_rules (list[dict]): The list of rules which determine what the updated priority should be + + Returns: + list[ErrorRegex]: A list of the same ErrorRegex objects but with their priorities updated + """ + + if priority_override_rules is None: + return error_regexes + + updated_error_regexes = [] + for regex_obj in error_regexes: + new_priority = self._resolve_priority(regex_obj, priority_override_rules) + regex_obj = regex_obj.model_copy(update={"event_priority": new_priority}) + updated_error_regexes.append(regex_obj) + return updated_error_regexes + + def _resolve_priority( self, regex_obj: ErrorRegex, priority_override_rules: list[dict], @@ -621,14 +646,9 @@ def analyze_data( args = DmesgAnalyzerArgs() final_error_regex = self._convert_and_extend_error_regex(args.error_regex, self.ERROR_REGEX) - - if args.priority_override_rules: - updated_regex = [] - for regex_obj in final_error_regex: - new_priority = self.resolve_priority(regex_obj, args.priority_override_rules) - regex_obj = regex_obj.model_copy(update={"event_priority": new_priority}) - updated_regex.append(regex_obj) - final_error_regex = updated_regex + final_error_regex = self.update_error_regex_priorities( + final_error_regex, args.priority_override_rules + ) # makes no changes if no rules are provided if args.analysis_range_start or args.analysis_range_end: self.logger.info( @@ -662,19 +682,25 @@ def analyze_data( self.result.events += known_err_events if args.check_unknown_dmesg_errors: + + unknown_dmesg_error_regexes = [ + ErrorRegex( + regex=re.compile( + r"kern :(?:err|crit|alert|emerg)\s+: \d{4}-\d+-\d+T\d+:\d+:\d+,\d+[+-]\d+:\d+ (.*)" + ), + message="Unknown dmesg error", + event_category=EventCategory.UNKNOWN, + event_priority=EventPriority.WARNING, + ) + ] + unknown_dmesg_error_regexes = self.update_error_regex_priorities( + unknown_dmesg_error_regexes, args.priority_override_rules + ) # makes no changes if no rules are provided + err_events = self.check_all_regexes( content=dmesg_content, source="dmesg", - error_regex=[ - ErrorRegex( - regex=re.compile( - r"kern :(?:err|crit|alert|emerg)\s+: \d{4}-\d+-\d+T\d+:\d+:\d+,\d+[+-]\d+:\d+ (.*)" - ), - message="Unknown dmesg error", - event_category=EventCategory.UNKNOWN, - event_priority=EventPriority.WARNING, - ) - ], + error_regex=unknown_dmesg_error_regexes, num_timestamps=args.num_timestamps, interval_to_collapse_event=args.interval_to_collapse_event, ) From 29aece1099fb3266abc9f31919465f1c47218f28 Mon Sep 17 00:00:00 2001 From: niratner Date: Thu, 23 Apr 2026 12:49:23 -0400 Subject: [PATCH 6/8] Updated comments --- nodescraper/plugins/inband/dmesg/dmesg_analyzer.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/nodescraper/plugins/inband/dmesg/dmesg_analyzer.py b/nodescraper/plugins/inband/dmesg/dmesg_analyzer.py index 5e49da60..cbc14a81 100644 --- a/nodescraper/plugins/inband/dmesg/dmesg_analyzer.py +++ b/nodescraper/plugins/inband/dmesg/dmesg_analyzer.py @@ -540,7 +540,7 @@ def update_error_regex_priorities( error_regexes: list[ErrorRegex], priority_override_rules: list[dict], ) -> list[EventPriority]: - """Updates the priorities of a list of ErrorRegex options based on given priority rules + """Updates the priorities of a list of ErrorRegex objects based on given priority rules Args: error_regexes (list[ErrorRegex]): A list of ErrorRegex objects to have their priorities updated @@ -648,7 +648,7 @@ def analyze_data( final_error_regex = self._convert_and_extend_error_regex(args.error_regex, self.ERROR_REGEX) final_error_regex = self.update_error_regex_priorities( final_error_regex, args.priority_override_rules - ) # makes no changes if no rules are provided + ) # updates the priorities of the ErrorRegex objects using the given rules. makes no changes if no rules are provided. if args.analysis_range_start or args.analysis_range_end: self.logger.info( @@ -682,7 +682,6 @@ def analyze_data( self.result.events += known_err_events if args.check_unknown_dmesg_errors: - unknown_dmesg_error_regexes = [ ErrorRegex( regex=re.compile( @@ -695,7 +694,7 @@ def analyze_data( ] unknown_dmesg_error_regexes = self.update_error_regex_priorities( unknown_dmesg_error_regexes, args.priority_override_rules - ) # makes no changes if no rules are provided + ) # updates the priorities of the ErrorRegex objects using the given rules. makes no changes if no rules are provided. err_events = self.check_all_regexes( content=dmesg_content, From 69ba66aac4068a785756e4efb4a5f386909cb0e9 Mon Sep 17 00:00:00 2001 From: niratner Date: Thu, 23 Apr 2026 13:02:53 -0400 Subject: [PATCH 7/8] updated unit tests with new priority override logic --- test/unit/plugin/test_dmesg_analyzer.py | 43 ++++++++++++++++++------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/test/unit/plugin/test_dmesg_analyzer.py b/test/unit/plugin/test_dmesg_analyzer.py index 7aaeb850..8509c340 100644 --- a/test/unit/plugin/test_dmesg_analyzer.py +++ b/test/unit/plugin/test_dmesg_analyzer.py @@ -720,7 +720,7 @@ def test_resolve_priority_no_match(system_info): event_category=EventCategory.RAS, ) rules = [{"event_category": "SW_DRIVER", "new_priority": "WARNING"}] - assert analyzer.resolve_priority(regex_obj, rules) == EventPriority.ERROR + assert analyzer._resolve_priority(regex_obj, rules) == EventPriority.ERROR def test_resolve_priority_match_by_category(system_info): @@ -732,7 +732,7 @@ def test_resolve_priority_match_by_category(system_info): event_category=EventCategory.RAS, ) rules = [{"event_category": "RAS", "new_priority": "WARNING"}] - result = analyzer.resolve_priority(regex_obj, rules) + result = analyzer._resolve_priority(regex_obj, rules) assert result == EventPriority.WARNING @@ -750,7 +750,7 @@ def test_resolve_priority_match_by_message_list(system_info): "new_priority": "WARNING", } ] - result = analyzer.resolve_priority(regex_obj, rules) + result = analyzer._resolve_priority(regex_obj, rules) assert result == EventPriority.WARNING @@ -763,7 +763,7 @@ def test_resolve_priority_no_change(system_info): event_category=EventCategory.RAS, ) rules = [{"event_category": "RAS", "new_priority": "NO_CHANGE"}] - assert analyzer.resolve_priority(regex_obj, rules) == EventPriority.ERROR + assert analyzer._resolve_priority(regex_obj, rules) == EventPriority.ERROR def test_resolve_priority_first_match_wins(system_info): @@ -778,7 +778,7 @@ def test_resolve_priority_first_match_wins(system_info): {"event_category": "RAS", "new_priority": "WARNING"}, {"event_category": "RAS", "new_priority": "ERROR"}, ] - result = analyzer.resolve_priority(regex_obj, rules) + result = analyzer._resolve_priority(regex_obj, rules) assert result == EventPriority.WARNING @@ -794,13 +794,13 @@ def test_resolve_priority_multiple_filter_fields(system_info): rules = [ {"event_category": "RAS", "message": "GPU reset failed", "new_priority": "WARNING"}, ] - assert analyzer.resolve_priority(regex_obj, rules) == EventPriority.WARNING + assert analyzer._resolve_priority(regex_obj, rules) == EventPriority.WARNING # Does NOT match because message differs → returns original priority rules_mismatch = [ {"event_category": "RAS", "message": "ACA Error", "new_priority": "WARNING"}, ] - assert analyzer.resolve_priority(regex_obj, rules_mismatch) == EventPriority.ERROR + assert analyzer._resolve_priority(regex_obj, rules_mismatch) == EventPriority.ERROR def test_resolve_priority_match_all_matches_any_regex(system_info): @@ -818,7 +818,7 @@ def test_resolve_priority_match_all_matches_any_regex(system_info): event_category=EventCategory.SW_DRIVER, ), ]: - result = analyzer.resolve_priority( + result = analyzer._resolve_priority( regex_obj, [{"match_all": True, "new_priority": "WARNING"}] ) assert ( @@ -836,7 +836,7 @@ def test_resolve_priority_match_all_ignores_non_matching_filters(system_info): ) # event_category is RAS, but filter says SW_DRIVER — would normally NOT match. # match_all=True should bypass this check and still apply the rule. - result = analyzer.resolve_priority( + result = analyzer._resolve_priority( regex_obj, [{"match_all": True, "event_category": "SW_DRIVER", "new_priority": "WARNING"}], ) @@ -852,14 +852,14 @@ def test_resolve_priority_match_all_false_still_filters(system_info): event_category=EventCategory.RAS, ) # match_all=False with a non-matching filter → returns original priority - result = analyzer.resolve_priority( + result = analyzer._resolve_priority( regex_obj, [{"match_all": False, "event_category": "SW_DRIVER", "new_priority": "WARNING"}], ) assert result == EventPriority.ERROR # match_all=False with a matching filter → should match - result = analyzer.resolve_priority( + result = analyzer._resolve_priority( regex_obj, [{"match_all": False, "event_category": "RAS", "new_priority": "WARNING"}], ) @@ -953,3 +953,24 @@ def test_custom_regex_with_multiline_pattern(system_info): assert len(res.events) >= 1 start_events = [e for e in res.events if e.description == "Start Error Block"] assert len(start_events) == 1 + + +def test_priority_override_updates_unkown_dmesg_error(system_info): + """NO_CHANGE rule leaves the original event priority intact.""" + dmesg_data = DmesgData( + dmesg_content=("kern :err : 2024-10-07T10:17:15,145363-04:00 UNKOWN DMESG ERROR") + ) + + analyzer = DmesgAnalyzer(system_info=system_info) + res = analyzer.analyze_data( + dmesg_data, + args=DmesgAnalyzerArgs( + check_unknown_dmesg_errors=True, + priority_override_rules=[ + {"message": "Unknown dmesg error", "new_priority": "ERROR"}, + ], + ), + ) + + assert len(res.events) == 1 + assert res.events[0].priority == EventPriority.ERROR From 18ae2c7ba6a3f4329fdc49f67e55185fc7a2ea01 Mon Sep 17 00:00:00 2001 From: niratner Date: Thu, 23 Apr 2026 13:05:08 -0400 Subject: [PATCH 8/8] updated test comment --- test/unit/plugin/test_dmesg_analyzer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/plugin/test_dmesg_analyzer.py b/test/unit/plugin/test_dmesg_analyzer.py index 8509c340..67faaf05 100644 --- a/test/unit/plugin/test_dmesg_analyzer.py +++ b/test/unit/plugin/test_dmesg_analyzer.py @@ -956,7 +956,7 @@ def test_custom_regex_with_multiline_pattern(system_info): def test_priority_override_updates_unkown_dmesg_error(system_info): - """NO_CHANGE rule leaves the original event priority intact.""" + """Updating an 'Unknown dmesg error', which is added after the base ErrorRegex list, successfully changes its priority""" dmesg_data = DmesgData( dmesg_content=("kern :err : 2024-10-07T10:17:15,145363-04:00 UNKOWN DMESG ERROR") )