#![cfg(feature = "std")]
use std::vec;
use std::vec::Vec;
use crate::event::TraceEvent;
#[repr(C)]
#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)]
pub struct WindowFeature {
pub window_idx: u32,
pub entity_id: u32,
pub event_count: u32,
pub error_count: u32,
pub sum_latency_us: u64,
}
impl WindowFeature {
#[must_use]
pub const fn flat_index(entity_id: u32, window_idx: u32, n_windows: u32) -> usize {
(entity_id * n_windows + window_idx) as usize
}
#[must_use]
pub const fn mean_latency_us(&self) -> u32 {
if self.event_count == 0 {
0
} else {
(self.sum_latency_us / self.event_count as u64) as u32
}
}
}
#[must_use]
pub fn compute_features(
events: &[TraceEvent],
n_windows: u32,
n_entities: u32,
window_size_ns: u64,
) -> Vec<WindowFeature> {
let len = (n_windows as usize) * (n_entities as usize);
let mut grid: Vec<WindowFeature> = vec![WindowFeature::default(); len];
for entity_id in 0..n_entities {
for window_idx in 0..n_windows {
let idx = WindowFeature::flat_index(entity_id, window_idx, n_windows);
grid[idx].entity_id = entity_id;
grid[idx].window_idx = window_idx;
}
}
for event in events {
let w = event.window_index(window_size_ns);
if w >= n_windows || event.entity_id >= n_entities {
continue;
}
let idx = WindowFeature::flat_index(event.entity_id, w, n_windows);
grid[idx].event_count = grid[idx].event_count.saturating_add(1);
if event.error_code != 0 {
grid[idx].error_count = grid[idx].error_count.saturating_add(1);
}
grid[idx].sum_latency_us = grid[idx]
.sum_latency_us
.saturating_add(u64::from(event.latency_us));
}
grid
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fixture::{synthesize, DEFAULT_SEED, N_ENTITIES, N_WINDOWS, WINDOW_SIZE_NS};
fn fresh(events: &[TraceEvent]) -> Vec<WindowFeature> {
compute_features(events, N_WINDOWS, N_ENTITIES, WINDOW_SIZE_NS)
}
#[test]
fn empty_input_yields_rectangular_zero_grid() {
let grid = compute_features(&[], 4, 2, 1_000_000_000);
assert_eq!(grid.len(), 8);
for cell in &grid {
assert_eq!(cell.event_count, 0);
assert_eq!(cell.error_count, 0);
assert_eq!(cell.sum_latency_us, 0);
}
}
#[test]
fn grid_metadata_is_seeded_in_entity_major_order() {
let grid = compute_features(&[], 3, 2, 1_000_000_000);
assert_eq!((grid[0].entity_id, grid[0].window_idx), (0, 0));
assert_eq!((grid[1].entity_id, grid[1].window_idx), (0, 1));
assert_eq!((grid[2].entity_id, grid[2].window_idx), (0, 2));
assert_eq!((grid[3].entity_id, grid[3].window_idx), (1, 0));
assert_eq!((grid[4].entity_id, grid[4].window_idx), (1, 1));
assert_eq!((grid[5].entity_id, grid[5].window_idx), (1, 2));
}
#[test]
fn events_route_to_their_window_and_entity() {
let events = [
TraceEvent::new(0, 0, 0, 1, 0, 100, 200, 0, 0, 0),
TraceEvent::new(500_000_000, 0, 0, 2, 1, 200, 200, 0, 0, 0),
TraceEvent::new(1_500_000_000, 1, 0, 3, 0, 300, 500, 500, 0, 0),
];
let grid = compute_features(&events, 2, 2, 1_000_000_000);
let cell00 = &grid[WindowFeature::flat_index(0, 0, 2)];
assert_eq!(cell00.event_count, 2);
assert_eq!(cell00.error_count, 0);
assert_eq!(cell00.sum_latency_us, 300);
let cell11 = &grid[WindowFeature::flat_index(1, 1, 2)];
assert_eq!(cell11.event_count, 1);
assert_eq!(cell11.error_count, 1);
assert_eq!(cell11.sum_latency_us, 300);
}
#[test]
fn out_of_bounds_events_are_dropped() {
let events = [
TraceEvent::new(5_500_000_000, 0, 0, 1, 0, 100, 200, 0, 0, 0),
TraceEvent::new(0, 9, 0, 2, 0, 200, 200, 0, 0, 0),
];
let grid = compute_features(&events, 2, 2, 1_000_000_000);
for cell in &grid {
assert_eq!(cell.event_count, 0);
}
}
#[test]
fn synthesized_fixture_distributes_across_all_windows() {
let events = synthesize(DEFAULT_SEED);
let grid = fresh(&events);
let total: u64 = grid.iter().map(|c| u64::from(c.event_count)).sum();
assert_eq!(total as usize, events.len());
for w in 0..N_WINDOWS {
let in_window: u32 = grid
.iter()
.filter(|c| c.window_idx == w)
.map(|c| c.event_count)
.sum();
assert!(in_window > 0, "window {w} is empty");
}
}
#[test]
fn ramp_episode_elevates_mean_latency() {
use crate::fixture::{N_ENTITIES, N_WINDOWS, WINDOW_SIZE_NS};
let events = synthesize(DEFAULT_SEED);
let grid = compute_features(&events, N_WINDOWS, N_ENTITIES, WINDOW_SIZE_NS);
let cell_first = &grid[WindowFeature::flat_index(3, 20, N_WINDOWS)];
let cell_last = &grid[WindowFeature::flat_index(3, 35, N_WINDOWS)];
let mean_first = cell_first.mean_latency_us();
let mean_last = cell_last.mean_latency_us();
assert!(
mean_last > mean_first + 30_000,
"ramp cell means: first={mean_first} last={mean_last}"
);
}
#[test]
fn windowing_is_deterministic() {
let events = synthesize(DEFAULT_SEED);
let a = fresh(&events);
let b = fresh(&events);
assert_eq!(a, b);
}
}