libprefetch: Start prefetch service based on build

1: Check the presence of the file 'prefetch_ready'. If it doesn't
 exist then the device is booting for the first time after wipe.
 Thus, we would just create the file and exit as we do not want
 to initiate the record after data wipe primiarly because boot
 after data wipe is long and the I/O pattern during first boot may not actually match
 with subsequent boot.

2: If the file 'prefetch_ready' is present:

   a: Compare the build-finger-print of the device with the one record format
   is associated with by reading the file 'build_finger_print'. If they match,
   start the prefetch_replay.

   b: If they don't match, then the device was updated through OTA. Hence, start
   a fresh record and delete the build-finger-print file. This should also cover
   the case of device rollback.

   c: If the build-finger-print file doesn't exist, then just restart the record
   from scratch.

Bug: 362507272
Test: Prefetch record/replay
Change-Id: I90b861ba9381ddba6ab7dedb9930a735e55b0e5d
Signed-off-by: Akilesh Kailash <akailash@google.com>
This commit is contained in:
Akilesh Kailash 2024-11-20 15:14:29 -08:00
parent e400d09c83
commit ef3a2c05fe
6 changed files with 195 additions and 22 deletions

View file

@ -1,3 +1,16 @@
on init && property:ro.prefetch_boot.enabled=true
start prefetch
service prefetch /system/bin/prefetch start
class main
user root
group root system
disabled
oneshot
on property:ro.prefetch_boot.record=true
start prefetch_record
service prefetch_record /system/bin/prefetch record --duration ${ro.prefetch_boot.duration_s:-0} service prefetch_record /system/bin/prefetch record --duration ${ro.prefetch_boot.duration_s:-0}
class main class main
user root user root
@ -5,6 +18,9 @@ service prefetch_record /system/bin/prefetch record --duration ${ro.prefetch_boo
disabled disabled
oneshot oneshot
on property:ro.prefetch_boot.replay=true
start prefetch_replay
service prefetch_replay /system/bin/prefetch replay --io-depth ${ro.prefetch_boot.io_depth:-2} --max-fds ${ro.prefetch_boot.max_fds:-128} service prefetch_replay /system/bin/prefetch replay --io-depth ${ro.prefetch_boot.io_depth:-2} --max-fds ${ro.prefetch_boot.max_fds:-128}
class main class main
user root user root

View file

@ -0,0 +1,118 @@
use crate::Error;
use crate::RecordArgs;
use crate::StartArgs;
use log::info;
use log::warn;
use std::fs::File;
use std::fs::OpenOptions;
use std::io::Write;
use std::time::Duration;
use rustutils::system_properties::error::PropertyWatcherError;
use rustutils::system_properties::PropertyWatcher;
const PREFETCH_RECORD_PROPERTY: &str = "ro.prefetch_boot.record";
const PREFETCH_REPLAY_PROPERTY: &str = "ro.prefetch_boot.replay";
const PREFETCH_RECORD_PROPERTY_STOP: &str = "ro.prefetch_boot.record_stop";
fn wait_for_property_true(
property_name: &str,
timeout: Option<Duration>,
) -> Result<(), PropertyWatcherError> {
let mut prop = PropertyWatcher::new(property_name)?;
prop.wait_for_value("1", timeout)?;
Ok(())
}
/// Wait for record to stop
pub fn wait_for_record_stop() {
wait_for_property_true(PREFETCH_RECORD_PROPERTY_STOP, None).unwrap_or_else(|e| {
warn!("failed to wait for {} with error: {}", PREFETCH_RECORD_PROPERTY_STOP, e)
});
}
fn start_prefetch_service(property_name: &str) -> Result<(), Error> {
match rustutils::system_properties::write(property_name, "true") {
Ok(_) => {}
Err(_) => {
return Err(Error::Custom { error: "Failed to start prefetch service".to_string() });
}
}
Ok(())
}
/// Start prefetch service
///
/// 1: Check the presence of the file 'prefetch_ready'. If it doesn't
/// exist then the device is booting for the first time after wipe.
/// Thus, we would just create the file and exit as we do not want
/// to initiate the record after data wipe primiarly because boot
/// after data wipe is long and the I/O pattern during first boot may not actually match
/// with subsequent boot.
///
/// 2: If the file 'prefetch_ready' is present:
///
/// a: Compare the build-finger-print of the device with the one record format
/// is associated with by reading the file 'build_finger_print'. If they match,
/// start the prefetch_replay.
///
/// b: If they don't match, then the device was updated through OTA. Hence, start
/// a fresh record and delete the build-finger-print file. This should also cover
/// the case of device rollback.
///
/// c: If the build-finger-print file doesn't exist, then just restart the record
/// from scratch.
pub fn start_prefetch(args: &StartArgs) -> Result<(), Error> {
if !args.path.exists() {
match File::create(args.path.clone()) {
Ok(_) => {}
Err(_) => {
return Err(Error::Custom { error: "File Creation failed".to_string() });
}
}
return Ok(());
}
if args.build_fingerprint_path.exists() {
let device_build_fingerprint = rustutils::system_properties::read("ro.build.fingerprint")
.map_err(|e| Error::Custom {
error: format!("Failed to read ro.build.fingerprint: {}", e),
})?;
let pack_build_fingerprint = std::fs::read_to_string(&args.build_fingerprint_path)?;
if pack_build_fingerprint.trim() == device_build_fingerprint.as_deref().unwrap_or_default()
{
info!("Start replay");
start_prefetch_service(PREFETCH_REPLAY_PROPERTY)?;
} else {
info!("Start record");
std::fs::remove_file(&args.build_fingerprint_path)?;
start_prefetch_service(PREFETCH_RECORD_PROPERTY)?;
}
} else {
info!("Start record");
start_prefetch_service(PREFETCH_RECORD_PROPERTY)?;
}
Ok(())
}
/// Write build finger print to associate prefetch pack file
pub fn write_build_fingerprint(args: &RecordArgs) -> Result<(), Error> {
let mut build_fingerprint_file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(&args.build_fingerprint_path)
.map_err(|source| Error::Create {
source,
path: args.build_fingerprint_path.to_str().unwrap().to_owned(),
})?;
let device_build_fingerprint =
rustutils::system_properties::read("ro.build.fingerprint").unwrap_or_default();
let device_build_fingerprint = device_build_fingerprint.unwrap_or_default();
build_fingerprint_file.write_all(device_build_fingerprint.as_bytes())?;
build_fingerprint_file.sync_all()?;
Ok(())
}

View file

@ -25,6 +25,8 @@ use std::process::exit;
pub use args_internal::OutputFormat; pub use args_internal::OutputFormat;
pub use args_internal::ReplayArgs; pub use args_internal::ReplayArgs;
#[cfg(target_os = "android")]
pub use args_internal::StartArgs;
pub use args_internal::TracerType; pub use args_internal::TracerType;
pub use args_internal::{DumpArgs, MainArgs, RecordArgs, SubCommands}; pub use args_internal::{DumpArgs, MainArgs, RecordArgs, SubCommands};
use serde::Deserialize; use serde::Deserialize;
@ -66,6 +68,8 @@ fn verify_and_fix(args: &mut MainArgs) -> Result<(), Error> {
SubCommands::Dump(arg) => { SubCommands::Dump(arg) => {
ensure_path_exists(&arg.path)?; ensure_path_exists(&arg.path)?;
} }
#[cfg(target_os = "android")]
SubCommands::Start(_arg) => return Ok(()),
} }
Ok(()) Ok(())
} }

View file

@ -40,6 +40,38 @@ pub enum SubCommands {
Replay(ReplayArgs), Replay(ReplayArgs),
/// Dump prefetch data in human readable format /// Dump prefetch data in human readable format
Dump(DumpArgs), Dump(DumpArgs),
/// Start prefetch service if possible
/// If the pack file is present, then prefetch replay is started
/// If the pack file is absent or if the build fingerprint
/// of the current pack file is different, then prefetch record is started.
#[cfg(target_os = "android")]
Start(StartArgs),
}
#[cfg(target_os = "android")]
fn default_ready_path() -> PathBuf {
PathBuf::from("/metadata/prefetch/prefetch_ready")
}
#[cfg(target_os = "android")]
fn default_build_finger_print_path() -> PathBuf {
PathBuf::from("/metadata/prefetch/build_finger_print")
}
#[cfg(target_os = "android")]
#[derive(Eq, PartialEq, Debug, Default, FromArgs)]
/// Start prefetch service based on if pack file is present.
#[argh(subcommand, name = "start")]
pub struct StartArgs {
/// file path to check if prefetch_ready is present.
///
/// A new file is created at the given path if it's not present.
#[argh(option, default = "default_ready_path()")]
pub path: PathBuf,
/// file path where build fingerprint is stored
#[argh(option, default = "default_build_finger_print_path()")]
pub build_fingerprint_path: PathBuf,
} }
impl Default for SubCommands { impl Default for SubCommands {
@ -110,6 +142,11 @@ pub struct RecordArgs {
from_str_fn(parse_tracing_instance) from_str_fn(parse_tracing_instance)
)] )]
pub tracing_instance: Option<String>, pub tracing_instance: Option<String>,
#[cfg(target_os = "android")]
/// store build_finger_print to tie the pack format
#[argh(option, default = "default_build_finger_print_path()")]
pub build_fingerprint_path: PathBuf,
} }
/// Type of tracing subsystem to use. /// Type of tracing subsystem to use.

View file

@ -20,6 +20,10 @@ mod error;
mod format; mod format;
mod replay; mod replay;
mod tracer; mod tracer;
#[cfg(target_os = "android")]
mod arch {
pub mod android;
}
use std::fs::File; use std::fs::File;
use std::fs::OpenOptions; use std::fs::OpenOptions;
@ -38,6 +42,8 @@ use log::LevelFilter;
pub use args::args_from_env; pub use args::args_from_env;
use args::OutputFormat; use args::OutputFormat;
pub use args::ReplayArgs; pub use args::ReplayArgs;
#[cfg(target_os = "android")]
pub use args::StartArgs;
pub use args::{DumpArgs, MainArgs, RecordArgs, SubCommands}; pub use args::{DumpArgs, MainArgs, RecordArgs, SubCommands};
pub use error::Error; pub use error::Error;
pub use format::FileId; pub use format::FileId;
@ -45,29 +51,11 @@ pub use format::InodeInfo;
pub use format::Record; pub use format::Record;
pub use format::RecordsFile; pub use format::RecordsFile;
use log::info; use log::info;
#[cfg(target_os = "android")]
use log::warn;
pub use replay::Replay; pub use replay::Replay;
pub use tracer::nanoseconds_since_boot; pub use tracer::nanoseconds_since_boot;
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
use rustutils::system_properties; pub use arch::android::*;
#[cfg(target_os = "android")]
use rustutils::system_properties::error::PropertyWatcherError;
#[cfg(target_os = "android")]
use rustutils::system_properties::PropertyWatcher;
#[cfg(target_os = "android")]
fn wait_for_property_true(property_name: &str) -> Result<(), PropertyWatcherError> {
let mut prop = PropertyWatcher::new(property_name)?;
loop {
prop.wait(None)?;
if system_properties::read_bool(property_name, false)? {
break;
}
}
Ok(())
}
/// Records prefetch data for the given configuration /// Records prefetch data for the given configuration
pub fn record(args: &RecordArgs) -> Result<(), Error> { pub fn record(args: &RecordArgs) -> Result<(), Error> {
@ -85,11 +73,10 @@ pub fn record(args: &RecordArgs) -> Result<(), Error> {
thread::sleep(duration); thread::sleep(duration);
} else { } else {
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
wait_for_property_true("sys.boot_completed").unwrap_or_else(|e| { wait_for_record_stop();
warn!("failed to wait for sys.boot_completed with error: {}", e)
});
} }
info!("Prefetch record exiting");
// We want to unwrap here on failure to send this signal. Otherwise // We want to unwrap here on failure to send this signal. Otherwise
// tracer will continue generating huge records data. // tracer will continue generating huge records data.
exit_tx.send(()).unwrap(); exit_tx.send(()).unwrap();
@ -107,9 +94,16 @@ pub fn record(args: &RecordArgs) -> Result<(), Error> {
std::fs::set_permissions(&args.path, std::fs::Permissions::from_mode(0o644)) std::fs::set_permissions(&args.path, std::fs::Permissions::from_mode(0o644))
.map_err(|source| Error::Create { source, path: args.path.to_str().unwrap().to_owned() })?; .map_err(|source| Error::Create { source, path: args.path.to_str().unwrap().to_owned() })?;
// Write the record file
out_file out_file
.write_all(&rf.add_checksum_and_serialize()?) .write_all(&rf.add_checksum_and_serialize()?)
.map_err(|source| Error::Write { path: args.path.to_str().unwrap().to_owned(), source })?; .map_err(|source| Error::Write { path: args.path.to_str().unwrap().to_owned(), source })?;
out_file.sync_all()?;
// Write build-finger-print file
#[cfg(target_os = "android")]
write_build_fingerprint(args)?;
Ok(()) Ok(())
} }

View file

@ -22,6 +22,8 @@ use prefetch_rs::dump;
use prefetch_rs::init_logging; use prefetch_rs::init_logging;
use prefetch_rs::record; use prefetch_rs::record;
use prefetch_rs::replay; use prefetch_rs::replay;
#[cfg(target_os = "android")]
use prefetch_rs::start_prefetch;
use prefetch_rs::LogLevel; use prefetch_rs::LogLevel;
use prefetch_rs::MainArgs; use prefetch_rs::MainArgs;
use prefetch_rs::SubCommands; use prefetch_rs::SubCommands;
@ -33,6 +35,8 @@ fn main() {
SubCommands::Record(args) => record(args), SubCommands::Record(args) => record(args),
SubCommands::Replay(args) => replay(args), SubCommands::Replay(args) => replay(args),
SubCommands::Dump(args) => dump(args), SubCommands::Dump(args) => dump(args),
#[cfg(target_os = "android")]
SubCommands::Start(args) => start_prefetch(args),
}; };
if let Err(err) = ret { if let Err(err) = ret {