Skip to main content

reth_bench_compare/
compilation.rs

1//! Compilation operations for reth and reth-bench.
2
3use crate::git::GitManager;
4use eyre::{eyre, Result, WrapErr};
5use std::{fs, path::PathBuf, process::Command};
6use tracing::{debug, error, info, warn};
7
8/// Manages compilation operations for reth components
9#[derive(Debug)]
10pub(crate) struct CompilationManager {
11    repo_root: String,
12    output_dir: PathBuf,
13    git_manager: GitManager,
14}
15
16impl CompilationManager {
17    /// Create a new `CompilationManager`
18    pub(crate) const fn new(
19        repo_root: String,
20        output_dir: PathBuf,
21        git_manager: GitManager,
22    ) -> Result<Self> {
23        Ok(Self { repo_root, output_dir, git_manager })
24    }
25
26    /// Get the path to the cached binary using explicit commit hash
27    pub(crate) fn get_cached_binary_path_for_commit(&self, commit: &str) -> PathBuf {
28        let identifier = &commit[..8]; // Use first 8 chars of commit
29        self.output_dir.join("bin").join(format!("reth_{identifier}"))
30    }
31
32    /// Compile reth using cargo build and cache the binary
33    pub(crate) fn compile_reth(&self, commit: &str, features: &str, rustflags: &str) -> Result<()> {
34        // Validate that current git commit matches the expected commit
35        let current_commit = self.git_manager.get_current_commit()?;
36        if current_commit != commit {
37            return Err(eyre!(
38                "Git commit mismatch! Expected: {}, but currently at: {}",
39                &commit[..8],
40                &current_commit[..8]
41            ));
42        }
43
44        let cached_path = self.get_cached_binary_path_for_commit(commit);
45
46        // Check if cached binary already exists (since path contains commit hash, it's valid)
47        if cached_path.exists() {
48            info!("Using cached binary (commit: {})", &commit[..8]);
49            return Ok(());
50        }
51
52        info!("No cached binary found, compiling (commit: {})...", &commit[..8]);
53
54        let binary_name = "reth";
55
56        info!(
57            "Compiling {} with profiling configuration (commit: {})...",
58            binary_name,
59            &commit[..8]
60        );
61
62        let mut cmd = Command::new("cargo");
63        cmd.arg("build").arg("--profile").arg("profiling");
64
65        cmd.arg("--features").arg(features);
66        info!("Using features: {features}");
67
68        cmd.current_dir(&self.repo_root);
69
70        // Set RUSTFLAGS
71        cmd.env("RUSTFLAGS", rustflags);
72        info!("Using RUSTFLAGS: {rustflags}");
73
74        info!("Compiling {binary_name} with {cmd:?}");
75
76        let output = cmd.output().wrap_err("Failed to execute cargo build command")?;
77
78        // Print stdout and stderr with prefixes at debug level
79        let stdout = String::from_utf8_lossy(&output.stdout);
80        let stderr = String::from_utf8_lossy(&output.stderr);
81
82        for line in stdout.lines() {
83            if !line.trim().is_empty() {
84                debug!("[CARGO] {}", line);
85            }
86        }
87
88        for line in stderr.lines() {
89            if !line.trim().is_empty() {
90                debug!("[CARGO] {}", line);
91            }
92        }
93
94        if !output.status.success() {
95            // Print all output when compilation fails
96            error!("Cargo build failed with exit code: {:?}", output.status.code());
97
98            if !stdout.trim().is_empty() {
99                error!("Cargo stdout:");
100                for line in stdout.lines() {
101                    error!("  {}", line);
102                }
103            }
104
105            if !stderr.trim().is_empty() {
106                error!("Cargo stderr:");
107                for line in stderr.lines() {
108                    error!("  {}", line);
109                }
110            }
111
112            return Err(eyre!("Compilation failed with exit code: {:?}", output.status.code()));
113        }
114
115        info!("{} compilation completed", binary_name);
116
117        // Copy the compiled binary to cache
118        let source_path =
119            PathBuf::from(&self.repo_root).join(format!("target/profiling/{}", binary_name));
120        if !source_path.exists() {
121            return Err(eyre!("Compiled binary not found at {:?}", source_path));
122        }
123
124        // Create bin directory if it doesn't exist
125        let bin_dir = self.output_dir.join("bin");
126        fs::create_dir_all(&bin_dir).wrap_err("Failed to create bin directory")?;
127
128        // Copy binary to cache
129        fs::copy(&source_path, &cached_path).wrap_err("Failed to copy binary to cache")?;
130
131        // Make the cached binary executable
132        #[cfg(unix)]
133        {
134            use std::os::unix::fs::PermissionsExt;
135            let mut perms = fs::metadata(&cached_path)?.permissions();
136            perms.set_mode(0o755);
137            fs::set_permissions(&cached_path, perms)?;
138        }
139
140        info!("Cached compiled binary at: {:?}", cached_path);
141        Ok(())
142    }
143
144    /// Check if reth-bench is available in PATH
145    pub(crate) fn is_reth_bench_available(&self) -> bool {
146        match Command::new("which").arg("reth-bench").output() {
147            Ok(output) => {
148                if output.status.success() {
149                    let path = String::from_utf8_lossy(&output.stdout);
150                    info!("Found reth-bench: {}", path.trim());
151                    true
152                } else {
153                    false
154                }
155            }
156            Err(_) => false,
157        }
158    }
159
160    /// Check if samply is available in PATH
161    pub(crate) fn is_samply_available(&self) -> bool {
162        match Command::new("which").arg("samply").output() {
163            Ok(output) => {
164                if output.status.success() {
165                    let path = String::from_utf8_lossy(&output.stdout);
166                    info!("Found samply: {}", path.trim());
167                    true
168                } else {
169                    false
170                }
171            }
172            Err(_) => false,
173        }
174    }
175
176    /// Install samply using cargo
177    pub(crate) fn install_samply(&self) -> Result<()> {
178        info!("Installing samply via cargo...");
179
180        let mut cmd = Command::new("cargo");
181        cmd.args(["install", "--locked", "samply"]);
182
183        info!("Installing samply with {cmd:?}");
184
185        let output = cmd.output().wrap_err("Failed to execute cargo install samply command")?;
186
187        // Print stdout and stderr with prefixes at debug level
188        let stdout = String::from_utf8_lossy(&output.stdout);
189        let stderr = String::from_utf8_lossy(&output.stderr);
190
191        for line in stdout.lines() {
192            if !line.trim().is_empty() {
193                debug!("[CARGO-SAMPLY] {}", line);
194            }
195        }
196
197        for line in stderr.lines() {
198            if !line.trim().is_empty() {
199                debug!("[CARGO-SAMPLY] {}", line);
200            }
201        }
202
203        if !output.status.success() {
204            // Print all output when installation fails
205            error!("Cargo install samply failed with exit code: {:?}", output.status.code());
206
207            if !stdout.trim().is_empty() {
208                error!("Cargo stdout:");
209                for line in stdout.lines() {
210                    error!("  {}", line);
211                }
212            }
213
214            if !stderr.trim().is_empty() {
215                error!("Cargo stderr:");
216                for line in stderr.lines() {
217                    error!("  {}", line);
218                }
219            }
220
221            return Err(eyre!(
222                "samply installation failed with exit code: {:?}",
223                output.status.code()
224            ));
225        }
226
227        info!("Samply installation completed");
228        Ok(())
229    }
230
231    /// Ensure samply is available, installing if necessary
232    pub(crate) fn ensure_samply_available(&self) -> Result<()> {
233        if self.is_samply_available() {
234            Ok(())
235        } else {
236            warn!("samply not found in PATH, installing...");
237            self.install_samply()
238        }
239    }
240
241    /// Ensure reth-bench is available, compiling if necessary
242    pub(crate) fn ensure_reth_bench_available(&self) -> Result<()> {
243        if self.is_reth_bench_available() {
244            Ok(())
245        } else {
246            warn!("reth-bench not found in PATH, compiling and installing...");
247            self.compile_reth_bench()
248        }
249    }
250
251    /// Compile and install reth-bench using `make install-reth-bench`
252    pub(crate) fn compile_reth_bench(&self) -> Result<()> {
253        info!("Compiling and installing reth-bench...");
254
255        let mut cmd = Command::new("make");
256        cmd.arg("install-reth-bench").current_dir(&self.repo_root);
257
258        info!("Compiling reth-bench with {cmd:?}");
259
260        let output = cmd.output().wrap_err("Failed to execute make install-reth-bench command")?;
261
262        // Print stdout and stderr with prefixes at debug level
263        let stdout = String::from_utf8_lossy(&output.stdout);
264        let stderr = String::from_utf8_lossy(&output.stderr);
265
266        for line in stdout.lines() {
267            if !line.trim().is_empty() {
268                debug!("[MAKE-BENCH] {}", line);
269            }
270        }
271
272        for line in stderr.lines() {
273            if !line.trim().is_empty() {
274                debug!("[MAKE-BENCH] {}", line);
275            }
276        }
277
278        if !output.status.success() {
279            // Print all output when compilation fails
280            error!("Make install-reth-bench failed with exit code: {:?}", output.status.code());
281
282            if !stdout.trim().is_empty() {
283                error!("Make stdout:");
284                for line in stdout.lines() {
285                    error!("  {}", line);
286                }
287            }
288
289            if !stderr.trim().is_empty() {
290                error!("Make stderr:");
291                for line in stderr.lines() {
292                    error!("  {}", line);
293                }
294            }
295
296            return Err(eyre!(
297                "reth-bench compilation failed with exit code: {:?}",
298                output.status.code()
299            ));
300        }
301
302        info!("Reth-bench compilation completed");
303        Ok(())
304    }
305}