use crate::circuit::{GlobalNodeId, RootCircuit, trace::SchedulerEvent};
use hashbrown::HashMap;
use std::{
cell::RefCell,
rc::Rc,
time::{Duration, Instant},
};
#[derive(Clone, Default, Debug)]
pub struct OperatorCPUProfile {
invocations: usize,
total_time: Duration,
}
impl OperatorCPUProfile {
pub fn add_event(&mut self, duration: Duration) {
self.invocations += 1;
self.total_time += duration;
}
pub fn invocations(&self) -> usize {
self.invocations
}
pub fn total_time(&self) -> Duration {
self.total_time
}
}
#[derive(Clone, Default, Debug)]
pub struct CircuitCPUProfile {
pub wait_profile: OperatorCPUProfile,
pub step_profile: OperatorCPUProfile,
pub idle_profile: OperatorCPUProfile,
}
#[derive(Default, Debug)]
struct CPUProfilerInner {
start_times: HashMap<GlobalNodeId, Instant>,
operators: HashMap<GlobalNodeId, OperatorCPUProfile>,
wait_start_times: HashMap<GlobalNodeId, Instant>,
step_start_times: HashMap<GlobalNodeId, Instant>,
step_end_times: HashMap<GlobalNodeId, Instant>,
circuit_profiles: HashMap<GlobalNodeId, CircuitCPUProfile>,
}
impl CPUProfilerInner {
fn scheduler_event(&mut self, event: &SchedulerEvent) {
match event {
SchedulerEvent::StepStart { circuit_id } => {
if let Some(end_time) = self.step_end_times.remove(*circuit_id) {
let duration = Instant::now().duration_since(end_time);
let circuit_profile = self
.circuit_profiles
.entry((*circuit_id).clone())
.or_insert_with(Default::default);
circuit_profile.idle_profile.add_event(duration);
};
self.step_start_times
.insert((*circuit_id).clone(), Instant::now());
}
SchedulerEvent::StepEnd { circuit_id } => {
if let Some(start_time) = self.step_start_times.remove(*circuit_id) {
let duration = Instant::now().duration_since(start_time);
let circuit_profile = self
.circuit_profiles
.entry((*circuit_id).clone())
.or_insert_with(Default::default);
circuit_profile.step_profile.add_event(duration);
};
self.step_end_times
.insert((*circuit_id).clone(), Instant::now());
}
SchedulerEvent::EvalStart { node } => {
self.start_times
.insert(node.global_id().clone(), Instant::now());
}
SchedulerEvent::EvalEnd { node } => {
if let Some(start_time) = self.start_times.remove(node.global_id()) {
let duration = Instant::now().duration_since(start_time);
let op_profile = self
.operators
.entry(node.global_id().clone())
.or_insert_with(Default::default);
op_profile.add_event(duration);
};
}
SchedulerEvent::WaitStart { circuit_id } => {
self.wait_start_times
.insert((*circuit_id).clone(), Instant::now());
}
SchedulerEvent::WaitEnd { circuit_id } => {
if let Some(start_time) = self.wait_start_times.remove(*circuit_id) {
let duration = Instant::now().duration_since(start_time);
let circuit_profile = self
.circuit_profiles
.entry((*circuit_id).clone())
.or_insert_with(Default::default);
circuit_profile.wait_profile.add_event(duration);
};
}
_ => (),
}
}
}
#[repr(transparent)]
#[derive(Clone, Default, Debug)]
pub struct CPUProfiler(Rc<RefCell<CPUProfilerInner>>);
impl CPUProfiler {
pub fn new() -> Self {
Self::default()
}
pub fn attach(&self, circuit: &RootCircuit, handler_name: &str) {
let self_clone = self.clone();
circuit.register_scheduler_event_handler(handler_name, move |event| {
if let Ok(mut this) = self_clone.0.try_borrow_mut() {
this.scheduler_event(event);
};
});
}
pub fn operator_profile(&self, node: &GlobalNodeId) -> Option<OperatorCPUProfile> {
if let Ok(this) = self.0.try_borrow() {
this.operators.get(node).cloned()
} else {
None
}
}
pub fn circuit_profile(&self, node: &GlobalNodeId) -> Option<CircuitCPUProfile> {
if let Ok(this) = self.0.try_borrow() {
this.circuit_profiles.get(node).cloned()
} else {
None
}
}
}