use crate::ir::{FrameCategory, ProfileIR};
use super::{CpuAnalysis, FunctionStats, PerformancePattern};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Priority {
Critical,
High,
Medium,
Low,
}
impl std::fmt::Display for Priority {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Critical => write!(f, "CRITICAL"),
Self::High => write!(f, "HIGH"),
Self::Medium => write!(f, "MEDIUM"),
Self::Low => write!(f, "LOW"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Effort {
QuickWin,
Moderate,
Significant,
Major,
}
impl std::fmt::Display for Effort {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::QuickWin => write!(f, "Quick Win"),
Self::Moderate => write!(f, "Moderate"),
Self::Significant => write!(f, "Significant"),
Self::Major => write!(f, "Major Refactor"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IssueType {
Algorithm,
Memory,
Dependency,
Serialization,
Caching,
Startup,
Recursion,
Hotspot,
}
impl std::fmt::Display for IssueType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Algorithm => write!(f, "Algorithm"),
Self::Memory => write!(f, "Memory"),
Self::Dependency => write!(f, "Dependency"),
Self::Serialization => write!(f, "Serialization"),
Self::Caching => write!(f, "Caching"),
Self::Startup => write!(f, "Startup"),
Self::Recursion => write!(f, "Recursion"),
Self::Hotspot => write!(f, "Hotspot"),
}
}
}
#[derive(Debug, Clone)]
pub struct Recommendation {
pub priority: Priority,
pub effort: Effort,
pub issue_type: IssueType,
pub title: String,
pub target: String,
pub location: String,
pub current_time_us: u64,
pub estimated_savings_us: u64,
pub root_cause: String,
pub actions: Vec<String>,
pub code_patterns: Vec<String>,
pub evidence: Vec<String>,
}
impl Recommendation {
pub fn roi_score(&self) -> f64 {
let effort_multiplier = match self.effort {
Effort::QuickWin => 4.0,
Effort::Moderate => 2.0,
Effort::Significant => 1.0,
Effort::Major => 0.5,
};
self.estimated_savings_us as f64 * effort_multiplier
}
#[expect(clippy::cast_precision_loss)]
pub fn savings_percent(&self, total_time: u64) -> f64 {
if total_time == 0 {
0.0
} else {
(self.estimated_savings_us as f64 / total_time as f64) * 100.0
}
}
}
#[derive(Debug, Clone)]
pub struct RecommendationReport {
pub recommendations: Vec<Recommendation>,
pub insights: Vec<String>,
pub quick_wins: Vec<usize>, pub investigations: Vec<String>,
}
pub struct RecommendationEngine;
impl RecommendationEngine {
fn should_include_category(category: FrameCategory, analysis: &CpuAnalysis) -> bool {
let filters = &analysis.metadata.filter_categories;
filters.is_empty() || filters.contains(&category)
}
#[expect(clippy::cast_precision_loss)]
pub fn analyze(profile: &ProfileIR, analysis: &CpuAnalysis) -> RecommendationReport {
let mut recommendations = Vec::new();
let mut insights = Vec::new();
let mut investigations = Vec::new();
let total_time = analysis.total_time;
let filters = &analysis.metadata.filter_categories;
Self::analyze_hotspots(analysis, total_time, &mut recommendations, &mut insights);
let should_analyze_gc = filters.is_empty()
|| filters.contains(&FrameCategory::V8Internal)
|| filters.contains(&FrameCategory::App) || filters.contains(&FrameCategory::Deps);
if should_analyze_gc {
Self::analyze_gc(analysis, total_time, &mut recommendations, &mut insights);
}
if Self::should_include_category(FrameCategory::Deps, analysis) {
Self::analyze_dependencies(analysis, total_time, &mut recommendations, &mut insights);
}
Self::analyze_recursion(analysis, total_time, &mut recommendations);
Self::analyze_phases(analysis, total_time, &mut recommendations, &mut insights);
Self::detect_patterns(profile, analysis, total_time, &mut recommendations);
Self::generate_investigations(analysis, &mut investigations);
let min_savings_threshold = total_time / 200; recommendations.retain(|r| {
let savings_pct = r.savings_percent(total_time);
if r.estimated_savings_us < min_savings_threshold {
return false;
}
if matches!(r.effort, Effort::Significant | Effort::Major) && savings_pct < 2.0 {
return false;
}
true
});
recommendations.sort_by(|a, b| {
a.priority.cmp(&b.priority).then_with(|| {
b.roi_score()
.partial_cmp(&a.roi_score())
.unwrap_or(std::cmp::Ordering::Equal)
})
});
let quick_wins: Vec<usize> = recommendations
.iter()
.enumerate()
.filter(|(_, r)| r.effort == Effort::QuickWin && r.savings_percent(total_time) >= 1.0)
.map(|(i, _)| i)
.collect();
RecommendationReport {
recommendations,
insights,
quick_wins,
investigations,
}
}
#[expect(clippy::cast_precision_loss)]
fn analyze_hotspots(
analysis: &CpuAnalysis,
total_time: u64,
recommendations: &mut Vec<Recommendation>,
insights: &mut Vec<String>,
) {
for func in analysis.functions.iter().take(10) {
let self_pct = func.self_percent(total_time);
let total_pct = func.total_percent(total_time);
if self_pct < 3.0 && total_pct < 10.0 {
continue;
}
let pattern = func.performance_pattern(analysis.total_samples);
let (priority, issue_type, root_cause, actions, effort, savings_ratio) =
Self::classify_hotspot(func, &pattern, self_pct, total_pct);
let estimated_savings = (func.self_time as f64 * savings_ratio) as u64;
let mut evidence = vec![
format!("{:.1}% of total CPU time", self_pct),
format!("{} samples", func.self_samples),
];
if func.is_recursive() {
evidence.push(format!(
"Recursive (max depth: {})",
func.max_recursion_depth
));
}
let code_patterns = Self::suggest_code_patterns(&func.name, &func.location, issue_type);
recommendations.push(Recommendation {
priority,
effort,
issue_type,
title: Self::generate_title(func, issue_type),
target: func.name.clone(),
location: func.location.clone(),
current_time_us: func.self_time,
estimated_savings_us: estimated_savings,
root_cause,
actions,
code_patterns,
evidence,
});
}
let top3_pct: f64 = analysis
.functions
.iter()
.take(3)
.map(|f| f.self_percent(total_time))
.sum();
if top3_pct > 50.0 {
insights.push(format!(
"Top 3 functions account for {:.0}% of CPU time — focused optimization will have high impact",
top3_pct
));
} else if top3_pct < 20.0 {
insights.push(
"CPU time is well-distributed — consider architectural improvements over micro-optimizations".to_string()
);
}
}
fn classify_hotspot(
func: &FunctionStats,
pattern: &PerformancePattern,
self_pct: f64,
total_pct: f64,
) -> (Priority, IssueType, String, Vec<String>, Effort, f64) {
match pattern {
PerformancePattern::CriticalPath => {
let priority = if self_pct >= 20.0 {
Priority::Critical
} else {
Priority::High
};
(
priority,
IssueType::Algorithm,
"Called frequently AND expensive per call — this is on the critical path"
.to_string(),
vec![
"Profile this function in isolation to find the slow code path".to_string(),
"Consider algorithmic improvements (caching, better data structures)"
.to_string(),
"Look for unnecessary work that can be skipped".to_string(),
"Consider breaking into smaller functions to isolate the bottleneck"
.to_string(),
],
Effort::Moderate,
0.5, )
}
PerformancePattern::ExpensiveOperation => {
let (priority, effort) = if self_pct >= 15.0 {
(Priority::High, Effort::Moderate)
} else if self_pct >= 5.0 {
(Priority::Medium, Effort::Moderate)
} else {
(Priority::Low, Effort::QuickWin)
};
(
priority,
IssueType::Algorithm,
"Each call is expensive but infrequent — optimize the operation itself"
.to_string(),
vec![
"Check for O(n²) or worse algorithms".to_string(),
"Look for synchronous I/O or blocking operations".to_string(),
"Consider lazy evaluation or streaming".to_string(),
"Profile memory allocations in this function".to_string(),
],
effort,
0.4,
)
}
PerformancePattern::FrequentlyCalled => {
let actions = if func.category == FrameCategory::Deps {
vec![
"Reduce call frequency by batching operations".to_string(),
"Cache results if the function is pure".to_string(),
"Consider inlining or replacing with native code".to_string(),
]
} else {
vec![
"Memoize results if inputs repeat".to_string(),
"Move invariant computations outside loops".to_string(),
"Consider batching multiple calls".to_string(),
"Check if calls can be eliminated entirely".to_string(),
]
};
let priority = if self_pct >= 10.0 {
Priority::High
} else if self_pct >= 3.0 {
Priority::Medium
} else {
Priority::Low
};
(
priority,
IssueType::Caching,
"Called very frequently — consider caching or batching".to_string(),
actions,
Effort::QuickWin,
0.3,
)
}
PerformancePattern::Normal => {
if total_pct >= 20.0 {
(
Priority::Medium,
IssueType::Hotspot,
"High inclusive time suggests expensive callees".to_string(),
vec![
"Examine what this function calls".to_string(),
"The bottleneck may be in a callee, not this function".to_string(),
"Check the caller/callee analysis for this function".to_string(),
],
Effort::Moderate,
0.2,
)
} else {
(
Priority::Low,
IssueType::Hotspot,
"Minor hotspot".to_string(),
vec![
"Low priority — optimize only if other issues are resolved".to_string(),
],
Effort::Moderate,
0.1,
)
}
}
}
}
fn generate_title(func: &FunctionStats, issue_type: IssueType) -> String {
match issue_type {
IssueType::Algorithm => format!("Optimize algorithm in `{}`", func.name),
IssueType::Caching => format!("Add caching/memoization to `{}`", func.name),
IssueType::Memory => format!("Reduce allocations in `{}`", func.name),
IssueType::Dependency => format!("Optimize or replace `{}`", func.name),
IssueType::Serialization => format!("Optimize serialization in `{}`", func.name),
IssueType::Startup => format!("Defer or lazy-load `{}`", func.name),
IssueType::Recursion => format!("Convert `{}` to iterative", func.name),
IssueType::Hotspot => format!("Investigate `{}`", func.name),
}
}
fn suggest_code_patterns(name: &str, location: &str, issue_type: IssueType) -> Vec<String> {
let mut patterns = Vec::new();
let name_lower = name.to_lowercase();
let loc_lower = location.to_lowercase();
if name_lower.contains("json")
|| name_lower.contains("parse")
|| name_lower.contains("stringify")
{
patterns.push("JSON.parse() / JSON.stringify() calls".to_string());
patterns.push("Consider streaming JSON parsing for large payloads".to_string());
}
if name_lower.contains("regex")
|| name_lower.contains("regexp")
|| name_lower.contains("match")
{
patterns.push("Regular expression operations".to_string());
patterns.push("Compile regex once and reuse, avoid in loops".to_string());
}
if name_lower.contains("sort")
|| name_lower.contains("find")
|| name_lower.contains("search")
{
patterns.push("Sorting or searching operations".to_string());
patterns.push("Check if data structure supports faster lookups (Map/Set)".to_string());
}
if name_lower.contains("each")
|| name_lower.contains("map")
|| name_lower.contains("filter")
{
patterns.push("Array iteration methods".to_string());
patterns
.push("Consider early termination, or use for loop for performance".to_string());
}
if name_lower.contains("transform")
|| name_lower.contains("convert")
|| name_lower.contains("compile")
{
patterns.push("Data transformation/compilation".to_string());
patterns.push("Cache transformation results if inputs repeat".to_string());
}
if loc_lower.contains("lodash") {
patterns.push("Consider native alternatives to lodash functions".to_string());
}
if loc_lower.contains("moment") {
patterns.push("Consider lighter date libraries (date-fns, dayjs)".to_string());
}
match issue_type {
IssueType::Caching => {
patterns.push("Look for repeated calls with same arguments".to_string());
patterns.push("Check if results can be memoized with a Map/WeakMap".to_string());
}
IssueType::Memory => {
patterns.push("Look for object/array creation in loops".to_string());
patterns.push("Consider object pooling or reuse".to_string());
}
_ => {}
}
patterns
}
#[expect(clippy::cast_precision_loss)]
fn analyze_gc(
analysis: &CpuAnalysis,
total_time: u64,
recommendations: &mut Vec<Recommendation>,
insights: &mut Vec<String>,
) {
let Some(gc) = &analysis.gc_analysis else {
return;
};
let gc_pct = if total_time > 0 {
(gc.total_time as f64 / total_time as f64) * 100.0
} else {
0.0
};
if gc_pct < 5.0 {
return;
}
let priority = if gc_pct >= 15.0 {
Priority::Critical
} else if gc_pct >= 10.0 {
Priority::High
} else {
Priority::Medium
};
let target_gc_time = (total_time as f64 * 0.02) as u64;
let potential_savings = gc.total_time.saturating_sub(target_gc_time);
insights.push(format!(
"GC overhead is {:.1}% — reducing allocations could save {:.1}ms",
gc_pct,
potential_savings as f64 / 1000.0
));
if let Some(hotspot) = gc.allocation_hotspots.first() {
let hotspot_savings =
(potential_savings as f64 * hotspot.gc_correlation / 100.0) as u64;
recommendations.push(Recommendation {
priority,
effort: Effort::Moderate,
issue_type: IssueType::Memory,
title: format!("Reduce allocations in `{}`", hotspot.name),
target: hotspot.name.clone(),
location: hotspot.location.clone(),
current_time_us: gc.total_time,
estimated_savings_us: hotspot_savings,
root_cause: format!(
"This function appears in {:.0}% of GC samples, indicating heavy allocation",
hotspot.gc_correlation
),
actions: vec![
"Reuse objects instead of creating new ones".to_string(),
"Use object pools for frequently created objects".to_string(),
"Avoid creating closures in loops".to_string(),
"Pre-allocate arrays with known size".to_string(),
"Use TypedArrays for numeric data".to_string(),
],
code_patterns: vec![
"new Object() / {} literals in loops".to_string(),
"Array.push() in tight loops (pre-allocate instead)".to_string(),
"String concatenation (use array.join or template literals)".to_string(),
"Spread operator creating copies".to_string(),
],
evidence: vec![
format!("{:.1}% GC overhead", gc_pct),
format!("{} GC events", gc.sample_count),
format!("~{:.0}μs average GC pause", gc.avg_pause_us),
],
});
}
}
#[expect(clippy::cast_precision_loss)]
fn analyze_dependencies(
analysis: &CpuAnalysis,
total_time: u64,
recommendations: &mut Vec<Recommendation>,
insights: &mut Vec<String>,
) {
let deps_pct = analysis.category_breakdown.percent(FrameCategory::Deps);
if deps_pct >= 40.0 {
insights.push(format!(
"Dependencies consume {:.0}% of CPU — review if all are necessary",
deps_pct
));
}
for pkg in analysis.package_stats.iter().take(3) {
let pkg_pct = if total_time > 0 {
(pkg.time as f64 / total_time as f64) * 100.0
} else {
0.0
};
if pkg_pct < 3.0 {
continue;
}
let (actions, effort, savings_ratio) = Self::get_package_advice(&pkg.package);
recommendations.push(Recommendation {
priority: if pkg_pct >= 15.0 {
Priority::High
} else {
Priority::Medium
},
effort,
issue_type: IssueType::Dependency,
title: format!("Optimize `{}` usage", pkg.package),
target: pkg.package.clone(),
location: pkg.top_function_location.clone(),
current_time_us: pkg.time,
estimated_savings_us: (pkg.time as f64 * savings_ratio) as u64,
root_cause: format!(
"Package `{}` consumes {:.1}% of CPU time",
pkg.package, pkg_pct
),
actions,
code_patterns: vec![
format!("import {{ ... }} from '{}'", pkg.package),
format!("require('{}')", pkg.package),
],
evidence: vec![
format!("{:.1}% of total CPU time", pkg_pct),
format!("{:.1}% of dependency time", pkg.percent_of_deps),
format!("Hottest function: {}", pkg.top_function),
],
});
}
}
fn get_package_advice(package: &str) -> (Vec<String>, Effort, f64) {
let pkg_lower = package.to_lowercase();
if pkg_lower.contains("lodash") {
return (
vec![
"Import only needed functions: `import map from 'lodash/map'`".to_string(),
"Consider native alternatives (Array.map, Object.keys, etc.)".to_string(),
"Use lodash-es for better tree-shaking".to_string(),
],
Effort::QuickWin,
0.3,
);
}
if pkg_lower.contains("moment") {
return (
vec![
"Replace with date-fns or dayjs (10-20x smaller)".to_string(),
"Use native Intl.DateTimeFormat for formatting".to_string(),
"Avoid parsing strings repeatedly".to_string(),
],
Effort::Moderate,
0.5,
);
}
if pkg_lower.contains("axios") {
return (
vec![
"Consider native fetch() API".to_string(),
"Reuse axios instances".to_string(),
"Check if interceptors add overhead".to_string(),
],
Effort::Moderate,
0.2,
);
}
if pkg_lower.contains("babel") || pkg_lower.contains("typescript") {
return (
vec![
"This is build-time overhead — ensure not running in production".to_string(),
"Pre-compile code instead of runtime transpilation".to_string(),
"Check for accidental ts-node or @babel/register in prod".to_string(),
],
Effort::QuickWin,
0.8,
);
}
if pkg_lower.contains("webpack")
|| pkg_lower.contains("esbuild")
|| pkg_lower.contains("vite")
{
return (
vec![
"Build tools should not run in production".to_string(),
"Check for dev dependencies imported at runtime".to_string(),
],
Effort::QuickWin,
0.9,
);
}
if pkg_lower.contains("ajv") || pkg_lower.contains("joi") || pkg_lower.contains("yup") {
return (
vec![
"Compile schemas once, reuse validators".to_string(),
"Consider lighter validation for hot paths".to_string(),
"Skip validation in trusted internal calls".to_string(),
],
Effort::QuickWin,
0.4,
);
}
(
vec![
"Check if this package is necessary".to_string(),
"Look for lighter alternatives".to_string(),
"Consider lazy-loading if not needed at startup".to_string(),
],
Effort::Moderate,
0.3,
)
}
#[expect(clippy::cast_precision_loss)]
fn analyze_recursion(
analysis: &CpuAnalysis,
total_time: u64,
recommendations: &mut Vec<Recommendation>,
) {
for rec_func in &analysis.recursive_functions {
let rec_pct = if total_time > 0 {
(rec_func.recursive_time as f64 / total_time as f64) * 100.0
} else {
0.0
};
if rec_pct < 3.0 || rec_func.max_depth < 5 {
continue;
}
recommendations.push(Recommendation {
priority: if rec_pct >= 10.0 { Priority::High } else { Priority::Medium },
effort: Effort::Moderate,
issue_type: IssueType::Recursion,
title: format!("Convert `{}` to iterative", rec_func.name),
target: rec_func.name.clone(),
location: rec_func.location.clone(),
current_time_us: rec_func.recursive_time,
estimated_savings_us: rec_func.recursive_time / 3, root_cause: format!(
"Deep recursion (max depth {}) causes stack overhead and potential stack overflow risk",
rec_func.max_depth
),
actions: vec![
"Convert to iterative algorithm with explicit stack".to_string(),
"Consider tail-call optimization if applicable".to_string(),
"Add memoization to avoid redundant recursive calls".to_string(),
"Limit recursion depth with an iterative fallback".to_string(),
],
code_patterns: vec![
"function f() { ... f() ... }".to_string(),
"Look for tree/graph traversal".to_string(),
],
evidence: vec![
format!("Max recursion depth: {}", rec_func.max_depth),
format!("{} recursive samples", rec_func.recursive_samples),
format!("{:.1}% of CPU time", rec_pct),
],
});
}
}
#[expect(clippy::cast_precision_loss)]
fn analyze_phases(
analysis: &CpuAnalysis,
total_time: u64,
recommendations: &mut Vec<Recommendation>,
insights: &mut Vec<String>,
) {
let Some(phases) = &analysis.phase_analysis else {
return;
};
let startup_time = phases.startup.end_us - phases.startup.start_us;
let startup_pct = if total_time > 0 {
(startup_time as f64 / total_time as f64) * 100.0
} else {
0.0
};
if startup_pct >= 30.0 && startup_time > 500_000 {
insights.push(format!(
"Startup phase takes {:.0}ms ({:.0}% of profile) — consider lazy loading",
startup_time as f64 / 1000.0,
startup_pct
));
if let Some(top_startup_func) = phases.startup.top_functions.first() {
if top_startup_func.percent >= 20.0 {
recommendations.push(Recommendation {
priority: Priority::High,
effort: Effort::Moderate,
issue_type: IssueType::Startup,
title: format!("Defer `{}` initialization", top_startup_func.name),
target: top_startup_func.name.clone(),
location: top_startup_func.location.clone(),
current_time_us: top_startup_func.self_time,
estimated_savings_us: top_startup_func.self_time * 8 / 10, root_cause: format!(
"This function takes {:.0}% of startup time",
top_startup_func.percent
),
actions: vec![
"Lazy-load this module on first use".to_string(),
"Move initialization to background/idle time".to_string(),
"Consider code-splitting this functionality".to_string(),
"Defer non-critical initialization".to_string(),
],
code_patterns: vec![
"Top-level await or sync initialization".to_string(),
"Large imports at module load time".to_string(),
],
evidence: vec![
format!("{:.1}% of startup time", top_startup_func.percent),
format!("Startup phase: {:.0}ms", startup_time as f64 / 1000.0),
],
});
}
}
}
}
#[expect(clippy::cast_precision_loss)]
fn detect_patterns(
profile: &ProfileIR,
analysis: &CpuAnalysis,
total_time: u64,
recommendations: &mut Vec<Recommendation>,
) {
let json_funcs: Vec<_> = analysis
.functions
.iter()
.filter(|f| {
let name_lower = f.name.to_lowercase();
name_lower.contains("json")
|| name_lower.contains("parse")
|| name_lower.contains("stringify")
|| name_lower.contains("serialize")
})
.collect();
let json_time: u64 = json_funcs.iter().map(|f| f.self_time).sum();
let json_pct = if total_time > 0 {
(json_time as f64 / total_time as f64) * 100.0
} else {
0.0
};
if json_pct >= 5.0 {
recommendations.push(Recommendation {
priority: if json_pct >= 15.0 {
Priority::High
} else {
Priority::Medium
},
effort: Effort::Moderate,
issue_type: IssueType::Serialization,
title: "Optimize JSON serialization".to_string(),
target: "JSON operations".to_string(),
location: json_funcs
.first()
.map_or("(multiple)".to_string(), |f| f.location.clone()),
current_time_us: json_time,
estimated_savings_us: json_time / 2,
root_cause: format!(
"JSON parsing/serialization consumes {:.1}% of CPU time",
json_pct
),
actions: vec![
"Use streaming JSON parsing for large payloads".to_string(),
"Consider binary formats (MessagePack, Protocol Buffers)".to_string(),
"Cache parsed results when possible".to_string(),
"Avoid stringify/parse roundtrips for cloning (use structuredClone)"
.to_string(),
],
code_patterns: vec![
"JSON.parse(JSON.stringify(obj)) for cloning".to_string(),
"Repeated parsing of same data".to_string(),
"Large object serialization".to_string(),
],
evidence: json_funcs
.iter()
.take(3)
.map(|f| format!("`{}` - {:.1}%", f.name, f.self_percent(total_time)))
.collect(),
});
}
let regex_funcs: Vec<_> = analysis
.functions
.iter()
.filter(|f| {
let name_lower = f.name.to_lowercase();
name_lower.contains("regexp")
|| name_lower.contains("regex")
|| f.name.contains("match")
})
.collect();
let regex_time: u64 = regex_funcs.iter().map(|f| f.self_time).sum();
let regex_pct = if total_time > 0 {
(regex_time as f64 / total_time as f64) * 100.0
} else {
0.0
};
if regex_pct >= 3.0 {
recommendations.push(Recommendation {
priority: Priority::Medium,
effort: Effort::QuickWin,
issue_type: IssueType::Algorithm,
title: "Optimize regular expressions".to_string(),
target: "RegExp operations".to_string(),
location: regex_funcs
.first()
.map_or("(multiple)".to_string(), |f| f.location.clone()),
current_time_us: regex_time,
estimated_savings_us: regex_time * 2 / 3,
root_cause: format!("Regular expressions consume {:.1}% of CPU time", regex_pct),
actions: vec![
"Compile regex once outside loops: `const re = /pattern/`".to_string(),
"Use simpler string methods when possible (includes, startsWith)".to_string(),
"Avoid capturing groups if not needed: `(?:...)` instead of `(...)`"
.to_string(),
"Consider using non-backtracking patterns".to_string(),
],
code_patterns: vec![
"/pattern/.test(str) inside loops".to_string(),
"new RegExp() called repeatedly".to_string(),
"Complex patterns with backtracking".to_string(),
],
evidence: regex_funcs
.iter()
.take(3)
.map(|f| format!("`{}` - {:.1}%", f.name, f.self_percent(total_time)))
.collect(),
});
}
for func in analysis.functions.iter().take(5) {
if func.self_samples > 100 && func.avg_time_per_sample() < 100.0 {
let name_lower = func.name.to_lowercase();
if name_lower.contains("get")
|| name_lower.contains("fetch")
|| name_lower.contains("load")
|| name_lower.contains("query")
|| name_lower.contains("find")
{
let _ = profile; recommendations.push(Recommendation {
priority: Priority::Medium,
effort: Effort::Moderate,
issue_type: IssueType::Caching,
title: format!("Batch `{}` calls", func.name),
target: func.name.clone(),
location: func.location.clone(),
current_time_us: func.self_time,
estimated_savings_us: func.self_time / 2,
root_cause: format!(
"Called {} times — potential N+1 pattern",
func.self_samples
),
actions: vec![
"Batch multiple calls into a single operation".to_string(),
"Use DataLoader pattern for automatic batching".to_string(),
"Prefetch data instead of loading on demand".to_string(),
"Add caching layer to avoid repeated fetches".to_string(),
],
code_patterns: vec![
"Loop calling getData(id) — batch to getData(ids)".to_string(),
"Multiple awaits in sequence that could be parallel".to_string(),
],
evidence: vec![
format!("{} calls in profile", func.self_samples),
format!("{:.0}μs average per call", func.avg_time_per_sample()),
],
});
break; }
}
}
}
fn generate_investigations(analysis: &CpuAnalysis, investigations: &mut Vec<String>) {
if Self::should_include_category(FrameCategory::Native, analysis)
&& analysis.native_time > analysis.total_time / 5
{
investigations.push(
"High native code time (>20%) — check native addons or C++ bindings".to_string(),
);
}
if Self::should_include_category(FrameCategory::V8Internal, analysis)
&& analysis.category_breakdown.v8_internal > analysis.total_time / 10
{
investigations.push(
"Significant V8 internal time (>10%) — may indicate JIT deoptimization".to_string(),
);
}
if analysis.hot_paths.len() == 1 {
investigations
.push("Single dominant code path — check if other paths are expected".to_string());
}
if analysis.functions.is_empty() {
investigations.push(
"No significant functions found — profile may be too short or app is I/O bound"
.to_string(),
);
}
}
}