trace-recorder-parser 0.19.0

A Rust library to parse Percepio's TraceRecorder data
Documentation
use pretty_assertions::assert_eq;
use std::fs::File;
use std::io::Read;
use std::path::Path;
use trace_recorder_parser::{streaming::event::*, streaming::*, time::*, types::*};

const TRACE_V10: &str = "test_resources/fixtures/streaming/v10/trace.psf";
const TRACE_V12: &str = "test_resources/fixtures/streaming/v12/trace.psf";
const TRACE_V13: &str = "test_resources/fixtures/streaming/v13/trace.psf";
const TRACE_V14: &str = "test_resources/fixtures/streaming/v14/trace.psf";

fn open_trace_file(trace_path: &str) -> File {
    let path = Path::new(env!("CARGO_MANIFEST_DIR")).join(trace_path);
    File::open(path).unwrap()
}

struct TestRecorderData<R: Read> {
    rd: RecorderData,
    f: R,
    event_cnt: u16,
    timestamp_ticks: u64,
}

impl<R> TestRecorderData<R>
where
    R: Read,
{
    pub fn check_event(&mut self, typ: EventType) {
        let (ec, ev) = self.rd.read_event(&mut self.f).unwrap().unwrap();
        assert_eq!(ec.event_type(), typ);
        assert_eq!(u16::from(ev.event_count()), self.event_cnt);
        assert_eq!(ev.timestamp().ticks(), self.timestamp_ticks);

        self.event_cnt += 1;
        self.timestamp_ticks += 1;
    }
}

// git tag: Tz4/4.6/v4.6.6
#[test]
fn streaming_v10_smoke() {
    common_tests(CommonTestConfig {
        trace_path: TRACE_V10,
        expected_trace_format_version: 10,
        expected_platform_cfg_version_minor: 0,
        initial_event_count: 1,
        latest_timestamp: Timestamp::zero(),
        high_water_mark: 0,
        extra_user_events: false,
        custom_printf_event_id: None,
    });
}

// git tag: Tz4/4.7/v4.7.0
#[test]
fn streaming_v12_smoke() {
    common_tests(CommonTestConfig {
        trace_path: TRACE_V12,
        expected_trace_format_version: 12,
        expected_platform_cfg_version_minor: 2,
        initial_event_count: 6,
        latest_timestamp: Timestamp::zero(),
        high_water_mark: 0,
        extra_user_events: false,
        custom_printf_event_id: None,
    });
}

// git tag: Tz4/4.8/v4.8.0.hotfix1
#[test]
fn streaming_v13_smoke() {
    common_tests(CommonTestConfig {
        trace_path: TRACE_V13,
        expected_trace_format_version: 13,
        expected_platform_cfg_version_minor: 2,
        initial_event_count: 6,
        latest_timestamp: Timestamp::zero(),
        high_water_mark: 0,
        extra_user_events: false,
        custom_printf_event_id: None,
    });
}

// git tag: Tz4/4.9/v4.9.2
#[test]
fn streaming_v14_smoke() {
    common_tests(CommonTestConfig {
        trace_path: TRACE_V14,
        expected_trace_format_version: 14,
        expected_platform_cfg_version_minor: 2,
        initial_event_count: 6,
        latest_timestamp: Timestamp::zero(),
        high_water_mark: 0,
        extra_user_events: true,
        custom_printf_event_id: Some(0x0FA0),
    });
}

#[test]
fn streaming_v14_garbage_with_trace_restart() {
    let path = Path::new(env!("CARGO_MANIFEST_DIR")).join(TRACE_V14);
    let trace_data = std::fs::read(path).unwrap();
    let garbage = vec![0x11, 0x22, 0x33];

    let mut data = Vec::new();
    data.extend_from_slice(&garbage);
    data.extend_from_slice(&trace_data);

    let cfg0 = CommonTestConfig {
        trace_path: TRACE_V14,
        expected_trace_format_version: 14,
        expected_platform_cfg_version_minor: 2,
        initial_event_count: 6,
        latest_timestamp: Timestamp::zero(),
        high_water_mark: 0,
        extra_user_events: true,
        custom_printf_event_id: Some(0x0FA0),
    };
    let mut reader = data.as_slice();

    let mut rd0 = RecorderData::find(&mut reader).unwrap();
    if let Some(custom_printf_event_id) = cfg0.custom_printf_event_id {
        rd0.set_custom_printf_event_id(custom_printf_event_id.into());
    }

    check_recorder_data(&rd0, &cfg0);

    for _ in 0..64 {
        let _ = rd0.read_event(&mut reader).unwrap().unwrap();
    }
    let next_psf_word = match rd0.read_event(&mut reader) {
        Err(Error::TraceRestarted(endianness)) => endianness,
        res => panic!("Expected TraceRestarted error. {res:?}"),
    };

    let cfg1 = CommonTestConfig {
        trace_path: TRACE_V14,
        expected_trace_format_version: 14,
        expected_platform_cfg_version_minor: 2,
        initial_event_count: 6,
        latest_timestamp: Timestamp::from(Ticks::new(63)),
        high_water_mark: 4,
        extra_user_events: true,
        custom_printf_event_id: None,
    };
    let rd1 = RecorderData::read_with_endianness(next_psf_word, &mut reader).unwrap();
    check_recorder_data(&rd1, &cfg1);

    {
        use EventType::*;
        let mut trd = TestRecorderData {
            rd: rd1,
            f: reader,
            event_cnt: 88,
            timestamp_ticks: 64,
        };
        trd.check_event(TraceStart);
    }
}

struct CommonTestConfig {
    trace_path: &'static str,
    expected_trace_format_version: u16,
    expected_platform_cfg_version_minor: u8,
    initial_event_count: u16,
    latest_timestamp: Timestamp,
    high_water_mark: u32,
    extra_user_events: bool,
    custom_printf_event_id: Option<u16>,
}

fn check_recorder_data(rd: &RecorderData, cfg: &CommonTestConfig) {
    assert_eq!(rd.protocol, Protocol::Streaming);

    let kernel_version: [u8; 2] = rd.header.kernel_version.into();
    assert_eq!(kernel_version, [0xA1, 0x1A]);
    assert_eq!(
        rd.header,
        HeaderInfo {
            endianness: Endianness::Little,
            format_version: cfg.expected_trace_format_version,
            kernel_version: rd.header.kernel_version,
            kernel_port: KernelPortIdentity::FreeRtos,
            options: 4,
            irq_priority_order: 0,
            num_cores: 1,
            isr_tail_chaining_threshold: 0,
            platform_cfg: "FreeRTOS".to_owned(),
            platform_cfg_version: PlatformCfgVersion {
                major: 1,
                minor: cfg.expected_platform_cfg_version_minor,
                patch: 0,
            },
        }
    );

    assert_eq!(u32::from(rd.timestamp_info.timer_frequency), 1000000_u32);
    assert_eq!(u32::from(rd.timestamp_info.os_tick_rate_hz), 1000_u32);
    assert_eq!(
        rd.timestamp_info,
        TimestampInfo {
            timer_type: TimerCounter::FreeRunning32Incr,
            timer_frequency: rd.timestamp_info.timer_frequency,
            timer_period: 0,
            timer_wraparounds: 0,
            os_tick_rate_hz: rd.timestamp_info.os_tick_rate_hz,
            latest_timestamp: cfg.latest_timestamp,
            os_tick_count: 0,
        },
    );

    assert_eq!(
        rd.entry_table
            .symbol(ObjectHandle::NO_TASK)
            .unwrap()
            .as_ref(),
        STARTUP_TASK_NAME
    );
    assert_eq!(
        rd.entry_table.class(ObjectHandle::NO_TASK).unwrap(),
        ObjectClass::Task,
    );

    assert_eq!(
        rd.system_heap(),
        &Heap {
            current: 0,
            high_water_mark: cfg.high_water_mark,
            max: 32768,
        }
    );
}

fn common_tests(cfg: CommonTestConfig) {
    let mut f = open_trace_file(cfg.trace_path);
    let mut rd = RecorderData::find(&mut f).unwrap();
    if let Some(custom_printf_event_id) = cfg.custom_printf_event_id {
        rd.set_custom_printf_event_id(custom_printf_event_id.into());
    }

    check_recorder_data(&rd, &cfg);

    {
        use EventType::*;
        let mut trd = TestRecorderData {
            rd,
            f,
            event_cnt: cfg.initial_event_count,
            timestamp_ticks: 0,
        };
        trd.check_event(TraceStart);
        trd.check_event(ObjectName);
        trd.check_event(ObjectName);
        trd.check_event(TaskCreate);
        trd.check_event(ObjectName);
        trd.check_event(TaskCreate);
        trd.check_event(DefineIsr);
        trd.check_event(QueueCreate);
        trd.check_event(ObjectName);
        trd.check_event(SemaphoreBinaryCreate);
        trd.check_event(ObjectName);
        trd.check_event(SemaphoreCountingCreate);
        trd.check_event(ObjectName);
        trd.check_event(TaskReady);
        trd.check_event(TaskActivate);
        trd.check_event(QueueSend);
        trd.check_event(QueueSendBlock);
        trd.check_event(QueueSendFront);
        trd.check_event(QueueSendFrontBlock);
        trd.check_event(SemaphoreGive);
        trd.check_event(SemaphoreGive);
        trd.check_event(SemaphoreGiveBlock);
        trd.check_event(SemaphoreGiveBlock);
        trd.check_event(MemoryAlloc);
        trd.check_event(MemoryFree);
        trd.check_event(TaskSwitchIsrBegin);
        trd.check_event(QueueSendFromIsr);
        trd.check_event(QueueSendFrontFromIsr);
        trd.check_event(SemaphoreGiveFromIsr);
        trd.check_event(SemaphoreGiveFromIsr);
        trd.check_event(TaskActivate);
        trd.check_event(TaskReady);
        trd.check_event(TaskActivate);
        trd.check_event(QueueReceive);
        trd.check_event(QueueReceiveBlock);
        trd.check_event(QueueReceiveFromIsr);
        trd.check_event(QueuePeek);
        trd.check_event(QueuePeekBlock);
        trd.check_event(SemaphoreTake);
        trd.check_event(SemaphoreTake);
        trd.check_event(SemaphoreTakeBlock);
        trd.check_event(SemaphoreTakeBlock);
        trd.check_event(SemaphorePeek);
        trd.check_event(SemaphorePeek);
        trd.check_event(SemaphorePeekBlock);
        trd.check_event(SemaphorePeekBlock);
        trd.check_event(SemaphoreTakeFromIsr);
        trd.check_event(SemaphoreTakeFromIsr);
        trd.check_event(UserEvent(3.into()));
        if cfg.extra_user_events {
            trd.check_event(ObjectName);
            trd.check_event(ObjectName);
            trd.check_event(UserEvent(8.into()));
            trd.check_event(ObjectName);
            trd.check_event(UserEvent(9.into()));
            trd.check_event(ObjectName);
            trd.check_event(UserEvent(10.into()));
            trd.check_event(ObjectName);
            trd.check_event(UserEvent(11.into()));
            trd.check_event(ObjectName);
            trd.check_event(UserEvent(12.into()));
            if cfg.custom_printf_event_id.is_some() {
                trd.check_event(Unknown(0x0FA0.into()));
            }
        }
        trd.check_event(TaskDelay);
        trd.check_event(QueueReceiveBlock);
        trd.check_event(UnusedStack);
    }
}