use std;
use std::collections::HashMap;
use std::io::Write;
use std::fs::File;
use std::path::Path;
use std::process::{Command, Stdio};
use failure::{Error, ResultExt};
use tempdir;
use stack_trace::StackTrace;
const FLAMEGRAPH_SCRIPT: &[u8] = include_bytes!("../vendor/flamegraph/flamegraph.pl");
pub struct Flamegraph {
pub counts: HashMap<Vec<u8>, usize>,
pub show_linenumbers: bool,
}
impl Flamegraph {
pub fn new(show_linenumbers: bool) -> Flamegraph {
Flamegraph { counts: HashMap::new(), show_linenumbers }
}
pub fn increment(&mut self, traces: &[StackTrace]) -> std::io::Result<()> {
for trace in traces {
if !(trace.active) {
continue;
}
let mut buf = vec![];
for frame in trace.frames.iter().rev() {
let filename = match &frame.short_filename { Some(f) => &f, None => &frame.filename };
if self.show_linenumbers {
write!(&mut buf, "{} ({}:{});", frame.name, filename, frame.line)?;
} else {
write!(&mut buf, "{} ({});", frame.name, filename)?;
}
}
*self.counts.entry(buf).or_insert(0) += 1;
}
Ok(())
}
pub fn write(&self, w: File) -> Result<(), Error> {
let tempdir = tempdir::TempDir::new("flamegraph").unwrap();
let stacks_file = tempdir.path().join("stacks.txt");
let mut file = File::create(&stacks_file).expect("couldn't create file");
for (k, v) in &self.counts {
file.write_all(&k)?;
writeln!(file, " {}", v)?;
}
write_flamegraph(&stacks_file, w)
}
}
fn write_flamegraph(source: &Path, target: File) -> Result<(), Error> {
let mut child = Command::new("perl")
.arg("-")
.arg("--inverted") .arg("--minwidth").arg("2") .arg(source)
.stdin(Stdio::piped()) .stdout(target)
.spawn()
.context("Couldn't execute perl")?;
{
let stdin = child.stdin.as_mut().expect("failed to write to stdin");
stdin.write_all(FLAMEGRAPH_SCRIPT)?;
}
child.wait()?;
Ok(())
}