1use 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);
52const ESTIMATED_MMAP_BUFFER_SIZE: u64 = 16 * 1024 * 1024;
62const LOST_RECORD_SIZE: u64 = 24;
65const 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
117unsafe 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 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_, 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, most_recent_enabled_time: u64,
185 total_time_running: u64,
189 rf_id: u64,
190 sample_id: u64,
191 _rf_lost: u64,
192 disabled: u64,
193 sample_type: u64,
194 perf_data_vmo: zx::Vmo,
197 ioctl_sender: future_mpsc::Sender<(IoctlOp, sync_mpsc::Sender<()>)>,
199}
200
201impl 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 pub security_state: security::PerfEventState,
234 seq_lock: Arc<OnceLock<Result<SeqLock<PerfMetadataHeader, PerfMetadataValue>, Errno>>>,
235}
236
237impl FileOps for PerfEventFile {
242 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(¤t_task.kernel);
248 let mut events = perf_state.format_id_lookup_table.lock();
249 events.remove(&file.id);
250 }
251
252 fn read(
254 &self,
255 _file: &FileObject,
256 current_task: &CurrentTask,
257 _offset: usize,
258 data: &mut dyn OutputBuffer,
259 ) -> Result<usize, Errno> {
260 let read_format_data = {
263 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 if perf_event_file.disabled == 0 {
273 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 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 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 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 output.extend(perf_event_file.rf_id.to_ne_bytes());
306 }
307
308 output
309 };
310
311 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; perf_event_file.most_recent_enabled_time =
343 zx::MonotonicInstant::get().into_nanos() as u64;
344 }
345
346 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 && 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; 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 && 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 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 Ok(unsafe { create_seq_lock(&vmo_copy, buffer_size) })
433 })
434 .as_ref()
435 .map_err(|e| e.clone())?;
436
437 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
475fn 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 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 let mut sample = Vec::<u8>::new();
529 if (sample_type & perf_event_sample_format_PERF_SAMPLE_IDENTIFIER as u64) != 0 {
531 sample.extend(sample_id.to_ne_bytes());
532 }
533 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 sample.extend(perf_record_sample.pid.expect("missing pid").to_ne_bytes());
541 sample.extend(perf_record_sample.tid.expect("missing tid").to_ne_bytes());
543 }
544
545 if (sample_type & perf_event_sample_format_PERF_SAMPLE_ID as u64) != 0 {
547 sample.extend(sample_id.to_ne_bytes());
548 }
549
550 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 sample.extend(perf_record_sample.ips.len().to_ne_bytes());
558
559 for i in perf_record_sample.ips {
561 sample.extend(i.to_ne_bytes());
562 }
563 }
564 let record_len = std::mem::size_of::<perf_event_header>() + sample.len();
570 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 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 let free_space =
604 (metadata.data_tail.wrapping_sub(head).wrapping_sub(1)) & (ring_buffer_size - 1);
605
606 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 *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 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
659fn 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 ips: Vec<u64>,
689}
690
691async fn set_up_profiler(
692 sample_period: zx::MonotonicDuration,
693) -> Result<(profiler::SessionProxy, fidl::AsyncSocket), Errno> {
694 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 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 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
748async 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 Some(Err(e)) => return Err(e.clone()),
766 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 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
858fn 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 let _ = profiling_complete_receiver.recv().unwrap();
883}
884
885unsafe fn create_seq_lock(
894 vmo_handle_ref: &zx::NullableHandle,
895 buffer_size: u64,
896) -> SeqLock<PerfMetadataHeader, PerfMetadataValue> {
897 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 data_head: 0,
919 data_tail: 0,
921 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 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 tid: tid_t,
949 cpu: i32,
950 group_fd: FdNumber,
951 _flags: u64,
952) -> Result<SyscallResult, Errno> {
953 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 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 if perf_event_file.disabled == 0 {
999 perf_event_file.most_recent_enabled_time =
1000 zx::MonotonicInstant::get().into_nanos() as u64;
1001 }
1002 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(¤t_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 let mut vmo_handle_copy =
1039 perf_event_file.perf_data_vmo.as_handle_ref().duplicate_handle(zx::Rights::SAME_RIGHTS);
1040
1041 let sample_period_in_ticks = unsafe { perf_event_file.attr.__bindgen_anon_1.sample_period };
1044 let zx_sample_period = zx::MonotonicDuration::from_nanos(sample_period_in_ticks as i64);
1047
1048 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 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 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 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 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(¤t_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#[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 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 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}