pub mod error_stats;
pub mod its_stats;
pub mod rdh_stats;
pub mod trigger_stats;
use super::stats_validation::validate_custom_stats;
use crate::util::*;
use error_stats::ErrorStats;
#[derive(Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct StatsCollector {
pub is_finalized: bool,
rdh_stats: RdhStats,
error_stats: ErrorStats,
alpide_stats: Option<AlpideStats>,
}
impl StatsCollector {
pub fn with_alpide_stats() -> Self {
Self {
alpide_stats: Some(AlpideStats::default()),
..Default::default()
}
}
pub fn collect(&mut self, stat: StatType) {
match stat {
StatType::RDHSeen(e) => self.rdh_stats.add_rdhs_seen(e),
StatType::HBFsSeen(val) => self.rdh_stats.add_hbfs_seen(val),
StatType::PayloadSize(sz) => self.rdh_stats.add_payload_size(sz as u64),
StatType::LinksObserved(id) => self.rdh_stats.record_link(id),
StatType::RdhVersion(v) => self.rdh_stats.record_rdh_version(v),
StatType::FeeId(id) => self.rdh_stats.record_fee_observed(id),
StatType::RunTriggerType((raw_trigger_type, trigger_type_str)) => self
.rdh_stats
.record_run_trigger_type((raw_trigger_type, trigger_type_str)),
StatType::TriggerType(t) => self.rdh_stats.record_trigger_type(t),
StatType::SystemId(id) => self.rdh_stats.record_system_id(id),
StatType::DataFormat(v) => self.rdh_stats.record_data_format(v),
StatType::LayerStaveSeen { layer, stave } => {
self.rdh_stats.record_layer_stave_seen((layer, stave))
}
StatType::RDHFiltered(e) => self.rdh_stats.add_rdhs_filtered(e),
StatType::AlpideStats(s) => self.alpide_stats.as_mut().unwrap().sum(s),
StatType::Error(m) => self.error_stats.add_err(m),
StatType::Fatal(m) => self.error_stats.add_fatal_err(m),
}
}
pub(crate) fn validate_custom_stats(&mut self, custom_checks: &'static impl CustomChecksOpt) {
if let Err(e) = validate_custom_stats(custom_checks, &self.rdh_stats) {
e.into_iter().for_each(|error_msg| {
self.error_stats.add_custom_check_error(error_msg);
});
}
}
pub fn finalize(&mut self, mute_errors: bool) {
if self.is_finalized {
return;
}
self.rdh_stats.finalize();
if matches!(self.rdh_stats().system_id(), Some(SystemId::ITS)) {
self.error_stats
.finalize_stats(mute_errors, Some(self.rdh_stats.layer_staves_as_slice()));
} else {
self.error_stats.finalize_stats(mute_errors, None);
}
self.is_finalized = true;
}
pub fn rdh_stats(&self) -> &RdhStats {
&self.rdh_stats
}
pub fn rdhs_seen(&self) -> u64 {
self.rdh_stats.rdhs_seen()
}
pub fn payload_size(&self) -> u64 {
self.rdh_stats.payload_size()
}
pub fn hbfs_seen(&self) -> u32 {
self.rdh_stats.hbfs_seen()
}
pub fn any_rdhs_seen(&self) -> bool {
self.rdh_stats.rdhs_seen() > 0
}
pub fn system_id(&self) -> Option<SystemId> {
self.rdh_stats.system_id()
}
pub fn layer_staves_as_slice(&self) -> &[(u8, u8)] {
self.rdh_stats.layer_staves_as_slice()
}
pub fn error_stats(&self) -> &ErrorStats {
&self.error_stats
}
pub fn err_count(&self) -> u64 {
self.error_stats.err_count()
}
pub fn any_errors(&self) -> bool {
self.error_stats.err_count() > 0
}
pub fn any_fatal_err(&self) -> bool {
self.error_stats.any_fatal_err()
}
pub fn fatal_err(&self) -> &str {
self.error_stats.fatal_err()
}
pub fn unique_error_codes_as_slice(&self) -> &[String] {
self.error_stats.unique_error_codes_as_slice()
}
pub fn staves_with_errors_as_slice(&self) -> Option<&[(u8, u8)]> {
self.error_stats.staves_with_errors_as_slice()
}
pub fn alpide_stats(&self) -> Option<&AlpideStats> {
self.alpide_stats.as_ref()
}
pub(crate) fn write_stats(&self, mode: &DataOutputMode, format: DataOutputFormat) {
if *mode == DataOutputMode::None {
return;
}
match format {
DataOutputFormat::JSON => write_stats_str(
mode,
&serde_json::to_string_pretty(&self).expect("Failed to serialize stats to JSON"),
),
DataOutputFormat::TOML => write_stats_str(
mode,
&toml::to_string_pretty(&self).expect("Failed to serialize stats to TOML"),
),
}
}
pub fn validate_other_stats(&self, other: &Self, mute_errors: bool) -> Result<(), io::Error> {
let mut errs = Vec::new();
if let Err(mut err_msgs) = self.rdh_stats.validate_other(other.rdh_stats()) {
errs.append(&mut err_msgs);
}
if let Err(mut err_msgs) = self.error_stats.validate_other(other.error_stats()) {
errs.append(&mut err_msgs);
}
if let Some(alpide_stats) = self.alpide_stats() {
if let Some(other_alpide_stats) = other.alpide_stats() {
if let Err(mut err_msgs) = alpide_stats.validate_other(other_alpide_stats) {
errs.append(&mut err_msgs);
}
} else {
errs.push(
"ALPIDE stats was collected but the input stats does not contain ALPIDE stats"
.into(),
);
}
} else if other.alpide_stats.is_some() {
log::warn!("Input stats contains ALPIDE stats but the chosen analysis did not collect ALPIDE stats (did you mean to use `check all its-stave`?)");
}
if errs.is_empty() {
Ok(())
} else {
if !mute_errors {
errs.iter().for_each(|err| {
crate::display_error(err);
});
}
Err(io::Error::new(
io::ErrorKind::InvalidData,
"Stats validation failed",
))
}
}
}
fn write_stats_str(mode: &DataOutputMode, stats_str: &str) {
match mode {
DataOutputMode::File(path) => {
fs::write(path, stats_str).expect("Failed writing stats output file")
}
DataOutputMode::Stdout => println!("{stats_str}"),
DataOutputMode::None => (),
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_serde() {
let mut stats_collector = StatsCollector::with_alpide_stats();
stats_collector.collect(StatType::Fatal("fatal error".into()));
stats_collector.collect(StatType::Error("error".into()));
stats_collector.collect(StatType::RunTriggerType((0, "trigger type".into())));
stats_collector.collect(StatType::TriggerType(0xE021));
stats_collector.collect(StatType::SystemId(SystemId::ZDC));
stats_collector.collect(StatType::RDHSeen(1));
stats_collector.collect(StatType::RDHFiltered(2));
stats_collector.collect(StatType::PayloadSize(3));
stats_collector.collect(StatType::LinksObserved(4));
stats_collector.collect(StatType::RdhVersion(5));
stats_collector.collect(StatType::DataFormat(2));
stats_collector.collect(StatType::HBFsSeen(1));
stats_collector.collect(StatType::LayerStaveSeen { layer: 6, stave: 7 });
stats_collector.collect(StatType::FeeId(8));
stats_collector.collect(StatType::AlpideStats(AlpideStats::default()));
stats_collector.finalize(false);
let json = serde_json::to_string(&stats_collector).unwrap();
let from_json = serde_json::from_str::<StatsCollector>(&json).unwrap();
println!(
"{}",
serde_json::to_string_pretty(&stats_collector).unwrap()
);
assert_eq!(stats_collector, from_json);
let toml = toml::to_string(&stats_collector).unwrap();
let from_toml = toml::from_str::<StatsCollector>(&toml).unwrap();
println!("{toml}");
assert_eq!(stats_collector, from_toml);
}
#[test]
fn test_validate_other_stats_default_succeeds() {
let stats_collector = StatsCollector::default();
let other_stats_collector = StatsCollector::default();
assert!(stats_collector
.validate_other_stats(&other_stats_collector, false)
.is_ok());
}
#[test]
fn test_validate_other_stats_with_alpide_stats_succeeds() {
let stats_collector = StatsCollector::with_alpide_stats();
let other_stats_collector = StatsCollector::with_alpide_stats();
assert!(stats_collector
.validate_other_stats(&other_stats_collector, false)
.is_ok());
}
#[test]
fn test_validate_other_stats_with_alpide_stats_and_default_fails() {
let stats_collector = StatsCollector::with_alpide_stats();
let other_stats_collector = StatsCollector::default();
if let Err(msgs) = stats_collector.validate_other_stats(&other_stats_collector, false) {
println!("{:?}", msgs);
} else {
panic!("Should have failed");
}
assert!(other_stats_collector
.validate_other_stats(&stats_collector, false)
.is_ok());
}
#[test]
fn test_validate_mismatch_errors() {
let stats_collector = StatsCollector::default();
let mut other_stats_collector = StatsCollector::default();
other_stats_collector.collect(StatType::Fatal("fatal error".into()));
other_stats_collector.collect(StatType::Error("error".into()));
other_stats_collector.collect(StatType::RunTriggerType((0, "trigger type".into())));
other_stats_collector.collect(StatType::TriggerType(0xE021));
other_stats_collector.collect(StatType::SystemId(SystemId::ZDC));
if let Err(err) = stats_collector.validate_other_stats(&other_stats_collector, false) {
println!("{:?}", err);
} else {
panic!("Should have failed");
}
}
#[test]
fn test_validate_match_various_vals() {
let mut stats_collector = StatsCollector::with_alpide_stats();
let mut other_stats_collector = StatsCollector::with_alpide_stats();
stats_collector.collect(StatType::Error("0xe0 : [E10] big error".into()));
other_stats_collector.collect(StatType::Error("0xe0 : [E10] big error".into()));
stats_collector.collect(StatType::FeeId(1));
other_stats_collector.collect(StatType::FeeId(1));
stats_collector.collect(StatType::RDHSeen(11));
other_stats_collector.collect(StatType::RDHSeen(11));
stats_collector.collect(StatType::RDHFiltered(10));
other_stats_collector.collect(StatType::RDHFiltered(10));
stats_collector.collect(StatType::PayloadSize(100));
other_stats_collector.collect(StatType::PayloadSize(100));
let mut alpide_stats = AlpideStats::default();
alpide_stats.log_readout_flags(0b0000_0001);
alpide_stats.log_readout_flags(0b0001_1001);
stats_collector.collect(StatType::AlpideStats(alpide_stats));
other_stats_collector.collect(StatType::AlpideStats(alpide_stats));
assert!(stats_collector
.validate_other_stats(&other_stats_collector, false)
.is_ok());
}
}