Skip to content

Refactor/11850 Flow_threads refactor: unify recycler and manager computation v2 - #41

Closed
KEIAHNY wants to merge 1 commit into
mainfrom
11850-refactor-flow_threads-v2
Closed

KEIAHNY wants to merge 1 commit into
mainfrom
11850-refactor-flow_threads-v2

Conversation

@KEIAHNY

@KEIAHNY KEIAHNY commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator

PR Type

Enhancement, Bug fix


Description

  • Extract shared get_up_windows helper for window scanning

  • Extract get_window_cpu_average_vector for CPU averaging

  • Reuse helpers in recycler and manager computations

  • Reset up flag each iteration, fixing stale state


Diagram Walkthrough

flowchart LR
  A["get_recycler_count"] -- "above HUBER_THRESHOLD" --> C["get_up_windows"]
  B["manager count logic"] -- "below MANAGER_SLOPE" --> C
  C -- "per-window averages" --> D["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

  • Added generic get_up_windows function unifying the duplicated
    window-scanning loops from recycler and manager computations,
    parameterized by threshold and above
  • Added get_window_cpu_average_vector helper for per-window CPU usage
    averaging
  • Simplified get_recycler_count and manager count logic to consume the
    new helpers
  • Reset up flag at each loop iteration fixing stale state across
    windows; fixed misplaced brace and added trailing newline
+66/-103

@KEIAHNY KEIAHNY self-assigned this Oct 12, 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 usage check

The manager computation previously verified that average CPU usage exceeded CPU_USAGE_MAX for each window element before marking a window as "up" (if avg > CPU_USAGE_MAX { up &= true; } else { up &= false; break; }). The new unified get_up_windows helper only checks the slope threshold, so this CPU check is silently removed from the manager path. As a result, windows where the flow manager CPU is low are now accepted as long as the slope is below MANAGER_SLOPE, which can change mgr_power and the resulting manager count. The now-unused avg variable in the loop confirms the check was lost rather than intentionally relocated.

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;
Potential out-of-bounds panic

get_up_windows advances counter until current_windows.last() matches one of the last three elements of result, relying on value equality of f64 samples. If no window's last element matches (e.g., monotonically increasing queue values where the final samples differ from earlier ones), result[(counter*WINDOWS) as usize..((counter+1)*WINDOWS) as usize] will eventually index past the end of result and panic. This existed in the previous code, but the refactor consolidates it into a shared helper now used by both the recycler and manager paths, so both call sites are exposed.

current_windows = result[(counter*WINDOWS) as usize..((counter+1)*WINDOWS) as usize].to_vec();

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;
    }
}
sm_counter=0;

if up {
    up_windows.push((counter, tmp_slope_vector.clone()));
}

let last_three = result.iter().rev().take(3).collect::<Vec<_>>();
if last_three.contains(&current_windows.last().expect("Unable to get last element from current_window vector."))  {
    break;
}

counter +=1;

@github-actions

github-actions Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Add bounds check to prevent index-out-of-bounds panic

*The loop termination relies on a fragile exact f64 equality check against the last
three elements of result. If that condition never matches, counter grows until the
slice result[(counter*WINDOWS)..((counter+1)WINDOWS)] panics with an
index-out-of-bounds error. Add an explicit bounds check at the top of the loop to
guarantee termination.

src/flow_threads.rs [83-86]

         loop {
+            if ((counter + 1) * WINDOWS) as usize > result.len() {
+                break;
+            }
             avg_cpu_usage = Self::get_window_cpu_average_vector(cpu_usages, num_elements, counter);
 
             current_windows = result[(counter*WINDOWS) as usize..((counter+1)*WINDOWS) as usize].to_vec();
Suggestion importance[1-10]: 7

__

Why: The loop's termination depends on a fragile float-equality check, and if it fails to trigger, the slice result[(counter*WINDOWS)..((counter+1)*WINDOWS)] can panic with an out-of-bounds error. Adding an explicit bounds check is a valid defensive fix for a real potential panic, though the existing termination logic usually covers the aligned case.

Medium
Guard against division by zero producing NaN

If get_up_windows returns an entry with an empty tmp_slope_vector (possible when a
window slice is empty and up stays true), the division by tmp_slope_vector.len()
produces NaN, which then propagates into mgr_power and the module result. Skip empty
vectors before computing the average.

src/flow_threads.rs [184-186]

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

__

Why: An empty tmp_slope_vector would produce NaN via division by zero, which would then propagate into mgr_power. The guard is correct but somewhat defensive since tmp_slope_vector is normally non-empty when up_windows entries are created; it is a minor robustness improvement.

Low
Handle empty stat vectors to avoid NaN averages

If now_in_queue or now_recycled is empty (e.g., now_recycled has fewer than 2
elements before the .windows(2) diff), the division by zero yields NaN for the
averages, which poisons slope_vector and the subsequent max_by comparison. Fall back
to 0.0 when the vectors are empty.

src/flow_threads.rs [156-158]

-            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: Guarding against empty now_in_queue/now_recycled avoids NaN averages, but these slices are derived from fixed-size index ranges that are expected to be non-empty, and silently defaulting to 0.0 could mask underlying data issues. It is a low-impact defensive change.

Low
General
Replace fragile float-equality loop termination

The termination condition compares floating-point values for exact equality, which
is unreliable for regression output and may never trigger (leading to unbounded
counter growth). Additionally, current_windows.last() panics if the slice is empty.
Compare slice indices/ranges instead of element values, e.g., break when the
remaining tail of result is exhausted.

src/flow_threads.rs [117-120]

             let last_three = result.iter().rev().take(3).collect::<Vec<_>>();
-            if last_three.contains(&current_windows.last().expect("Unable to get last element from current_window vector."))  {
+            if last_three.contains(current_windows.last().unwrap_or(&0.0)) {
                 break;
             }
 
+            if result.len() - (counter as usize * WINDOWS as usize) < WINDOWS as usize {
+                break;
+            }
+
Suggestion importance[1-10]: 4

__

Why: The concern about exact f64 equality for loop termination and the potential panic on current_windows.last() is valid, but the improved_code does not fully implement the suggestion — it retains the float-equality check and merely adds a bounds check and unwrap_or, so it only partially addresses the described problem.

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

@KEIAHNY

KEIAHNY commented Oct 12, 2026

Copy link
Copy Markdown
Collaborator Author

Closing - applying changes.

@KEIAHNY KEIAHNY closed this Oct 12, 2026
@KEIAHNY
KEIAHNY deleted the 11850-refactor-flow_threads-v2 branch October 12, 2026 15:43
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