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

Implement --factor option #65

Merged
merged 7 commits into from
Feb 28, 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
15 changes: 10 additions & 5 deletions src/bin/flamegraph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ struct Opt {
/// Collapsed perf output files. With no INFILE, or INFILE is -, read STDIN.
#[structopt(name = "INFILE", parse(from_os_str))]
infiles: Vec<PathBuf>,
/// set color palette
/// Set color palette
#[structopt(
short = "c",
long = "colors",
Expand All @@ -31,19 +31,23 @@ struct Opt {
)
)]
colors: Palette,
/// set background colors. Gradient choices are yellow (default), blue, green, grey; flat colors use "#rrggbb"
/// Set background colors. Gradient choices are yellow (default), blue, green, grey; flat colors use "#rrggbb"
#[structopt(long = "bgcolors")]
bgcolors: Option<BackgroundColor>,
/// colors are keyed by function name hash
/// Colors are keyed by function name hash
#[structopt(long = "hash")]
hash: bool,
/// use consistent palette (palette.map)
/// Use consistent palette (palette.map)
#[structopt(long = "cp")]
cp: bool,

/// switch differential hues (green<->red)
/// Switch differential hues (green<->red)
#[structopt(long = "negate")]
negate: bool,

/// Factor to scale sample counts by
#[structopt(long = "factor", default_value = "1.0")]
factor: f64,
}

impl Into<Options> for Opt {
Expand All @@ -66,6 +70,7 @@ impl Into<Options> for Opt {
options.title = "Icicle Graph".to_string();
}
options.negate_differentials = self.negate;
options.factor = self.factor;
options
}
}
Expand Down
44 changes: 28 additions & 16 deletions src/flamegraph/merge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ pub(super) struct Frame<'a> {
pub(super) depth: usize,
}

#[derive(Debug, PartialEq, Eq, Hash)]
#[derive(Debug, PartialEq)]
pub(super) struct TimedFrame<'a> {
pub(super) location: Frame<'a>,
pub(super) start_time: usize,
pub(super) end_time: usize,
pub(super) delta: Option<isize>,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub(super) struct FrameTime {
pub(super) start_time: usize,
pub(super) delta: Option<isize>,
Expand Down Expand Up @@ -114,6 +114,7 @@ where
let mut frames = Default::default();
let mut delta = None;
let mut delta_max = 1;
let mut stripped_fractional_samples = false;
for line in lines {
let mut line = line.trim();
if line.is_empty() {
Expand All @@ -124,17 +125,20 @@ where
// Usually there will only be one samples column at the end of a line,
// but for differentials there will be two. When there are two we compute the
// delta between them and use the second one.
let nsamples = if let Some(samples) = parse_nsamples(&mut line) {
// See if there's also a differential column present
if let Some(original_samples) = parse_nsamples(&mut line) {
delta = Some(samples as isize - original_samples as isize);
delta_max = std::cmp::max(delta.unwrap().abs() as usize, delta_max);
}
samples
} else {
ignored += 1;
continue;
};
let nsamples =
if let Some(samples) = parse_nsamples(&mut line, &mut stripped_fractional_samples) {
// See if there's also a differential column present
if let Some(original_samples) =
parse_nsamples(&mut line, &mut stripped_fractional_samples)
{
delta = Some(samples as isize - original_samples as isize);
delta_max = std::cmp::max(delta.unwrap().abs() as usize, delta_max);
}
samples
} else {
ignored += 1;
continue;
};

if line.is_empty() {
ignored += 1;
Expand Down Expand Up @@ -180,14 +184,22 @@ where
}

// Parse and remove the number of samples from the end of a line.
fn parse_nsamples(line: &mut &str) -> Option<usize> {
fn parse_nsamples(line: &mut &str, stripped_fractional_samples: &mut bool) -> Option<usize> {
let samplesi = line.rfind(' ')?;
let mut samples = &line[(samplesi + 1)..];

// strip fractional part (if any);
// Strip fractional part (if any);
// foobar 1.klwdjlakdj
// TODO: Properly handle fractional samples (see issue #43)
//
// The Perl version keeps the fractional part but this can be problematic
// because of cumulative floating point errors. Instead we recommend to
// use the --factor option. See https://github.com/brendangregg/FlameGraph/pull/18
if let Some(doti) = samples.find('.') {
// Warn if we're stripping a non-zero fractional part, but only the first time.
if !*stripped_fractional_samples && !samples[doti + 1..].chars().all(|c| c == '0') {
*stripped_fractional_samples = true;
warn!("The input data has fractional sample counts that will be truncated to integers. If you need to retain the extra precision you can scale up the sample data and use the --factor option to scale it back down.");
}
samples = &samples[..doti];
}

Expand Down
25 changes: 22 additions & 3 deletions src/flamegraph/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,25 @@ pub struct Options {
///
/// [differential]: http://www.brendangregg.com/blog/2014-11-09/differential-flame-graphs.html
pub negate_differentials: bool,

/// Factor to scale sample counts by in the flame graph.
///
/// This option can be useful if the sample data has fractional sample counts since the fractional
/// parts are stripped off when creating the flame graph. To work around this you can scale up the
/// sample counts to be integers, then scale them back down in the graph with the `factor` option.
///
/// For example, if you have `23.4` as a sample count you can upscale it to `234`, then set `factor`
/// to `0.1`.
///
/// Defaults to 1.0.
pub factor: f64,
}

impl Default for Options {
fn default() -> Self {
Options {
title: "Flame Graph".to_string(),
factor: 1.0,
colors: Default::default(),
bgcolors: Default::default(),
hash: Default::default(),
Expand Down Expand Up @@ -324,7 +337,13 @@ where
};
let rect = Rectangle { x1, y1, x2, y2 };

let samples = frame.end_time - frame.start_time;
// The rounding here can differ from the Perl version when the fractional part is `0.5`.
// The Perl version does `my $samples = sprintf "%.0f", ($etime - $stime) * $factor;`,
// but this can format in strange ways as shown in these examples:
// `sprintf "%.0f", 1.5` produces "2"
// `sprintf "%.0f", 2.5` produces "2"
// `sprintf "%.0f", 3.5` produces "4"
let samples = ((frame.end_time - frame.start_time) as f64 * opt.factor).round() as usize;

// add thousands separators to `samples`
let _ = samples_txt_buffer.write_formatted(&samples, &Locale::en);
Expand All @@ -333,7 +352,7 @@ where
let info = if frame.location.function.is_empty() && frame.location.depth == 0 {
write!(buffer, "all ({} samples, 100%)", samples_txt)
} else {
let pct = (100 * samples) as f64 / timemax as f64;
let pct = (100 * samples) as f64 / (timemax as f64 * opt.factor);
let function = deannotate(&frame.location.function);
match frame.delta {
None => write!(
Expand All @@ -351,7 +370,7 @@ where
if opt.negate_differentials {
delta = -delta;
}
let delta_pct = (100 * delta) as f64 / timemax as f64;
let delta_pct = (100 * delta) as f64 / (timemax as f64 * opt.factor);
write!(
buffer,
"{} ({} samples, {:.2}%; {:+.2}%)",
Expand Down
Loading