use std::fmt::Write as _;
use std::io;
use std::path::Path;
mod frames;
mod writer;
use frames::{build, FrameTable, ProgramPoint};
use writer::{write_json_string, write_key};
pub(crate) fn dhat_json_string() -> String {
let (ftbl, pps) = build();
render(&ftbl, &pps)
}
pub(crate) fn write_dhat_json(path: &Path) -> io::Result<()> {
let bytes = dhat_json_string();
std::fs::write(path, bytes)
}
fn render(ftbl: &FrameTable, pps: &[ProgramPoint]) -> String {
let mut out = String::with_capacity(256 + pps.len() * 64 + ftbl.entries.len() * 32);
out.push('{');
write_key(&mut out, "dhatFileVersion");
out.push_str("2,");
write_key(&mut out, "mode");
write_json_string(&mut out, "rust-heap");
out.push(',');
write_key(&mut out, "verb");
write_json_string(&mut out, "Allocated");
out.push(',');
write_key(&mut out, "bklt");
out.push_str("false,");
write_key(&mut out, "bkacc");
out.push_str("false,");
write_key(&mut out, "bu");
write_json_string(&mut out, "byte");
out.push(',');
write_key(&mut out, "bsu");
write_json_string(&mut out, "bytes");
out.push(',');
write_key(&mut out, "bksu");
write_json_string(&mut out, "blocks");
out.push(',');
write_key(&mut out, "tu");
write_json_string(&mut out, "instrs");
out.push(',');
write_key(&mut out, "Mtu");
write_json_string(&mut out, "Minstr");
out.push(',');
write_key(&mut out, "tuth");
out.push_str("0,");
write_key(&mut out, "cmd");
write_json_string(&mut out, ¤t_cmd());
out.push(',');
write_key(&mut out, "pid");
let _ = write!(out, "{}", std::process::id());
out.push(',');
write_key(&mut out, "tg");
out.push_str("0,");
write_key(&mut out, "te");
out.push_str("0,");
write_key(&mut out, "pps");
out.push('[');
for (i, pp) in pps.iter().enumerate() {
if i > 0 {
out.push(',');
}
write_pp(&mut out, pp);
}
out.push(']');
out.push(',');
write_key(&mut out, "ftbl");
out.push('[');
for (i, frame) in ftbl.entries.iter().enumerate() {
if i > 0 {
out.push(',');
}
write_json_string(&mut out, frame);
}
out.push(']');
out.push('}');
out
}
fn write_pp(out: &mut String, pp: &ProgramPoint) {
out.push('{');
write_key(out, "tb");
let _ = write!(out, "{},", pp.total_bytes);
write_key(out, "tbk");
let _ = write!(out, "{},", pp.count);
write_key(out, "eb");
out.push_str("0,");
write_key(out, "ebk");
out.push_str("0,");
write_key(out, "fs");
out.push('[');
for (i, idx) in pp.frame_indices.iter().enumerate() {
if i > 0 {
out.push(',');
}
let _ = write!(out, "{idx}");
}
out.push(']');
out.push('}');
}
fn current_cmd() -> String {
let mut parts = std::env::args_os();
let Some(first) = parts.next() else {
return "<unknown>".to_string();
};
let mut out = first.to_string_lossy().into_owned();
for arg in parts {
out.push(' ');
out.push_str(&arg.to_string_lossy());
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_report_is_wellformed() {
let s = dhat_json_string();
assert!(s.starts_with('{'));
assert!(s.ends_with('}'));
for key in [
"\"dhatFileVersion\":2",
"\"mode\":\"rust-heap\"",
"\"verb\":\"Allocated\"",
"\"bklt\":false",
"\"bkacc\":false",
"\"pps\":[",
"\"ftbl\":[",
"\"[root]\"",
] {
assert!(s.contains(key), "missing key/value: {key}\nfull: {s}");
}
}
#[test]
fn current_cmd_returns_nonempty() {
let s = current_cmd();
assert!(!s.is_empty());
}
}