1use std::fmt;
25use std::sync::atomic::{AtomicU64, Ordering};
26use std::sync::{Arc, Mutex, OnceLock};
27
28use super::{DurabilityError, DurableStore, ScanCursor, ScanPage, StoredEntry};
29
30#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
35pub struct StoreCounts {
36 pub apply_routed_batch_calls: u64,
38 pub apply_routed_batch_items: u64,
40 pub get_routed_calls: u64,
42 pub get_routed_rows: u64,
44
45 pub append_calls: u64,
47 pub append_rows: u64,
49 pub append_bytes: u64,
51 pub append_all_calls: u64,
53 pub append_all_rows: u64,
55 pub append_all_bytes: u64,
57 pub read_from_calls: u64,
59 pub read_from_rows: u64,
61 pub read_at_calls: u64,
63 pub read_at_rows: u64,
65 pub cas_calls: u64,
67 pub read_value_calls: u64,
69 pub read_stream_next_seq_calls: u64,
71 pub scan_page_calls: u64,
73 pub scan_page_rows: u64,
75 pub flushes: u64,
77 pub truncate_calls: u64,
79 pub truncate_rows: u64,
81}
82
83impl StoreCounts {
84 #[must_use]
91 pub const fn since(&self, earlier: &Self) -> Self {
92 Self {
93 apply_routed_batch_calls: self
94 .apply_routed_batch_calls
95 .saturating_sub(earlier.apply_routed_batch_calls),
96 apply_routed_batch_items: self
97 .apply_routed_batch_items
98 .saturating_sub(earlier.apply_routed_batch_items),
99 get_routed_calls: self
100 .get_routed_calls
101 .saturating_sub(earlier.get_routed_calls),
102 get_routed_rows: self.get_routed_rows.saturating_sub(earlier.get_routed_rows),
103
104 append_calls: self.append_calls.saturating_sub(earlier.append_calls),
105 append_rows: self.append_rows.saturating_sub(earlier.append_rows),
106 append_bytes: self.append_bytes.saturating_sub(earlier.append_bytes),
107 append_all_calls: self
108 .append_all_calls
109 .saturating_sub(earlier.append_all_calls),
110 append_all_rows: self.append_all_rows.saturating_sub(earlier.append_all_rows),
111 append_all_bytes: self
112 .append_all_bytes
113 .saturating_sub(earlier.append_all_bytes),
114 read_from_calls: self.read_from_calls.saturating_sub(earlier.read_from_calls),
115 read_from_rows: self.read_from_rows.saturating_sub(earlier.read_from_rows),
116 read_at_calls: self.read_at_calls.saturating_sub(earlier.read_at_calls),
117 read_at_rows: self.read_at_rows.saturating_sub(earlier.read_at_rows),
118 cas_calls: self.cas_calls.saturating_sub(earlier.cas_calls),
119 read_value_calls: self
120 .read_value_calls
121 .saturating_sub(earlier.read_value_calls),
122 read_stream_next_seq_calls: self
123 .read_stream_next_seq_calls
124 .saturating_sub(earlier.read_stream_next_seq_calls),
125 scan_page_calls: self.scan_page_calls.saturating_sub(earlier.scan_page_calls),
126 scan_page_rows: self.scan_page_rows.saturating_sub(earlier.scan_page_rows),
127 flushes: self.flushes.saturating_sub(earlier.flushes),
128 truncate_calls: self.truncate_calls.saturating_sub(earlier.truncate_calls),
129 truncate_rows: self.truncate_rows.saturating_sub(earlier.truncate_rows),
130 }
131 }
132
133 #[must_use]
135 pub const fn plus(&self, other: &Self) -> Self {
136 Self {
137 apply_routed_batch_calls: self
138 .apply_routed_batch_calls
139 .saturating_add(other.apply_routed_batch_calls),
140 apply_routed_batch_items: self
141 .apply_routed_batch_items
142 .saturating_add(other.apply_routed_batch_items),
143 get_routed_calls: self.get_routed_calls.saturating_add(other.get_routed_calls),
144 get_routed_rows: self.get_routed_rows.saturating_add(other.get_routed_rows),
145
146 append_calls: self.append_calls.saturating_add(other.append_calls),
147 append_rows: self.append_rows.saturating_add(other.append_rows),
148 append_bytes: self.append_bytes.saturating_add(other.append_bytes),
149 append_all_calls: self.append_all_calls.saturating_add(other.append_all_calls),
150 append_all_rows: self.append_all_rows.saturating_add(other.append_all_rows),
151 append_all_bytes: self.append_all_bytes.saturating_add(other.append_all_bytes),
152 read_from_calls: self.read_from_calls.saturating_add(other.read_from_calls),
153 read_from_rows: self.read_from_rows.saturating_add(other.read_from_rows),
154 read_at_calls: self.read_at_calls.saturating_add(other.read_at_calls),
155 read_at_rows: self.read_at_rows.saturating_add(other.read_at_rows),
156 cas_calls: self.cas_calls.saturating_add(other.cas_calls),
157 read_value_calls: self.read_value_calls.saturating_add(other.read_value_calls),
158 read_stream_next_seq_calls: self
159 .read_stream_next_seq_calls
160 .saturating_add(other.read_stream_next_seq_calls),
161 scan_page_calls: self.scan_page_calls.saturating_add(other.scan_page_calls),
162 scan_page_rows: self.scan_page_rows.saturating_add(other.scan_page_rows),
163 flushes: self.flushes.saturating_add(other.flushes),
164 truncate_calls: self.truncate_calls.saturating_add(other.truncate_calls),
165 truncate_rows: self.truncate_rows.saturating_add(other.truncate_rows),
166 }
167 }
168
169 #[must_use]
172 pub const fn named(&self) -> [(&'static str, u64); 22] {
173 [
174 ("append_calls", self.append_calls),
175 ("append_rows", self.append_rows),
176 ("append_bytes", self.append_bytes),
177 ("append_all_calls", self.append_all_calls),
178 ("append_all_rows", self.append_all_rows),
179 ("append_all_bytes", self.append_all_bytes),
180 ("read_from_calls", self.read_from_calls),
181 ("read_from_rows", self.read_from_rows),
182 ("read_at_calls", self.read_at_calls),
183 ("read_at_rows", self.read_at_rows),
184 ("cas_calls", self.cas_calls),
185 ("read_value_calls", self.read_value_calls),
186 (
187 "read_stream_next_seq_calls",
188 self.read_stream_next_seq_calls,
189 ),
190 ("scan_page_calls", self.scan_page_calls),
191 ("scan_page_rows", self.scan_page_rows),
192 ("flushes", self.flushes),
193 ("truncate_calls", self.truncate_calls),
194 ("truncate_rows", self.truncate_rows),
195 ("apply_routed_batch_calls", self.apply_routed_batch_calls),
196 ("apply_routed_batch_items", self.apply_routed_batch_items),
197 ("get_routed_calls", self.get_routed_calls),
198 ("get_routed_rows", self.get_routed_rows),
199 ]
200 }
201}
202
203impl fmt::Display for StoreCounts {
206 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
207 for (name, value) in self.named() {
208 writeln!(formatter, "{name} {value}")?;
209 }
210 Ok(())
211 }
212}
213
214pub trait StoreCountObserver: fmt::Debug + Send + Sync {
219 fn changed(&self);
221
222 fn flushed(&self, store: &str, batch_rows: Option<u64>);
224}
225
226#[derive(Debug)]
227struct ObserverBinding {
228 name: String,
229 observer: Arc<dyn StoreCountObserver>,
230}
231
232static OBSERVER_INSTALL: Mutex<()> = Mutex::new(());
233
234#[derive(Debug, Default)]
236struct Counters {
237 apply_routed_batch_calls: AtomicU64,
238 apply_routed_batch_items: AtomicU64,
239 get_routed_calls: AtomicU64,
240 get_routed_rows: AtomicU64,
241
242 append_calls: AtomicU64,
243 append_rows: AtomicU64,
244 append_bytes: AtomicU64,
245 append_all_calls: AtomicU64,
246 append_all_rows: AtomicU64,
247 append_all_bytes: AtomicU64,
248 read_from_calls: AtomicU64,
249 read_from_rows: AtomicU64,
250 read_at_calls: AtomicU64,
251 read_at_rows: AtomicU64,
252 cas_calls: AtomicU64,
253 read_value_calls: AtomicU64,
254 read_stream_next_seq_calls: AtomicU64,
255 scan_page_calls: AtomicU64,
256 scan_page_rows: AtomicU64,
257 flushes: AtomicU64,
258 truncate_calls: AtomicU64,
259 truncate_rows: AtomicU64,
260 observer: OnceLock<ObserverBinding>,
261}
262
263impl Counters {
264 fn add(&self, counter: &AtomicU64, amount: u64) {
265 if amount == 0 {
266 return;
267 }
268 counter.fetch_add(amount, Ordering::Relaxed);
269 if let Some(binding) = self.observer.get() {
270 binding.observer.changed();
271 }
272 }
273
274 fn flushed(&self) {
275 if let Some(binding) = self.observer.get() {
276 binding.observer.flushed(&binding.name, None);
277 }
278 }
279
280 fn snapshot(&self) -> StoreCounts {
282 let read = |counter: &AtomicU64| counter.load(Ordering::Relaxed);
283 StoreCounts {
284 apply_routed_batch_calls: read(&self.apply_routed_batch_calls),
285 apply_routed_batch_items: read(&self.apply_routed_batch_items),
286 get_routed_calls: read(&self.get_routed_calls),
287 get_routed_rows: read(&self.get_routed_rows),
288 append_calls: read(&self.append_calls),
289 append_rows: read(&self.append_rows),
290 append_bytes: read(&self.append_bytes),
291 append_all_calls: read(&self.append_all_calls),
292 append_all_rows: read(&self.append_all_rows),
293 append_all_bytes: read(&self.append_all_bytes),
294 read_from_calls: read(&self.read_from_calls),
295 read_from_rows: read(&self.read_from_rows),
296 read_at_calls: read(&self.read_at_calls),
297 read_at_rows: read(&self.read_at_rows),
298 cas_calls: read(&self.cas_calls),
299 read_value_calls: read(&self.read_value_calls),
300 read_stream_next_seq_calls: read(&self.read_stream_next_seq_calls),
301 scan_page_calls: read(&self.scan_page_calls),
302 scan_page_rows: read(&self.scan_page_rows),
303 flushes: read(&self.flushes),
304 truncate_calls: read(&self.truncate_calls),
305 truncate_rows: read(&self.truncate_rows),
306 }
307 }
308}
309
310#[derive(Clone, Debug, Default)]
317pub struct StoreCountsHandle {
318 stores: Vec<Arc<Counters>>,
319}
320
321impl StoreCountsHandle {
322 #[must_use]
324 pub fn store_count(&self) -> usize {
325 self.stores.len()
326 }
327
328 pub fn install_observer(
335 &self,
336 names: &[&str],
337 observer: Arc<dyn StoreCountObserver>,
338 ) -> Result<(), DurabilityError> {
339 if self.stores.is_empty() || names.len() != self.stores.len() {
340 return Err(observer_error(
341 "one name is required for each counted store",
342 ));
343 }
344 if names.iter().any(|name| name.trim().is_empty()) {
345 return Err(observer_error("store name is empty"));
346 }
347 for (index, counters) in self.stores.iter().enumerate() {
348 if self.stores[..index]
349 .iter()
350 .any(|earlier| Arc::ptr_eq(earlier, counters))
351 {
352 return Err(observer_error(
353 "the handle contains a duplicate counted store",
354 ));
355 }
356 }
357 let installation = OBSERVER_INSTALL
358 .lock()
359 .map_err(|_| observer_error("installation lock poisoned"))?;
360 if self
361 .stores
362 .iter()
363 .any(|counters| counters.observer.get().is_some())
364 {
365 return Err(observer_error("observer already installed"));
366 }
367 for ((counters, name), observer) in self
368 .stores
369 .iter()
370 .zip(names)
371 .zip(std::iter::repeat_n(observer, self.stores.len()))
372 {
373 counters
374 .observer
375 .set(ObserverBinding {
376 name: (*name).to_owned(),
377 observer,
378 })
379 .map_err(|_| observer_error("observer installation invariant failed"))?;
380 }
381 drop(installation);
382 Ok(())
383 }
384
385 #[must_use]
387 pub fn with(mut self, other: &Self) -> Self {
388 self.stores.extend(other.stores.iter().cloned());
389 self
390 }
391
392 #[must_use]
396 pub fn snapshot(&self) -> StoreCounts {
397 self.stores
398 .iter()
399 .fold(StoreCounts::default(), |total, counters| {
400 total.plus(&counters.snapshot())
401 })
402 }
403}
404
405static STORES_OPENED: AtomicU64 = AtomicU64::new(0);
407
408#[must_use]
411pub fn stores_opened() -> u64 {
412 STORES_OPENED.load(Ordering::Relaxed)
413}
414
415pub fn record_store_opened() {
418 add(&STORES_OPENED, 1);
419}
420
421fn add(counter: &AtomicU64, amount: u64) {
426 counter.fetch_add(amount, Ordering::Relaxed);
427}
428
429fn observer_error(message: &str) -> DurabilityError {
430 DurabilityError::ConfigError(format!("store count observer: {message}"))
431}
432
433fn widen(amount: usize) -> u64 {
435 u64::try_from(amount).unwrap_or(u64::MAX)
436}
437
438#[derive(Debug)]
443pub struct CountingStore<S> {
444 inner: S,
445 counters: Arc<Counters>,
446}
447
448impl<S: DurableStore> CountingStore<S> {
449 #[must_use]
451 pub fn new(inner: S) -> Self {
452 Self {
453 inner,
454 counters: Arc::default(),
455 }
456 }
457
458 #[must_use]
461 pub fn handle(&self) -> StoreCountsHandle {
462 StoreCountsHandle {
463 stores: vec![Arc::clone(&self.counters)],
464 }
465 }
466
467 #[must_use]
473 pub fn snapshot(&self) -> StoreCounts {
474 self.counters.snapshot()
475 }
476}
477
478#[async_trait::async_trait]
479impl<S: DurableStore> DurableStore for CountingStore<S> {
480 async fn apply_routed_batch(
481 &self,
482 route_key: &str,
483 items: Vec<super::RoutedBatchItem>,
484 ) -> Result<(), DurabilityError> {
485 self.counters
486 .add(&self.counters.apply_routed_batch_calls, 1);
487 let count = widen(items.len());
488 self.inner.apply_routed_batch(route_key, items).await?;
489 self.counters
490 .add(&self.counters.apply_routed_batch_items, count);
491 Ok(())
492 }
493
494 async fn get_routed(
495 &self,
496 route_key: &str,
497 key: &[u8],
498 ) -> Result<Option<Vec<u8>>, DurabilityError> {
499 self.counters.add(&self.counters.get_routed_calls, 1);
500 let value = self.inner.get_routed(route_key, key).await?;
501 if value.is_some() {
502 self.counters.add(&self.counters.get_routed_rows, 1);
503 }
504 Ok(value)
505 }
506
507 async fn append(
508 &self,
509 stream_key: &str,
510 payload: Vec<u8>,
511 expected_seq: u64,
512 ) -> Result<u64, DurabilityError> {
513 self.counters.add(&self.counters.append_calls, 1);
514 let bytes = widen(payload.len());
515 let assigned = self.inner.append(stream_key, payload, expected_seq).await?;
516 self.counters.add(&self.counters.append_rows, 1);
517 self.counters.add(&self.counters.append_bytes, bytes);
518 Ok(assigned)
519 }
520
521 async fn read_from(
522 &self,
523 stream_key: &str,
524 offset: u64,
525 limit: usize,
526 ) -> Result<Vec<StoredEntry>, DurabilityError> {
527 self.counters.add(&self.counters.read_from_calls, 1);
528 let entries = self.inner.read_from(stream_key, offset, limit).await?;
529 self.counters
530 .add(&self.counters.read_from_rows, widen(entries.len()));
531 Ok(entries)
532 }
533
534 async fn append_all(
535 &self,
536 stream_key: &str,
537 payloads: Vec<Vec<u8>>,
538 expected_seq: u64,
539 ) -> Result<u64, DurabilityError> {
540 self.counters.add(&self.counters.append_all_calls, 1);
541 let rows = widen(payloads.len());
542 let bytes = payloads
543 .iter()
544 .map(|payload| widen(payload.len()))
545 .fold(0_u64, u64::saturating_add);
546 let next = self
547 .inner
548 .append_all(stream_key, payloads, expected_seq)
549 .await?;
550 self.counters.add(&self.counters.append_all_rows, rows);
551 self.counters.add(&self.counters.append_all_bytes, bytes);
552 Ok(next)
553 }
554
555 async fn read_at(
556 &self,
557 stream_key: &str,
558 sequence: u64,
559 ) -> Result<Option<StoredEntry>, DurabilityError> {
560 self.counters.add(&self.counters.read_at_calls, 1);
561 let entry = self.inner.read_at(stream_key, sequence).await?;
562 if entry.is_some() {
563 self.counters.add(&self.counters.read_at_rows, 1);
564 }
565 Ok(entry)
566 }
567
568 async fn cas(&self, key: &str, old_value: u64, new_value: u64) -> Result<(), DurabilityError> {
569 self.counters.add(&self.counters.cas_calls, 1);
570 self.inner.cas(key, old_value, new_value).await
571 }
572
573 async fn read_stream_next_seq(&self, stream_key: &str) -> Result<Option<u64>, DurabilityError> {
574 self.counters
575 .add(&self.counters.read_stream_next_seq_calls, 1);
576 self.inner.read_stream_next_seq(stream_key).await
577 }
578
579 async fn read_value(&self, key: &str) -> Result<Option<u64>, DurabilityError> {
580 self.counters.add(&self.counters.read_value_calls, 1);
581 self.inner.read_value(key).await
582 }
583
584 async fn scan_page(
585 &self,
586 prefix: &str,
587 cursor: Option<ScanCursor>,
588 limit: usize,
589 ) -> Result<ScanPage, DurabilityError> {
590 self.counters.add(&self.counters.scan_page_calls, 1);
591 let page = self.inner.scan_page(prefix, cursor, limit).await?;
592 self.counters
593 .add(&self.counters.scan_page_rows, widen(page.entries.len()));
594 Ok(page)
595 }
596
597 async fn flush(&self) -> Result<(), DurabilityError> {
598 self.counters.add(&self.counters.flushes, 1);
599 self.counters.flushed();
600 self.inner.flush().await
601 }
602
603 async fn truncate_before(
604 &self,
605 stream_key: &str,
606 before_seq: u64,
607 ) -> Result<u64, DurabilityError> {
608 add(&self.counters.truncate_calls, 1);
609 let deleted = self.inner.truncate_before(stream_key, before_seq).await?;
610 add(&self.counters.truncate_rows, deleted);
611 Ok(deleted)
612 }
613
614 async fn scan_routed(
615 &self,
616 route_key: &str,
617 from: &[u8],
618 to: &[u8],
619 ) -> Result<Vec<(Vec<u8>, Vec<u8>)>, DurabilityError> {
620 self.inner.scan_routed(route_key, from, to).await
621 }
622
623 async fn remove_routed_range(
624 &self,
625 route_key: &str,
626 from: &[u8],
627 to: &[u8],
628 ) -> Result<u64, DurabilityError> {
629 self.inner.remove_routed_range(route_key, from, to).await
630 }
631
632 fn store_counts(&self) -> Option<StoreCountsHandle> {
633 Some(self.handle())
634 }
635}
636
637#[cfg(test)]
638mod tests;