use super::{Backend, Dependency, DependencyCheck, SpawnConfig, shell_escape};
use crate::daemon::session_tmp;
pub struct MassifBackend;
impl Backend for MassifBackend {
fn name(&self) -> &'static str {
"massif"
}
fn description(&self) -> &'static str {
"heap profiler (valgrind)"
}
fn types(&self) -> &'static [&'static str] {
&["massif"]
}
fn spawn_config(&self, target: &str, args: &[String]) -> anyhow::Result<SpawnConfig> {
let out_file = session_tmp("massif.out");
let out_str = out_file.display().to_string();
let mut valgrind_cmd = format!(
"valgrind --tool=massif --massif-out-file={} {}",
shell_escape(&out_str),
shell_escape(target)
);
for a in args {
valgrind_cmd.push(' ');
valgrind_cmd.push_str(&shell_escape(a));
}
Ok(SpawnConfig {
bin: "bash".into(),
args: vec!["--norc".into(), "--noprofile".into()],
env: vec![("PS1".into(), "$ ".into()), ("MASSIF_OUT".into(), out_str)],
init_commands: vec![valgrind_cmd, "echo '--- massif data ready ---'".into()],
})
}
fn prompt_pattern(&self) -> &str {
r"\$ $"
}
fn dependencies(&self) -> Vec<Dependency> {
vec![
Dependency {
name: "valgrind",
check: DependencyCheck::Binary {
name: "valgrind",
alternatives: &["valgrind"],
version_cmd: None,
},
install: "sudo apt install valgrind # or: brew install valgrind",
},
Dependency {
name: "ms_print",
check: DependencyCheck::Binary {
name: "ms_print",
alternatives: &["ms_print"],
version_cmd: None,
},
install: "sudo apt install valgrind # included with valgrind",
},
]
}
fn run_command(&self) -> &'static str {
"ms_print \"$MASSIF_OUT\""
}
fn is_profile_backend(&self) -> bool {
true
}
fn quit_command(&self) -> &'static str {
"exit"
}
fn parse_help(&self, _raw: &str) -> String {
"massif: ms_print [--threshold=N] \"$MASSIF_OUT\"".to_string()
}
fn clean(&self, _cmd: &str, output: &str) -> String {
let mut lines = Vec::new();
for line in output.lines() {
let trimmed = line.trim();
if trimmed.starts_with("==")
&& (trimmed.contains("Massif") || trimmed.contains("Copyright"))
{
continue;
}
lines.push(line);
}
lines.join("\n")
}
fn adapters(&self) -> Vec<(&'static str, &'static str)> {
vec![(
"massif.md",
include_str!("../../skills/references/adapters/massif.md"),
)]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn clean_filters_massif_header() {
let input = " == Massif, a heap profiler\n == Copyright (C) 2022\nactual data\nmore data";
let r = MassifBackend.clean("ms_print", input);
assert!(!r.contains("Massif"));
assert!(!r.contains("Copyright"));
assert!(r.contains("actual data"));
}
#[test]
fn clean_keeps_non_header_lines() {
let input = " == Some other valgrind line\ndata";
let r = MassifBackend.clean("ms_print", input);
assert!(r.contains("Some other valgrind line"));
}
#[test]
fn spawn_config_sets_massif_out_env() {
let cfg = MassifBackend.spawn_config("./app", &[]).unwrap();
assert!(cfg.env.iter().any(|(k, _)| k == "MASSIF_OUT"));
}
#[test]
fn spawn_config_valgrind_cmd_with_args() {
let cfg = MassifBackend
.spawn_config("./app", &["--size".into(), "100".into()])
.unwrap();
let cmd = &cfg.init_commands[0];
assert!(cmd.contains("--tool=massif"));
assert!(cmd.contains("./app"));
assert!(cmd.contains("--size"));
assert!(cmd.contains("100"));
}
#[test]
fn spawn_config_escapes_spaces() {
let cfg = MassifBackend.spawn_config("./my app", &[]).unwrap();
let cmd = &cfg.init_commands[0];
assert!(cmd.contains("'./my app'"), "target not escaped: {cmd}");
}
#[test]
fn session_output_path_is_quoted_in_commands() {
assert_eq!(MassifBackend.run_command(), "ms_print \"$MASSIF_OUT\"");
assert!(MassifBackend.parse_help("").contains("\"$MASSIF_OUT\""));
}
}