#[cfg(feature = "tracy")]
pub use tracy_client;
pub trait TracyIntegration {
fn tracy_frame_mark(&self);
fn tracy_alloc(&self, ptr: *const u8, size: usize, name: &str);
fn tracy_free(&self, ptr: *const u8);
}
#[derive(Debug, Clone)]
pub enum MemoryEvent {
Alloc {
ptr: usize,
size: usize,
tag: Option<&'static str>,
},
Free {
ptr: usize,
},
FrameMark {
frame_number: u64,
},
ZoneBegin {
name: &'static str,
},
ZoneEnd,
}
pub type ProfilerCallback = Box<dyn Fn(MemoryEvent) + Send + Sync>;
pub struct ProfilerHooks {
callback: Option<ProfilerCallback>,
enabled: bool,
}
impl ProfilerHooks {
pub fn new() -> Self {
Self {
callback: None,
enabled: false,
}
}
pub fn set_callback<F>(&mut self, callback: F)
where
F: Fn(MemoryEvent) + Send + Sync + 'static,
{
self.callback = Some(Box::new(callback));
self.enabled = true;
}
pub fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
}
pub fn is_enabled(&self) -> bool {
self.enabled && self.callback.is_some()
}
pub fn emit_alloc(&self, ptr: *const u8, size: usize, tag: Option<&'static str>) {
if let Some(ref callback) = self.callback {
if self.enabled {
callback(MemoryEvent::Alloc {
ptr: ptr as usize,
size,
tag,
});
}
}
}
pub fn emit_free(&self, ptr: *const u8) {
if let Some(ref callback) = self.callback {
if self.enabled {
callback(MemoryEvent::Free {
ptr: ptr as usize,
});
}
}
}
pub fn emit_frame_mark(&self, frame_number: u64) {
if let Some(ref callback) = self.callback {
if self.enabled {
callback(MemoryEvent::FrameMark { frame_number });
}
}
}
pub fn emit_zone_begin(&self, name: &'static str) {
if let Some(ref callback) = self.callback {
if self.enabled {
callback(MemoryEvent::ZoneBegin { name });
}
}
}
pub fn emit_zone_end(&self) {
if let Some(ref callback) = self.callback {
if self.enabled {
callback(MemoryEvent::ZoneEnd);
}
}
}
}
impl Default for ProfilerHooks {
fn default() -> Self {
Self::new()
}
}
pub struct ProfilerZone<'a> {
hooks: &'a ProfilerHooks,
}
impl<'a> ProfilerZone<'a> {
pub fn new(hooks: &'a ProfilerHooks, name: &'static str) -> Self {
hooks.emit_zone_begin(name);
Self { hooks }
}
}
impl<'a> Drop for ProfilerZone<'a> {
fn drop(&mut self) {
self.hooks.emit_zone_end();
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
#[test]
fn test_profiler_hooks() {
let mut hooks = ProfilerHooks::new();
let counter = Arc::new(AtomicUsize::new(0));
let counter_clone = counter.clone();
hooks.set_callback(move |_event| {
counter_clone.fetch_add(1, Ordering::Relaxed);
});
hooks.emit_alloc(std::ptr::null(), 100, None);
hooks.emit_free(std::ptr::null());
hooks.emit_frame_mark(1);
assert_eq!(counter.load(Ordering::Relaxed), 3);
}
#[test]
fn test_disabled_hooks() {
let mut hooks = ProfilerHooks::new();
let counter = Arc::new(AtomicUsize::new(0));
let counter_clone = counter.clone();
hooks.set_callback(move |_event| {
counter_clone.fetch_add(1, Ordering::Relaxed);
});
hooks.set_enabled(false);
hooks.emit_alloc(std::ptr::null(), 100, None);
assert_eq!(counter.load(Ordering::Relaxed), 0);
}
}