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 //! A library to prefetch files on the file system to optimize startup times
16 //!
17
18 mod args;
19 mod error;
20 mod format;
21 mod replay;
22 mod tracer;
23 #[cfg(target_os = "android")]
24 mod arch {
25 pub mod android;
26 }
27
28 use std::fs::File;
29 use std::fs::OpenOptions;
30 use std::io;
31 use std::io::Write;
32 use std::os::unix::fs::PermissionsExt;
33 use std::string::ToString;
34 use std::thread;
35 use std::time::Duration;
36
37 #[cfg(target_os = "android")]
38 use log::Level;
39 #[cfg(target_os = "linux")]
40 use log::LevelFilter;
41
42 pub use args::args_from_env;
43 use args::OutputFormat;
44 pub use args::ReplayArgs;
45 pub use args::{DumpArgs, MainArgs, RecordArgs, SubCommands};
46 pub use error::Error;
47 pub use format::FileId;
48 pub use format::InodeInfo;
49 pub use format::Record;
50 pub use format::RecordsFile;
51 use log::info;
52 pub use replay::Replay;
53 pub use tracer::nanoseconds_since_boot;
54
55 #[cfg(target_os = "android")]
56 pub use arch::android::*;
57
58 /// Records prefetch data for the given configuration
record(args: &RecordArgs) -> Result<(), Error>59 pub fn record(args: &RecordArgs) -> Result<(), Error> {
60 #[cfg(target_os = "android")]
61 if !ensure_record_is_ready(&args.ready_path, &args.path, &args.build_fingerprint_path)? {
62 info!("Cannot perform record -- skipping");
63 return Ok(());
64 }
65
66 info!("Starting record.");
67 let (mut tracer, exit_tx) = tracer::Tracer::create(
68 args.trace_buffer_size_kib,
69 args.tracing_subsystem.clone(),
70 args.tracing_instance.clone(),
71 args.setup_tracing,
72 )?;
73 let duration = Duration::from_secs(args.duration as u64);
74
75 let thd = thread::spawn(move || {
76 if !duration.is_zero() {
77 info!("Record start - waiting for duration: {:?}", duration);
78 thread::sleep(duration);
79 } else {
80 #[cfg(target_os = "android")]
81 wait_for_record_stop();
82 }
83
84 info!("Prefetch record exiting");
85 // We want to unwrap here on failure to send this signal. Otherwise
86 // tracer will continue generating huge records data.
87 exit_tx.send(()).unwrap();
88 });
89
90 let mut rf = tracer.trace(args.int_path.as_ref())?;
91 thd.join()
92 .map_err(|_| Error::ThreadPool { error: "Failed to join timeout thread".to_string() })?;
93
94 let mut out_file =
95 OpenOptions::new().write(true).create(true).truncate(true).open(&args.path).map_err(
96 |source| Error::Create { source, path: args.path.to_str().unwrap().to_owned() },
97 )?;
98
99 std::fs::set_permissions(&args.path, std::fs::Permissions::from_mode(0o644))
100 .map_err(|source| Error::Create { source, path: args.path.to_str().unwrap().to_owned() })?;
101
102 // Write the record file
103 out_file
104 .write_all(&rf.add_checksum_and_serialize()?)
105 .map_err(|source| Error::Write { path: args.path.to_str().unwrap().to_owned(), source })?;
106 out_file.sync_all()?;
107
108 // Write build-finger-print file
109 #[cfg(target_os = "android")]
110 write_build_fingerprint(args)?;
111
112 Ok(())
113 }
114
115 /// Replays prefetch data for the given configuration
replay(args: &ReplayArgs) -> Result<(), Error>116 pub fn replay(args: &ReplayArgs) -> Result<(), Error> {
117 #[cfg(target_os = "android")]
118 if !can_perform_replay(&args.path, &args.build_fingerprint_path)? {
119 info!("Cannot perform replay -- exiting.");
120 return Ok(());
121 }
122
123 info!("Starting replay.");
124 let replay = Replay::new(args)?;
125 replay.replay()
126 }
127
128 /// Dumps prefetch data in the human readable form
dump(args: &DumpArgs) -> Result<(), Error>129 pub fn dump(args: &DumpArgs) -> Result<(), Error> {
130 let reader = File::open(&args.path)
131 .map_err(|source| Error::Open { source, path: args.path.to_str().unwrap().to_string() })?;
132 let rf: RecordsFile =
133 serde_cbor::from_reader(reader).map_err(|e| Error::Deserialize { error: e.to_string() })?;
134 match args.format {
135 OutputFormat::Json => println!(
136 "{:#}",
137 serde_json::to_string_pretty(&rf)
138 .map_err(|e| Error::Serialize { error: e.to_string() })?
139 ),
140 OutputFormat::Csv => rf.serialize_records_to_csv(&mut io::stdout())?,
141 }
142 Ok(())
143 }
144
145 /// An alias of android_logger::Level to use log level across android and linux.
146 #[cfg(target_os = "android")]
147 pub type LogLevel = Level;
148
149 /// An alias of log::LevelFilter to use log level across android and linux.
150 #[cfg(not(target_os = "android"))]
151 pub type LogLevel = LevelFilter;
152
153 /// Convenience logging initializer that is shared between the prefetch tool and c wrapper library
154 #[cfg(target_os = "android")]
init_logging(_level: LogLevel)155 pub fn init_logging(_level: LogLevel) {
156 android_logger::init_once(
157 android_logger::Config::default().with_max_level(log::LevelFilter::Info).format(
158 |f, record| {
159 write!(
160 f,
161 "{} prefetch_rs: {}:{} {}: {}",
162 nanoseconds_since_boot(),
163 record.file().unwrap_or("unknown_file"),
164 record.line().unwrap_or(0),
165 record.level(),
166 record.args()
167 )
168 },
169 ),
170 )
171 }
172
173 /// Convenience logging initializer that is shared between the prefetch tool and c wrapper library
174 #[cfg(target_os = "linux")]
init_logging(level: LogLevel)175 pub fn init_logging(level: LogLevel) {
176 let mut builder = env_logger::Builder::from_default_env();
177
178 builder
179 .filter(None, level)
180 .format(|buf, record| {
181 writeln!(
182 buf,
183 "{} prefetch_rs: {}:{} {}: {}",
184 nanoseconds_since_boot(),
185 record.file().unwrap_or("unknown_file"),
186 record.line().unwrap_or(0),
187 record.level(),
188 record.args()
189 )
190 })
191 .init();
192 }
193