xmtp_mls/worker/
metrics.rs1use futures::FutureExt;
2use parking_lot::Mutex;
3use std::{
4 collections::HashMap,
5 fmt::Debug,
6 future::Future,
7 hash::Hash,
8 pin::Pin,
9 sync::{
10 Arc,
11 atomic::{AtomicUsize, Ordering},
12 },
13 time::Duration,
14};
15use tokio::sync::Notify;
16use xmtp_proto::types::InstallationId;
17
18pub struct MetricInterest<Metric> {
19 fut: Pin<Box<dyn Future<Output = ()> + Send>>,
20 count: usize,
21 info: Info,
22 metric: Metric,
23}
24
25impl<Metric> MetricInterest<Metric>
26where
27 Metric: Debug,
28{
29 pub async fn wait(self) -> Result<(), xmtp_common::time::Expired> {
31 let Self {
32 fut,
33 count,
34 info,
35 metric,
36 } = self;
37
38 let result = xmtp_common::time::timeout(Duration::from_secs(10), fut).await;
39 tracing::info!("successfully waited for {metric:?}, {result:?}");
40 if info.count() >= count {
41 return Ok(());
42 }
43 tracing::error!(
44 "Timed out waiting for {:?} to be >= {}. Value: {}",
45 metric,
46 count,
47 info.count()
48 );
49 result
50 }
51}
52
53#[derive(Debug, Clone)]
55struct Info {
56 count: Arc<AtomicUsize>,
57 notify: Arc<Notify>,
59}
60
61impl Info {
62 fn new() -> Self {
63 Self {
64 count: Arc::new(AtomicUsize::default()),
65 notify: Arc::new(Notify::new()),
66 }
67 }
68
69 fn count(&self) -> usize {
70 self.count.load(Ordering::SeqCst)
71 }
72
73 fn increment(&self) {
74 self.count.fetch_add(1, Ordering::Relaxed);
75 }
76
77 fn clear(&self) {
78 self.count.store(0, Ordering::SeqCst)
79 }
80
81 fn register_interest(&self, count: usize) -> impl Future<Output = ()> + 'static {
84 let notify = self.notify.clone();
85 let info_count = self.count.clone();
86 async move {
87 while info_count.load(Ordering::SeqCst) < count {
88 notify.notified().await;
89 }
90 }
91 }
92
93 fn fire(&self) {
94 self.notify.notify_waiters();
95 }
96}
97
98#[derive(Debug)]
99pub struct WorkerMetrics<Metric> {
100 metrics: Mutex<HashMap<Metric, Info>>,
101}
102
103impl<Metric> WorkerMetrics<Metric>
104where
105 Metric: PartialEq + Eq + Hash + Clone + Copy + Debug,
106{
107 pub fn new(_installation_id: InstallationId) -> Self {
108 Self {
109 metrics: Mutex::default(),
110 }
111 }
112
113 fn info(&self, metric: Metric) -> Info {
114 self.metrics
115 .lock()
116 .entry(metric)
117 .or_insert(Info::new())
118 .clone()
119 }
120
121 pub fn get(&self, metric: Metric) -> usize {
122 self.info(metric).count()
123 }
124
125 pub(crate) fn increment_metric(&self, metric: Metric) {
126 self.info(metric).increment();
127 tracing::trace!("firing {metric:?}");
128 self.info(metric).fire();
129 }
130
131 pub fn reset_metrics(&self) {
132 *self.metrics.lock() = HashMap::new();
133 }
134
135 pub fn register_interest(&self, metric_key: Metric, count: usize) -> MetricInterest<Metric> {
139 tracing::info!("registering interest in {metric_key:?}");
140 let info = self.info(metric_key);
141
142 let fut = if self
143 .metrics
144 .lock()
145 .get(&metric_key)
146 .is_some_and(|info| info.count() >= count)
147 {
148 futures::future::ready(()).boxed()
149 } else {
150 info.register_interest(count).boxed()
151 };
152
153 MetricInterest {
154 fut,
155 count,
156 info: self.info(metric_key),
157 metric: metric_key,
158 }
159 }
160
161 pub async fn do_until<F, Fut>(
162 &self,
163 metric: Metric,
164 count: usize,
165 f: F,
166 ) -> Result<(), xmtp_common::time::Expired>
167 where
168 F: Fn() -> Fut,
169 Fut: Future<Output = ()>,
170 {
171 let info = {
172 let mut m = self.metrics.lock();
173 m.entry(metric).or_insert(Info::new()).clone()
174 };
175 xmtp_common::time::timeout(Duration::from_secs(20), async {
176 while info.count() < count {
177 f().await;
178 xmtp_common::task::yield_now().await;
179 }
180 })
181 .await
182 }
183
184 pub fn clear_metric(&self, metric: Metric) {
185 self.metrics
186 .lock()
187 .entry(metric)
188 .or_insert(Info::new())
189 .clear();
190 }
191}
192
193#[cfg(all(test, not(target_arch = "wasm32")))]
194mod tests;