1 // Copyright 2022 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #![cfg(feature = "log_page_fault")]
6 #![deny(missing_docs)]
7
8 use std::fs::File;
9 use std::fs::OpenOptions;
10 use std::io::Write;
11 use std::path::Path;
12 use std::time::Instant;
13 use std::time::SystemTime;
14 use std::time::UNIX_EPOCH;
15
16 use anyhow::Context;
17 use base::info;
18 use base::AsRawDescriptor;
19 use base::RawDescriptor;
20 use serde::Deserialize;
21 use serde::Serialize;
22 use vm_memory::GuestMemory;
23 use vm_memory::MemoryRegionInformation;
24
25 /// Logs page fault events into a log file in json format.
26 pub struct PageFaultEventLogger {
27 file: File,
28 base_time: Instant,
29 }
30
31 impl PageFaultEventLogger {
32 /// Creates a log file named "page_fault.log" in the `swap_dir` and logs the initial log.
33 ///
34 /// initial log contains the regions information and base time.
35 ///
36 /// # Arguments
37 ///
38 /// * `swap_dir` - directory to store the log file.
39 /// * `guest_memory` - [GuestMemory] containing regions info.
create(swap_dir: &Path, guest_memory: &GuestMemory) -> anyhow::Result<Self>40 pub fn create(swap_dir: &Path, guest_memory: &GuestMemory) -> anyhow::Result<Self> {
41 let file_path = swap_dir.join("page_fault.log");
42 let file = OpenOptions::new()
43 .read(true)
44 .write(true)
45 .create(true)
46 .truncate(true)
47 .open(&file_path)
48 .context("open pagefault event log file")?;
49 let regions = regions_from_guest_memory(guest_memory);
50 let base_time = Instant::now();
51 serde_json::to_writer(
52 &file,
53 &PageFaultInitialLog {
54 base_timestamp: SystemTime::now().duration_since(UNIX_EPOCH)?.as_millis(),
55 regions,
56 },
57 )
58 .context("log initial event")?;
59 let mut logger = Self { file, base_time };
60 logger.line_break();
61 info!("start logging page faults at {:?}", file_path);
62 Ok(logger)
63 }
64
65 /// Logs a page fault event.
66 ///
67 /// # Arguments
68 ///
69 /// * `address` - the address page fault occured.
log_page_fault(&mut self, address: usize, id_uffd: u32)70 pub fn log_page_fault(&mut self, address: usize, id_uffd: u32) {
71 // it is not optimized (e.g. buffered io). but it is fine because this logger is for debug
72 // purpose only.
73 let _ = serde_json::to_writer(
74 &self.file,
75 &PageFaultEventLog {
76 elapsed_nanos: self.base_time.elapsed().as_nanos(),
77 address,
78 id_uffd,
79 },
80 );
81 self.line_break();
82 }
83
line_break(&mut self)84 fn line_break(&mut self) {
85 const LINE_BREAK: &[u8] = &[b'\n'];
86 let _ = self.file.write(LINE_BREAK);
87 }
88 }
89
90 impl AsRawDescriptor for PageFaultEventLogger {
as_raw_descriptor(&self) -> RawDescriptor91 fn as_raw_descriptor(&self) -> RawDescriptor {
92 self.file.as_raw_descriptor()
93 }
94 }
95
96 #[derive(Serialize, Deserialize, Debug)]
97 struct PageFaultInitialLog {
98 base_timestamp: u128,
99 regions: Vec<MemoryRegion>,
100 }
101
regions_from_guest_memory(guest_memory: &GuestMemory) -> Vec<MemoryRegion>102 fn regions_from_guest_memory(guest_memory: &GuestMemory) -> Vec<MemoryRegion> {
103 let mut regions = Vec::new();
104 guest_memory
105 .with_regions::<_, ()>(
106 |MemoryRegionInformation {
107 len, base_address, ..
108 }| {
109 regions.push(MemoryRegion { base_address, len });
110 Ok(())
111 },
112 )
113 .unwrap(); // the call back never return error.
114 regions
115 }
116
117 #[derive(Serialize, Deserialize, Debug)]
118 struct MemoryRegion {
119 base_address: usize,
120 len: usize,
121 }
122
123 #[derive(Serialize, Deserialize, Debug)]
124 struct PageFaultEventLog {
125 elapsed_nanos: u128,
126 address: usize,
127 id_uffd: u32,
128 }
129