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