use std::collections::HashMap;
use std::sync::{Mutex, OnceLock};
use puffin::{ScopeId, ThreadProfiler};
static SCOPE_IDS: OnceLock<Mutex<HashMap<String, ScopeId>>> = OnceLock::new();
#[must_use = "bind the guard so the scope stays open for the block"]
pub struct ScopeGuard(Option<usize>);
pub fn scope(name: &str) -> ScopeGuard {
if !puffin::are_scopes_on() {
return ScopeGuard(None);
}
let ids = SCOPE_IDS.get_or_init(|| Mutex::new(HashMap::new()));
let mut map = ids.lock().unwrap();
let id = *map
.entry(name.to_owned())
.or_insert_with(|| ThreadProfiler::call(|tp| tp.register_function_scope(name.to_owned(), "", 0)));
drop(map);
let offset = ThreadProfiler::call(|tp| tp.begin_scope(id, ""));
ScopeGuard(Some(offset))
}
impl Drop for ScopeGuard {
fn drop(&mut self) {
if let Some(offset) = self.0 {
ThreadProfiler::call(|tp| tp.end_scope(offset));
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn dynamic_scope_lands_in_the_flame_collection() {
crate::enable();
let view = puffin::GlobalFrameView::default();
{
let _s = super::scope("py::train_step");
std::thread::sleep(std::time::Duration::from_micros(50));
}
crate::frame_mark();
let view = view.lock();
let found = view
.scope_collection()
.scopes_by_id()
.values()
.any(|d| d.name().as_ref() == "py::train_step");
assert!(found, "runtime scope name should reach the flame collection");
}
}