diff --git a/Cargo.toml b/Cargo.toml index f7865cb..ab702ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ documentation = "https://docs.rs/flame" [features] default = ["json"] json = ["serde", "serde_derive", "serde_json"] +off = [] [dependencies] lazy_static = "1.*.*" diff --git a/src/empty.rs b/src/empty.rs new file mode 100644 index 0000000..59043dc --- /dev/null +++ b/src/empty.rs @@ -0,0 +1,217 @@ +//! Here's an example of how to use some of FLAMEs APIs: +//! +//! ``` +//! extern crate flame; +//! +//! use std::fs::File; +//! +//! pub fn main() { +//! // Manual `start` and `end` +//! flame::start("read file"); +//! let x = read_a_file(); +//! flame::end("read file"); +//! +//! // Time the execution of a closure. (the result of the closure is returned) +//! let y = flame::span_of("database query", || query_database()); +//! +//! // Time the execution of a block by creating a guard. +//! let z = { +//! let _guard = flame::start_guard("cpu-heavy calculation"); +//! cpu_heavy_operations_1(); +//! // Notes can be used to annotate a particular instant in time. +//! flame::note("something interesting happened", None); +//! cpu_heavy_operations_2() +//! }; +//! +//! // Dump the report to disk +//! flame::dump_html(&mut File::create("flame-graph.html").unwrap()).unwrap(); +//! +//! // Or read and process the data yourself! +//! let spans = flame::spans(); +//! +//! println!("{} {} {}", x, y, z); +//! } +//! +//! # fn read_a_file() -> bool { true } +//! # fn query_database() -> bool { true } +//! # fn cpu_heavy_operations_1() {} +//! # fn cpu_heavy_operations_2() -> bool { true } +//! ``` + +use std; + +use std::borrow::Cow; +use std::io::{Error as IoError, Write}; + +pub type StrCow = Cow<'static, str>; + +/// A named timespan. +/// +/// The span is the most important feature of Flame. It denotes +/// a chunk of time that is important to you. +/// +/// The Span records +/// * Start and stop time +/// * A list of children (also called sub-spans) +/// * A list of notes +#[derive(Debug, Clone)] +#[cfg_attr(feature = "json", derive(Serialize))] +pub struct Span { + /// The name of the span + pub name: StrCow, + /// The timestamp of the start of the span + pub start_ns: u64, + /// The timestamp of the end of the span + pub end_ns: u64, + /// The time that ellapsed between start_ns and end_ns + pub delta: u64, + /// How deep this span is in the tree + pub depth: u16, + /// A list of spans that occurred inside this one + pub children: Vec, + /// A list of notes that occurred inside this span + pub notes: Vec, + #[cfg_attr(feature = "json", serde(skip_serializing))] collapsable: bool, + #[cfg_attr(feature = "json", serde(skip_serializing))] _priv: (), +} + +/// A note for use in debugging. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "json", derive(Serialize))] +pub struct Note { + /// A short name describing what happened at some instant in time + pub name: StrCow, + /// A longer description + pub description: Option, + /// The time that the note was added + pub instant: u64, + #[cfg_attr(feature = "json", serde(skip_serializing))] _priv: (), +} + +/// A collection of events that happened on a single thread. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "json", derive(Serialize))] +pub struct Thread { + pub id: usize, + pub name: Option, + pub spans: Vec, + #[cfg_attr(feature = "json", serde(skip_serializing))] _priv: (), +} + +pub struct SpanGuard {} + +impl Drop for SpanGuard { + fn drop(&mut self) {} +} + +impl SpanGuard { + pub fn end(self) {} + pub fn end_collapse(self) {} +} + + +impl Span { + #[cfg(feature = "json")] + pub fn into_json(&self) -> String { + panic!(); + } +} + +impl Thread { + #[cfg(feature = "json")] + pub fn into_json(&self) -> String { + panic!(); + } + + #[cfg(feature = "json")] + pub fn into_json_list(_threads: &Vec) -> String { + panic!(); + } +} + +/// Starts a `Span` and also returns a `SpanGuard`. +/// +/// When the `SpanGuard` is dropped (or `.end()` is called on it), +/// the span will automatically be ended. +pub fn start_guard>(_name: S) -> SpanGuard { + SpanGuard {} +} + +/// Starts and ends a `Span` that lasts for the duration of the +/// function `f`. +pub fn span_of(_name: S, f: F) -> R +where + S: Into, + F: FnOnce() -> R, +{ + f() +} + +/// Starts a new Span +pub fn start>(_name: S) {} + + +/// Ends the current Span and returns the number +/// of nanoseconds that passed. +pub fn end>(_name: S) -> u64 { + 0 +} + +/// Ends the current Span and returns a given result. +/// +/// This is mainly useful for code generation / plugins where +/// wrapping all returned expressions is easier than creating +/// a temporary variable to hold the result. +pub fn end_with, R>(_name: S, result: R) -> R { + result +} + +/// Ends the current Span and returns the number of +/// nanoseconds that passed. +/// +/// If this span is a leaf node, and the previous span +/// has the same name and depth, then collapse this +/// span into the previous one. The end_ns field will +/// be updated to the end time of *this* span, and the +/// delta field will be the sum of the deltas from this +/// and the previous span. +/// +/// This means that it is possible for end_ns - start_n +/// to not be equal to delta. +pub fn end_collapse>(_name: S) -> u64 { + 0 +} + +/// Records a note on the current Span. +pub fn note>(_name: S, _description: Option) {} + +/// Clears all of the recorded info that Flame has +/// tracked. +pub fn clear() {} + +/// Returns a list of spans from the current thread +pub fn spans() -> Vec { + vec![] +} + +pub fn threads() -> Vec { + vec![] +} + +/// Prints all of the frames to stdout. +pub fn debug() {} + +pub fn dump_text_to_writer(_out: W) -> Result<(), IoError> { + Ok(()) +} + +pub fn dump_stdout() {} + +#[cfg(feature = "json")] +pub fn dump_json(_out: &mut W) -> std::io::Result<()> { + Ok(()) +} + +pub use html::{dump_html, dump_html_custom}; + +pub fn commit_thread() {} diff --git a/src/lib.rs b/src/lib.rs index 3bf4f22..d6d4f2c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,557 +1,27 @@ -#![allow(unused)] - -//! Here's an example of how to use some of FLAMEs APIs: -//! -//! ``` -//! extern crate flame; -//! -//! use std::fs::File; -//! -//! pub fn main() { -//! // Manual `start` and `end` -//! flame::start("read file"); -//! let x = read_a_file(); -//! flame::end("read file"); -//! -//! // Time the execution of a closure. (the result of the closure is returned) -//! let y = flame::span_of("database query", || query_database()); -//! -//! // Time the execution of a block by creating a guard. -//! let z = { -//! let _guard = flame::start_guard("cpu-heavy calculation"); -//! cpu_heavy_operations_1(); -//! // Notes can be used to annotate a particular instant in time. -//! flame::note("something interesting happened", None); -//! cpu_heavy_operations_2() -//! }; -//! -//! // Dump the report to disk -//! flame::dump_html(&mut File::create("flame-graph.html").unwrap()).unwrap(); -//! -//! // Or read and process the data yourself! -//! let spans = flame::spans(); -//! -//! println!("{} {} {}", x, y, z); -//! } -//! -//! # fn read_a_file() -> bool { true } -//! # fn query_database() -> bool { true } -//! # fn cpu_heavy_operations_1() {} -//! # fn cpu_heavy_operations_2() -> bool { true } -//! ``` - - +#[cfg(not(feature = "off"))] #[macro_use] extern crate lazy_static; +#[cfg(not(feature = "off"))] extern crate thread_id; +#[cfg(feature = "json")] +extern crate serde; #[cfg(feature = "json")] #[macro_use] extern crate serde_derive; #[cfg(feature = "json")] -extern crate serde; -#[cfg(feature = "json")] extern crate serde_json; -mod html; - -use std::cell::{RefCell, Cell}; -use std::iter::Peekable; -use std::borrow::Cow; -use std::sync::Mutex; -use std::time::{Duration, Instant}; -use std::io::{Write, Error as IoError}; - -pub type StrCow = Cow<'static, str>; - -lazy_static!(static ref ALL_THREADS: Mutex, PrivateFrame)>> = Mutex::new(Vec::new());); -thread_local!(static LIBRARY: RefCell = RefCell::new(Library::new())); - -#[derive(Debug)] -struct Library { - name: Option, - current: PrivateFrame, - epoch: Instant, -} - -#[derive(Debug)] -struct PrivateFrame { - next_id: u32, - all: Vec, - id_stack: Vec, -} - -#[derive(Debug)] -struct Event { - id: u32, - parent: Option, - name: StrCow, - collapse: bool, - start_ns: u64, - end_ns: Option, - delta: Option, - notes: Vec, -} - -/// A named timespan. -/// -/// The span is the most important feature of Flame. It denotes -/// a chunk of time that is important to you. -/// -/// The Span records -/// * Start and stop time -/// * A list of children (also called sub-spans) -/// * A list of notes -#[derive(Debug, Clone)] -#[cfg_attr(feature = "json", derive(Serialize))] -pub struct Span { - /// The name of the span - pub name: StrCow, - /// The timestamp of the start of the span - pub start_ns: u64, - /// The timestamp of the end of the span - pub end_ns: u64, - /// The time that ellapsed between start_ns and end_ns - pub delta: u64, - /// How deep this span is in the tree - pub depth: u16, - /// A list of spans that occurred inside this one - pub children: Vec, - /// A list of notes that occurred inside this span - pub notes: Vec, - #[cfg_attr(feature = "json", serde(skip_serializing))] - collapsable: bool, - #[cfg_attr(feature = "json", serde(skip_serializing))] - _priv: (), -} - -/// A note for use in debugging. -#[derive(Debug, Clone)] -#[cfg_attr(feature = "json", derive(Serialize))] -pub struct Note { - /// A short name describing what happened at some instant in time - pub name: StrCow, - /// A longer description - pub description: Option, - /// The time that the note was added - pub instant: u64, - #[cfg_attr(feature = "json", serde(skip_serializing))] - _priv: (), -} - -/// A collection of events that happened on a single thread. -#[derive(Debug, Clone)] -#[cfg_attr(feature = "json", derive(Serialize))] -pub struct Thread { - pub id: usize, - pub name: Option, - pub spans: Vec, - #[cfg_attr(feature = "json", serde(skip_serializing))] - _priv: (), -} - -pub struct SpanGuard { - name: Option, - collapse: bool, -} - -impl Drop for SpanGuard { - fn drop(&mut self) { - if ::std::thread::panicking() { return; } - let name = self.name.take().unwrap(); - end_impl(name, self.collapse); - } -} - -impl SpanGuard { - pub fn end(self) { } - pub fn end_collapse(mut self) { - self.collapse = true; - } -} - -fn ns_since_epoch(epoch: Instant) -> u64 { - let elapsed = epoch.elapsed(); - elapsed.as_secs() * 1000_000_000 + elapsed.subsec_nanos() as u64 -} - -fn convert_events_to_span<'a, I>(events: I) -> Vec -where I: Iterator { - let mut iterator = events.peekable(); - let mut v = vec![]; - while let Some(event) = iterator.next() { - if let Some(span) = event_to_span(event, &mut iterator, 0) { - v.push(span); - } - } - v -} - -fn event_to_span<'a, I: Iterator>(event: &Event, events: &mut Peekable, depth: u16) -> Option { - if event.end_ns.is_some() && event.delta.is_some() { - let mut span = Span { - name: event.name.clone(), - start_ns: event.start_ns, - end_ns: event.end_ns.unwrap(), - delta: event.delta.unwrap(), - depth: depth, - children: vec![], - notes: event.notes.clone(), - collapsable: event.collapse, - _priv: () - }; - - loop { - { - match events.peek() { - Some(next) if next.parent != Some(event.id) => break, - None => break, - _ => {} - } - } - - let next = events.next().unwrap(); - let child = event_to_span(next, events, depth + 1); - if let Some(child) = child { - // Try to collapse with the previous span - if span.children.len() != 0 && child.collapsable && child.children.len() == 0 { - let last = span.children.last_mut().unwrap(); - if last.name == child.name && last.depth == child.depth { - last.end_ns = child.end_ns; - last.delta += child.delta; - continue; - } - } - - // Otherwise, it's a new node - span.children.push(child); - } - } - Some(span) - } else { - None - } -} - -impl Span { - #[cfg(feature = "json")] - pub fn into_json(&self) -> String { - ::serde_json::to_string_pretty(self).unwrap() - } -} - -impl Thread { - #[cfg(feature = "json")] - pub fn into_json(&self) -> String { - ::serde_json::to_string_pretty(self).unwrap() - } - - #[cfg(feature = "json")] - pub fn into_json_list(threads: &Vec) -> String { - ::serde_json::to_string_pretty(threads).unwrap() - } -} - -impl Library { - fn new() -> Library { - Library { - name: ::std::thread::current().name().map(Into::into), - current: PrivateFrame { - all: vec![], - id_stack: vec![], - next_id: 0, - }, - epoch: Instant::now(), - } - } -} - -fn commit_impl(library: &mut Library) { - use std::thread; - use std::sync::MutexGuard; - use std::mem; - - let mut frame = PrivateFrame { - all: vec![], - id_stack: vec![], - next_id: 0, - }; - - mem::swap(&mut frame, &mut library.current); - if frame.all.is_empty() { - return; - } - - if let Ok(mut handle) = ALL_THREADS.lock() { - let thread_name = library.name.clone(); - let thread_id = ::thread_id::get(); - handle.push((thread_id, thread_name, frame)) - } -} - -pub fn commit_thread() { - LIBRARY.with(|library| commit_impl(&mut *library.borrow_mut())); -} - -impl Drop for Library { - fn drop(&mut self) { - if ::std::thread::panicking() { return; } - commit_impl(self); - } -} +#[cfg(not(feature = "off"))] +mod real; -/// Starts a `Span` and also returns a `SpanGuard`. -/// -/// When the `SpanGuard` is dropped (or `.end()` is called on it), -/// the span will automatically be ended. -pub fn start_guard>(name: S) -> SpanGuard { - let name = name.into(); - start(name.clone()); - SpanGuard { name: Some(name), collapse: false } -} +#[cfg(feature = "off")] +mod empty; -/// Starts and ends a `Span` that lasts for the duration of the -/// function `f`. -pub fn span_of(name: S, f: F) -> R where -S: Into, -F: FnOnce() -> R -{ - let name = name.into(); - start(name.clone()); - let r = f(); - end(name); - r -} +#[cfg(not(feature = "off"))] +pub use real::*; -/// Starts a new Span -pub fn start>(name: S) { - LIBRARY.with(|library| { - let mut library = library.borrow_mut(); - let epoch = library.epoch; +#[cfg(feature = "off")] +pub use empty::*; - let collector = &mut library.current; - let id = collector.next_id; - collector.next_id += 1; - - let this = Event { - id: id, - parent: collector.id_stack.last().cloned(), - name: name.into(), - collapse: false, - start_ns: ns_since_epoch(epoch), - end_ns: None, - delta: None, - notes: vec![] - }; - - collector.all.push(this); - collector.id_stack.push(id); - }); -} - -fn end_impl>(name: S, collapse: bool) -> u64 { - use std::thread; - - let name = name.into(); - let delta = LIBRARY.with(|library| { - let mut library = library.borrow_mut(); - let epoch = library.epoch; - let collector = &mut library.current; - - let current_id = match collector.id_stack.pop() { - Some(id) => id, - None if thread::panicking() => 0, - None => panic!("flame::end({:?}) called without a currently running span!", &name) - }; - - let event = &mut collector.all[current_id as usize]; - - if event.name != name { - panic!("flame::end({}) attempted to end {}", &name, event.name); - } - - let timestamp = ns_since_epoch(epoch); - event.end_ns = Some(timestamp); - event.collapse = collapse; - event.delta = Some(timestamp - event.start_ns); - event.delta - }); - - match delta { - Some(d) => d, - None => 0, // panicking - } -} - -/// Ends the current Span and returns the number -/// of nanoseconds that passed. -pub fn end>(name: S) -> u64 { - end_impl(name, false) -} - -/// Ends the current Span and returns a given result. -/// -/// This is mainly useful for code generation / plugins where -/// wrapping all returned expressions is easier than creating -/// a temporary variable to hold the result. -pub fn end_with, R>(name: S, result: R) -> R { - end_impl(name, false); - result -} - -/// Ends the current Span and returns the number of -/// nanoseconds that passed. -/// -/// If this span is a leaf node, and the previous span -/// has the same name and depth, then collapse this -/// span into the previous one. The end_ns field will -/// be updated to the end time of *this* span, and the -/// delta field will be the sum of the deltas from this -/// and the previous span. -/// -/// This means that it is possible for end_ns - start_n -/// to not be equal to delta. -pub fn end_collapse>(name: S) -> u64 { - end_impl(name, false) -} - -/// Records a note on the current Span. -pub fn note>(name: S, description: Option) { - let name = name.into(); - let description = description.map(Into::into); - - LIBRARY.with(|library| { - let mut library = library.borrow_mut(); - let epoch = library.epoch; - - let collector = &mut library.current; - - let current_id = match collector.id_stack.last() { - Some(id) => *id, - None => panic!("flame::note({}, {:?}) called without a currently running span!", - &name, &description) - }; - - let event = &mut collector.all[current_id as usize]; - event.notes.push(Note { - name: name, - description: description, - instant: ns_since_epoch(epoch), - _priv: () - }); - }); -} - -/// Clears all of the recorded info that Flame has -/// tracked. -pub fn clear() { - if ::std::thread::panicking() { return; } - LIBRARY.with(|library| { - let mut library = library.borrow_mut(); - library.current = PrivateFrame { - all: vec![], - id_stack: vec![], - next_id: 0, - }; - library.epoch = Instant::now(); - }); - - let mut handle = ALL_THREADS.lock().unwrap(); - handle.clear(); -} - -/// Returns a list of spans from the current thread -pub fn spans() -> Vec { - if ::std::thread::panicking() { return vec![]; } - LIBRARY.with(|library| { - let library = library.borrow(); - let cur = &library.current; - convert_events_to_span(cur.all.iter()) - }) -} - -pub fn threads() -> Vec { - if ::std::thread::panicking() { return vec![]; } - - let my_thread_name = ::std::thread::current().name().map(Into::into); - let my_thread_id = ::thread_id::get(); - - let mut out = vec![ Thread { - id: my_thread_id, - name: my_thread_name, - spans: spans(), - _priv: (), - }]; - - if let Ok(mut handle) = ALL_THREADS.lock() { - for &(id, ref name, ref frm) in &*handle { - out.push(Thread { - id: id, - name: name.clone(), - spans: convert_events_to_span(frm.all.iter()), - _priv: (), - }); - } - } - - out -} - -/// Prints all of the frames to stdout. -pub fn debug() { - if ::std::thread::panicking() { return; } - LIBRARY.with(|library| { - println!("{:?}", library); - }); -} - -pub fn dump_text_to_writer(mut out: W) -> Result<(), IoError> { - fn print_span(span: &Span, out: &mut W) -> Result { - let mut buf = String::new(); - for _ in 0 .. span.depth { - buf.push_str(" "); - } - buf.push_str("| "); - let ms = span.delta as f32 / 1000000.0; - buf.push_str(&format!("{}: {}ms", span.name, ms)); - writeln!(out, "{}", buf)?; - let mut missing = ms; - for child in &span.children { - missing -= print_span(child, out)?; - } - - if !span.children.is_empty() { - let mut buf = String::new(); - for _ in 0 .. (span.depth + 1) { - buf.push_str(" "); - } - buf.push_str("+ "); - buf.push_str(&format!("{}ms", missing)); - writeln!(out, "{}", buf)?; - } - - Ok(ms) - } - - for thread in threads() { - writeln!(out, "THREAD: {}", thread.id)?; - for span in thread.spans { - print_span(&span, &mut out)?; - } - writeln!(out, "")?; - } - Ok(()) -} - -pub fn dump_stdout() { - let stdout = ::std::io::stdout(); - let stdout = stdout.lock(); - dump_text_to_writer(stdout); -} - -#[cfg(feature="json")] -pub fn dump_json(out: &mut W) -> std::io::Result<()> { - out.write_all(serde_json::to_string_pretty(&threads()).unwrap().as_bytes()) -} - -pub use html::{dump_html, dump_html_custom}; +mod html; diff --git a/src/real.rs b/src/real.rs new file mode 100644 index 0000000..4f24578 --- /dev/null +++ b/src/real.rs @@ -0,0 +1,541 @@ +//! Here's an example of how to use some of FLAMEs APIs: +//! +//! ``` +//! extern crate flame; +//! +//! use std::fs::File; +//! +//! pub fn main() { +//! // Manual `start` and `end` +//! flame::start("read file"); +//! let x = read_a_file(); +//! flame::end("read file"); +//! +//! // Time the execution of a closure. (the result of the closure is returned) +//! let y = flame::span_of("database query", || query_database()); +//! +//! // Time the execution of a block by creating a guard. +//! let z = { +//! let _guard = flame::start_guard("cpu-heavy calculation"); +//! cpu_heavy_operations_1(); +//! // Notes can be used to annotate a particular instant in time. +//! flame::note("something interesting happened", None); +//! cpu_heavy_operations_2() +//! }; +//! +//! // Dump the report to disk +//! flame::dump_html(&mut File::create("flame-graph.html").unwrap()).unwrap(); +//! +//! // Or read and process the data yourself! +//! let spans = flame::spans(); +//! +//! println!("{} {} {}", x, y, z); +//! } +//! +//! # fn read_a_file() -> bool { true } +//! # fn query_database() -> bool { true } +//! # fn cpu_heavy_operations_1() {} +//! # fn cpu_heavy_operations_2() -> bool { true } +//! ``` + +use std; +use serde_json; + +use std::cell::RefCell; +use std::iter::Peekable; +use std::borrow::Cow; +use std::sync::Mutex; +use std::time::Instant; +use std::io::{Write, Error as IoError}; + +pub type StrCow = Cow<'static, str>; + +lazy_static!(static ref ALL_THREADS: Mutex, PrivateFrame)>> = Mutex::new(Vec::new());); +thread_local!(static LIBRARY: RefCell = RefCell::new(Library::new())); + +#[derive(Debug)] +struct Library { + name: Option, + current: PrivateFrame, + epoch: Instant, +} + +#[derive(Debug)] +struct PrivateFrame { + next_id: u32, + all: Vec, + id_stack: Vec, +} + +#[derive(Debug)] +struct Event { + id: u32, + parent: Option, + name: StrCow, + collapse: bool, + start_ns: u64, + end_ns: Option, + delta: Option, + notes: Vec, +} + +/// A named timespan. +/// +/// The span is the most important feature of Flame. It denotes +/// a chunk of time that is important to you. +/// +/// The Span records +/// * Start and stop time +/// * A list of children (also called sub-spans) +/// * A list of notes +#[derive(Debug, Clone)] +#[cfg_attr(feature = "json", derive(Serialize))] +pub struct Span { + /// The name of the span + pub name: StrCow, + /// The timestamp of the start of the span + pub start_ns: u64, + /// The timestamp of the end of the span + pub end_ns: u64, + /// The time that ellapsed between start_ns and end_ns + pub delta: u64, + /// How deep this span is in the tree + pub depth: u16, + /// A list of spans that occurred inside this one + pub children: Vec, + /// A list of notes that occurred inside this span + pub notes: Vec, + #[cfg_attr(feature = "json", serde(skip_serializing))] + collapsable: bool, + #[cfg_attr(feature = "json", serde(skip_serializing))] + _priv: (), +} + +/// A note for use in debugging. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "json", derive(Serialize))] +pub struct Note { + /// A short name describing what happened at some instant in time + pub name: StrCow, + /// A longer description + pub description: Option, + /// The time that the note was added + pub instant: u64, + #[cfg_attr(feature = "json", serde(skip_serializing))] + _priv: (), +} + +/// A collection of events that happened on a single thread. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "json", derive(Serialize))] +pub struct Thread { + pub id: usize, + pub name: Option, + pub spans: Vec, + #[cfg_attr(feature = "json", serde(skip_serializing))] + _priv: (), +} + +pub struct SpanGuard { + name: Option, + collapse: bool, +} + +impl Drop for SpanGuard { + fn drop(&mut self) { + if ::std::thread::panicking() { return; } + let name = self.name.take().unwrap(); + end_impl(name, self.collapse); + } +} + +impl SpanGuard { + pub fn end(self) { } + pub fn end_collapse(mut self) { + self.collapse = true; + } +} + +fn ns_since_epoch(epoch: Instant) -> u64 { + let elapsed = epoch.elapsed(); + elapsed.as_secs() * 1000_000_000 + elapsed.subsec_nanos() as u64 +} + +fn convert_events_to_span<'a, I>(events: I) -> Vec +where I: Iterator { + let mut iterator = events.peekable(); + let mut v = vec![]; + while let Some(event) = iterator.next() { + if let Some(span) = event_to_span(event, &mut iterator, 0) { + v.push(span); + } + } + v +} + +fn event_to_span<'a, I: Iterator>(event: &Event, events: &mut Peekable, depth: u16) -> Option { + if event.end_ns.is_some() && event.delta.is_some() { + let mut span = Span { + name: event.name.clone(), + start_ns: event.start_ns, + end_ns: event.end_ns.unwrap(), + delta: event.delta.unwrap(), + depth: depth, + children: vec![], + notes: event.notes.clone(), + collapsable: event.collapse, + _priv: () + }; + + loop { + { + match events.peek() { + Some(next) if next.parent != Some(event.id) => break, + None => break, + _ => {} + } + } + + let next = events.next().unwrap(); + let child = event_to_span(next, events, depth + 1); + if let Some(child) = child { + // Try to collapse with the previous span + if span.children.len() != 0 && child.collapsable && child.children.len() == 0 { + let last = span.children.last_mut().unwrap(); + if last.name == child.name && last.depth == child.depth { + last.end_ns = child.end_ns; + last.delta += child.delta; + continue; + } + } + + // Otherwise, it's a new node + span.children.push(child); + } + } + Some(span) + } else { + None + } +} + +impl Span { + #[cfg(feature = "json")] + pub fn into_json(&self) -> String { + ::serde_json::to_string_pretty(self).unwrap() + } +} + +impl Thread { + #[cfg(feature = "json")] + pub fn into_json(&self) -> String { + ::serde_json::to_string_pretty(self).unwrap() + } + + #[cfg(feature = "json")] + pub fn into_json_list(threads: &Vec) -> String { + ::serde_json::to_string_pretty(threads).unwrap() + } +} + +impl Library { + fn new() -> Library { + Library { + name: ::std::thread::current().name().map(Into::into), + current: PrivateFrame { + all: vec![], + id_stack: vec![], + next_id: 0, + }, + epoch: Instant::now(), + } + } +} + +fn commit_impl(library: &mut Library) { + use std::mem; + + let mut frame = PrivateFrame { + all: vec![], + id_stack: vec![], + next_id: 0, + }; + + mem::swap(&mut frame, &mut library.current); + if frame.all.is_empty() { + return; + } + + if let Ok(mut handle) = ALL_THREADS.lock() { + let thread_name = library.name.clone(); + let thread_id = ::thread_id::get(); + handle.push((thread_id, thread_name, frame)) + } +} + +pub fn commit_thread() { + LIBRARY.with(|library| commit_impl(&mut *library.borrow_mut())); +} + +impl Drop for Library { + fn drop(&mut self) { + if ::std::thread::panicking() { return; } + commit_impl(self); + } +} + +/// Starts a `Span` and also returns a `SpanGuard`. +/// +/// When the `SpanGuard` is dropped (or `.end()` is called on it), +/// the span will automatically be ended. +pub fn start_guard>(name: S) -> SpanGuard { + let name = name.into(); + start(name.clone()); + SpanGuard { name: Some(name), collapse: false } +} + +/// Starts and ends a `Span` that lasts for the duration of the +/// function `f`. +pub fn span_of(name: S, f: F) -> R where +S: Into, +F: FnOnce() -> R +{ + let name = name.into(); + start(name.clone()); + let r = f(); + end(name); + r +} + +/// Starts a new Span +pub fn start>(name: S) { + LIBRARY.with(|library| { + let mut library = library.borrow_mut(); + let epoch = library.epoch; + + let collector = &mut library.current; + let id = collector.next_id; + collector.next_id += 1; + + let this = Event { + id: id, + parent: collector.id_stack.last().cloned(), + name: name.into(), + collapse: false, + start_ns: ns_since_epoch(epoch), + end_ns: None, + delta: None, + notes: vec![] + }; + + collector.all.push(this); + collector.id_stack.push(id); + }); +} + +fn end_impl>(name: S, collapse: bool) -> u64 { + use std::thread; + + let name = name.into(); + let delta = LIBRARY.with(|library| { + let mut library = library.borrow_mut(); + let epoch = library.epoch; + let collector = &mut library.current; + + let current_id = match collector.id_stack.pop() { + Some(id) => id, + None if thread::panicking() => 0, + None => panic!("flame::end({:?}) called without a currently running span!", &name) + }; + + let event = &mut collector.all[current_id as usize]; + + if event.name != name { + panic!("flame::end({}) attempted to end {}", &name, event.name); + } + + let timestamp = ns_since_epoch(epoch); + event.end_ns = Some(timestamp); + event.collapse = collapse; + event.delta = Some(timestamp - event.start_ns); + event.delta + }); + + match delta { + Some(d) => d, + None => 0, // panicking + } +} + +/// Ends the current Span and returns the number +/// of nanoseconds that passed. +pub fn end>(name: S) -> u64 { + end_impl(name, false) +} + +/// Ends the current Span and returns a given result. +/// +/// This is mainly useful for code generation / plugins where +/// wrapping all returned expressions is easier than creating +/// a temporary variable to hold the result. +pub fn end_with, R>(name: S, result: R) -> R { + end_impl(name, false); + result +} + +/// Ends the current Span and returns the number of +/// nanoseconds that passed. +/// +/// If this span is a leaf node, and the previous span +/// has the same name and depth, then collapse this +/// span into the previous one. The end_ns field will +/// be updated to the end time of *this* span, and the +/// delta field will be the sum of the deltas from this +/// and the previous span. +/// +/// This means that it is possible for end_ns - start_n +/// to not be equal to delta. +pub fn end_collapse>(name: S) -> u64 { + end_impl(name, false) +} + +/// Records a note on the current Span. +pub fn note>(name: S, description: Option) { + let name = name.into(); + let description = description.map(Into::into); + + LIBRARY.with(|library| { + let mut library = library.borrow_mut(); + let epoch = library.epoch; + + let collector = &mut library.current; + + let current_id = match collector.id_stack.last() { + Some(id) => *id, + None => panic!("flame::note({}, {:?}) called without a currently running span!", + &name, &description) + }; + + let event = &mut collector.all[current_id as usize]; + event.notes.push(Note { + name: name, + description: description, + instant: ns_since_epoch(epoch), + _priv: () + }); + }); +} + +/// Clears all of the recorded info that Flame has +/// tracked. +pub fn clear() { + if ::std::thread::panicking() { return; } + LIBRARY.with(|library| { + let mut library = library.borrow_mut(); + library.current = PrivateFrame { + all: vec![], + id_stack: vec![], + next_id: 0, + }; + library.epoch = Instant::now(); + }); + + let mut handle = ALL_THREADS.lock().unwrap(); + handle.clear(); +} + +/// Returns a list of spans from the current thread +pub fn spans() -> Vec { + if ::std::thread::panicking() { return vec![]; } + LIBRARY.with(|library| { + let library = library.borrow(); + let cur = &library.current; + convert_events_to_span(cur.all.iter()) + }) +} + +pub fn threads() -> Vec { + if ::std::thread::panicking() { return vec![]; } + + let my_thread_name = ::std::thread::current().name().map(Into::into); + let my_thread_id = ::thread_id::get(); + + let mut out = vec![ Thread { + id: my_thread_id, + name: my_thread_name, + spans: spans(), + _priv: (), + }]; + + if let Ok(handle) = ALL_THREADS.lock() { + for &(id, ref name, ref frm) in &*handle { + out.push(Thread { + id: id, + name: name.clone(), + spans: convert_events_to_span(frm.all.iter()), + _priv: (), + }); + } + } + + out +} + +/// Prints all of the frames to stdout. +pub fn debug() { + if ::std::thread::panicking() { return; } + LIBRARY.with(|library| { + println!("{:?}", library); + }); +} + +pub fn dump_text_to_writer(mut out: W) -> Result<(), IoError> { + fn print_span(span: &Span, out: &mut W) -> Result { + let mut buf = String::new(); + for _ in 0 .. span.depth { + buf.push_str(" "); + } + buf.push_str("| "); + let ms = span.delta as f32 / 1000000.0; + buf.push_str(&format!("{}: {}ms", span.name, ms)); + writeln!(out, "{}", buf)?; + let mut missing = ms; + for child in &span.children { + missing -= print_span(child, out)?; + } + + if !span.children.is_empty() { + let mut buf = String::new(); + for _ in 0 .. (span.depth + 1) { + buf.push_str(" "); + } + buf.push_str("+ "); + buf.push_str(&format!("{}ms", missing)); + writeln!(out, "{}", buf)?; + } + + Ok(ms) + } + + for thread in threads() { + writeln!(out, "THREAD: {}", thread.id)?; + for span in thread.spans { + print_span(&span, &mut out)?; + } + writeln!(out, "")?; + } + Ok(()) +} + +pub fn dump_stdout() { + let stdout = ::std::io::stdout(); + let stdout = stdout.lock(); + dump_text_to_writer(stdout).unwrap(); +} + +#[cfg(feature="json")] +pub fn dump_json(out: &mut W) -> std::io::Result<()> { + out.write_all(serde_json::to_string_pretty(&threads()).unwrap().as_bytes()) +} + +pub use html::{dump_html, dump_html_custom};