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