@@ -6,6 +6,7 @@ use std::{
66 fs:: { read_dir, File } ,
77 io:: { BufRead , BufReader } ,
88 str:: FromStr ,
9+ time:: { Duration , Instant } ,
910} ;
1011
1112use eyre:: { eyre, Result } ;
@@ -30,28 +31,26 @@ const SECTOR_SIZE: u64 = 512;
3031pub struct DiskMetricsCollector < M : Mmc > {
3132 mmc : Vec < M > ,
3233 prev_bytes_reading : Option < u64 > ,
34+ last_lifetime_reading : Option < Instant > ,
3335}
3436
3537impl < M > DiskMetricsCollector < M >
3638where
3739 M : Mmc ,
3840{
41+ // Only read lifetime once an hour
42+ const LIFETIME_READING_INTERVAL : Duration = Duration :: from_secs ( 3600 ) ;
43+
3944 pub fn new ( mmc : Vec < M > ) -> Self {
4045 Self {
4146 mmc,
4247 prev_bytes_reading : None ,
48+ last_lifetime_reading : None ,
4349 }
4450 }
4551
46- fn get_disk_metrics (
47- mmc : & M ,
48- disk_stats : Option < & Vec < u64 > > ,
49- prev_bytes_reading : & mut Option < u64 > ,
50- ) -> Result < Vec < KeyedMetricReading > > {
51- let disk_name = mmc. disk_name ( ) ;
52-
53- let mut metrics = vec ! [ ] ;
54-
52+ fn get_lifetime_readings ( disk_name : & str , mmc : & M ) -> Result < Vec < KeyedMetricReading > > {
53+ let mut metrics = Vec :: with_capacity ( 2 ) ;
5554 if let Some ( lifetime) = mmc. read_lifetime ( ) ? {
5655 match lifetime. lifetime_a_pct {
5756 Some ( lifetime_a_pct) => {
@@ -100,6 +99,39 @@ where
10099 }
101100 }
102101
102+ Ok ( metrics)
103+ }
104+
105+ fn get_disk_metrics (
106+ mmc : & M ,
107+ disk_stats : Option < & Vec < u64 > > ,
108+ prev_bytes_reading : & mut Option < u64 > ,
109+ last_lifetime_reading : & mut Option < Instant > ,
110+ ) -> Result < Vec < KeyedMetricReading > > {
111+ let disk_name = mmc. disk_name ( ) ;
112+
113+ let mut metrics = vec ! [ ] ;
114+
115+ match last_lifetime_reading {
116+ Some ( last_reading) => {
117+ let now = Instant :: now ( ) ;
118+ let get_next_reading =
119+ now. checked_duration_since ( * last_reading)
120+ . is_some_and ( |duration_since| {
121+ duration_since >= Self :: LIFETIME_READING_INTERVAL
122+ } ) ;
123+
124+ if get_next_reading {
125+ metrics. extend ( Self :: get_lifetime_readings ( disk_name, mmc) ?) ;
126+ * last_lifetime_reading = Some ( now) ;
127+ }
128+ }
129+ None => {
130+ metrics. extend ( Self :: get_lifetime_readings ( disk_name, mmc) ?) ;
131+ * last_lifetime_reading = Some ( Instant :: now ( ) ) ;
132+ }
133+ }
134+
103135 let bytes_written = disk_stats
104136 . and_then ( |disk_stats| disk_stats. get ( 6 ) )
105137 . map ( |sectors_written| * sectors_written * SECTOR_SIZE ) ;
@@ -269,7 +301,12 @@ where
269301 . iter ( )
270302 . filter_map ( |m| {
271303 let disk_stats_line = disk_stats_map. get ( m. disk_name ( ) ) ;
272- match Self :: get_disk_metrics ( m, disk_stats_line, & mut self . prev_bytes_reading ) {
304+ match Self :: get_disk_metrics (
305+ m,
306+ disk_stats_line,
307+ & mut self . prev_bytes_reading ,
308+ & mut self . last_lifetime_reading ,
309+ ) {
273310 Ok ( metrics) => Some ( metrics) ,
274311 Err ( e) => {
275312 error ! (
@@ -394,11 +431,13 @@ mod test {
394431 let disk_stats = vec ! [ 0 , 0 , 0 , 0 , 0 , 0 , 1000 , 0 , 0 , 0 , 0 ] ;
395432 let mut prev_bytes_reading = None ;
396433
434+ let hour_ago = Instant :: now ( ) - DiskMetricsCollector :: < FakeMmc > :: LIFETIME_READING_INTERVAL ;
397435 // First call should return MLC and SLC metrics but no bytes written (no previous reading)
398436 let metrics = DiskMetricsCollector :: get_disk_metrics (
399437 & fake_mmc,
400438 Some ( & disk_stats) ,
401439 & mut prev_bytes_reading,
440+ & mut Some ( hour_ago) ,
402441 )
403442 . unwrap ( ) ;
404443
@@ -412,6 +451,7 @@ mod test {
412451 & fake_mmc,
413452 Some ( & updated_disk_stats) ,
414453 & mut prev_bytes_reading,
454+ & mut Some ( hour_ago) ,
415455 )
416456 . unwrap ( ) ;
417457
@@ -442,12 +482,14 @@ mod test {
442482 // Create disk stats (sectors written = 1000)
443483 let disk_stats = vec ! [ 0 , 0 , 0 , 0 , 0 , 0 , 1000 , 0 , 0 , 0 , 0 ] ;
444484 let mut prev_bytes_reading = None ;
485+ let hour_ago = Instant :: now ( ) - DiskMetricsCollector :: < FakeMmc > :: LIFETIME_READING_INTERVAL ;
445486
446487 // First call should return MLC and SLC metrics but no bytes written (no previous reading)
447488 let metrics = DiskMetricsCollector :: get_disk_metrics (
448489 & fake_mmc,
449490 Some ( & disk_stats) ,
450491 & mut prev_bytes_reading,
492+ & mut Some ( hour_ago) ,
451493 )
452494 . unwrap ( ) ;
453495
@@ -461,6 +503,7 @@ mod test {
461503 & fake_mmc,
462504 Some ( & updated_disk_stats) ,
463505 & mut prev_bytes_reading,
506+ & mut Some ( hour_ago) ,
464507 )
465508 . unwrap ( ) ;
466509
@@ -491,12 +534,14 @@ mod test {
491534 // Create disk stats (sectors written = 1000)
492535 let disk_stats = vec ! [ 0 , 0 , 0 , 0 , 0 , 0 , 1000 , 0 , 0 , 0 , 0 ] ;
493536 let mut prev_bytes_reading = None ;
537+ let hour_ago = Instant :: now ( ) - DiskMetricsCollector :: < FakeMmc > :: LIFETIME_READING_INTERVAL ;
494538
495539 // First call should return MLC and SLC metrics but no bytes written (no previous reading)
496540 let metrics = DiskMetricsCollector :: get_disk_metrics (
497541 & fake_mmc,
498542 Some ( & disk_stats) ,
499543 & mut prev_bytes_reading,
544+ & mut Some ( hour_ago) ,
500545 )
501546 . unwrap ( ) ;
502547
@@ -507,6 +552,84 @@ mod test {
507552 } ) ;
508553 }
509554
555+ #[ test]
556+ fn test_lifetime_interval_update ( ) {
557+ let fake_mmc = FakeMmc {
558+ disk_name : "mmcblk0" . to_string ( ) ,
559+ product_name : "SG123" . to_string ( ) ,
560+ manufacturer_id : "0x00015" . to_string ( ) ,
561+ lifetime : MmcLifeTime {
562+ lifetime_a_pct : Some ( 90 ) ,
563+ lifetime_b_pct : Some ( 85 ) ,
564+ } ,
565+ sector_count : 100 ,
566+ manufacture_date : "11/2023" . to_string ( ) ,
567+ revision : "1.0" . to_string ( ) ,
568+ serial : "0x1234567890" . to_string ( ) ,
569+ } ;
570+
571+ let mut prev_bytes_reading = None ;
572+ let mut last_lifetime_reading = None ;
573+
574+ // First reading should set the last reading time
575+ let metrics = DiskMetricsCollector :: get_disk_metrics (
576+ & fake_mmc,
577+ None ,
578+ & mut prev_bytes_reading,
579+ & mut last_lifetime_reading,
580+ )
581+ . unwrap ( ) ;
582+
583+ assert_eq ! ( metrics. len( ) , 8 ) ;
584+ assert ! ( last_lifetime_reading. is_some( ) ) ;
585+ }
586+
587+ #[ test]
588+ fn test_lifetime_interval_read ( ) {
589+ let fake_mmc = FakeMmc {
590+ disk_name : "mmcblk0" . to_string ( ) ,
591+ product_name : "SG123" . to_string ( ) ,
592+ manufacturer_id : "0x00015" . to_string ( ) ,
593+ lifetime : MmcLifeTime {
594+ lifetime_a_pct : Some ( 90 ) ,
595+ lifetime_b_pct : Some ( 85 ) ,
596+ } ,
597+ sector_count : 100 ,
598+ manufacture_date : "11/2023" . to_string ( ) ,
599+ revision : "1.0" . to_string ( ) ,
600+ serial : "0x1234567890" . to_string ( ) ,
601+ } ;
602+
603+ let mut prev_bytes_reading = None ;
604+ let mut last_lifetime_reading =
605+ Some ( Instant :: now ( ) - DiskMetricsCollector :: < FakeMmc > :: LIFETIME_READING_INTERVAL ) ;
606+
607+ let metrics = DiskMetricsCollector :: get_disk_metrics (
608+ & fake_mmc,
609+ None ,
610+ & mut prev_bytes_reading,
611+ & mut last_lifetime_reading,
612+ )
613+ . unwrap ( ) ;
614+
615+ // Should read lifetime metrics again since the interval has passed
616+ assert_eq ! ( metrics. len( ) , 8 ) ;
617+
618+ let mut prev_bytes_reading = None ;
619+ let mut last_lifetime_reading = Some ( Instant :: now ( ) ) ;
620+
621+ let metrics = DiskMetricsCollector :: get_disk_metrics (
622+ & fake_mmc,
623+ None ,
624+ & mut prev_bytes_reading,
625+ & mut last_lifetime_reading,
626+ )
627+ . unwrap ( ) ;
628+
629+ // Should not read lifetime metrics again since the interval has not passed
630+ assert_eq ! ( metrics. len( ) , 6 ) ;
631+ }
632+
510633 #[ test]
511634 fn test_calc_bytes_reading_overflow ( ) {
512635 let mut prev_bytes_reading = Some ( 1000 ) ;
0 commit comments