1use std::{
5 borrow::{Borrow, Cow},
6 fmt::Debug,
7 hash::Hash,
8 num::NonZeroUsize,
9 sync::{
10 Arc,
11 atomic::{AtomicUsize, Ordering},
12 },
13};
14
15use get_size2::GetSize;
16use hashlink::LruCache;
17use parking_lot::RwLock;
18use prometheus_client::{
19 collector::Collector,
20 encoding::{DescriptorEncoder, EncodeMetric},
21 metrics::gauge::Gauge,
22 registry::Unit,
23};
24
25pub trait KeyConstraints:
26 GetSize + Debug + Send + Sync + Hash + PartialEq + Eq + Clone + 'static
27{
28}
29
30impl<T> KeyConstraints for T where
31 T: GetSize + Debug + Send + Sync + Hash + PartialEq + Eq + Clone + 'static
32{
33}
34
35pub trait LruValueConstraints: GetSize + Debug + Send + Sync + Clone + 'static {}
36
37impl<T> LruValueConstraints for T where T: GetSize + Debug + Send + Sync + Clone + 'static {}
38
39#[derive(Debug, Clone)]
40pub struct SizeTrackingLruCache<K, V>
41where
42 K: KeyConstraints,
43 V: LruValueConstraints,
44{
45 cache_id: usize,
46 cache_name: Cow<'static, str>,
47 cache: Arc<RwLock<LruCache<K, V>>>,
48}
49
50impl<K, V> SizeTrackingLruCache<K, V>
51where
52 K: KeyConstraints,
53 V: LruValueConstraints,
54{
55 fn register_metrics(&self) {
56 crate::metrics::register_collector(Box::new(self.clone()));
57 }
58
59 fn new_inner(cache_name: Cow<'static, str>, capacity: Option<NonZeroUsize>) -> Self {
60 static ID_GENERATOR: AtomicUsize = AtomicUsize::new(0);
61
62 Self {
63 cache_id: ID_GENERATOR.fetch_add(1, Ordering::Relaxed),
64 cache_name,
65 #[allow(clippy::disallowed_methods)]
66 cache: Arc::new(RwLock::new(
67 capacity
68 .map(From::from)
69 .map(LruCache::new)
70 .unwrap_or_else(LruCache::new_unbounded),
72 )),
73 }
74 }
75
76 pub fn new_without_metrics_registry(
77 cache_name: Cow<'static, str>,
78 capacity: NonZeroUsize,
79 ) -> Self {
80 Self::new_inner(cache_name, Some(capacity))
81 }
82
83 pub fn new_with_metrics(cache_name: Cow<'static, str>, capacity: NonZeroUsize) -> Self {
84 let c = Self::new_without_metrics_registry(cache_name, capacity);
85 c.register_metrics();
86 c
87 }
88
89 pub fn unbounded_without_metrics_registry(cache_name: Cow<'static, str>) -> Self {
90 Self::new_inner(cache_name, None)
91 }
92
93 pub fn unbounded_with_metrics(cache_name: Cow<'static, str>) -> Self {
94 let c = Self::unbounded_without_metrics_registry(cache_name);
95 c.register_metrics();
96 c
97 }
98
99 pub fn cache(&self) -> &Arc<RwLock<LruCache<K, V>>> {
100 &self.cache
101 }
102
103 pub fn push(&self, k: K, v: V) -> Option<V> {
104 self.cache.write().insert(k, v)
105 }
106
107 pub fn contains<Q>(&self, k: &Q) -> bool
108 where
109 K: Borrow<Q>,
110 Q: Hash + Eq + ?Sized,
111 {
112 self.cache.read().contains_key(k)
113 }
114
115 pub fn get_cloned<Q>(&self, k: &Q) -> Option<V>
116 where
117 K: Borrow<Q>,
118 Q: Hash + Eq + ?Sized,
119 {
120 self.cache.write().get(k).cloned()
121 }
122
123 pub fn peek_cloned<Q>(&self, k: &Q) -> Option<V>
124 where
125 K: Borrow<Q>,
126 Q: Hash + Eq + ?Sized,
127 {
128 self.cache.read().peek(k).cloned()
129 }
130
131 pub fn pop_lru(&self) -> Option<(K, V)> {
132 self.cache.write().remove_lru()
133 }
134
135 pub fn len(&self) -> usize {
136 self.cache.read().len()
137 }
138
139 pub fn cap(&self) -> usize {
140 self.cache.read().capacity()
141 }
142
143 pub(crate) fn size_in_bytes(&self) -> usize {
144 let mut size = 0_usize;
145 for (k, v) in self.cache.read().iter() {
146 size = size
147 .saturating_add(k.get_size())
148 .saturating_add(v.get_size());
149 }
150 size
151 }
152}
153
154impl<K, V> Collector for SizeTrackingLruCache<K, V>
155where
156 K: KeyConstraints,
157 V: LruValueConstraints,
158{
159 fn encode(&self, mut encoder: DescriptorEncoder) -> Result<(), std::fmt::Error> {
160 {
161 let size_in_bytes = {
162 let g: Gauge = Default::default();
163 g.set(self.size_in_bytes() as _);
164 g
165 };
166 let size_metric_name = format!("cache_{}_{}_size", self.cache_name, self.cache_id);
167 let size_metric_help = format!(
168 "Size of LruCache {}_{} in bytes",
169 self.cache_name, self.cache_id
170 );
171 let size_metric_encoder = encoder.encode_descriptor(
172 &size_metric_name,
173 &size_metric_help,
174 Some(&Unit::Bytes),
175 size_in_bytes.metric_type(),
176 )?;
177 size_in_bytes.encode(size_metric_encoder)?;
178 }
179 {
180 let len_metric_name = format!("{}_{}_len", self.cache_name, self.cache_id);
181 let len_metric_help =
182 format!("Length of LruCache {}_{}", self.cache_name, self.cache_id);
183 let len: Gauge = Default::default();
184 len.set(self.len() as _);
185 let len_metric_encoder = encoder.encode_descriptor(
186 &len_metric_name,
187 &len_metric_help,
188 None,
189 len.metric_type(),
190 )?;
191 len.encode(len_metric_encoder)?;
192 }
193 {
194 let cap_metric_name = format!("{}_{}_cap", self.cache_name, self.cache_id);
195 let cap_metric_help =
196 format!("Capacity of LruCache {}_{}", self.cache_name, self.cache_id);
197 let cap: Gauge = Default::default();
198 cap.set(self.cap() as _);
199 let cap_metric_encoder = encoder.encode_descriptor(
200 &cap_metric_name,
201 &cap_metric_help,
202 None,
203 cap.metric_type(),
204 )?;
205 cap.encode(cap_metric_encoder)?;
206 }
207
208 Ok(())
209 }
210}