#![forbid(unsafe_code)]
#![warn(
clippy::all,
clippy::doc_markdown,
clippy::dbg_macro,
clippy::todo,
clippy::empty_enum,
clippy::enum_glob_use,
clippy::pub_enum_variant_names,
clippy::mem_forget,
clippy::filter_map_next,
clippy::needless_continue,
clippy::needless_borrow,
clippy::match_wildcard_for_single_variants,
clippy::if_let_mutex,
clippy::mismatched_target_os,
clippy::await_holding_lock,
clippy::match_on_vec_items,
clippy::imprecise_flops,
clippy::lossy_float_literal,
clippy::rest_pat_in_fully_bound_structs,
clippy::fn_params_excessive_bools,
clippy::exit,
clippy::inefficient_to_string,
clippy::linkedlist,
clippy::macro_use_imports,
clippy::option_option,
clippy::verbose_file_reads,
rust_2018_idioms,
future_incompatible,
nonstandard_style
)]
mod data;
mod merge;
pub use data::*;
pub use merge::*;
use std::collections::BTreeMap;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Mutex;
static MACROS_ON: AtomicBool = AtomicBool::new(false);
pub fn set_scopes_on(on: bool) {
MACROS_ON.store(on, Ordering::Relaxed);
}
pub fn are_scopes_on() -> bool {
MACROS_ON.load(Ordering::Relaxed)
}
pub type NanoSecond = i64;
#[derive(Clone, Default)]
pub struct Stream(Vec<u8>);
impl Stream {
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn bytes(&self) -> &[u8] {
&self.0
}
pub fn append(&mut self, mut other: Self) {
self.0.append(&mut other.0);
}
}
impl From<Vec<u8>> for Stream {
fn from(v: Vec<u8>) -> Self {
Self(v)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Record<'s> {
pub start_ns: NanoSecond,
pub duration_ns: NanoSecond,
pub id: &'s str,
pub location: &'s str,
pub data: &'s str,
}
impl<'s> Record<'s> {
pub fn stop_ns(&self) -> NanoSecond {
self.start_ns + self.duration_ns
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Scope<'s> {
pub record: Record<'s>,
pub child_begin_position: u64,
pub child_end_position: u64,
pub next_sibling_position: u64,
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct ThreadInfo {
pub start_time_ns: Option<NanoSecond>,
pub name: String,
}
#[derive(Clone, Default)]
pub struct FullProfileData(pub BTreeMap<ThreadInfo, Stream>);
impl FullProfileData {
pub fn range_ns(&self) -> Result<Option<(NanoSecond, NanoSecond)>> {
let mut min_ns = NanoSecond::MAX;
let mut max_ns = NanoSecond::MIN;
for stream in self.0.values() {
let top_scopes = Reader::from_start(stream).read_top_scopes()?;
if !top_scopes.is_empty() {
min_ns = min_ns.min(top_scopes.first().unwrap().record.start_ns);
max_ns = max_ns.max(top_scopes.last().unwrap().record.stop_ns());
}
}
Ok(if min_ns < max_ns {
Some((min_ns, max_ns))
} else {
None
})
}
pub fn duration_ns(&self) -> Result<NanoSecond> {
if let Some((min, max)) = self.range_ns()? {
Ok(max - min)
} else {
Ok(0)
}
}
pub fn insert(&mut self, info: ThreadInfo, stream: Stream) {
self.0.entry(info).or_default().append(stream);
}
}
type NsSource = fn() -> NanoSecond;
type ThreadReporter = fn(ThreadInfo, Stream);
pub fn global_reporter(info: ThreadInfo, stream: Stream) {
GlobalProfiler::lock().report(info, stream)
}
pub struct ThreadProfiler {
stream: Stream,
depth: usize,
now_ns: NsSource,
reporter: ThreadReporter,
start_time_ns: Option<NanoSecond>,
}
impl Default for ThreadProfiler {
fn default() -> Self {
Self {
stream: Default::default(),
depth: 0,
now_ns: crate::now_ns,
reporter: global_reporter,
start_time_ns: None,
}
}
}
impl ThreadProfiler {
pub fn initialize(now_ns: NsSource, reporter: ThreadReporter) {
ThreadProfiler::call(|tp| {
tp.now_ns = now_ns;
tp.reporter = reporter;
});
}
#[must_use]
pub fn begin_scope(&mut self, id: &str, location: &str, data: &str) -> usize {
let now_ns = (self.now_ns)();
self.start_time_ns = Some(self.start_time_ns.unwrap_or(now_ns));
self.depth += 1;
self.stream.begin_scope(now_ns, id, location, data)
}
pub fn end_scope(&mut self, start_offset: usize) {
if self.depth > 0 {
self.depth -= 1;
} else {
eprintln!("ERROR: Mismatched scope begin/end calls");
}
self.stream.end_scope(start_offset, (self.now_ns)());
if self.depth == 0 {
let info = ThreadInfo {
start_time_ns: self.start_time_ns,
name: std::thread::current().name().unwrap_or_default().to_owned(),
};
let stream = std::mem::take(&mut self.stream);
(self.reporter)(info, stream);
}
}
pub fn call<R>(f: impl Fn(&mut Self) -> R) -> R {
thread_local! {
pub static THREAD_PROFILER: std::cell::RefCell<ThreadProfiler> = Default::default();
}
THREAD_PROFILER.with(|p| f(&mut p.borrow_mut()))
}
}
#[derive(Default)]
pub struct GlobalProfiler {
spike_frame: FullProfileData,
past_frame: FullProfileData,
current_frame: FullProfileData,
}
impl GlobalProfiler {
pub fn lock() -> std::sync::MutexGuard<'static, Self> {
use once_cell::sync::Lazy;
static GLOBAL_PROFILER: Lazy<Mutex<GlobalProfiler>> = Lazy::new(Default::default);
GLOBAL_PROFILER.lock().unwrap()
}
pub fn new_frame(&mut self) {
self.past_frame = std::mem::take(&mut self.current_frame);
if self.past_frame.duration_ns().unwrap_or_default()
> self.spike_frame.duration_ns().unwrap_or_default()
{
self.spike_frame = self.past_frame.clone();
}
}
pub fn report(&mut self, info: ThreadInfo, stream: Stream) {
self.current_frame.insert(info, stream);
}
pub fn past_frame(&self) -> &FullProfileData {
&self.past_frame
}
pub fn spike_frame(&self) -> &FullProfileData {
&self.spike_frame
}
pub fn clear_spike_frame(&mut self) {
self.spike_frame = Default::default();
}
}
pub fn now_ns() -> NanoSecond {
use once_cell::sync::Lazy;
use std::time::Instant;
static START_TIME: Lazy<Instant> = Lazy::new(Instant::now);
START_TIME.elapsed().as_nanos() as NanoSecond
}
pub struct ProfilerScope {
start_stream_offset: usize,
_dont_send_me: std::marker::PhantomData<*const ()>,
}
impl ProfilerScope {
pub fn new(id: &'static str, location: &str, data: impl AsRef<str>) -> Self {
Self {
start_stream_offset: ThreadProfiler::call(|tp| {
tp.begin_scope(id, location, data.as_ref())
}),
_dont_send_me: Default::default(),
}
}
}
impl Drop for ProfilerScope {
fn drop(&mut self) {
ThreadProfiler::call(|tp| tp.end_scope(self.start_stream_offset))
}
}
#[doc(hidden)]
pub fn type_name_of<T>(_: T) -> &'static str {
std::any::type_name::<T>()
}
#[macro_export]
macro_rules! current_function_name {
() => {{
fn f() {}
let name = $crate::type_name_of(f);
let name = &name.get(..name.len() - 3).unwrap();
$crate::clean_function_name(name)
}};
}
#[doc(hidden)]
pub fn clean_function_name(name: &str) -> &str {
if let Some(colon) = name.rfind("::") {
if let Some(colon) = name[..colon].rfind("::") {
&name[colon + 2..]
} else {
name
}
} else {
name
}
}
#[test]
fn test_clean_function_name() {
assert_eq!(clean_function_name(""), "");
assert_eq!(clean_function_name("foo"), "foo");
assert_eq!(clean_function_name("foo::bar"), "foo::bar");
assert_eq!(clean_function_name("foo::bar::baz"), "bar::baz");
}
#[macro_export]
macro_rules! current_file_name {
() => {
$crate::short_file_name(file!())
};
}
#[doc(hidden)]
pub fn short_file_name(name: &str) -> &str {
if let Some(slash) = name.rfind('/') {
&name[slash + 1..]
} else {
name
}
}
#[test]
fn test_short_file_name() {
assert_eq!(short_file_name(""), "");
assert_eq!(short_file_name("foo.rs"), "foo.rs");
assert_eq!(short_file_name("foo/bar.rs"), "bar.rs");
assert_eq!(short_file_name("foo/bar/baz.rs"), "baz.rs");
}
#[macro_export]
macro_rules! profile_function {
() => {
$crate::profile_function!("");
};
($data:expr) => {
let _profiler_scope = if $crate::are_scopes_on() {
Some($crate::ProfilerScope::new(
$crate::current_function_name!(),
$crate::current_file_name!(),
$data,
))
} else {
None
};
};
}
#[deprecated = "Use puffin::profile_function!(data); instead"]
#[macro_export]
macro_rules! profile_function_data {
($data:expr) => {
$crate::profile_function($data);
};
}
#[macro_export]
macro_rules! profile_scope {
($id:expr) => {
$crate::profile_scope!($id, "");
};
($id:expr, $data:expr) => {
let _profiler_scope = if $crate::are_scopes_on() {
Some($crate::ProfilerScope::new(
$id,
$crate::current_file_name!(),
$data,
))
} else {
None
};
};
}
#[deprecated = "Use puffin::profile_scope!(id, data) instead"]
#[macro_export]
macro_rules! profile_scope_data {
($id:expr, $data:expr) => {
$crate::profile_scope_function($id, $data);
};
}