Skip to main content

flint_store/
counters.rs

1//! Requests a store has sent, by kind.
2//!
3//! What a backend bills by, and what an idle workspace must keep flat: a
4//! syncer that trades empty generations with a peer, or retries in a
5//! loop, shows up here before it shows up on an invoice. Counted once per
6//! request the store issues — a HEAD that precedes a COPY is two — and
7//! never per SDK retry of the same request.
8
9use std::sync::atomic::{AtomicU64, Ordering};
10
11/// A point-in-time copy of a store's request counts.
12#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
13pub struct RequestCounts {
14    pub get: u64,
15    pub head: u64,
16    pub put: u64,
17    pub copy: u64,
18    pub delete: u64,
19    pub list: u64,
20    /// CreateMultipartUpload, CompleteMultipartUpload, AbortMultipartUpload
21    /// (each part is a `put` or a `copy`).
22    pub multipart: u64,
23}
24
25impl RequestCounts {
26    pub fn total(&self) -> u64 {
27        self.get + self.head + self.put + self.copy + self.delete + self.list + self.multipart
28    }
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum RequestKind {
33    Get,
34    Head,
35    Put,
36    Copy,
37    Delete,
38    List,
39    Multipart,
40}
41
42/// Lock-free counters a store bumps as it sends.
43#[derive(Debug, Default)]
44pub struct RequestCounter {
45    get: AtomicU64,
46    head: AtomicU64,
47    put: AtomicU64,
48    copy: AtomicU64,
49    delete: AtomicU64,
50    list: AtomicU64,
51    multipart: AtomicU64,
52}
53
54impl RequestCounter {
55    pub fn bump(&self, kind: RequestKind) {
56        let c = match kind {
57            RequestKind::Get => &self.get,
58            RequestKind::Head => &self.head,
59            RequestKind::Put => &self.put,
60            RequestKind::Copy => &self.copy,
61            RequestKind::Delete => &self.delete,
62            RequestKind::List => &self.list,
63            RequestKind::Multipart => &self.multipart,
64        };
65        c.fetch_add(1, Ordering::Relaxed);
66    }
67
68    pub fn snapshot(&self) -> RequestCounts {
69        RequestCounts {
70            get: self.get.load(Ordering::Relaxed),
71            head: self.head.load(Ordering::Relaxed),
72            put: self.put.load(Ordering::Relaxed),
73            copy: self.copy.load(Ordering::Relaxed),
74            delete: self.delete.load(Ordering::Relaxed),
75            list: self.list.load(Ordering::Relaxed),
76            multipart: self.multipart.load(Ordering::Relaxed),
77        }
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use crate::memory::MemoryStore;
84    use crate::{crc64_nvme, GenerationStamps, ObjectStore, PutCondition};
85    use bytes::Bytes;
86
87    /// Each request lands in its own kind, and a store that counts says
88    /// so. The memory double folds its per-method calls into the kinds a
89    /// real store would have sent.
90    #[tokio::test]
91    async fn requests_are_counted_by_kind() {
92        let s = MemoryStore::new();
93        let st = GenerationStamps { generation: 1, epoch: 0, flush_uuid: "t".into(), boundary_source: None, posix: None };
94        let m = s.put_whole("k", Bytes::from_static(b"x"), &PutCondition::IfNoneMatchAny, &st, crc64_nvme(b"x")).await.unwrap();
95        s.head("k").await.unwrap();
96        s.get_whole("k", None).await.unwrap();
97        s.get_whole("k", None).await.unwrap();
98        s.list("").await.unwrap();
99        s.delete_if_match("k", &m.etag).await.unwrap();
100        let c = s.request_counts().expect("the double counts");
101        assert_eq!((c.put, c.head, c.get, c.list, c.delete, c.copy, c.multipart), (1, 1, 2, 1, 1, 0, 0), "{c:?}");
102        assert_eq!(c.total(), 6);
103    }
104}