Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/gpui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ screen-capture = [
]
windows-manifest = ["dep:embed-resource"]
input-latency-histogram = ["dep:hdrhistogram"]
profiler = []

[lib]
path = "src/gpui.rs"
Expand Down
1 change: 1 addition & 0 deletions crates/gpui/src/action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@ impl ActionRegistry {
Ok(self.build_action(name, None)?)
}

#[cfg(feature = "profiler")]
pub(crate) fn try_resolve_action(&self, type_id: &TypeId) -> Option<&'static str> {
self.names_by_type_id.get(type_id).copied()
}
Expand Down
39 changes: 35 additions & 4 deletions crates/gpui/src/profiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ use std::{
cell::LazyCell,
collections::{HashMap, VecDeque},
hash::{DefaultHasher, Hash, Hasher},
hint::cold_path,
sync::{
Arc,
atomic::{AtomicBool, Ordering},
Expand All @@ -21,23 +20,48 @@ use serde::{Deserialize, Serialize};

use crate::{SharedString, TasksIncluded};

#[cfg(feature = "profiler")]
#[doc(hidden)]
pub fn get_all_timings(included: gpui::TasksIncluded) -> Vec<gpui::ThreadTaskTimings> {
let global_thread_timings = GLOBAL_THREAD_TIMINGS.lock();
ThreadTaskTimings::collect(&global_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> {
let global_timings = GLOBAL_THREAD_TIMINGS.lock();
ThreadTaskStatistics::collect_and_reset(&global_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);
Expand Down Expand Up @@ -378,6 +402,7 @@ impl ProfilingCollector {
// 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)]
Expand Down Expand Up @@ -443,7 +468,7 @@ impl TaskStatistics {
fn add_yield_timing(&mut self, task: TaskTiming) {
let yielded_after = task.poll_duration();
if yielded_after >= self.poll_time_to_beat {
cold_path(); // most tasks are not the worst, optimize for that
std::hint::cold_path(); // most tasks are not the worst, optimize for that
let to_replace = self
.longest_poll_times
.iter()
Expand All @@ -464,7 +489,7 @@ impl TaskStatistics {
fn add_runtime(&mut self, task: TaskTiming) {
let runtime = task.since_spawn();
if runtime >= self.runtime_to_beat {
cold_path(); // most tasks are not the worst, optimize for that
std::hint::cold_path(); // most tasks are not the worst, optimize for that
let to_replace = self
.longest_runtimes
.iter()
Expand Down Expand Up @@ -530,6 +555,7 @@ impl ThreadTimings {
}
}

#[cfg(feature = "profiler")]
pub fn update_running_task(
&mut self,
spawned: SpawnTime,
Expand All @@ -542,7 +568,10 @@ impl ThreadTimings {
start,
});
}
#[cfg(not(feature = "profiler"))]
pub fn update_running_task(&mut self, _: SpawnTime, _: &'static std::panic::Location<'_>) {}

#[cfg(feature = "profiler")]
pub fn save_task_timing(&mut self, ended: YieldTime) {
let ActiveTiming {
location,
Expand All @@ -563,14 +592,16 @@ impl ThreadTimings {
self.stats.add_runtime(timing);

if trace_enabled() {
cold_path(); // optimize for when the profiling is off
std::hint::cold_path(); // optimize for when the profiling is off
if self.timings.len() >= MAX_TASK_TIMINGS {
self.timings.pop_front();
}
self.timings.push_back(timing);
self.total_pushed += 1;
}
}
#[cfg(not(feature = "profiler"))]
pub fn save_task_timing(&mut self, _: YieldTime) {}

// Running tasks are included in the reliability trace, which is written
// whenever the foreground executor makes no progress for > n seconds
Expand Down
33 changes: 27 additions & 6 deletions crates/gpui/src/profiler/actions.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
use std::{
hint::cold_path,
time::{Duration, Instant},
};
use std::time::{Duration, Instant};

use itertools::Itertools;

Expand Down Expand Up @@ -75,10 +72,14 @@ impl ActionStatistics {
self.longest_runtimes.is_empty()
}

#[cfg(feature = "profiler")]
pub fn update_running_action(&mut self, action: &'static str, started: Instant) {
self.running = Some((action, started));
}
#[cfg(not(feature = "profiler"))]
pub fn update_running_action(&mut self, _action: &'static str, _started: Instant) {}

#[cfg(feature = "profiler")]
pub fn save_action_timing(&mut self) {
let now = Instant::now();

Expand All @@ -89,13 +90,13 @@ impl ActionStatistics {
// When ran sequentially self.running will always be Some. When ran
// concurrently that is no longer true. But that is fine, we do not
// need to track action timings in tests.
cold_path();
std::hint::cold_path();
return;
};

let runtime = now.duration_since(started);
if runtime >= self.runtime_to_beat {
cold_path(); // most actions are not the worst, optimize for that
std::hint::cold_path(); // most actions are not the worst, optimize for that

if self.longest_runtimes.is_full()
&& let Some(to_replace) = self
Expand Down Expand Up @@ -126,6 +127,8 @@ impl ActionStatistics {
.expect("never empty");
}
}
#[cfg(not(feature = "profiler"))]
pub fn save_action_timing(&mut self) {}

pub fn longest_runtimes(&self, include_running: bool) -> impl Iterator<Item = ActionTiming> {
self.longest_runtimes.iter().copied().chain(
Expand Down Expand Up @@ -174,10 +177,12 @@ impl ActionTiming {

// The profiler is careful to never block when the lock is held, therefore a
// spinlock is optimal.
#[cfg(feature = "profiler")]
static ACTION_STATISTICS: spin::Mutex<ActionStatistics> =
const { spin::Mutex::new(ActionStatistics::new()) };

#[doc(hidden)]
#[cfg(feature = "profiler")]
pub(crate) fn update_running_action(action: &(dyn Action + 'static), cx: &mut crate::App) {
let now = Instant::now();
let action = action.type_id();
Expand All @@ -186,11 +191,27 @@ pub(crate) fn update_running_action(action: &(dyn Action + 'static), cx: &mut cr
}

#[doc(hidden)]
#[cfg(not(feature = "profiler"))]
pub(crate) fn update_running_action(_: &(dyn Action + 'static), _: &mut crate::App) {}

#[doc(hidden)]
#[cfg(feature = "profiler")]
pub(crate) fn save_action_timing() {
ACTION_STATISTICS.lock().save_action_timing();
}

#[doc(hidden)]
#[cfg(not(feature = "profiler"))]
pub(crate) fn save_action_timing() {}

#[doc(hidden)]
#[cfg(feature = "profiler")]
pub fn take_action_stats() -> ActionStatistics {
ACTION_STATISTICS.lock().take()
}

#[doc(hidden)]
#[cfg(not(feature = "profiler"))]
pub fn take_action_stats() -> ActionStatistics {
ActionStatistics::default()
}
1 change: 1 addition & 0 deletions crates/gpui/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use crate::{
WindowOptions, WindowParams, WindowTextSystem, point, prelude::*, profiler, px, rems, size,
transparent_black,
};

use anyhow::{Context as _, Result, anyhow};
use collections::{FxHashMap, FxHashSet};
#[cfg(target_os = "macos")]
Expand Down
Loading