bsv_overlay_engine/
meter.rs1use serde::{Deserialize, Serialize};
3use std::{
4 cell::{Cell, RefCell},
5 future::Future,
6 pin::Pin,
7 rc::Rc,
8 task::{Context, Poll},
9};
10
11#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
14#[serde(default)]
15pub struct SyncCost {
16 pub calls_out: u64,
17 pub peer_requests: u64,
18 pub courier_rungs: u64,
19 pub storage_reads: u64,
20 pub storage_writes: u64,
21 pub d1_queries: u64,
22 pub courier_not_found_memo_hits: u64,
23 pub peer_absent_memo_hits: u64,
27 pub batch_requests: u64,
30 pub batch_nodes: u64,
32 pub ms: u64,
33 pub outputs_walked: u64,
34 pub ms_per_1000_outputs: Option<u64>,
35}
36
37impl SyncCost {
38 pub fn since(self, before: Self) -> Self {
39 let mut cost = Self {
40 calls_out: self.calls_out.saturating_sub(before.calls_out),
41 peer_requests: self.peer_requests.saturating_sub(before.peer_requests),
42 courier_rungs: self.courier_rungs.saturating_sub(before.courier_rungs),
43 storage_reads: self.storage_reads.saturating_sub(before.storage_reads),
44 storage_writes: self.storage_writes.saturating_sub(before.storage_writes),
45 d1_queries: self.d1_queries.saturating_sub(before.d1_queries),
46 courier_not_found_memo_hits: self
47 .courier_not_found_memo_hits
48 .saturating_sub(before.courier_not_found_memo_hits),
49 peer_absent_memo_hits: self
50 .peer_absent_memo_hits
51 .saturating_sub(before.peer_absent_memo_hits),
52 batch_requests: self.batch_requests.saturating_sub(before.batch_requests),
53 batch_nodes: self.batch_nodes.saturating_sub(before.batch_nodes),
54 ms: self.ms.saturating_sub(before.ms),
55 outputs_walked: self.outputs_walked.saturating_sub(before.outputs_walked),
56 ms_per_1000_outputs: None,
57 };
58 cost.refresh_speed();
59 cost
60 }
61
62 pub fn refresh_speed(&mut self) {
63 self.ms_per_1000_outputs = (self.outputs_walked != 0).then(|| {
64 ((u128::from(self.ms) * 1000) / u128::from(self.outputs_walked))
65 .min(u128::from(u64::MAX)) as u64
66 });
67 }
68
69 pub fn add(&mut self, other: Self) {
70 self.calls_out = self.calls_out.saturating_add(other.calls_out);
71 self.peer_requests = self.peer_requests.saturating_add(other.peer_requests);
72 self.courier_rungs = self.courier_rungs.saturating_add(other.courier_rungs);
73 self.storage_reads = self.storage_reads.saturating_add(other.storage_reads);
74 self.storage_writes = self.storage_writes.saturating_add(other.storage_writes);
75 self.d1_queries = self.d1_queries.saturating_add(other.d1_queries);
76 self.courier_not_found_memo_hits = self
77 .courier_not_found_memo_hits
78 .saturating_add(other.courier_not_found_memo_hits);
79 self.peer_absent_memo_hits = self
80 .peer_absent_memo_hits
81 .saturating_add(other.peer_absent_memo_hits);
82 self.batch_requests = self.batch_requests.saturating_add(other.batch_requests);
83 self.batch_nodes = self.batch_nodes.saturating_add(other.batch_nodes);
84 self.ms = self.ms.saturating_add(other.ms);
85 self.outputs_walked = self.outputs_walked.saturating_add(other.outputs_walked);
86 self.refresh_speed();
87 }
88}
89
90struct Data {
91 cost: Cell<SyncCost>,
92 started: f64,
93 clock: Rc<dyn Fn() -> f64>,
94}
95#[derive(Clone)]
96pub struct LoadMeter(Rc<Data>);
97impl Default for LoadMeter {
98 fn default() -> Self {
99 Self::with_clock(Rc::new(crate::engine::current_timestamp_ms))
100 }
101}
102impl LoadMeter {
103 pub fn with_clock(clock: Rc<dyn Fn() -> f64>) -> Self {
105 Self(Rc::new(Data {
106 cost: Cell::default(),
107 started: clock(),
108 clock,
109 }))
110 }
111 pub fn snapshot(&self) -> SyncCost {
112 let mut cost = self.0.cost.get();
113 cost.ms = ((self.0.clock)() - self.0.started).max(0.0) as u64;
114 cost.refresh_speed();
115 cost
116 }
117 pub fn scope<F: Future>(&self, future: F) -> Measured<F> {
118 Measured {
119 meter: self.clone(),
120 future: Box::pin(future),
121 }
122 }
123}
124thread_local! {
125 static CURRENT: RefCell<Option<LoadMeter>> = const { RefCell::new(None) };
126 static ROOT: RefCell<Option<Rc<RootLimit>>> = const { RefCell::new(None) };
127}
128struct Restore(Option<LoadMeter>);
129impl Drop for Restore {
130 fn drop(&mut self) {
131 CURRENT.with(|c| *c.borrow_mut() = self.0.take());
132 }
133}
134pub struct Measured<F> {
135 meter: LoadMeter,
136 future: Pin<Box<F>>,
137}
138impl<F: Future> Future for Measured<F> {
139 type Output = F::Output;
140 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
141 let _restore = Restore(CURRENT.with(|c| c.borrow_mut().replace(self.meter.clone())));
142 self.future.as_mut().poll(cx)
143 }
144}
145pub fn snapshot() -> SyncCost {
146 CURRENT.with(|c| {
147 c.borrow()
148 .as_ref()
149 .map_or_else(SyncCost::default, LoadMeter::snapshot)
150 })
151}
152fn note(f: impl FnOnce(&mut SyncCost)) {
153 CURRENT.with(|c| {
154 if let Some(m) = c.borrow().as_ref() {
155 let mut v = m.0.cost.get();
156 f(&mut v);
157 m.0.cost.set(v);
158 }
159 });
160}
161pub fn walk_output() {
162 note(|v| v.outputs_walked += 1);
163}
164pub fn peer_request() {
165 note(|v| {
166 v.calls_out += 1;
167 v.peer_requests += 1;
168 });
169}
170pub fn batch_request() {
172 note(|v| {
173 v.calls_out += 1;
174 v.peer_requests += 1;
175 v.batch_requests += 1;
176 });
177}
178pub fn batch_nodes(count: u64) {
179 note(|v| v.batch_nodes = v.batch_nodes.saturating_add(count));
180}
181pub fn storage_read() {
182 note(|v| v.storage_reads += 1);
183}
184pub fn storage_write() {
185 note(|v| v.storage_writes += 1);
186}
187pub fn d1_queries(count: u64) {
189 note(|v| v.d1_queries = v.d1_queries.saturating_add(count));
190}
191pub fn not_found_memo_hit() {
192 note(|v| v.courier_not_found_memo_hits += 1);
193}
194pub fn peer_absent_memo_hit() {
195 note(|v| v.peer_absent_memo_hits += 1);
196}
197
198pub const ROOT_MAX_COURIER_RUNGS: u32 = 12;
203pub const COURIER_RUNG_TIMEOUT_MS: u64 = 1_000;
204pub const ROOT_MAX_MS: u64 = 15_000;
205#[derive(Default)]
206struct RootLimit {
207 calls: Cell<u32>,
208 exhausted: Cell<bool>,
209}
210pub struct RootScope<F> {
211 limit: Rc<RootLimit>,
212 future: Pin<Box<F>>,
213}
214pub fn root_scope<F: Future>(future: F) -> RootScope<F> {
215 RootScope {
216 limit: Rc::default(),
217 future: Box::pin(future),
218 }
219}
220struct RestoreRoot(Option<Rc<RootLimit>>);
221impl Drop for RestoreRoot {
222 fn drop(&mut self) {
223 ROOT.with(|c| *c.borrow_mut() = self.0.take());
224 }
225}
226impl<F: Future> Future for RootScope<F> {
227 type Output = F::Output;
228 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
229 let _restore = RestoreRoot(ROOT.with(|c| c.borrow_mut().replace(self.limit.clone())));
230 self.future.as_mut().poll(cx)
231 }
232}
233pub fn root_scoped() -> bool {
234 ROOT.with(|c| c.borrow().is_some())
235}
236pub fn root_exhausted() -> bool {
237 ROOT.with(|c| c.borrow().as_ref().is_some_and(|r| r.exhausted.get()))
238}
239pub fn courier_request() -> Result<(), &'static str> {
241 let allowed = ROOT.with(|c| {
242 c.borrow().as_ref().is_none_or(|r| {
243 if r.calls.get() >= ROOT_MAX_COURIER_RUNGS {
244 r.exhausted.set(true);
245 false
246 } else {
247 r.calls.set(r.calls.get() + 1);
248 true
249 }
250 })
251 });
252 if !allowed {
253 return Err("root courier rung budget exhausted");
254 }
255 note(|v| {
256 v.calls_out += 1;
257 v.courier_rungs += 1;
258 });
259 Ok(())
260}
261
262#[cfg(test)]
263mod tests {
264 use super::*;
265 #[tokio::test]
266 async fn load_0112_cancelled_run_keeps_its_attempts() {
267 let meter = LoadMeter::default();
268 let mut task = Box::pin(meter.scope(async {
269 peer_request();
270 storage_write();
271 std::future::pending::<()>().await;
272 }));
273 std::future::poll_fn(|cx| {
274 assert!(task.as_mut().poll(cx).is_pending());
275 Poll::Ready(())
276 })
277 .await;
278 drop(task);
279 assert_eq!(
280 (
281 meter.snapshot().peer_requests,
282 meter.snapshot().storage_writes
283 ),
284 (1, 1)
285 );
286 assert_eq!(snapshot(), SyncCost::default());
287 }
288
289 #[tokio::test]
290 async fn load_0112_poll_scopes_keep_interleaved_runs_separate() {
291 let a = LoadMeter::default();
292 let b = LoadMeter::default();
293 let ((), ()) = tokio::join!(
294 a.scope(async {
295 peer_request();
296 tokio::task::yield_now().await;
297 storage_read();
298 }),
299 b.scope(async {
300 storage_write();
301 tokio::task::yield_now().await;
302 courier_request().unwrap();
303 })
304 );
305 assert_eq!(
306 (
307 a.snapshot().peer_requests,
308 a.snapshot().storage_reads,
309 a.snapshot().storage_writes
310 ),
311 (1, 1, 0)
312 );
313 assert_eq!(
314 (
315 b.snapshot().courier_rungs,
316 b.snapshot().storage_writes,
317 b.snapshot().storage_reads
318 ),
319 (1, 1, 0)
320 );
321 assert_eq!(snapshot(), SyncCost::default());
322 }
323}