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
170pub trait StoreCountObserver: fmt::Debug + Send + Sync {
175 fn changed(&self);
177
178 fn flushed(&self, store: &str, batch_rows: Option<u64>);
180}
181
182#[derive(Debug)]
183struct ObserverBinding {
184 name: String,
185 observer: Arc<dyn StoreCountObserver>,
186}
187
188static OBSERVER_INSTALL: Mutex<()> = Mutex::new(());
189
190#[derive(Debug, Default)]
192struct Counters {
193 apply_routed_batch_calls: AtomicU64,
194 apply_routed_batch_items: AtomicU64,
195 get_routed_calls: AtomicU64,
196 get_routed_rows: AtomicU64,
197
198 append_calls: AtomicU64,
199 append_rows: AtomicU64,
200 append_bytes: AtomicU64,
201 append_all_calls: AtomicU64,
202 append_all_rows: AtomicU64,
203 append_all_bytes: AtomicU64,
204 read_from_calls: AtomicU64,
205 read_from_rows: AtomicU64,
206 read_at_calls: AtomicU64,
207 read_at_rows: AtomicU64,
208 cas_calls: AtomicU64,
209 read_value_calls: AtomicU64,
210 read_stream_next_seq_calls: AtomicU64,
211 scan_page_calls: AtomicU64,
212 scan_page_rows: AtomicU64,
213 flushes: AtomicU64,
214 truncate_calls: AtomicU64,
215 truncate_rows: AtomicU64,
216 observer: OnceLock<ObserverBinding>,
217}
218
219impl Counters {
220 fn add(&self, counter: &AtomicU64, amount: u64) {
221 if amount == 0 {
222 return;
223 }
224 counter.fetch_add(amount, Ordering::Relaxed);
225 if let Some(binding) = self.observer.get() {
226 binding.observer.changed();
227 }
228 }
229
230 fn flushed(&self) {
231 if let Some(binding) = self.observer.get() {
232 binding.observer.flushed(&binding.name, None);
233 }
234 }
235
236 fn snapshot(&self) -> StoreCounts {
238 let read = |counter: &AtomicU64| counter.load(Ordering::Relaxed);
239 StoreCounts {
240 apply_routed_batch_calls: read(&self.apply_routed_batch_calls),
241 apply_routed_batch_items: read(&self.apply_routed_batch_items),
242 get_routed_calls: read(&self.get_routed_calls),
243 get_routed_rows: read(&self.get_routed_rows),
244 append_calls: read(&self.append_calls),
245 append_rows: read(&self.append_rows),
246 append_bytes: read(&self.append_bytes),
247 append_all_calls: read(&self.append_all_calls),
248 append_all_rows: read(&self.append_all_rows),
249 append_all_bytes: read(&self.append_all_bytes),
250 read_from_calls: read(&self.read_from_calls),
251 read_from_rows: read(&self.read_from_rows),
252 read_at_calls: read(&self.read_at_calls),
253 read_at_rows: read(&self.read_at_rows),
254 cas_calls: read(&self.cas_calls),
255 read_value_calls: read(&self.read_value_calls),
256 read_stream_next_seq_calls: read(&self.read_stream_next_seq_calls),
257 scan_page_calls: read(&self.scan_page_calls),
258 scan_page_rows: read(&self.scan_page_rows),
259 flushes: read(&self.flushes),
260 truncate_calls: read(&self.truncate_calls),
261 truncate_rows: read(&self.truncate_rows),
262 }
263 }
264}
265
266#[derive(Clone, Debug, Default)]
273pub struct StoreCountsHandle {
274 stores: Vec<Arc<Counters>>,
275}
276
277impl StoreCountsHandle {
278 #[must_use]
280 pub fn store_count(&self) -> usize {
281 self.stores.len()
282 }
283
284 pub fn install_observer(
291 &self,
292 names: &[&str],
293 observer: Arc<dyn StoreCountObserver>,
294 ) -> Result<(), DurabilityError> {
295 if self.stores.is_empty() || names.len() != self.stores.len() {
296 return Err(observer_error(
297 "one name is required for each counted store",
298 ));
299 }
300 if names.iter().any(|name| name.trim().is_empty()) {
301 return Err(observer_error("store name is empty"));
302 }
303 for (index, counters) in self.stores.iter().enumerate() {
304 if self.stores[..index]
305 .iter()
306 .any(|earlier| Arc::ptr_eq(earlier, counters))
307 {
308 return Err(observer_error(
309 "the handle contains a duplicate counted store",
310 ));
311 }
312 }
313 let installation = OBSERVER_INSTALL
314 .lock()
315 .map_err(|_| observer_error("installation lock poisoned"))?;
316 if self
317 .stores
318 .iter()
319 .any(|counters| counters.observer.get().is_some())
320 {
321 return Err(observer_error("observer already installed"));
322 }
323 for ((counters, name), observer) in self
324 .stores
325 .iter()
326 .zip(names)
327 .zip(std::iter::repeat_n(observer, self.stores.len()))
328 {
329 counters
330 .observer
331 .set(ObserverBinding {
332 name: (*name).to_owned(),
333 observer,
334 })
335 .map_err(|_| observer_error("observer installation invariant failed"))?;
336 }
337 drop(installation);
338 Ok(())
339 }
340
341 #[must_use]
343 pub fn with(mut self, other: &Self) -> Self {
344 self.stores.extend(other.stores.iter().cloned());
345 self
346 }
347
348 #[must_use]
352 pub fn snapshot(&self) -> StoreCounts {
353 self.stores
354 .iter()
355 .fold(StoreCounts::default(), |total, counters| {
356 total.plus(&counters.snapshot())
357 })
358 }
359}
360
361static STORES_OPENED: AtomicU64 = AtomicU64::new(0);
363
364#[must_use]
367pub fn stores_opened() -> u64 {
368 STORES_OPENED.load(Ordering::Relaxed)
369}
370
371pub fn record_store_opened() {
374 add(&STORES_OPENED, 1);
375}
376
377fn add(counter: &AtomicU64, amount: u64) {
382 counter.fetch_add(amount, Ordering::Relaxed);
383}
384
385fn observer_error(message: &str) -> DurabilityError {
386 DurabilityError::ConfigError(format!("store count observer: {message}"))
387}
388
389fn widen(amount: usize) -> u64 {
391 u64::try_from(amount).unwrap_or(u64::MAX)
392}
393
394#[derive(Debug)]
399pub struct CountingStore<S> {
400 inner: S,
401 counters: Arc<Counters>,
402}
403
404impl<S: DurableStore> CountingStore<S> {
405 #[must_use]
407 pub fn new(inner: S) -> Self {
408 Self {
409 inner,
410 counters: Arc::default(),
411 }
412 }
413
414 #[must_use]
417 pub fn handle(&self) -> StoreCountsHandle {
418 StoreCountsHandle {
419 stores: vec![Arc::clone(&self.counters)],
420 }
421 }
422
423 #[must_use]
429 pub fn snapshot(&self) -> StoreCounts {
430 self.counters.snapshot()
431 }
432}
433
434#[async_trait::async_trait]
435impl<S: DurableStore> DurableStore for CountingStore<S> {
436 fn reclaim_spine(&self) -> Option<std::sync::Arc<haematite::EventStore>> {
437 self.inner.reclaim_spine()
438 }
439
440 fn max_request_bytes(&self) -> Option<usize> {
441 self.inner.max_request_bytes()
442 }
443
444 async fn apply_routed_batch(
445 &self,
446 route_key: &str,
447 items: Vec<super::RoutedBatchItem>,
448 ) -> Result<(), DurabilityError> {
449 self.counters
450 .add(&self.counters.apply_routed_batch_calls, 1);
451 let count = widen(items.len());
452 self.inner.apply_routed_batch(route_key, items).await?;
453 self.counters
454 .add(&self.counters.apply_routed_batch_items, count);
455 Ok(())
456 }
457
458 async fn apply_routed_entries(
461 &self,
462 route_key: &str,
463 entries: Vec<super::RoutedBatchEntry>,
464 ) -> Result<(), DurabilityError> {
465 self.counters
466 .add(&self.counters.apply_routed_batch_calls, 1);
467 let count = widen(entries.len());
468 self.inner.apply_routed_entries(route_key, entries).await?;
469 self.counters
470 .add(&self.counters.apply_routed_batch_items, count);
471 Ok(())
472 }
473
474 async fn get_routed(
475 &self,
476 route_key: &str,
477 key: &[u8],
478 ) -> Result<Option<Vec<u8>>, DurabilityError> {
479 self.counters.add(&self.counters.get_routed_calls, 1);
480 let value = self.inner.get_routed(route_key, key).await?;
481 if value.is_some() {
482 self.counters.add(&self.counters.get_routed_rows, 1);
483 }
484 Ok(value)
485 }
486
487 async fn append(
488 &self,
489 stream_key: &str,
490 payload: Vec<u8>,
491 expected_seq: u64,
492 ) -> Result<u64, DurabilityError> {
493 self.counters.add(&self.counters.append_calls, 1);
494 let bytes = widen(payload.len());
495 let assigned = self.inner.append(stream_key, payload, expected_seq).await?;
496 self.counters.add(&self.counters.append_rows, 1);
497 self.counters.add(&self.counters.append_bytes, bytes);
498 Ok(assigned)
499 }
500
501 async fn read_from(
502 &self,
503 stream_key: &str,
504 offset: u64,
505 limit: usize,
506 ) -> Result<Vec<StoredEntry>, DurabilityError> {
507 self.counters.add(&self.counters.read_from_calls, 1);
508 let entries = self.inner.read_from(stream_key, offset, limit).await?;
509 self.counters
510 .add(&self.counters.read_from_rows, widen(entries.len()));
511 Ok(entries)
512 }
513
514 async fn append_all(
515 &self,
516 stream_key: &str,
517 payloads: Vec<Vec<u8>>,
518 expected_seq: u64,
519 ) -> Result<u64, DurabilityError> {
520 self.counters.add(&self.counters.append_all_calls, 1);
521 let rows = widen(payloads.len());
522 let bytes = payloads
523 .iter()
524 .map(|payload| widen(payload.len()))
525 .fold(0_u64, u64::saturating_add);
526 let next = self
527 .inner
528 .append_all(stream_key, payloads, expected_seq)
529 .await?;
530 self.counters.add(&self.counters.append_all_rows, rows);
531 self.counters.add(&self.counters.append_all_bytes, bytes);
532 Ok(next)
533 }
534
535 async fn read_at(
536 &self,
537 stream_key: &str,
538 sequence: u64,
539 ) -> Result<Option<StoredEntry>, DurabilityError> {
540 self.counters.add(&self.counters.read_at_calls, 1);
541 let entry = self.inner.read_at(stream_key, sequence).await?;
542 if entry.is_some() {
543 self.counters.add(&self.counters.read_at_rows, 1);
544 }
545 Ok(entry)
546 }
547
548 async fn cas(&self, key: &str, old_value: u64, new_value: u64) -> Result<(), DurabilityError> {
549 self.counters.add(&self.counters.cas_calls, 1);
550 self.inner.cas(key, old_value, new_value).await
551 }
552
553 async fn read_stream_next_seq(&self, stream_key: &str) -> Result<Option<u64>, DurabilityError> {
554 self.counters
555 .add(&self.counters.read_stream_next_seq_calls, 1);
556 self.inner.read_stream_next_seq(stream_key).await
557 }
558
559 async fn read_value(&self, key: &str) -> Result<Option<u64>, DurabilityError> {
560 self.counters.add(&self.counters.read_value_calls, 1);
561 self.inner.read_value(key).await
562 }
563
564 async fn scan_page(
565 &self,
566 prefix: &str,
567 cursor: Option<ScanCursor>,
568 limit: usize,
569 ) -> Result<ScanPage, DurabilityError> {
570 self.counters.add(&self.counters.scan_page_calls, 1);
571 let page = self.inner.scan_page(prefix, cursor, limit).await?;
572 self.counters
573 .add(&self.counters.scan_page_rows, widen(page.entries.len()));
574 Ok(page)
575 }
576
577 async fn flush(&self) -> Result<(), DurabilityError> {
578 self.counters.add(&self.counters.flushes, 1);
579 self.counters.flushed();
580 self.inner.flush().await
581 }
582
583 async fn truncate_before(
584 &self,
585 stream_key: &str,
586 before_seq: u64,
587 ) -> Result<u64, DurabilityError> {
588 self.counters.add(&self.counters.truncate_calls, 1);
589 let deleted = self.inner.truncate_before(stream_key, before_seq).await?;
590 self.counters.add(&self.counters.truncate_rows, deleted);
591 Ok(deleted)
592 }
593
594 async fn scan_routed(
595 &self,
596 route_key: &str,
597 from: &[u8],
598 to: &[u8],
599 ) -> Result<Vec<(Vec<u8>, Vec<u8>)>, DurabilityError> {
600 self.inner.scan_routed(route_key, from, to).await
601 }
602
603 async fn remove_routed_range(
604 &self,
605 route_key: &str,
606 from: &[u8],
607 to: &[u8],
608 ) -> Result<u64, DurabilityError> {
609 self.inner.remove_routed_range(route_key, from, to).await
610 }
611
612 fn store_counts(&self) -> Option<StoreCountsHandle> {
613 Some(self.handle())
614 }
615}
616
617#[path = "counting_names.rs"]
618mod names;
619
620#[cfg(test)]
621mod tests;