Skip to repository content218 lines · 6.5 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T05:54:41.209Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
actions.rs
1use std::time::{Duration, Instant};
2
3use itertools::Itertools;
4
5use crate::action::Action;
6
7#[doc(hidden)]
8#[derive(Clone)]
9pub struct ActionStatistics {
10 runtime_to_beat: Duration,
11
12 longest_runtimes: heapless::Vec<ActionTiming, 5>,
13 running: Option<(&'static str, Instant)>,
14}
15
16impl std::fmt::Debug for ActionStatistics {
17 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18 f.debug_struct("ActionStatistics")
19 .field("runtime_to_beat", &self.runtime_to_beat)
20 .field("longest_runtimes", &self.longest_runtimes)
21 .field(
22 "running",
23 &self.running.map(|(id, started)| (id, started.elapsed())),
24 )
25 .finish()
26 }
27}
28
29impl std::fmt::Display for ActionStatistics {
30 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31 f.write_str("Actions that blocked the longest\n")?;
32 for action in self
33 .longest_runtimes(true)
34 .sorted_by_key(|action| action.runtime())
35 .rev()
36 {
37 f.write_fmt(format_args!(
38 "{:<20} - {}",
39 format!("{:?}", action.runtime()), // impl dbg does not support alignment
40 action.name
41 ))?;
42 writeln!(f)?;
43 }
44 Ok(())
45 }
46}
47
48impl Default for ActionStatistics {
49 fn default() -> Self {
50 Self::new()
51 }
52}
53
54impl ActionStatistics {
55 const fn new() -> Self {
56 Self {
57 // This keeps more calls on the fast path by only tracking
58 // problematic polls
59 runtime_to_beat: Duration::from_micros(100),
60 longest_runtimes: heapless::Vec::new(),
61 running: None,
62 }
63 }
64
65 pub fn take(&mut self) -> Self {
66 let taken = std::mem::take(self);
67 self.running = taken.running;
68 taken
69 }
70
71 pub fn is_empty(&self) -> bool {
72 self.longest_runtimes.is_empty()
73 }
74
75 #[cfg(feature = "profiler")]
76 pub fn update_running_action(&mut self, action: &'static str, started: Instant) {
77 self.running = Some((action, started));
78 }
79 #[cfg(not(feature = "profiler"))]
80 pub fn update_running_action(&mut self, _action: &'static str, _started: Instant) {}
81
82 #[cfg(feature = "profiler")]
83 pub fn save_action_timing(&mut self) {
84 let now = Instant::now();
85
86 let Some((action, started)) = self.running.take() else {
87 // Actions are ran only on the foreground executor and therefore
88 // sequentially _except_ in tests where they can run concurrently.
89 //
90 // When ran sequentially self.running will always be Some. When ran
91 // concurrently that is no longer true. But that is fine, we do not
92 // need to track action timings in tests.
93 std::hint::cold_path();
94 return;
95 };
96
97 let runtime = now.duration_since(started);
98 if runtime >= self.runtime_to_beat {
99 std::hint::cold_path(); // most actions are not the worst, optimize for that
100
101 if self.longest_runtimes.is_full()
102 && let Some(to_replace) = self
103 .longest_runtimes
104 .iter_mut()
105 .min_by_key(|action| runtime >= action.runtime())
106 {
107 *to_replace = ActionTiming {
108 name: action,
109 start: started,
110 end: now,
111 };
112 } else {
113 self.longest_runtimes
114 .push(ActionTiming {
115 name: action,
116 start: started,
117 end: now,
118 })
119 .expect("just checked it is not full");
120 };
121
122 self.runtime_to_beat = self
123 .longest_runtimes
124 .iter()
125 .map(|action| action.runtime())
126 .min()
127 .expect("never empty");
128 }
129 }
130 #[cfg(not(feature = "profiler"))]
131 pub fn save_action_timing(&mut self) {}
132
133 pub fn longest_runtimes(&self, include_running: bool) -> impl Iterator<Item = ActionTiming> {
134 self.longest_runtimes.iter().copied().chain(
135 self.running
136 .into_iter()
137 .filter(move |_| include_running)
138 .map(|(name, start)| ActionTiming {
139 name,
140 start,
141 end: Instant::now(),
142 }),
143 )
144 }
145}
146
147#[doc(hidden)]
148/// UNSTABLE only for use in the profiler and zed-reliability
149#[derive(Copy, Clone)]
150pub struct ActionTiming {
151 pub name: &'static str,
152 pub start: Instant,
153 pub end: Instant,
154}
155
156impl core::fmt::Debug for ActionTiming {
157 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158 f.debug_struct("ActionTiming")
159 .field("name", &self.name)
160 .field("runtime", &self.runtime())
161 .finish()
162 }
163}
164
165impl ActionTiming {
166 pub fn duration(&self) -> Duration {
167 self.end.saturating_duration_since(self.start)
168 }
169}
170
171impl ActionTiming {
172 #[doc(hidden)]
173 pub fn runtime(&self) -> Duration {
174 self.end - self.start
175 }
176}
177
178// The profiler is careful to never block when the lock is held, therefore a
179// spinlock is optimal.
180#[cfg(feature = "profiler")]
181static ACTION_STATISTICS: spin::Mutex<ActionStatistics> =
182 const { spin::Mutex::new(ActionStatistics::new()) };
183
184#[doc(hidden)]
185#[cfg(feature = "profiler")]
186pub(crate) fn update_running_action(action: &(dyn Action + 'static), cx: &mut crate::App) {
187 let now = Instant::now();
188 let action = action.type_id();
189 let action = cx.actions.try_resolve_action(&action).unwrap_or("un-named");
190 ACTION_STATISTICS.lock().update_running_action(action, now);
191}
192
193#[doc(hidden)]
194#[cfg(not(feature = "profiler"))]
195pub(crate) fn update_running_action(_: &(dyn Action + 'static), _: &mut crate::App) {}
196
197#[doc(hidden)]
198#[cfg(feature = "profiler")]
199pub(crate) fn save_action_timing() {
200 ACTION_STATISTICS.lock().save_action_timing();
201}
202
203#[doc(hidden)]
204#[cfg(not(feature = "profiler"))]
205pub(crate) fn save_action_timing() {}
206
207#[doc(hidden)]
208#[cfg(feature = "profiler")]
209pub fn take_action_stats() -> ActionStatistics {
210 ACTION_STATISTICS.lock().take()
211}
212
213#[doc(hidden)]
214#[cfg(not(feature = "profiler"))]
215pub fn take_action_stats() -> ActionStatistics {
216 ActionStatistics::default()
217}
218