Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 65d0286

Browse files
committed
feat: add event bus, RunResults type, and post-run error analysis
Introduce a crossbeam-channel event bus (RunEvent/RegionEvent) for the unified runner, add serializable RunConfig and RunResults types, and extract error analysis into a standalone analyze() post-processing step. Release-As: 0.2.0
1 parent 487473c commit 65d0286

11 files changed

Lines changed: 632 additions & 26 deletions

File tree

Cargo.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ pub_underscore_fields = "deny"
6161
ansi-to-tui = { version = "8.0.1", optional = true }
6262
anyhow = "1.0.102"
6363
clap = { version = "4.6.0", features = ["derive"] }
64+
crossbeam-channel = "0.5.15"
6465
crossterm = { version = "0.29.0", optional = true }
6566
nix = { version = "0.31.2", features = ["mman", "process", "resource", "user"] }
6667
owo-colors = "4.3.0"

src/error_analysis.rs

Lines changed: 260 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1+
use serde::Serialize;
2+
13
use 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)]
116120
pub 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)]
128196
mod 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

Comments
 (0)