use alloc::boxed::Box;
use cubecl_common::profile::{Duration, Instant, ProfileDuration, ProfileTicks};
use cubecl_environment::backtrace::BackTrace;
use cubecl_environment::collections::HashMap;
use cubecl_environment::sync::Arc;
use crate::device_events::{Event, EventApi, EventPool, Pooled};
use crate::driver::DriverError;
use crate::server::{ProfileError, ProfilingToken, ServerError};
const ANCHOR_MAX_AGE: Duration = Duration::from_secs(1);
const UNREADABLE: Duration = Duration::from_secs(3600);
pub struct EventProfiler<A: EventApi> {
open: HashMap<ProfilingToken, Result<Open<A>, ProfileError>>,
counter: u64,
pool: EventPool<A>,
anchoring: Option<Anchoring<A>>,
}
struct Open<A: EventApi> {
start: Pooled<A>,
anchor: Anchor<A>,
}
impl<A: EventApi> EventProfiler<A> {
pub fn start(&mut self, stream: A::Stream) -> Result<ProfilingToken, ServerError> {
let anchor = self.anchor()?;
let start = self.pool.acquire()?;
start.record(stream)?;
let token = ProfilingToken { id: self.counter };
self.counter += 1;
self.open.insert(token, Ok(Open { start, anchor }));
Ok(token)
}
pub fn stop(
&mut self,
stream: A::Stream,
token: ProfilingToken,
) -> Result<ProfileDuration, ProfileError> {
let Open { start, anchor } = match self.open.remove(&token) {
Some(state) => state?,
None => {
return Err(ProfileError::NotRegistered {
backtrace: BackTrace::capture(),
});
}
};
let end = self.pool.acquire().map_err(profile_error)?;
end.record(stream).map_err(profile_error)?;
Ok(ProfileDuration::new_device_time(async move {
read::<A>(&start, &end, &anchor).unwrap_or_else(|err| {
log::error!(
"Could not read back a {} profiling window ({err}); reporting {UNREADABLE:?} \
so nothing mistakes it for a fast one",
A::BACKEND
);
let now = Instant::now();
ProfileTicks::from_start_end(now, now + UNREADABLE)
})
}))
}
pub fn abandon(&mut self, token: ProfilingToken) {
self.open.remove(&token);
}
pub fn failure(&mut self, error: &ServerError) {
if self.open.is_empty() {
return;
}
let error = ProfileError::from(error);
self.open
.values_mut()
.for_each(|state| *state = Err(error.clone()));
}
fn anchor(&mut self) -> Result<Anchor<A>, DriverError> {
if self.anchoring.is_none() {
self.anchoring = Some(Anchoring::new(&self.pool)?);
}
let anchoring = self.anchoring.as_mut().expect("filled right above");
if anchoring.current.instant.elapsed() > ANCHOR_MAX_AGE {
anchoring.current = Anchor::take(anchoring.stream, &self.pool)?;
}
Ok(anchoring.current.clone())
}
}
impl<A: EventApi> Default for EventProfiler<A> {
fn default() -> Self {
Self {
open: HashMap::default(),
counter: 0,
pool: EventPool::default(),
anchoring: None,
}
}
}
impl<A: EventApi> core::fmt::Debug for EventProfiler<A> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("EventProfiler")
.field("backend", &A::BACKEND)
.field("open", &self.open.len())
.field("anchored", &self.anchoring.is_some())
.finish()
}
}
fn read<A: EventApi>(
start: &Event<A>,
end: &Event<A>,
anchor: &Anchor<A>,
) -> Result<ProfileTicks, DriverError> {
start.wait()?;
end.wait()?;
let offset = anchor.event.elapsed(start)?;
let span = start.elapsed(end)?;
let start_instant = anchor.instant + offset;
Ok(ProfileTicks::from_start_end(
start_instant,
start_instant + span,
))
}
struct Anchor<A: EventApi> {
event: Arc<Pooled<A>>,
instant: Instant,
}
impl<A: EventApi> Anchor<A> {
fn take(stream: A::Stream, pool: &EventPool<A>) -> Result<Self, DriverError> {
let event = pool.acquire()?;
event.record(stream)?;
event.wait()?;
Ok(Self {
event: Arc::new(event),
instant: Instant::now(),
})
}
}
impl<A: EventApi> Clone for Anchor<A> {
fn clone(&self) -> Self {
Self {
event: self.event.clone(),
instant: self.instant,
}
}
}
struct Anchoring<A: EventApi> {
stream: A::Stream,
current: Anchor<A>,
}
impl<A: EventApi> Anchoring<A> {
fn new(pool: &EventPool<A>) -> Result<Self, DriverError> {
let stream = A::stream_create_non_blocking()?;
let current = Anchor::take(stream, pool)?;
Ok(Self { stream, current })
}
}
impl<A: EventApi> Drop for Anchoring<A> {
fn drop(&mut self) {
if let Err(err) = A::stream_destroy(self.stream) {
log::warn!(
"Failed to release the {} profiling anchor stream: {err}",
A::BACKEND
);
}
}
}
fn profile_error(error: DriverError) -> ProfileError {
ProfileError::Server(Box::new(ServerError::from(error)))
}