use crate::circuit::{GlobalNodeId, RootCircuit, ThreadCpuTime, trace::SchedulerEvent};
use hashbrown::HashMap;
use std::{
cell::RefCell,
rc::Rc,
sync::{
Arc,
atomic::{AtomicU64, Ordering},
},
time::{Duration, Instant},
};
#[derive(Clone, Debug)]
pub struct RuntimeIdle {
base: Instant,
park_start: Arc<AtomicU64>,
total: Arc<AtomicU64>,
}
impl Default for RuntimeIdle {
fn default() -> Self {
Self::new()
}
}
impl RuntimeIdle {
pub fn new() -> Self {
Self {
base: Instant::now(),
park_start: Arc::new(AtomicU64::new(0)),
total: Arc::new(AtomicU64::new(0)),
}
}
fn now(&self) -> u64 {
self.base.elapsed().as_nanos() as u64
}
pub fn park(&self) {
self.park_start.store(self.now(), Ordering::Release);
}
pub fn unpark(&self) {
let start = self.park_start.swap(0, Ordering::AcqRel);
if start != 0 {
self.total
.fetch_add(self.now().saturating_sub(start), Ordering::Release);
}
}
pub fn total(&self) -> Duration {
Duration::from_nanos(self.total.load(Ordering::Acquire))
}
}
#[derive(Clone, Default, Debug)]
pub struct OperatorCPUProfile {
invocations: usize,
real_time: Duration,
cpu_time: Duration,
}
impl OperatorCPUProfile {
pub fn add_event(&mut self, real_time: Duration, cpu_time: Duration) {
self.invocations += 1;
self.real_time += real_time;
self.cpu_time += cpu_time;
}
pub fn invocations(&self) -> usize {
self.invocations
}
pub fn real_time(&self) -> Duration {
self.real_time
}
pub fn cpu_time(&self) -> Duration {
self.cpu_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 {
operators: HashMap<GlobalNodeId, OperatorCPUProfile>,
step_start_times: HashMap<GlobalNodeId, Instant>,
step_end_times: HashMap<GlobalNodeId, Instant>,
step_start_cpu: HashMap<GlobalNodeId, Duration>,
step_start_idle: HashMap<GlobalNodeId, Duration>,
circuit_profiles: HashMap<GlobalNodeId, CircuitCPUProfile>,
runtime_idle: Option<RuntimeIdle>,
}
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, Duration::ZERO);
};
self.step_start_times
.insert((*circuit_id).clone(), Instant::now());
self.step_start_cpu
.insert((*circuit_id).clone(), ThreadCpuTime::now().0);
if let Some(idle) = &self.runtime_idle {
self.step_start_idle
.insert((*circuit_id).clone(), idle.total());
}
}
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 cpu = self
.step_start_cpu
.remove(*circuit_id)
.map(|start| ThreadCpuTime::now().0.saturating_sub(start))
.unwrap_or_default();
let circuit_profile = self
.circuit_profiles
.entry((*circuit_id).clone())
.or_insert_with(Default::default);
circuit_profile.step_profile.add_event(duration, cpu);
if let (Some(idle), Some(before)) = (
self.runtime_idle.as_ref(),
self.step_start_idle.remove(*circuit_id),
) {
circuit_profile
.wait_profile
.add_event(idle.total().saturating_sub(before), Duration::ZERO);
}
};
self.step_end_times
.insert((*circuit_id).clone(), Instant::now());
}
SchedulerEvent::EvalStart { .. } => {}
SchedulerEvent::EvalEnd { node, elapsed_time } => {
let op_profile = self
.operators
.entry(node.global_id().clone())
.or_insert_with(Default::default);
op_profile.add_event(elapsed_time.real, elapsed_time.cpu);
}
_ => (),
}
}
}
#[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, runtime_idle: RuntimeIdle) {
if let Ok(mut this) = self.0.try_borrow_mut() {
this.runtime_idle = Some(runtime_idle);
}
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
}
}
}