1use crate::traits::BlockStore;
7use async_trait::async_trait;
8use ipfrs_core::{Block, Cid, Result};
9use serde::{Deserialize, Serialize};
10use std::sync::atomic::{AtomicU64, Ordering};
11use std::sync::Arc;
12use std::time::{Duration, Instant};
13
14#[derive(Debug, Clone, Default, Serialize, Deserialize)]
16pub struct StorageMetrics {
17 pub put_count: u64,
19 pub get_count: u64,
21 pub has_count: u64,
23 pub delete_count: u64,
25 pub get_hits: u64,
27 pub get_misses: u64,
29 pub bytes_written: u64,
31 pub bytes_read: u64,
33 pub avg_put_latency_us: u64,
35 pub avg_get_latency_us: u64,
37 pub avg_has_latency_us: u64,
39 pub peak_put_latency_us: u64,
41 pub peak_get_latency_us: u64,
43 pub error_count: u64,
45 pub batch_op_count: u64,
47 pub batch_items_count: u64,
49 pub avg_batch_size: u64,
51}
52
53impl StorageMetrics {
54 pub fn cache_hit_rate(&self) -> f64 {
56 let total = self.get_hits + self.get_misses;
57 if total == 0 {
58 0.0
59 } else {
60 self.get_hits as f64 / total as f64
61 }
62 }
63
64 pub fn avg_operation_latency_us(&self) -> u64 {
66 let total_ops = self.put_count + self.get_count + self.has_count;
67 let total_latency = (self.put_count * self.avg_put_latency_us)
68 + (self.get_count * self.avg_get_latency_us)
69 + (self.has_count * self.avg_has_latency_us);
70 total_latency.checked_div(total_ops).unwrap_or(0)
71 }
72
73 pub fn ops_per_second(&self, duration: Duration) -> f64 {
75 let total_ops = self.put_count + self.get_count + self.has_count + self.delete_count;
76 let seconds = duration.as_secs_f64();
77 if seconds > 0.0 {
78 total_ops as f64 / seconds
79 } else {
80 0.0
81 }
82 }
83
84 pub fn batch_efficiency(&self) -> f64 {
86 let total_ops = self.put_count + self.get_count + self.has_count + self.delete_count;
87 if total_ops == 0 {
88 0.0
89 } else {
90 self.batch_items_count as f64 / total_ops as f64
91 }
92 }
93
94 pub fn write_throughput_bps(&self, duration: Duration) -> f64 {
96 let seconds = duration.as_secs_f64();
97 if seconds > 0.0 {
98 self.bytes_written as f64 / seconds
99 } else {
100 0.0
101 }
102 }
103
104 pub fn read_throughput_bps(&self, duration: Duration) -> f64 {
106 let seconds = duration.as_secs_f64();
107 if seconds > 0.0 {
108 self.bytes_read as f64 / seconds
109 } else {
110 0.0
111 }
112 }
113}
114
115struct MetricsCollector {
117 put_count: AtomicU64,
118 get_count: AtomicU64,
119 has_count: AtomicU64,
120 delete_count: AtomicU64,
121 get_hits: AtomicU64,
122 get_misses: AtomicU64,
123 bytes_written: AtomicU64,
124 bytes_read: AtomicU64,
125 put_latency_sum: AtomicU64,
126 get_latency_sum: AtomicU64,
127 has_latency_sum: AtomicU64,
128 peak_put_latency: AtomicU64,
129 peak_get_latency: AtomicU64,
130 error_count: AtomicU64,
131 batch_op_count: AtomicU64,
132 batch_items_count: AtomicU64,
133 start_time: Instant,
134}
135
136impl Default for MetricsCollector {
137 fn default() -> Self {
138 Self {
139 put_count: AtomicU64::new(0),
140 get_count: AtomicU64::new(0),
141 has_count: AtomicU64::new(0),
142 delete_count: AtomicU64::new(0),
143 get_hits: AtomicU64::new(0),
144 get_misses: AtomicU64::new(0),
145 bytes_written: AtomicU64::new(0),
146 bytes_read: AtomicU64::new(0),
147 put_latency_sum: AtomicU64::new(0),
148 get_latency_sum: AtomicU64::new(0),
149 has_latency_sum: AtomicU64::new(0),
150 peak_put_latency: AtomicU64::new(0),
151 peak_get_latency: AtomicU64::new(0),
152 error_count: AtomicU64::new(0),
153 batch_op_count: AtomicU64::new(0),
154 batch_items_count: AtomicU64::new(0),
155 start_time: Instant::now(),
156 }
157 }
158}
159
160impl MetricsCollector {
161 fn snapshot(&self) -> StorageMetrics {
162 let put_count = self.put_count.load(Ordering::Relaxed);
163 let get_count = self.get_count.load(Ordering::Relaxed);
164 let has_count = self.has_count.load(Ordering::Relaxed);
165 let batch_op_count = self.batch_op_count.load(Ordering::Relaxed);
166 let batch_items_count = self.batch_items_count.load(Ordering::Relaxed);
167
168 StorageMetrics {
169 put_count,
170 get_count,
171 has_count,
172 delete_count: self.delete_count.load(Ordering::Relaxed),
173 get_hits: self.get_hits.load(Ordering::Relaxed),
174 get_misses: self.get_misses.load(Ordering::Relaxed),
175 bytes_written: self.bytes_written.load(Ordering::Relaxed),
176 bytes_read: self.bytes_read.load(Ordering::Relaxed),
177 avg_put_latency_us: self
178 .put_latency_sum
179 .load(Ordering::Relaxed)
180 .checked_div(put_count)
181 .unwrap_or(0),
182 avg_get_latency_us: self
183 .get_latency_sum
184 .load(Ordering::Relaxed)
185 .checked_div(get_count)
186 .unwrap_or(0),
187 avg_has_latency_us: self
188 .has_latency_sum
189 .load(Ordering::Relaxed)
190 .checked_div(has_count)
191 .unwrap_or(0),
192 peak_put_latency_us: self.peak_put_latency.load(Ordering::Relaxed),
193 peak_get_latency_us: self.peak_get_latency.load(Ordering::Relaxed),
194 error_count: self.error_count.load(Ordering::Relaxed),
195 batch_op_count,
196 batch_items_count,
197 avg_batch_size: batch_items_count.checked_div(batch_op_count).unwrap_or(0),
198 }
199 }
200
201 fn record_put(&self, bytes: u64, latency_us: u64) {
202 self.put_count.fetch_add(1, Ordering::Relaxed);
203 self.bytes_written.fetch_add(bytes, Ordering::Relaxed);
204 self.put_latency_sum
205 .fetch_add(latency_us, Ordering::Relaxed);
206
207 let mut current_peak = self.peak_put_latency.load(Ordering::Relaxed);
208 while latency_us > current_peak {
209 match self.peak_put_latency.compare_exchange_weak(
210 current_peak,
211 latency_us,
212 Ordering::Relaxed,
213 Ordering::Relaxed,
214 ) {
215 Ok(_) => break,
216 Err(x) => current_peak = x,
217 }
218 }
219 }
220
221 fn record_get(&self, bytes: Option<u64>, latency_us: u64) {
222 self.get_count.fetch_add(1, Ordering::Relaxed);
223
224 if let Some(bytes) = bytes {
225 self.get_hits.fetch_add(1, Ordering::Relaxed);
226 self.bytes_read.fetch_add(bytes, Ordering::Relaxed);
227 } else {
228 self.get_misses.fetch_add(1, Ordering::Relaxed);
229 }
230
231 self.get_latency_sum
232 .fetch_add(latency_us, Ordering::Relaxed);
233
234 let mut current_peak = self.peak_get_latency.load(Ordering::Relaxed);
235 while latency_us > current_peak {
236 match self.peak_get_latency.compare_exchange_weak(
237 current_peak,
238 latency_us,
239 Ordering::Relaxed,
240 Ordering::Relaxed,
241 ) {
242 Ok(_) => break,
243 Err(x) => current_peak = x,
244 }
245 }
246 }
247
248 fn record_has(&self, latency_us: u64) {
249 self.has_count.fetch_add(1, Ordering::Relaxed);
250 self.has_latency_sum
251 .fetch_add(latency_us, Ordering::Relaxed);
252 }
253
254 fn record_delete(&self) {
255 self.delete_count.fetch_add(1, Ordering::Relaxed);
256 }
257
258 fn record_error(&self) {
259 self.error_count.fetch_add(1, Ordering::Relaxed);
260 }
261
262 fn record_batch(&self, batch_size: usize) {
263 self.batch_op_count.fetch_add(1, Ordering::Relaxed);
264 self.batch_items_count
265 .fetch_add(batch_size as u64, Ordering::Relaxed);
266 }
267
268 fn uptime(&self) -> Duration {
269 self.start_time.elapsed()
270 }
271
272 fn reset(&self) {
273 self.put_count.store(0, Ordering::Relaxed);
274 self.get_count.store(0, Ordering::Relaxed);
275 self.has_count.store(0, Ordering::Relaxed);
276 self.delete_count.store(0, Ordering::Relaxed);
277 self.get_hits.store(0, Ordering::Relaxed);
278 self.get_misses.store(0, Ordering::Relaxed);
279 self.bytes_written.store(0, Ordering::Relaxed);
280 self.bytes_read.store(0, Ordering::Relaxed);
281 self.put_latency_sum.store(0, Ordering::Relaxed);
282 self.get_latency_sum.store(0, Ordering::Relaxed);
283 self.has_latency_sum.store(0, Ordering::Relaxed);
284 self.peak_put_latency.store(0, Ordering::Relaxed);
285 self.peak_get_latency.store(0, Ordering::Relaxed);
286 self.error_count.store(0, Ordering::Relaxed);
287 self.batch_op_count.store(0, Ordering::Relaxed);
288 self.batch_items_count.store(0, Ordering::Relaxed);
289 }
290}
291
292pub struct MetricsBlockStore<S: BlockStore> {
294 inner: S,
295 metrics: Arc<MetricsCollector>,
296}
297
298impl<S: BlockStore> MetricsBlockStore<S> {
299 pub fn new(inner: S) -> Self {
301 Self {
302 inner,
303 metrics: Arc::new(MetricsCollector::default()),
304 }
305 }
306
307 pub fn metrics(&self) -> StorageMetrics {
309 self.metrics.snapshot()
310 }
311
312 pub fn uptime(&self) -> Duration {
314 self.metrics.uptime()
315 }
316
317 pub fn reset_metrics(&self) {
322 self.metrics.reset();
323 }
324
325 pub fn inner(&self) -> &S {
327 &self.inner
328 }
329
330 pub fn into_inner(self) -> S {
332 self.inner
333 }
334}
335
336#[async_trait]
337impl<S: BlockStore> BlockStore for MetricsBlockStore<S> {
338 async fn put(&self, block: &Block) -> Result<()> {
339 let start = Instant::now();
340 let result = self.inner.put(block).await;
341 let latency_us = start.elapsed().as_micros() as u64;
342
343 match &result {
344 Ok(_) => {
345 self.metrics
346 .record_put(block.data().len() as u64, latency_us);
347 }
348 Err(_) => {
349 self.metrics.record_error();
350 }
351 }
352
353 result
354 }
355
356 async fn put_many(&self, blocks: &[Block]) -> Result<()> {
357 let start = Instant::now();
358 let result = self.inner.put_many(blocks).await;
359 let latency_us = start.elapsed().as_micros() as u64;
360
361 match &result {
362 Ok(_) => {
363 self.metrics.record_batch(blocks.len());
365 let avg_latency = latency_us / blocks.len().max(1) as u64;
367 for block in blocks {
368 self.metrics
369 .record_put(block.data().len() as u64, avg_latency);
370 }
371 }
372 Err(_) => {
373 self.metrics.record_error();
374 }
375 }
376
377 result
378 }
379
380 async fn get(&self, cid: &Cid) -> Result<Option<Block>> {
381 let start = Instant::now();
382 let result = self.inner.get(cid).await;
383 let latency_us = start.elapsed().as_micros() as u64;
384
385 match &result {
386 Ok(Some(block)) => {
387 self.metrics
388 .record_get(Some(block.data().len() as u64), latency_us);
389 }
390 Ok(None) => {
391 self.metrics.record_get(None, latency_us);
392 }
393 Err(_) => {
394 self.metrics.record_error();
395 }
396 }
397
398 result
399 }
400
401 async fn get_many(&self, cids: &[Cid]) -> Result<Vec<Option<Block>>> {
402 let start = Instant::now();
403 let result = self.inner.get_many(cids).await;
404 let latency_us = start.elapsed().as_micros() as u64;
405
406 match &result {
407 Ok(blocks) => {
408 self.metrics.record_batch(blocks.len());
410 let avg_latency = latency_us / blocks.len().max(1) as u64;
411 for block in blocks {
412 match block {
413 Some(b) => {
414 self.metrics
415 .record_get(Some(b.data().len() as u64), avg_latency);
416 }
417 None => {
418 self.metrics.record_get(None, avg_latency);
419 }
420 }
421 }
422 }
423 Err(_) => {
424 self.metrics.record_error();
425 }
426 }
427
428 result
429 }
430
431 async fn has(&self, cid: &Cid) -> Result<bool> {
432 let start = Instant::now();
433 let result = self.inner.has(cid).await;
434 let latency_us = start.elapsed().as_micros() as u64;
435
436 match &result {
437 Ok(_) => {
438 self.metrics.record_has(latency_us);
439 }
440 Err(_) => {
441 self.metrics.record_error();
442 }
443 }
444
445 result
446 }
447
448 async fn has_many(&self, cids: &[Cid]) -> Result<Vec<bool>> {
449 let start = Instant::now();
450 let result = self.inner.has_many(cids).await;
451 let latency_us = start.elapsed().as_micros() as u64;
452
453 match &result {
454 Ok(results) => {
455 self.metrics.record_batch(results.len());
457 let avg_latency = latency_us / results.len().max(1) as u64;
458 for _ in results {
459 self.metrics.record_has(avg_latency);
460 }
461 }
462 Err(_) => {
463 self.metrics.record_error();
464 }
465 }
466
467 result
468 }
469
470 async fn delete(&self, cid: &Cid) -> Result<()> {
471 let result = self.inner.delete(cid).await;
472
473 match &result {
474 Ok(_) => {
475 self.metrics.record_delete();
476 }
477 Err(_) => {
478 self.metrics.record_error();
479 }
480 }
481
482 result
483 }
484
485 async fn delete_many(&self, cids: &[Cid]) -> Result<()> {
486 let result = self.inner.delete_many(cids).await;
487
488 match &result {
489 Ok(_) => {
490 self.metrics.record_batch(cids.len());
492 for _ in cids {
493 self.metrics.record_delete();
494 }
495 }
496 Err(_) => {
497 self.metrics.record_error();
498 }
499 }
500
501 result
502 }
503
504 fn list_cids(&self) -> Result<Vec<Cid>> {
505 self.inner.list_cids()
506 }
507
508 fn len(&self) -> usize {
509 self.inner.len()
510 }
511
512 fn is_empty(&self) -> bool {
513 self.inner.is_empty()
514 }
515
516 async fn flush(&self) -> Result<()> {
517 self.inner.flush().await
518 }
519
520 async fn close(&self) -> Result<()> {
521 self.inner.close().await
522 }
523}
524
525#[cfg(test)]
526mod tests {
527 use super::*;
528 use crate::MemoryBlockStore;
529 use bytes::Bytes;
530
531 #[tokio::test]
532 async fn test_metrics_tracking() {
533 let store = MemoryBlockStore::new();
534 let metrics_store = MetricsBlockStore::new(store);
535
536 let block = Block::new(Bytes::from("test data")).unwrap();
538 metrics_store.put(&block).await.unwrap();
539
540 let metrics = metrics_store.metrics();
541 assert_eq!(metrics.put_count, 1);
542 assert_eq!(metrics.bytes_written, 9); let retrieved = metrics_store.get(block.cid()).await.unwrap();
546 assert!(retrieved.is_some());
547
548 let metrics = metrics_store.metrics();
549 assert_eq!(metrics.get_count, 1);
550 assert_eq!(metrics.get_hits, 1);
551 assert_eq!(metrics.get_misses, 0);
552 assert_eq!(metrics.bytes_read, 9);
553
554 assert_eq!(metrics.cache_hit_rate(), 1.0);
556 }
557
558 #[tokio::test]
559 async fn test_metrics_cache_miss() {
560 let store = MemoryBlockStore::new();
561 let metrics_store = MetricsBlockStore::new(store);
562
563 let fake_block = Block::new(Bytes::from("fake")).unwrap();
565 let result = metrics_store.get(fake_block.cid()).await.unwrap();
566 assert!(result.is_none());
567
568 let metrics = metrics_store.metrics();
569 assert_eq!(metrics.get_count, 1);
570 assert_eq!(metrics.get_hits, 0);
571 assert_eq!(metrics.get_misses, 1);
572 assert_eq!(metrics.cache_hit_rate(), 0.0);
573 }
574
575 #[tokio::test]
576 async fn test_metrics_latency_tracking() {
577 let store = MemoryBlockStore::new();
578 let metrics_store = MetricsBlockStore::new(store);
579
580 for i in 0..5 {
582 let block = Block::new(Bytes::from(format!("block {}", i))).unwrap();
583 tokio::time::sleep(std::time::Duration::from_micros(10)).await;
585 metrics_store.put(&block).await.unwrap();
586 }
587
588 let metrics = metrics_store.metrics();
589 assert_eq!(metrics.put_count, 5);
590 assert!(metrics.avg_put_latency_us > 0);
591 assert!(metrics.peak_put_latency_us > 0);
592 }
593
594 #[test]
595 fn test_storage_metrics_calculations() {
596 let metrics = StorageMetrics {
597 put_count: 100,
598 get_count: 200,
599 has_count: 50,
600 delete_count: 10,
601 get_hits: 180,
602 get_misses: 20,
603 bytes_written: 10000,
604 bytes_read: 18000,
605 avg_put_latency_us: 100,
606 avg_get_latency_us: 50,
607 avg_has_latency_us: 10,
608 peak_put_latency_us: 500,
609 peak_get_latency_us: 200,
610 error_count: 5,
611 batch_op_count: 10,
612 batch_items_count: 50,
613 avg_batch_size: 5,
614 };
615
616 assert_eq!(metrics.cache_hit_rate(), 0.9); let avg_latency = metrics.avg_operation_latency_us();
621 let expected = (100 * 100 + 200 * 50 + 50 * 10) / 350;
622 assert_eq!(avg_latency, expected);
623
624 let duration = Duration::from_secs(10);
626 let ops_per_sec = metrics.ops_per_second(duration);
627 assert_eq!(ops_per_sec, 36.0); }
629}