veloq-core 0.2.2

Shared envelope, ProfileSource trait, and sort/time helpers for the VeloQ profile-query CLI.
Documentation
use super::time_window::WindowRef;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TimelineBucket {
    pub start_ns: i64,
    pub interval_ns: i64,
}

impl TimelineBucket {
    pub fn new(start_ns: i64, interval_ns: i64) -> Self {
        Self {
            start_ns,
            interval_ns,
        }
    }

    pub fn first_start_for(origin_ns: i64, interval_ns: i64, event_start_ns: i64) -> i64 {
        let offset = event_start_ns.saturating_sub(origin_ns);
        origin_ns.saturating_add((offset / interval_ns) * interval_ns)
    }

    pub fn end_ns(self) -> i64 {
        self.start_ns.saturating_add(self.interval_ns)
    }

    pub fn key(self) -> String {
        timeline_bucket_key(self.start_ns, self.end_ns())
    }

    pub fn overlap_ns(
        self,
        event_start_ns: i64,
        event_end_ns: i64,
        window: WindowRef,
    ) -> Option<i64> {
        let (start_ns, end_ns) = window.clipped_interval(event_start_ns, event_end_ns)?;
        let overlap = end_ns
            .min(self.end_ns())
            .saturating_sub(start_ns.max(self.start_ns));
        (overlap > 0).then_some(overlap)
    }
}

pub fn timeline_bucket_key(start_ns: i64, end_ns: i64) -> String {
    format!("bucket|{start_ns}..{end_ns}")
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn key_uses_canonical_bucket_wire_format() {
        assert_eq!(
            TimelineBucket::new(100, 25).key(),
            "bucket|100..125".to_string()
        );
        assert_eq!(timeline_bucket_key(100, 125), "bucket|100..125".to_string());
    }

    #[test]
    fn first_start_aligns_to_origin_relative_interval() {
        assert_eq!(TimelineBucket::first_start_for(10, 5, 23), 20);
        assert_eq!(TimelineBucket::first_start_for(10, 5, 10), 10);
    }

    #[test]
    fn overlap_clips_to_bucket_and_query_window() {
        let bucket = TimelineBucket::new(100, 10);
        let window = WindowRef::from_option(Some((105, 120)));

        assert_eq!(bucket.overlap_ns(90, 108, window), Some(3));
        assert_eq!(bucket.overlap_ns(110, 115, window), None);
        assert_eq!(bucket.overlap_ns(95, 100, WindowRef::Any), None);
    }
}