bpf-tracing 0.0.1

An eBPF tracing facility that integrates neatly into Rust's tracing
Documentation
use std::str::FromStr;
use tracing::{self, Level};

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Event {
    pub kind: Kind,
    pub cpu: usize,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Kind {
    Message(String, Level),
    StartSpan(String, Level),
    EndSpan(String),
}

impl Kind {
    pub fn level(&self) -> Option<Level> {
        match self {
            Kind::Message(_, level) | Kind::StartSpan(_, level) => Some(*level),
            Kind::EndSpan(_) => None,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParseError {
    InvalidLog,
    InvalidLevel,
    InvalidCpu,
}

impl FromStr for Event {
    type Err = ParseError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        const MARKER: &str = "bpf_trace_printk:";

        fn extract(lhs: char, rhs: char, s: &str) -> Option<(usize, &str)> {
            let Some(l) = s.find(lhs) else { return None };
            let Some(r) = s[l + 1..].find(rhs) else {
                return None;
            };
            let r = l + 1 + r;
            Some((r, &s[l + 1..r]))
        }

        let Some((cpu_idx, cpu)) = extract('[', ']', s) else {
            return Err(ParseError::InvalidCpu);
        };

        let Some(msg_idx) = s[cpu_idx..].find(MARKER) else {
            return Err(ParseError::InvalidLog);
        };
        let msg_idx = cpu_idx + msg_idx;

        let Some((level_idx, level)) = extract('[', ']', &s[msg_idx..]) else {
            return Err(ParseError::InvalidLevel);
        };
        let level_idx = level_idx + msg_idx;

        let Ok(cpu) = cpu.parse() else {
            return Err(ParseError::InvalidCpu);
        };

        let msg = s[level_idx + 1..].trim().to_string();

        let kind = if &level[0..1] == "<" {
            let name = level[1..].trim().to_string();
            Kind::EndSpan(name)
        } else {
            if &level[0..1] == ">" {
                let Ok(level) = Level::from_str(&level[1..]) else {
                    return Err(ParseError::InvalidLevel);
                };
                Kind::StartSpan(msg, level)
            } else {
                let Ok(level) = Level::from_str(level) else {
                    return Err(ParseError::InvalidLevel);
                };
                Kind::Message(msg, level)
            }
        };

        Ok(Event { kind, cpu })
    }
}

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

    #[test]
    fn parse_message() {
        let log = format!(
            "packets-149149 [{:03}] ...11 78517.088267: bpf_trace_printk: [{}] {}",
            7,
            Level::DEBUG,
            "test"
        );
        let event: Event = log.parse().expect("parse");
        assert_eq!(
            event.kind,
            Kind::Message(String::from("test"), Level::DEBUG)
        );
        assert_eq!(event.cpu, 7);
    }

    #[test]
    fn parse_span_start() {
        let log = format!(
            "packets-149149 [{:03}] ...11 78517.088267: bpf_trace_printk: [>{}] {}",
            7,
            Level::ERROR,
            "test"
        );
        let event: Event = log.parse().expect("parse");
        assert_eq!(
            event.kind,
            Kind::StartSpan(String::from("test"), Level::ERROR)
        );
        assert_eq!(event.cpu, 7);
    }

    #[test]
    fn parse_span_end() {
        let log = format!(
            "packets-149149 [{:03}] ...11 78517.088267: bpf_trace_printk: [<{}]",
            12, "test_start"
        );
        let event: Event = log.parse().expect("parse");
        assert_eq!(
            event.kind,
            Kind::EndSpan(String::from("test_start"))
        );
        assert_eq!(event.cpu, 12);
    }
}