axbuild 0.4.21

An OS build lib toolkit used by arceos
use anyhow::bail;
use ostool::build::config::Cargo;

use super::super::{ArgsPerf, PerfCallchain, PerfFormat};

pub(super) fn apply_perf_cargo_features(cargo: &mut Cargo, args: &ArgsPerf) {
    // `starry perf` is an explicit command selection. The package owns the complete feature
    // preset so axbuild only selects that declared command capability.
    cargo.features.push("qperf".to_string());
    if args.qperf_metrics {
        cargo.features.push("qperf-metrics".to_string());
    }
    cargo.features.sort();
    cargo.features.dedup();
    if perf_needs_debuginfo(args) {
        cargo.env.insert("DWARF".to_string(), "y".to_string());
    }
    if perf_needs_frame_pointers(args) {
        cargo.env.insert("BACKTRACE".to_string(), "y".to_string());
    }
    apply_perf_rustflags(cargo, args);
}

fn apply_perf_rustflags(cargo: &mut Cargo, args: &ArgsPerf) {
    let mut flags = Vec::new();
    if perf_needs_debuginfo(args) {
        flags.push("-Cdebuginfo=2".to_string());
        flags.push("-Cstrip=none".to_string());
    }
    if perf_needs_frame_pointers(args) {
        flags.push("-Cforce-frame-pointers=yes".to_string());
    }
    if flags.is_empty() {
        return;
    }

    cargo
        .env
        .insert("CARGO_ENCODED_RUSTFLAGS".to_string(), flags.join("\x1f"));
    cargo.args.push("--config".to_string());
    let rustflags = toml::Value::Array(flags.into_iter().map(toml::Value::String).collect());
    cargo
        .args
        .push(format!("target.'{}'.rustflags={rustflags}", cargo.target));
}

pub(super) fn validate_args(args: &ArgsPerf) -> anyhow::Result<()> {
    if args.freq == 0 {
        bail!("--freq must be greater than 0");
    }
    if args.max_depth == 0 {
        bail!("--max-depth must be greater than 0");
    }
    if args.min_percent < 0.0 {
        bail!("--min-percent must be non-negative");
    }
    if matches!(args.format, PerfFormat::Pprof) {
        bail!("--format pprof is not supported yet; use --format folded, svg, or all");
    }
    if args
        .shell_init_cmd
        .as_deref()
        .is_some_and(|cmd| cmd.trim().is_empty())
    {
        bail!("--shell-init-cmd must not be empty");
    }
    if args
        .shell_prefix
        .as_deref()
        .is_some_and(|prefix| prefix.is_empty())
    {
        bail!("--shell-prefix must not be empty");
    }
    if args.host_perf && args.host_perf_events.trim().is_empty() {
        bail!("--host-perf-events must not be empty when --host-perf is set");
    }
    if matches!(effective_callchain(args), PerfCallchain::Logical) {
        bail!(
            "--perf-callchain logical is not implemented yet; use --perf-callchain fp or \
             --full-stack for frame-pointer unwinding"
        );
    }
    if args.include_user_symbols {
        eprintln!(
            "qperf: --include-user-symbols requested, but current analyzer resolves only the \
             StarryOS kernel ELF; user symbols will remain unresolved unless they are present in \
             the kernel image"
        );
    }
    if args
        .start_marker
        .as_deref()
        .is_some_and(|marker| marker.trim().is_empty())
    {
        bail!("--start-marker must not be empty");
    }
    if args
        .stop_marker
        .as_deref()
        .is_some_and(|marker| marker.trim().is_empty())
    {
        bail!("--stop-marker must not be empty");
    }
    if args.workload_timeout == Some(0) {
        bail!("--workload-timeout must be greater than 0");
    }
    Ok(())
}

pub(super) fn host_time_enabled(args: &ArgsPerf) -> bool {
    args.host_time || !args.no_host_time
}

pub(super) fn flamegraph_min_percent(args: &ArgsPerf) -> f64 {
    if args.no_truncate {
        0.0
    } else {
        args.min_percent
    }
}

pub(super) fn effective_max_depth(args: &ArgsPerf) -> usize {
    if args.full_stack {
        args.max_depth.max(256)
    } else {
        args.max_depth
    }
}

pub(super) fn effective_callchain(args: &ArgsPerf) -> PerfCallchain {
    if args.full_stack {
        PerfCallchain::Fp
    } else {
        args.callchain.unwrap_or(PerfCallchain::Leaf)
    }
}

pub(super) fn perf_needs_debuginfo(args: &ArgsPerf) -> bool {
    args.full_stack || args.debuginfo
}

pub(super) fn perf_needs_frame_pointers(args: &ArgsPerf) -> bool {
    args.full_stack
        || args.force_frame_pointers
        || matches!(effective_callchain(args), PerfCallchain::Fp)
}