#[cfg(feature = "fastrace")]
pub mod async_;
pub(crate) mod clock;
pub use self::clock::Nanos;
#[cfg(feature = "fastrace")]
pub(crate) mod fastrace;
#[cfg(feature = "instant")]
pub mod instant;
#[cfg(feature = "profile-with-puffin")]
pub(crate) mod puffin;
#[cfg(all(feature = "profile-with-superluminal", windows))]
pub(crate) mod superluminal;
#[cfg(feature = "profile-with-tracing")]
pub(crate) mod tracing_scope;
#[cfg(feature = "profile-with-tracy")]
pub(crate) mod tracy;
#[cfg(all(feature = "web", target_arch = "wasm32", target_os = "unknown"))]
pub(crate) mod web;
macro_rules! profiling_backend {
(
// `enabled` is ONE parenthesized cfg predicate (a single token tree),
$wrap:ident, $backend:ident, $guard:ident, enabled = $enabled:tt
$(, finish_frame = $finish_frame:ident)?
$(, on_enable = $on_enable:ident)?
$(,)?
) => {
#[doc(hidden)]
pub mod $wrap {
#[cfg $enabled]
pub use super::$backend::{dummy, enter, $($finish_frame,)? $($on_enable,)? $guard};
#[cfg(not $enabled)]
pub struct $guard;
#[cfg(not $enabled)]
pub const fn enter<'a>(_name: &'a str, _tag: Option<&'a str>) -> $guard {
$guard
}
#[cfg(not $enabled)]
pub const fn dummy() -> $guard {
$guard
}
$(
#[cfg(not $enabled)]
pub const fn $finish_frame() {}
)?
$(
#[cfg(not $enabled)]
pub const fn $on_enable() {}
)?
pub const AVAILABLE: bool = cfg! $enabled;
}
};
}
profiling_backend!(
instant_wrap,
instant,
InstantGuard,
enabled = (feature = "instant"),
finish_frame = finish_frame,
);
profiling_backend!(
fastrace_wrap,
fastrace,
FastraceGuard,
enabled = (feature = "fastrace"),
);
profiling_backend!(
puffin_wrap,
puffin,
PuffinGuard,
enabled = (feature = "profile-with-puffin"),
finish_frame = finish_frame,
on_enable = on_enable,
);
profiling_backend!(
tracy_wrap,
tracy,
TracyGuard,
enabled = (feature = "profile-with-tracy"),
);
profiling_backend!(
superluminal_wrap,
superluminal,
SuperluminalGuard,
enabled = (all(feature = "profile-with-superluminal", windows)),
);
profiling_backend!(
tracing_wrap,
tracing_scope,
TracingGuard,
enabled = (feature = "profile-with-tracing"),
);
#[doc(hidden)]
pub mod web_wrap {
#[cfg(all(feature = "web", target_arch = "wasm32", target_os = "unknown"))]
pub use super::web::{WebMarkGuard, dummy_mark, enter_mark};
#[cfg(not(all(feature = "web", target_arch = "wasm32", target_os = "unknown")))]
pub struct WebMarkGuard {
_not_send: std::marker::PhantomData<*const ()>,
}
#[cfg(not(all(feature = "web", target_arch = "wasm32", target_os = "unknown")))]
#[must_use]
pub const fn enter_mark(_name: &'static str) -> WebMarkGuard {
WebMarkGuard {
_not_send: std::marker::PhantomData,
}
}
#[cfg(not(all(feature = "web", target_arch = "wasm32", target_os = "unknown")))]
#[must_use]
pub const fn dummy_mark() -> WebMarkGuard {
WebMarkGuard {
_not_send: std::marker::PhantomData,
}
}
}
#[allow(
dead_code,
reason = "fields held only for Drop side effects; `_not_send` pins the guard to its creating thread"
)]
#[must_use = "a scope guard records on Drop — bind it (let _s = scope!(...)) or the span is zero-length"]
pub struct ScopeGuard {
instant: instant_wrap::InstantGuard,
web_mark: web_wrap::WebMarkGuard,
fastrace: fastrace_wrap::FastraceGuard,
puffin: puffin_wrap::PuffinGuard,
tracy: tracy_wrap::TracyGuard,
superluminal: superluminal_wrap::SuperluminalGuard,
tracing: tracing_wrap::TracingGuard,
_not_send: PhantomData<*const ()>,
}
impl ScopeGuard {
pub fn new_static(name: &'static str, tag: Option<&'static str>) -> Self {
use crate::config::Backends;
let mask = crate::config::config().backends();
macro_rules! backend_guard_field {
($(($field:ident, $backend:ident, $wrap:ident)),* $(,)?) => {
Self {
instant: if mask.contains(Backends::INSTANT) || mask.contains(Backends::WEB) {
instant_wrap::enter(name, tag)
} else {
instant_wrap::dummy()
},
web_mark: if mask.contains(Backends::WEB) {
web_wrap::enter_mark(name)
} else {
web_wrap::dummy_mark()
},
$($field: if mask.contains(Backends::$backend) {
$wrap::enter(name, tag)
} else {
$wrap::dummy()
},)*
_not_send: PhantomData,
}
};
}
backend_guard_field! {
(fastrace, FASTRACE, fastrace_wrap),
(puffin, PUFFIN, puffin_wrap),
(tracy, TRACY, tracy_wrap),
(superluminal, SUPERLUMINAL, superluminal_wrap),
(tracing, TRACING, tracing_wrap),
}
}
pub fn new(name: &str, tag: Option<&str>) -> Self {
Self::new_static(intern(name), tag.map(intern))
}
}
pub(crate) fn intern(s: &str) -> &'static str {
static INTERN: LazyLock<Mutex<HashSet<&'static str>>> =
LazyLock::new(|| Mutex::new(HashSet::new()));
let mut set = INTERN.lock();
if let Some(&existing) = set.get(s) {
return existing;
}
let leaked: &'static str = Box::leak(s.to_owned().into_boxed_str());
set.insert(leaked);
leaked
}
#[macro_export]
macro_rules! scope {
($name:expr) => {{
let _guard = $crate::profiling::ScopeGuard::new_static($name, None);
_guard
}};
($name:expr, $tag:expr) => {{
let _guard = $crate::profiling::ScopeGuard::new_static($name, Some($tag.as_ref()));
_guard
}};
}
#[cfg(feature = "fastrace")]
#[macro_export]
macro_rules! root_span {
($name:expr) => {{
let _root = $crate::profiling::async_::root_span($name);
_root
}};
($name:expr, $ctx:expr) => {{
let _root = $crate::profiling::async_::root_span_with($name, $ctx);
_root
}};
}
#[macro_export]
macro_rules! profiling {
() => {
let _func_scope = $crate::profiling::enter_function_scope(::std::borrow::Cow::Borrowed(
$crate::func_path!(),
));
};
($data:expr) => {
let _func_scope = $crate::profiling::enter_function_scope_with_tag(
::std::borrow::Cow::Borrowed($crate::func_path!()),
$data,
);
};
}
#[macro_export]
macro_rules! func_path {
() => {{
struct S;
let type_name = core::any::type_name::<S>();
&type_name[..type_name.len() - 3]
}};
}
#[macro_export]
macro_rules! function_scope {
() => {
$crate::profiling!()
};
($data:expr) => {
let _func_scope = $crate::profiling::enter_function_scope_with_tag(
::std::borrow::Cow::Borrowed($crate::func_path!()),
$data,
);
};
}
#[macro_export]
macro_rules! finish_frame {
() => {{
let mask = $crate::config::config().backends();
if mask.contains($crate::config::Backends::INSTANT)
|| mask.contains($crate::config::Backends::WEB)
{
$crate::profiling::instant_wrap::finish_frame();
}
if mask.contains($crate::config::Backends::PUFFIN) {
$crate::profiling::puffin_wrap::finish_frame();
}
}};
}
use std::borrow::Cow;
use std::cell::RefCell;
use std::collections::HashSet;
use std::marker::PhantomData;
use std::sync::LazyLock;
use parking_lot::Mutex;
use self::clock::Instant;
thread_local! {
pub(crate) static CURRENT_SCOPE: RefCell<Vec<(Cow<'static, str>, Instant)>> = const { RefCell::new(Vec::new()) };
static LAST_SCOPE_DIAGNOSTIC: RefCell<Option<Cow<'static, str>>> = const { RefCell::new(None) };
}
fn set_scope_diagnostic(name: Option<&str>) {
let changed = LAST_SCOPE_DIAGNOSTIC
.try_with(|cache| {
let mut cache = cache.borrow_mut();
if cache.as_deref() == name {
return false;
}
*cache = name.map(|n| Cow::Owned(n.to_owned()));
true
})
.unwrap_or(false);
if !changed {
return;
}
match name {
Some(n) => logforth::diagnostic::ThreadLocalDiagnostic::insert("scope", n),
None => logforth::diagnostic::ThreadLocalDiagnostic::remove("scope"),
}
}
#[must_use]
#[allow(
clippy::needless_pass_by_value,
reason = "the name is stored in the thread-local — taking by value avoids a double clone at the call site"
)]
pub fn enter_function_scope(name: Cow<'static, str>) -> FunctionScopeGuard {
CURRENT_SCOPE.with(|s| s.borrow_mut().push((name.clone(), Instant::now())));
set_scope_diagnostic(Some(name.as_ref()));
FunctionScopeGuard {
_not_send: PhantomData,
}
}
#[must_use]
#[allow(
clippy::needless_pass_by_value,
reason = "the name is stored in the thread-local — taking by value avoids a double clone at the call site"
)]
pub fn enter_function_scope_with_tag(
name: Cow<'static, str>,
tag: impl AsRef<str>,
) -> FunctionScopeGuard {
let full = Cow::Owned(format!("{}:{}", name, tag.as_ref()));
enter_function_scope(full)
}
pub struct FunctionScopeGuard {
_not_send: PhantomData<*const ()>,
}
impl Drop for FunctionScopeGuard {
fn drop(&mut self) {
let Ok(parent) = CURRENT_SCOPE.try_with(|s| {
let mut stack = s.borrow_mut();
stack.pop();
stack.last().map(|(name, _)| name.clone())
}) else {
return;
};
set_scope_diagnostic(parent.as_deref());
}
}
#[must_use]
pub fn current_scope_name() -> Option<Cow<'static, str>> {
CURRENT_SCOPE.with(|s| s.borrow().last().map(|(name, _)| name.clone()))
}
#[must_use]
pub fn scope_path() -> Vec<Cow<'static, str>> {
CURRENT_SCOPE.with(|s| s.borrow().iter().map(|(name, _)| name.clone()).collect())
}
#[must_use]
pub fn current_scope_elapsed_ms() -> Option<u128> {
CURRENT_SCOPE.with(|s| {
s.borrow()
.last()
.map(|(_, entered)| entered.elapsed().as_millis())
})
}
pub use fast_observe_macros::{all_functions, instrument, skip};
#[cfg(test)]
mod tests {
#[test]
fn intern_dedups_dynamic_names() {
let a = super::intern("same_name");
let b = super::intern("same_name");
assert!(
std::ptr::eq(a, b),
"same input must return the same pointer"
);
let c = super::intern("other_name");
assert!(!std::ptr::eq(a, c), "different inputs must not alias");
}
}