1+ use serde:: Serialize ;
2+
13use crate :: Failure ;
4+ use crate :: pattern:: Pattern ;
5+ use crate :: runner:: RunResults ;
26
37/// Aggregate bit-flip statistics across multiple failures.
48#[ derive( Debug , Clone ) ]
@@ -112,7 +116,7 @@ impl BitErrorStats {
112116}
113117
114118/// Classification of error patterns across all failures.
115- #[ derive( Debug , PartialEq , Eq ) ]
119+ #[ derive( Debug , PartialEq , Eq , Serialize ) ]
116120pub enum ErrorClassification {
117121 /// No errors recorded.
118122 NoErrors ,
@@ -124,6 +128,70 @@ pub enum ErrorClassification {
124128 Mixed ,
125129}
126130
131+ /// Enriched error analysis attached to [`RunResults`] after post-processing.
132+ #[ derive( Debug , Serialize ) ]
133+ pub struct ErrorAnalysis {
134+ pub classification : ErrorClassification ,
135+ /// `(bit_position, flip_count)` pairs for every bit that flipped at least once.
136+ pub bit_positions : Vec < ( u8 , u32 ) > ,
137+ /// OR of all XOR masks -- which bit positions have ever flipped.
138+ pub union_xor_mask : u64 ,
139+ /// Lowest physical address with an error.
140+ pub lowest_phys : Option < u64 > ,
141+ /// Highest physical address with an error.
142+ pub highest_phys : Option < u64 > ,
143+ /// Per-pattern failure counts.
144+ pub per_pattern_failures : Vec < ( Pattern , usize ) > ,
145+ }
146+
147+ /// Analyze raw run results and attach enriched error analysis.
148+ ///
149+ /// Computes [`BitErrorStats`] from all failures across all passes, classifies
150+ /// the error pattern, and stores the result in `results.error_analysis`.
151+ ///
152+ /// This is a no-op when there are no failures.
153+ pub fn analyze ( results : & mut RunResults ) {
154+ if results. total_failures == 0 {
155+ return ;
156+ }
157+
158+ let mut stats = BitErrorStats :: new ( ) ;
159+ let mut per_pattern: Vec < ( Pattern , usize ) > = Vec :: new ( ) ;
160+
161+ for pass_result in & results. passes {
162+ for pattern_result in & pass_result. pattern_results {
163+ let count = pattern_result. failures . len ( ) ;
164+ if count > 0 {
165+ if let Some ( entry) = per_pattern
166+ . iter_mut ( )
167+ . find ( |( p, _) | * p == pattern_result. pattern )
168+ {
169+ entry. 1 += count;
170+ } else {
171+ per_pattern. push ( ( pattern_result. pattern , count) ) ;
172+ }
173+ }
174+ for f in & pattern_result. failures {
175+ stats. record ( f) ;
176+ }
177+ }
178+ }
179+
180+ let bit_positions: Vec < ( u8 , u32 ) > = ( 0u8 ..64 )
181+ . filter ( |& bit| stats. bit_positions [ bit as usize ] > 0 )
182+ . map ( |bit| ( bit, stats. bit_positions [ bit as usize ] ) )
183+ . collect ( ) ;
184+
185+ results. error_analysis = Some ( ErrorAnalysis {
186+ classification : stats. classification ( ) ,
187+ bit_positions,
188+ union_xor_mask : stats. union_xor_mask ,
189+ lowest_phys : stats. lowest_phys ,
190+ highest_phys : stats. highest_phys ,
191+ per_pattern_failures : per_pattern,
192+ } ) ;
193+ }
194+
127195#[ cfg( test) ]
128196mod tests {
129197 use assert2:: { assert, check} ;
@@ -342,4 +410,195 @@ mod tests {
342410 }
343411 }
344412 }
413+
414+ mod analyze_fn {
415+ use std:: time:: Duration ;
416+
417+ use assert2:: { assert, check} ;
418+
419+ use crate :: pattern:: Pattern ;
420+ use crate :: runner:: { PassResult , PatternResult , RunConfig , RunResults } ;
421+
422+ use super :: * ;
423+
424+ fn make_config ( ) -> RunConfig {
425+ RunConfig {
426+ size : 8192 ,
427+ passes : 1 ,
428+ patterns : vec ! [ Pattern :: SolidBits ] ,
429+ regions : 1 ,
430+ parallel : false ,
431+ }
432+ }
433+
434+ #[ test]
435+ fn no_failures_leaves_none ( ) {
436+ let mut results = RunResults :: from_passes (
437+ vec ! [ PassResult {
438+ pass_number: 1 ,
439+ pattern_results: vec![ PatternResult {
440+ pattern: Pattern :: SolidBits ,
441+ failures: vec![ ] ,
442+ elapsed: Duration :: from_millis( 100 ) ,
443+ bytes_processed: 8192 ,
444+ } ] ,
445+ ecc_deltas: vec![ ] ,
446+ } ] ,
447+ make_config ( ) ,
448+ Duration :: from_millis ( 100 ) ,
449+ ) ;
450+
451+ analyze ( & mut results) ;
452+ assert ! ( results. error_analysis. is_none( ) ) ;
453+ }
454+
455+ #[ test]
456+ fn stuck_bit_detected ( ) {
457+ let failures = vec ! [ f( 0x1000 , 0x0 , 1 << 20 ) , f( 0x2000 , 0x0 , 1 << 20 ) ] ;
458+ let mut results = RunResults :: from_passes (
459+ vec ! [ PassResult {
460+ pass_number: 1 ,
461+ pattern_results: vec![ PatternResult {
462+ pattern: Pattern :: SolidBits ,
463+ failures,
464+ elapsed: Duration :: from_millis( 50 ) ,
465+ bytes_processed: 8192 ,
466+ } ] ,
467+ ecc_deltas: vec![ ] ,
468+ } ] ,
469+ make_config ( ) ,
470+ Duration :: from_millis ( 50 ) ,
471+ ) ;
472+
473+ analyze ( & mut results) ;
474+ let ea = results. error_analysis . as_ref ( ) . unwrap ( ) ;
475+ assert ! ( let ErrorClassification :: StuckBit { .. } = & ea. classification) ;
476+ check ! ( ea. union_xor_mask == 1 << 20 ) ;
477+ check ! ( ea. bit_positions == vec![ ( 20 , 2 ) ] ) ;
478+ check ! ( ea. per_pattern_failures == vec![ ( Pattern :: SolidBits , 2 ) ] ) ;
479+ }
480+
481+ #[ test]
482+ fn per_pattern_aggregation ( ) {
483+ let mut config = make_config ( ) ;
484+ config. patterns = vec ! [ Pattern :: SolidBits , Pattern :: Checkerboard ] ;
485+
486+ let mut results = RunResults :: from_passes (
487+ vec ! [ PassResult {
488+ pass_number: 1 ,
489+ pattern_results: vec![
490+ PatternResult {
491+ pattern: Pattern :: SolidBits ,
492+ failures: vec![ f( 0x1000 , 0x0 , 1 << 5 ) ] ,
493+ elapsed: Duration :: from_millis( 50 ) ,
494+ bytes_processed: 8192 ,
495+ } ,
496+ PatternResult {
497+ pattern: Pattern :: Checkerboard ,
498+ failures: vec![ f( 0x2000 , 0x0 , 1 << 5 ) , f( 0x3000 , 0x0 , 1 << 5 ) ] ,
499+ elapsed: Duration :: from_millis( 50 ) ,
500+ bytes_processed: 8192 ,
501+ } ,
502+ ] ,
503+ ecc_deltas: vec![ ] ,
504+ } ] ,
505+ config,
506+ Duration :: from_millis ( 100 ) ,
507+ ) ;
508+
509+ analyze ( & mut results) ;
510+ let ea = results. error_analysis . as_ref ( ) . unwrap ( ) ;
511+ check ! ( ea. per_pattern_failures. len( ) == 2 ) ;
512+ check ! ( ea. per_pattern_failures[ 0 ] == ( Pattern :: SolidBits , 1 ) ) ;
513+ check ! ( ea. per_pattern_failures[ 1 ] == ( Pattern :: Checkerboard , 2 ) ) ;
514+ }
515+
516+ #[ test]
517+ fn multi_pass_aggregation ( ) {
518+ let mut results = RunResults :: from_passes (
519+ vec ! [
520+ PassResult {
521+ pass_number: 1 ,
522+ pattern_results: vec![ PatternResult {
523+ pattern: Pattern :: SolidBits ,
524+ failures: vec![ f( 0x1000 , 0x0 , 1 << 3 ) ] ,
525+ elapsed: Duration :: from_millis( 50 ) ,
526+ bytes_processed: 8192 ,
527+ } ] ,
528+ ecc_deltas: vec![ ] ,
529+ } ,
530+ PassResult {
531+ pass_number: 2 ,
532+ pattern_results: vec![ PatternResult {
533+ pattern: Pattern :: SolidBits ,
534+ failures: vec![ f( 0x2000 , 0x0 , 1 << 3 ) ] ,
535+ elapsed: Duration :: from_millis( 50 ) ,
536+ bytes_processed: 8192 ,
537+ } ] ,
538+ ecc_deltas: vec![ ] ,
539+ } ,
540+ ] ,
541+ make_config ( ) ,
542+ Duration :: from_millis ( 100 ) ,
543+ ) ;
544+
545+ analyze ( & mut results) ;
546+ let ea = results. error_analysis . as_ref ( ) . unwrap ( ) ;
547+ check ! ( ea. per_pattern_failures == vec![ ( Pattern :: SolidBits , 2 ) ] ) ;
548+ check ! ( ea. bit_positions == vec![ ( 3 , 2 ) ] ) ;
549+ }
550+
551+ #[ test]
552+ fn physical_address_range ( ) {
553+ let failures = vec ! [
554+ f_phys( 0x1000 , 0x0 , 1 << 10 , 0x5000 ) ,
555+ f_phys( 0x2000 , 0x0 , 1 << 10 , 0x9000 ) ,
556+ ] ;
557+ let mut results = RunResults :: from_passes (
558+ vec ! [ PassResult {
559+ pass_number: 1 ,
560+ pattern_results: vec![ PatternResult {
561+ pattern: Pattern :: SolidBits ,
562+ failures,
563+ elapsed: Duration :: from_millis( 50 ) ,
564+ bytes_processed: 8192 ,
565+ } ] ,
566+ ecc_deltas: vec![ ] ,
567+ } ] ,
568+ make_config ( ) ,
569+ Duration :: from_millis ( 50 ) ,
570+ ) ;
571+
572+ analyze ( & mut results) ;
573+ let ea = results. error_analysis . as_ref ( ) . unwrap ( ) ;
574+ check ! ( ea. lowest_phys == Some ( 0x5000 ) ) ;
575+ check ! ( ea. highest_phys == Some ( 0x9000 ) ) ;
576+ }
577+
578+ #[ test]
579+ fn serializes_to_json ( ) {
580+ let failures = vec ! [ f( 0x1000 , 0xFF , 0xFE ) ] ;
581+ let mut results = RunResults :: from_passes (
582+ vec ! [ PassResult {
583+ pass_number: 1 ,
584+ pattern_results: vec![ PatternResult {
585+ pattern: Pattern :: SolidBits ,
586+ failures,
587+ elapsed: Duration :: from_millis( 50 ) ,
588+ bytes_processed: 8192 ,
589+ } ] ,
590+ ecc_deltas: vec![ ] ,
591+ } ] ,
592+ make_config ( ) ,
593+ Duration :: from_millis ( 50 ) ,
594+ ) ;
595+
596+ analyze ( & mut results) ;
597+ let json = serde_json:: to_value ( & results) . unwrap ( ) ;
598+ check ! ( json[ "total_failures" ] == 1 ) ;
599+ assert ! ( json[ "error_analysis" ] . is_object( ) ) ;
600+ assert ! ( json[ "error_analysis" ] [ "classification" ] . is_object( ) ) ;
601+ assert ! ( json[ "config" ] [ "size" ] == 8192 ) ;
602+ }
603+ }
345604}
0 commit comments