helix_core/compiler/workflow/
bench.rs1use 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}