fxt/
profiler.rs

1// Copyright 2025 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::init::Ticks;
6use crate::session::ResolveCtx;
7use crate::thread::{ProcessKoid, ProcessRef, ThreadKoid, ThreadRef};
8use crate::{
9    PROFILER_RECORD_TYPE, ParseError, ParseResult, ParseWarning, take_n_padded, trace_header,
10};
11use flyweights::FlyStr;
12use nom::Parser;
13use nom::combinator::all_consuming;
14use nom::number::complete::le_u64;
15
16const MODULE_RECORD_TYPE: u8 = 0;
17const MAPPING_RECORD_TYPE: u8 = 1;
18const BACKTRACE_RECORD_TYPE: u8 = 2;
19
20macro_rules! profiler_header {
21    ($name:ident $(($profiler_ty:expr))? { $($record_specific:tt)* }) => {
22        trace_header! {
23            $name (PROFILER_RECORD_TYPE) {
24                $($record_specific)*
25                u8, profiler_sub_type: 16, 19;
26                u8, thread_ref: 20, 27;
27            } => |_h: &$name| {
28                $(if _h.profiler_sub_type() != $profiler_ty {
29                    return Err(ParseError::WrongType {
30                        context: stringify!($name),
31                        expected: $profiler_ty,
32                        observed: _h.profiler_sub_type(),
33                    });
34                })?
35                Ok(())
36            }
37        }
38    };
39}
40
41profiler_header! {BaseProfilerRecordHeader{}}
42
43profiler_header! {
44    ModuleRecordHeader(MODULE_RECORD_TYPE) {
45        // [28 .. 43]: module id (16 bit)
46        u16, module_id: 28, 43;
47        // [44 .. 51]: the length of name in bytes
48        u8, name_length: 44, 51;
49        // [52 .. 59]: the length of build id in bytes
50        u8, build_id_length: 52, 59;
51    }
52}
53
54profiler_header! {
55    MmapRecordHeader(MAPPING_RECORD_TYPE) {
56        // [28 .. 43]: module id (16 bit)
57        u16, module_id: 28, 43;
58        // [44 .. 46]: flags
59        u8, flags: 44, 46;
60    }
61}
62
63profiler_header! {
64    BacktraceRecordHeader(BACKTRACE_RECORD_TYPE) {
65        // [28 .. 35]: the number of backtrace record
66        u8, num_records: 28, 35;
67        // [36 .. 63]: flags
68        u32, flags: 36, 63;
69    }
70}
71
72#[derive(Debug, PartialEq)]
73pub(super) enum RawProfilerRecordType<'a> {
74    RawModuleRecord(RawModuleRecord<'a>),
75    RawMappingRecord(RawMappingRecord),
76    RawBacktraceRecord(RawBacktraceRecord),
77    Unknown { raw_type: u8 },
78}
79
80#[derive(Debug, PartialEq)]
81pub(super) struct RawModuleRecord<'a> {
82    ticks: Ticks,
83    process: ProcessRef,
84    thread: ThreadRef,
85    module_id: u16,
86    name: String,
87    build_id: &'a [u8],
88}
89
90impl<'a> RawModuleRecord<'a> {
91    pub(super) fn parse(buf: &'a [u8]) -> ParseResult<'a, Self> {
92        let (buf, header) = ModuleRecordHeader::parse(buf)?;
93        let (rem, payload) = header.take_payload(buf)?;
94        let (payload, ticks) = Ticks::parse(payload)?;
95        let (payload, process) = ProcessRef::parse(header.thread_ref(), payload)?;
96        let (payload, thread) = ThreadRef::parse(header.thread_ref(), payload)?;
97        let (payload, name) = parse_padded_module_name(header.name_length() as usize, payload)?;
98        let (empty, build_id) =
99            all_consuming(|p| take_n_padded(header.build_id_length() as usize, p))
100                .parse(payload)?;
101        assert!(empty.is_empty(), "all_consuming must not return any remaining buffer");
102        Ok((rem, Self { ticks, process, thread, module_id: header.module_id(), name, build_id }))
103    }
104}
105
106pub(crate) fn parse_padded_module_name(unpadded_len: usize, buf: &[u8]) -> ParseResult<'_, String> {
107    let (rem, bytes) = take_n_padded(unpadded_len, buf)?;
108    Ok((rem, String::from_utf8_lossy(bytes).into_owned()))
109}
110
111#[derive(Debug, PartialEq)]
112pub(super) struct RawMappingRecord {
113    ticks: Ticks,
114    process: ProcessRef,
115    thread: ThreadRef,
116    module_id: u16,
117    start_addr: u64,
118    range: u64,
119    vaddr: u64,
120    flags: u8,
121}
122
123impl RawMappingRecord {
124    pub(super) fn parse(buf: &[u8]) -> ParseResult<'_, Self> {
125        let (buf, header) = MmapRecordHeader::parse(buf)?;
126        let (rem, payload) = header.take_payload(buf)?;
127        let (payload, ticks) = Ticks::parse(payload)?;
128        let (payload, process) = ProcessRef::parse(header.thread_ref(), payload)?;
129        let (payload, thread) = ThreadRef::parse(header.thread_ref(), payload)?;
130        let (payload, start_addr) = le_u64(payload)?;
131        let (payload, range) = le_u64(payload)?;
132        let (empty, vaddr) = le_u64(payload)?;
133        assert!(empty.is_empty(), "after vaddr must not return any remaining buffer");
134        Ok((
135            rem,
136            Self {
137                ticks,
138                process,
139                thread,
140                module_id: header.module_id(),
141                start_addr,
142                range,
143                vaddr,
144                flags: header.flags(),
145            },
146        ))
147    }
148}
149
150#[derive(Debug, PartialEq)]
151pub(super) struct RawBacktraceRecord {
152    ticks: Ticks,
153    process: ProcessRef,
154    thread: ThreadRef,
155    num_records: u8,
156    data: Vec<u64>,
157}
158
159impl RawBacktraceRecord {
160    pub(super) fn parse(buf: &[u8]) -> ParseResult<'_, Self> {
161        let (buf, header) = BacktraceRecordHeader::parse(buf)?;
162        let (rem, payload) = header.take_payload(buf)?;
163        let (payload, ticks) = Ticks::parse(payload)?;
164        let (payload, process) = ProcessRef::parse(header.thread_ref(), payload)?;
165        let (payload, thread) = ThreadRef::parse(header.thread_ref(), payload)?;
166        let (empty, data) = all_consuming(nom::multi::count(le_u64, header.num_records() as usize))
167            .parse(payload)?;
168        assert!(empty.is_empty(), "all_consuming must not return any remaining buffer");
169        Ok((rem, Self { ticks, process, thread, num_records: header.num_records(), data }))
170    }
171}
172
173impl<'a> RawProfilerRecordType<'a> {
174    pub(super) fn parse(buf: &'a [u8]) -> ParseResult<'a, Self> {
175        use nom::combinator::map;
176        match BaseProfilerRecordHeader::parse(buf)?.1.profiler_sub_type() {
177            MODULE_RECORD_TYPE => {
178                map(RawModuleRecord::parse, |module| Self::RawModuleRecord(module)).parse(buf)
179            }
180            MAPPING_RECORD_TYPE => {
181                map(RawMappingRecord::parse, |mapping| Self::RawMappingRecord(mapping)).parse(buf)
182            }
183            BACKTRACE_RECORD_TYPE => {
184                map(RawBacktraceRecord::parse, |backtrace| Self::RawBacktraceRecord(backtrace))
185                    .parse(buf)
186            }
187            unknown => Ok((buf, Self::Unknown { raw_type: unknown })),
188        }
189    }
190}
191
192#[derive(Clone, Debug, PartialEq)]
193pub enum ProfilerRecord {
194    Module(ModuleRecord),
195    Mapping(MappingRecord),
196    Backtrace(BacktraceRecord),
197}
198
199impl ProfilerRecord {
200    pub(super) fn resolve<'a>(
201        ctx: &mut ResolveCtx,
202        raw: RawProfilerRecordType<'a>,
203    ) -> Option<Self> {
204        match raw {
205            RawProfilerRecordType::RawModuleRecord(raw) => {
206                Some(Self::Module(ModuleRecord::resolve(ctx, raw)))
207            }
208            RawProfilerRecordType::RawMappingRecord(raw) => {
209                Some(Self::Mapping(MappingRecord::resolve(ctx, raw)))
210            }
211            RawProfilerRecordType::RawBacktraceRecord(raw) => {
212                Some(Self::Backtrace(BacktraceRecord::resolve(ctx, raw)))
213            }
214            RawProfilerRecordType::Unknown { raw_type } => {
215                ctx.add_warning(ParseWarning::UnknownProfilerRecordType(raw_type));
216                None
217            }
218        }
219    }
220}
221
222#[derive(Clone, Debug, PartialEq)]
223pub struct ModuleRecord {
224    pub timestamp: i64,
225    pub process: ProcessKoid,
226    pub thread: ThreadKoid,
227    pub module_id: u16,
228    pub name: FlyStr,
229    pub build_id: Vec<u8>,
230}
231
232impl ModuleRecord {
233    pub(super) fn resolve<'a>(ctx: &mut ResolveCtx, raw: RawModuleRecord<'a>) -> Self {
234        Self {
235            timestamp: ctx.resolve_ticks(raw.ticks),
236            process: ctx.resolve_process(raw.process),
237            thread: ctx.resolve_thread(raw.thread),
238            module_id: raw.module_id,
239            name: raw.name.into(),
240            build_id: raw.build_id.to_vec(),
241        }
242    }
243}
244
245#[derive(Clone, Debug, PartialEq)]
246pub struct MappingRecord {
247    pub timestamp: i64,
248    pub process: ProcessKoid,
249    pub thread: ThreadKoid,
250    pub module_id: u16,
251    pub start_addr: u64,
252    pub range: u64,
253    pub vaddr: u64,
254    pub flags: u8,
255}
256
257impl MappingRecord {
258    pub(super) fn resolve(ctx: &mut ResolveCtx, raw: RawMappingRecord) -> Self {
259        Self {
260            timestamp: ctx.resolve_ticks(raw.ticks),
261            process: ctx.resolve_process(raw.process),
262            thread: ctx.resolve_thread(raw.thread),
263            module_id: raw.module_id,
264            start_addr: raw.start_addr,
265            range: raw.range,
266            vaddr: raw.vaddr,
267            flags: raw.flags,
268        }
269    }
270}
271
272#[derive(Clone, Debug, PartialEq)]
273pub struct BacktraceRecord {
274    pub timestamp: i64,
275    pub process: ProcessKoid,
276    pub thread: ThreadKoid,
277    pub num_records: u8,
278    pub data: Vec<u64>,
279}
280
281impl BacktraceRecord {
282    pub(super) fn resolve(ctx: &mut ResolveCtx, raw: RawBacktraceRecord) -> Self {
283        Self {
284            timestamp: ctx.resolve_ticks(raw.ticks),
285            process: ctx.resolve_process(raw.process),
286            thread: ctx.resolve_thread(raw.thread),
287            num_records: raw.num_records,
288            data: raw.data,
289        }
290    }
291}
292
293#[cfg(test)]
294mod tests {
295    use super::*;
296
297    #[test]
298    fn test_parse_module_record_from_hardcoded_bytes() {
299        let mut buffer = vec![];
300
301        let record_type: u64 = 10;
302        let sub_type: u64 = 0; // sub-type 0
303        let thread_ref: u64 = 0; // Inline process koid
304        let module_id: u64 = 99;
305        let name_len: u64 = 14; // "test_module.so"
306        let build_id_len: u64 = 20;
307        let size_words: u64 = 9; // (Header + 64 payload bytes) / 8
308
309        let header_val: u64 = (build_id_len << 52)
310            | (name_len << 44)
311            | (module_id << 28)
312            | (thread_ref << 20)
313            | (sub_type << 16)
314            | (size_words << 4)
315            | record_type;
316
317        buffer.extend_from_slice(&header_val.to_le_bytes());
318        buffer.extend_from_slice(&[0x88, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); // Ticks(5000)
319        buffer.extend_from_slice(&[0x7b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); // ProcessKoid(123)
320        buffer.extend_from_slice(&[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); // ThreadKoid(0)
321        buffer.extend_from_slice(&[
322            // "test_module.so" (14 bytes) + 2 padding
323            0x74, 0x65, 0x73, 0x74, 0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2e, 0x73, 0x6f,
324            0x00, 0x00,
325        ]);
326        buffer.extend_from_slice(&[
327            // Build ID (20 bytes): 68920bd06a8149b58004cea8a0dd260b05e87d57
328            0x68, 0x92, 0x0b, 0xd0, 0x6a, 0x81, 0x49, 0xb5, 0x80, 0x04, 0xce, 0xa8, 0xa0, 0xdd,
329            0x26, 0x0b, 0x05, 0xe8, 0x7d, 0x57,
330        ]);
331        buffer.extend_from_slice(&[0; 4]); // Build ID padding to 8-byte alignment
332
333        let (empty, raw_record) = RawProfilerRecordType::parse(&buffer).unwrap();
334        assert!(empty.is_empty(), "Buffer should be fully consumed");
335
336        let mut ctx = ResolveCtx::new();
337        let resolved = ProfilerRecord::resolve(&mut ctx, raw_record).unwrap();
338
339        let expected = ProfilerRecord::Module(ModuleRecord {
340            timestamp: ctx.resolve_ticks(Ticks(5000)),
341            process: ProcessKoid::from(123u64),
342            thread: ThreadKoid::from(0u64),
343            module_id: 99,
344            name: "test_module.so".into(),
345            build_id: vec![
346                0x68, 0x92, 0x0b, 0xd0, 0x6a, 0x81, 0x49, 0xb5, 0x80, 0x04, 0xce, 0xa8, 0xa0, 0xdd,
347                0x26, 0x0b, 0x05, 0xe8, 0x7d, 0x57,
348            ],
349        });
350        assert_eq!(resolved, expected);
351    }
352
353    #[test]
354    fn test_parse_mapping_record_from_hardcoded_bytes() {
355        let mut buffer = vec![];
356
357        let record_type: u64 = 10;
358        let sub_type: u64 = 1; // sub-type 1
359        let thread_ref: u64 = 0; // Inline process koid
360        let module_id: u64 = 101;
361        let flags: u64 = 1;
362        let size_words: u64 = 7; // (Header + 48 payload bytes) / 8
363
364        let header_val: u64 = (flags << 44)
365            | (module_id << 28)
366            | (thread_ref << 20)
367            | (sub_type << 16)
368            | (size_words << 4)
369            | record_type;
370
371        buffer.extend_from_slice(&header_val.to_le_bytes());
372        buffer.extend_from_slice(&[0x70, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); // Ticks(6000)
373        buffer.extend_from_slice(&[0xc8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); // ProcessKoid(456)
374        buffer.extend_from_slice(&[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); // ThreadKoid(0)
375        buffer.extend_from_slice(&[0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); // Start Address(0x1000)
376        buffer.extend_from_slice(&[0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); // Addr Range(0x2000)
377        buffer.extend_from_slice(&[0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); // Vaddr(0x3000)
378
379        let (empty, raw_record) = RawProfilerRecordType::parse(&buffer).unwrap();
380        assert!(empty.is_empty(), "Buffer should be fully consumed");
381
382        let mut ctx = ResolveCtx::new();
383        let resolved = ProfilerRecord::resolve(&mut ctx, raw_record).unwrap();
384
385        let expected = ProfilerRecord::Mapping(MappingRecord {
386            timestamp: ctx.resolve_ticks(Ticks(6000)),
387            process: ProcessKoid::from(456u64),
388            thread: ThreadKoid::from(0u64),
389            module_id: 101,
390            start_addr: 0x1000,
391            range: 0x2000,
392            vaddr: 0x3000,
393            flags: 1,
394        });
395        assert_eq!(resolved, expected);
396    }
397
398    #[test]
399    fn test_parse_backtrace_record_from_hardcoded_bytes() {
400        let mut buffer = vec![];
401
402        let record_type: u64 = 10;
403        let sub_type: u64 = 2; // sub-type 2
404        let thread_ref: u64 = 0; // Inline process and thread koid
405        let num_records: u64 = 2;
406        let flags: u64 = 2;
407        let size_words: u64 = 6; // (Header + 40 payload bytes) / 8
408
409        let header_val: u64 = (flags << 36)
410            | (num_records << 28)
411            | (thread_ref << 20)
412            | (sub_type << 16)
413            | (size_words << 4)
414            | record_type;
415
416        buffer.extend_from_slice(&header_val.to_le_bytes());
417        buffer.extend_from_slice(&[0x58, 0x1B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); // Ticks(7000)
418        buffer.extend_from_slice(&[0x15, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); // ProcessKoid(789)
419        buffer.extend_from_slice(&[0xDB, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); // ThreadKoid(987)
420        buffer.extend_from_slice(&[0x99, 0x14, 0x4b, 0x96, 0x2a, 0x40, 0x00, 0x00]); // Backtrace data[0] (0x402a964b1499)
421        buffer.extend_from_slice(&[0x3b, 0x29, 0xe0, 0x4a, 0x00, 0x00, 0x00, 0x00]); // Backtrace data[1] (0x4ae0293b)
422
423        let (empty, raw_record) = RawProfilerRecordType::parse(&buffer).unwrap();
424        assert!(empty.is_empty(), "Buffer should be fully consumed");
425
426        let mut ctx = ResolveCtx::new();
427        let resolved = ProfilerRecord::resolve(&mut ctx, raw_record).unwrap();
428
429        let expected = ProfilerRecord::Backtrace(BacktraceRecord {
430            timestamp: ctx.resolve_ticks(Ticks(7000)),
431            process: ProcessKoid::from(789u64),
432            thread: ThreadKoid::from(987u64),
433            num_records: 2,
434            data: vec![0x402a964b1499, 0x4ae0293b],
435        });
436        assert_eq!(resolved, expected);
437    }
438}