#![allow(deprecated)]
use std::sync::Arc;
use crate::api::stats::AllocStats;
use crate::sync::mutex::Mutex;
use super::snapshot::{AllocatorSnapshot, SnapshotHistory};
#[derive(Debug, Clone)]
pub enum DiagnosticsEvent {
FrameBegin { frame_number: u64 },
FrameEnd { frame_number: u64 },
LargeAllocation { size: usize, tag: Option<&'static str> },
MemoryPressure { current: usize, limit: usize },
SlabRefill { size_class: usize, count: usize },
DeferredFree { count: usize },
BudgetWarning { tag: &'static str, current: usize, limit: usize },
BudgetExceeded { tag: &'static str, current: usize, limit: usize },
}
pub struct DiagnosticsHooks {
enabled: bool,
frame_number: u64,
history: SnapshotHistory,
listeners: Vec<Box<dyn Fn(&DiagnosticsEvent) + Send + Sync>>,
data_provider: Option<Arc<dyn DiagnosticsProvider + Send + Sync>>,
}
impl DiagnosticsHooks {
pub fn new() -> Self {
Self {
enabled: true,
frame_number: 0,
history: SnapshotHistory::default(),
listeners: Vec::new(),
data_provider: None,
}
}
pub fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
}
pub fn is_enabled(&self) -> bool {
self.enabled
}
pub fn add_listener<F>(&mut self, listener: F)
where
F: Fn(&DiagnosticsEvent) + Send + Sync + 'static,
{
self.listeners.push(Box::new(listener));
}
pub fn set_provider<P>(&mut self, provider: P)
where
P: DiagnosticsProvider + Send + Sync + 'static,
{
self.data_provider = Some(Arc::new(provider));
}
pub fn emit(&self, event: DiagnosticsEvent) {
if !self.enabled {
return;
}
for listener in &self.listeners {
listener(&event);
}
}
pub fn on_frame_begin(&mut self) {
self.frame_number += 1;
self.emit(DiagnosticsEvent::FrameBegin {
frame_number: self.frame_number,
});
}
pub fn on_frame_end(&mut self, stats: &AllocStats) {
self.emit(DiagnosticsEvent::FrameEnd {
frame_number: self.frame_number,
});
if let Some(ref provider) = self.data_provider {
let snapshot = provider.take_snapshot(self.frame_number);
self.history.push(snapshot);
}
let _ = stats; }
pub fn history(&self) -> &SnapshotHistory {
&self.history
}
pub fn history_mut(&mut self) -> &mut SnapshotHistory {
&mut self.history
}
pub fn frame_number(&self) -> u64 {
self.frame_number
}
pub fn get_memory_graph_data(&self, max_points: usize) -> MemoryGraphData {
let timeline = self.history.memory_timeline();
let peak_timeline = self.history.peak_timeline();
let step = if timeline.len() > max_points {
timeline.len() / max_points
} else {
1
};
MemoryGraphData {
current: timeline.iter().step_by(step).map(|&(_, v)| v).collect(),
peak: peak_timeline.iter().step_by(step).map(|&(_, v)| v).collect(),
frames: timeline.iter().step_by(step).map(|&(f, _)| f).collect(),
}
}
}
impl Default for DiagnosticsHooks {
fn default() -> Self {
Self::new()
}
}
pub trait DiagnosticsProvider {
fn take_snapshot(&self, frame_number: u64) -> AllocatorSnapshot;
fn get_stats(&self) -> AllocStats;
}
#[derive(Debug, Clone, Default)]
pub struct MemoryGraphData {
pub current: Vec<usize>,
pub peak: Vec<usize>,
pub frames: Vec<u64>,
}
impl MemoryGraphData {
pub fn max_value(&self) -> usize {
self.peak.iter().copied().max().unwrap_or(0)
}
pub fn normalized_current(&self) -> Vec<f32> {
let max = self.max_value() as f32;
if max == 0.0 {
return vec![0.0; self.current.len()];
}
self.current.iter().map(|&v| v as f32 / max).collect()
}
}
pub struct SharedDiagnostics {
inner: Mutex<DiagnosticsHooks>,
}
impl SharedDiagnostics {
pub fn new() -> Self {
Self {
inner: Mutex::new(DiagnosticsHooks::new()),
}
}
pub fn with<F, R>(&self, f: F) -> R
where
F: FnOnce(&mut DiagnosticsHooks) -> R,
{
let mut hooks = self.inner.lock();
f(&mut hooks)
}
pub fn emit(&self, event: DiagnosticsEvent) {
let hooks = self.inner.lock();
hooks.emit(event);
}
}
impl Default for SharedDiagnostics {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicUsize, Ordering};
#[test]
fn test_event_emission() {
let mut hooks = DiagnosticsHooks::new();
let counter = Arc::new(AtomicUsize::new(0));
let counter_clone = counter.clone();
hooks.add_listener(move |_event| {
counter_clone.fetch_add(1, Ordering::Relaxed);
});
hooks.emit(DiagnosticsEvent::FrameBegin { frame_number: 1 });
hooks.emit(DiagnosticsEvent::FrameEnd { frame_number: 1 });
assert_eq!(counter.load(Ordering::Relaxed), 2);
}
#[test]
fn test_disabled_hooks() {
let mut hooks = DiagnosticsHooks::new();
hooks.set_enabled(false);
let counter = Arc::new(AtomicUsize::new(0));
let counter_clone = counter.clone();
hooks.add_listener(move |_event| {
counter_clone.fetch_add(1, Ordering::Relaxed);
});
hooks.emit(DiagnosticsEvent::FrameBegin { frame_number: 1 });
assert_eq!(counter.load(Ordering::Relaxed), 0);
}
}