dfir_rs/scheduled/
metrics.rs1use std::cell::Cell;
4use std::pin::Pin;
5use std::rc::Rc;
6use std::task::{Context, Poll};
7
8use dfir_lang::graph_ids::{GraphNodeId, GraphSubgraphId};
9use pin_project_lite::pin_project;
10use slotmap::SecondaryMap;
11use web_time::{Duration, Instant};
12
13#[derive(Default, Clone)]
19#[non_exhaustive]
20pub struct DfirMetrics {
21 pub subgraphs: SecondaryMap<GraphSubgraphId, SubgraphMetrics>,
23 pub handoffs: SecondaryMap<GraphNodeId, HandoffMetrics>,
25}
26
27impl DfirMetrics {
28 pub(super) fn diff(&mut self, other: &Self) {
30 for (sg_id, prev_sg_metrics) in other.subgraphs.iter() {
31 if let Some(curr_sg_metrics) = self.subgraphs.get_mut(sg_id) {
32 curr_sg_metrics.diff(prev_sg_metrics);
33 }
34 }
35 for (handoff_id, prev_handoff_metrics) in other.handoffs.iter() {
36 if let Some(curr_handoff_metrics) = self.handoffs.get_mut(handoff_id) {
37 curr_handoff_metrics.diff(prev_handoff_metrics);
38 }
39 }
40 }
41}
42
43#[derive(Clone)]
52pub struct DfirMetricsIntervals {
53 pub(super) curr: Rc<DfirMetrics>,
55 pub(super) prev: Option<DfirMetrics>,
57}
58
59impl DfirMetricsIntervals {
60 pub fn take_interval(&mut self) -> DfirMetrics {
65 let mut curr = self.curr.as_ref().clone();
66 if let Some(prev) = self.prev.replace(curr.clone()) {
67 curr.diff(&prev);
68 }
69 curr
70 }
71
72 pub fn all_metrics(&self) -> Rc<DfirMetrics> {
76 Rc::clone(&self.curr)
77 }
78}
79
80macro_rules! define_metrics {
82 (
83 $(#[$struct_attr:meta])*
84 pub struct $struct_name:ident {
85 $(
86 $( #[doc = $doc:literal] )*
87 #[diff($diff:ident)]
88 $( #[$field_attr:meta] )*
89 $field_name:ident: Cell<$field_type:ty>,
90 )*
91 }
92 ) => {
93 $(#[$struct_attr])*
94 #[derive(Default, Debug, Clone)]
95 #[non_exhaustive] pub struct $struct_name {
97 $(
98 #[doc(hidden)] $(#[$field_attr])*
100 pub $field_name: Cell<$field_type>,
101 )*
102 }
103
104 impl $struct_name {
105 $(
106 $( #[doc = $doc] )*
107 pub fn $field_name(&self) -> $field_type {
108 self.$field_name.get()
109 }
110 )*
111
112 fn diff(&mut self, other: &Self) {
113 $(
114 define_metrics_diff_field!($diff, $field_name, self, other);
115 )*
116 }
117 }
118 };
119}
120
121macro_rules! define_metrics_diff_field {
122 (total, $field:ident, $slf:ident, $other:ident) => {
123 debug_assert!($other.$field.get() <= $slf.$field.get());
124 $slf.$field.update(|x| x - $other.$field.get());
125 };
126 (curr, $field:ident, $slf:ident, $other:ident) => {};
127}
128
129define_metrics! {
130 pub struct HandoffMetrics {
132 #[diff(curr)]
134 curr_items_count: Cell<usize>,
135
136 #[diff(total)]
138 total_items_count: Cell<usize>,
139 }
140}
141
142define_metrics! {
143 pub struct SubgraphMetrics {
145 #[diff(total)]
147 total_run_count: Cell<usize>,
148
149 #[diff(total)]
151 total_poll_duration: Cell<Duration>,
152
153 #[diff(total)]
155 total_poll_count: Cell<usize>,
156
157 #[diff(total)]
159 total_idle_duration: Cell<Duration>,
160
161 #[diff(total)]
163 total_idle_count: Cell<usize>,
164 }
165}
166
167pin_project! {
168 #[doc(hidden)]
170 pub struct InstrumentSubgraph<'a, Fut> {
171 #[pin]
172 future: Fut,
173 idle_start: Option<Instant>,
174 metrics: &'a SubgraphMetrics,
175 }
176}
177
178impl<'a, Fut> InstrumentSubgraph<'a, Fut> {
179 pub fn new(future: Fut, metrics: &'a SubgraphMetrics) -> Self {
181 Self {
182 future,
183 idle_start: None,
184 metrics,
185 }
186 }
187}
188
189impl<'a, Fut> Future for InstrumentSubgraph<'a, Fut>
190where
191 Fut: Future,
192{
193 type Output = Fut::Output;
194 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
195 let this = self.project();
196
197 if let Some(idle_start) = this.idle_start {
199 this.metrics
200 .total_idle_duration
201 .update(|x| x + idle_start.elapsed());
202 this.metrics.total_idle_count.update(|x| x + 1);
203 }
204
205 let poll_start = Instant::now();
207 let out = this.future.poll(cx);
208
209 this.metrics
211 .total_poll_duration
212 .update(|x| x + poll_start.elapsed());
213 this.metrics.total_poll_count.update(|x| x + 1);
214
215 this.idle_start.replace(Instant::now());
217
218 out
219 }
220}
221
222#[cfg(test)]
223mod test {
224 use dfir_lang::graph_ids::{GraphNodeId, GraphSubgraphId};
225 use slotmap::SlotMap;
226
227 use super::*;
228
229 #[test]
230 fn test_dfir_metrics_intervals() {
231 let mut sg_map: SlotMap<GraphSubgraphId, ()> = SlotMap::with_key();
233 let mut node_map: SlotMap<GraphNodeId, ()> = SlotMap::with_key();
234 let sg_id = sg_map.insert(());
235 let handoff_id = node_map.insert(());
236
237 let mut metrics = DfirMetrics::default();
238 metrics.subgraphs.insert(
239 sg_id,
240 SubgraphMetrics {
241 total_run_count: Cell::new(5),
242 total_poll_count: Cell::new(10),
243 total_idle_count: Cell::new(2),
244 total_poll_duration: Cell::new(Duration::from_millis(500)),
245 total_idle_duration: Cell::new(Duration::from_millis(200)),
246 },
247 );
248 metrics.handoffs.insert(
249 handoff_id,
250 HandoffMetrics {
251 curr_items_count: Cell::new(3),
252 total_items_count: Cell::new(100),
253 },
254 );
255 let metrics = Rc::new(metrics);
256
257 let mut intervals = DfirMetricsIntervals {
258 curr: Rc::clone(&metrics),
259 prev: None,
260 };
261
262 let first = intervals.take_interval();
264 let sg_metrics = &first.subgraphs[sg_id];
265 assert_eq!(sg_metrics.total_run_count(), 5);
266 let hoff_metrics = &first.handoffs[handoff_id];
267 assert_eq!(hoff_metrics.total_items_count(), 100);
268 assert_eq!(hoff_metrics.curr_items_count(), 3);
269
270 let sg_metrics = &metrics.subgraphs[sg_id];
272 sg_metrics.total_run_count.set(12);
273 sg_metrics.total_poll_count.set(25);
274 sg_metrics.total_idle_count.set(7);
275 sg_metrics
276 .total_poll_duration
277 .set(Duration::from_millis(1200));
278 sg_metrics
279 .total_idle_duration
280 .set(Duration::from_millis(600));
281 let hoff_metrics = &metrics.handoffs[handoff_id];
282 hoff_metrics.total_items_count.set(250);
283 hoff_metrics.curr_items_count.set(10);
284
285 let second = intervals.take_interval();
287 let sg_metrics = &second.subgraphs[sg_id];
288 assert_eq!(sg_metrics.total_run_count(), 7); assert_eq!(sg_metrics.total_poll_count(), 15); assert_eq!(sg_metrics.total_idle_count(), 5); let hoff_metrics = &second.handoffs[handoff_id];
293 assert_eq!(hoff_metrics.total_items_count(), 150); assert_eq!(hoff_metrics.curr_items_count(), 10);
297 }
298}