linux_perf_data/
simpleperf.rs

1use std::collections::HashMap;
2
3use byteorder::{BigEndian, ByteOrder, LittleEndian, ReadBytesExt};
4use linux_perf_event_reader::Endianness;
5use prost::Message;
6
7use crate::Error;
8
9pub struct SimplePerfEventType {
10    pub name: String,
11    pub type_: u64,
12    pub config: u64,
13}
14
15impl SimplePerfEventType {
16    pub fn new(name: String, type_: u64, config: u64) -> Self {
17        Self {
18            name,
19            type_,
20            config,
21        }
22    }
23}
24
25/// Parse a nul-byte-separated list of (key, value) pairs into a string map.
26///
27/// Simpleperf assembles the info map contents here: https://cs.android.com/android/platform/superproject/+/main:system/extras/simpleperf/cmd_record.cpp;l=2109-2157;drc=aec31f83f65ac7c58e67c9605d9cc438545f5c94
28///
29/// # Example:
30///
31/// ```plaintext
32/// {
33///     "android_sdk_version": "33",
34///     "android_build_type": "user",
35///     "event_type_info": "cpu-clock,1,0\nsched:sched_switch,2,45",
36///     "trace_offcpu": "true",
37///     "app_type": "debuggable",
38///     "product_props": "samsung:SM-A515F:a51nseea",
39///     "clockid": "monotonic",
40///     "system_wide_collection": "false",
41///     "android_version": "13",
42///     "kernel_version": "4.14.113-25950142",
43///     "android_build_fingerprint": "samsung/a51nseea/a51:13/TP1A.220624.014/A515FXXU7HWF1:user/release-keys",
44///     "app_package_name": "org.mozilla.geckoview_example",
45///     "kernel_symbols_available": "false",
46///     "timestamp": "1696864401",
47///     "simpleperf_version": "1.build.7848450",
48/// }
49/// ```
50pub fn parse_meta_info_map(bytes: &[u8]) -> Result<HashMap<&str, &str>, std::str::Utf8Error> {
51    let iter = bytes.split(|c| *c == b'\0');
52    let keys = iter.clone().step_by(2);
53    let values = iter.skip(1).step_by(2);
54    let mut map = HashMap::new();
55    for (key, value) in keys.zip(values) {
56        let key = std::str::from_utf8(key)?;
57        let value = std::str::from_utf8(value)?;
58        map.insert(key, value);
59    }
60    Ok(map)
61}
62
63pub fn get_event_types(meta_info_map: &HashMap<&str, &str>) -> Option<Vec<SimplePerfEventType>> {
64    let event_type_info = meta_info_map.get("event_type_info")?;
65    let mut event_types = Vec::new();
66    for line in event_type_info.split('\n') {
67        let mut parts = line.split(',');
68        let name = parts.next()?.to_string();
69        let type_ = parts.next()?.parse().ok()?;
70        let config = parts.next()?.parse().ok()?;
71        event_types.push(SimplePerfEventType::new(name, type_, config));
72    }
73    Some(event_types)
74}
75
76/// Constants used in [`SimpleperfFileRecord`]'s `type` property.
77pub mod simpleperf_dso_type {
78    pub const DSO_KERNEL: u32 = 0;
79    pub const DSO_KERNEL_MODULE: u32 = 1;
80    pub const DSO_ELF_FILE: u32 = 2;
81    /// For files containing dex files, like .vdex files.
82    pub const DSO_DEX_FILE: u32 = 3;
83    pub const DSO_SYMBOL_MAP_FILE: u32 = 4;
84    pub const DSO_UNKNOWN_FILE: u32 = 5;
85}
86
87/// Used in the `SIMPLEPERF_FILE` and `SIMPLEPERF_FILE2` section.
88///
89/// Carries symbol tables that were obtained on the device.
90#[derive(Clone, PartialEq, Eq, ::prost_derive::Message)]
91pub struct SimpleperfFileRecord {
92    #[prost(string, tag = "1")]
93    pub path: ::prost::alloc::string::String,
94    /// Uses constants from [`simpleperf_dso_type`].
95    #[prost(uint32, tag = "2")]
96    pub r#type: u32,
97    #[prost(uint64, tag = "3")]
98    pub min_vaddr: u64,
99    #[prost(message, repeated, tag = "4")]
100    pub symbol: ::prost::alloc::vec::Vec<SimpleperfSymbol>,
101    #[prost(oneof = "SimpleperfTypeSpecificInfo", tags = "5, 6, 7")]
102    pub type_specific_msg: ::core::option::Option<SimpleperfTypeSpecificInfo>,
103}
104
105/// A single symbol, contained in the symbol table inside a [`SimpleperfFileRecord`].
106#[derive(Clone, PartialEq, Eq, ::prost_derive::Message)]
107pub struct SimpleperfSymbol {
108    #[prost(uint64, tag = "1")]
109    pub vaddr: u64,
110    #[prost(uint32, tag = "2")]
111    pub len: u32,
112    #[prost(string, tag = "3")]
113    pub name: ::prost::alloc::string::String,
114}
115
116/// DEX-specific info inside a [`SimpleperfFileRecord`].
117#[derive(Clone, PartialEq, Eq, ::prost_derive::Message)]
118pub struct SimpleperfDexFileInfo {
119    #[prost(uint64, repeated, tag = "1")]
120    pub dex_file_offset: ::prost::alloc::vec::Vec<u64>,
121}
122
123/// ELF object specific info inside a [`SimpleperfFileRecord`].
124#[derive(Clone, PartialEq, Eq, ::prost_derive::Message)]
125pub struct SimpleperfElfFileInfo {
126    #[prost(uint64, tag = "1")]
127    pub file_offset_of_min_vaddr: u64,
128}
129
130/// Kernel module specific info inside a [`SimpleperfFileRecord`].
131#[derive(Clone, PartialEq, Eq, ::prost_derive::Message)]
132pub struct SimpleperfKernelModuleInfo {
133    #[prost(uint64, tag = "1")]
134    pub memory_offset_of_min_vaddr: u64,
135}
136
137/// Type-specif info inside a [`SimpleperfFileRecord`].
138#[derive(Clone, PartialEq, Eq, ::prost_derive::Oneof)]
139pub enum SimpleperfTypeSpecificInfo {
140    /// Only when type = DSO_DEX_FILE
141    #[prost(message, tag = "5")]
142    SimpleperfDexFileInfo(SimpleperfDexFileInfo),
143    /// Only when type = DSO_ELF_FILE
144    #[prost(message, tag = "6")]
145    ElfFile(SimpleperfElfFileInfo),
146    /// Only when type = DSO_KERNEL_MODULE
147    #[prost(message, tag = "7")]
148    KernelModule(SimpleperfKernelModuleInfo),
149}
150
151pub fn parse_file2_section(
152    mut bytes: &[u8],
153    endian: Endianness,
154) -> Result<Vec<SimpleperfFileRecord>, Error> {
155    let mut files = Vec::new();
156    // `bytes` contains the sequence of encoded SimpleperfFileRecord.
157    // Each record is proceded by a u32 which is the length in bytes
158    // of the protobuf-encoded representation.
159    while !bytes.is_empty() {
160        let len = match endian {
161            Endianness::LittleEndian => bytes.read_u32::<LittleEndian>()?,
162            Endianness::BigEndian => bytes.read_u32::<BigEndian>()?,
163        };
164        let len = len as usize;
165        let file_data = bytes.get(..len).ok_or(Error::FeatureSectionTooSmall)?;
166        bytes = &bytes[len..];
167        let file = SimpleperfFileRecord::decode(file_data)
168            .map_err(Error::ProtobufParsingSimpleperfFileSection)?;
169        files.push(file);
170    }
171    Ok(files)
172}
173
174/// Parses the legacy `SIMPLEPERF_FILE` section. This section is emitted by
175/// "on-device simpleperf" on Android 11 and 12 at least (not on 14).
176///
177/// "On-device simpleperf" is used whenever you profile a non-debuggable app,
178/// unless you have a rooted phone and manually run /data/local/tmp/simpleperf
179/// as root.
180pub fn parse_file_section(
181    mut bytes: &[u8],
182    endian: Endianness,
183) -> Result<Vec<SimpleperfFileRecord>, Error> {
184    let mut files = Vec::new();
185    // `bytes` contains the sequence of encoded SimpleperfFileRecord.
186    // Each record is proceded by a u32 which is the length in bytes
187    // of the manually-encoded representation.
188    while !bytes.is_empty() {
189        let len = match endian {
190            Endianness::LittleEndian => bytes.read_u32::<LittleEndian>()?,
191            Endianness::BigEndian => bytes.read_u32::<BigEndian>()?,
192        };
193        let len = len as usize;
194        let file_data = bytes.get(..len).ok_or(Error::FeatureSectionTooSmall)?;
195        bytes = &bytes[len..];
196        let file_result = match endian {
197            Endianness::LittleEndian => SimpleperfFileRecord::decode_v1::<LittleEndian>(file_data),
198            Endianness::BigEndian => SimpleperfFileRecord::decode_v1::<BigEndian>(file_data),
199        };
200        let file = file_result.map_err(Error::ParsingSimpleperfFileV1Section)?;
201        files.push(file);
202    }
203    Ok(files)
204}
205
206impl SimpleperfFileRecord {
207    pub fn decode_v1<T: ByteOrder>(mut data: &[u8]) -> Result<Self, std::io::Error> {
208        let path = data.read_nul_terminated_str()?.to_owned();
209        let file_type = data.read_u32::<T>()?;
210        if file_type > simpleperf_dso_type::DSO_UNKNOWN_FILE {
211            return Err(std::io::Error::new(
212                std::io::ErrorKind::InvalidData,
213                format!("unknown file type for {path} in file feature section: {file_type}"),
214            ));
215        }
216        let min_vaddr = data.read_u64::<T>()?;
217        let symbol_count = data.read_u32::<T>()? as usize;
218        if symbol_count > data.len() {
219            return Err(std::io::Error::new(
220                std::io::ErrorKind::InvalidData,
221                format!(
222                    "Unreasonable symbol count for {path} in file feature section: {symbol_count}"
223                ),
224            ));
225        }
226        let mut symbols = Vec::with_capacity(symbol_count);
227        for _ in 0..symbol_count {
228            let vaddr = data.read_u64::<T>()?;
229            let len = data.read_u32::<T>()?;
230            let name = data.read_nul_terminated_str()?.to_owned();
231            symbols.push(SimpleperfSymbol { vaddr, len, name });
232        }
233        let type_specific_msg = match file_type {
234            simpleperf_dso_type::DSO_DEX_FILE => {
235                let offset_count = data.read_u32::<T>()? as usize;
236                if offset_count > data.len() {
237                    return Err(std::io::Error::new(
238                    std::io::ErrorKind::InvalidData,
239                    format!("Unreasonable symbol count for {path} in file feature section: {offset_count}"),
240                ));
241                }
242                let mut dex_file_offset = Vec::with_capacity(offset_count);
243                for _ in 0..offset_count {
244                    dex_file_offset.push(data.read_u64::<T>()?);
245                }
246                Some(SimpleperfTypeSpecificInfo::SimpleperfDexFileInfo(
247                    SimpleperfDexFileInfo { dex_file_offset },
248                ))
249            }
250            simpleperf_dso_type::DSO_ELF_FILE => {
251                let file_offset_of_min_vaddr = if data.is_empty() {
252                    u64::MAX
253                } else {
254                    data.read_u64::<T>()?
255                };
256                Some(SimpleperfTypeSpecificInfo::ElfFile(SimpleperfElfFileInfo {
257                    file_offset_of_min_vaddr,
258                }))
259            }
260            simpleperf_dso_type::DSO_KERNEL_MODULE => {
261                let memory_offset_of_min_vaddr = if data.is_empty() {
262                    u64::MAX
263                } else {
264                    data.read_u64::<T>()?
265                };
266                Some(SimpleperfTypeSpecificInfo::KernelModule(
267                    SimpleperfKernelModuleInfo {
268                        memory_offset_of_min_vaddr,
269                    },
270                ))
271            }
272            _ => None,
273        };
274
275        Ok(Self {
276            path,
277            r#type: file_type,
278            min_vaddr,
279            symbol: symbols,
280            type_specific_msg,
281        })
282    }
283}
284
285trait SliceReadStringExt<'a> {
286    fn read_nul_terminated_str(&mut self) -> std::io::Result<&'a str>;
287}
288
289impl<'a> SliceReadStringExt<'a> for &'a [u8] {
290    fn read_nul_terminated_str(&mut self) -> std::io::Result<&'a str> {
291        let Some(len) = memchr::memchr(0, self) else {
292            return Err(std::io::Error::new(
293                std::io::ErrorKind::UnexpectedEof,
294                "Nul terminator not found",
295            ));
296        };
297        let s = std::str::from_utf8(&self[..len])
298            .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
299        *self = &self[(len + 1)..];
300        Ok(s)
301    }
302}