foyer_common/
metrics.rs

1// Copyright 2026 foyer Project Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::{borrow::Cow, fmt::Debug};
16
17use mixtrics::metrics::{BoxedCounter, BoxedGauge, BoxedHistogram, BoxedRegistry, Buckets};
18
19#[expect(missing_docs)]
20pub struct Metrics {
21    /* in-memory cache metrics */
22    pub memory_insert: BoxedCounter,
23    pub memory_replace: BoxedCounter,
24    pub memory_hit: BoxedCounter,
25    pub memory_miss: BoxedCounter,
26    pub memory_remove: BoxedCounter,
27    pub memory_evict: BoxedCounter,
28    pub memory_reinsert: BoxedCounter,
29    pub memory_release: BoxedCounter,
30    pub memory_queue: BoxedCounter,
31    pub memory_fetch: BoxedCounter,
32
33    pub memory_usage: BoxedGauge,
34    pub memory_entries: BoxedGauge,
35
36    /* disk cache metrics */
37    pub storage_enqueue: BoxedCounter,
38    pub storage_hit: BoxedCounter,
39    pub storage_miss: BoxedCounter,
40    pub storage_throttled: BoxedCounter,
41    pub storage_delete: BoxedCounter,
42    pub storage_error: BoxedCounter,
43
44    pub storage_enqueue_duration: BoxedHistogram,
45    pub storage_hit_duration: BoxedHistogram,
46    pub storage_miss_duration: BoxedHistogram,
47    pub storage_throttled_duration: BoxedHistogram,
48    pub storage_delete_duration: BoxedHistogram,
49
50    pub storage_queue_rotate: BoxedCounter,
51    pub storage_queue_rotate_duration: BoxedHistogram,
52    pub storage_queue_buffer_overflow: BoxedCounter,
53    pub storage_queue_channel_overflow: BoxedCounter,
54
55    pub storage_disk_write: BoxedCounter,
56    pub storage_disk_read: BoxedCounter,
57    pub storage_disk_flush: BoxedCounter,
58
59    pub storage_disk_write_bytes: BoxedCounter,
60    pub storage_disk_read_bytes: BoxedCounter,
61
62    pub storage_disk_write_duration: BoxedHistogram,
63    pub storage_disk_read_duration: BoxedHistogram,
64    pub storage_disk_flush_duration: BoxedHistogram,
65
66    pub storage_block_engine_block_clean: BoxedGauge,
67    pub storage_block_engine_block_writing: BoxedGauge,
68    pub storage_block_engine_block_evictable: BoxedGauge,
69    pub storage_block_engine_block_reclaiming: BoxedGauge,
70
71    pub storage_block_engine_block_size_bytes: BoxedGauge,
72
73    pub storage_entry_serialize_duration: BoxedHistogram,
74    pub storage_entry_deserialize_duration: BoxedHistogram,
75
76    pub storage_block_engine_indexer_conflict: BoxedCounter,
77    pub storage_block_engine_enqueue_skip: BoxedCounter,
78    pub storage_block_engine_buffer_efficiency: BoxedHistogram,
79    pub storage_block_engine_recover_duration: BoxedHistogram,
80
81    /* hybrid cache metrics */
82    pub hybrid_insert: BoxedCounter,
83    pub hybrid_hit: BoxedCounter,
84    pub hybrid_miss: BoxedCounter,
85    pub hybrid_throttled: BoxedCounter,
86    pub hybrid_remove: BoxedCounter,
87    pub hybrid_error: BoxedCounter,
88
89    pub hybrid_insert_duration: BoxedHistogram,
90    pub hybrid_hit_duration: BoxedHistogram,
91    pub hybrid_miss_duration: BoxedHistogram,
92    pub hybrid_throttled_duration: BoxedHistogram,
93    pub hybrid_remove_duration: BoxedHistogram,
94    pub hybrid_error_duration: BoxedHistogram,
95}
96
97impl Debug for Metrics {
98    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99        f.debug_struct("Metrics").finish()
100    }
101}
102
103impl Metrics {
104    /// Create a new metric with the given name.
105    pub fn new(name: impl Into<Cow<'static, str>>, registry: &BoxedRegistry) -> Self {
106        let name = name.into();
107
108        /* in-memory cache metrics */
109
110        let foyer_memory_op_total = registry.register_counter_vec(
111            "foyer_memory_op_total".into(),
112            "foyer in-memory cache operations".into(),
113            &["name", "op"],
114        );
115        let foyer_memory_usage = registry.register_gauge_vec(
116            "foyer_memory_usage".into(),
117            "foyer in-memory cache usage".into(),
118            &["name"],
119        );
120        let foyer_memory_entries = registry.register_gauge_vec(
121            "foyer_memory_entries".into(),
122            "foyer in-memory cache entries".into(),
123            &["name"],
124        );
125
126        let memory_insert = foyer_memory_op_total.counter(&[name.clone(), "insert".into()]);
127        let memory_replace = foyer_memory_op_total.counter(&[name.clone(), "replace".into()]);
128        let memory_hit = foyer_memory_op_total.counter(&[name.clone(), "hit".into()]);
129        let memory_miss = foyer_memory_op_total.counter(&[name.clone(), "miss".into()]);
130        let memory_remove = foyer_memory_op_total.counter(&[name.clone(), "remove".into()]);
131        let memory_evict = foyer_memory_op_total.counter(&[name.clone(), "evict".into()]);
132        let memory_reinsert = foyer_memory_op_total.counter(&[name.clone(), "reinsert".into()]);
133        let memory_release = foyer_memory_op_total.counter(&[name.clone(), "release".into()]);
134        let memory_queue = foyer_memory_op_total.counter(&[name.clone(), "queue".into()]);
135        let memory_fetch = foyer_memory_op_total.counter(&[name.clone(), "fetch".into()]);
136
137        let memory_usage = foyer_memory_usage.gauge(std::slice::from_ref(&name));
138        let memory_entries = foyer_memory_entries.gauge(std::slice::from_ref(&name));
139
140        /* disk cache metrics */
141
142        let foyer_storage_op_total = registry.register_counter_vec(
143            "foyer_storage_op_total".into(),
144            "foyer disk cache operations".into(),
145            &["name", "op"],
146        );
147        let foyer_storage_op_duration = registry.register_histogram_vec_with_buckets(
148            "foyer_storage_op_duration".into(),
149            "foyer disk cache op durations".into(),
150            &["name", "op"],
151            // 1us ~ 4s
152            Buckets::exponential(0.000_001, 2.0, 23),
153        );
154
155        let foyer_storage_inner_op_total = registry.register_counter_vec(
156            "foyer_storage_inner_op_total".into(),
157            "foyer disk cache inner operations".into(),
158            &["name", "op"],
159        );
160        let foyer_storage_inner_op_duration = registry.register_histogram_vec_with_buckets(
161            "foyer_storage_inner_op_duration".into(),
162            "foyer disk cache inner op durations".into(),
163            &["name", "op"],
164            // 1us ~ 16s
165            Buckets::exponential(0.000_001, 2.0, 25),
166        );
167
168        let foyer_storage_disk_io_total = registry.register_counter_vec(
169            "foyer_storage_disk_io_total".into(),
170            "foyer disk cache disk operations".into(),
171            &["name", "op"],
172        );
173        let foyer_storage_disk_io_bytes = registry.register_counter_vec(
174            "foyer_storage_disk_io_bytes_total".into(),
175            "foyer disk cache disk io bytes".into(),
176            &["name", "op"],
177        );
178        let foyer_storage_disk_io_duration = registry.register_histogram_vec_with_buckets(
179            "foyer_storage_disk_io_duration".into(),
180            "foyer disk cache disk io duration".into(),
181            &["name", "op"],
182            // 1us ~ 4s
183            Buckets::exponential(0.000_001, 2.0, 23),
184        );
185
186        let foyer_storage_block_engine_block = registry.register_gauge_vec(
187            "foyer_storage_block_engine_block".into(),
188            "foyer large object disk cache blocks".into(),
189            &["name", "type"],
190        );
191        let foyer_storage_block_engine_block_size_bytes = registry.register_gauge_vec(
192            "foyer_storage_block_engine_block_size_bytes".into(),
193            "foyer large object disk cache blocks sizes".into(),
194            &["name"],
195        );
196
197        let foyer_storage_entry_serde_duration = registry.register_histogram_vec_with_buckets(
198            "foyer_storage_entry_serde_duration".into(),
199            "foyer disk cache entry serde durations".into(),
200            &["name", "op"],
201            // 10ns ~ 40ms
202            Buckets::exponential(0.000_000_01, 2.0, 23),
203        );
204
205        let foyer_storage_block_engine_op_total = registry.register_counter_vec(
206            "foyer_storage_block_engine_op_total".into(),
207            "foyer large object disk cache operations".into(),
208            &["name", "op"],
209        );
210
211        let foyer_storage_block_engine_buffer_efficiency = registry.register_histogram_vec_with_buckets(
212            "foyer_storage_block_engine_buffer_efficiency".into(),
213            "foyer large object disk cache buffer efficiency".into(),
214            &["name"],
215            // 0% ~ 100%
216            Buckets::linear(0.1, 0.1, 10),
217        );
218
219        let foyer_storage_block_engine_recover_duration = registry.register_histogram_vec_with_buckets(
220            "foyer_storage_block_engine_recover_duration".into(),
221            "foyer large object disk cache recover duration".into(),
222            &["name"],
223            // 1ms ~ 1000s
224            Buckets::exponential(0.001, 2.0, 21),
225        );
226
227        let storage_enqueue = foyer_storage_op_total.counter(&[name.clone(), "enqueue".into()]);
228        let storage_hit = foyer_storage_op_total.counter(&[name.clone(), "hit".into()]);
229        let storage_miss = foyer_storage_op_total.counter(&[name.clone(), "miss".into()]);
230        let storage_delete = foyer_storage_op_total.counter(&[name.clone(), "delete".into()]);
231        let storage_throttled = foyer_storage_op_total.counter(&[name.clone(), "throttled".into()]);
232        let storage_error = foyer_storage_op_total.counter(&[name.clone(), "error".into()]);
233
234        let storage_enqueue_duration = foyer_storage_op_duration.histogram(&[name.clone(), "enqueue".into()]);
235        let storage_hit_duration = foyer_storage_op_duration.histogram(&[name.clone(), "hit".into()]);
236        let storage_miss_duration = foyer_storage_op_duration.histogram(&[name.clone(), "miss".into()]);
237        let storage_throttled_duration = foyer_storage_op_duration.histogram(&[name.clone(), "throttled".into()]);
238        let storage_delete_duration = foyer_storage_op_duration.histogram(&[name.clone(), "delete".into()]);
239
240        let storage_queue_rotate = foyer_storage_inner_op_total.counter(&[name.clone(), "queue_rotate".into()]);
241        let storage_queue_buffer_overflow =
242            foyer_storage_inner_op_total.counter(&[name.clone(), "buffer_overflow".into()]);
243        let storage_queue_channel_overflow =
244            foyer_storage_inner_op_total.counter(&[name.clone(), "channel_overflow".into()]);
245
246        let storage_queue_rotate_duration =
247            foyer_storage_inner_op_duration.histogram(&[name.clone(), "queue_rotate".into()]);
248
249        let storage_disk_write = foyer_storage_disk_io_total.counter(&[name.clone(), "write".into()]);
250        let storage_disk_read = foyer_storage_disk_io_total.counter(&[name.clone(), "read".into()]);
251        let storage_disk_flush = foyer_storage_disk_io_total.counter(&[name.clone(), "flush".into()]);
252
253        let storage_disk_write_bytes = foyer_storage_disk_io_bytes.counter(&[name.clone(), "write".into()]);
254        let storage_disk_read_bytes = foyer_storage_disk_io_bytes.counter(&[name.clone(), "read".into()]);
255
256        let storage_disk_write_duration = foyer_storage_disk_io_duration.histogram(&[name.clone(), "write".into()]);
257        let storage_disk_read_duration = foyer_storage_disk_io_duration.histogram(&[name.clone(), "read".into()]);
258        let storage_disk_flush_duration = foyer_storage_disk_io_duration.histogram(&[name.clone(), "flush".into()]);
259
260        let storage_block_engine_block_clean = foyer_storage_block_engine_block.gauge(&[name.clone(), "clean".into()]);
261        let storage_block_engine_block_writing =
262            foyer_storage_block_engine_block.gauge(&[name.clone(), "writing".into()]);
263        let storage_block_engine_block_evictable =
264            foyer_storage_block_engine_block.gauge(&[name.clone(), "evictable".into()]);
265        let storage_block_engine_block_reclaiming =
266            foyer_storage_block_engine_block.gauge(&[name.clone(), "reclaiming".into()]);
267
268        let storage_block_engine_block_size_bytes =
269            foyer_storage_block_engine_block_size_bytes.gauge(std::slice::from_ref(&name));
270
271        let storage_entry_serialize_duration =
272            foyer_storage_entry_serde_duration.histogram(&[name.clone(), "serialize".into()]);
273        let storage_entry_deserialize_duration =
274            foyer_storage_entry_serde_duration.histogram(&[name.clone(), "deserialize".into()]);
275
276        let storage_block_engine_indexer_conflict =
277            foyer_storage_block_engine_op_total.counter(&[name.clone(), "indexer_conflict".into()]);
278        let storage_block_engine_enqueue_skip =
279            foyer_storage_block_engine_op_total.counter(&[name.clone(), "enqueue_skip".into()]);
280        let storage_block_engine_buffer_efficiency =
281            foyer_storage_block_engine_buffer_efficiency.histogram(std::slice::from_ref(&name));
282        let storage_block_engine_recover_duration =
283            foyer_storage_block_engine_recover_duration.histogram(std::slice::from_ref(&name));
284
285        /* hybrid cache metrics */
286
287        let foyer_hybrid_op_total = registry.register_counter_vec(
288            "foyer_hybrid_op_total".into(),
289            "foyer hybrid cache operations".into(),
290            &["name", "op"],
291        );
292        let foyer_hybrid_op_duration = registry.register_histogram_vec(
293            "foyer_hybrid_op_duration".into(),
294            "foyer hybrid cache operation durations".into(),
295            &["name", "op"],
296        );
297
298        let hybrid_insert = foyer_hybrid_op_total.counter(&[name.clone(), "insert".into()]);
299        let hybrid_hit = foyer_hybrid_op_total.counter(&[name.clone(), "hit".into()]);
300        let hybrid_miss = foyer_hybrid_op_total.counter(&[name.clone(), "miss".into()]);
301        let hybrid_throttled = foyer_hybrid_op_total.counter(&[name.clone(), "throttled".into()]);
302        let hybrid_remove = foyer_hybrid_op_total.counter(&[name.clone(), "remove".into()]);
303        let hybrid_error = foyer_hybrid_op_total.counter(&[name.clone(), "error".into()]);
304
305        let hybrid_insert_duration = foyer_hybrid_op_duration.histogram(&[name.clone(), "insert".into()]);
306        let hybrid_hit_duration = foyer_hybrid_op_duration.histogram(&[name.clone(), "hit".into()]);
307        let hybrid_miss_duration = foyer_hybrid_op_duration.histogram(&[name.clone(), "miss".into()]);
308        let hybrid_throttled_duration = foyer_hybrid_op_duration.histogram(&[name.clone(), "throttled".into()]);
309        let hybrid_remove_duration = foyer_hybrid_op_duration.histogram(&[name.clone(), "remove".into()]);
310        let hybrid_error_duration = foyer_hybrid_op_duration.histogram(&[name.clone(), "error".into()]);
311
312        Self {
313            memory_insert,
314            memory_replace,
315            memory_hit,
316            memory_miss,
317            memory_remove,
318            memory_evict,
319            memory_reinsert,
320            memory_release,
321            memory_queue,
322            memory_fetch,
323            memory_usage,
324            memory_entries,
325
326            storage_enqueue,
327            storage_hit,
328            storage_miss,
329            storage_throttled,
330            storage_delete,
331            storage_error,
332            storage_enqueue_duration,
333            storage_hit_duration,
334            storage_miss_duration,
335            storage_throttled_duration,
336            storage_delete_duration,
337            storage_queue_rotate,
338            storage_queue_rotate_duration,
339            storage_queue_buffer_overflow,
340            storage_queue_channel_overflow,
341            storage_disk_write,
342            storage_disk_read,
343            storage_disk_flush,
344            storage_disk_write_bytes,
345            storage_disk_read_bytes,
346            storage_disk_write_duration,
347            storage_disk_read_duration,
348            storage_disk_flush_duration,
349            storage_block_engine_block_clean,
350            storage_block_engine_block_writing,
351            storage_block_engine_block_evictable,
352            storage_block_engine_block_reclaiming,
353            storage_block_engine_block_size_bytes,
354            storage_entry_serialize_duration,
355            storage_entry_deserialize_duration,
356            storage_block_engine_indexer_conflict,
357            storage_block_engine_enqueue_skip,
358            storage_block_engine_buffer_efficiency,
359            storage_block_engine_recover_duration,
360
361            hybrid_insert,
362            hybrid_hit,
363            hybrid_miss,
364            hybrid_throttled,
365            hybrid_throttled_duration,
366            hybrid_remove,
367            hybrid_error,
368            hybrid_insert_duration,
369            hybrid_hit_duration,
370            hybrid_miss_duration,
371            hybrid_remove_duration,
372            hybrid_error_duration,
373        }
374    }
375
376    /// Build noop metrics.
377    ///
378    /// Note: `noop` is only supposed to be called by other foyer components.
379    #[doc(hidden)]
380    pub fn noop() -> Self {
381        let registry: BoxedRegistry = Box::new(mixtrics::registry::noop::NoopMetricsRegistry);
382        Self::new("test", &registry)
383    }
384}
385
386#[cfg(test)]
387mod tests {
388    use mixtrics::metrics::BoxedRegistry;
389
390    use super::Metrics;
391
392    fn test_fn(registry: &BoxedRegistry) {
393        Metrics::new("test", registry);
394    }
395
396    mixtrics::test! { test_fn }
397}