metrale_bench/benchmarks/agentic/
warm.rs1use std::ffi::OsString;
16use std::path::{Path, PathBuf};
17use std::process::Stdio;
18use std::time::Duration;
19
20use anyhow::{Context, Result, bail};
21
22use crate::plugin::PluginHandle;
23
24const PREWARM_TIMEOUT: Duration = Duration::from_secs(1800);
27
28pub const TEMPLATE_MANIFEST: &str = r#"[package]
31name = "metrale-warm-template"
32version = "0.1.0"
33edition = "2021"
34
35[dependencies]
36axum = { version = "0.8", features = ["json", "macros", "ws", "multipart"] }
37tokio = { version = "1", features = ["full"] }
38serde = { version = "1", features = ["derive"] }
39serde_json = "1"
40tower = { version = "0.5", features = ["full"] }
41tower-http = { version = "0.6", features = ["full"] }
42hyper = { version = "1", features = ["full"] }
43reqwest = { version = "0.12", features = ["json"] }
44anyhow = "1"
45thiserror = "2"
46tracing = "0.1"
47tracing-subscriber = { version = "0.3", features = ["env-filter"] }
48
49[dev-dependencies]
50reqwest = { version = "0.12", features = ["json"] }
51"#;
52
53pub const TEMPLATE_MAIN: &str = r#"use axum::{routing::get, Router};
57
58async fn ping() -> &'static str {
59 "pong"
60}
61
62#[tokio::main]
63async fn main() {
64 let _ = serde_json::json!({"ok": true});
65 let _v: tower::ServiceBuilder<tower::layer::util::Identity> = tower::ServiceBuilder::new();
66 let app = Router::new().route("/ping", get(ping));
67 let port: u16 = std::env::var("METRALE_HARNESS_PORT")
68 .unwrap_or_else(|_| "3001".to_string())
69 .parse()
70 .unwrap();
71 let listener = tokio::net::TcpListener::bind(("0.0.0.0", port)).await.unwrap();
72 axum::serve(listener, app).await.unwrap();
73}
74"#;
75
76fn dir_from(explicit: Option<OsString>, home: Option<OsString>, leaf: &str) -> Result<PathBuf> {
80 if let Some(p) = explicit.filter(|p| !p.is_empty()) {
81 return Ok(PathBuf::from(p));
82 }
83 let home = home
84 .filter(|h| !h.is_empty())
85 .context("HOME is not set, so the shared warm cargo dir has no home")?;
86 Ok(PathBuf::from(home).join(".cargo").join(leaf))
87}
88
89pub fn warm_target_dir() -> Result<PathBuf> {
94 dir_from(
95 std::env::var_os("METRALE_WARM_TARGET_DIR"),
96 std::env::var_os("HOME"),
97 "metrale-warm-target",
98 )
99}
100
101pub fn template_dir() -> Result<PathBuf> {
104 dir_from(
105 std::env::var_os("METRALE_WARM_TEMPLATE_DIR"),
106 std::env::var_os("HOME"),
107 "metrale-warm-template",
108 )
109}
110
111pub fn write_template(dir: &Path) -> Result<()> {
114 let src = dir.join("src");
115 std::fs::create_dir_all(&src)
116 .with_context(|| format!("creating warm template dir {}", src.display()))?;
117 write_if_changed(&dir.join("Cargo.toml"), TEMPLATE_MANIFEST)?;
118 write_if_changed(&src.join("main.rs"), TEMPLATE_MAIN)?;
119 Ok(())
120}
121
122fn write_if_changed(path: &Path, content: &str) -> Result<()> {
125 if std::fs::read_to_string(path).is_ok_and(|old| old == content) {
126 return Ok(());
127 }
128 std::fs::write(path, content).with_context(|| format!("writing {}", path.display()))
129}
130
131async fn cargo(args: &[&str], warm: &Path, manifest: &Path) -> Result<()> {
134 let mut cmd = tokio::process::Command::new("cargo");
135 cmd.args(args)
136 .arg("--manifest-path")
137 .arg(manifest)
138 .env("CARGO_TARGET_DIR", warm)
139 .stdin(Stdio::null())
140 .stdout(Stdio::null())
141 .stderr(Stdio::piped())
142 .kill_on_drop(true);
143 let out = tokio::time::timeout(PREWARM_TIMEOUT, cmd.output())
144 .await
145 .with_context(|| {
146 format!(
147 "`cargo {}` exceeded {}s while warming {}",
148 args.join(" "),
149 PREWARM_TIMEOUT.as_secs(),
150 warm.display()
151 )
152 })?
153 .with_context(|| format!("`cargo {}` could not start", args.join(" ")))?;
154 if !out.status.success() {
155 let err = String::from_utf8_lossy(&out.stderr);
156 let tail = err.lines().rev().take(8).collect::<Vec<_>>().join(" ");
157 bail!(
158 "`cargo {}` failed while warming {}: {}",
159 args.join(" "),
160 warm.display(),
161 super::super::one_line(tail)
162 );
163 }
164 Ok(())
165}
166
167pub async fn prepare(handle: &PluginHandle) -> Result<PathBuf> {
171 let warm = warm_target_dir()?;
172 let template = template_dir()?;
173 std::fs::create_dir_all(&warm)
174 .with_context(|| format!("creating warm target dir {}", warm.display()))?;
175 write_template(&template)?;
176 let manifest = template.join("Cargo.toml");
177
178 handle.check_cancelled()?;
182 handle.status(format!("warming cargo debug profile in {}", warm.display()));
183 cargo(&["test", "--no-run"], &warm, &manifest).await?;
184 handle.check_cancelled()?;
185 handle.status(format!(
186 "warming cargo release profile in {}",
187 warm.display()
188 ));
189 cargo(&["build", "--release"], &warm, &manifest).await?;
190 Ok(warm)
191}
192
193#[cfg(test)]
194#[path = "warm_tests.rs"]
195mod tests;