1use crate::task::{Kernel, PidTable};
6use fuchsia_rcu::RcuReadScope;
7use starnix_logging::{log_debug, log_error, log_warn};
8use starnix_sync::LockDepRwLock;
9use starnix_uapi::{pid_t, tid_t};
10use std::collections::{HashMap, HashSet};
11use std::sync::atomic::{AtomicUsize, Ordering};
12use std::sync::{Arc, Mutex, Weak};
13use zx::Koid;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub struct ZirconIdentity {
28 pub process: Koid,
30 pub thread: Koid,
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum LinuxIdentity {
37 Thread { pid: pid_t, tid: tid_t },
39 Process { pid: pid_t },
41}
42
43#[derive(Debug, Default)]
51struct PidKoidMap {
52 tid_to_koid: HashMap<tid_t, ZirconIdentity>,
54 pid_to_koid: HashMap<pid_t, Koid>,
56 koid_to_pid: HashMap<Koid, LinuxIdentity>,
58 unmapped_processes: HashSet<Koid>,
66}
67
68impl PidKoidMap {
69 fn insert(&mut self, pid: pid_t, tid: tid_t, identity: ZirconIdentity) {
70 self.tid_to_koid.insert(tid, identity);
71 self.pid_to_koid.entry(pid).or_insert(identity.process);
72 self.koid_to_pid.insert(identity.thread, LinuxIdentity::Thread { pid, tid });
73 self.koid_to_pid.entry(identity.process).or_insert(LinuxIdentity::Process { pid });
76 self.unmapped_processes.remove(&identity.process);
79 }
80
81 fn get_zircon_identity(&self, tid: tid_t) -> Option<&ZirconIdentity> {
82 self.tid_to_koid.get(&tid)
83 }
84
85 fn get_process_koid(&self, pid: pid_t) -> Option<Koid> {
86 self.pid_to_koid.get(&pid).copied()
87 }
88
89 fn get_linux_identity(&self, koid: Koid) -> Option<LinuxIdentity> {
90 self.koid_to_pid.get(&koid).copied()
91 }
92
93 fn extend_from(&mut self, other: PidKoidMap) {
97 for (tid, identity) in other.tid_to_koid {
98 self.unmapped_processes.remove(&identity.process);
99 self.tid_to_koid.entry(tid).or_insert(identity);
100 }
101 for (pid, koid) in other.pid_to_koid {
102 self.pid_to_koid.entry(pid).or_insert(koid);
103 }
104 for (koid, identity) in other.koid_to_pid {
105 self.koid_to_pid.entry(koid).or_insert(identity);
106 }
107 self.unmapped_processes.extend(other.unmapped_processes);
108 }
109}
110
111pub struct TracePerformanceEventManager {
124 weak_kernel: Weak<Kernel>,
128
129 active_sessions: AtomicUsize,
132
133 state_lock: Mutex<()>,
135
136 map: LockDepRwLock<PidKoidMap, starnix_sync::PidToKoidMapInnerLock>,
140}
141
142impl TracePerformanceEventManager {
143 pub fn new(weak_kernel: Weak<Kernel>) -> Self {
145 Self {
146 weak_kernel,
147 active_sessions: AtomicUsize::new(0),
148 state_lock: Mutex::new(()),
149 map: LockDepRwLock::new(PidKoidMap::default()),
150 }
151 }
152
153 pub fn is_recording(&self) -> bool {
155 self.active_sessions.load(Ordering::Acquire) > 0
156 }
157
158 pub fn open(self: &Arc<Self>) -> PidKoidSession {
163 self.start_session_internal();
164 PidKoidSession { manager: self.clone() }
165 }
166
167 pub fn record(&self, pid: pid_t, tid: tid_t, identity: ZirconIdentity) {
171 if self.active_sessions.load(Ordering::Acquire) == 0 {
172 return;
173 }
174 let mut map = self.map.write();
175 if self.active_sessions.load(Ordering::Acquire) == 0 {
176 return;
177 }
178 map.insert(pid, tid, identity);
179 }
180
181 pub(crate) fn get_zircon_identity(&self, tid: tid_t) -> Option<ZirconIdentity> {
183 self.map.read().get_zircon_identity(tid).copied()
184 }
185
186 pub(crate) fn get_process_koid(&self, pid: pid_t) -> Option<Koid> {
188 self.map.read().get_process_koid(pid)
189 }
190
191 fn resolve_koids(&self, pkoid: Koid, tkoid: Koid) -> Option<LinuxIdentity> {
194 {
195 let map = self.map.read();
196 if map.unmapped_processes.contains(&pkoid) {
197 return None;
198 }
199 if let Some(identity) = map.get_linux_identity(tkoid) {
200 return Some(identity);
201 }
202 if map.koid_to_pid.contains_key(&pkoid) {
208 return None;
209 }
210 }
211
212 let mut map = self.map.write();
216 if let Some(identity) = map.get_linux_identity(tkoid) {
217 return Some(identity);
218 }
219 if !map.koid_to_pid.contains_key(&pkoid) {
220 map.unmapped_processes.insert(pkoid);
221 }
222 None
223 }
224
225 fn start_session_internal(&self) {
228 let _guard = self.state_lock.lock().unwrap();
229 let current = self.active_sessions.fetch_add(1, Ordering::AcqRel);
230 if current == 0 {
231 if let Some(kernel) = self.weak_kernel.upgrade() {
232 let snapshot = Self::snapshot_existing_tasks(&kernel.pids);
233 self.map.write().extend_from(snapshot);
234 } else {
235 log_warn!("Kernel is shutting down, unable to snapshot running tasks");
236 }
237 }
238 }
239
240 fn stop_session_internal(&self) {
242 let _guard = self.state_lock.lock().unwrap();
243 if self.active_sessions.load(Ordering::Acquire) == 0 {
244 log_error!("session stopped without an active session");
245 return;
246 }
247 let current = self.active_sessions.fetch_sub(1, Ordering::AcqRel);
248 if current == 1 {
249 *self.map.write() = PidKoidMap::default();
250 }
251 }
252
253 fn snapshot_existing_tasks(pid_table: &PidTable) -> PidKoidMap {
263 let mut pid_map = PidKoidMap::default();
264
265 let scope = RcuReadScope::new();
266 let mut count = 0;
267 for pid in pid_table.running_task_ids(&scope) {
268 count += 1;
269 let Ok(task) = pid.get_task() else {
272 continue;
273 };
274 if let Some(identity) = task.get_zircon_identity() {
275 pid_map.insert(task.get_pid(), pid.id, identity);
276 }
277 }
278
279 log_debug!("Initialized {} pid mappings. From {} ids", pid_map.tid_to_koid.len(), count);
280 pid_map
281 }
282}
283
284#[must_use = "Recording stops when this guard is dropped"]
292pub struct PidKoidSession {
293 manager: Arc<TracePerformanceEventManager>,
294}
295
296impl PidKoidSession {
297 pub fn resolve_tid_to_koid(&self, tid: tid_t) -> Option<Koid> {
299 if tid == 0 {
300 return Some(Koid::from_raw(0));
301 }
302 self.manager.get_zircon_identity(tid).map(|id| id.thread)
303 }
304
305 pub fn resolve_pid_to_koid(&self, pid: pid_t) -> Option<Koid> {
307 if pid == 0 {
308 return Some(Koid::from_raw(0));
309 }
310 self.manager.get_process_koid(pid)
311 }
312
313 pub fn resolve_koids(&self, pkoid: Koid, tkoid: Koid) -> Option<LinuxIdentity> {
317 self.manager.resolve_koids(pkoid, tkoid)
318 }
319}
320
321impl Drop for PidKoidSession {
322 fn drop(&mut self) {
323 self.manager.stop_session_internal();
324 }
325}
326
327#[cfg(test)]
328mod tests {
329 use super::*;
330 use crate::task::ZirconThread;
331 use crate::testing::{create_task, spawn_kernel_and_run};
332 use futures::channel::oneshot;
333
334 impl PidKoidMap {
335 fn is_empty(&self) -> bool {
336 self.tid_to_koid.is_empty() && self.pid_to_koid.is_empty()
337 }
338 }
339
340 impl TracePerformanceEventManager {
341 fn new_for_testing() -> Self {
342 Self::new(Weak::new())
343 }
344 }
345
346 fn identity(process: u64, thread: u64) -> ZirconIdentity {
347 ZirconIdentity { process: Koid::from_raw(process), thread: Koid::from_raw(thread) }
348 }
349
350 #[fuchsia::test]
351 async fn test_snapshot_existing_tasks() {
352 let (sender, receiver) = oneshot::channel();
353 spawn_kernel_and_run(async move |current_task| {
354 let kernel = current_task.kernel();
355 let pid = current_task.task.get_pid();
356 let tid = current_task.task.get_tid();
357 let identity = current_task.task.get_zircon_identity().unwrap();
358
359 let _another_current = create_task(&kernel, "another-task");
361
362 let session = kernel.trace_event_manager.open();
363
364 assert_eq!(session.resolve_tid_to_koid(tid), Some(identity.thread));
365 assert_eq!(session.resolve_pid_to_koid(pid), Some(identity.process));
366 assert_eq!(kernel.trace_event_manager.map.read().tid_to_koid.len(), 1);
367 assert_eq!(kernel.trace_event_manager.map.read().pid_to_koid.len(), 1);
368
369 sender.send(()).unwrap();
370 })
371 .await;
372 receiver.await.unwrap();
373 }
374
375 #[fuchsia::test]
376 fn test_forward_resolution() {
377 let manager = Arc::new(TracePerformanceEventManager::new_for_testing());
378 let session = manager.open();
379
380 manager.record(10, 10, identity(1001, 2001));
381 manager.record(10, 11, identity(1001, 2002));
382
383 manager.record(20, 201, identity(2001, 3001));
385
386 assert_eq!(session.resolve_tid_to_koid(0), Some(Koid::from_raw(0)));
388 assert_eq!(session.resolve_pid_to_koid(0), Some(Koid::from_raw(0)));
389
390 assert_eq!(session.resolve_tid_to_koid(10), Some(Koid::from_raw(2001)));
392 assert_eq!(session.resolve_tid_to_koid(11), Some(Koid::from_raw(2002)));
393 assert_eq!(session.resolve_pid_to_koid(10), Some(Koid::from_raw(1001)));
394
395 assert_eq!(session.resolve_pid_to_koid(20), Some(Koid::from_raw(2001)));
397 assert_eq!(session.resolve_tid_to_koid(201), Some(Koid::from_raw(3001)));
398 assert_eq!(session.resolve_tid_to_koid(20), None);
399
400 assert_eq!(session.resolve_tid_to_koid(999), None);
402 assert_eq!(session.resolve_pid_to_koid(999), None);
403 }
404
405 #[fuchsia::test]
406 fn test_reverse_table_maintained() {
407 let manager = Arc::new(TracePerformanceEventManager::new_for_testing());
408 let _session = manager.open();
409 manager.record(10, 100, identity(1000, 2000));
410
411 assert_eq!(
412 manager.map.read().get_linux_identity(Koid::from_raw(2000)),
413 Some(LinuxIdentity::Thread { pid: 10, tid: 100 })
414 );
415 assert_eq!(
416 manager.map.read().get_linux_identity(Koid::from_raw(1000)),
417 Some(LinuxIdentity::Process { pid: 10 })
418 );
419 assert_eq!(manager.get_zircon_identity(100), Some(identity(1000, 2000)));
420 }
421
422 #[fuchsia::test]
423 fn test_seed_does_not_overwrite_fresh_record() {
424 let manager = Arc::new(TracePerformanceEventManager::new_for_testing());
425 let _session = manager.open();
426
427 manager.record(1, 1, identity(101, 201));
430 let mut stale = PidKoidMap::default();
431 stale.insert(1, 1, identity(101, 999));
432 manager.map.write().extend_from(stale);
433
434 assert_eq!(manager.get_zircon_identity(1).map(|id| id.thread), Some(Koid::from_raw(201)));
435 }
436
437 #[fuchsia::test]
438 fn test_reverse_resolution() {
439 let manager = Arc::new(TracePerformanceEventManager::new_for_testing());
440 let session = manager.open();
441 manager.record(10, 100, identity(1000, 2000));
442
443 assert_eq!(
445 session.resolve_koids(Koid::from_raw(1000), Koid::from_raw(2000)),
446 Some(LinuxIdentity::Thread { pid: 10, tid: 100 })
447 );
448
449 assert_eq!(session.resolve_koids(Koid::from_raw(1000), Koid::from_raw(9999)), None);
451 assert!(!manager.map.read().unmapped_processes.contains(&Koid::from_raw(1000)));
452
453 assert_eq!(session.resolve_koids(Koid::from_raw(8), Koid::from_raw(7)), None);
455 assert!(manager.map.read().unmapped_processes.contains(&Koid::from_raw(8)));
456
457 manager.record(30, 300, identity(8, 9));
460 assert!(!manager.map.read().unmapped_processes.contains(&Koid::from_raw(8)));
461 assert_eq!(
462 session.resolve_koids(Koid::from_raw(8), Koid::from_raw(9)),
463 Some(LinuxIdentity::Thread { pid: 30, tid: 300 })
464 );
465
466 drop(session);
469 assert_eq!(manager.resolve_koids(Koid::from_raw(1000), Koid::from_raw(2000)), None);
470 }
471
472 #[fuchsia::test]
473 async fn test_overlapping_sessions() {
474 spawn_kernel_and_run(async move |current_task| {
475 let kernel = current_task.kernel();
476 let manager = &kernel.trace_event_manager;
477 assert!(!manager.is_recording());
478
479 let session_a = manager.open();
481 let session_b = manager.open();
482 assert!(manager.is_recording());
483
484 manager.record(99, 999, identity(9900, 9901));
485 assert_eq!(session_b.resolve_tid_to_koid(999), Some(Koid::from_raw(9901)));
486
487 drop(session_a);
489 assert!(manager.is_recording());
490 assert_eq!(session_b.resolve_tid_to_koid(999), Some(Koid::from_raw(9901)));
491
492 drop(session_b);
495 assert!(!manager.is_recording());
496 assert!(manager.map.read().is_empty());
497 })
498 .await;
499 }
500
501 #[fuchsia::test]
502 async fn test_lifecycle() {
503 let (sender, receiver) = oneshot::channel();
504 spawn_kernel_and_run(async move |current_task| {
505 let kernel = current_task.kernel();
506 let session = kernel.trace_event_manager.open();
507
508 assert_eq!(kernel.trace_event_manager.map.read().tid_to_koid.len(), 1);
509
510 let another_current = create_task(&kernel, "another-task");
512 let test_thread = another_current
513 .thread_group()
514 .process
515 .create_thread(b"my-new-test-thread")
516 .expect("test thread");
517
518 {
519 another_current
520 .running_state()
521 .thread
522 .set(ZirconThread::new(Arc::new(test_thread)))
523 .expect("test thread set");
524 }
525
526 assert_eq!(kernel.trace_event_manager.map.read().tid_to_koid.len(), 1);
527
528 another_current.record_pid_koid_mapping();
530
531 assert_eq!(kernel.trace_event_manager.map.read().tid_to_koid.len(), 2);
533 assert!(
534 kernel
535 .trace_event_manager
536 .get_zircon_identity(another_current.task.get_tid())
537 .is_some()
538 );
539
540 drop(session);
541
542 another_current.record_pid_koid_mapping();
544 assert!(kernel.trace_event_manager.map.read().is_empty());
545 sender.send(()).unwrap();
546 })
547 .await;
548 receiver.await.unwrap();
549 }
550
551 #[fuchsia::test]
552 fn test_resolve_koids_avoids_write_lock_for_known_process() {
553 let manager = Arc::new(TracePerformanceEventManager::new_for_testing());
554 let _session = manager.open();
555 manager.record(10, 100, identity(1000, 2000));
556
557 let read_guard = manager.map.read();
562 let (sender, receiver) = std::sync::mpsc::channel();
563 let manager_clone = manager.clone();
564
565 std::thread::spawn(move || {
566 let res = manager_clone.resolve_koids(Koid::from_raw(1000), Koid::from_raw(9999));
567 let _ = sender.send(res);
568 });
569
570 let result = receiver.recv_timeout(std::time::Duration::from_millis(500));
571 drop(read_guard);
572 assert_eq!(result, Ok(None), "resolve_koids blocked on write lock for known process");
573 }
574}