Skip to main content

gpui_platform/
profiler.rs

1//! Profiler hooks used by the executor runtime.
2//!
3//! Platform backends bracket each dispatched runnable with
4//! [`update_running_task`] and [`save_task_timing`]. The executor runtime in
5//! this crate has no notion of task timing, so the framework installs its own
6//! implementation through [`set_task_profiler`]; until then the calls are
7//! no-ops.
8
9use std::sync::{
10    Arc, OnceLock,
11    atomic::{AtomicUsize, Ordering},
12};
13
14/// Tracks how many foreground runnables are queued on the current thread.
15///
16/// The executor increments this when it queues a runnable on the main thread and
17/// the profiler decrements it when that runnable is polled, so the journal can
18/// distinguish an idle foreground from one with outstanding work.
19#[derive(Clone)]
20pub struct ForegroundRunnableCounter(Arc<AtomicUsize>);
21
22impl ForegroundRunnableCounter {
23    /// Creates a counter with no queued runnables.
24    pub fn new() -> Self {
25        Self(Arc::new(AtomicUsize::new(0)))
26    }
27
28    /// Records that a runnable was queued.
29    pub fn queued(&self) {
30        self.0.fetch_add(1, Ordering::Release);
31    }
32
33    /// Records that a queued runnable finished.
34    pub fn finished(&self) {
35        let _ = self
36            .0
37            .fetch_update(Ordering::AcqRel, Ordering::Acquire, |count| {
38                count.checked_sub(1)
39            });
40    }
41
42    /// Whether any runnables are currently queued.
43    pub fn has_runnables(&self) -> bool {
44        self.0.load(Ordering::Acquire) > 0
45    }
46}
47
48impl Default for ForegroundRunnableCounter {
49    fn default() -> Self {
50        Self::new()
51    }
52}
53
54thread_local! {
55    static FOREGROUND_RUNNABLES: ForegroundRunnableCounter = ForegroundRunnableCounter::new();
56}
57
58/// Returns the current thread's foreground runnable counter.
59pub fn foreground_runnable_counter() -> ForegroundRunnableCounter {
60    FOREGROUND_RUNNABLES.with(Clone::clone)
61}
62
63/// Records that a queued foreground runnable finished on the current thread.
64pub fn foreground_runnable_finished() {
65    FOREGROUND_RUNNABLES.with(ForegroundRunnableCounter::finished);
66}
67
68/// Signature for recording that a runnable began polling.
69pub type UpdateRunningTaskFn = fn(scheduler::SpawnTime, &'static std::panic::Location<'static>);
70
71/// Signature for recording that the running runnable yielded.
72pub type SaveTaskTimingFn = fn();
73
74struct TaskProfilerHooks {
75    update_running_task: UpdateRunningTaskFn,
76    save_task_timing: SaveTaskTimingFn,
77}
78
79static TASK_PROFILER_HOOKS: OnceLock<TaskProfilerHooks> = OnceLock::new();
80
81/// Installs the framework's task profiler hooks.
82///
83/// Only the first call takes effect. Until a profiler is installed,
84/// [`update_running_task`] and [`save_task_timing`] do nothing.
85pub fn set_task_profiler(
86    update_running_task: UpdateRunningTaskFn,
87    save_task_timing: SaveTaskTimingFn,
88) {
89    if TASK_PROFILER_HOOKS
90        .set(TaskProfilerHooks {
91            update_running_task,
92            save_task_timing,
93        })
94        .is_err()
95    {
96        log::debug!("task profiler hooks were already installed");
97    }
98}
99
100/// Records that a runnable began polling.
101///
102/// Platform backends call this immediately before running a dispatched runnable.
103#[doc(hidden)]
104#[inline]
105pub fn update_running_task(
106    spawned: scheduler::SpawnTime,
107    location: &'static std::panic::Location<'static>,
108) {
109    if let Some(hooks) = TASK_PROFILER_HOOKS.get() {
110        (hooks.update_running_task)(spawned, location);
111    }
112}
113
114/// Records that the running runnable yielded.
115///
116/// Platform backends call this immediately after running a dispatched runnable.
117#[doc(hidden)]
118#[inline]
119pub fn save_task_timing() {
120    if let Some(hooks) = TASK_PROFILER_HOOKS.get() {
121        (hooks.save_task_timing)();
122    }
123}