Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T04:47:54.136Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

actions.rs

218 lines · 6.5 KB · rust
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
Served at tenant.openagents/omega Member data and write actions are omitted.