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