Skip to content

[ISSUE-41]: Refactor cpu_utils.py#50

Open
lmzuccarelli wants to merge 4 commits intoredhat-et:mainfrom
lmzuccarelli:ISSUE-41
Open

[ISSUE-41]: Refactor cpu_utils.py#50
lmzuccarelli wants to merge 4 commits intoredhat-et:mainfrom
lmzuccarelli:ISSUE-41

Conversation

@lmzuccarelli
Copy link
Collaborator

@lmzuccarelli lmzuccarelli commented Mar 9, 2026

LscpuParser class :

  • Parses lscpu data once and builds indices for efficient queries
  • Uses @DataClass(frozen=True) for immutable CPU info

Error handling improvements

  • Line numbers in parse errors (e.g., "Line 5: Expected 3 columns...")
  • Context in type errors (e.g., "got dict" instead of just the type)
  • Index tracking in merge_cpu_ranges() for debugging
  • Range validation (start > end detection)

Best practices:

  • Type hints throughout
  • _format_range() helper to eliminate duplication
  • frozen=True dataclass for CPU info

Summary by CodeRabbit

  • New Features

    • Extract primary/all CPUs and NUMA node lists from lscpu output.
    • Accept CPU lists or comma-separated ranges and emit compact CPU-range strings.
    • Robust size-string parsing (units and plain numbers) with clearer handling.
  • Refactor

    • Improved CPU topology parsing and NUMA-aware merging for reliability.
    • Stronger typing, deduplication, sorting and range formatting.
  • Bug Fixes

    • Better validation and clearer error messages for malformed/empty inputs.

@coderabbitai
Copy link

coderabbitai bot commented Mar 9, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a frozen CpuInfo dataclass, LscpuParseError, and LscpuParser; introduces NUMA-aware CPU extraction helpers and public exports; refactors CPU range parsing/merging and size parsing with _SIZE_PATTERN; improves typing, input validation, and error handling in cpu_utils.py.

Changes

Cohort / File(s) Summary
CPU parsing & public API
automation/test-execution/ansible/filter_plugins/cpu_utils.py
Added CpuInfo (frozen dataclass), LscpuParseError, and LscpuParser with accessors (get_primary_cpus, get_all_cpus, get_numa_nodes, is_empty). Added and exported helpers: extract_primary_cpus, extract_all_cpus, extract_numa_nodes.
CPU range formatting & merging
automation/test-execution/ansible/filter_plugins/cpu_utils.py
Refactored cpu_list_to_range to accept Union[List[int], str], perform validation, de-duplication, sorting, and format ranges via new internal _format_range. Reworked merge_cpu_ranges to parse composite ranges, validate start/end ordering, accumulate CPU lists, and reformat output.
Size parsing & validation
automation/test-execution/ansible/filter_plugins/cpu_utils.py
Added _SIZE_PATTERN and updated extract_size_value to use regex-based parsing, handle empty inputs, accept plain numbers or unit-suffixed values, and raise clearer errors on invalid input.
Typing, errors & exports
automation/test-execution/ansible/filter_plugins/cpu_utils.py
Added typing imports and annotations, improved error messages, provided AnsibleFilterError fallback, and updated FilterModule.filters() to expose: cpu_list_to_range, extract_primary_cpus, extract_all_cpus, extract_numa_nodes, merge_cpu_ranges, extract_size_value.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title references ISSUE-41 and describes a refactor of cpu_utils.py, which aligns with the main changes—introducing a formal CPU topology parser, enhancing parsing utilities, and improving error handling and typing throughout the module.
Docstring Coverage ✅ Passed Docstring coverage is 92.86% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@automation/test-execution/ansible/filter_plugins/cpu_utils.py`:
- Around line 54-61: Inside __init__ remove the dead local property and unused
attribute: delete the local definition of `@property` cpu_entries and the
self.__cpu_entries assignment, and also remove the commented-out _node_to_cpus
and _node_core_to_min_cpu lines; if a public read-only accessor is required, add
a class-level property named cpu_entries that returns tuple(self._cpu_entries)
so callers use the existing _cpu_entries list; update references to use
_cpu_entries and ensure CpuInfo typing remains correct.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4349dd62-c603-435c-92da-b35921829289

📥 Commits

Reviewing files that changed from the base of the PR and between b8430a4 and 08fff84.

📒 Files selected for processing (1)
  • automation/test-execution/ansible/filter_plugins/cpu_utils.py

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
automation/test-execution/ansible/filter_plugins/cpu_utils.py (1)

387-397: ⚠️ Potential issue | 🟡 Minor

Fix trailing whitespace to pass pre-commit hook.

The pipeline is failing due to trailing whitespace detected by the pre-commit hook. Ensure no trailing whitespace exists at the end of lines (likely line 397).

🔧 Proposed fix
     def filters(self):
         return {
             'cpu_list_to_range': cpu_list_to_range,
             'extract_primary_cpus': extract_primary_cpus,
             'extract_all_cpus': extract_all_cpus,
             'extract_numa_nodes': extract_numa_nodes,
             'merge_cpu_ranges': merge_cpu_ranges,
             'extract_size_value': extract_size_value,
-        } 
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@automation/test-execution/ansible/filter_plugins/cpu_utils.py` around lines
387 - 397, Remove trailing whitespace in the FilterModule definition (the
filters method returning the dict of filter names such as cpu_list_to_range,
extract_primary_cpus, etc.). Edit the end of that return block to strip any
spaces at the ends of lines (especially the final line after the dict) and
ensure the file ends with a single newline with no trailing spaces so the
pre-commit hook passes.
♻️ Duplicate comments (1)
automation/test-execution/ansible/filter_plugins/cpu_utils.py (1)

54-62: ⚠️ Potential issue | 🟠 Major

Remove dead code: property defined inside __init__ is invalid Python.

Lines 54-57 define a @property decorator and method inside __init__, which creates a local function that is immediately discarded—not a class-level property. The __cpu_entries attribute (line 54) is also never used; all actual logic uses _cpu_entries (line 59).

🔧 Proposed fix
         if not isinstance(lscpu_data, str):
             raise LscpuParseError(
                 f"Expected string input, got {type(lscpu_data).__name__}"
             )

-        self.__cpu_entries: List[CpuInfo] = []
-        `@property`
-        def cpu_entries(self) -> Sequence[CpuInfo]:
-            return tuple(self.__cpu_entries)
-
         self._cpu_entries: List[CpuInfo] = []
         self._numa_nodes: Set[int] = set()

If a public read-only accessor is needed, define it at the class level:

`@property`
def cpu_entries(self) -> Sequence[CpuInfo]:
    return tuple(self._cpu_entries)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@automation/test-execution/ansible/filter_plugins/cpu_utils.py` around lines
54 - 62, Remove the dead local property and unused private attribute created
inside __init__: delete the __cpu_entries definition and the `@property`
cpu_entries method that currently lives inside __init__; instead, add a
class-level read-only accessor named cpu_entries that returns
tuple(self._cpu_entries) so callers get an immutable Sequence[CpuInfo], and keep
using the existing _cpu_entries storage and other structures (_numa_nodes,
_node_to_cpus, _node_core_to_min_cpu) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@automation/test-execution/ansible/filter_plugins/cpu_utils.py`:
- Around line 387-397: Remove trailing whitespace in the FilterModule definition
(the filters method returning the dict of filter names such as
cpu_list_to_range, extract_primary_cpus, etc.). Edit the end of that return
block to strip any spaces at the ends of lines (especially the final line after
the dict) and ensure the file ends with a single newline with no trailing spaces
so the pre-commit hook passes.

---

Duplicate comments:
In `@automation/test-execution/ansible/filter_plugins/cpu_utils.py`:
- Around line 54-62: Remove the dead local property and unused private attribute
created inside __init__: delete the __cpu_entries definition and the `@property`
cpu_entries method that currently lives inside __init__; instead, add a
class-level read-only accessor named cpu_entries that returns
tuple(self._cpu_entries) so callers get an immutable Sequence[CpuInfo], and keep
using the existing _cpu_entries storage and other structures (_numa_nodes,
_node_to_cpus, _node_core_to_min_cpu) unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f0e01adf-12ed-4f90-a4a6-005af846e07a

📥 Commits

Reviewing files that changed from the base of the PR and between 08fff84 and ce42164.

📒 Files selected for processing (1)
  • automation/test-execution/ansible/filter_plugins/cpu_utils.py

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@automation/test-execution/ansible/filter_plugins/cpu_utils.py`:
- Around line 220-221: The guards that return empty values when lscpu_data is
missing or not a string should instead raise AnsibleFilterError so callers fail
fast; locate the checks that examine lscpu_data (the if not lscpu_data or not
isinstance(lscpu_data, str) conditions) and replace the silent return with
raising AnsibleFilterError including a clear message like "expected lscpu output
string, got: <type/value>"; apply the same change to the other similar guards
around the function(s) handling topology parsing (the checks at the other
occurrences reported at lines 249-250 and 277-278) so parsing errors surface
immediately.
- Around line 302-304: The loop currently silences non-string elements by
continuing; change it to only skip empty strings and raise AnsibleFilterError
for unexpected types: inside the for idx, cpu_range in enumerate(range_list)
block, if cpu_range == "" (or cpu_range.strip() == ""), continue; if not
isinstance(cpu_range, str), raise AnsibleFilterError with a message that
includes idx and the offending type/value so callers can debug; ensure
AnsibleFilterError is imported/available in cpu_utils.py and use the identifiers
range_list, cpu_range and idx in the error message.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ece7acac-3b15-4117-90de-1918cbb164af

📥 Commits

Reviewing files that changed from the base of the PR and between ce42164 and f47ae27.

📒 Files selected for processing (1)
  • automation/test-execution/ansible/filter_plugins/cpu_utils.py

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
automation/test-execution/ansible/filter_plugins/cpu_utils.py (1)

92-98: Redundant check due to defaultdict.

Since _node_core_to_min_cpu is initialized as defaultdict(dict) at line 57, the explicit check on lines 93-94 is unnecessary—accessing a missing key automatically creates an empty dict.

♻️ Suggested simplification
-            # Index by NUMA node and core for primary CPU tracking
-            if node not in self._node_core_to_min_cpu:
-                self._node_core_to_min_cpu[node] = {}
-            
-            core_dict = self._node_core_to_min_cpu[node]
+            # Index by NUMA node and core for primary CPU tracking
+            core_dict = self._node_core_to_min_cpu[node]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@automation/test-execution/ansible/filter_plugins/cpu_utils.py` around lines
92 - 98, The explicit existence check for node before assigning
self._node_core_to_min_cpu[node] is redundant because _node_core_to_min_cpu is a
defaultdict(dict); simplify the block by directly obtaining core_dict =
self._node_core_to_min_cpu[node] without the if node not in ... check, then keep
the existing min-selection logic (if core not in core_dict or cpu <
core_dict[core]: core_dict[core] = cpu) so primary CPU tracking behavior remains
unchanged; update the code around the assignment to remove the unnecessary
branch referencing _node_core_to_min_cpu, node, core_dict, core, and cpu.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@automation/test-execution/ansible/filter_plugins/cpu_utils.py`:
- Line 89: There is a trailing whitespace in the cpu_utils.py module that trips
the pre-commit hook; open the cpu_utils.py file and remove any trailing spaces
on the blank/empty line around the reported area (near the top of the file
around the existing cpu utility functions), then run `pre-commit run
--all-files` to ensure the trailing-whitespace hook passes before committing.

---

Nitpick comments:
In `@automation/test-execution/ansible/filter_plugins/cpu_utils.py`:
- Around line 92-98: The explicit existence check for node before assigning
self._node_core_to_min_cpu[node] is redundant because _node_core_to_min_cpu is a
defaultdict(dict); simplify the block by directly obtaining core_dict =
self._node_core_to_min_cpu[node] without the if node not in ... check, then keep
the existing min-selection logic (if core not in core_dict or cpu <
core_dict[core]: core_dict[core] = cpu) so primary CPU tracking behavior remains
unchanged; update the code around the assignment to remove the unnecessary
branch referencing _node_core_to_min_cpu, node, core_dict, core, and cpu.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 61419423-987d-478b-886f-04f28b989435

📥 Commits

Reviewing files that changed from the base of the PR and between f47ae27 and fdc2aea.

📒 Files selected for processing (1)
  • automation/test-execution/ansible/filter_plugins/cpu_utils.py

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (3)
automation/test-execution/ansible/filter_plugins/cpu_utils.py (3)

302-304: ⚠️ Potential issue | 🟠 Major

Non-string elements in range_list are silently dropped.

If a templating bug passes None or an integer, the merged affinity mask becomes incomplete with no error. Empty strings should be skipped, but unexpected types should raise.

🔧 Proposed fix
     for idx, cpu_range in enumerate(range_list):
-        if not cpu_range or not isinstance(cpu_range, str):
-            continue
+        if not isinstance(cpu_range, str):
+            raise AnsibleFilterError(
+                f"Invalid element at index {idx}: expected string, got {type(cpu_range).__name__}"
+            )
+        if not cpu_range.strip():
+            continue
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@automation/test-execution/ansible/filter_plugins/cpu_utils.py` around lines
302 - 304, The loop over range_list silently skips non-string values (cpu_range)
which masks templating bugs; update the iteration (the for idx, cpu_range in
enumerate(range_list) block) to continue only for empty strings but raise a
TypeError for any cpu_range that is not a str, including the offending index and
value in the error message so callers can diagnose the bad input rather than
producing an incomplete affinity mask.

249-250: ⚠️ Potential issue | 🟠 Major

Inconsistent error handling: extract_all_cpus silently returns empty while extract_primary_cpus raises.

extract_primary_cpus (lines 218-221) raises AnsibleFilterError for invalid input, but extract_all_cpus silently returns "". Since both consume lscpu output from the same source (per the playbook), this should fail fast consistently.

🔧 Proposed fix
     if not lscpu_data or not isinstance(lscpu_data, str):
-        return ""
+        raise AnsibleFilterError(
+            f"Invalid lscpu input: expected non-empty string, got {type(lscpu_data).__name__}"
+        )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@automation/test-execution/ansible/filter_plugins/cpu_utils.py` around lines
249 - 250, The function extract_all_cpus currently returns an empty string on
invalid input while extract_primary_cpus raises AnsibleFilterError; update
extract_all_cpus to mirror extract_primary_cpus by raising AnsibleFilterError
when lscpu_data is falsy or not a str instead of returning "", i.e., replace the
early-return in extract_all_cpus with a raise AnsibleFilterError carrying a
clear message about invalid lscpu input so both functions fail fast and behave
consistently.

277-278: ⚠️ Potential issue | 🟠 Major

Same inconsistency: extract_numa_nodes should fail fast on invalid input.

Similar to extract_all_cpus, this silently returns [] for invalid input while extract_primary_cpus raises. Apply the same fix for consistency.

🔧 Proposed fix
     if not lscpu_data or not isinstance(lscpu_data, str):
-        return []
+        raise AnsibleFilterError(
+            f"Invalid lscpu input: expected non-empty string, got {type(lscpu_data).__name__}"
+        )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@automation/test-execution/ansible/filter_plugins/cpu_utils.py` around lines
277 - 278, The function extract_numa_nodes currently returns [] on invalid
input; make it fail fast like extract_primary_cpus by validating lscpu_data and
raising the same exception type used in extract_primary_cpus (instead of
returning an empty list). Locate extract_numa_nodes and replace the early return
for non-string/empty lscpu_data with the same raise (e.g., ValueError or
TypeError) and message pattern used in extract_primary_cpus to keep behavior and
error messages consistent across extract_all_cpus, extract_primary_cpus, and
extract_numa_nodes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@automation/test-execution/ansible/filter_plugins/cpu_utils.py`:
- Around line 302-304: The loop over range_list silently skips non-string values
(cpu_range) which masks templating bugs; update the iteration (the for idx,
cpu_range in enumerate(range_list) block) to continue only for empty strings but
raise a TypeError for any cpu_range that is not a str, including the offending
index and value in the error message so callers can diagnose the bad input
rather than producing an incomplete affinity mask.
- Around line 249-250: The function extract_all_cpus currently returns an empty
string on invalid input while extract_primary_cpus raises AnsibleFilterError;
update extract_all_cpus to mirror extract_primary_cpus by raising
AnsibleFilterError when lscpu_data is falsy or not a str instead of returning
"", i.e., replace the early-return in extract_all_cpus with a raise
AnsibleFilterError carrying a clear message about invalid lscpu input so both
functions fail fast and behave consistently.
- Around line 277-278: The function extract_numa_nodes currently returns [] on
invalid input; make it fail fast like extract_primary_cpus by validating
lscpu_data and raising the same exception type used in extract_primary_cpus
(instead of returning an empty list). Locate extract_numa_nodes and replace the
early return for non-string/empty lscpu_data with the same raise (e.g.,
ValueError or TypeError) and message pattern used in extract_primary_cpus to
keep behavior and error messages consistent across extract_all_cpus,
extract_primary_cpus, and extract_numa_nodes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9e8ebc63-2229-4ca7-8890-9882ec80e1fd

📥 Commits

Reviewing files that changed from the base of the PR and between fdc2aea and f30311b.

📒 Files selected for processing (1)
  • automation/test-execution/ansible/filter_plugins/cpu_utils.py

Copy link
Collaborator

@PoolPooer PoolPooer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me 👍🏻

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants