use std::borrow::Cow;
use crate::GlobalProfiler;
use crate::NanoSecond;
use crate::NsSource;
use crate::fetch_add_scope_id;
use crate::ScopeDetails;
use crate::ScopeId;
use crate::StreamInfo;
use crate::StreamInfoRef;
pub(crate) fn internal_profile_reporter(
info: ThreadInfo,
scope_details: &[ScopeDetails],
stream_scope_times: &StreamInfoRef<'_>,
) {
GlobalProfiler::lock().report(info, scope_details, stream_scope_times);
}
pub struct ThreadProfiler {
stream_info: StreamInfo,
scope_details: Vec<ScopeDetails>,
depth: usize,
now_ns: NsSource,
reporter: ThreadReporter,
start_time_ns: Option<NanoSecond>,
}
impl Default for ThreadProfiler {
fn default() -> Self {
Self {
stream_info: Default::default(),
scope_details: Default::default(),
depth: 0,
now_ns: crate::now_ns,
reporter: internal_profile_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 register_function_scope(
&mut self,
function_name: impl Into<Cow<'static, str>>,
file_path: impl Into<Cow<'static, str>>,
line_nr: u32,
) -> ScopeId {
let new_id = fetch_add_scope_id();
self.scope_details.push(
ScopeDetails::from_scope_id(new_id)
.with_function_name(function_name)
.with_file(file_path)
.with_line_nr(line_nr),
);
new_id
}
#[must_use]
pub fn register_named_scope(
&mut self,
scope_name: impl Into<Cow<'static, str>>,
function_name: impl Into<Cow<'static, str>>,
file_path: impl Into<Cow<'static, str>>,
line_nr: u32,
) -> ScopeId {
let new_id = fetch_add_scope_id();
self.scope_details.push(
ScopeDetails::from_scope_id(new_id)
.with_scope_name(scope_name)
.with_function_name(function_name)
.with_file(file_path)
.with_line_nr(line_nr),
);
new_id
}
#[must_use]
pub fn begin_scope(&mut self, scope_id: ScopeId, data: &str) -> usize {
self.depth += 1;
let (offset, start_ns) = self
.stream_info
.stream
.begin_scope(self.now_ns, scope_id, data);
self.stream_info.range_ns.0 = self.stream_info.range_ns.0.min(start_ns);
self.start_time_ns = Some(self.start_time_ns.unwrap_or(start_ns));
offset
}
pub fn end_scope(&mut self, start_offset: usize) {
let now_ns = (self.now_ns)();
self.stream_info.depth = self.stream_info.depth.max(self.depth);
self.stream_info.num_scopes += 1;
self.stream_info.range_ns.1 = self.stream_info.range_ns.1.max(now_ns);
if self.depth > 0 {
self.depth -= 1;
} else {
eprintln!("puffin ERROR: Mismatched scope begin/end calls");
}
self.stream_info.stream.end_scope(start_offset, 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(),
};
(self.reporter)(
info,
&self.scope_details,
&self.stream_info.as_stream_into_ref(),
);
self.scope_details.clear();
self.stream_info.clear();
}
}
#[inline]
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(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct ThreadInfo {
pub start_time_ns: Option<NanoSecond>,
pub name: String,
}
type ThreadReporter = fn(ThreadInfo, &[ScopeDetails], &StreamInfoRef<'_>);