use crate::{
dlt::{ExtendedHeader, LogLevel, MessageType, StandardHeader, StorageHeader},
parse::{dlt_extended_header, dlt_standard_header, dlt_storage_header, DltParseError},
read::DltMessageReader,
};
use std::io::Read;
pub trait StatisticCollector {
fn collect_statistic(&mut self, statistic: Statistic) -> Result<(), DltParseError>;
}
pub struct Statistic<'a> {
pub log_level: Option<LogLevel>,
pub storage_header: Option<StorageHeader>,
pub standard_header: StandardHeader,
pub extended_header: Option<ExtendedHeader>,
pub payload: &'a [u8],
pub is_verbose: bool,
}
pub fn collect_statistics<S: Read>(
reader: &mut DltMessageReader<S>,
collector: &mut impl StatisticCollector,
) -> Result<(), DltParseError> {
let with_storage_header = reader.with_storage_header();
loop {
let slice = reader.next_message_slice()?;
if slice.is_empty() {
break;
}
match next_statistic(slice, with_storage_header) {
Ok(statistic) => {
collector.collect_statistic(statistic)?;
}
Err(error) => match error {
DltParseError::ParsingHickup(_) => {
continue;
}
_ => return Err(error),
},
}
}
Ok(())
}
fn next_statistic(slice: &[u8], with_storage_header: bool) -> Result<Statistic, DltParseError> {
let (rest_before_standard_header, storage_header) = if with_storage_header {
let result = dlt_storage_header(slice)?;
let rest = result.0;
let header = if let Some(header) = result.1 {
Some(header.0)
} else {
None
};
(rest, header)
} else {
(slice, None)
};
let (rest_after_standard_header, standard_header) =
dlt_standard_header(rest_before_standard_header)?;
let (rest_after_all_headers, extended_header, log_level, is_verbose) =
if standard_header.has_extended_header {
let result = dlt_extended_header(rest_after_standard_header)?;
let rest = result.0;
let header = result.1;
let level = match header.message_type {
MessageType::Log(level) => Some(level),
_ => None,
};
let verbose = header.verbose;
(rest, Some(header), level, verbose)
} else {
(rest_after_standard_header, None, None, false)
};
Ok(Statistic {
log_level,
storage_header,
standard_header,
extended_header,
payload: rest_after_all_headers,
is_verbose,
})
}
pub mod common {
use super::*;
use rustc_hash::FxHashMap;
type IdMap = FxHashMap<String, LevelDistribution>;
#[derive(Default)]
pub struct StatisticInfoCollector {
app_ids: IdMap,
context_ids: IdMap,
ecu_ids: IdMap,
contained_non_verbose: bool,
}
impl StatisticInfoCollector {
pub fn collect(self) -> StatisticInfo {
StatisticInfo {
app_ids: self
.app_ids
.into_iter()
.collect::<Vec<(String, LevelDistribution)>>(),
context_ids: self
.context_ids
.into_iter()
.collect::<Vec<(String, LevelDistribution)>>(),
ecu_ids: self
.ecu_ids
.into_iter()
.collect::<Vec<(String, LevelDistribution)>>(),
contained_non_verbose: self.contained_non_verbose,
}
}
}
impl StatisticCollector for StatisticInfoCollector {
fn collect_statistic(&mut self, statistic: Statistic) -> Result<(), DltParseError> {
let log_level = statistic.log_level;
match statistic.standard_header.ecu_id {
Some(id) => add_for_level(log_level, &mut self.ecu_ids, id),
None => add_for_level(log_level, &mut self.ecu_ids, "NONE".to_string()),
};
if let Some(extended_header) = statistic.extended_header {
add_for_level(log_level, &mut self.app_ids, extended_header.application_id);
add_for_level(log_level, &mut self.context_ids, extended_header.context_id);
}
self.contained_non_verbose = self.contained_non_verbose || !statistic.is_verbose;
Ok(())
}
}
#[cfg_attr(
feature = "serialization",
derive(serde::Serialize, serde::Deserialize)
)]
#[derive(Debug)]
pub struct StatisticInfo {
pub app_ids: Vec<(String, LevelDistribution)>,
pub context_ids: Vec<(String, LevelDistribution)>,
pub ecu_ids: Vec<(String, LevelDistribution)>,
pub contained_non_verbose: bool,
}
impl StatisticInfo {
pub fn new() -> Self {
Self {
app_ids: vec![],
context_ids: vec![],
ecu_ids: vec![],
contained_non_verbose: false,
}
}
pub fn merge(&mut self, stat: StatisticInfo) {
StatisticInfo::merge_levels(&mut self.app_ids, stat.app_ids);
StatisticInfo::merge_levels(&mut self.context_ids, stat.context_ids);
StatisticInfo::merge_levels(&mut self.ecu_ids, stat.ecu_ids);
self.contained_non_verbose = self.contained_non_verbose || stat.contained_non_verbose;
}
fn merge_levels(
owner: &mut Vec<(String, LevelDistribution)>,
incomes: Vec<(String, LevelDistribution)>,
) {
incomes.iter().for_each(|(income_id, income)| {
if let Some((_, existed)) =
owner.iter_mut().find(|(owner_id, _)| owner_id == income_id)
{
existed.merge(income);
} else {
owner.push((income_id.to_owned(), income.clone()));
}
});
}
}
impl Default for StatisticInfo {
fn default() -> Self {
Self::new()
}
}
#[cfg_attr(
feature = "serialization",
derive(serde::Serialize, serde::Deserialize)
)]
#[derive(Debug, Default, Clone)]
pub struct LevelDistribution {
pub non_log: usize,
pub log_fatal: usize,
pub log_error: usize,
pub log_warning: usize,
pub log_info: usize,
pub log_debug: usize,
pub log_verbose: usize,
pub log_invalid: usize,
}
impl LevelDistribution {
pub fn new(level: Option<LogLevel>) -> LevelDistribution {
let all_zero = Default::default();
match level {
None => LevelDistribution {
non_log: 1,
..all_zero
},
Some(LogLevel::Fatal) => LevelDistribution {
log_fatal: 1,
..all_zero
},
Some(LogLevel::Error) => LevelDistribution {
log_error: 1,
..all_zero
},
Some(LogLevel::Warn) => LevelDistribution {
log_warning: 1,
..all_zero
},
Some(LogLevel::Info) => LevelDistribution {
log_info: 1,
..all_zero
},
Some(LogLevel::Debug) => LevelDistribution {
log_debug: 1,
..all_zero
},
Some(LogLevel::Verbose) => LevelDistribution {
log_verbose: 1,
..all_zero
},
_ => LevelDistribution {
log_invalid: 1,
..all_zero
},
}
}
pub fn merge(&mut self, outside: &LevelDistribution) {
self.non_log += outside.non_log;
self.log_fatal += outside.log_fatal;
self.log_error += outside.log_error;
self.log_warning += outside.log_warning;
self.log_info += outside.log_info;
self.log_debug += outside.log_debug;
self.log_verbose += outside.log_verbose;
self.log_invalid += outside.log_invalid;
}
}
fn add_for_level(level: Option<LogLevel>, ids: &mut IdMap, id: String) {
if let Some(n) = ids.get_mut(&id) {
match level {
Some(LogLevel::Fatal) => {
n.log_fatal += 1;
}
Some(LogLevel::Error) => {
n.log_error += 1;
}
Some(LogLevel::Warn) => {
n.log_warning += 1;
}
Some(LogLevel::Info) => {
n.log_info += 1;
}
Some(LogLevel::Debug) => {
n.log_debug += 1;
}
Some(LogLevel::Verbose) => {
n.log_verbose += 1;
}
Some(LogLevel::Invalid(_)) => {
n.log_invalid += 1;
}
None => {
n.non_log += 1;
}
}
} else {
ids.insert(id, LevelDistribution::new(level));
}
}
}
#[cfg(test)]
mod tests {
use super::{common::*, *};
use crate::tests::{DLT_MESSAGE, DLT_MESSAGE_WITH_STORAGE_HEADER};
#[test]
fn test_empty_statistics() {
let collector = StatisticInfoCollector::default();
let stats = collector.collect();
assert_eq!(0, stats.app_ids.len());
assert_eq!(0, stats.context_ids.len());
assert_eq!(0, stats.ecu_ids.len());
assert!(!stats.contained_non_verbose);
}
#[test]
fn test_collect_statistics() {
let messages_with_storage = [
(DLT_MESSAGE, false),
(DLT_MESSAGE_WITH_STORAGE_HEADER, true),
];
for message_with_storage in &messages_with_storage {
let bytes = message_with_storage.0;
let with_storage_header = message_with_storage.1;
let mut reader = DltMessageReader::new(bytes, with_storage_header);
let mut collector = StatisticInfoCollector::default();
collect_statistics(&mut reader, &mut collector).expect("collect statistics");
let stats = collector.collect();
assert_eq!(1, stats.app_ids.len());
assert_eq!(1, stats.context_ids.len());
assert_eq!(1, stats.ecu_ids.len());
assert!(!stats.contained_non_verbose);
}
}
#[test]
fn test_collect_statistics_robustness() {
#[rustfmt::skip]
let bytes = [
[
0xFF, 0x4C, 0x54, 0x01, 0x2B, 0x2C, 0xC9, 0x4D,
0x7A, 0xE8, 0x01, 0x00, 0x45, 0x43, 0x55, 0x00,
]
.to_vec(),
[
0x44, 0x4C, 0x54, 0x01, 0x2B, 0x2C, 0xC9, 0x4D,
0x7A, 0xE8, 0x01, 0x00, 0x45, 0x43, 0x55, 0x00,
0x21, 0x0A, 0x00, 0x00,
]
.to_vec(),
DLT_MESSAGE_WITH_STORAGE_HEADER.to_vec(),
]
.concat();
let mut reader = DltMessageReader::new(bytes.as_slice(), true);
let mut collector = StatisticInfoCollector::default();
collect_statistics(&mut reader, &mut collector).expect("collect statistics");
let stats = collector.collect();
assert_eq!(1, stats.app_ids.len());
assert_eq!(1, stats.context_ids.len());
assert_eq!(1, stats.ecu_ids.len());
assert!(!stats.contained_non_verbose);
}
}