1use std::fmt;
25use std::sync::{Arc, Mutex, OnceLock};
26use std::sync::atomic::{AtomicU64, Ordering};
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 fn reclaim_spine(&self) -> Option<std::sync::Arc<haematite::EventStore>> {
481 self.inner.reclaim_spine()
482 }
483
484 fn max_request_bytes(&self) -> Option<usize> {
485 self.inner.max_request_bytes()
486 }
487
488 async fn apply_routed_batch(
489 &self,
490 route_key: &str,
491 items: Vec<super::RoutedBatchItem>,
492 ) -> Result<(), DurabilityError> {
493 self.counters
494 .add(&self.counters.apply_routed_batch_calls, 1);
495 let count = widen(items.len());
496 self.inner.apply_routed_batch(route_key, items).await?;
497 self.counters
498 .add(&self.counters.apply_routed_batch_items, count);
499 Ok(())
500 }
501
502 async fn get_routed(
503 &self,
504 route_key: &str,
505 key: &[u8],
506 ) -> Result<Option<Vec<u8>>, DurabilityError> {
507 self.counters.add(&self.counters.get_routed_calls, 1);
508 let value = self.inner.get_routed(route_key, key).await?;
509 if value.is_some() {
510 self.counters.add(&self.counters.get_routed_rows, 1);
511 }
512 Ok(value)
513 }
514
515 async fn append(
516 &self,
517 stream_key: &str,
518 payload: Vec<u8>,
519 expected_seq: u64,
520 ) -> Result<u64, DurabilityError> {
521 self.counters.add(&self.counters.append_calls, 1);
522 let bytes = widen(payload.len());
523 let assigned = self.inner.append(stream_key, payload, expected_seq).await?;
524 self.counters.add(&self.counters.append_rows, 1);
525 self.counters.add(&self.counters.append_bytes, bytes);
526 Ok(assigned)
527 }
528
529 async fn read_from(
530 &self,
531 stream_key: &str,
532 offset: u64,
533 limit: usize,
534 ) -> Result<Vec<StoredEntry>, DurabilityError> {
535 self.counters.add(&self.counters.read_from_calls, 1);
536 let entries = self.inner.read_from(stream_key, offset, limit).await?;
537 self.counters
538 .add(&self.counters.read_from_rows, widen(entries.len()));
539 Ok(entries)
540 }
541
542 async fn append_all(
543 &self,
544 stream_key: &str,
545 payloads: Vec<Vec<u8>>,
546 expected_seq: u64,
547 ) -> Result<u64, DurabilityError> {
548 self.counters.add(&self.counters.append_all_calls, 1);
549 let rows = widen(payloads.len());
550 let bytes = payloads
551 .iter()
552 .map(|payload| widen(payload.len()))
553 .fold(0_u64, u64::saturating_add);
554 let next = self
555 .inner
556 .append_all(stream_key, payloads, expected_seq)
557 .await?;
558 self.counters.add(&self.counters.append_all_rows, rows);
559 self.counters.add(&self.counters.append_all_bytes, bytes);
560 Ok(next)
561 }
562
563 async fn read_at(
564 &self,
565 stream_key: &str,
566 sequence: u64,
567 ) -> Result<Option<StoredEntry>, DurabilityError> {
568 self.counters.add(&self.counters.read_at_calls, 1);
569 let entry = self.inner.read_at(stream_key, sequence).await?;
570 if entry.is_some() {
571 self.counters.add(&self.counters.read_at_rows, 1);
572 }
573 Ok(entry)
574 }
575
576 async fn cas(&self, key: &str, old_value: u64, new_value: u64) -> Result<(), DurabilityError> {
577 self.counters.add(&self.counters.cas_calls, 1);
578 self.inner.cas(key, old_value, new_value).await
579 }
580
581 async fn read_stream_next_seq(&self, stream_key: &str) -> Result<Option<u64>, DurabilityError> {
582 self.counters
583 .add(&self.counters.read_stream_next_seq_calls, 1);
584 self.inner.read_stream_next_seq(stream_key).await
585 }
586
587 async fn read_value(&self, key: &str) -> Result<Option<u64>, DurabilityError> {
588 self.counters.add(&self.counters.read_value_calls, 1);
589 self.inner.read_value(key).await
590 }
591
592 async fn scan_page(
593 &self,
594 prefix: &str,
595 cursor: Option<ScanCursor>,
596 limit: usize,
597 ) -> Result<ScanPage, DurabilityError> {
598 self.counters.add(&self.counters.scan_page_calls, 1);
599 let page = self.inner.scan_page(prefix, cursor, limit).await?;
600 self.counters
601 .add(&self.counters.scan_page_rows, widen(page.entries.len()));
602 Ok(page)
603 }
604
605 async fn flush(&self) -> Result<(), DurabilityError> {
606 self.counters.add(&self.counters.flushes, 1);
607 self.counters.flushed();
608 self.inner.flush().await
609 }
610
611 async fn truncate_before(
612 &self,
613 stream_key: &str,
614 before_seq: u64,
615 ) -> Result<u64, DurabilityError> {
616 self.counters.add(&self.counters.truncate_calls, 1);
617 let deleted = self.inner.truncate_before(stream_key, before_seq).await?;
618 self.counters.add(&self.counters.truncate_rows, deleted);
619 Ok(deleted)
620 }
621
622 async fn scan_routed(
623 &self,
624 route_key: &str,
625 from: &[u8],
626 to: &[u8],
627 ) -> Result<Vec<(Vec<u8>, Vec<u8>)>, DurabilityError> {
628 self.inner.scan_routed(route_key, from, to).await
629 }
630
631 async fn remove_routed_range(
632 &self,
633 route_key: &str,
634 from: &[u8],
635 to: &[u8],
636 ) -> Result<u64, DurabilityError> {
637 self.inner.remove_routed_range(route_key, from, to).await
638 }
639
640 fn store_counts(&self) -> Option<StoreCountsHandle> {
641 Some(self.handle())
642 }
643}
644
645#[cfg(test)]
646mod tests;