Skip to content

Refactor/11850 Flow_threads refactor: unify recycler and manager computation v4 - #43

Merged
KEIAHNY merged 1 commit into
mainfrom
11850-refactor-flow_threads-v4
Oct 13, 2026
Merged

KEIAHNY merged 1 commit into
mainfrom
11850-refactor-flow_threads-v4

Conversation

@KEIAHNY

@KEIAHNY KEIAHNY commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator

PR Type

Enhancement


Description

  • Extract shared get_up_windows helper for window analysis

  • Parameterize threshold comparison via above flag

  • Add get_window_cpu_average_vector CPU averaging helper

  • Remove resolved duplication item from ISSUES.md


Diagram Walkthrough

flowchart LR
  recycler["get_recycler_count"] -- "above HUBER_THRESHOLD" --> upw["get_up_windows"]
  manager["manager count computation"] -- "below MANAGER_SLOPE" --> upw
  upw -- "averages CPU per window" --> cpu["get_window_cpu_average_vector"]
Loading

File Walkthrough

Relevant files
Enhancement
flow_threads.rs
Unify recycler and manager window computation logic           

src/flow_threads.rs

  • Extract duplicated window-scanning loop into shared get_up_windows
    helper
  • Add above boolean parameter to support both > and < threshold checks
  • Extract CPU usage averaging into get_window_cpu_average_vector helper
  • Simplify get_recycler_count and manager computation to reuse the
    helpers
+68/-106
Documentation
ISSUES.md
Drop resolved duplication issue from tracker                         

ISSUES.md

  • Remove code quality item about recycler/manager duplicated code
+0/-1     

@KEIAHNY KEIAHNY self-assigned this Oct 13, 2026
@github-actions

Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Dropped CPU check

The unified get_up_windows helper dropped the if avg > CPU_USAGE_MAX check that existed in the old manager count loop. Previously, a manager window only counted as "up" if both the slope was below MANAGER_SLOPE and the average CPU usage exceeded CPU_USAGE_MAX; now the CPU condition is silently ignored, so windows with low CPU usage are treated as up and included in slope_vector. This can cause the module to recommend adding manager threads when manager CPU is not saturated. The leftover unused avg computation inside the loop confirms the check was accidentally omitted during the refactor — either restore the check (possibly behind a parameter, like above) or remove the dead avg computation.

for current_window in &current_windows {
    let avg: f32 = avg_cpu_usage[(sm_counter * (avg_cpu_usage.len()/WINDOWS as usize)) ..
        ((sm_counter + 1) * (avg_cpu_usage.len()/WINDOWS as usize))]
        .iter().sum::<f32>() / (avg_cpu_usage.len()/WINDOWS as usize) as f32;
    sm_counter+=1;

    if (above && current_window > &threshold) || (!above && current_window < &threshold) {
        tmp_slope_vector.push(*current_window);
        up &= true;
    }
    else {
        up &= false;
        break;
    }

    if avg > CPU_USAGE_MAX {
        up &= true;
    }
    else {
        up &= false;
        break;
    }
Behavioral change in 'up' reset

The old code declared up once and never reset it, so after the first failing window up &= false kept it false for all subsequent iterations — meaning only windows before the first failure could be counted. The new code resets up = true at the end of each loop iteration, so all qualifying windows are now collected. This changes the results for both the recycler and manager paths whenever an early window fails the threshold check and a later one passes. If this is an intentional bug fix it should be verified; otherwise restore the previous semantics.

up = true;

@github-actions

github-actions Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Prevent out-of-bounds panics on CPU usage arrays

The loop in get_up_windows only validates bounds against result.len(), but
get_window_cpu_average_vector indexes into each thread's cpu_usage array using the
same window counter. If the CPU usage arrays are shorter than the regression output,
this slice will panic with an index-out-of-range error. Add a bounds check so a
window is skipped when the CPU data does not cover it.

src/flow_threads.rs [239-244]

         for i in 0..WINDOWS {
+            let start = ((counter*WINDOWS+i)*num_elements) as usize;
+            let end = (((counter*WINDOWS+1)+i)*num_elements) as usize;
             for cpu_usage in cpu_usages {
-                let avg: f32 = cpu_usage.cpu_usage[((counter*WINDOWS+i)*num_elements) as usize..(((counter*WINDOWS+1)+i)*num_elements) as usize]
+                if end > cpu_usage.cpu_usage.len() {
+                    return Vec::new();
+                }
+                let avg: f32 = cpu_usage.cpu_usage[start..end]
                     .iter().sum::<f32>() / (num_elements as f32);
                 avg_cpu_usage.push(avg);
             }
         }
Suggestion importance[1-10]: 4

__

Why: The get_up_windows loop only bounds-checks against result.len() while get_window_cpu_average_vector slices cpu_usage with the same window counter, so a length mismatch would panic. The suggested guard is a reasonable defensive fix, though the Vec::new() early return would itself shift the empty-vector problem to the caller, and in practice the arrays are expected to be consistent, so the impact is moderate.

Low
Guard averages against empty vector division

now_recycled is produced via windows(2) over a slice, so if the slice has fewer than
two elements it becomes empty and the division by now_recycled.len() yields NaN (or
panics on a zero-length division in debug). Guard the averages against empty vectors
so NaN values do not propagate into queue_growth comparisons.

src/flow_threads.rs [155-157]

-            recycler_up.now_in_queue_avg = now_in_queue.iter().sum::<f64>() / now_in_queue.len() as f64;
-            recycler_up.now_recycled_avg = now_recycled.iter().sum::<f64>() / now_recycled.len() as f64;
+            recycler_up.now_in_queue_avg = if now_in_queue.is_empty() { 0.0 } else { now_in_queue.iter().sum::<f64>() / now_in_queue.len() as f64 };
+            recycler_up.now_recycled_avg = if now_recycled.is_empty() { 0.0 } else { now_recycled.iter().sum::<f64>() / now_recycled.len() as f64 };
             slope_vector.push(recycler_up);
Suggestion importance[1-10]: 4

__

Why: now_recycled derived via windows(2) could be empty, producing a NaN that propagates into the max_by/partial_cmp comparisons. The guard is valid and correctly applied, but the claim about a debug panic is inaccurate (f64 division by zero yields NaN in Rust), and the scenario is an edge case, limiting its importance.

Low
General
Handle missing CPU usage data explicitly

If cpu_usages is empty, avg_cpu_usage remains empty and avg_cpu_usage.len()/WINDOWS
as usize evaluates to 0, making avg a 0.0/0.0 NaN. Since NaN comparisons with
CPU_USAGE_MAX are always false, up becomes false and all windows are silently
discarded. Short-circuit when there is no CPU usage data so the behavior is
explicit.

src/flow_threads.rs [92-96]

+            if avg_cpu_usage.is_empty() {
+                break;
+            }
             for current_window in &current_windows {
                 let avg: f32 = avg_cpu_usage[(sm_counter * (avg_cpu_usage.len()/WINDOWS as usize)) ..
                     ((sm_counter + 1) * (avg_cpu_usage.len()/WINDOWS as usize))]
                     .iter().sum::<f32>() / (avg_cpu_usage.len()/WINDOWS as usize) as f32;
                 sm_counter+=1;
Suggestion importance[1-10]: 2

__

Why: The empty avg_cpu_usage scenario is real but its consequences are overstated: the CPU_USAGE_MAX check referenced in the suggestion was removed in this PR, and the computed avg is now unused, so the NaN only affects a dead computation. The added early break would also abort the whole window loop rather than just the current window.

Low
Filter empty windows before averaging

If a window qualifies with no collected elements (e.g., an empty tmp_slope_vector
returned for an edge-case window), dividing by tmp_slope_vector.len() yields NaN,
which then corrupts the subsequent min_by/partial_cmp on slope_vector. Filter out
empty vectors before computing the average.

src/flow_threads.rs [183-185]

         for (_counter, tmp_slope_vector) in Self::get_up_windows(&flow_mgr_full_result, &mgr_cpu_usages, num_elements, MANAGER_SLOPE, false) {
+            if tmp_slope_vector.is_empty() {
+                continue;
+            }
             slope_vector.push(tmp_slope_vector.iter().sum::<f64>() / tmp_slope_vector.len() as f64);
         }
Suggestion importance[1-10]: 2

__

Why: In get_up_windows, a window is only recorded when up holds, which requires every element of current_windows to be pushed into tmp_slope_vector, so an empty vector is essentially impossible (unless WINDOWS is 0). The filter is harmless but addresses a speculative edge case with negligible practical impact.

Low
  • Author self-review: I have reviewed the PR code suggestions, and addressed the relevant ones.

@KEIAHNY

KEIAHNY commented Oct 13, 2026

Copy link
Copy Markdown
Collaborator Author

Merging - all the other warnings are about checking input - another PR.

@KEIAHNY
KEIAHNY merged commit 8e76297 into main Oct 13, 2026
3 checks passed
@KEIAHNY
KEIAHNY deleted the 11850-refactor-flow_threads-v4 branch October 13, 2026 09:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant