use alloc::{format, string::String, vec::Vec};
use core::num::NonZero;
use lru::LruCache;
use crate::{KernelTraceOps, TraceEntry, TracePointMap};
#[derive(Clone, Debug)]
pub struct TracePipeRecord {
timestamp: u64,
cpu_id: u32,
event: Vec<u8>,
}
impl TracePipeRecord {
pub fn new(timestamp: u64, cpu_id: u32, event: Vec<u8>) -> Self {
Self {
timestamp,
cpu_id,
event,
}
}
pub fn timestamp(&self) -> u64 {
self.timestamp
}
pub fn cpu_id(&self) -> u32 {
self.cpu_id
}
pub fn event(&self) -> &[u8] {
&self.event
}
}
pub trait TracePipeOps {
fn peek(&self) -> Option<&TracePipeRecord>;
fn pop(&mut self) -> Option<TracePipeRecord>;
fn is_empty(&self) -> bool;
}
pub struct TracePipeRaw {
max_record: usize,
event_buf: Vec<TracePipeRecord>,
}
impl TracePipeRaw {
pub const fn new(max_record: usize) -> Self {
Self {
max_record,
event_buf: Vec::new(),
}
}
pub fn set_max_record(&mut self, max_record: usize) {
self.max_record = max_record;
if self.event_buf.len() > max_record {
let remove_count = self.event_buf.len() - max_record;
self.event_buf.drain(0..remove_count);
}
}
pub fn push_event(&mut self, event: Vec<u8>) {
self.push_record(0, 0, event);
}
pub fn push_record(&mut self, timestamp: u64, cpu_id: u32, event: Vec<u8>) {
if self.max_record == 0 {
return;
}
if self.event_buf.len() >= self.max_record {
self.event_buf.remove(0); }
self.event_buf
.push(TracePipeRecord::new(timestamp, cpu_id, event));
}
pub fn event_count(&self) -> usize {
self.event_buf.len()
}
pub fn clear(&mut self) {
self.event_buf.clear();
}
pub fn snapshot(&self) -> TracePipeSnapshot {
TracePipeSnapshot::new(self.event_buf.clone())
}
pub fn max_record(&self) -> usize {
self.max_record
}
}
impl TracePipeOps for TracePipeRaw {
fn peek(&self) -> Option<&TracePipeRecord> {
self.event_buf.first()
}
fn pop(&mut self) -> Option<TracePipeRecord> {
if self.event_buf.is_empty() {
None
} else {
Some(self.event_buf.remove(0))
}
}
fn is_empty(&self) -> bool {
self.event_buf.is_empty()
}
}
#[derive(Debug)]
pub struct TracePipeSnapshot(Vec<TracePipeRecord>);
impl TracePipeSnapshot {
pub fn new(event_buf: Vec<TracePipeRecord>) -> Self {
Self(event_buf)
}
pub fn default_fmt_str(&self) -> String {
let show = "#
#
# _-----=> irqs-off/BH-disabled
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| / _-=> migrate-disable
# |||| / delay
# TASK-PID CPU# ||||| TIMESTAMP FUNCTION
# | | | ||||| | |
";
format!(
"# tracer: nop\n#\n# entries-in-buffer/entries-written: {}/{} #P:32\n{}",
self.0.len(),
self.0.len(),
show
)
}
}
impl TracePipeOps for TracePipeSnapshot {
fn peek(&self) -> Option<&TracePipeRecord> {
self.0.first()
}
fn pop(&mut self) -> Option<TracePipeRecord> {
if self.0.is_empty() {
None
} else {
Some(self.0.remove(0))
}
}
fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
pub struct TraceCmdLineCache {
cmdline: LruCache<u32, String>,
}
impl TraceCmdLineCache {
pub fn new(max_record: NonZero<usize>) -> Self {
Self {
cmdline: LruCache::new(max_record),
}
}
pub fn insert(&mut self, id: u32, cmdline: &str) {
const MAX_CMDLINE_LEN: usize = 16;
let (cmdline, _) = cmdline.split_at(MAX_CMDLINE_LEN.min(cmdline.len()));
let line = format!("{} {}\n", id, cmdline);
self.cmdline.put(id, line);
}
pub fn get(&self, id: u32) -> Option<&str> {
self.cmdline
.iter()
.find(|(key, _)| **key == id)
.map(|(_, value)| {
let line = value.as_str();
line.splitn(2, ' ').nth(1).unwrap().trim_end_matches('\n')
})
}
pub fn set_max_record(&mut self, max_len: NonZero<usize>) {
self.cmdline.resize(max_len);
}
pub fn max_record(&self) -> usize {
self.cmdline.cap().get()
}
pub fn snapshot(&self) -> TraceCmdLineCacheSnapshot {
let cmdline = self
.cmdline
.iter()
.map(|(_, value)| value)
.cloned()
.collect();
TraceCmdLineCacheSnapshot::new(cmdline)
}
}
#[derive(Debug)]
pub struct TraceCmdLineCacheSnapshot(Vec<String>);
impl TraceCmdLineCacheSnapshot {
pub fn new(cmdline: Vec<String>) -> Self {
Self(cmdline)
}
pub fn peek(&self) -> Option<&String> {
self.0.first()
}
pub fn pop(&mut self) -> Option<String> {
if self.0.is_empty() {
None
} else {
Some(self.0.remove(0))
}
}
}
pub struct TraceEntryParser;
impl TraceEntryParser {
pub fn parse<K: KernelTraceOps>(
tracepoint_map: &TracePointMap<K>,
cmdline_cache: &TraceCmdLineCache,
record: &TracePipeRecord,
) -> String {
let entry = record.event();
let trace_entry = unsafe { &*(entry.as_ptr() as *const TraceEntry) };
let id = trace_entry.common_type as u32;
let tracepoint = tracepoint_map.get(&id).expect("TracePoint not found");
let fmt_func = tracepoint.fmt_func();
let offset = core::mem::size_of::<TraceEntry>();
let str = fmt_func(&entry[offset..]);
let time = record.timestamp();
let cpu_id = record.cpu_id();
let pid = trace_entry.common_pid;
let pname = cmdline_cache
.get(trace_entry.common_pid as u32)
.unwrap_or("<...>");
let secs = time / 1_000_000_000;
let usec_rem = time % 1_000_000_000 / 1000;
format!(
"{:>16}-{:<7} [{:03}] {} {:5}.{:06}: {}({})\n",
pname,
pid,
cpu_id,
trace_entry.trace_print_lat_fmt(),
secs,
usec_rem,
tracepoint.name(),
str
)
}
}