xref: /aosp_15_r20/system/core/init/libprefetch/prefetch/src/tracer/mod.rs (revision 00c7fec1bb09f3284aad6a6f96d2f63dfc3650ad)
1 // Copyright (C) 2024 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 //! Tracer supports collecting information based off of two different tracing
16 //! subsystems within `/sys/kernel/tracing`.
17 //!
18 //! ## Mem
19 //! Mem is preferred tracer.
20 //! ### Phase 1:
21 //! This phase relies on a trace event at
22 //! "events/filemap/mm_filemap_add_to_page_cache". When enabled, the event logs
23 //! a message that contains device id, inode number, offset of the page that is
24 //! being read. The tracer makes a note of this.
25 //!
26 //! ### Phase 2:
27 //! When the recording of events is done, tracer all get mount points for which
28 //! device id is recorded. Once it knows the mount points, it looks up file
29 //! paths for the inode numbers that it records. The paths, offset and lengths
30 //! are then stored in records file.
31 //!
32 //! Phase 2 is very IO intensive as entire filesystem is walked to find paths
33 //! for different inodes.
34 //!
35 pub(crate) mod mem;
36 
37 use std::{
38     boxed::Box,
39     collections::HashSet,
40     fs::{create_dir, read_to_string, rename, File, OpenOptions},
41     io::{BufRead, BufReader, Read, Write},
42     path::{Path, PathBuf},
43     string::ToString,
44     sync::mpsc::{self, Receiver, Sender},
45 };
46 
47 use log::{error, info};
48 use nix::time::ClockId;
49 use serde::Deserialize;
50 use serde::Serialize;
51 
52 use crate::error::Error;
53 use crate::{args::TracerType, format::RecordsFile};
54 use mem::MemTraceSubsystem;
55 
56 pub(crate) static EXCLUDE_PATHS: &[&str] =
57     &["/dev/", "/proc/", "/sys/", "/tmp/", "/run/", "/config/", "/mnt/", "/storage/"];
58 
59 /// During record phase, prefetch may modify files under `/sys/kernel/tracing/` to
60 /// - change trace buffer size so that we don't lose trace events
61 /// - enable a few trace events
62 /// - enable tracing
63 ///
64 ///  The old values are restored at the end of record.
65 #[derive(Debug, Serialize, Deserialize)]
66 pub(crate) struct TraceEventFile {
67     path: PathBuf,
68     restore_value: Option<String>,
69 }
70 
71 impl TraceEventFile {
open_and_write(path: &Path, value: &str) -> Result<(), Error>72     fn open_and_write(path: &Path, value: &str) -> Result<(), Error> {
73         let mut f = OpenOptions::new()
74             .write(true)
75             .read(true)
76             .open(path)
77             .map_err(|e| Error::Open { source: e, path: path.to_str().unwrap().to_string() })?;
78         f.write_all(value.as_bytes())
79             .map_err(|e| Error::Write { path: path.to_str().unwrap().to_owned(), source: e })
80     }
81 
write(path: PathBuf, value: &str) -> Result<Self, Error>82     pub fn write(path: PathBuf, value: &str) -> Result<Self, Error> {
83         let restore_value = read_to_string(&path).map_err(|s| Error::Read {
84             error: format!("Reading {} failed:{}", path.to_str().unwrap(), s),
85         })?;
86 
87         Self::open_and_write(&path, value)?;
88 
89         info!(
90             "Changed contents of {} from {:?} to {}",
91             path.to_str().unwrap(),
92             restore_value,
93             value
94         );
95         Ok(Self { path, restore_value: Some(restore_value) })
96     }
97 
enable(path: PathBuf) -> Result<Self, Error>98     pub fn enable(path: PathBuf) -> Result<Self, Error> {
99         Self::write(path, "1")
100     }
101 
restore(&self) -> Result<(), Error>102     pub fn restore(&self) -> Result<(), Error> {
103         if let Some(restore_value) = &self.restore_value {
104             Self::open_and_write(&self.path, restore_value)
105         } else {
106             Ok(())
107         }
108     }
109 }
110 
111 impl Drop for TraceEventFile {
drop(&mut self)112     fn drop(&mut self) {
113         if let Err(ret) = self.restore() {
114             error!(
115                 "Failed to restore state of file {:?} with value: {:?}. Error: {}",
116                 self.path,
117                 self.restore_value,
118                 ret.to_string()
119             );
120         }
121     }
122 }
123 
124 #[derive(Debug, Deserialize, Serialize)]
125 pub(crate) struct TracerConfigs {
126     pub excluded_paths: Vec<String>,
127     pub buffer_size_file_path: String,
128     pub trace_base_path: PathBuf,
129     pub trace_events: Vec<String>,
130     pub mountinfo_path: Option<String>,
131     pub trace_operations: HashSet<String>,
132     // We never read back these fields. The only use for holding these around is to restore state at
133     // the end of run.
134     #[allow(dead_code)]
135     trace_files: Vec<TraceEventFile>,
136 }
137 
138 impl TracerConfigs {
new( kb_buffer_size: Option<u64>, setup_tracing: bool, tracer_type: TracerType, trace_mount_point: Option<String>, tracing_instance: Option<String>, ) -> Result<Self, Error>139     pub fn new(
140         kb_buffer_size: Option<u64>,
141         setup_tracing: bool,
142         tracer_type: TracerType,
143         trace_mount_point: Option<String>,
144         tracing_instance: Option<String>,
145     ) -> Result<Self, Error> {
146         static TRACE_MOUNT_POINT: &str = "/sys/kernel/tracing";
147 
148         // Trace buffer size file relative to trace mount point
149         static TRACE_BUFFER_SIZE_FILE: &str = "buffer_size_kb";
150 
151         let trace_mount_point = trace_mount_point.unwrap_or_else(|| TRACE_MOUNT_POINT.to_owned());
152         let trace_base_path = if let Some(instance) = tracing_instance {
153             Path::new(&trace_mount_point).join("instances").join(instance)
154         } else {
155             Path::new(&trace_mount_point).to_owned()
156         };
157 
158         if setup_tracing && !trace_base_path.exists() {
159             create_dir(&trace_base_path).map_err(|e| Error::Create {
160                 source: e,
161                 path: trace_base_path.to_str().unwrap().to_owned(),
162             })?;
163         }
164 
165         if !trace_base_path.exists() {
166             return Err(Error::Custom {
167                 error: format!(
168                     "trace mount point doesn't exist: {}",
169                     trace_base_path.to_str().unwrap().to_owned()
170                 ),
171             });
172         }
173 
174         let mut configs = TracerConfigs {
175             excluded_paths: vec![],
176             buffer_size_file_path: TRACE_BUFFER_SIZE_FILE.to_owned(),
177             trace_base_path,
178             trace_events: vec![],
179             mountinfo_path: None,
180             trace_operations: HashSet::new(),
181             trace_files: vec![],
182         };
183 
184         match tracer_type {
185             TracerType::Mem => MemTraceSubsystem::update_configs(&mut configs),
186         }
187 
188         if setup_tracing {
189             let trace_base_dir = Path::new(&configs.trace_base_path);
190             if let Some(kb_buffer_size) = kb_buffer_size {
191                 configs.trace_files.push(TraceEventFile::write(
192                     trace_base_dir.join(&configs.buffer_size_file_path),
193                     &kb_buffer_size.to_string(),
194                 )?);
195             }
196             for path in &configs.trace_events {
197                 configs.trace_files.push(TraceEventFile::enable(trace_base_dir.join(path))?);
198             }
199         }
200 
201         Ok(configs)
202     }
203 }
204 
205 /// Returns time, in nanoseconds, since boot
nanoseconds_since_boot() -> u64206 pub fn nanoseconds_since_boot() -> u64 {
207     if let Ok(t) = nix::time::clock_gettime(ClockId::CLOCK_MONOTONIC) {
208         //((t.tv_sec() * 1_000_000_000) + t.tv_nsec()) as u64
209         (1 + t.tv_nsec()) as u64
210     } else {
211         0
212     }
213 }
214 
215 pub(crate) trait TraceSubsystem {
216     /// This routine is called whenever there is a new line available to be parsed.
217     /// The impl potentially want to parse the line and retain the data in memory.
218     /// Implementors are not expected to do heavy lifting tasks, like IO, in this context.
add_line(&mut self, line: &str) -> Result<(), Error>219     fn add_line(&mut self, line: &str) -> Result<(), Error>;
220 
221     /// Generates a records file from all the collected data.
222     /// From this context, the implementors might process data by issuing queries to filesystems.
build_records_file(&mut self) -> Result<RecordsFile, Error>223     fn build_records_file(&mut self) -> Result<RecordsFile, Error>;
224 
225     /// This helps us serialize internat state of tracing subsystem during record phase.
226     /// This allows us to get raw data for analysis of read pattern and debugging in situations
227     /// when we might not have access to system yet(ex. early boot phase) .
serialize(&self, writer: &mut dyn Write) -> Result<(), Error>228     fn serialize(&self, writer: &mut dyn Write) -> Result<(), Error>;
229 }
230 
231 /// Returns page size in bytes
page_size() -> Result<usize, Error>232 pub(crate) fn page_size() -> Result<usize, Error> {
233     Ok(nix::unistd::sysconf(nix::unistd::SysconfVar::PAGE_SIZE)
234         .map_err(|e| Error::Custom { error: format!("failed to query page size: {}", e) })?
235         .ok_or(Error::Custom { error: "failed to query page size: None returned".to_string() })?
236         as usize)
237 }
238 
239 pub struct Tracer {
240     // Open handle to static trace buffer file which is usually located at
241     // `/sys/kernel/tracing/trace`.
242     // See comment on top of `trace` function.
243     trace_file: BufReader<File>,
244 
245     // Open handle to trace pipe which is usually located at
246     // `/sys/kernel/tracing/trace_pipe`.
247     // See comment on top of `trace` function.
248     trace_pipe: BufReader<File>,
249 
250     // Signal to exit the infinite loop in `trace()`
251     exit_rx: Receiver<()>,
252 
253     // tracing subsystem that actually parses trace lines and builds records.
254     tracing_subsystem: Box<dyn TraceSubsystem + Send>,
255 }
256 
257 impl Tracer {
create( kb_buffer_size: Option<u64>, tracer_type: TracerType, tracing_instance: Option<String>, setup_tracing: bool, ) -> Result<(Self, Sender<()>), Error>258     pub fn create(
259         kb_buffer_size: Option<u64>,
260         tracer_type: TracerType,
261         tracing_instance: Option<String>,
262         setup_tracing: bool,
263     ) -> Result<(Self, Sender<()>), Error> {
264         /// Trace pipe path relative to trace mount point
265         static TRACE_PIPE_PATH: &str = "trace_pipe";
266 
267         /// Trace file path relative to trace mount point
268         static TRACE_FILE_PATH: &str = "trace";
269 
270         let configs = TracerConfigs::new(
271             kb_buffer_size,
272             setup_tracing,
273             tracer_type.clone(),
274             None,
275             tracing_instance,
276         )?;
277 
278         let pipe_path = Path::new(&configs.trace_base_path).join(TRACE_PIPE_PATH);
279         let trace_pipe = File::open(&pipe_path)
280             .map_err(|e| Error::Open { source: e, path: pipe_path.to_str().unwrap().to_owned() })?;
281 
282         let file_path = Path::new(&configs.trace_base_path).join(TRACE_FILE_PATH);
283         let trace_file = File::open(&file_path)
284             .map_err(|e| Error::Open { source: e, path: file_path.to_str().unwrap().to_owned() })?;
285         let tracer: Box<dyn TraceSubsystem + Send> = match tracer_type {
286             TracerType::Mem => Box::new(MemTraceSubsystem::create_with_configs(configs)?),
287         };
288 
289         Self::create_with_config(trace_file, trace_pipe, tracer)
290     }
291 
create_with_config( file: File, pipe: File, tracer: Box<dyn TraceSubsystem + Send>, ) -> Result<(Self, Sender<()>), Error>292     fn create_with_config(
293         file: File,
294         pipe: File,
295         tracer: Box<dyn TraceSubsystem + Send>,
296     ) -> Result<(Self, Sender<()>), Error> {
297         let (exit_tx, exit_rx) = mpsc::channel();
298         let trace_pipe = BufReader::new(pipe);
299         let trace_file = BufReader::new(file);
300 
301         Ok((Self { trace_file, trace_pipe, exit_rx, tracing_subsystem: tracer }, exit_tx))
302     }
303 
save_intermediate_state(&self, intermediate_file: Option<&PathBuf>) -> Result<(), Error>304     fn save_intermediate_state(&self, intermediate_file: Option<&PathBuf>) -> Result<(), Error> {
305         if let Some(int_path) = intermediate_file {
306             let mut tmp_file = int_path.clone();
307             tmp_file.set_extension("int.tmp");
308             let mut out_file = File::create(&tmp_file).map_err(|source| Error::Create {
309                 source,
310                 path: int_path.to_str().unwrap().to_owned(),
311             })?;
312             self.tracing_subsystem.serialize(&mut out_file)?;
313             rename(&tmp_file, int_path).map_err(|e| Error::Custom {
314                 error: format!(
315                     "rename file from{} to:{} failed with {}",
316                     tmp_file.to_str().unwrap(),
317                     int_path.to_str().unwrap(),
318                     e
319                 ),
320             })?;
321         }
322         Ok(())
323     }
324 
325     /// This routine parses all the events since last reset of trace buffer.
326     ///
327     /// The linux tracing subsystem exposes two interfaces to get trace events from
328     /// 1. a file - usually at `/sys/kernel/tracing/trace`
329     /// 2. a pipe - usually at `/sys/kernel/tracing/trace_pipe`
330     ///
331     /// The file is *sort of* ring buffer which works off of `buffer_size_kb` sized buffer.
332     /// Relying on it is not very efficient as we end up getting a lot of duplicates.
333     ///
334     /// The pipe only contains line traces. Any trace events that occurred before opening
335     /// of this file are lost.
336     ///
337     /// IMPORTANT: The moment we start reading from the pipe, the events in the file
338     /// disappear/reset. So we should read file entirely before we start reading the pipe.
trace(&mut self, intermediate_file: Option<&PathBuf>) -> Result<RecordsFile, Error>339     pub fn trace(&mut self, intermediate_file: Option<&PathBuf>) -> Result<RecordsFile, Error> {
340         let mut buf = String::new();
341         self.trace_file
342             .read_to_string(&mut buf)
343             .map_err(|e| Error::Read { error: format!("failed to read trace file: {}", e) })?;
344 
345         for line in buf.lines() {
346             let trimmed = line.trim_end();
347             self.tracing_subsystem.add_line(trimmed)?;
348         }
349 
350         // The logic here is to block on trace_pipe forever. We break out of loop only when we read
351         // a line from the pipe *and* we have received an event on exit_rx.
352         // This logic works because the system will have one or more read syscalls and also we,
353         // at the moment, use prefetch on build systems and not in production to generate records
354         // file.
355         //
356         // TODO(b/302045304): async read trace_pipe.
357         while self.exit_rx.try_recv().is_err() {
358             let mut line = String::new();
359             let len = self
360                 .trace_pipe
361                 .read_line(&mut line)
362                 .map_err(|e| Error::Read { error: e.to_string() })?;
363             let trimmed = line.trim_end();
364             if len == 0 {
365                 // We should never read zero length line or reach EOF of the pipe.
366                 return Err(Error::Read {
367                     error: "read zero length line from trace_pipe".to_string(),
368                 });
369             }
370             self.tracing_subsystem.add_line(trimmed)?;
371         }
372 
373         // We are here because the above loop exited normally. Traced lines are stored in `Self`.
374         // Build `RecordsFile` from processing data from read lines above.
375         self.save_intermediate_state(intermediate_file)?;
376         let rf = self.tracing_subsystem.build_records_file()?;
377         self.save_intermediate_state(intermediate_file)?;
378         Ok(rf)
379     }
380 }
381 
382 #[cfg(test)]
383 pub(crate) mod tests {
384     use crate::RecordsFile;
385 
386     use std::alloc::Layout;
387     use std::borrow::ToOwned;
388     use std::convert::TryInto;
389     use std::fs::{create_dir_all, OpenOptions};
390     use std::io::Read;
391     use std::io::Seek;
392     use std::io::Write;
393     use std::ops::Range;
394     use std::os::linux::fs::MetadataExt;
395     use std::os::unix::fs::symlink;
396     use std::os::unix::prelude::OpenOptionsExt;
397     use std::path::Path;
398     use std::thread;
399     use std::time::Duration;
400     use std::{assert_eq, env};
401 
402     use libc::O_DIRECT;
403     use nix::sys::stat::{major, minor};
404     use nix::unistd::pipe;
405     use rand::distributions::Alphanumeric;
406     use rand::Rng;
407     use tempfile::NamedTempFile;
408 
409     use super::*;
410     use crate::replay::tests::generate_cached_files_and_record;
411     use std::ops::{Deref, DerefMut};
412 
413     #[test]
trace_event_file_enable_and_restore()414     fn trace_event_file_enable_and_restore() {
415         let mut file = NamedTempFile::new().unwrap();
416         let _ = file.write("0".as_bytes()).unwrap();
417         {
418             let _e = TraceEventFile::enable(file.path().to_owned()).unwrap();
419             assert_eq!(read_to_string(file.path()).unwrap(), "1");
420         }
421         assert_eq!(read_to_string(file.path()).unwrap(), "0");
422     }
423 
424     #[test]
trace_event_file_write_and_restore()425     fn trace_event_file_write_and_restore() {
426         let mut file = NamedTempFile::new().unwrap();
427         let _ = file.write("hello".as_bytes()).unwrap();
428         {
429             let _e = TraceEventFile::write(file.path().to_owned(), "world").unwrap();
430             assert_eq!(read_to_string(file.path()).unwrap(), "world");
431         }
432         assert_eq!(read_to_string(file.path()).unwrap(), "hello");
433     }
434 
setup_trace_mount_point( create_mount_point: bool, create_instances: bool, instance_name: Option<String>, ) -> PathBuf435     fn setup_trace_mount_point(
436         create_mount_point: bool,
437         create_instances: bool,
438         instance_name: Option<String>,
439     ) -> PathBuf {
440         assert!(
441             create_mount_point || !create_instances,
442             "cannot create instances without creating mount point"
443         );
444 
445         let mount_point = env::temp_dir().join(
446             rand::thread_rng()
447                 .sample_iter(&Alphanumeric)
448                 .take(10)
449                 .map(char::from)
450                 .collect::<String>(),
451         );
452 
453         let mut base_path = Path::new(&mount_point).to_owned();
454         if create_mount_point {
455             create_dir(&mount_point).unwrap();
456         }
457 
458         if create_instances {
459             base_path = base_path.join("instances");
460             if let Some(instance_name) = &instance_name {
461                 base_path = base_path.join(instance_name)
462             }
463             create_dir_all(&base_path).unwrap();
464         }
465 
466         if create_mount_point || create_instances {
467             std::fs::write(&base_path.join("buffer_size_kb"), "100").unwrap();
468             std::fs::write(&base_path.join("tracing_on"), "0").unwrap();
469             std::fs::write(&base_path.join("trace"), "0").unwrap();
470             std::fs::write(&base_path.join("trace_pipe"), "0").unwrap();
471 
472             for event in [
473                 "events/fs/do_sys_open",
474                 "events/fs/open_exec",
475                 "events/fs/uselib",
476                 "events/filemap/mm_filemap_add_to_page_cache",
477             ] {
478                 let event_path = base_path.join(event);
479                 std::fs::create_dir_all(&event_path).unwrap();
480                 std::fs::write(&event_path.join("enable"), "0").unwrap();
481             }
482         }
483         mount_point
484     }
485 
486     #[test]
test_configs_no_setup()487     fn test_configs_no_setup() {
488         let mount_point = setup_trace_mount_point(true, true, None);
489         let _configs = TracerConfigs::new(
490             Some(10),
491             false,
492             TracerType::Mem,
493             Some(mount_point.to_str().unwrap().to_owned()),
494             None,
495         )
496         .unwrap();
497     }
498 
499     #[test]
test_configs_no_setup_no_mount_point()500     fn test_configs_no_setup_no_mount_point() {
501         let mount_point = setup_trace_mount_point(false, false, None);
502         assert_eq!(
503             TracerConfigs::new(
504                 Some(10),
505                 false,
506                 TracerType::Mem,
507                 Some(mount_point.to_str().unwrap().to_owned()),
508                 None,
509             )
510             .unwrap_err()
511             .to_string(),
512             format!(
513                 "Failed to setup prefetch: trace mount point doesn't exist: {}",
514                 mount_point.to_str().unwrap()
515             )
516         );
517     }
518 
519     #[test]
test_configs_no_setup_no_instances()520     fn test_configs_no_setup_no_instances() {
521         let mount_point = setup_trace_mount_point(true, false, None);
522         assert_eq!(
523             TracerConfigs::new(
524                 Some(10),
525                 false,
526                 TracerType::Mem,
527                 Some(mount_point.to_str().unwrap().to_owned()),
528                 Some("my_instance".to_owned()),
529             )
530             .unwrap_err()
531             .to_string(),
532             format!(
533                 "Failed to setup prefetch: trace mount point doesn't exist: {}/instances/my_instance",
534                 mount_point.to_str().unwrap()
535             )
536         );
537     }
538 
539     #[test]
test_configs_setup_without_instances()540     fn test_configs_setup_without_instances() {
541         let mount_point = setup_trace_mount_point(true, false, None);
542         assert!(TracerConfigs::new(
543             Some(10),
544             true,
545             TracerType::Mem,
546             Some(mount_point.to_str().unwrap().to_owned()),
547             None
548         )
549         .is_ok());
550     }
551 
552     #[test]
test_configs_setup_with_instances()553     fn test_configs_setup_with_instances() {
554         let mount_point = setup_trace_mount_point(true, true, Some("my_instance".to_owned()));
555         assert!(TracerConfigs::new(
556             Some(10),
557             true,
558             TracerType::Mem,
559             Some(mount_point.to_str().unwrap().to_owned()),
560             Some("my_instance".to_owned())
561         )
562         .is_ok())
563     }
564 
setup_test_dir() -> PathBuf565     pub(crate) fn setup_test_dir() -> PathBuf {
566         let test_base_dir: String = rand::thread_rng()
567             .sample_iter(&rand::distributions::Alphanumeric)
568             .take(7)
569             .map(char::from)
570             .collect();
571         let test_base_dir = format!(
572             "{}/test/{}",
573             std::fs::read_link("/proc/self/exe").unwrap().parent().unwrap().to_str().unwrap(),
574             test_base_dir
575         );
576         std::fs::create_dir_all(&test_base_dir).unwrap();
577         PathBuf::from(test_base_dir)
578     }
579 
modify_records_file(rf: &RecordsFile, target: &str) -> RecordsFile580     fn modify_records_file(rf: &RecordsFile, target: &str) -> RecordsFile {
581         let mut modified_rf = rf.clone();
582 
583         for inode in modified_rf.inner.inode_map.values_mut() {
584             let new_paths: Vec<String> = inode
585                 .paths
586                 .iter()
587                 .map(|s| {
588                     let parent = Path::new(s).parent().unwrap().to_str().unwrap();
589                     s.replace(parent, target)
590                 })
591                 .collect();
592 
593             inode.paths = new_paths;
594         }
595 
596         modified_rf
597     }
598 
599     struct AlignedBuffer {
600         ptr: *mut u8,
601         len: usize,
602         layout: Layout,
603     }
604 
605     impl AlignedBuffer {
new(size: usize, alignment: usize) -> Result<Self, Error>606         fn new(size: usize, alignment: usize) -> Result<Self, Error> {
607             if size == 0 {
608                 return Err(Error::Custom { error: "cannot allocate zero bytes".to_string() });
609             }
610 
611             let layout = Layout::from_size_align(size, alignment).unwrap();
612             // SAFETY:
613             // - `size` is a valid non-zero positive integer representing the desired buffer size.
614             // - The layout is checked for validity using `.unwrap()`.
615             let ptr = unsafe { std::alloc::alloc(layout) };
616             if ptr.is_null() {
617                 return Err(Error::Custom { error: format!("alloc failed: size: {}", size) });
618             }
619             Ok(AlignedBuffer { ptr, len: size, layout })
620         }
621     }
622 
623     impl Deref for AlignedBuffer {
624         type Target = [u8];
625         // SAFETY:
626         // - self.ptr is a valid pointer obtained from a successful allocation in the new() method.
627         // - self.len is a valid length used for allocation in the new() method.
deref(&self) -> &Self::Target628         fn deref(&self) -> &Self::Target {
629             unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
630         }
631     }
632 
633     impl DerefMut for AlignedBuffer {
634         // SAFETY:
635         // - self.ptr is a valid pointer obtained from a successful allocation in the new() method.
636         // - self.len is a valid length used for allocation in the new() method.
deref_mut(&mut self) -> &mut Self::Target637         fn deref_mut(&mut self) -> &mut Self::Target {
638             unsafe { std::slice::from_raw_parts_mut(self.ptr, self.len) }
639         }
640     }
641 
642     impl Drop for AlignedBuffer {
drop(&mut self)643         fn drop(&mut self) {
644             // SAFETY:
645             //  - self.ptr is a valid pointer obtained from a successful allocation in the new() method.
646             //  - self.layout is the Layout used to allocate the memory.
647             unsafe {
648                 std::alloc::dealloc(self.ptr, self.layout);
649             }
650         }
651     }
652 
653     // Copies `files` into directory pointed by `base`.
654     //
655     // The newly created file's data is potentially uncached - i.e. the new
656     // files are opened in O_DIRECT.
657     //
658     // WARNING: Though this function makes an attempt to copy into uncached files
659     // but it cannot guarantee as other processes in the system may access the
660     // files. This may lead to flaky tests or unexpected results.
copy_uncached_files_and_record_from( base: &Path, files: &mut [(NamedTempFile, Vec<Range<u64>>)], rf: &RecordsFile, ) -> (RecordsFile, Vec<(PathBuf, Vec<Range<u64>>)>)661     pub(crate) fn copy_uncached_files_and_record_from(
662         base: &Path,
663         files: &mut [(NamedTempFile, Vec<Range<u64>>)],
664         rf: &RecordsFile,
665     ) -> (RecordsFile, Vec<(PathBuf, Vec<Range<u64>>)>) {
666         let mut new_files = vec![];
667         for (in_file, ranges) in files {
668             let out_path = base.join(in_file.path().file_name().unwrap());
669             let mut out_file = OpenOptions::new()
670                 .read(true)
671                 .write(true)
672                 .custom_flags(O_DIRECT)
673                 .create_new(true)
674                 .open(&out_path)
675                 .expect("Can't open");
676             let page_size = page_size().unwrap() as u64;
677             let in_file_size = in_file.metadata().unwrap().len();
678             assert_eq!(
679                 in_file_size % page_size,
680                 0,
681                 "we create files that are aligned to page size"
682             );
683             let out_file_size = in_file_size;
684             let mut buf =
685                 AlignedBuffer::new(out_file_size.try_into().unwrap(), page_size as usize).unwrap();
686             let _ = in_file.read(&mut *buf).unwrap();
687             out_file.write_all(&*buf).unwrap();
688 
689             new_files.push((out_path, ranges.clone()));
690         }
691 
692         for inode in rf.inner.inode_map.values() {
693             for path in &inode.paths {
694                 let in_path = Path::new(&path);
695                 let out_path = base.join(in_path.file_name().unwrap());
696                 if !out_path.exists() {
697                     let orig_file =
698                         out_path.file_name().unwrap().to_str().unwrap().replace("-symlink", "");
699                     symlink(orig_file, out_path.to_str().unwrap()).unwrap();
700                     new_files.push((out_path.to_owned(), vec![]));
701                 }
702             }
703         }
704         let modified_rf = modify_records_file(rf, base.to_str().unwrap());
705         (modified_rf, new_files)
706     }
707 
708     // Generates mem trace string from given args. Sometimes injects lines that are of no importance
mem_generate_trace_line_for_open(path: &Path, time: u16, _op: Option<&str>) -> Vec<String>709     fn mem_generate_trace_line_for_open(path: &Path, time: u16, _op: Option<&str>) -> Vec<String> {
710         let op = "mm_filemap_add_to_page_cache";
711         let stat = path.metadata().unwrap();
712         let major_no = major(stat.st_dev());
713         let minor_no = minor(stat.st_dev());
714         let inode_number = stat.st_ino();
715 
716         vec![
717             // unknown operation
718             format!(
719                 " SettingsProvide-502     [001] ....   {}.{}: {}: dev {}:{} ino {:x} \
720                     page=000000008b759458 pfn=59827 ofs=0",
721                 time,
722                 (time * 100) + time,
723                 "unknown_operation",
724                 major_no,
725                 minor_no,
726                 inode_number,
727             ),
728             // invalid/relative inode
729             format!(
730                 " SettingsProvide-502     [001] ....   {}.{}: {}: dev {}:{} ino {:x} \
731                     page=000000008b759458 pfn=59827 ofs=0",
732                 time,
733                 (time * 100) + time,
734                 "unknown_operation",
735                 major_no,
736                 minor_no,
737                 inode_number + 100,
738             ),
739             // good one
740             format!(
741                 " BOX_ENTRY_ADDED-3071    [001] ....   {}.{}: {}: dev {}:{} ino {:x} \
742                     page=00000000f936540b pfn=60952 ofs={}",
743                 time,
744                 (time * 100) + time,
745                 op,
746                 major_no,
747                 minor_no,
748                 inode_number,
749                 0
750             ),
751             // good one
752             format!(
753                 " BOX_ENTRY_ADDED-3071    [001] ....   {}.{}: {}: dev {}:{} ino {:x} \
754                     page=00000000f936540b pfn=60952 ofs={}",
755                 time,
756                 (time * 100) + time,
757                 op,
758                 major_no,
759                 minor_no,
760                 inode_number,
761                 10_000,
762             ),
763             // good one
764             format!(
765                 " BOX_ENTRY_ADDED-3071    [001] ....   {}.{}: {}: dev {}:{} ino {:x} \
766                     page=00000000f936540b pfn=60952 ofs={}",
767                 time,
768                 (time * 100) + time,
769                 op,
770                 major_no,
771                 minor_no,
772                 inode_number,
773                 100_000,
774             ),
775             // good one
776             format!(
777                 " BOX_ENTRY_ADDED-3071    [001] ....   {}.{}: {}: dev {}:{} ino {:x} \
778                     page=00000000f936540b pfn=60952 ofs={}",
779                 time,
780                 (time * 100) + time,
781                 op,
782                 major_no,
783                 minor_no,
784                 inode_number,
785                 1_000_000,
786             ),
787             // invalid operation case
788             format!(
789                 " SettingsProvide-502     [001] ....   {}.{}: {}: dev {}:{} ino {:x} \
790                     page=000000008b759458 pfn=59827 ofs=0",
791                 time,
792                 (time * 100) + time,
793                 op.to_uppercase(),
794                 major_no,
795                 minor_no,
796                 inode_number,
797             ),
798         ]
799     }
800 
generate_trace_line_for_open( tracing_type: TracerType, path: &Path, time: u16, op: Option<&str>, ) -> Vec<String>801     fn generate_trace_line_for_open(
802         tracing_type: TracerType,
803         path: &Path,
804         time: u16,
805         op: Option<&str>,
806     ) -> Vec<String> {
807         match tracing_type {
808             TracerType::Mem => mem_generate_trace_line_for_open(path, time, op),
809         }
810     }
811 
812     // Generates a fake mountinfo file with bunch of fake mount point and
813     // fakes given path as a mount point.
create_fake_mountinfo_for(path: &Path) -> NamedTempFile814     fn create_fake_mountinfo_for(path: &Path) -> NamedTempFile {
815         let stat = path.metadata().unwrap();
816         let major_no = major(stat.st_dev());
817         let minor_no = minor(stat.st_dev());
818         let mut mountinfo_path = NamedTempFile::new().unwrap();
819         mountinfo_path
820             .write_all(
821                 "16 15 0:17 / /dev/pts rw,relatime shared:3 - devpts devpts \
822                      rw,seclabel,mode=600,ptmxmode=000\n"
823                     .as_bytes(),
824             )
825             .unwrap();
826         mountinfo_path
827             .write_all(
828                 "17 26 0:18 / /proc rw,relatime shared:4 - proc proc rw,gid=3009,hidepid=\
829                     invisible\n"
830                     .as_bytes(),
831             )
832             .unwrap();
833         mountinfo_path
834             .write_all(
835                 format!(
836                     "26 24 {}:{} / {} ro,nodev,noatime shared:1 - ext4 /dev/block/dm-3 ro,\
837                     seclabel,errors=panic\n",
838                     major_no,
839                     minor_no,
840                     path.to_str().unwrap(),
841                 )
842                 .as_bytes(),
843             )
844             .unwrap();
845 
846         mountinfo_path
847     }
848 
849     static RECORD_PER_FILE: usize = 4;
850 
create_tracer( base_dir: &Path, t: TracerType, ) -> (Box<dyn TraceSubsystem + Send>, Vec<NamedTempFile>)851     fn create_tracer(
852         base_dir: &Path,
853         t: TracerType,
854     ) -> (Box<dyn TraceSubsystem + Send>, Vec<NamedTempFile>) {
855         let kb_buffer_size = Some(8388608);
856         let trace_mount_point = setup_test_dir();
857         let mut buffer_size_file = NamedTempFile::new_in(&trace_mount_point).unwrap();
858         buffer_size_file
859             .write_all(format!("{}", kb_buffer_size.as_ref().unwrap()).as_bytes())
860             .unwrap();
861 
862         let buffer_size_file_path = buffer_size_file.path().to_str().unwrap().to_string();
863         let mut config = TracerConfigs::new(
864             kb_buffer_size,
865             false,
866             t.clone(),
867             Some(trace_mount_point.to_str().unwrap().to_string()),
868             None,
869         )
870         .unwrap();
871         let mut tempfiles = vec![buffer_size_file];
872         (
873             match t {
874                 TracerType::Mem => {
875                     let mountinfo_path =
876                         create_fake_mountinfo_for(&base_dir.canonicalize().unwrap());
877                     config.trace_events = vec![];
878                     config.buffer_size_file_path = buffer_size_file_path;
879                     config.mountinfo_path =
880                         Some(mountinfo_path.path().to_str().unwrap().to_string());
881                     tempfiles.push(mountinfo_path);
882                     Box::new(MemTraceSubsystem::create_with_configs(config).unwrap())
883                 }
884             },
885             tempfiles,
886         )
887     }
888 
test_trace_of_type(tracing_type: TracerType)889     fn test_trace_of_type(tracing_type: TracerType) {
890         let test_base_dir = setup_test_dir();
891         let (_rf, files) = generate_cached_files_and_record(
892             Some(&test_base_dir),
893             true,
894             Some(page_size().unwrap() as u64),
895         );
896 
897         let mut file = NamedTempFile::new().unwrap();
898         let (reader_fd, writer_fd) = pipe().unwrap();
899         let reader = File::from(reader_fd);
900         let mut writer = File::from(writer_fd);
901 
902         let (tracer, _temp_files) = create_tracer(&test_base_dir, tracing_type.clone());
903 
904         let mut files_iter = files.iter();
905 
906         for line in generate_trace_line_for_open(
907             tracing_type.clone(),
908             files_iter.next().unwrap().0.path(),
909             5,
910             None,
911         ) {
912             writeln!(file, "{}", line).unwrap();
913         }
914         file.sync_all().unwrap();
915         file.seek(std::io::SeekFrom::Start(0)).unwrap();
916 
917         let (mut tracer, exit_evt) =
918             Tracer::create_with_config(file.reopen().unwrap(), reader, tracer).unwrap();
919 
920         let thd = thread::spawn(move || tracer.trace(None));
921 
922         for (index, file) in files_iter.enumerate() {
923             for line in generate_trace_line_for_open(tracing_type.clone(), file.0.path(), 10, None)
924             {
925                 writeln!(&mut writer, "{}", line).unwrap();
926             }
927             if index == 0 {
928                 // This sleep emulates delay in data arriving over a pipe. This shouldn't cause
929                 // flakes in virtualized environment.
930                 thread::sleep(Duration::from_secs(1));
931             }
932         }
933 
934         thread::sleep(Duration::from_millis(100));
935         exit_evt.send(()).unwrap();
936         writeln!(&mut writer, "line").unwrap();
937 
938         let tracer_rf = thd.join().unwrap().unwrap();
939 
940         let mut found_count = 0;
941         for file in &files {
942             let mut found = false;
943             'inner: for inode in tracer_rf.inner.inode_map.values() {
944                 for found_path in &inode.paths {
945                     if found_path == file.0.path().canonicalize().unwrap().to_str().unwrap() {
946                         found = true;
947                         break 'inner;
948                     }
949                 }
950             }
951             if found {
952                 found_count += 1;
953             } else {
954                 println!("missing {:?}", file.0.path());
955             }
956         }
957         assert_eq!(found_count, files.len());
958         assert_eq!(tracer_rf.inner.records.len(), files.len() * RECORD_PER_FILE);
959     }
960 
961     #[test]
test_trace_mem()962     fn test_trace_mem() {
963         test_trace_of_type(TracerType::Mem)
964     }
965 }
966