Skip to main content

starnix_core/perf/
mod.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::task::dynamic_thread_spawner::SpawnRequestBuilder;
6use anyhow::Context;
7use fidl_fuchsia_cpu_profiler as profiler;
8use fuchsia_component::client::connect_to_protocol;
9use fuchsia_runtime;
10use futures::StreamExt;
11use futures::channel::mpsc as future_mpsc;
12use regex_lite::Regex;
13use std::collections::HashMap;
14use std::error::Error;
15use std::sync::atomic::{AtomicU64, Ordering};
16use std::sync::{Arc, OnceLock, mpsc as sync_mpsc};
17use zerocopy::{Immutable, IntoBytes};
18
19use futures::io::{AsyncReadExt, Cursor};
20use fxt::TraceRecord;
21use fxt::profiler::ProfilerRecord;
22use fxt::session::SessionParser;
23use seq_lock::{SeqLock, SeqLockable, WriteSize};
24use starnix_logging::{log_info, log_warn, track_stub};
25use starnix_sync::{LockDepMutex, LockDepRwLock, PerfEventLevel, PerfFormatIdLookupTableLock};
26use starnix_syscalls::{SUCCESS, SyscallArg, SyscallResult};
27use starnix_uapi::arch32::{
28    PERF_EVENT_IOC_DISABLE, PERF_EVENT_IOC_ENABLE, PERF_EVENT_IOC_ID,
29    PERF_EVENT_IOC_MODIFY_ATTRIBUTES, PERF_EVENT_IOC_PAUSE_OUTPUT, PERF_EVENT_IOC_PERIOD,
30    PERF_EVENT_IOC_QUERY_BPF, PERF_EVENT_IOC_REFRESH, PERF_EVENT_IOC_RESET, PERF_EVENT_IOC_SET_BPF,
31    PERF_EVENT_IOC_SET_FILTER, PERF_EVENT_IOC_SET_OUTPUT, PERF_RECORD_MISC_KERNEL,
32    perf_event_sample_format_PERF_SAMPLE_CALLCHAIN, perf_event_sample_format_PERF_SAMPLE_ID,
33    perf_event_sample_format_PERF_SAMPLE_IDENTIFIER, perf_event_sample_format_PERF_SAMPLE_IP,
34    perf_event_sample_format_PERF_SAMPLE_PERIOD, perf_event_sample_format_PERF_SAMPLE_TID,
35    perf_event_type_PERF_RECORD_SAMPLE,
36};
37use starnix_uapi::errors::Errno;
38use starnix_uapi::open_flags::OpenFlags;
39use starnix_uapi::user_address::UserRef;
40use starnix_uapi::{
41    errno, error, from_status_like_fdio, perf_event_attr, perf_event_header,
42    perf_event_mmap_page__bindgen_ty_1, perf_event_read_format_PERF_FORMAT_GROUP,
43    perf_event_read_format_PERF_FORMAT_ID, perf_event_read_format_PERF_FORMAT_LOST,
44    perf_event_read_format_PERF_FORMAT_TOTAL_TIME_ENABLED,
45    perf_event_read_format_PERF_FORMAT_TOTAL_TIME_RUNNING, tid_t, uapi,
46};
47
48use crate::security::{self, TargetTaskType};
49use crate::task::Kernel;
50
51static READ_FORMAT_ID_GENERATOR: AtomicU64 = AtomicU64::new(0);
52// Default buffer size to read from socket (for sampling data).
53const DEFAULT_CHUNK_SIZE: usize = 4096;
54// 4096 * 10, page size * 10.
55// If tests flake due to running out of buffer space, or if the profiling duration is
56// significantly increased, this buffer size may need further adjustment (expansion).
57const ESTIMATED_MMAP_BUFFER_SIZE: u64 = 40960;
58// FXT magic bytes (little endian).
59const FXT_MAGIC_BYTES: [u8; 8] = [0x10, 0x00, 0x04, 0x46, 0x78, 0x54, 0x16, 0x00];
60
61mod event;
62pub use event::{TraceEvent, TraceEventQueue, TraceEventQueueList};
63
64pub mod lockless_ring_buffer;
65
66#[repr(C)]
67#[derive(Copy, Clone, IntoBytes, Immutable)]
68struct PerfMetadataHeader {
69    version: u32,
70    compat_version: u32,
71}
72
73#[repr(C)]
74#[derive(Copy, Clone, IntoBytes, Immutable)]
75struct PerfMetadataValue {
76    lock: u32,
77    index: u32,
78    offset: i64,
79    time_enabled: u64,
80    time_running: u64,
81    __bindgen_anon_1: perf_event_mmap_page__bindgen_ty_1,
82    pmc_width: u16,
83    time_shift: u16,
84    time_mult: u32,
85    time_offset: u64,
86    time_zero: u64,
87    size: u32,
88    __reserved_1: u32,
89    time_cycles: u64,
90    time_mask: u64,
91    __reserved: [u8; 928usize],
92    data_head: u64,
93    data_tail: u64,
94    data_offset: u64,
95    data_size: u64,
96    aux_head: u64,
97    aux_tail: u64,
98    aux_offset: u64,
99    aux_size: u64,
100}
101
102// SAFETY: `PerfMetadataValue` can be safely written to shared memory in 8-byte chunks.
103// This is because it is composed of two u32s followed by only u64s.
104// The first u32 is the `lock` field, which is why HAS_INLINE_SEQUENCE is true.
105unsafe impl SeqLockable for PerfMetadataValue {
106    const WRITE_SIZE: WriteSize = WriteSize::Eight;
107    const HAS_INLINE_SEQUENCE: bool = true;
108    const VMO_NAME: &'static [u8] = b"starnix:perf_event";
109}
110
111struct PerfState {
112    // This table maps a group leader's file object id to its unique u64 "format ID".
113    //
114    // When a sample is generated for any event in a group, we use this
115    // "format ID" from the group leader as the value for *both* the
116    // `PERF_SAMPLE_ID` and `PERF_SAMPLE_IDENTIFIER` fields.
117    format_id_lookup_table: LockDepMutex<HashMap<FileObjectId, u64>, PerfFormatIdLookupTableLock>,
118}
119
120impl Default for PerfState {
121    fn default() -> Self {
122        Self { format_id_lookup_table: Default::default() }
123    }
124}
125
126fn get_perf_state(kernel: &Arc<Kernel>) -> Arc<PerfState> {
127    kernel.expando.get_or_init(PerfState::default)
128}
129
130uapi::check_arch_independent_layout! {
131    perf_event_attr {
132        type_, // "type" is a reserved keyword so add a trailing underscore.
133        size,
134        config,
135        __bindgen_anon_1,
136        sample_type,
137        read_format,
138        _bitfield_1,
139        __bindgen_anon_2,
140        bp_type,
141        __bindgen_anon_3,
142        __bindgen_anon_4,
143        branch_sample_type,
144        sample_regs_user,
145        sample_stack_user,
146        clockid,
147        sample_regs_intr,
148        aux_watermark,
149        sample_max_stack,
150        __reserved_2,
151        aux_sample_size,
152        __reserved_3,
153        sig_data,
154        config3,
155    }
156}
157
158#[derive(Clone, Copy, Debug, PartialEq)]
159enum IoctlOp {
160    Enable,
161    Disable,
162}
163
164struct PerfEventFileState {
165    attr: perf_event_attr,
166    rf_value: u64, // "count" for the config we passed in for the event.
167    // The most recent timestamp (ns) where we changed into an enabled state
168    // i.e. the most recent time we got an ENABLE ioctl().
169    most_recent_enabled_time: u64,
170    // Sum of all previous enablement segment durations (ns). If we are
171    // currently in an enabled state, explicitly does NOT include the current
172    // segment.
173    total_time_running: u64,
174    rf_id: u64,
175    sample_id: u64,
176    _rf_lost: u64,
177    disabled: u64,
178    sample_type: u64,
179    // Handle to blob that stores all the perf data that a user may want.
180    // At the moment it only stores some metadata and backtraces (bts).
181    perf_data_vmo: zx::Vmo,
182    // Channel used to send IoctlOps to start/stop sampling.
183    ioctl_sender: future_mpsc::Sender<(IoctlOp, sync_mpsc::Sender<()>)>,
184}
185
186// Have an implementation for PerfEventFileState because VMO
187// doesn't have Default so we can't derive it.
188impl PerfEventFileState {
189    fn new(
190        attr: perf_event_attr,
191        rf_value: u64,
192        disabled: u64,
193        sample_type: u64,
194        perf_data_vmo: zx::Vmo,
195        ioctl_sender: future_mpsc::Sender<(IoctlOp, sync_mpsc::Sender<()>)>,
196    ) -> PerfEventFileState {
197        PerfEventFileState {
198            attr,
199            rf_value,
200            most_recent_enabled_time: 0,
201            total_time_running: 0,
202            rf_id: 0,
203            sample_id: 0,
204            _rf_lost: 0,
205            disabled,
206            sample_type,
207            perf_data_vmo,
208            ioctl_sender,
209        }
210    }
211}
212
213pub struct PerfEventFile {
214    _tid: tid_t,
215    _cpu: i32,
216    perf_event_file: LockDepRwLock<PerfEventFileState, PerfEventLevel>,
217    // The security state for this PerfEventFile.
218    pub security_state: security::PerfEventState,
219    seq_lock: Arc<OnceLock<Result<SeqLock<PerfMetadataHeader, PerfMetadataValue>, Errno>>>,
220}
221
222// PerfEventFile object that implements FileOps.
223// See https://man7.org/linux/man-pages/man2/perf_event_open.2.html for
224// implementation details.
225// This object can be saved as a FileDescriptor.
226impl FileOps for PerfEventFile {
227    // Don't need to implement seek or sync for PerfEventFile.
228    fileops_impl_nonseekable!();
229    fileops_impl_noop_sync!();
230
231    fn close(self: Box<Self>, file: &FileObjectState, current_task: &CurrentTask) {
232        let perf_state = get_perf_state(&current_task.kernel);
233        let mut events = perf_state.format_id_lookup_table.lock();
234        events.remove(&file.id);
235    }
236
237    // See "Reading results" section of https://man7.org/linux/man-pages/man2/perf_event_open.2.html.
238    fn read(
239        &self,
240        _file: &FileObject,
241        current_task: &CurrentTask,
242        _offset: usize,
243        data: &mut dyn OutputBuffer,
244    ) -> Result<usize, Errno> {
245        // Create/calculate and return the ReadFormatData object.
246        // If we create it earlier we might want to change it and it's immutable once created.
247        let read_format_data = {
248            // Once we get the `value` or count from kernel, we can change this to a read()
249            // call instead of write().
250            let mut perf_event_file = self.perf_event_file.write();
251
252            security::check_perf_event_read_access(current_task, &self)?;
253
254            let mut total_time_running_including_curr = perf_event_file.total_time_running;
255
256            // Only update values if enabled (either by perf_event_attr or ioctl ENABLE call).
257            if perf_event_file.disabled == 0 {
258                // Calculate the value or "count" of the config we're interested in.
259                // This value should reflect the value we are counting (defined in the config).
260                // E.g. for PERF_COUNT_SW_CPU_CLOCK it would return the value from the CPU clock.
261                // For now we just return rf_value + 1.
262                track_stub!(
263                    TODO("https://fxbug.dev/402938671"),
264                    "[perf_event_open] implement read_format value"
265                );
266                perf_event_file.rf_value += 1;
267
268                // Update time duration.
269                let curr_time = zx::MonotonicInstant::get().into_nanos() as u64;
270                total_time_running_including_curr +=
271                    curr_time - perf_event_file.most_recent_enabled_time;
272            }
273
274            let mut output = Vec::<u8>::new();
275            let value = perf_event_file.rf_value.to_ne_bytes();
276            output.extend(value);
277
278            let read_format = perf_event_file.attr.read_format;
279
280            if (read_format & perf_event_read_format_PERF_FORMAT_TOTAL_TIME_ENABLED as u64) != 0 {
281                // Total time (ns) event was enabled and running (currently same as TIME_RUNNING).
282                output.extend(total_time_running_including_curr.to_ne_bytes());
283            }
284            if (read_format & perf_event_read_format_PERF_FORMAT_TOTAL_TIME_RUNNING as u64) != 0 {
285                // Total time (ns) event was enabled and running (currently same as TIME_ENABLED).
286                output.extend(total_time_running_including_curr.to_ne_bytes());
287            }
288            if (read_format & perf_event_read_format_PERF_FORMAT_ID as u64) != 0 {
289                // Adds a 64-bit unique value that corresponds to the event group.
290                output.extend(perf_event_file.rf_id.to_ne_bytes());
291            }
292
293            output
294        };
295
296        // The regular read() call allows the case where the bytes-we-want-to-read-in won't
297        // fit in the output buffer. However, for perf_event_open's read(), "If you attempt to read
298        // into a buffer that is not big enough to hold the data, the error ENOSPC results."
299        if data.available() < read_format_data.len() {
300            return error!(ENOSPC);
301        }
302        track_stub!(
303            TODO("https://fxbug.dev/402453955"),
304            "[perf_event_open] implement remaining error handling"
305        );
306
307        data.write(&read_format_data)
308    }
309
310    fn ioctl(
311        &self,
312        _file: &FileObject,
313        current_task: &CurrentTask,
314        op: u32,
315        _arg: SyscallArg,
316    ) -> Result<SyscallResult, Errno> {
317        track_stub!(
318            TODO("https://fxbug.dev/405463320"),
319            "[perf_event_open] implement PERF_IOC_FLAG_GROUP"
320        );
321        security::check_perf_event_write_access(current_task, &self)?;
322        let mut perf_event_file = self.perf_event_file.write();
323        match op {
324            PERF_EVENT_IOC_ENABLE => {
325                if perf_event_file.disabled != 0 {
326                    perf_event_file.disabled = 0; // 0 = false.
327                    perf_event_file.most_recent_enabled_time =
328                        zx::MonotonicInstant::get().into_nanos() as u64;
329                }
330
331                // If we are sampling, invoke the profiler and collect a sample.
332                // Currently this is an example sample collection.
333                track_stub!(
334                    TODO("https://fxbug.dev/398914921"),
335                    "[perf_event_open] implement full sampling features"
336                );
337                if perf_event_file.attr.freq() == 0
338                // SAFETY: sample_period is a u64 field in a union with u64 sample_freq.
339                // This is always sound regardless of the union's tag.
340                    && unsafe { perf_event_file.attr.__bindgen_anon_1.sample_period != 0 }
341                {
342                    ping_receiver(perf_event_file.ioctl_sender.clone(), IoctlOp::Enable);
343                }
344                return Ok(SUCCESS);
345            }
346            PERF_EVENT_IOC_DISABLE => {
347                if perf_event_file.disabled == 0 {
348                    perf_event_file.disabled = 1; // 1 = true.
349
350                    // Update total_time_running now that the segment has ended.
351                    let curr_time = zx::MonotonicInstant::get().into_nanos() as u64;
352                    perf_event_file.total_time_running +=
353                        curr_time - perf_event_file.most_recent_enabled_time;
354                }
355                if perf_event_file.attr.freq() == 0
356                // SAFETY: sample_period is a u64 field in a union with u64 sample_freq.
357                // This is always sound regardless of the union's tag.
358                    && unsafe { perf_event_file.attr.__bindgen_anon_1.sample_period != 0 }
359                {
360                    ping_receiver(perf_event_file.ioctl_sender.clone(), IoctlOp::Disable);
361                }
362                return Ok(SUCCESS);
363            }
364            PERF_EVENT_IOC_RESET => {
365                perf_event_file.rf_value = 0;
366                return Ok(SUCCESS);
367            }
368            PERF_EVENT_IOC_REFRESH
369            | PERF_EVENT_IOC_PERIOD
370            | PERF_EVENT_IOC_SET_OUTPUT
371            | PERF_EVENT_IOC_SET_FILTER
372            | PERF_EVENT_IOC_ID
373            | PERF_EVENT_IOC_SET_BPF
374            | PERF_EVENT_IOC_PAUSE_OUTPUT
375            | PERF_EVENT_IOC_MODIFY_ATTRIBUTES
376            | PERF_EVENT_IOC_QUERY_BPF => {
377                track_stub!(
378                    TODO("https://fxbug.dev/404941053"),
379                    "[perf_event_open] implement remaining ioctl() calls"
380                );
381                return error!(ENOSYS);
382            }
383            _ => error!(ENOTTY),
384        }
385    }
386
387    // TODO(https://fxbug.dev/460245383) match behavior when mmap() is called multiple times.
388    // Gets called when mmap() is called.
389    // Immediately before sampling, this should get called by the user (e.g. the test
390    // or Perfetto). We will then write the metadata to the VMO and return the pointer to it.
391    fn get_memory(
392        &self,
393        _file: &FileObject,
394        current_task: &CurrentTask,
395        length: Option<usize>,
396        _prot: ProtectionFlags,
397    ) -> Result<Arc<MemoryObject>, Errno> {
398        let buffer_size: u64 = length.unwrap_or(0) as u64;
399        if buffer_size == 0 {
400            return error!(EINVAL);
401        }
402
403        self.seq_lock
404            .get_or_init(|| {
405                let perf_event_file = self.perf_event_file.read();
406                let vmo_copy = perf_event_file
407                    .perf_data_vmo
408                    .as_handle_ref()
409                    .duplicate_handle(zx::Rights::SAME_RIGHTS)
410                    .map_err(|status| from_status_like_fdio!(status))?;
411                // SAFETY: See safety requirements on `create_seq_lock`.
412                Ok(unsafe { create_seq_lock(&vmo_copy, buffer_size) })
413            })
414            .as_ref()
415            .map_err(|e| e.clone())?;
416
417        // Write to a MemoryObject and return it (expected return type for get_memory()).
418        security::check_perf_event_read_access(current_task, &self)?;
419        let perf_event_file = self.perf_event_file.read();
420        match perf_event_file
421            .perf_data_vmo
422            .as_handle_ref()
423            .duplicate_handle(zx::Rights::SAME_RIGHTS)
424        {
425            Ok(vmo) => {
426                let vmo: zx::Vmo = vmo.into();
427                let memory = MemoryObject::from(vmo);
428                return Ok(Arc::new(memory));
429            }
430            Err(_) => {
431                track_stub!(
432                    TODO("https://fxbug.dev/416323134"),
433                    "[perf_event_open] handle get_memory() errors"
434                );
435                return error!(EINVAL);
436            }
437        };
438    }
439
440    fn write(
441        &self,
442        _file: &FileObject,
443        _current_task: &CurrentTask,
444        _offset: usize,
445        _data: &mut dyn InputBuffer,
446    ) -> Result<usize, Errno> {
447        track_stub!(
448            TODO("https://fxbug.dev/394960158"),
449            "[perf_event_open] implement perf event functions"
450        );
451        error!(ENOSYS)
452    }
453}
454
455// Given a PerfRecordSample struct, write it via the correct output format
456// (per https://man7.org/linux/man-pages/man2/perf_event_open.2.html) to the VMO.
457// We don't currently support all the sample_types listed in the docs.
458// Input:
459//    PerfRecordSample { pid: 5, tid: 10, nr: 3, ips[nr]: [111, 222, 333] }
460// Human-understandable output:
461//    9 1 40 111 5 10 3 111 222 333
462// Actual output (no spaces or \n in real output, just making it more readable):
463//    0x0000 0x0009                 <-- starts at `offset` bytes
464//    0x0001
465//    0x0040
466//    0x0000 0x0000 0x0000 0x006F   <-- starts at `offset` + 8 bytes
467//    0x0000 0x0000 0x0000 0x0005
468//    0x0000 0x0000 0x0000 0x0010
469//    0x0000 0x0000 0x0000 0x0003
470//    0x0000 0x0000 0x0000 0x006F
471//    0x0000 0x0000 0x0000 0x00DE
472//    0x0000 0x0000 0x0000 0x014D
473//
474//    Returns the length of bytes written. In above case, 8 + 28 = 36.
475//    This information is used to increment the global offset.
476fn write_record_to_vmo(
477    perf_record_sample: PerfRecordSample,
478    perf_data_vmo: &zx::Vmo,
479    sample_type: u64,
480    sample_id: u64,
481    sample_period: u64,
482    offset: u64,
483) -> u64 {
484    // First, build record to determine its size (so that we can fill out `size` in header).
485    let mut sample = Vec::<u8>::new();
486    // sample_id
487    if (sample_type & perf_event_sample_format_PERF_SAMPLE_IDENTIFIER as u64) != 0 {
488        sample.extend(sample_id.to_ne_bytes());
489    }
490    // ip
491    if (sample_type & perf_event_sample_format_PERF_SAMPLE_IP as u64) != 0 {
492        sample.extend(perf_record_sample.ips[0].to_ne_bytes());
493    }
494
495    if (sample_type & perf_event_sample_format_PERF_SAMPLE_TID as u64) != 0 {
496        // pid
497        sample.extend(perf_record_sample.pid.expect("missing pid").to_ne_bytes());
498        // tid
499        sample.extend(perf_record_sample.tid.expect("missing tid").to_ne_bytes());
500    }
501
502    // id
503    if (sample_type & perf_event_sample_format_PERF_SAMPLE_ID as u64) != 0 {
504        sample.extend(sample_id.to_ne_bytes());
505    }
506
507    // sample period
508    if (sample_type & perf_event_sample_format_PERF_SAMPLE_PERIOD as u64) != 0 {
509        sample.extend(sample_period.to_ne_bytes());
510    }
511
512    if (sample_type & perf_event_sample_format_PERF_SAMPLE_CALLCHAIN as u64) != 0 {
513        // nr
514        sample.extend(perf_record_sample.ips.len().to_ne_bytes());
515
516        // ips[nr] - list of ips, u64 per ip.
517        for i in perf_record_sample.ips {
518            sample.extend(i.to_ne_bytes());
519        }
520    }
521    // The remaining data are not defined for now.
522
523    // Now that we know the sample size, we can calculate the record size.
524    // record_size = perf_event_header_size + sample_size.
525    // perf_event_header is defined to be 8 bytes.
526    let record_size: u64 = (std::mem::size_of::<perf_event_header>() + sample.len()) as u64;
527
528    track_stub!(
529        TODO("https://fxbug.dev/432501467"),
530        "[perf_event_open] determines whether the record is KERNEL or USER"
531    );
532    let perf_event_header = perf_event_header {
533        type_: perf_event_type_PERF_RECORD_SAMPLE,
534        misc: PERF_RECORD_MISC_KERNEL as u16,
535        size: record_size as u16,
536    };
537
538    // Total data offset. This is where the record should start getting written.
539    // The first page is reserved for metadata, so we need to add the page size.
540    // Example:
541    //  You're writing the first record (size 100). Start writing at 0 + 4096.
542    //  You're writing the second record. Start writing at 100 + 4096.
543    let data_offset = offset + (zx::system_get_page_size() as u64);
544
545    // Write header to memory.
546    match perf_data_vmo.write(&perf_event_header.as_bytes(), data_offset) {
547        Ok(_) => (),
548        Err(e) => log_warn!("Failed to write perf_event_header: {}", e),
549    }
550
551    // Write sample to memory immediately after the header.
552    match perf_data_vmo
553        .write(&sample, data_offset + (std::mem::size_of::<perf_event_header>() as u64))
554    {
555        Ok(_) => {
556            // Return the total size we wrote (header + sample) so that we can
557            // increment offset counter.
558            return record_size;
559        }
560        Err(e) => {
561            log_warn!("Failed to write PerfRecordSample to VMO due to: {}", e);
562            // Failed to write. Don't increment offset counter.
563            return 0;
564        }
565    }
566}
567
568#[derive(Debug, Clone)]
569struct PerfRecordSample {
570    pid: Option<u32>,
571    tid: Option<u32>,
572    // Instruction pointers (currently this is the address). First one is `ip` param.
573    ips: Vec<u64>,
574}
575
576// Parses a backtrace (bt) to obtain the params for a PerfRecordSample. Example:
577//
578// 1234                     pid
579// 5555                     tid
580// {{{bt:0:0x1111:pc}}}    {{{bt:frame_number:address:type}}}
581// {{{bt:1:0x2222:ra}}}
582// {{{bt:2:0x3333:ra}}}
583//
584// Results in:
585// PerfRecordSample { pid: 1234, tid: 5555, nr: 3, ips: [0x1111, 0x2222, 0x3333] }
586
587fn parse_perf_record_sample_format(backtrace: &str) -> Option<PerfRecordSample> {
588    let mut pid: Option<u32> = None;
589    let mut tid: Option<u32> = None;
590    let mut ips: Vec<u64> = Vec::new();
591    let mut numbers_found = 0;
592    track_stub!(TODO("https://fxbug.dev/437171287"), "[perf_event_open] handle regex nuances");
593    let backtrace_regex =
594        Regex::new(r"^\s*\{\{\{bt:\d+:((0x[0-9a-fA-F]+)):(?:pc|ra)\}\}\}\s*$").unwrap();
595
596    for line in backtrace.lines() {
597        let trimmed_line = line.trim();
598        // Try to parse as a raw number (for PID/TID).
599        if numbers_found < 2 {
600            if let Ok(num) = trimmed_line.parse::<u32>() {
601                if numbers_found == 0 {
602                    pid = Some(num);
603                } else {
604                    tid = Some(num);
605                }
606                numbers_found += 1;
607                continue;
608            }
609        }
610
611        // Try to parse as a backtrace line.
612        if let Some(parsed_bt) = backtrace_regex.captures(trimmed_line) {
613            let address_str = parsed_bt.get(1).unwrap().as_str();
614            if let Ok(ip_addr) = u64::from_str_radix(address_str.trim_start_matches("0x"), 16) {
615                ips.push(ip_addr);
616            }
617        }
618    }
619
620    if pid == None || tid == None || ips.is_empty() {
621        // This data chunk might've been an {{{mmap}}} chunk, and not a {{{bt}}}.
622        log_info!("No ips while getting PerfRecordSample");
623        None
624    } else {
625        Some(PerfRecordSample { pid: pid, tid: tid, ips: ips })
626    }
627}
628
629async fn set_up_profiler(
630    sample_period: zx::MonotonicDuration,
631) -> Result<(profiler::SessionProxy, fidl::AsyncSocket), Errno> {
632    // Configuration for how we want to sample.
633    let sample = profiler::Sample {
634        callgraph: Some(profiler::CallgraphConfig {
635            strategy: Some(profiler::CallgraphStrategy::FramePointer),
636            ..Default::default()
637        }),
638        ..Default::default()
639    };
640
641    let sampling_config = profiler::SamplingConfig {
642        period: Some(sample_period.into_nanos() as u64),
643        timebase: Some(profiler::Counter::PlatformIndependent(profiler::CounterId::Nanoseconds)),
644        sample: Some(sample),
645        ..Default::default()
646    };
647
648    track_stub!(
649        TODO("https://fxbug.dev/398914921"),
650        "[perf_event_open] allow for profiling system-wide not during tests"
651    );
652    let job = fuchsia_runtime::job_default();
653    let koid = job.koid().map_err(|e| errno!(EINVAL, e.to_string()))?;
654    let tasks = vec![
655        // Should return ~1300 samples for 1000 millis.
656        profiler::Task::Job(koid.raw_koid()),
657    ];
658    let targets = profiler::TargetConfig::Tasks(tasks);
659    let config = profiler::Config {
660        configs: Some(vec![sampling_config]),
661        target: Some(targets),
662        ..Default::default()
663    };
664    let (client, server) = fidl::Socket::create_stream();
665    let configure = profiler::SessionConfigureRequest {
666        output: Some(server),
667        config: Some(config),
668        ..Default::default()
669    };
670
671    let proxy = connect_to_protocol::<profiler::SessionMarker>()
672        .context("Error connecting to Profiler protocol");
673    let session_proxy: profiler::SessionProxy = match proxy {
674        Ok(p) => p.clone(),
675        Err(e) => return error!(EINVAL, e),
676    };
677
678    // Must configure before sampling start().
679    let config_request = session_proxy.configure(configure).await;
680    match config_request {
681        Ok(_) => Ok((session_proxy, fidl::AsyncSocket::from_socket(client))),
682        Err(e) => return error!(EINVAL, e),
683    }
684}
685
686// Collects samples and puts backtrace in VMO.
687// - Reads in the buffer from the socket for that duration in chunks.
688// - Parses the buffer backtraces into PERF_RECORD_SAMPLE format.
689// - Writes the PERF_RECORD_SAMPLE into VMO.
690async fn stop_and_collect_samples(
691    session_proxy: profiler::SessionProxy,
692    mut client: fidl::AsyncSocket,
693    seq_lock: &OnceLock<Result<SeqLock<PerfMetadataHeader, PerfMetadataValue>, Errno>>,
694    perf_data_vmo: &zx::Vmo,
695    sample_type: u64,
696    sample_id: u64,
697    sample_period: u64,
698    vmo_write_offset: &mut u64,
699) -> Result<(), Errno> {
700    let stats = session_proxy.stop().await;
701
702    let seq_lock_wrapper = match seq_lock.get() {
703        Some(Ok(l)) => l,
704        // Initialization failed in a previous mmap() call. Propagate the error.
705        Some(Err(e)) => return Err(e.clone()),
706        // Not initialized yet (i.e. mmap() hasn't been called). Skip updating metadata.
707        None => return Ok(()),
708    };
709
710    let samples_collected = match stats {
711        Ok(stats) => stats.samples_collected.unwrap(),
712        Err(e) => return error!(EINVAL, e),
713    };
714
715    track_stub!(
716        TODO("https://fxbug.dev/422502681"),
717        "[perf_event_open] symbolize sample output and delete the below log_info"
718    );
719    log_info!("profiler samples_collected: {:?}", samples_collected);
720
721    // Peek at the first 8 bytes to determine if it's FXT or text.
722    let mut header = [0; 8];
723    let mut bytes_read = 0;
724    while bytes_read < 8 {
725        match client.read(&mut header[bytes_read..]).await {
726            Ok(0) => {
727                // Peer closed the socket. This is the normal end of the stream.
728                log_info!("[perf_event_open] Finished reading fxt record from socket.");
729                break;
730            }
731            Ok(n) => bytes_read += n,
732            Err(e) => {
733                log_warn!("[perf_event_open] Error reading from socket: {:?}", e);
734                break;
735            }
736        }
737    }
738
739    if bytes_read > 0 {
740        if bytes_read == 8 && header == FXT_MAGIC_BYTES {
741            // FXT format.
742            let header_cursor = Cursor::new(header);
743            let reader = header_cursor.chain(client);
744            let (mut stream, _task) = SessionParser::new_async(reader);
745            while let Some(record_result) = stream.next().await {
746                match record_result {
747                    Ok(TraceRecord::Profiler(ProfilerRecord::Backtrace(backtrace))) => {
748                        let ips: Vec<u64> = backtrace.data;
749                        let pid = Some(backtrace.process.0 as u32);
750                        let tid = Some(backtrace.thread.0 as u32);
751                        let perf_record_sample = PerfRecordSample { pid, tid, ips };
752                        let bytes_written = write_record_to_vmo(
753                            perf_record_sample,
754                            perf_data_vmo,
755                            sample_type,
756                            sample_id,
757                            sample_period,
758                            *vmo_write_offset,
759                        );
760                        // Update data_head after writing sample.
761                        if bytes_written > 0 {
762                            *vmo_write_offset += bytes_written;
763                            let mut metadata = seq_lock_wrapper.get();
764                            metadata.data_head = *vmo_write_offset;
765                            seq_lock_wrapper.set_value(metadata);
766                        }
767                    }
768                    Ok(_) => {
769                        // Ignore other records.
770                    }
771                    Err(e) => {
772                        log_warn!("[perf_event_open] Error parsing FXT: {:?}", e);
773                        break;
774                    }
775                }
776            }
777        } else {
778            // Text format.
779            // Read chunks of sampling data from socket in this buffer temporarily. We will parse
780            // the data and write it into the output VMO (the one mmap points to).
781            let mut buffer = vec![0; DEFAULT_CHUNK_SIZE];
782
783            loop {
784                // Attempt to read data. This awaits until data is available, EOF, or error.
785                // Ignore the first 8 bytes as it's the {{{reset}}} marker.
786                let socket_data = client.read(&mut buffer).await;
787
788                match socket_data {
789                    Ok(0) => {
790                        // Peer closed the socket. This is the normal end of the stream.
791                        log_info!("[perf_event_open] Finished reading from socket.");
792                        break;
793                    }
794                    Ok(bytes_read) => {
795                        // Receive data in format {{{...}}}.
796                        let received_data = match std::str::from_utf8(&buffer[..bytes_read]) {
797                            Ok(data) => data,
798                            Err(e) => return error!(EINVAL, e),
799                        };
800                        // Parse data to PerfRecordSample struct.
801                        if let Some(perf_record_sample) =
802                            parse_perf_record_sample_format(received_data)
803                        {
804                            let bytes_written = write_record_to_vmo(
805                                perf_record_sample,
806                                perf_data_vmo,
807                                sample_type,
808                                sample_id,
809                                sample_period,
810                                *vmo_write_offset,
811                            );
812                            // Update data_head after writing sample.
813                            if bytes_written > 0 {
814                                *vmo_write_offset += bytes_written;
815                                let mut metadata = seq_lock_wrapper.get();
816                                metadata.data_head = *vmo_write_offset;
817                                seq_lock_wrapper.set_value(metadata);
818                            }
819                        }
820                    }
821                    Err(e) => {
822                        log_warn!("[perf_event_open] Error reading from socket: {:?}", e);
823                        break;
824                    }
825                }
826            }
827        }
828    }
829
830    let reset_status = session_proxy.reset().await;
831    return match reset_status {
832        Ok(_) => Ok(()),
833        Err(e) => error!(EINVAL, e),
834    };
835}
836
837// Notifies other thread that we should start/stop sampling.
838// Once sampling is complete, that profiler session is no longer needed.
839// At that point, send back notification so that this is no longer blocking
840// (e.g. so that other profiler sessions can start).
841fn ping_receiver(
842    mut ioctl_sender: future_mpsc::Sender<(IoctlOp, sync_mpsc::Sender<()>)>,
843    command: IoctlOp,
844) {
845    log_info!("[perf_event_open] Received sampling command: {:?}", command);
846    let (profiling_complete_sender, profiling_complete_receiver) = sync_mpsc::channel::<()>();
847    match ioctl_sender.try_send((command, profiling_complete_sender)) {
848        Ok(_) => (),
849        Err(e) => {
850            if e.is_full() {
851                log_warn!("[perf_event_open] Failed to send {:?}: Channel full", command);
852            } else if e.is_disconnected() {
853                log_warn!("[perf_event_open] Failed to send {:?}: Receiver disconnected", command);
854            } else {
855                log_warn!("[perf_event_open] Failed to send {:?} due to {:?}", command, e.source());
856            }
857        }
858    };
859    // Block on / wait until profiling is complete before returning.
860    // This notifies that the profiler is free to be used for another session.
861    let _ = profiling_complete_receiver.recv().unwrap();
862}
863
864// Creates a seq lock for the given VMO. Initializes the seq lock with
865// known initial values (unknown values default to 0).
866// Does NOT actually save this as a memory object until mmap() is called.
867//
868// # Safety
869//
870// The caller must ensure that the kernel maintains exclusive write access to this VMO and
871// there are only atomic accesses to this memory (see seq_lock lib.rs for details).
872unsafe fn create_seq_lock(
873    vmo_handle_ref: &zx::NullableHandle,
874    buffer_size: u64,
875) -> SeqLock<PerfMetadataHeader, PerfMetadataValue> {
876    // Currently we hardcode everything just to get something E2E working.
877    let metadata_header = PerfMetadataHeader { version: 1, compat_version: 2 };
878    let page_size = zx::system_get_page_size() as u64;
879    let metadata_value = PerfMetadataValue {
880        lock: 0,
881        index: 3,
882        offset: 19337,
883        time_enabled: 0,
884        time_running: 0,
885        __bindgen_anon_1: perf_event_mmap_page__bindgen_ty_1 { capabilities: 30 },
886        pmc_width: 0,
887        time_shift: 0,
888        time_mult: 0,
889        time_offset: 0,
890        time_zero: 0,
891        size: 0,
892        __reserved_1: 0,
893        time_cycles: 0,
894        time_mask: 0,
895        __reserved: [0; 928usize],
896        // This first page (metadata) has finished writing. Start data_head at 0.
897        data_head: 0,
898        // Start reading from 0; it is the user's responsibility to increment on their end.
899        data_tail: 0,
900        // We know the data will start after 1 page size so we can set this now.
901        data_offset: page_size,
902        data_size: buffer_size - page_size,
903        aux_head: 0,
904        aux_tail: 0,
905        aux_offset: 0,
906        aux_size: 0,
907    };
908    let vmo = zx::Vmo::from(vmo_handle_ref.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap());
909
910    // Create a SeqLock and safely initialize the `header` and `value` for it.
911    // SeqLock is formatted thusly:
912    //   header_struct : any size, params `version` and `compat_version` should not change
913    //   sequence_counter : u32, this is the lock and should increment
914    //   value_struct : any size, each param can change
915    //
916    // SAFETY: See safety requirements on `create_seq_lock`.
917    unsafe {
918        SeqLock::new_from_vmo(metadata_header, metadata_value, vmo)
919            .expect("failed to create seq_lock for perf metadata")
920    }
921}
922
923pub fn sys_perf_event_open(
924    current_task: &CurrentTask,
925    attr: UserRef<perf_event_attr>,
926    // Note that this is pid in Linux docs.
927    tid: tid_t,
928    cpu: i32,
929    group_fd: FdNumber,
930    _flags: u64,
931) -> Result<SyscallResult, Errno> {
932    // So far, the implementation only sets the read_data_format according to the "Reading results"
933    // section of https://man7.org/linux/man-pages/man2/perf_event_open.2.html for a single event.
934    // Other features will be added in the future (see below track_stubs).
935    let perf_event_attrs: perf_event_attr = current_task.read_object(attr)?;
936
937    if tid == -1 && cpu == -1 {
938        return error!(EINVAL);
939    }
940
941    let target_task_type = match tid {
942        -1 => TargetTaskType::AllTasks,
943        0 => TargetTaskType::CurrentTask,
944        _ => {
945            track_stub!(TODO("https://fxbug.dev/409621963"), "[perf_event_open] implement tid > 0");
946            return error!(ENOSYS);
947        }
948    };
949    security::check_perf_event_open_access(
950        current_task,
951        target_task_type,
952        &perf_event_attrs,
953        perf_event_attrs.type_.try_into()?,
954    )?;
955
956    // Channel used to send info between notifier and spawned task thread.
957    // We somewhat arbitrarily picked 8 for now in case we get a bunch of ioctls that are in
958    // quick succession (instead of something lower).
959    let (sender, mut receiver) = future_mpsc::channel(8);
960
961    let mut perf_event_file = PerfEventFileState::new(
962        perf_event_attrs,
963        0,
964        perf_event_attrs.disabled(),
965        perf_event_attrs.sample_type,
966        zx::Vmo::create(ESTIMATED_MMAP_BUFFER_SIZE).unwrap(),
967        sender,
968    );
969
970    let read_format = perf_event_attrs.read_format;
971
972    if (read_format & perf_event_read_format_PERF_FORMAT_TOTAL_TIME_ENABLED as u64) != 0
973        || (read_format & perf_event_read_format_PERF_FORMAT_TOTAL_TIME_RUNNING as u64) != 0
974    {
975        // Only keep track of most_recent_enabled_time if we are currently in ENABLED state,
976        // as otherwise this param shouldn't be used for calculating anything.
977        if perf_event_file.disabled == 0 {
978            perf_event_file.most_recent_enabled_time =
979                zx::MonotonicInstant::get().into_nanos() as u64;
980        }
981        // Initialize this to 0 as we will need to return a time duration later during read().
982        perf_event_file.total_time_running = 0;
983    }
984
985    let event_id = READ_FORMAT_ID_GENERATOR.fetch_add(1, Ordering::Relaxed);
986    perf_event_file.rf_id = event_id;
987
988    if group_fd.raw() == -1 {
989        perf_event_file.sample_id = event_id;
990    } else {
991        let group_file = current_task.files().get(group_fd)?;
992        let group_file_object_id = group_file.id;
993        let perf_state = get_perf_state(&current_task.kernel);
994        let events = perf_state.format_id_lookup_table.lock();
995        if let Some(rf_id) = events.get(&group_file_object_id) {
996            perf_event_file.sample_id = *rf_id;
997        } else {
998            return error!(EINVAL);
999        }
1000    }
1001
1002    if (read_format & perf_event_read_format_PERF_FORMAT_GROUP as u64) != 0 {
1003        track_stub!(
1004            TODO("https://fxbug.dev/402238049"),
1005            "[perf_event_open] implement read_format group"
1006        );
1007        return error!(ENOSYS);
1008    }
1009    if (read_format & perf_event_read_format_PERF_FORMAT_LOST as u64) != 0 {
1010        track_stub!(
1011            TODO("https://fxbug.dev/402260383"),
1012            "[perf_event_open] implement read_format lost"
1013        );
1014    }
1015
1016    // Set up notifier for handling ioctl calls to enable/disable sampling.
1017    let mut vmo_handle_copy =
1018        perf_event_file.perf_data_vmo.as_handle_ref().duplicate_handle(zx::Rights::SAME_RIGHTS);
1019
1020    // SAFETY: sample_period is a u64 field in a union with u64 sample_freq.
1021    // This is always sound regardless of the union's tag.
1022    let sample_period_in_ticks = unsafe { perf_event_file.attr.__bindgen_anon_1.sample_period };
1023    // The sample period from the PERF_COUNT_SW_CPU_CLOCK is
1024    // 1 nanosecond per tick. Convert this duration into zx::duration.
1025    let zx_sample_period = zx::MonotonicDuration::from_nanos(sample_period_in_ticks as i64);
1026
1027    // SeqLock does not get instantiated with metadata values until mmap() is called.
1028    let seq_lock =
1029        Arc::new(OnceLock::<Result<SeqLock<PerfMetadataHeader, PerfMetadataValue>, Errno>>::new());
1030    let cloned_seq_lock = Arc::clone(&seq_lock);
1031    let mut vmo_write_offset = 0;
1032
1033    let closure = async move |_: &CurrentTask| {
1034        let mut profiler_state: Option<(profiler::SessionProxy, fidl::AsyncSocket)> = None;
1035
1036        // This loop will wait for messages from the sender.
1037        while let Some((command, profiling_complete_receiver)) = receiver.next().await {
1038            match command {
1039                IoctlOp::Enable => {
1040                    match set_up_profiler(zx_sample_period).await {
1041                        Ok((session_proxy, client)) => {
1042                            let start_request = profiler::SessionStartRequest {
1043                                buffer_results: Some(true),
1044                                buffer_size_mb: Some(8 as u64),
1045                                ..Default::default()
1046                            };
1047                            if let Err(e) = session_proxy.start(&start_request).await {
1048                                log_warn!("Failed to start profiling: {}", e);
1049                            } else {
1050                                profiler_state = Some((session_proxy, client));
1051                            }
1052                        }
1053                        Err(e) => {
1054                            log_warn!("Failed to profile: {}", e);
1055                        }
1056                    };
1057                    // Send notification anyway to unblock the ioctl caller.
1058                    let _ = profiling_complete_receiver.send(());
1059                }
1060                IoctlOp::Disable => {
1061                    if let Some((session_proxy, client)) = profiler_state.take() {
1062                        let handle = vmo_handle_copy
1063                            .as_mut()
1064                            .expect("Failed to get VMO handle")
1065                            .as_handle_ref()
1066                            .duplicate_handle(zx::Rights::SAME_RIGHTS)
1067                            .unwrap();
1068
1069                        if let Err(e) = stop_and_collect_samples(
1070                            session_proxy,
1071                            client,
1072                            &cloned_seq_lock,
1073                            &zx::Vmo::from(handle),
1074                            perf_event_file.sample_type,
1075                            perf_event_file.sample_id,
1076                            sample_period_in_ticks,
1077                            &mut vmo_write_offset,
1078                        )
1079                        .await
1080                        {
1081                            log_warn!("Failed to collect sample: {:?}", e);
1082                        }
1083                    }
1084                    // Send notification anyway to unblock the ioctl caller.
1085                    let _ = profiling_complete_receiver.send(());
1086                }
1087            }
1088        }
1089        ()
1090    };
1091    let req = SpawnRequestBuilder::new()
1092        .with_debug_name("perf-event-sampler")
1093        .with_async_closure(closure)
1094        .build();
1095    current_task.kernel().kthreads.spawner().spawn_from_request(req);
1096
1097    let file = Box::new(PerfEventFile {
1098        _tid: tid,
1099        _cpu: cpu,
1100        perf_event_file: perf_event_file.into(),
1101        security_state: security::perf_event_alloc(current_task),
1102        seq_lock: seq_lock,
1103    });
1104    // TODO: https://fxbug.dev/404739824 - Confirm whether to handle this as a "private" node.
1105    let file_handle = Anon::new_private_file(current_task, file, OpenFlags::RDWR, "[perf_event]");
1106    let file_object_id = file_handle.id;
1107    let file_descriptor: Result<FdNumber, Errno> =
1108        current_task.add_file(file_handle, FdFlags::empty());
1109
1110    match file_descriptor {
1111        Ok(fd) => {
1112            if group_fd.raw() == -1 {
1113                let perf_state = get_perf_state(&current_task.kernel);
1114                let mut events = perf_state.format_id_lookup_table.lock();
1115                events.insert(file_object_id, event_id);
1116            }
1117            Ok(fd.into())
1118        }
1119        Err(_) => {
1120            track_stub!(
1121                TODO("https://fxbug.dev/402453955"),
1122                "[perf_event_open] implement remaining error handling"
1123            );
1124            error!(EMFILE)
1125        }
1126    }
1127}
1128// Syscalls for arch32 usage
1129#[cfg(target_arch = "aarch64")]
1130mod arch32 {
1131    pub use super::sys_perf_event_open as sys_arch32_perf_event_open;
1132}
1133
1134#[cfg(target_arch = "aarch64")]
1135pub use arch32::*;
1136
1137use crate::mm::memory::MemoryObject;
1138use crate::mm::{MemoryAccessorExt, ProtectionFlags};
1139use crate::task::CurrentTask;
1140use crate::vfs::{
1141    Anon, FdFlags, FdNumber, FileObject, FileObjectId, FileObjectState, FileOps, InputBuffer,
1142    OutputBuffer,
1143};
1144use crate::{fileops_impl_nonseekable, fileops_impl_noop_sync};