1use crate::task::{Kernel, PidTable};
6use fuchsia_rcu::RcuReadScope;
7use starnix_logging::{log_debug, log_error, log_warn};
8use starnix_sync::{
9 LockDepMutex, LockDepRwLock, PidToKoidMapInnerLock, TracePerformanceEventManagerStateLock,
10};
11use starnix_uapi::{pid_t, tid_t};
12use std::collections::{HashMap, HashSet};
13use std::sync::atomic::{AtomicUsize, Ordering};
14use std::sync::{Arc, Weak};
15use zx::Koid;
16
17pub trait TraceEventSink: Send + Sync {
19 fn emit_association(
22 &self,
23 pid: pid_t,
24 tid: tid_t,
25 identity: ZirconIdentity,
26 source: &'static str,
27 );
28
29 fn on_dump_complete(&self);
31}
32
33#[derive(Default)]
35pub struct ProductionTraceEventSink;
36
37impl TraceEventSink for ProductionTraceEventSink {
38 fn emit_association(
39 &self,
40 pid: pid_t,
41 tid: tid_t,
42 identity: ZirconIdentity,
43 source: &'static str,
44 ) {
45 fuchsia_trace::instant!(
46 "starnix:meta",
47 source,
48 fuchsia_trace::Scope::Process,
49 "tid" => tid,
50 "pid" => pid,
51 "koid" => identity.thread.raw_koid().to_string().as_str(),
52 "process_koid" => identity.process.raw_koid().to_string().as_str()
53 );
54 }
55
56 fn on_dump_complete(&self) {
57 if fuchsia_trace::category_enabled(c"starnix:meta") {
58 if let Err(status) = fuchsia_trace::flush_buffer() {
59 log_warn!("fuchsia_trace::flush_buffer failed with status: {status}");
60 }
61 }
62 }
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub struct ZirconIdentity {
78 pub process: Koid,
80 pub thread: Koid,
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86pub enum LinuxIdentity {
87 Thread { pid: pid_t, tid: tid_t },
89 Process { pid: pid_t },
91}
92
93#[derive(Debug, Default)]
101struct PidKoidMap {
102 tid_to_koid: HashMap<tid_t, ZirconIdentity>,
104 pid_to_koid: HashMap<pid_t, Koid>,
106 koid_to_pid: HashMap<Koid, LinuxIdentity>,
108 unmapped_processes: HashSet<Koid>,
116}
117
118impl PidKoidMap {
119 fn insert(&mut self, pid: pid_t, tid: tid_t, identity: ZirconIdentity) {
120 self.tid_to_koid.insert(tid, identity);
121 self.pid_to_koid.entry(pid).or_insert(identity.process);
122 self.koid_to_pid.insert(identity.thread, LinuxIdentity::Thread { pid, tid });
123 self.koid_to_pid.entry(identity.process).or_insert(LinuxIdentity::Process { pid });
126 self.unmapped_processes.remove(&identity.process);
129 }
130
131 fn get_zircon_identity(&self, tid: tid_t) -> Option<&ZirconIdentity> {
132 self.tid_to_koid.get(&tid)
133 }
134
135 fn get_process_koid(&self, pid: pid_t) -> Option<Koid> {
136 self.pid_to_koid.get(&pid).copied()
137 }
138
139 fn get_linux_identity(&self, koid: Koid) -> Option<LinuxIdentity> {
140 self.koid_to_pid.get(&koid).copied()
141 }
142
143 fn extend_from(&mut self, other: PidKoidMap) {
147 for (tid, identity) in other.tid_to_koid {
148 self.unmapped_processes.remove(&identity.process);
149 self.tid_to_koid.entry(tid).or_insert(identity);
150 }
151 for (pid, koid) in other.pid_to_koid {
152 self.pid_to_koid.entry(pid).or_insert(koid);
153 }
154 for (koid, identity) in other.koid_to_pid {
155 self.koid_to_pid.entry(koid).or_insert(identity);
156 }
157 self.unmapped_processes.extend(other.unmapped_processes);
158 }
159}
160
161pub struct TracePerformanceEventManager {
174 weak_kernel: Weak<Kernel>,
178
179 active_sessions: AtomicUsize,
182
183 state_lock: LockDepMutex<(), TracePerformanceEventManagerStateLock>,
185
186 map: LockDepRwLock<PidKoidMap, PidToKoidMapInnerLock>,
190
191 sink: Arc<dyn TraceEventSink>,
193}
194
195impl TracePerformanceEventManager {
196 pub fn new(weak_kernel: Weak<Kernel>) -> Self {
198 Self::new_with_sink(weak_kernel, Arc::new(ProductionTraceEventSink))
199 }
200
201 pub fn new_with_sink(weak_kernel: Weak<Kernel>, sink: Arc<dyn TraceEventSink>) -> Self {
203 Self {
204 weak_kernel,
205 active_sessions: AtomicUsize::new(0),
206 state_lock: LockDepMutex::new(()),
207 map: LockDepRwLock::new(PidKoidMap::default()),
208 sink,
209 }
210 }
211
212 pub fn init_trace_observer(&self, kernel: &Arc<Kernel>) {
215 let weak_kernel = Arc::downgrade(kernel);
216 kernel.kthreads.spawn_future(
217 move || async move {
218 let observer = fuchsia_trace_observer::TraceObserver::new();
219 let mut session: Option<PidKoidSession> = None;
220 if fuchsia_trace::is_enabled() {
221 if let Some(kernel) = weak_kernel.upgrade() {
222 session = Some(kernel.trace_event_manager.open());
223 log_debug!(
224 "trace-meta-observer: session active on startup: {}",
225 session.is_some()
226 );
227 }
228 }
229 while let Ok(state) = observer.on_state_changed().await {
230 match state {
231 fuchsia_trace::TraceState::Started => {
232 if let Some(kernel) = weak_kernel.upgrade() {
233 session = Some(kernel.trace_event_manager.open());
234 log_debug!(
235 "trace-meta-observer: session active on trace start: {}",
236 session.is_some()
237 );
238 }
239 }
240 fuchsia_trace::TraceState::Stopping
241 | fuchsia_trace::TraceState::Stopped => {
242 if session.take().is_some() {
243 log_debug!("trace-meta-observer: session dropped on trace stop");
244 }
245 }
246 }
247 }
248 log_warn!("trace-meta-observer loop terminated unexpectedly");
249 },
250 "trace-meta-observer",
251 );
252 }
253
254 pub fn is_recording(&self) -> bool {
256 self.active_sessions.load(Ordering::Acquire) > 0
257 }
258
259 pub fn open(self: &Arc<Self>) -> PidKoidSession {
264 self.start_session_internal();
265 PidKoidSession { manager: self.clone() }
266 }
267
268 pub fn record(&self, pid: pid_t, tid: tid_t, identity: ZirconIdentity) {
272 if self.active_sessions.load(Ordering::Acquire) == 0 {
273 return;
274 }
275 let mut map = self.map.write();
276 if self.active_sessions.load(Ordering::Acquire) == 0 {
277 return;
278 }
279 map.insert(pid, tid, identity);
280 self.sink.emit_association(pid, tid, identity, "new_task");
281 }
282
283 pub(crate) fn get_zircon_identity(&self, tid: tid_t) -> Option<ZirconIdentity> {
285 self.map.read().get_zircon_identity(tid).copied()
286 }
287
288 pub(crate) fn get_process_koid(&self, pid: pid_t) -> Option<Koid> {
290 self.map.read().get_process_koid(pid)
291 }
292
293 fn resolve_koids(&self, pkoid: Koid, tkoid: Koid) -> Option<LinuxIdentity> {
296 {
297 let map = self.map.read();
298 if map.unmapped_processes.contains(&pkoid) {
299 return None;
300 }
301 if let Some(identity) = map.get_linux_identity(tkoid) {
302 return Some(identity);
303 }
304 if map.koid_to_pid.contains_key(&pkoid) {
310 return None;
311 }
312 }
313
314 let mut map = self.map.write();
318 if let Some(identity) = map.get_linux_identity(tkoid) {
319 return Some(identity);
320 }
321 if !map.koid_to_pid.contains_key(&pkoid) {
322 map.unmapped_processes.insert(pkoid);
323 }
324 None
325 }
326
327 fn start_session_internal(&self) {
330 let _guard = self.state_lock.lock();
331 let current = self.active_sessions.fetch_add(1, Ordering::AcqRel);
332 if current == 0 {
333 if let Some(kernel) = self.weak_kernel.upgrade() {
334 let snapshot = Self::snapshot_existing_tasks(&kernel.pids, &*self.sink);
335 self.map.write().extend_from(snapshot);
336 self.sink.on_dump_complete();
337 } else {
338 log_warn!("Kernel is shutting down, unable to snapshot running tasks");
339 }
340 }
341 }
342
343 fn stop_session_internal(&self) {
345 let _guard = self.state_lock.lock();
346 if self.active_sessions.load(Ordering::Acquire) == 0 {
347 log_error!("session stopped without an active session");
348 return;
349 }
350 let current = self.active_sessions.fetch_sub(1, Ordering::AcqRel);
351 if current == 1 {
352 *self.map.write() = PidKoidMap::default();
353 }
354 }
355
356 fn snapshot_existing_tasks(pid_table: &PidTable, sink: &dyn TraceEventSink) -> PidKoidMap {
366 let mut pid_map = PidKoidMap::default();
367
368 let scope = RcuReadScope::new();
369 let mut count = 0;
370 for pid in pid_table.running_task_ids(&scope) {
371 count += 1;
372 let Ok(task) = pid.get_task() else {
375 continue;
376 };
377 if let Some(identity) = task.get_zircon_identity() {
378 let pid_val = task.get_pid();
379 pid_map.insert(pid_val, pid.id, identity);
380 sink.emit_association(pid_val, pid.id, identity, "dump");
381 }
382 }
383
384 log_debug!("Initialized {} pid mappings. From {} ids", pid_map.tid_to_koid.len(), count);
385 pid_map
386 }
387}
388
389#[must_use = "Recording stops when this guard is dropped"]
397pub struct PidKoidSession {
398 manager: Arc<TracePerformanceEventManager>,
399}
400
401impl PidKoidSession {
402 pub fn resolve_tid_to_koid(&self, tid: tid_t) -> Option<Koid> {
404 if tid == 0 {
405 return Some(Koid::from_raw(0));
406 }
407 self.manager.get_zircon_identity(tid).map(|id| id.thread)
408 }
409
410 pub fn resolve_pid_to_koid(&self, pid: pid_t) -> Option<Koid> {
412 if pid == 0 {
413 return Some(Koid::from_raw(0));
414 }
415 self.manager.get_process_koid(pid)
416 }
417
418 pub fn resolve_koids(&self, pkoid: Koid, tkoid: Koid) -> Option<LinuxIdentity> {
422 self.manager.resolve_koids(pkoid, tkoid)
423 }
424}
425
426impl Drop for PidKoidSession {
427 fn drop(&mut self) {
428 self.manager.stop_session_internal();
429 }
430}
431
432#[cfg(test)]
433mod tests {
434 use super::*;
435 use crate::task::ZirconThread;
436 use crate::testing::{create_task, spawn_kernel_and_run};
437 use futures::channel::oneshot;
438 use std::sync::Mutex;
439
440 impl PidKoidMap {
441 fn is_empty(&self) -> bool {
442 self.tid_to_koid.is_empty() && self.pid_to_koid.is_empty()
443 }
444 }
445
446 struct TestTraceEventSink {
447 events: Arc<Mutex<Vec<(pid_t, tid_t, ZirconIdentity, String)>>>,
448 dump_completed: Arc<Mutex<bool>>,
449 }
450
451 impl TestTraceEventSink {
452 fn new() -> (Self, Arc<Mutex<Vec<(pid_t, tid_t, ZirconIdentity, String)>>>, Arc<Mutex<bool>>)
453 {
454 let events = Arc::new(Mutex::new(Vec::new()));
455 let dump_completed = Arc::new(Mutex::new(false));
456 (
457 Self { events: Arc::clone(&events), dump_completed: Arc::clone(&dump_completed) },
458 events,
459 dump_completed,
460 )
461 }
462 }
463
464 impl TraceEventSink for TestTraceEventSink {
465 fn emit_association(
466 &self,
467 pid: pid_t,
468 tid: tid_t,
469 identity: ZirconIdentity,
470 source: &'static str,
471 ) {
472 self.events.lock().unwrap().push((pid, tid, identity, source.to_string()));
473 }
474
475 fn on_dump_complete(&self) {
476 *self.dump_completed.lock().unwrap() = true;
477 }
478 }
479
480 impl TracePerformanceEventManager {
481 fn new_for_testing() -> Self {
482 Self::new(Weak::new())
483 }
484 }
485
486 fn identity(process: u64, thread: u64) -> ZirconIdentity {
487 ZirconIdentity { process: Koid::from_raw(process), thread: Koid::from_raw(thread) }
488 }
489
490 #[fuchsia::test]
491 async fn test_snapshot_existing_tasks() {
492 let (sender, receiver) = oneshot::channel();
493 spawn_kernel_and_run(async move |current_task| {
494 let kernel = current_task.kernel();
495 let pid = current_task.task.get_pid();
496 let tid = current_task.task.get_tid();
497 let identity = current_task.task.get_zircon_identity().unwrap();
498
499 let _another_current = create_task(&kernel, "another-task");
501
502 let session = kernel.trace_event_manager.open();
503
504 assert_eq!(session.resolve_tid_to_koid(tid), Some(identity.thread));
505 assert_eq!(session.resolve_pid_to_koid(pid), Some(identity.process));
506 assert_eq!(kernel.trace_event_manager.map.read().tid_to_koid.len(), 1);
507 assert_eq!(kernel.trace_event_manager.map.read().pid_to_koid.len(), 1);
508
509 sender.send(()).unwrap();
510 })
511 .await;
512 receiver.await.unwrap();
513 }
514
515 #[fuchsia::test]
516 fn test_forward_resolution() {
517 let manager = Arc::new(TracePerformanceEventManager::new_for_testing());
518 let session = manager.open();
519
520 manager.record(10, 10, identity(1001, 2001));
521 manager.record(10, 11, identity(1001, 2002));
522
523 manager.record(20, 201, identity(2001, 3001));
525
526 assert_eq!(session.resolve_tid_to_koid(0), Some(Koid::from_raw(0)));
528 assert_eq!(session.resolve_pid_to_koid(0), Some(Koid::from_raw(0)));
529
530 assert_eq!(session.resolve_tid_to_koid(10), Some(Koid::from_raw(2001)));
532 assert_eq!(session.resolve_tid_to_koid(11), Some(Koid::from_raw(2002)));
533 assert_eq!(session.resolve_pid_to_koid(10), Some(Koid::from_raw(1001)));
534
535 assert_eq!(session.resolve_pid_to_koid(20), Some(Koid::from_raw(2001)));
537 assert_eq!(session.resolve_tid_to_koid(201), Some(Koid::from_raw(3001)));
538 assert_eq!(session.resolve_tid_to_koid(20), None);
539
540 assert_eq!(session.resolve_tid_to_koid(999), None);
542 assert_eq!(session.resolve_pid_to_koid(999), None);
543 }
544
545 #[fuchsia::test]
546 fn test_reverse_table_maintained() {
547 let manager = Arc::new(TracePerformanceEventManager::new_for_testing());
548 let _session = manager.open();
549 manager.record(10, 100, identity(1000, 2000));
550
551 assert_eq!(
552 manager.map.read().get_linux_identity(Koid::from_raw(2000)),
553 Some(LinuxIdentity::Thread { pid: 10, tid: 100 })
554 );
555 assert_eq!(
556 manager.map.read().get_linux_identity(Koid::from_raw(1000)),
557 Some(LinuxIdentity::Process { pid: 10 })
558 );
559 assert_eq!(manager.get_zircon_identity(100), Some(identity(1000, 2000)));
560 }
561
562 #[fuchsia::test]
563 fn test_seed_does_not_overwrite_fresh_record() {
564 let manager = Arc::new(TracePerformanceEventManager::new_for_testing());
565 let _session = manager.open();
566
567 manager.record(1, 1, identity(101, 201));
570 let mut stale = PidKoidMap::default();
571 stale.insert(1, 1, identity(101, 999));
572 manager.map.write().extend_from(stale);
573
574 assert_eq!(manager.get_zircon_identity(1).map(|id| id.thread), Some(Koid::from_raw(201)));
575 }
576
577 #[fuchsia::test]
578 fn test_reverse_resolution() {
579 let manager = Arc::new(TracePerformanceEventManager::new_for_testing());
580 let session = manager.open();
581 manager.record(10, 100, identity(1000, 2000));
582
583 assert_eq!(
585 session.resolve_koids(Koid::from_raw(1000), Koid::from_raw(2000)),
586 Some(LinuxIdentity::Thread { pid: 10, tid: 100 })
587 );
588
589 assert_eq!(session.resolve_koids(Koid::from_raw(1000), Koid::from_raw(9999)), None);
591 assert!(!manager.map.read().unmapped_processes.contains(&Koid::from_raw(1000)));
592
593 assert_eq!(session.resolve_koids(Koid::from_raw(8), Koid::from_raw(7)), None);
595 assert!(manager.map.read().unmapped_processes.contains(&Koid::from_raw(8)));
596
597 manager.record(30, 300, identity(8, 9));
600 assert!(!manager.map.read().unmapped_processes.contains(&Koid::from_raw(8)));
601 assert_eq!(
602 session.resolve_koids(Koid::from_raw(8), Koid::from_raw(9)),
603 Some(LinuxIdentity::Thread { pid: 30, tid: 300 })
604 );
605
606 drop(session);
609 assert_eq!(manager.resolve_koids(Koid::from_raw(1000), Koid::from_raw(2000)), None);
610 }
611
612 #[fuchsia::test]
613 async fn test_overlapping_sessions() {
614 spawn_kernel_and_run(async move |current_task| {
615 let kernel = current_task.kernel();
616 let manager = &kernel.trace_event_manager;
617 assert!(!manager.is_recording());
618
619 let session_a = manager.open();
621 let session_b = manager.open();
622 assert!(manager.is_recording());
623
624 manager.record(99, 999, identity(9900, 9901));
625 assert_eq!(session_b.resolve_tid_to_koid(999), Some(Koid::from_raw(9901)));
626
627 drop(session_a);
629 assert!(manager.is_recording());
630 assert_eq!(session_b.resolve_tid_to_koid(999), Some(Koid::from_raw(9901)));
631
632 drop(session_b);
635 assert!(!manager.is_recording());
636 assert!(manager.map.read().is_empty());
637 })
638 .await;
639 }
640
641 #[fuchsia::test]
642 async fn test_lifecycle() {
643 let (sender, receiver) = oneshot::channel();
644 spawn_kernel_and_run(async move |current_task| {
645 let kernel = current_task.kernel();
646 let session = kernel.trace_event_manager.open();
647
648 assert_eq!(kernel.trace_event_manager.map.read().tid_to_koid.len(), 1);
649
650 let another_current = create_task(&kernel, "another-task");
652 let test_thread = another_current
653 .thread_group()
654 .process
655 .create_thread(b"my-new-test-thread")
656 .expect("test thread");
657
658 {
659 another_current
660 .running_state()
661 .thread
662 .set(ZirconThread::new(Arc::new(test_thread)))
663 .expect("test thread set");
664 }
665
666 assert_eq!(kernel.trace_event_manager.map.read().tid_to_koid.len(), 1);
667
668 another_current.record_pid_koid_mapping();
670
671 assert_eq!(kernel.trace_event_manager.map.read().tid_to_koid.len(), 2);
673 assert!(
674 kernel
675 .trace_event_manager
676 .get_zircon_identity(another_current.task.get_tid())
677 .is_some()
678 );
679
680 drop(session);
681
682 another_current.record_pid_koid_mapping();
684 assert!(kernel.trace_event_manager.map.read().is_empty());
685 sender.send(()).unwrap();
686 })
687 .await;
688 receiver.await.unwrap();
689 }
690
691 #[fuchsia::test]
692 fn test_resolve_koids_avoids_write_lock_for_known_process() {
693 let manager = Arc::new(TracePerformanceEventManager::new_for_testing());
694 let _session = manager.open();
695 manager.record(10, 100, identity(1000, 2000));
696
697 let read_guard = manager.map.read();
702 let (sender, receiver) = std::sync::mpsc::channel();
703 let manager_clone = manager.clone();
704
705 std::thread::spawn(move || {
706 let res = manager_clone.resolve_koids(Koid::from_raw(1000), Koid::from_raw(9999));
707 let _ = sender.send(res);
708 });
709
710 let result = receiver.recv_timeout(std::time::Duration::from_millis(500));
711 drop(read_guard);
712 assert_eq!(result, Ok(None), "resolve_koids blocked on write lock for known process");
713 }
714
715 #[fuchsia::test]
716 async fn test_trace_sink_emits_dump_and_new_task() {
717 let (sender, receiver) = oneshot::channel();
718 spawn_kernel_and_run(async move |current_task| {
719 let kernel = current_task.kernel();
720 let pid = current_task.task.get_pid();
721 let tid = current_task.task.get_tid();
722 let identity = current_task.task.get_zircon_identity().unwrap();
723
724 let (sink, events, dump_completed) = TestTraceEventSink::new();
725 let manager = Arc::new(TracePerformanceEventManager::new_with_sink(
726 Arc::downgrade(&kernel),
727 Arc::new(sink),
728 ));
729
730 let session = manager.open();
731
732 assert!(*dump_completed.lock().unwrap());
734 {
735 let dumped = events.lock().unwrap().clone();
736 assert_eq!(dumped.len(), 1);
737 assert_eq!(dumped[0], (pid, tid, identity, "dump".to_string()));
738 }
739
740 let another_current = create_task(&kernel, "another-task");
742 let test_thread = another_current
743 .thread_group()
744 .process
745 .create_thread(b"another-thread")
746 .expect("test thread");
747 let thread_koid = test_thread.koid().unwrap();
748 let process_koid = another_current.thread_group().process.koid().unwrap();
749 let another_identity = ZirconIdentity { process: process_koid, thread: thread_koid };
750
751 another_current
752 .running_state()
753 .thread
754 .set(ZirconThread::new(Arc::new(test_thread)))
755 .expect("test thread set");
756
757 let another_pid = another_current.task.get_pid();
758 let another_tid = another_current.task.get_tid();
759
760 manager.record(another_pid, another_tid, another_identity);
761
762 {
763 let all_events = events.lock().unwrap().clone();
764 assert_eq!(all_events.len(), 2);
765 assert_eq!(
766 all_events[1],
767 (another_pid, another_tid, another_identity, "new_task".to_string())
768 );
769 }
770
771 drop(session);
772 sender.send(()).unwrap();
773 })
774 .await;
775 receiver.await.unwrap();
776 }
777}