blob: a8d9723577e183cf9cb43e845e4169d36ca1c4d1 [file] [log] [blame]
//
// Copyright (C) 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//! ProfCollect configurations.
use anyhow::Result;
use macaddr::MacAddr6;
use rand::Rng;
use serde::{Deserialize, Serialize};
use std::error::Error;
use std::fs::{read_dir, remove_file};
use std::path::Path;
use std::process::Command;
use std::str::FromStr;
use std::sync::LazyLock;
use std::time::Duration;
const PROFCOLLECT_CONFIG_NAMESPACE: &str = "aconfig_flags.profcollect_native_boot";
const PROFCOLLECT_NODE_ID_PROPERTY: &str = "persist.profcollectd.node_id";
const DEFAULT_BINARY_FILTER: &str = "(^/(system|apex/.+|vendor)/(bin|lib64)/.+)|\
(^/data/app/.+\\.so$)|kernel.kallsyms";
pub const REPORT_RETENTION_SECS: u64 = 14 * 24 * 60 * 60; // 14 days.
// Static configs that cannot be changed.
pub static TRACE_OUTPUT_DIR: LazyLock<&'static Path> =
LazyLock::new(|| Path::new("/data/misc/profcollectd/trace/"));
pub static PROFILE_OUTPUT_DIR: LazyLock<&'static Path> =
LazyLock::new(|| Path::new("/data/misc/profcollectd/output/"));
pub static REPORT_OUTPUT_DIR: LazyLock<&'static Path> =
LazyLock::new(|| Path::new("/data/misc/profcollectd/report/"));
pub static CONFIG_FILE: LazyLock<&'static Path> =
LazyLock::new(|| Path::new("/data/misc/profcollectd/output/config.json"));
pub static LOG_FILE: LazyLock<&'static Path> =
LazyLock::new(|| Path::new("/data/misc/profcollectd/output/trace.log"));
/// Dynamic configs, stored in config.json.
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
pub struct Config {
/// Version of config file scheme, always equals to 1.
version: u32,
/// Application specific node ID.
pub node_id: MacAddr6,
/// Device build fingerprint.
pub build_fingerprint: String,
/// Interval between collections.
pub collection_interval: Duration,
/// An optional filter to limit which binaries to or not to profile.
pub binary_filter: String,
/// Maximum size of the trace directory.
pub max_trace_limit_mb: u64,
/// The kernel release version
pub kernel_release: String,
}
impl Config {
pub fn from_env() -> Result<Self> {
Ok(Config {
version: 1,
node_id: get_or_initialise_node_id()?,
build_fingerprint: get_build_fingerprint()?,
collection_interval: Duration::from_secs(get_device_config(
"collection_interval",
600,
)?),
binary_filter: get_device_config("binary_filter", DEFAULT_BINARY_FILTER.to_string())?,
max_trace_limit_mb: get_device_config("max_trace_limit_mb", 768)?,
kernel_release: get_kernel_release(),
})
}
}
impl std::fmt::Display for Config {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", serde_json::to_string(self).expect("Failed to deserialise configuration."))
}
}
impl FromStr for Config {
type Err = serde_json::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
serde_json::from_str::<Config>(s)
}
}
fn get_or_initialise_node_id() -> Result<MacAddr6> {
let mut node_id = get_property(PROFCOLLECT_NODE_ID_PROPERTY, MacAddr6::nil())?;
if node_id.is_nil() {
node_id = generate_random_node_id();
set_property(PROFCOLLECT_NODE_ID_PROPERTY, node_id)?;
}
Ok(node_id)
}
fn get_build_fingerprint() -> Result<String> {
get_property("ro.build.fingerprint", "unknown".to_string())
}
fn get_device_config<T>(key: &str, default_value: T) -> Result<T>
where
T: FromStr + ToString,
T::Err: Error + Send + Sync + 'static,
{
let default_value = default_value.to_string();
let config =
flags_rust::GetServerConfigurableFlag(PROFCOLLECT_CONFIG_NAMESPACE, key, &default_value);
Ok(T::from_str(&config)?)
}
pub fn get_sampling_period() -> Duration {
let default_period = 1500;
Duration::from_millis(
get_device_config("sampling_period", default_period).unwrap_or(default_period),
)
}
fn get_property<T>(key: &str, default_value: T) -> Result<T>
where
T: FromStr + ToString,
T::Err: Error + Send + Sync + 'static,
{
let default_value = default_value.to_string();
let value = rustutils::system_properties::read(key).unwrap_or(None).unwrap_or(default_value);
Ok(T::from_str(&value)?)
}
fn set_property<T>(key: &str, value: T) -> Result<()>
where
T: ToString,
{
let value = value.to_string();
Ok(rustutils::system_properties::write(key, &value)?)
}
fn generate_random_node_id() -> MacAddr6 {
let mut node_id = rand::thread_rng().gen::<[u8; 6]>();
node_id[0] |= 0x1;
MacAddr6::from(node_id)
}
fn get_kernel_release() -> String {
match Command::new("uname").args(["-r"]).output() {
Ok(output) if output.status.success() => {
String::from_utf8_lossy(&output.stdout).trim().to_string()
}
_ => String::new(),
}
}
pub fn clear_data() -> Result<()> {
fn remove_files(path: &Path) -> Result<()> {
read_dir(path)?
.filter_map(|e| e.ok())
.map(|e| e.path())
.filter(|e| e.is_file() && e != *LOG_FILE)
.try_for_each(remove_file)?;
Ok(())
}
remove_files(&TRACE_OUTPUT_DIR)?;
remove_files(&PROFILE_OUTPUT_DIR)?;
remove_files(&REPORT_OUTPUT_DIR)?;
Ok(())
}
pub fn clear_processed_files() -> Result<()> {
read_dir(&PROFILE_OUTPUT_DIR as &Path)?
.filter_map(|e| e.ok())
.map(|e| e.path())
.filter(|e| e.is_file() && e != (&CONFIG_FILE as &Path))
.try_for_each(remove_file)?;
Ok(())
}