Skip to main content

starnix_core/task/
tracing.rs

1// Copyright 2025 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::task::{Kernel, PidTable};
6use starnix_logging::{log_debug, log_error, log_info};
7use starnix_sync::RwLock;
8use starnix_uapi::{pid_t, tid_t};
9use std::collections::HashMap;
10use std::sync::{Arc, Weak};
11use zx::Koid;
12
13#[derive(Debug, Clone)]
14pub struct KoidPair {
15    pub process: Option<Koid>,
16    pub thread: Option<Koid>,
17}
18
19/// The Linux pid/tid to Koid map is a thread safe hashmap.
20pub type PidToKoidMap = Arc<RwLock<HashMap<tid_t, KoidPair>>>;
21
22pub struct TracePerformanceEventManager {
23    // This is the map of pid/tid to koid where the tuple is the process koid, thread koid.
24    // This grows unbounded (in the range of valid tid_t values). Users of this struct should
25    // call |stop| and |clear| once the trace processing is completed to avoid holding memory.
26    map: PidToKoidMap,
27
28    // In order to reduce overhead when processing the trace events, make a local copy of the mappings
29    // that is not thread safe and use that as the first level cache. Since this makes copies of
30    // copies of mappings, |clear| should be called when the mappings are no longer needed.
31    local_map: HashMap<tid_t, KoidPair>,
32
33    // Hold a weak reference to the Kernel so we can make sure the pid to koid map is removed from
34    // the kernel when this object is dropped.
35    // This reference is also used to indicate this manager has been started.
36    weak_kernel: Weak<Kernel>,
37}
38
39impl Drop for TracePerformanceEventManager {
40    fn drop(&mut self) {
41        // Stop is idempotent, and does not error if not started, or already stopped.
42        self.stop();
43    }
44}
45
46impl TracePerformanceEventManager {
47    pub fn new() -> Self {
48        Self { map: PidToKoidMap::default(), local_map: HashMap::new(), weak_kernel: Weak::new() }
49    }
50
51    /// Registers the map with the pid_table so the pid/tid to koid mappings can be recorded when
52    /// new threads are created. Since processing the trace events could be done past a thread's
53    /// lifetime, no mappings are removed when a thread or process exits.
54    /// Additional work may be needed to handle pid reuse (https://fxbug.dev/322874557), currently
55    /// new mapping information overwrites existing mappings.
56    ///
57    /// Calling |start| when this instance has already been started will panic.
58    ///
59    /// NOTE: This will record all thread and process mappings until |stop| is called. The mapping will
60    /// continue to exist in memory until |clear| is called. It is expected that this is a relatively
61    /// short period of time, such as the time during capturing a performance trace.
62    pub fn start(&mut self, kernel: &Arc<Kernel>) {
63        // Provide a reference to the mapping to the kernel so it can be updated as
64        // new threads/processes are created.
65
66        if self.weak_kernel.upgrade().is_some() {
67            log_error!(
68                "TracePerformanceEventManager has already been started. Re-initializing mapping"
69            );
70        }
71
72        self.weak_kernel = Arc::downgrade(kernel);
73        *kernel.pid_to_koid_mapping.write() = Some(self.map.clone());
74
75        let kernel_pids = kernel.pids.read();
76        let existing_pid_map = Self::read_existing_pid_map(&*kernel_pids);
77        self.map.write().extend(existing_pid_map);
78    }
79
80    /// Clears the pid to koid map reference in the kernel passed in to |start|. Stop is a no-op
81    /// if start has not been called, or if stop has already been called.
82    pub fn stop(&mut self) {
83        if let Some(kernel) = self.weak_kernel.upgrade() {
84            log_info!("Stopping trace pid mapping. Notifier set to None.");
85            *kernel.pid_to_koid_mapping.write() = None;
86            self.weak_kernel = Weak::new();
87        }
88    }
89
90    /// Clears the pid-koid map. After starting, call |load_pid_mappings| to
91    /// initialize the table with existing task/process data.
92    pub fn clear(&mut self) {
93        self.map.write().clear();
94        self.local_map.clear();
95    }
96
97    // Look up the pid/tid from a local copy of the pid-koid mapping table, and only
98    // take a lock on the mapping table if there is a missing key from the local map.
99    // Any new keys are added to the local map.
100    fn get_mapping(&mut self, pid: pid_t) -> &KoidPair {
101        if self.local_map.is_empty() {
102            let shared_map = self.map.read().clone();
103            self.local_map.extend(shared_map);
104        }
105
106        if self.local_map.contains_key(&pid) {
107            return self.local_map.get(&pid).expect("pid should always have a KoidPair.");
108        }
109
110        // If there is a miss, check the shared mapping table. This would only happen in
111        // extreme cases where the tracing events are being mapped while new events are being
112        // created by new threads.
113        let shared_map = self.map.read();
114        if let Some(koid_pair) = shared_map.get(&pid) {
115            self.local_map.insert(pid, koid_pair.clone());
116            return self.local_map.get(&pid).expect("pid should always have a KoidPair.");
117        }
118
119        unreachable!("all pids including {pid} should have mappings")
120    }
121
122    /// Maps a "pid" to the koid. This is also referred to as the "Process Id" in Perfetto terms.
123    pub fn map_pid_to_koid(&mut self, pid: pid_t) -> Koid {
124        self.get_mapping(pid).process.expect("all pids should have a process koid.")
125    }
126
127    /// Maps a "tid" to the koid. This is also referred to as the "Thread Id" in Perfetto terms.
128    pub fn map_tid_to_koid(&mut self, tid: tid_t) -> Koid {
129        self.get_mapping(tid).thread.expect("all tids should have a thread koid.")
130    }
131
132    /// Use the kernel pid table to make a mapping from linux pid to koid for existing entries.
133    fn read_existing_pid_map(pid_table: &PidTable) -> HashMap<tid_t, KoidPair> {
134        let mut pid_map = HashMap::new();
135
136        let ids = pid_table.task_ids();
137        for tid in &ids {
138            let task_ref = pid_table.get_task(*tid);
139            let task = task_ref.upgrade().expect("Empty mapping for {tid}.");
140            let live = task.live().expect("tid {tid} is not live.");
141            let pair = KoidPair {
142                process: task.thread_group().get_process_koid().ok(),
143                thread: live.thread.read().as_ref().and_then(|t| t.koid().ok()),
144            };
145            // ignore entries with no process or thread.
146            if pair.process.is_some() || pair.thread.is_some() {
147                pid_map.insert(*tid, pair);
148            }
149        }
150
151        log_debug!("Initialized {} pid mappings. From {} ids", pid_map.len(), ids.len());
152        pid_map
153    }
154}
155
156#[cfg(test)]
157mod tests {
158    use super::*;
159    use crate::testing::{create_task, spawn_kernel_and_run};
160    use futures::channel::oneshot;
161
162    #[fuchsia::test]
163    async fn test_initialize_pid_map() {
164        let (sender, receiver) = oneshot::channel();
165        spawn_kernel_and_run(async move |locked, current_task| {
166            let kernel = current_task.kernel();
167            let pid = current_task.task.tid;
168            let tkoid = current_task.live().thread.read().as_ref().and_then(|t| t.koid().ok());
169            let pkoid = current_task.thread_group().get_process_koid().ok();
170
171            let _another_current = create_task(locked, &kernel, "another-task");
172
173            let pid_map = TracePerformanceEventManager::read_existing_pid_map(&*kernel.pids.read());
174
175            assert!(tkoid.is_some());
176            assert_eq!(pid_map.len(), 2, "Expected 2 entries in pid_map got {pid_map:?}");
177            assert!(pid_map.contains_key(&pid));
178
179            let pair = pid_map.get(&pid).unwrap();
180            assert_eq!(pair.process, pkoid);
181            assert_eq!(pair.thread, tkoid);
182            sender.send(()).unwrap();
183        })
184        .await;
185        receiver.await.unwrap();
186    }
187
188    #[fuchsia::test]
189    fn test_mapping() {
190        let mut manager = TracePerformanceEventManager::new();
191        let mut map = HashMap::new();
192        map.insert(
193            1,
194            KoidPair { process: Some(Koid::from_raw(101)), thread: Some(Koid::from_raw(201)) },
195        );
196        map.insert(2, KoidPair { process: Some(Koid::from_raw(102)), thread: None });
197        manager.map.write().extend(map);
198
199        assert_eq!(manager.map_pid_to_koid(1), Koid::from_raw(101));
200        assert_eq!(manager.map_tid_to_koid(1), Koid::from_raw(201));
201        assert_eq!(manager.map_pid_to_koid(2), Koid::from_raw(102));
202    }
203
204    #[fuchsia::test]
205    #[should_panic]
206    fn test_unmapped_tid() {
207        let mut manager = TracePerformanceEventManager::new();
208
209        manager.map_tid_to_koid(2);
210    }
211
212    #[fuchsia::test]
213    async fn test_lifecycle() {
214        let (sender, receiver) = oneshot::channel();
215        spawn_kernel_and_run(async move |locked, current_task| {
216            let kernel = current_task.kernel();
217            let mut manager = TracePerformanceEventManager::new();
218
219            manager.start(&kernel);
220
221            let pid_map = manager.map.read().clone();
222            assert_eq!(pid_map.len(), 1, "Expected 1 entry in pid_map got {pid_map:?}");
223
224            // Associate a thread with a new task.
225            let another_current = create_task(locked, &kernel, "another-task");
226            let test_thread = another_current
227                .thread_group()
228                .process
229                .create_thread(b"my-new-test-thread")
230                .expect("test thread");
231
232            {
233                let another_current_live = another_current.live();
234                let mut thread = another_current_live.thread.write();
235                *thread = Some(Arc::new(test_thread));
236                drop(thread);
237            }
238
239            let pid_map = manager.map.read().clone();
240            let pid_dump = format!("{pid_map:?}");
241            assert_eq!(pid_map.len(), 1, "Expected 1 entry in pid_map got {pid_dump}");
242
243            // This is called by the task when it is all ready to run.
244            another_current.record_pid_koid_mapping();
245
246            // Now expect 2 mappings.
247            let pid_map = manager.map.read().clone();
248            let pid_dump = format!("{pid_map:?}");
249            assert_eq!(pid_map.len(), 2, "Expected 2 entries in pid_map got {pid_dump}");
250
251            // Read the mappings, if it is not present, it will panic.
252            let _ = manager.map_pid_to_koid(another_current.task.get_pid());
253            let _ = manager.map_pid_to_koid(another_current.task.get_tid());
254
255            manager.stop();
256
257            manager.clear();
258            assert!(manager.map.read().is_empty());
259            sender.send(()).unwrap();
260        })
261        .await;
262        receiver.await.unwrap();
263    }
264}