Skip to content

Commit

Permalink
Merge pull request #114 from benfred/inferno
Browse files Browse the repository at this point in the history
Use inferno to generate flamegraphs
  • Loading branch information
benfred committed May 18, 2019
2 parents 3629dc8 + e3a6a47 commit d637a25
Show file tree
Hide file tree
Showing 8 changed files with 171 additions and 1,554 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
## v0.1.11

* Fix to detect GIL status on Python 3.7+ [#104](https://github.com/benfred/py-spy/pull/104)
* Generate flamegraphs without perl (using Inferno) [#38](https://github.com/benfred/py-spy/issues/38)
* Use irregular sampling interval to avoid incorrect results [#94](https://github.com/benfred/py-spy/issues/94)
* Detect python packages when generating short filenames [#75](https://github.com/benfred/py-spy/issues/75)
* Fix issue with finding interpreter with Python 3.7 and 32bit Linux [#101](https://github.com/benfred/py-spy/issues/101)
* Detect "v2.7.15+" as a valid version string [#81](https://github.com/benfred/py-spy/issues/81)
* Fix to cleanup venv after failing to build with setup.py [#69](https://github.com/benfred/py-spy/issues/69)

## v0.1.10

* Fix running py-spy inside a docker container [#68](https://github.com/benfred/py-spy/issues/68)
Expand Down
137 changes: 137 additions & 0 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ indicatif = "0.11"
env_logger = "0.6"
failure = "0.1.1"
goblin = "0.0.21"
inferno = "0.7.0"
lazy_static = "1.1.0"
libc = "0.2.34"
log = "0.4"
Expand Down
62 changes: 22 additions & 40 deletions src/flamegraph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,16 @@ SOFTWARE.

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;
use failure::Error;
use inferno::flamegraph::{Direction, Options};

const FLAMEGRAPH_SCRIPT: &[u8] = include_bytes!("../vendor/flamegraph/flamegraph.pl");
use stack_trace::StackTrace;

pub struct Flamegraph {
pub counts: HashMap<Vec<u8>, usize>,
pub counts: HashMap<String, usize>,
pub show_linenumbers: bool,
}

Expand All @@ -55,47 +51,33 @@ impl Flamegraph {
if !(trace.active) {
continue;
}
let mut buf = vec![];
for frame in trace.frames.iter().rev() {

// convert the frame into a single ';' delimited String
let frame = trace.frames.iter().rev().map(|frame| {
let filename = match &frame.short_filename { Some(f) => &f, None => &frame.filename };
if self.show_linenumbers && frame.line != 0 {
write!(&mut buf, "{} ({}:{});", frame.name, filename, frame.line)?;
format!("{} ({}:{})", frame.name, filename, frame.line)
} else {
write!(&mut buf, "{} ({});", frame.name, filename)?;
format!("{} ({})", frame.name, filename)
}
}
*self.counts.entry(buf).or_insert(0) += 1;
}).collect::<Vec<String>>().join(";");

// update counts for that frame
*self.counts.entry(frame).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)
}
}
let lines: Vec<String> = self.counts.iter().map(|(k, v)| format!("{} {}", k, v)).collect();
let mut opts = Options {
direction: Direction::Inverted,
min_width: 1.0,
title: "py-spy".to_owned(),
..Default::default()
};

fn write_flamegraph(source: &Path, target: File) -> Result<(), Error> {
let mut child = Command::new("perl")
.arg("-")
.arg("--inverted") // icicle graphs are easier to read
.arg("--minwidth").arg("1") // min width 2 pixels saves on disk space
.arg(source)
.stdin(Stdio::piped()) // pipe in the flamegraph.pl script to stdin
.stdout(target)
.spawn()
.context("Couldn't execute perl")?;
// TODO(nll): Remove this silliness after non-lexical lifetimes land.
{
let stdin = child.stdin.as_mut().expect("failed to write to stdin");
stdin.write_all(FLAMEGRAPH_SCRIPT)?;
inferno::flamegraph::from_lines(&mut opts, lines.iter().map(|x| x.as_str()), w).unwrap();
Ok(())
}
child.wait()?;
Ok(())
}
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ extern crate env_logger;
extern crate failure;
extern crate goblin;
extern crate indicatif;
extern crate inferno;
#[macro_use]
extern crate lazy_static;
extern crate libc;
Expand Down
5 changes: 0 additions & 5 deletions vendor/flamegraph/README.md

This file was deleted.

Loading

0 comments on commit d637a25

Please sign in to comment.