Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use inferno to generate flamegraphs #114

Merged
merged 1 commit into from
May 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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