• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 //! See top level documentation for `crate::tracer`.
16 
17 use std::collections::hash_map::Iter;
18 use std::fs::symlink_metadata;
19 use std::io::{ErrorKind, Write};
20 use std::iter::Iterator;
21 use std::mem::take;
22 use std::os::unix::fs::MetadataExt;
23 use std::{
24     collections::{HashMap, HashSet},
25     fs::read_to_string,
26     option::Option,
27     path::{Path, PathBuf},
28 };
29 
30 use log::{debug, error, info, warn};
31 use regex::Regex;
32 use serde::Deserialize;
33 use serde::Serialize;
34 use walkdir::{DirEntry, WalkDir};
35 
36 use crate::format::{coalesce_records, FsInfo};
37 use crate::tracer::{page_size, TracerConfigs};
38 use crate::{
39     format::{DeviceNumber, InodeNumber},
40     tracer::{TraceSubsystem, EXCLUDE_PATHS},
41     Error, FileId, Record, RecordsFile,
42 };
43 
44 static MOUNTINFO_PATH: &str = "/proc/self/mountinfo";
45 
46 // Trace events to enable
47 // Paths are relative to trace mount point
48 static TRACE_EVENTS: &[&str] =
49     &["events/filemap/mm_filemap_add_to_page_cache/enable", "tracing_on"];
50 
51 // Filesystem types to ignore
52 static EXCLUDED_FILESYSTEM_TYPES: &[&str] = &[
53     "binder",
54     "bpf",
55     "cgroup",
56     "cgroup2",
57     "configfs",
58     "devpts",
59     "fuse", // No emulated storage
60     "fusectl",
61     "proc",
62     "pstore",
63     "selinuxfs",
64     "sysfs",
65     "tmpfs", // Check for apex mount points
66     "tracefs",
67     "functionfs", // adb, fastboot
68     "f2fs",       // Skip /data mounts
69 ];
70 
71 #[cfg(target_os = "linux")]
72 type MajorMinorType = u32;
73 #[cfg(target_os = "android")]
74 type MajorMinorType = i32;
75 
76 // TODO(b/302056482): Once we uprev nix crate, we can use the function exported by the crate.
major(dev: DeviceNumber) -> MajorMinorType77 fn major(dev: DeviceNumber) -> MajorMinorType {
78     (((dev >> 32) & 0xffff_f000) | ((dev >> 8) & 0x0000_0fff)) as MajorMinorType
79 }
80 
81 // TODO(b/302056482): Once we uprev nix crate, we can use the function exported by the crate.
minor(dev: DeviceNumber) -> MajorMinorType82 fn minor(dev: DeviceNumber) -> MajorMinorType {
83     (((dev >> 12) & 0xffff_ff00) | ((dev) & 0x0000_00ff)) as MajorMinorType
84 }
85 
86 // TODO(b/302056482): Once we uprev nix crate, we can use the function exported by the crate.
makedev(major: MajorMinorType, minor: MajorMinorType) -> DeviceNumber87 fn makedev(major: MajorMinorType, minor: MajorMinorType) -> DeviceNumber {
88     let major = major as DeviceNumber;
89     let minor = minor as DeviceNumber;
90     ((major & 0xffff_f000) << 32)
91         | ((major & 0x0000_0fff) << 8)
92         | ((minor & 0xffff_ff00) << 12)
93         | (minor & 0x0000_00ff)
94 }
95 
build_device_number(major: &str, minor: &str) -> Result<DeviceNumber, Error>96 fn build_device_number(major: &str, minor: &str) -> Result<DeviceNumber, Error> {
97     Ok(makedev(
98         major.parse::<MajorMinorType>().map_err(|e| Error::Custom {
99             error: format!("Failed to parse major number from {} with {}", major, e),
100         })?,
101         minor.parse::<MajorMinorType>().map_err(|e| Error::Custom {
102             error: format!("Failed to parse major number from {} with {}", major, e),
103         })?,
104     ))
105 }
106 
107 // Returns timestamp in nanoseconds
build_timestamp(seconds: &str, microseconds: &str) -> Result<u64, Error>108 fn build_timestamp(seconds: &str, microseconds: &str) -> Result<u64, Error> {
109     let seconds = seconds.parse::<u64>().map_err(|e| Error::Custom {
110         error: format!("Failed to parse seconds from {} with {}", seconds, e),
111     })?;
112     let microseconds = microseconds.parse::<u64>().map_err(|e| Error::Custom {
113         error: format!("Failed to parse microseconds from {} with {}", seconds, e),
114     })?;
115     Ok((seconds * 1_000_000_000) + (microseconds * 1_000))
116 }
117 
118 #[cfg(not(target_os = "android"))]
is_highly_privileged_path(_path: &Path) -> bool119 fn is_highly_privileged_path(_path: &Path) -> bool {
120     false
121 }
122 
123 #[cfg(target_os = "android")]
is_highly_privileged_path(path: &Path) -> bool124 fn is_highly_privileged_path(path: &Path) -> bool {
125     // Following directories contain a mix of files with and without access to stat/read.
126     // We do not completely exclude these directories as there is still a lot of
127     // file we can issue readahead on. Some of the files on which readahead fails include
128     // - /system/bin/run-as
129     // - /data/data/com.android.storagemanager
130     // - /system/apex/com.android.art/bin/dex2oat32
131     // - /data/user/0/com.android.systemui
132     //
133     // - TODO: /system/apex: Apex files in read-only partition may be read during boot.
134     // However, some files may not have access. Double check the record files
135     // to filter out the exact path.
136     let privileged_paths = [
137         "/data/data",
138         "/data/user/0",
139         "/data/user_de/0",
140         "/system/bin/",
141         "/system/etc/selinux/",
142         "/system/system_ext/etc/selinux/",
143         "/system/product/etc/selinux/",
144         "/system/vendor/etc/selinux/",
145         "/system_ext/etc/selinux/",
146         "/product/etc/selinux/",
147         "/vendor/etc/selinux/",
148         "/system/xbin",
149         "/system/etc/",
150         "/data/",
151         "/postinstall/",
152         "/mnt/",
153         "/metadata/",
154     ];
155     for privileged in privileged_paths {
156         if path.to_str().unwrap().starts_with(privileged) {
157             return true;
158         }
159     }
160     false
161 }
162 
163 enum DeviceState {
164     Include((DeviceNumber, PathBuf)),
165     Exclude(DeviceNumber),
166 }
167 
168 /// Utility struct that helps to include and exclude devices and mount points that need and don't
169 /// need prefetching.
170 #[derive(Debug, Deserialize, Serialize)]
171 struct MountInfo {
172     // Map of device number to mount points
173     included_devices: HashMap<DeviceNumber, PathBuf>,
174 
175     // Devices that we don't want to prefetch - like devices backing tempfs and sysfs
176     excluded_devices: HashSet<DeviceNumber>,
177 }
178 
179 impl MountInfo {
180     // Parses file at `path` to build `Self`.`
create(path: &str) -> Result<Self, Error>181     fn create(path: &str) -> Result<Self, Error> {
182         let buf = read_to_string(path)
183             .map_err(|e| Error::Read { error: format!("Reading {} failed with: {}", path, e) })?;
184         Self::with_buf(&buf)
185     }
186 
187     // Parses string in `buf` to build `Self`.
with_buf(buf: &str) -> Result<Self, Error>188     fn with_buf(buf: &str) -> Result<Self, Error> {
189         let regex = Self::get_regex()?;
190         let mut included_devices: HashMap<DeviceNumber, PathBuf> = HashMap::new();
191         let mut excluded_devices = HashSet::new();
192         let excluded_filesystem_types: HashSet<String> =
193             EXCLUDED_FILESYSTEM_TYPES.iter().map(|s| String::from(*s)).collect();
194         for line in buf.lines() {
195             if let Some(state) = Self::parse_line(&regex, &excluded_filesystem_types, line)? {
196                 match state {
197                     DeviceState::Include((device, path)) => {
198                         included_devices.insert(device, path);
199                     }
200                     DeviceState::Exclude(device) => {
201                         excluded_devices.insert(device);
202                     }
203                 }
204             }
205         }
206 
207         Ok(Self { included_devices, excluded_devices })
208     }
209 
parse_line( re: &Regex, excluded_filesystem_types: &HashSet<String>, line: &str, ) -> Result<Option<DeviceState>, Error>210     fn parse_line(
211         re: &Regex,
212         excluded_filesystem_types: &HashSet<String>,
213         line: &str,
214     ) -> Result<Option<DeviceState>, Error> {
215         let caps = match re.captures(line) {
216             Some(caps) => caps,
217             None => {
218                 return Ok(None);
219             }
220         };
221         if &caps["relative_path"] != "/" {
222             return Ok(None);
223         }
224 
225         let mount_point = &caps["mount_point"];
226         let mnt_pnt_with_slash = format!("{}/", mount_point);
227         let device_number = build_device_number(&caps["major"], &caps["minor"])?;
228         let fs_type = &caps["fs_type"];
229 
230         if excluded_filesystem_types.contains(fs_type) {
231             info!(
232                 "excluding fs type: {} for {} mount-point {} slash {}",
233                 fs_type, line, mount_point, mnt_pnt_with_slash
234             );
235             return Ok(Some(DeviceState::Exclude(device_number)));
236         }
237 
238         for excluded in EXCLUDE_PATHS {
239             if mnt_pnt_with_slash.starts_with(excluded) {
240                 info!(
241                     "exclude-paths fs type: {} for {} mount-point {} slash {}",
242                     fs_type, line, mount_point, mnt_pnt_with_slash
243                 );
244                 return Ok(Some(DeviceState::Exclude(device_number)));
245             }
246         }
247 
248         Ok(Some(DeviceState::Include((device_number, PathBuf::from(mount_point)))))
249     }
250 
get_regex() -> Result<Regex, Error>251     fn get_regex() -> Result<Regex, Error> {
252         Regex::new(concat!(
253             r"^\s*(?P<id_unknown1>\S+)",
254             r"\s+(?P<id_unknown2>\S+)",
255             r"\s+(?P<major>[0-9]+):(?P<minor>[0-9]+)",
256             r"\s+(?P<relative_path>\S+)",
257             r"\s+(?P<mount_point>\S+)",
258             r"\s+(?P<mount_opt>\S+)",
259             r"\s+(?P<shared>\S+)",
260             r"\s+\S+",
261             r"\s+(?P<fs_type>\S+)",
262             r"\s+(?P<device_path>\S+)"
263         ))
264         .map_err(|e| Error::Custom {
265             error: format!("create regex for parsing mountinfo failed with: {}", e),
266         })
267     }
268 
is_excluded(&self, device: &DeviceNumber) -> bool269     fn is_excluded(&self, device: &DeviceNumber) -> bool {
270         self.excluded_devices.contains(device)
271     }
272 
get_included(&self) -> Iter<DeviceNumber, PathBuf>273     fn get_included(&self) -> Iter<DeviceNumber, PathBuf> {
274         self.included_devices.iter()
275     }
276 }
277 
278 #[derive(Default, PartialEq, Debug, Eq, Hash)]
279 struct TraceLineInfo {
280     device: DeviceNumber,
281     inode: InodeNumber,
282     offset: u64,
283     timestamp: u64,
284 }
285 
286 impl TraceLineInfo {
from_trace_line(re: &Regex, line: &str) -> Result<Option<Self>, Error>287     pub fn from_trace_line(re: &Regex, line: &str) -> Result<Option<Self>, Error> {
288         let caps = match re.captures(line) {
289             Some(caps) => caps,
290             None => return Ok(None),
291         };
292         let major = &caps["major"];
293         let minor = &caps["minor"];
294         let ino = &caps["ino"];
295         let offset = &caps["offset"];
296         let timestamp = build_timestamp(&caps["seconds"], &caps["microseconds"])?;
297         Ok(Some(TraceLineInfo {
298             device: build_device_number(major, minor)?,
299             inode: u64::from_str_radix(ino, 16).map_err(|e| Error::Custom {
300                 error: format!("failed parsing inode: {} : {}", ino, e),
301             })?,
302             offset: offset.parse::<u64>().map_err(|e| Error::Custom {
303                 error: format!("failed parsing offset: {} : {}", offset, e),
304             })?,
305             timestamp,
306         }))
307     }
308 
309     #[cfg(test)]
from_fields( major: MajorMinorType, minor: MajorMinorType, inode: u64, offset: u64, timestamp: u64, ) -> Self310     pub fn from_fields(
311         major: MajorMinorType,
312         minor: MajorMinorType,
313         inode: u64,
314         offset: u64,
315         timestamp: u64,
316     ) -> Self {
317         Self { device: makedev(major, minor), inode, offset, timestamp }
318     }
319 
320     // Convenience function to create regex. Used once per life of `record` but multiple times in
321     // case of tests.
get_trace_line_regex() -> Result<Regex, Error>322     pub fn get_trace_line_regex() -> Result<Regex, Error> {
323         // `page=[hex]` entry exists in 5.x kernel format but not in 6.x.
324         // Conversely, `order=[digit]` entry exists in 6.x kernel format but not in 5.x.
325         Regex::new(concat!(
326             r"^\s+(?P<cmd_pid>\S+)",
327             r"\s+(?P<cpu>\S+)",
328             r"\s+(?P<irq_stuff>\S+)",
329             r"\s+(?P<seconds>[0-9]+)\.(?P<microseconds>[0-9]+):",
330             r"\s+mm_filemap_add_to_page_cache:",
331             r"\s+dev\s+(?P<major>[0-9]+):(?P<minor>[0-9]+)",
332             r"\s+ino\s+(?P<ino>\S+)",
333             r"(?:\s+(?P<page>page=\S+))?",
334             r"\s+(?P<pfn>\S+)",
335             r"\s+ofs=(?P<offset>[0-9]+)",
336             r"(?:\s+(?P<order>\S+))?"
337         ))
338         .map_err(|e| Error::Custom {
339             error: format!("create regex for tracing failed with: {}", e),
340         })
341     }
342 }
343 
344 #[derive(Debug, Serialize, Deserialize)]
345 struct MissingFile {
346     major_no: MajorMinorType,
347     minor_no: MajorMinorType,
348     inode: InodeNumber,
349     records: Vec<Record>,
350 }
351 
352 #[derive(Debug, Default, Deserialize, Serialize)]
353 struct DebugInfo {
354     // Check all inodes for which paths don't exists. These are the files which
355     // * got deleted before we got to them
356     // * are filesystem internal files that fs access only via inode numbers.
357     missing_files: HashMap<FileId, MissingFile>,
358 
359     // Number of bytes read that belongs to directory type inodes.
360     directory_read_bytes: u64,
361 
362     // Number of bytes read from files for which we could not find a path in
363     // the filesystems.
364     missing_path_bytes: u64,
365 
366     // Paths for which the current process doesn't have read permission.
367     privileged_paths: Vec<PathBuf>,
368 }
369 
370 #[derive(Debug, Serialize)]
371 pub(crate) struct MemTraceSubsystem {
372     device_inode_map: HashMap<DeviceNumber, HashMap<InodeNumber, FileId>>,
373     // Count of all InodeNumber held by `device_inode_map`. This is handy to assign unique
374     // FileId.
375     inode_count: u64,
376 
377     // `Record`s built from parsing read trace lines.
378     records: Vec<Record>,
379 
380     // Regex to parse lines from trace_pipe.
381     #[serde(skip_serializing)]
382     regex: Regex,
383 
384     // Mounted devices/filesystems either at the time of parsing trace file or at the time
385     // of building RecordsFile from parsed lines.
386     mount_info: MountInfo,
387 
388     // A copy of TracerConfigs
389     tracer_configs: Option<TracerConfigs>,
390 
391     // system page size stored to avoid frequent syscall to get the page size.
392     page_size: u64,
393 
394     // The fields of the debug_info are populated when build_records_file is called (after lines
395     // are parsed from the trace file/pipe).
396     debug_info: DebugInfo,
397 }
398 
399 impl MemTraceSubsystem {
update_configs(configs: &mut TracerConfigs)400     pub fn update_configs(configs: &mut TracerConfigs) {
401         for path in EXCLUDE_PATHS {
402             configs.excluded_paths.push(path.to_owned().to_string());
403         }
404 
405         for event in TRACE_EVENTS {
406             configs.trace_events.push(event.to_owned().to_string());
407         }
408         configs.mountinfo_path = Some(MOUNTINFO_PATH.to_string());
409     }
410 
create_with_configs(tracer_configs: TracerConfigs) -> Result<Self, Error>411     pub fn create_with_configs(tracer_configs: TracerConfigs) -> Result<Self, Error> {
412         static INITIAL_RECORDS_CAPACITY: usize = 100_000;
413         debug!("TracerConfig: {:#?}", tracer_configs);
414 
415         let regex = TraceLineInfo::get_trace_line_regex()?;
416         let mount_info = MountInfo::create(tracer_configs.mountinfo_path.as_ref().unwrap())?;
417         debug!("mountinfo: {:#?}", mount_info);
418 
419         Ok(Self {
420             device_inode_map: HashMap::new(),
421             inode_count: 0,
422             // For one product of android, we see around 50k records. To avoid a lot allocations
423             // and copying of records, we create a vec of this size.
424             //
425             // We do this to reduces chances of losing data, however unlikely, coming over
426             // `trace_pipe`.
427             //
428             // Note: Once we are done reading trace lines, we are less pedantic about allocations
429             // and mem copies.
430             records: Vec::with_capacity(INITIAL_RECORDS_CAPACITY),
431             regex,
432             mount_info,
433             tracer_configs: Some(tracer_configs),
434             page_size: page_size()? as u64,
435             debug_info: DebugInfo {
436                 missing_files: HashMap::new(),
437                 directory_read_bytes: 0,
438                 missing_path_bytes: 0,
439                 privileged_paths: vec![],
440             },
441         })
442     }
443 
new_file_id(&mut self) -> FileId444     fn new_file_id(&mut self) -> FileId {
445         let id = self.inode_count;
446         self.inode_count += 1;
447         FileId(id)
448     }
449 
get_trace_info(&self, line: &str) -> Result<Option<TraceLineInfo>, Error>450     fn get_trace_info(&self, line: &str) -> Result<Option<TraceLineInfo>, Error> {
451         TraceLineInfo::from_trace_line(&self.regex, line)
452     }
453 
454     // Returns true if the file or directory is on a device which is excluded from walking.
455     // If the path was excluded because the current process doesn't have privileged to read it,
456     // the path gets added to `privileged` list.
is_excluded(&self, entry: &DirEntry, device: u64, privileged: &mut Vec<PathBuf>) -> bool457     fn is_excluded(&self, entry: &DirEntry, device: u64, privileged: &mut Vec<PathBuf>) -> bool {
458         // We skip paths that are reside on excluded devices here. This is ok because a
459         // non-excluded mount point will have a separate entry in MountInfo. For example
460         // - `/` has ext4
461         // - `/tmp` has tempfs
462         // - `/tmp/mnt` has ext4 that we are interested in.
463         // MountInfo will have three entries - `/`, `/tmp/` and `/tmp/mnt`. Skipping walking
464         // `/tmp` while walking `/` is ok as next `mount_info.get_included()` will return
465         // `/tmp/mnt` path.
466         //
467         //
468         // We skip links here as they can refer to mount points across
469         // filesystems. If that path is valid and access are valid, then
470         // we should have entry by the file's <device, inode> pair.
471         //
472         //
473         // We skip devices that don't match current walking device because we eventually
474         // walk other devices.
475         match symlink_metadata(entry.path()) {
476             Ok(lstat) => {
477                 if self.mount_info.is_excluded(&lstat.dev())
478                     || lstat.dev() != device
479                     || lstat.file_type().is_symlink()
480                 {
481                     return true;
482                 }
483             }
484             Err(e) => {
485                 error!("stat on {} failed with {}", entry.path().to_str().unwrap(), e);
486 
487                 // We treat EACCES special because on some platforms, like android, process needs to
488                 // have very special set of permissions to access some inodes.
489                 // We ignore errors in such cases *after* making an effort to get to them.
490                 if e.kind() == ErrorKind::PermissionDenied
491                     && is_highly_privileged_path(entry.path())
492                 {
493                     privileged.push(entry.path().to_owned());
494                     return true;
495                 }
496             }
497         }
498 
499         // On error, we return false because if lstat has failed, it will fail following operations
500         // including stat.
501         false
502     }
503 }
504 
505 impl TraceSubsystem for MemTraceSubsystem {
add_line(&mut self, line: &str) -> Result<(), Error>506     fn add_line(&mut self, line: &str) -> Result<(), Error> {
507         if let Some(info) = self.get_trace_info(line)? {
508             if self.mount_info.is_excluded(&info.device) {
509                 return Ok(());
510             }
511 
512             self.device_inode_map.entry(info.device).or_default();
513 
514             let file_id = if let Some(id) =
515                 self.device_inode_map.get_mut(&info.device).unwrap().get(&info.inode)
516             {
517                 id.clone()
518             } else {
519                 self.new_file_id()
520             };
521             self.device_inode_map
522                 .get_mut(&info.device)
523                 .unwrap()
524                 .insert(info.inode, file_id.clone());
525 
526             self.records.push(Record {
527                 file_id,
528                 offset: info.offset,
529                 length: self.page_size,
530                 timestamp: info.timestamp,
531             });
532         }
533 
534         Ok(())
535     }
536 
build_records_file(&mut self) -> Result<RecordsFile, Error>537     fn build_records_file(&mut self) -> Result<RecordsFile, Error> {
538         // reset debug_info in case build_records_file was called twice.
539         self.debug_info = DebugInfo::default();
540         let mut rf = RecordsFile::default();
541         let mut directories = HashSet::new();
542 
543         // TODO(b/302194377): We are holding all privileged_paths in this variable and then
544         // transferring it to `self.debug_info.privileged_paths` later. We can avoid this step
545         // if we directly update `self.debug_info.privileged_paths`. To do so, we need to refactor
546         // code to make borrow not complain at several places - ex. immutably borrowing
547         // `self.mount_info` in outer loop and then mutably borrowing
548         // `self.debug_info.privileged_paths`.
549         let mut privileged_paths = vec![];
550 
551         // Reload mount_info. When we created mount_info for the first time, maybe
552         // the system was in early boot phase. Reload the mount_info so as to get
553         // current/new mount points.
554         if let Some(tracer_config) = &self.tracer_configs {
555             self.mount_info = MountInfo::create(tracer_config.mountinfo_path.as_ref().unwrap())?;
556             debug!("reloaded mountinfo: {:#?}", self.mount_info);
557         }
558 
559         for (device, root_path) in self.mount_info.get_included() {
560             let inode_map = if let Some(map) = self.device_inode_map.get(device) {
561                 map
562             } else {
563                 continue;
564             };
565 
566             if inode_map.is_empty() {
567                 return Err(Error::Custom {
568                     error: format!("Unexpected empty records for {:?}", root_path),
569                 });
570             }
571 
572             let mut block_size = 0;
573             let walker = WalkDir::new(root_path).into_iter();
574 
575             for entry in
576                 walker.filter_entry(|e| !self.is_excluded(e, *device, &mut privileged_paths))
577             {
578                 let path = match entry {
579                     Ok(entry) => entry.path().to_owned(),
580                     Err(e) => {
581                         error!("walking directory failed: {} {}", root_path.to_str().unwrap(), e);
582                         continue;
583                     }
584                 };
585 
586                 let stat = match path.metadata() {
587                     Ok(stat) => stat,
588                     Err(e) => {
589                         error!("stat on {} failed with {}", path.to_str().unwrap(), e);
590                         continue;
591                     }
592                 };
593 
594                 block_size = stat.blksize();
595 
596                 let file_id = if let Some(id) = inode_map.get(&stat.ino()) {
597                     id.clone()
598                 } else {
599                     continue;
600                 };
601 
602                 // We cannot issue a normal readahead on directories. So we skip those records that
603                 // belong to directories.
604                 if stat.file_type().is_dir() {
605                     info!(
606                         "skipping directory readahead record for file_id:{file_id} ino:{} path:{} ",
607                         stat.ino(),
608                         path.to_str().unwrap()
609                     );
610                     directories.insert(file_id.clone());
611                     continue;
612                 }
613 
614                 rf.insert_or_update_inode(file_id, &stat, path.to_str().unwrap().to_owned());
615             }
616 
617             rf.inner.filesystems.insert(*device, FsInfo { block_size });
618         }
619 
620         self.debug_info.privileged_paths.append(&mut privileged_paths);
621 
622         for (device, inode_map) in &self.device_inode_map {
623             for (inode, file_id) in inode_map {
624                 if !rf.inner.inode_map.contains_key(file_id) {
625                     let major_no: MajorMinorType = major(*device);
626                     let minor_no: MajorMinorType = minor(*device);
627                     self.debug_info.missing_files.insert(
628                         file_id.clone(),
629                         MissingFile { major_no, minor_no, inode: *inode, records: vec![] },
630                     );
631                 }
632             }
633         }
634 
635         // Remove all records that belong to directories or for which we did not find paths.
636         let mut records = vec![];
637         for record in take(&mut self.records) {
638             if directories.contains(&record.file_id) {
639                 self.debug_info.directory_read_bytes += record.length;
640             } else if let Some(missing_file) =
641                 self.debug_info.missing_files.get_mut(&record.file_id)
642             {
643                 self.debug_info.missing_path_bytes += record.length;
644                 missing_file.records.push(record);
645             } else {
646                 records.push(record);
647             }
648         }
649 
650         warn!(
651             "Recorded {} bytes worth of data read from directories",
652             self.debug_info.directory_read_bytes
653         );
654         warn!(
655             "Recorded {} bytes worth of data read from files that don't have paths",
656             self.debug_info.missing_path_bytes
657         );
658 
659         rf.inner.records = coalesce_records(records, true);
660 
661         Ok(rf)
662     }
663 
serialize(&self, write: &mut dyn Write) -> Result<(), Error>664     fn serialize(&self, write: &mut dyn Write) -> Result<(), Error> {
665         write
666             .write_all(
667                 &serde_json::to_vec(&self)
668                     .map_err(|e| Error::Serialize { error: e.to_string() })?,
669             )
670             .map_err(|source| Error::Write { path: "intermediate file".to_owned(), source })
671     }
672 }
673 
674 #[cfg(test)]
675 mod tests {
676     use nix::sys::stat::{major, minor};
677     use std::assert_eq;
678     use std::path::Path;
679 
680     use crate::tracer::tests::{copy_uncached_files_and_record_from, setup_test_dir};
681 
682     use crate::replay::tests::generate_cached_files_and_record;
683 
684     use super::*;
685 
686     static TRACE_BUFFER: &str = concat!(
687         // kernel 5.x
688         " Settingide-502  [001] ....   484.360292: mm_filemap_add_to_page_CACHE: dev 254:6 ino cf1 page=68d477 pfn=59833 ofs=32768\n",
689         " Settingide-502  [001] ....   484.360311: mm_filemap_add_to_page_cache: dev 254:6 ino cf1 page=759458 pfn=59827 ofs=57344\n",
690         " BOX_ENTDED-3071 [001] ....   485.276715: mm_filemap_add_to_pag_ecache: dev 254:6 ino 1 page=00cc1c pfn=81748 ofs=13574144\n",
691         " BOX_ENTDED-3071 [001] ....   485.276990: mm_filemap_add_to_page_cache: dev 254:6 ino cf2 page=36540b pfn=60952 ofs=0\n",
692         " .gms.peent-843  [001] ....   485.545516: mm_filemap_add_to_page_cache: dev 254:6 ino 1 page=002e8b pfn=58928 ofs=13578240\n",
693         " .gms.peent-843  [001] ....   485.545820: mm_filemap_add_to_page_cache: dev 254:6 ino cf3 page=6233ce pfn=58108 ofs=0\n",
694         "      an.bg-459  [001] ....   494.029396: mm_filemap_add_to_page_cache: dev 254:3 ino 7cf page=c5b5c7 pfn=373933 ofs=1310720\n",
695         "      an.bg-459  [001] ....   494.029398: mm_filemap_add_to_page_cache: dev 254:3 ino 7cf page=b8b9ec pfn=410074 ofs=1314816\n",
696 
697         // kernel 6.x
698         " logcat-686     [006] ..... 148216.040320: mm_filemap_add_to_page_CACHE: dev 254:85 ino 3f15 pfn=0x213bc2 ofs=528384 order=0\n",
699         " logcat-686     [001] ..... 148217.776227: mm_filemap_add_to_page_cache: dev 254:85 ino 3f15 pfn=0x21d306 ofs=532480 order=0\n",
700         " logcat-686     [003] ..... 148219.044389: mm_filemap_add_to_pag_ecache: dev 254:85 ino 3f15 pfn=0x224b8d ofs=536576 order=0\n",
701         " logcat-686     [001] ..... 148220.780964: mm_filemap_add_to_page_cache: dev 254:85 ino 3f15 pfn=0x1bfe0a ofs=540672 order=0\n",
702         " logcat-686     [001] ..... 148223.046560: mm_filemap_add_to_page_cache: dev 254:85 ino 3f15 pfn=0x1f3d29 ofs=544768 order=0",
703     );
704 
sample_mem_traces() -> (String, Vec<Option<TraceLineInfo>>)705     fn sample_mem_traces() -> (String, Vec<Option<TraceLineInfo>>) {
706         (
707             TRACE_BUFFER.to_owned(),
708             vec![
709                 // 5.x
710                 None,
711                 Some(TraceLineInfo::from_fields(254, 6, 0xcf1, 57344, 484360311000)),
712                 None,
713                 Some(TraceLineInfo::from_fields(254, 6, 0xcf2, 0, 485276990000)),
714                 Some(TraceLineInfo::from_fields(254, 6, 0x1, 13578240, 485545516000)),
715                 Some(TraceLineInfo::from_fields(254, 6, 0xcf3, 0, 485545820000)),
716                 Some(TraceLineInfo::from_fields(254, 3, 0x7cf, 1310720, 494029396000)),
717                 Some(TraceLineInfo::from_fields(254, 3, 0x7cf, 1314816, 494029398000)),
718                 // 6.x
719                 None,
720                 Some(TraceLineInfo::from_fields(254, 85, 0x3f15, 532480, 148217776227000)),
721                 None,
722                 Some(TraceLineInfo::from_fields(254, 85, 0x3f15, 540672, 148220780964000)),
723                 Some(TraceLineInfo::from_fields(254, 85, 0x3f15, 544768, 148223046560000)),
724             ],
725         )
726     }
727 
728     #[test]
test_parse_trace_line()729     fn test_parse_trace_line() {
730         let (buf, res) = sample_mem_traces();
731         let re = TraceLineInfo::get_trace_line_regex().unwrap();
732         for (index, line) in buf.lines().enumerate() {
733             let found = TraceLineInfo::from_trace_line(&re, line).unwrap();
734             let expected = res.get(index).unwrap();
735             assert_eq!(found.is_some(), expected.is_some());
736             if found.is_some() {
737                 assert_eq!(found.unwrap(), *expected.as_ref().unwrap());
738             }
739         }
740     }
741 
742     #[test]
test_add_line()743     fn test_add_line() {
744         let test_base_dir = setup_test_dir();
745         let (rf, mut files) =
746             generate_cached_files_and_record(None, true, Some(page_size().unwrap() as u64));
747         let (_uncached_rf, uncached_files) =
748             copy_uncached_files_and_record_from(Path::new(&test_base_dir), &mut files, &rf);
749         let mut mount_include = HashMap::new();
750 
751         let included_dev = uncached_files.get(0).unwrap().0.metadata().unwrap().dev();
752         let included_inode1 = uncached_files.get(0).unwrap().0.metadata().unwrap().ino();
753         let included_inode2 = uncached_files.get(1).unwrap().0.metadata().unwrap().ino();
754         let included_major = major(included_dev);
755         let included_minor = minor(included_dev);
756         mount_include.insert(included_dev, std::fs::canonicalize(test_base_dir).unwrap());
757         let mut mount_exclude = HashSet::new();
758         mount_exclude.insert(0);
759 
760         let mut mem_tracer = MemTraceSubsystem {
761             device_inode_map: HashMap::new(),
762             inode_count: 0,
763             records: vec![],
764             regex: TraceLineInfo::get_trace_line_regex().unwrap(),
765             mount_info: MountInfo {
766                 included_devices: mount_include,
767                 excluded_devices: mount_exclude,
768             },
769             tracer_configs: None,
770             page_size: page_size().unwrap() as u64,
771             debug_info: DebugInfo {
772                 missing_files: HashMap::new(),
773                 directory_read_bytes: 0,
774                 missing_path_bytes: 0,
775                 privileged_paths: vec![],
776             },
777         };
778 
779         let pg_size = page_size().unwrap();
780         // Format is major, minor, inode, offset
781         let inputs = vec![
782             (0, 0, 2, 10), // to be excluded. bad device.
783             (included_major, included_minor, included_inode1, 0),
784             (included_major, included_minor, included_inode1, 3 * pg_size),
785             // duplicate read
786             (included_major, included_minor, included_inode1, 3 * pg_size),
787             (0, 0, included_inode1, 10), // to be excluded. bad device.
788             (included_major, included_minor, included_inode1, 2 * pg_size), // contiguous
789             // non-contiguous
790             (included_major, included_minor, included_inode1, 12 * pg_size),
791             // same offset different inode
792             (included_major, included_minor, included_inode2, 3 * pg_size),
793             // Contiguous offset different inode
794             (included_major, included_minor, included_inode2, pg_size),
795         ];
796 
797         for (i, (major, minor, inode, offset)) in inputs.iter().enumerate() {
798             // used to timestamp the log line.
799             let seconds = i;
800             // used to timestamp the log line.
801             let microseconds = i;
802             for operation in &["mm_filemap_add_to_page_cache", "some_other_operation"] {
803                 let line = format!(
804                     " BOX_ENTRY_ADDED-3071    [001] ....   {}.{}: {}: \
805                     dev {}:{} ino {:x} page=00000000f936540b pfn=60952 ofs={}",
806                     seconds, microseconds, operation, major, minor, inode, offset
807                 );
808                 mem_tracer.add_line(&line).unwrap();
809             }
810         }
811         assert_eq!(mem_tracer.records.len(), 7);
812         assert_eq!(mem_tracer.device_inode_map.len(), 1);
813         assert_eq!(mem_tracer.device_inode_map.get(&included_dev).unwrap().len(), 2);
814         assert!(mem_tracer
815             .device_inode_map
816             .get(&included_dev)
817             .unwrap()
818             .contains_key(&included_inode1));
819         assert!(mem_tracer
820             .device_inode_map
821             .get(&included_dev)
822             .unwrap()
823             .contains_key(&included_inode2));
824     }
825 
new_record(file: u64, offset: u64, length: u64, timestamp: u64) -> Record826     fn new_record(file: u64, offset: u64, length: u64, timestamp: u64) -> Record {
827         Record { file_id: FileId(file), offset, length, timestamp }
828     }
829 
830     #[test]
test_get_records_file()831     fn test_get_records_file() {
832         let test_base_dir = setup_test_dir();
833         let (rf, mut files) =
834             generate_cached_files_and_record(None, true, Some(page_size().unwrap() as u64));
835         let (_uncached_rf, uncached_files) =
836             copy_uncached_files_and_record_from(Path::new(&test_base_dir), &mut files, &rf);
837         let mut mount_include = HashMap::new();
838 
839         let included_dev = uncached_files.get(0).unwrap().0.metadata().unwrap().dev();
840         let included_inode1 = uncached_files.get(0).unwrap().0.metadata().unwrap().ino();
841         let included_inode2 = uncached_files.get(1).unwrap().0.metadata().unwrap().ino();
842         let included_major = major(included_dev);
843         let included_minor = minor(included_dev);
844         mount_include.insert(included_dev, std::fs::canonicalize(test_base_dir).unwrap());
845         let mut mount_exclude = HashSet::new();
846         mount_exclude.insert(0);
847 
848         let mut mem_tracer = MemTraceSubsystem {
849             device_inode_map: HashMap::new(),
850             inode_count: 0,
851             records: vec![],
852             regex: TraceLineInfo::get_trace_line_regex().unwrap(),
853             mount_info: MountInfo {
854                 included_devices: mount_include,
855                 excluded_devices: mount_exclude,
856             },
857             tracer_configs: None,
858             page_size: page_size().unwrap() as u64,
859             debug_info: DebugInfo {
860                 missing_files: HashMap::new(),
861                 directory_read_bytes: 0,
862                 missing_path_bytes: 0,
863                 privileged_paths: vec![],
864             },
865         };
866 
867         let pg_size = page_size().unwrap() as u64;
868         // Format is major, minor, inode, offset
869         let inputs = vec![
870             (0, 0, 2, 10), // to be excluded. bad device.
871             (included_major, included_minor, included_inode1, 0),
872             (included_major, included_minor, included_inode1, 3 * pg_size),
873             // duplicate read
874             (included_major, included_minor, included_inode1, 3 * pg_size),
875             (0, 0, included_inode1, 10), // to be excluded. bad device.
876             (included_major, included_minor, included_inode1, 2 * pg_size), // contiguous
877             // non-contiguous
878             (included_major, included_minor, included_inode1, 12 * pg_size),
879             // same offset different inode
880             (included_major, included_minor, included_inode2, 3 * pg_size),
881             // Contiguous offset different inode
882             (included_major, included_minor, included_inode2, pg_size),
883         ];
884 
885         for (i, (major, minor, inode, offset)) in inputs.iter().enumerate() {
886             // used to timestamp the log line.
887             let seconds = i;
888             // used to timestamp the log line.
889             let microseconds = i;
890             for operation in &["mm_filemap_add_to_page_cache", "some_other_operation"] {
891                 let line = format!(
892                     " BOX_ENTRY_ADDED-3071    [001] ....   {}.{}: {}: \
893                     dev {}:{} ino {:x} page=00000000f936540b pfn=60952 ofs={}",
894                     seconds, microseconds, operation, major, minor, inode, offset
895                 );
896                 mem_tracer.add_line(&line).unwrap();
897             }
898         }
899         let rf = mem_tracer.build_records_file().unwrap();
900         assert_eq!(
901             rf.inner.records,
902             vec![
903                 new_record(0, 0, pg_size, 1000001000),
904                 new_record(0, 2 * pg_size, 2 * pg_size, 2000002000),
905                 new_record(0, 12 * pg_size, pg_size, 6000006000),
906                 new_record(1, pg_size, pg_size, 8000008000),
907                 new_record(1, 3 * pg_size, pg_size, 7000007000),
908             ]
909         );
910     }
911 }
912