1use super::tracing_directory::TraceMarkerFile;
6use starnix_core::perf::{TraceEventQueue, TraceEventQueueList};
7use starnix_core::task::CurrentTask;
8use starnix_core::vfs::pseudo::dynamic_file::ConstFile;
9use starnix_core::vfs::pseudo::simple_directory::SimpleDirectory;
10use starnix_core::vfs::pseudo::simple_file::{BytesFile, BytesFileOps, SimpleFileNode};
11use starnix_core::vfs::{
12 CacheMode, FileObject, FileOps, FileSystem, FileSystemHandle, FileSystemOps, FileSystemOptions,
13 FsNodeOps, FsStr, OutputBuffer,
14};
15use starnix_core::{fileops_impl_nonseekable, fileops_impl_noop_sync};
16use starnix_logging::{CATEGORY_TRACE_META, track_stub};
17use starnix_sync::{LockDepMutex, TraceFsDataLock};
18use starnix_types::PAGE_SIZE;
19use starnix_types::vfs::default_statfs;
20use starnix_uapi::errors::Errno;
21use starnix_uapi::file_mode::mode;
22use starnix_uapi::{TRACEFS_MAGIC, errno, error, statfs};
23use std::borrow::Cow;
24use std::sync::{Arc, LazyLock};
25
26pub fn trace_fs(
27 current_task: &CurrentTask,
28 options: FileSystemOptions,
29) -> Result<FileSystemHandle, Errno> {
30 struct TraceFsHandle(FileSystemHandle);
31
32 let handle = current_task.kernel().expando.get_or_init(|| {
33 TraceFsHandle(
34 TraceFs::new_fs(current_task, options).expect("tracefs constructed with valid options"),
35 )
36 });
37 Ok(handle.0.clone())
38}
39
40pub struct TraceFs;
41
42impl FileSystemOps for TraceFs {
43 fn statfs(&self, _fs: &FileSystem, _current_task: &CurrentTask) -> Result<statfs, Errno> {
44 Ok(default_statfs(TRACEFS_MAGIC))
45 }
46
47 fn name(&self) -> &'static FsStr {
48 "tracefs".into()
49 }
50
51 fn manages_timestamps(&self) -> bool {
52 true
53 }
54}
55
56impl TraceFs {
57 pub fn new_fs(
58 current_task: &CurrentTask,
59 options: FileSystemOptions,
60 ) -> Result<FileSystemHandle, Errno> {
61 let kernel = current_task.kernel();
62 let event_queue_collection = TraceEventQueueList::from(kernel);
63 let fs = FileSystem::new(kernel, CacheMode::Uncached, TraceFs, options)?;
64 let dir = SimpleDirectory::new();
65 dir.edit(&fs, |dir| {
66 dir.entry("trace", TraceFile::new_node(), mode!(IFREG, 0o755));
67 dir.subdir("per_cpu", 0o755, |dir| {
68 static CPU_DIR_NAMES: LazyLock<Vec<String>> = LazyLock::new(|| {
70 (0..zx::system_get_num_cpus()).map(|cpu| format!("cpu{}", cpu)).collect()
71 });
72 for (cpu, dir_name) in CPU_DIR_NAMES.iter().map(|s| s.as_str()).enumerate() {
73 dir.subdir(dir_name, 0o755, |dir| {
74 let ops: Box<dyn FsNodeOps> = Box::new(TraceRawFile::new_node(
75 event_queue_collection.queues[cpu].clone(),
76 ));
77 dir.entry("trace_pipe_raw", ops, mode!(IFREG, 0o444));
78 dir.entry("trace", TraceFile::new_node(), mode!(IFREG, 0o444));
79 });
80 }
81 });
82 dir.subdir("events", 0o755, |dir| {
83 dir.entry("header_page", EventsHeaderPage::new_node(), mode!(IFREG, 0o444));
84 dir.subdir("ftrace", 0o755, |dir| {
85 dir.subdir("print", 0o755, |dir| {
86 dir.entry("format", FtracePrintFormatFile::new_node(), mode!(IFREG, 0o444));
87 });
88 if kernel.features.selinux_test_suite {
89 dir.subdir("function", 0o755, |dir| {
92 dir.entry(
93 "id",
94 BytesFile::new_node(b"1\n".to_vec()),
95 mode!(IFREG, 0o444),
96 );
97 });
98 }
99 });
100 dir.entry("enable", TraceBytesFile::new_node(), mode!(IFREG, 0o755));
101 });
102 dir.subdir("options", 0o755, |dir| {
103 dir.entry("overwrite", TraceBytesFile::new_node(), mode!(IFREG, 0o444));
104 });
105 dir.entry(
106 "tracing_on",
107 TracingOnFile::new_node(event_queue_collection.clone()),
108 mode!(IFREG, 0o666),
109 );
110 dir.entry("current_tracer", ConstFile::new_node("nop".into()), mode!(IFREG, 0o755));
111 dir.entry(
112 "trace_marker",
113 TraceMarkerFile::new_node(event_queue_collection.clone()),
114 mode!(IFREG, 0o222),
115 );
116 dir.entry("printk_formats", TraceBytesFile::new_node(), mode!(IFREG, 0o755));
117 dir.entry(
118 "trace_clock",
119 ConstFile::new_node(
120 "[local] global counter uptime perf mono mono_raw boot tai x86-tsc ".into(),
121 ),
122 mode!(IFREG, 0o755),
123 );
124 dir.entry(
125 "buffer_size_kb",
126 BufferSizeKbFile::new_node(event_queue_collection.clone()),
127 mode!(IFREG, 0o666),
128 );
129 });
130
131 let root_ino = fs.allocate_ino();
132 fs.create_root(root_ino, dir);
133 Ok(fs)
134 }
135}
136
137#[derive(Clone)]
138struct BufferSizeKbFile {
139 event_queue_collection: Arc<TraceEventQueueList>,
140}
141
142impl BufferSizeKbFile {
143 pub fn new_node(event_queue_collection: Arc<TraceEventQueueList>) -> impl FsNodeOps {
144 BytesFile::new_node(Self { event_queue_collection })
145 }
146}
147
148impl BytesFileOps for BufferSizeKbFile {
149 fn write(&self, _current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
150 let text = std::str::from_utf8(&data).map_err(|_| errno!(EINVAL))?;
151 let trimmed = text.trim();
152 let size_kb: usize = trimmed.parse().map_err(|_| errno!(EINVAL))?;
153 self.event_queue_collection.set_buffer_size_kb(size_kb)?;
154 Ok(())
155 }
156
157 fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
158 let size_kb = self.event_queue_collection.buffer_size_kb();
159 let s = format!("{}\n", size_kb);
160 Ok(Cow::Owned(s.into_bytes()))
161 }
162}
163
164#[derive(Default)]
165struct TraceBytesFile {
166 data: LockDepMutex<Vec<u8>, TraceFsDataLock>,
167}
168
169impl TraceBytesFile {
170 #[track_caller]
171 fn new_node() -> impl FsNodeOps {
172 SimpleFileNode::new(move |_| Ok(BytesFile::new(Self::default())))
173 }
174}
175
176impl BytesFileOps for TraceBytesFile {
177 fn write(&self, _current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
178 *self.data.lock() = data;
179 Ok(())
180 }
181 fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
182 let data = self.data.lock().clone();
183 Ok(data.into())
184 }
185}
186
187#[derive(Clone)]
188struct TraceRawFile {
189 queue: Arc<TraceEventQueue>,
190}
191
192impl TraceRawFile {
193 pub fn new_node(queue: Arc<TraceEventQueue>) -> impl FsNodeOps {
194 SimpleFileNode::new(move |_| Ok(Self { queue: queue.clone() }))
195 }
196}
197
198impl FileOps for TraceRawFile {
199 fileops_impl_nonseekable!();
200 fileops_impl_noop_sync!();
201
202 fn read(
203 &self,
204 _file: &FileObject,
205 current_task: &CurrentTask,
206 _offset: usize,
207 data: &mut dyn OutputBuffer,
208 ) -> Result<usize, Errno> {
209 if data.available() as u64 != *PAGE_SIZE {
211 return error!(EINVAL);
212 }
213 let _guard = fuchsia_trace::async_enter!(
214 self.queue.async_id_read,
215 CATEGORY_TRACE_META,
216 self.queue.read_track_name(),
217 "tid" => current_task.tid.id
218 );
219 self.queue.read(data)
220 }
221
222 fn write(
223 &self,
224 _file: &FileObject,
225 _current_task: &CurrentTask,
226 _offset: usize,
227 _data: &mut dyn starnix_core::vfs::InputBuffer,
228 ) -> Result<usize, Errno> {
229 error!(ENOSYS)
230 }
231}
232
233#[derive(Clone)]
234struct TracingOnFile {
235 event_queue_collection: Arc<TraceEventQueueList>,
236}
237
238impl TracingOnFile {
239 pub fn new_node(event_queue_collection: Arc<TraceEventQueueList>) -> impl FsNodeOps {
240 BytesFile::new_node(Self { event_queue_collection })
241 }
242}
243
244impl BytesFileOps for TracingOnFile {
245 fn write(&self, _current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
246 let state_str = std::str::from_utf8(&data).map_err(|_| errno!(EINVAL))?;
247 let clean_state_str = state_str.split('\n').next().unwrap_or("");
248 match clean_state_str {
249 "0" => self.event_queue_collection.disable(),
250 "1" => self.event_queue_collection.enable(),
251 _ => error!(EINVAL),
252 }
253 }
254
255 fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
256 if self.event_queue_collection.is_enabled() {
257 Ok(Cow::Borrowed(&b"1\n"[..]))
258 } else {
259 Ok(Cow::Borrowed(&b"0\n"[..]))
260 }
261 }
262}
263
264#[derive(Clone)]
265struct EventsHeaderPage;
266impl EventsHeaderPage {
267 fn new_node() -> impl FsNodeOps {
268 ConstFile::new_node(
269 format!(
270 "\tfield: u64 timestamp;\toffset:0;\tsize:8;\tsigned:0\n\
271 \tfield: local_t commit;\toffset:8;\tsize:8;\tsigned:1\n\
272 \tfield: int overwrite;\toffset:8;\tsize:1;\tsigned:1\n\
273 \tfield: char data;\toffset:16;\tsize:{};\tsigned:0\n",
274 *PAGE_SIZE - 16
275 )
276 .into_bytes(),
277 )
278 }
279}
280
281#[derive(Clone)]
282struct FtracePrintFormatFile;
283impl FtracePrintFormatFile {
284 fn new_node() -> impl FsNodeOps {
285 ConstFile::new_node(
286 "name: print\n\
287 ID: 5\n\
288 format:\n\
289 \tfield:unsigned short common_type;\toffset:0;\tsize:2;\tsigned:0;\n\
290 \tfield:unsigned char common_flags;\toffset:2;\tsize:1;\tsigned:0;\n\
291 \tfield:unsigned char common_preempt_count;\toffset:3;\tsize:1;\tsigned:0;\n\
292 \tfield:int common_pid;\toffset:4;\tsize:4;\tsigned:1;\n\
293 \n\
294 \tfield:unsigned long ip;\toffset:8;\tsize:8;\tsigned:0;\n\
295 \tfield:char buf[];\toffset:16;\tsize:0;\tsigned:0;\n\
296 \n\
297 print fmt: \"%ps: %s\", (void *)REC->ip, REC- >buf\n"
298 .into(),
299 )
300 }
301}
302
303#[derive(Clone)]
304struct TraceFile;
305impl TraceFile {
306 fn new_node() -> impl FsNodeOps {
307 track_stub!(TODO("https://fxbug.dev/357665908"), "/sys/kernel/tracing/trace");
308 ConstFile::new_node("# tracer: nop\n".into())
309 }
310}
311
312#[cfg(test)]
313mod tests {
314 use super::*;
315 use starnix_core::testing::spawn_kernel_and_run;
316 use starnix_core::vfs::buffers::{InputBuffer, VecInputBuffer, VecOutputBuffer};
317 use starnix_core::vfs::{FsNodeInfo, NamespaceNode, OpenAccessCheck};
318 use starnix_uapi::auth::FsCred;
319 use starnix_uapi::file_mode::mode;
320 use starnix_uapi::open_flags::OpenFlags;
321
322 #[::fuchsia::test]
323 async fn test_events_header_page_size() {
324 spawn_kernel_and_run(async move |current_task| {
325 let fs = current_task.fs().root().entry.node.fs();
326 let info = FsNodeInfo::new(mode!(IFREG, 0o444), FsCred::root());
327 let node = fs.create_node_and_allocate_node_id(EventsHeaderPage::new_node(), info);
328 let ns_node = NamespaceNode::new_anonymous_unrooted(current_task, node);
329 let file = ns_node
330 .open(current_task, OpenAccessCheck::skip(OpenFlags::RDONLY))
331 .expect("open header_page node");
332
333 let mut buffer = VecOutputBuffer::new(*PAGE_SIZE as usize);
334 let bytes_read = file.read(current_task, &mut buffer).expect("read header_page");
335 let content = std::str::from_utf8(&buffer.data()[..bytes_read]).expect("valid utf8");
336
337 let expected_content = format!(
338 "\tfield: u64 timestamp;\toffset:0;\tsize:8;\tsigned:0\n\
339 \tfield: local_t commit;\toffset:8;\tsize:8;\tsigned:1\n\
340 \tfield: int overwrite;\toffset:8;\tsize:1;\tsigned:1\n\
341 \tfield: char data;\toffset:16;\tsize:{};\tsigned:0\n",
342 *PAGE_SIZE - 16
343 );
344 assert_eq!(
345 content, expected_content,
346 "Expected header_page to match exactly:\n{}\nGot:\n{}",
347 expected_content, content
348 );
349 })
350 .await;
351 }
352
353 #[::fuchsia::test]
354 async fn test_trace_marker_write_small_and_large_and_disabled() {
355 spawn_kernel_and_run(async move |current_task| {
356 let kernel = current_task.kernel();
357 let event_queue_collection = TraceEventQueueList::from(kernel);
358 let fs = TraceFs::new_fs(current_task, FileSystemOptions::default())
359 .expect("create tracefs");
360 assert!(fs.manages_timestamps());
361 let info = FsNodeInfo::new(mode!(IFREG, 0o222), FsCred::root());
362 let node = fs.create_node_and_allocate_node_id(
363 TraceMarkerFile::new_node(event_queue_collection.clone()),
364 info,
365 );
366 let ns_node = NamespaceNode::new_anonymous_unrooted(current_task, node);
367 let file = ns_node
368 .open(current_task, OpenAccessCheck::skip(OpenFlags::WRONLY))
369 .expect("open trace_marker node");
370 let mtime_before = file.node().info().time_modify;
371
372 event_queue_collection.disable().expect("disable queue");
374 let mut disabled_buf = VecInputBuffer::new(b"B|1234|disabled_slice\n");
375 let written = file.write(current_task, &mut disabled_buf).expect("write disabled");
376 assert_eq!(written, b"B|1234|disabled_slice\n".len());
377 assert_eq!(disabled_buf.available(), 0);
378 assert_eq!(file.node().info().time_modify, mtime_before);
379
380 event_queue_collection.enable().expect("enable queue");
384 let num_cpus = event_queue_collection.queues.len();
385 for _ in 0..num_cpus {
386 let mut small_buf = VecInputBuffer::new(b"B|1234|activityStart\n");
387 let written = file.write(current_task, &mut small_buf).expect("write small");
388 assert_eq!(written, b"B|1234|activityStart\n".len());
389 assert_eq!(small_buf.available(), 0);
390 }
391 let large_payload = vec![b'x'; 768];
392 for _ in 0..(6 * num_cpus) {
393 let mut large_buf = VecInputBuffer::new(&large_payload);
394 let written = file.write(current_task, &mut large_buf).expect("write large");
395 assert_eq!(written, large_payload.len());
396 assert_eq!(large_buf.available(), 0);
397 }
398 assert_eq!(file.node().info().time_modify, mtime_before);
399
400 let mut read_buf = VecOutputBuffer::new(*PAGE_SIZE as usize);
401 let bytes_read = event_queue_collection.queues[0]
402 .read(&mut read_buf)
403 .expect("read full page from queue 0");
404 assert_eq!(bytes_read, *PAGE_SIZE as usize);
405 let page_data = read_buf.data();
406 assert!(
407 page_data
408 .windows(b"B|1234|activityStart\n".len())
409 .any(|w| w == b"B|1234|activityStart\n")
410 );
411 assert!(page_data.windows(large_payload.len()).any(|w| w == large_payload.as_slice()));
412 event_queue_collection.disable().expect("disable queue after test");
413 })
414 .await;
415 }
416
417 #[::fuchsia::test]
418 async fn test_buffer_size_kb_read_write() {
419 spawn_kernel_and_run(async move |current_task| {
420 let kernel = current_task.kernel();
421 let event_queue_collection = TraceEventQueueList::from(kernel);
422 let fs = current_task.fs().root().entry.node.fs();
423 let info = FsNodeInfo::new(mode!(IFREG, 0o666), FsCred::root());
424 let node = fs.create_node_and_allocate_node_id(
425 BufferSizeKbFile::new_node(event_queue_collection.clone()),
426 info,
427 );
428 let ns_node = NamespaceNode::new_anonymous_unrooted(current_task, node);
429 let file = ns_node
430 .open(current_task, OpenAccessCheck::skip(OpenFlags::RDWR))
431 .expect("open buffer_size_kb node");
432
433 let mut buffer = VecOutputBuffer::new(64);
434 let bytes_read =
435 file.read_at(current_task, 0, &mut buffer).expect("read buffer_size_kb");
436 assert_eq!(&buffer.data()[..bytes_read], b"2048\n");
437
438 let mut input = starnix_core::vfs::buffers::VecInputBuffer::new(b"4096\n");
439 let bytes_written = file.write(current_task, &mut input).expect("write buffer_size_kb");
440 assert_eq!(bytes_written, 5);
441
442 let mut buffer2 = VecOutputBuffer::new(64);
443 let bytes_read2 =
444 file.read_at(current_task, 0, &mut buffer2).expect("read updated buffer_size_kb");
445 assert_eq!(&buffer2.data()[..bytes_read2], b"4096\n");
446
447 let mut input3 = starnix_core::vfs::buffers::VecInputBuffer::new(b"4\n");
449 file.write(current_task, &mut input3).expect("write small buffer_size_kb");
450 let mut buffer3 = VecOutputBuffer::new(64);
451 let bytes_read3 =
452 file.read_at(current_task, 0, &mut buffer3).expect("read clamped buffer_size_kb");
453 assert_eq!(&buffer3.data()[..bytes_read3], b"12\n");
454
455 let mut input_zero = starnix_core::vfs::buffers::VecInputBuffer::new(b"0\n");
457 assert_eq!(file.write(current_task, &mut input_zero), error!(EINVAL));
458
459 let mut input_bad = starnix_core::vfs::buffers::VecInputBuffer::new(b"invalid\n");
461 assert_eq!(file.write(current_task, &mut input_bad), error!(EINVAL));
462
463 event_queue_collection.enable().expect("enable tracing");
465 let mut input_enabled = starnix_core::vfs::buffers::VecInputBuffer::new(b"2048\n");
466 let bytes_written_enabled =
467 file.write(current_task, &mut input_enabled).expect("write enabled buffer_size_kb");
468 assert_eq!(bytes_written_enabled, 5);
469 let mut buffer_enabled = VecOutputBuffer::new(64);
470 let bytes_read_enabled = file
471 .read_at(current_task, 0, &mut buffer_enabled)
472 .expect("read buffer_size_kb while enabled");
473 assert_eq!(&buffer_enabled.data()[..bytes_read_enabled], b"2048\n");
474 event_queue_collection.disable().expect("disable tracing");
475 })
476 .await;
477 }
478}