Skip to main content

profile_bee/
types.rs

1use std::path::Path;
2use std::path::PathBuf;
3
4use profile_bee_common::StackInfo;
5
6/// Container for stack frame information with count
7///
8/// Used to track how many times a particular stack trace appears
9/// in the profile data for generating accurate flamegraphs.
10pub struct FrameCount {
11    pub frames: Vec<StackFrameInfo>,
12    pub count: u64,
13}
14
15/// Struct to contain information about a userspace/kernel stack frame
16///
17/// Represents a single frame in a stack trace with information about its
18/// memory address, associated binary, symbol name, and source location.
19/// Used for generating human-readable stack traces in flamegraphs.
20#[derive(Debug, Default, Clone, Eq, PartialEq)]
21pub struct StackFrameInfo {
22    pub pid: usize,
23    pub cmd: String,
24
25    /// Physical memory address
26    pub address: u64,
27    /// Shared Object / Module
28    pub object_path: Option<PathBuf>,
29
30    /// Source file and location
31    pub symbol: Option<String>,
32
33    /// Source file and location
34    pub source: Option<String>,
35
36    pub cpu_id: Option<u32>,
37
38    /// namespace
39    pub ns: Option<u64>,
40}
41
42impl StackFrameInfo {
43    /// Creates an empty/default StackFrameInfo
44    pub fn prepare(meta: &StackInfo) -> Self {
45        Self {
46            pid: meta.tgid as usize,
47            cmd: meta.get_cmd(),
48            // "".to_string(), // don't really need meta.get_cmd(),
49            ..Default::default()
50        }
51    }
52
53    /// Creates an StackFrameInfo placeholder for process name
54    pub fn process_only(meta: &StackInfo) -> Self {
55        let cmd = meta.get_cmd();
56        let with_pid = false;
57
58        let sym = if with_pid {
59            format!("{} ({})", cmd, meta.tgid)
60        } else {
61            cmd.to_owned()
62        };
63
64        Self {
65            pid: meta.tgid as usize,
66            cmd,
67            symbol: Some(sym),
68            ..Default::default()
69        }
70    }
71
72    pub fn new(address: u64, object_path: Option<PathBuf>) -> Self {
73        Self {
74            address,
75            object_path,
76            ..Default::default()
77        }
78    }
79
80    /// Physical memory address
81    pub fn address(&self) -> u64 {
82        self.address
83    }
84
85    /// Executable or library path. This can be empty if there is no associated object on the filesystem
86    pub fn object_path(&self) -> Option<&Path> {
87        self.object_path.as_deref()
88    }
89
90    pub fn fmt(&self) -> String {
91        format!(
92            "{:#x}\t{}\t{}\t{}",
93            self.address(),
94            self.cmd,
95            self.fmt_object(),
96            self.fmt_symbol()
97        )
98    }
99
100    pub fn fmt_symbol(&self) -> String {
101        format!(
102            "{}{}",
103            self.symbol.as_deref().unwrap_or(
104                //"[unknown]"
105                format!("{}+{:#x}", self.fmt_object(), self.address).as_str()
106            ),
107            self.fmt_source()
108        )
109    }
110
111    pub fn fmt_object(&self) -> &str {
112        self.object_path()
113            .and_then(|v| v.file_name())
114            .and_then(|v| v.to_str())
115            .unwrap_or(&self.cmd)
116    }
117
118    fn fmt_shorter_source(&self, count: usize) -> Option<String> {
119        StackFrameInfo::fmt_shorter(self.source.as_deref(), count)
120    }
121
122    /// instead of bla/meh/mah/test.c
123    /// returns mah/test.c for example
124    fn fmt_shorter(op: Option<&str>, count: usize) -> Option<String> {
125        op.map(|v| {
126            v.split('/')
127                .rev()
128                .take(count)
129                .map(|v| v.to_string())
130                .collect::<Vec<String>>()
131                .into_iter()
132                .rev()
133                .collect::<Vec<String>>()
134                .join("/")
135        })
136    }
137
138    pub fn fmt_source(&self) -> String {
139        let short = self.fmt_shorter_source(4);
140
141        if short.is_some() {
142            format!(" ({})", short.unwrap())
143        } else {
144            "".to_string()
145        }
146    }
147}
148
149pub trait StackInfoExt {
150    fn get_cmd(&self) -> String;
151    fn get_cpu_id(&self) -> Option<u32>;
152}
153
154impl StackInfoExt for StackInfo {
155    fn get_cmd(&self) -> String {
156        str_from_u8_nul_utf8(&self.cmd).unwrap().to_owned()
157    }
158
159    fn get_cpu_id(&self) -> Option<u32> {
160        if self.cpu == u32::MAX {
161            return None;
162        }
163
164        Some(self.cpu)
165    }
166}
167
168pub fn str_from_u8_nul_utf8(utf8_src: &[u8]) -> core::result::Result<&str, std::str::Utf8Error> {
169    let nul_range_end = utf8_src
170        .iter()
171        .position(|&c| c == b'\0')
172        .unwrap_or(utf8_src.len()); // default to length if no `\0` present
173    ::std::str::from_utf8(&utf8_src[0..nul_range_end])
174}