830 lines · 25.8 KB · Rust
RawDownload
use itertools::Itertools;use scheduler::{Instant, SpawnTime};use std::{    cell::LazyCell,    collections::{HashMap, VecDeque},    hash::{DefaultHasher, Hash, Hasher},    sync::{        Arc,        atomic::{AtomicBool, Ordering},    },    thread::ThreadId,    time::Duration,}; mod actions;pub use actions::{ActionStatistics, ActionTiming, take_action_stats};pub(crate) use actions::{save_action_timing, update_running_action}; use serde::{Deserialize, Serialize}; use crate::{SharedString, TasksIncluded, WindowId}; #[cfg(feature = "profiler")]#[doc(hidden)]pub fn get_all_timings(included: gpui::TasksIncluded) -> Vec<gpui::ThreadTaskTimings> {    ThreadTaskTimings::collect(upgraded_thread_timings(), included)} #[cfg(feature = "profiler")]#[doc(hidden)]pub fn get_current_thread_timings(included: TasksIncluded) -> gpui::ThreadTaskTimings {    gpui::profiler::get_current_thread_task_timings(included)} #[cfg(feature = "profiler")]#[doc(hidden)]pub fn take_all_stats(included: TasksIncluded) -> Vec<gpui::ThreadTaskStatistics> {    ThreadTaskStatistics::collect_and_reset(upgraded_thread_timings(), included)} #[cfg(not(feature = "profiler"))]#[doc(hidden)]pub fn get_all_timings(_included: gpui::TasksIncluded) -> Vec<gpui::ThreadTaskTimings> {    Vec::new()}#[cfg(not(feature = "profiler"))]#[doc(hidden)]pub fn get_current_thread_timings(_included: TasksIncluded) -> gpui::ThreadTaskTimings {    gpui::ThreadTaskTimings {        thread_name: None,        thread_id: std::thread::current().id(),        timings: Vec::new(),        stats: TaskStatistics::default(),        total_pushed: 0,    }}#[cfg(not(feature = "profiler"))]#[doc(hidden)]pub fn take_all_stats(_included: TasksIncluded) -> Vec<gpui::ThreadTaskStatistics> {    Vec::new()} #[doc(hidden)]#[derive(Debug, Copy, Clone)]pub struct YieldTime(pub Instant); #[doc(hidden)]#[derive(Copy, Clone)]pub struct TaskTiming {    pub location: &'static core::panic::Location<'static>,    pub spawned: SpawnTime,    pub start: Instant,    pub end: YieldTime,} impl std::fmt::Debug for TaskTiming {    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {        f.debug_struct("TaskTiming")            .field("location", &self.location)            .field("since_spawned", &self.spawned.0.elapsed())            .field("last_poll_duration", &self.poll_duration())            .field("total_runtime", &self.since_spawn())            .finish()    }} #[doc(hidden)]#[derive(Debug, Copy, Clone)]pub struct ActiveTiming {    pub location: &'static core::panic::Location<'static>,    pub spawned: SpawnTime,    pub start: Instant,} impl TaskTiming {    /// A task timing with a duration of zero. Any task will replace this in history.    pub fn placeholder() -> Self {        let now = Instant::now();        Self {            location: std::panic::Location::caller(),            spawned: SpawnTime(now),            start: now,            end: YieldTime(now),        }    }     #[inline(always)]    pub fn poll_duration(&self) -> Duration {        self.end.0 - self.start    }     #[inline(always)]    fn since_spawn(&self) -> Duration {        self.end.0 - self.spawned.0    }} #[doc(hidden)]#[derive(Debug, Clone)]pub struct ThreadTaskTimings {    pub thread_name: Option<String>,    pub thread_id: ThreadId,    pub timings: Vec<TaskTiming>,    pub stats: TaskStatistics,    pub total_pushed: u64,} impl ThreadTaskTimings {    /// Convert upgraded per-thread timings into their structured format.    pub fn collect(        timings: Vec<(ThreadId, Arc<GuardedTaskTimings>)>,        included: TasksIncluded,    ) -> Vec<Self> {        timings            .into_iter()            .map(|(thread_id, timings)| {                let timings = timings.lock();                let thread_name = timings.thread_name.clone();                let total_pushed = timings.total_pushed;                let completed = &timings.timings;                 let mut vec = Vec::with_capacity(completed.len() + 1); // +1 for running task                let (s1, s2) = completed.as_slices();                vec.extend_from_slice(s1);                vec.extend_from_slice(s2);                if let TasksIncluded::CompletedAndRunning = included                    && let Some(running) = timings.running                {                    vec.push(TaskTiming {                        location: running.location,                        spawned: running.spawned,                        start: running.start,                        end: YieldTime(Instant::now()),                    })                }                 ThreadTaskTimings {                    thread_name,                    thread_id,                    timings: vec,                    stats: timings.stats.clone(),                    total_pushed,                }            })            .collect()    }} #[doc(hidden)]#[derive(Debug)]pub struct ThreadTaskStatistics {    pub thread_name: Option<String>,    pub thread_id: ThreadId,    pub stats: TaskStatistics,} impl ThreadTaskStatistics {    pub fn collect_and_reset(        timings: Vec<(ThreadId, Arc<GuardedTaskTimings>)>,        include_running: TasksIncluded,    ) -> Vec<Self> {        timings            .into_iter()            .map(|(thread_id, timings)| {                let mut timings = timings.lock();                let thread_name = timings.thread_name.clone();                 let mut stats = std::mem::take(&mut timings.stats);                if let TasksIncluded::CompletedAndRunning = include_running                    && let Some(ActiveTiming {                        location,                        spawned,                        start,                    }) = timings.running                {                    let end = YieldTime(Instant::now());                    let timing = TaskTiming {                        location,                        spawned,                        start,                        end,                    };                    stats.add_runtime(timing);                    stats.add_yield_timing(timing);                }                 Self {                    thread_name,                    thread_id,                    stats,                }            })            .collect()    }} /// Serializable variant of [`core::panic::Location`]#[derive(Debug, Clone, Serialize, Deserialize)]pub struct SerializedLocation {    /// Name of the source file    pub file: SharedString,    /// Line in the source file    pub line: u32,    /// Column in the source file    pub column: u32,} impl From<&core::panic::Location<'static>> for SerializedLocation {    fn from(value: &core::panic::Location<'static>) -> Self {        SerializedLocation {            file: value.file().into(),            line: value.line(),            column: value.column(),        }    }} /// Serializable variant of [`TaskTiming`]#[derive(Debug, Clone, Serialize, Deserialize)]pub struct SerializedTaskTiming {    /// Location of the timing    pub location: SerializedLocation,    /// Time at which the measurement was reported in nanoseconds    pub start: u128,    /// Duration of the measurement in nanoseconds    pub duration: u128,} impl SerializedTaskTiming {    /// Convert an array of [`TaskTiming`] into their serializable format    ///    /// # Params    ///    /// `anchor` - [`Instant`] that should be earlier than all timings to use as base anchor    pub fn convert(anchor: Instant, timings: &[TaskTiming]) -> Vec<SerializedTaskTiming> {        let serialized = timings            .iter()            .map(|timing| {                let start = timing.start.duration_since(anchor).as_nanos();                let duration = timing.end.0.duration_since(timing.start).as_nanos();                SerializedTaskTiming {                    location: timing.location.into(),                    start,                    duration,                }            })            .collect::<Vec<_>>();         serialized    }     /// `anchor` - [`Instant`] that should be earlier than all timings to use as base anchor    pub fn from(anchor: Instant, timing: TaskTiming) -> SerializedTaskTiming {        let start = timing.start.duration_since(anchor).as_nanos();        let duration = timing.end.0.duration_since(timing.start).as_nanos();        SerializedTaskTiming {            location: timing.location.into(),            start,            duration,        }    }} /// Serializable variant of [`ThreadTaskTimings`]#[derive(Debug, Clone, Serialize, Deserialize)]pub struct SerializedThreadTaskTimings {    /// Thread name    pub thread_name: Option<String>,    /// Hash of the thread id    pub thread_id: u64,    /// Timing records for this thread    pub timings: Vec<SerializedTaskTiming>,} impl SerializedThreadTaskTimings {    /// Convert [`ThreadTaskTimings`] into their serializable format    ///    /// # Params    ///    /// `anchor` - [`Instant`] that should be earlier than all timings to use as base anchor    pub fn convert(anchor: Instant, timings: ThreadTaskTimings) -> SerializedThreadTaskTimings {        let serialized_timings = SerializedTaskTiming::convert(anchor, &timings.timings);         let mut hasher = DefaultHasher::new();        timings.thread_id.hash(&mut hasher);        let thread_id = hasher.finish();         SerializedThreadTaskTimings {            thread_name: timings.thread_name,            thread_id,            timings: serialized_timings,        }    }} #[doc(hidden)]#[derive(Debug, Clone)]pub struct ThreadTimingsDelta {    /// Hashed thread id    pub thread_id: u64,    /// Thread name, if known    pub thread_name: Option<String>,    /// New timings since the last call. If the circular buffer wrapped around    /// since the previous poll, some entries may have been lost.    pub new_timings: Vec<SerializedTaskTiming>,} /// Tracks which timing events have already been seen so that callers can request only unseen events.#[doc(hidden)]pub struct ProfilingCollector {    startup_time: Instant,    cursors: HashMap<ThreadId, u64>,} impl ProfilingCollector {    pub fn new(startup_time: Instant) -> Self {        Self {            startup_time,            cursors: HashMap::default(),        }    }     pub fn startup_time(&self) -> Instant {        self.startup_time    }     pub fn collect_unseen(        &mut self,        all_timings: Vec<ThreadTaskTimings>,    ) -> Vec<ThreadTimingsDelta> {        let mut deltas = Vec::with_capacity(all_timings.len());         for thread in all_timings {            let mut hasher = DefaultHasher::new();            thread.thread_id.hash(&mut hasher);            let hashed_id = hasher.finish();             let prev_cursor = self.cursors.get(&thread.thread_id).copied().unwrap_or(0);            let buffer_len = thread.timings.len() as u64;            let buffer_start = thread.total_pushed.saturating_sub(buffer_len);             let mut slice = if prev_cursor < buffer_start {                // Cursor fell behind the buffer — some entries were evicted.                // Return everything still in the buffer.                thread.timings.as_slice()            } else {                let skip = (prev_cursor - buffer_start) as usize;                &thread.timings[skip.min(thread.timings.len())..]            };             let cursor_advance = thread.total_pushed;            self.cursors.insert(thread.thread_id, cursor_advance);             if slice.is_empty() {                continue;            }             let new_timings = SerializedTaskTiming::convert(self.startup_time, slice);             deltas.push(ThreadTimingsDelta {                thread_id: hashed_id,                thread_name: thread.thread_name,                new_timings,            });        }         deltas    }     pub fn reset(&mut self) {        self.cursors.clear();    }} // Allow 16MiB of task timing entries.// VecDeque grows by doubling its capacity when full, so keep this a power of 2 to avoid wasting// memory.#[cfg(feature = "profiler")]const MAX_TASK_TIMINGS: usize = (16 * 1024 * 1024) / core::mem::size_of::<TaskTiming>(); #[doc(hidden)]pub(crate) type TaskTimings = VecDeque<TaskTiming>; #[doc(hidden)]pub type GuardedTaskTimings = spin::Mutex<ThreadTimings>; #[doc(hidden)]pub struct GlobalThreadTimings {    pub thread_id: ThreadId,    pub timings: std::sync::Weak<GuardedTaskTimings>,} #[doc(hidden)]#[derive(Debug, Clone)]pub struct TaskStatistics {    pub poll_time_to_beat: Duration,    pub runtime_to_beat: Duration,    pub longest_poll_times: [TaskTiming; 5],    pub longest_runtimes: [TaskTiming; 5],} impl std::fmt::Display for TaskStatistics {    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {        f.write_str("Tasks that blocked the longest before yielding\n")?;        for timing in self.longest_poll_times {            f.write_fmt(format_args!(                "{:<20} - {}:{}\n",                format!("{:?}", timing.poll_duration()),                timing.location.file(),                timing.location.column()            ))?;        }        f.write_str("Tasks that ran the longest\n")?;        for timing in self.longest_runtimes {            f.write_fmt(format_args!(                "{:<20} - {}:{}\n",                format!("{:?}", timing.since_spawn()),                timing.location.file(),                timing.location.column()            ))?;        }        Ok(())    }} impl Default for TaskStatistics {    fn default() -> Self {        Self {            // Do not track polls that are not problematic            // this keeps more calls on the fast path            poll_time_to_beat: Duration::from_micros(100),            runtime_to_beat: Duration::from_micros(100),            longest_poll_times: [TaskTiming::placeholder(); 5],            longest_runtimes: [TaskTiming::placeholder(); 5],        }    }} impl TaskStatistics {    #[inline(always)]    fn add_yield_timing(&mut self, task: TaskTiming) {        let yielded_after = task.poll_duration();        if yielded_after >= self.poll_time_to_beat {            std::hint::cold_path(); // most tasks are not the worst, optimize for that            let to_replace = self                .longest_poll_times                .iter()                .position_min_by_key(|task| task.since_spawn())                .expect("guarded by the comparison with nth_longest_yield_time");            self.longest_poll_times[to_replace] = task;             self.poll_time_to_beat = self                .longest_poll_times                .iter()                .map(|task| task.since_spawn())                .min()                .expect("never empty");        }    }     #[inline(always)]    fn add_runtime(&mut self, task: TaskTiming) {        let runtime = task.since_spawn();        if runtime >= self.runtime_to_beat {            std::hint::cold_path(); // most tasks are not the worst, optimize for that            let to_replace = self                .longest_runtimes                .iter()                .position_min_by_key(|task| task.since_spawn())                .expect("guarded by the comparison with nth_longest_yield_time");            self.longest_runtimes[to_replace] = task;             self.runtime_to_beat = self                .longest_runtimes                .iter()                .map(|task| task.since_spawn())                .min()                .expect("never empty");        }    }

Lines 1–500 of 830.