reth_bench_compare/
compilation.rs

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