Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a0c570bf authored by Soichiro Fujii's avatar Soichiro Fujii
Browse files

libprefetch: replay/record checks if they can run on its own

Currently, prefetch `record` or `replay` command is executed
when the respective property is set, which is done by `start`
command. However, due to the timing of which property trigger
gets executed, this results in prefetch not starting neither
`record` or `replay` until the very end of boot phase.

Instead of going through `start` to determine which command
to run using property, each command will check whether they
can execute on their own. this way we can tweak exactly when
we want to run each commands.

Bug: 380766679
Test: Builds
Test: On cuttlefish, observe replay correctly exiting when
replay condition is not met.
Test: On cuttlefish, observe record correctly exiting when
record condition is not met.

Change-Id: Iad926450f00984d703f72c9c7b1876a0b3d3ecfd
parent efe22769
Loading
Loading
Loading
Loading
+25 −17
Original line number Diff line number Diff line
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:prefetch_boot.record=true
    start prefetch_record
# Reads data from disk in advance and populates page cache
# to speed up subsequent disk access.
#
# Record:
#   start by `start prefetch_record` at appropriate timing.
#   stop by setting `prefetch_boot.record_stop` to 1.
#   set --duration to only capture for a certain duration instead.
#
# Replay:
#   start by `start prefetch_replay` at appropriate timing.
#   it will depend on several files generated from record.
#
#   replay is I/O intensive. make sure you pick appropriate
#   timing to run each, so that you can maximize the page cache
#   hit for subsequent disk access.
#
# Example:
#   on early-init && property:ro.prefetch_boot.enabled=true
#     start prefetch_replay
#
#   on init && property:ro.prefetch_boot.enabled=true
#     start prefetch_record
#
#   on property:sys.boot_completed=1 && property:ro.prefetch_boot.enabled=true
#     setprop prefetch_boot.record_stop 1

service prefetch_record /system/bin/prefetch record --duration ${ro.prefetch_boot.duration_s:-0}
    class main
    user root
    group root system
    disabled
    oneshot

on property: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}
    class main
    user root
    group root system
    disabled
+38 −0
Original line number Diff line number Diff line
@@ -6,6 +6,7 @@ use log::warn;
use std::fs::File;
use std::fs::OpenOptions;
use std::io::Write;
use std::path::Path;
use std::time::Duration;

use rustutils::system_properties::error::PropertyWatcherError;
@@ -41,6 +42,43 @@ fn start_prefetch_service(property_name: &str) -> Result<(), Error> {
    Ok(())
}

/// Checks if we can perform replay phase.
/// Ensure that the pack file exists and is up-to-date, returns false otherwise.
pub fn can_perform_replay(pack_path: &Path, fingerprint_path: &Path) -> Result<bool, Error> {
    if !pack_path.exists() || !fingerprint_path.exists() {
        return Ok(false);
    }

    let saved_fingerprint = std::fs::read_to_string(fingerprint_path)?;

    let current_device_fingerprint = rustutils::system_properties::read("ro.build.fingerprint")
        .map_err(|e| Error::Custom {
            error: format!("Failed to read ro.build.fingerprint: {}", e),
        })?;

    Ok(current_device_fingerprint.is_some_and(|fp| fp == saved_fingerprint.trim()))
}

/// Checks if we can perform record phase.
/// Ensure that following conditions hold:
///   - File specified in ready_path exists. otherwise, create a new file and return false.
///   - can_perform_replay is false.
pub fn ensure_record_is_ready(
    ready_path: &Path,
    pack_path: &Path,
    fingerprint_path: &Path,
) -> Result<bool, Error> {
    if !ready_path.exists() {
        File::create(ready_path)
            .map_err(|_| Error::Custom { error: "File Creation failed".to_string() })?;

        return Ok(false);
    }

    let can_replay = can_perform_replay(pack_path, fingerprint_path)?;
    Ok(!can_replay)
}

/// Start prefetch service
///
/// 1: Check the presence of the file 'prefetch_ready'. If it doesn't
+12 −0
Original line number Diff line number Diff line
@@ -147,6 +147,13 @@ pub struct RecordArgs {
    /// store build_finger_print to tie the pack format
    #[argh(option, default = "default_build_finger_print_path()")]
    pub build_fingerprint_path: PathBuf,

    #[cfg(target_os = "android")]
    /// 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 ready_path: PathBuf,
}

/// Type of tracing subsystem to use.
@@ -204,6 +211,11 @@ pub struct ReplayArgs {
    /// file path from where the prefetch config file will be read
    #[argh(option, default = "PathBuf::new()")]
    pub config_path: PathBuf,

    #[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,
}

/// dump records file in given format
+14 −0
Original line number Diff line number Diff line
@@ -59,6 +59,13 @@ pub use arch::android::*;

/// Records prefetch data for the given configuration
pub fn record(args: &RecordArgs) -> Result<(), Error> {
    #[cfg(target_os = "android")]
    if !ensure_record_is_ready(&args.ready_path, &args.path, &args.build_fingerprint_path)? {
        info!("Cannot perform record -- skipping");
        return Ok(());
    }

    info!("Starting record.");
    let (mut tracer, exit_tx) = tracer::Tracer::create(
        args.trace_buffer_size_kib,
        args.tracing_subsystem.clone(),
@@ -109,6 +116,13 @@ pub fn record(args: &RecordArgs) -> Result<(), Error> {

/// Replays prefetch data for the given configuration
pub fn replay(args: &ReplayArgs) -> Result<(), Error> {
    #[cfg(target_os = "android")]
    if !can_perform_replay(&args.path, &args.build_fingerprint_path)? {
        info!("Cannot perform replay -- exiting.");
        return Ok(());
    }

    info!("Starting replay.");
    let replay = Replay::new(args)?;
    replay.replay()
}