helix_core/compiler/workflow/
bench.rs

1use std::path::PathBuf;
2use std::fs;
3use anyhow::{Result, Context};
4use std::time::{Duration, Instant};
5pub fn run_benchmarks(
6    pattern: Option<String>,
7    iterations: Option<usize>,
8    verbose: bool,
9) -> Result<()> {
10    let project_dir = find_project_root()?;
11    if verbose {
12        println!("⚔ Running hlx benchmarks:");
13        println!("  Project: {}", project_dir.display());
14        println!("  Pattern: {}", pattern.as_deref().unwrap_or("all"));
15        println!("  Iterations: {}", iterations.unwrap_or(100));
16    }
17    let iterations = iterations.unwrap_or(100);
18    let mut benchmark_count = 0;
19    let mut total_time = Duration::new(0, 0);
20    let benchmark_files = find_benchmark_files(&project_dir, &pattern)?;
21    if benchmark_files.is_empty() {
22        println!("ā„¹ļø  No benchmark files found.");
23        println!("  Create benchmark files in benches/ directory with .hlx extension");
24        println!("  Or add benchmark functions to your source files");
25        return Ok(());
26    }
27    println!("šŸ“‹ Found {} benchmark files", benchmark_files.len());
28    for benchmark_file in benchmark_files {
29        if verbose {
30            println!("\nšŸ” Running benchmarks in: {}", benchmark_file.display());
31        }
32        match run_benchmark_file(&benchmark_file, iterations, verbose) {
33            Ok((benchmarks, total_file_time)) => {
34                benchmark_count += benchmarks;
35                total_time += total_file_time;
36            }
37            Err(e) => {
38                eprintln!(
39                    "āŒ Failed to run benchmarks in {}: {}", benchmark_file.display(), e
40                );
41            }
42        }
43    }
44    println!("\nšŸ“Š Benchmark Results:");
45    println!("  Total benchmarks: {}", benchmark_count);
46    println!("  Total time: {:?}", total_time);
47    if benchmark_count > 0 {
48        let avg_time = total_time / benchmark_count as u32;
49        println!("  Average time per benchmark: {:?}", avg_time);
50    }
51    Ok(())
52}
53fn find_benchmark_files(
54    project_dir: &PathBuf,
55    pattern: &Option<String>,
56) -> Result<Vec<PathBuf>> {
57    let mut benchmark_files = Vec::new();
58    let benches_dir = project_dir.join("benches");
59    if benches_dir.exists() {
60        find_helix_files(&benches_dir, &mut benchmark_files, pattern)?;
61    }
62    let src_dir = project_dir.join("src");
63    if src_dir.exists() {
64        find_helix_files(&src_dir, &mut benchmark_files, pattern)?;
65    }
66    Ok(benchmark_files)
67}
68fn find_helix_files(
69    dir: &PathBuf,
70    files: &mut Vec<PathBuf>,
71    pattern: &Option<String>,
72) -> Result<()> {
73    let entries = fs::read_dir(dir).context("Failed to read directory")?;
74    for entry in entries {
75        let entry = entry.context("Failed to read directory entry")?;
76        let path = entry.path();
77        if path.is_file() {
78            if let Some(extension) = path.extension() {
79                if extension == "hlx" {
80                    if let Some(pattern) = pattern {
81                        if let Some(file_name) = path
82                            .file_name()
83                            .and_then(|n| n.to_str())
84                        {
85                            if !file_name.contains(pattern) {
86                                continue;
87                            }
88                        }
89                    }
90                    files.push(path);
91                }
92            }
93        } else if path.is_dir() {
94            find_helix_files(&path, files, pattern)?;
95        }
96    }
97    Ok(())
98}
99fn run_benchmark_file(
100    benchmark_file: &PathBuf,
101    iterations: usize,
102    verbose: bool,
103) -> Result<(usize, Duration)> {
104    let content = fs::read_to_string(benchmark_file)
105        .context("Failed to read benchmark file")?;
106    let benchmarks = extract_benchmarks(&content)?;
107    if benchmarks.is_empty() {
108        if verbose {
109            println!("  No benchmark functions found in {}", benchmark_file.display());
110        }
111        return Ok((0, Duration::new(0, 0)));
112    }
113    let benchmark_count = benchmarks.len();
114    let mut total_time = Duration::new(0, 0);
115    for benchmark in benchmarks {
116        if verbose {
117            println!("  Running benchmark: {}", benchmark.name);
118        }
119        match run_single_benchmark(&benchmark, iterations, verbose) {
120            Ok(duration) => {
121                total_time += duration;
122                if verbose {
123                    println!("    ⚔ {} iterations in {:?}", iterations, duration);
124                    println!(
125                        "    šŸ“Š Average: {:?} per iteration", duration / iterations as
126                        u32
127                    );
128                }
129            }
130            Err(e) => {
131                if verbose {
132                    println!("    āŒ ERROR: {}", e);
133                }
134            }
135        }
136    }
137    Ok((benchmark_count, total_time))
138}
139#[derive(Debug)]
140struct BenchmarkFunction {
141    name: String,
142    #[allow(dead_code)]
143    content: String,
144    #[allow(dead_code)]
145    line: usize,
146}
147fn extract_benchmarks(content: &str) -> Result<Vec<BenchmarkFunction>> {
148    let mut benchmarks = Vec::new();
149    let lines: Vec<&str> = content.lines().collect();
150    for (i, line) in lines.iter().enumerate() {
151        let trimmed = line.trim();
152        if trimmed.starts_with("bench ") || trimmed.starts_with("benchmark ")
153            || trimmed.starts_with("bench_")
154        {
155            if let Some(name_start) = trimmed.find(' ') {
156                let name = trimmed[name_start + 1..].trim();
157                if let Some(open_brace) = name.find('(') {
158                    let benchmark_name = name[..open_brace].trim().to_string();
159                    let mut benchmark_content = String::new();
160                    let mut brace_count = 0;
161                    let mut in_function = false;
162                    for j in i..lines.len() {
163                        let current_line = lines[j];
164                        benchmark_content.push_str(current_line);
165                        benchmark_content.push('\n');
166                        for ch in current_line.chars() {
167                            if ch == '{' {
168                                brace_count += 1;
169                                in_function = true;
170                            } else if ch == '}' {
171                                brace_count -= 1;
172                                if in_function && brace_count == 0 {
173                                    benchmarks
174                                        .push(BenchmarkFunction {
175                                            name: benchmark_name.clone(),
176                                            content: benchmark_content.clone(),
177                                            line: i + 1,
178                                        });
179                                    break;
180                                }
181                            }
182                        }
183                        if in_function && brace_count == 0 {
184                            break;
185                        }
186                    }
187                }
188            }
189        }
190    }
191    Ok(benchmarks)
192}
193fn run_single_benchmark(
194    _benchmark: &BenchmarkFunction,
195    iterations: usize,
196    _verbose: bool,
197) -> Result<Duration> {
198    let start = Instant::now();
199    for _ in 0..iterations {
200        std::thread::sleep(Duration::from_micros(1));
201    }
202    let duration = start.elapsed();
203    Ok(duration)
204}
205fn find_project_root() -> Result<PathBuf> {
206    let mut current_dir = std::env::current_dir()
207        .context("Failed to get current directory")?;
208    loop {
209        let manifest_path = current_dir.join("project.hlx");
210        if manifest_path.exists() {
211            return Ok(current_dir);
212        }
213        if let Some(parent) = current_dir.parent() {
214            current_dir = parent.to_path_buf();
215        } else {
216            break;
217        }
218    }
219    Err(anyhow::anyhow!("No HELIX project found. Run 'helix init' first."))
220}